mirror of
https://github.com/chatmail/core.git
synced 2026-04-02 21:42:10 +03:00
Compare commits
1 Commits
fix_export
...
fix/setupc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
601b284ed8 |
@@ -4,9 +4,6 @@ executors:
|
||||
docker:
|
||||
- image: filecoin/rust:latest
|
||||
working_directory: /mnt/crate
|
||||
doxygen:
|
||||
docker:
|
||||
- image: hrektts/doxygen
|
||||
|
||||
restore-workspace: &restore-workspace
|
||||
attach_workspace:
|
||||
@@ -15,7 +12,7 @@ restore-workspace: &restore-workspace
|
||||
restore-cache: &restore-cache
|
||||
restore_cache:
|
||||
keys:
|
||||
- cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||
- cargo-v1-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||
- repo-source-{{ .Branch }}-{{ .Revision }}
|
||||
|
||||
commands:
|
||||
@@ -26,9 +23,20 @@ commands:
|
||||
steps:
|
||||
- *restore-workspace
|
||||
- *restore-cache
|
||||
- setup_remote_docker:
|
||||
docker_layer_caching: true
|
||||
# TODO: move into image
|
||||
- run:
|
||||
name: Install Docker client
|
||||
command: |
|
||||
set -x
|
||||
VER="18.09.2"
|
||||
curl -L -o /tmp/docker-$VER.tgz https://download.docker.com/linux/static/stable/x86_64/docker-$VER.tgz
|
||||
tar -xz -C /tmp -f /tmp/docker-$VER.tgz
|
||||
mv /tmp/docker/* /usr/bin
|
||||
- run:
|
||||
name: Test (<< parameters.target >>)
|
||||
command: TARGET=<< parameters.target >> ci_scripts/run-rust-test.sh
|
||||
command: TARGET=<< parameters.target >> ci/run.sh
|
||||
no_output_timeout: 15m
|
||||
|
||||
jobs:
|
||||
@@ -44,7 +52,7 @@ jobs:
|
||||
command: cargo generate-lockfile
|
||||
- restore_cache:
|
||||
keys:
|
||||
- cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||
- cargo-v1-{{ 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
|
||||
@@ -53,14 +61,13 @@ jobs:
|
||||
- run: cargo fetch
|
||||
- run: rustc +stable --version
|
||||
- run: rustc +$(cat rust-toolchain) --version
|
||||
# make sure this git repo doesn't grow too big
|
||||
- run: git gc
|
||||
- run: rm -rf .git
|
||||
- persist_to_workspace:
|
||||
root: /mnt
|
||||
paths:
|
||||
- crate
|
||||
- save_cache:
|
||||
key: cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||
key: cargo-v1-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||
paths:
|
||||
- "~/.cargo"
|
||||
- "~/.rustup"
|
||||
@@ -95,7 +102,7 @@ jobs:
|
||||
- run: cargo fetch
|
||||
- run:
|
||||
name: Test
|
||||
command: TARGET=x86_64-apple-darwin ci_scripts/run-rust-test.sh
|
||||
command: TARGET=x86_64-apple-darwin ci/run.sh
|
||||
|
||||
test_x86_64-unknown-linux-gnu:
|
||||
executor: default
|
||||
@@ -116,31 +123,19 @@ jobs:
|
||||
target: "aarch64-linux-android"
|
||||
|
||||
|
||||
build_doxygen:
|
||||
executor: doxygen
|
||||
build_test_docs_wheel:
|
||||
machine: True
|
||||
steps:
|
||||
- checkout
|
||||
- run: bash ci_scripts/run-doxygen.sh
|
||||
- run: mkdir -p workspace/c-docs
|
||||
- run: cp -av deltachat-ffi/{html,xml} workspace/c-docs/
|
||||
- persist_to_workspace:
|
||||
root: workspace
|
||||
paths:
|
||||
- c-docs
|
||||
|
||||
build_test_docs_wheel:
|
||||
docker:
|
||||
- image: deltachat/coredeps
|
||||
environment:
|
||||
TESTS: 1
|
||||
DOCS: 1
|
||||
working_directory: /mnt/crate
|
||||
steps:
|
||||
- *restore-workspace
|
||||
- *restore-cache
|
||||
# - run: docker pull deltachat/doxygen
|
||||
- run: docker pull deltachat/coredeps
|
||||
- run:
|
||||
name: build docs, run tests and build wheels
|
||||
command: ci_scripts/run-python.sh
|
||||
command: ci_scripts/ci_run.sh
|
||||
environment:
|
||||
TESTS: 1
|
||||
DOCS: 1
|
||||
|
||||
- run:
|
||||
name: copying docs and wheels to workspace
|
||||
command: |
|
||||
@@ -148,6 +143,7 @@ jobs:
|
||||
# cp -av docs workspace/c-docs
|
||||
cp -av python/.docker-tox/wheelhouse workspace/
|
||||
cp -av python/doc/_build/ workspace/py-docs
|
||||
|
||||
- persist_to_workspace:
|
||||
root: workspace
|
||||
paths:
|
||||
@@ -163,7 +159,7 @@ jobs:
|
||||
at: workspace
|
||||
- run: pyenv global 3.5.2
|
||||
- run: ls -laR workspace
|
||||
- run: ci_scripts/ci_upload.sh workspace/py-docs workspace/wheelhouse workspace/c-docs
|
||||
- run: ci_scripts/ci_upload.sh workspace/py-docs workspace/wheelhouse
|
||||
|
||||
clippy:
|
||||
executor: default
|
||||
@@ -180,22 +176,18 @@ workflows:
|
||||
|
||||
test:
|
||||
jobs:
|
||||
- cargo_fetch
|
||||
- build_doxygen
|
||||
|
||||
- build_test_docs_wheel:
|
||||
requires:
|
||||
- cargo_fetch
|
||||
- build_test_docs_wheel
|
||||
- upload_docs_wheels:
|
||||
requires:
|
||||
- build_test_docs_wheel
|
||||
- build_doxygen
|
||||
- cargo_fetch
|
||||
- rustfmt:
|
||||
requires:
|
||||
- cargo_fetch
|
||||
|
||||
- clippy:
|
||||
requires:
|
||||
- cargo_fetch
|
||||
- cargo_fetch
|
||||
|
||||
# Linux Desktop 64bit
|
||||
- test_x86_64-unknown-linux-gnu:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -16,7 +16,6 @@ python/.tox
|
||||
*.egg-info
|
||||
__pycache__
|
||||
python/src/deltachat/capi*.so
|
||||
python/.venv/
|
||||
|
||||
python/liveconfig*
|
||||
|
||||
|
||||
88
CHANGELOG.md
88
CHANGELOG.md
@@ -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
|
||||
|
||||
859
Cargo.lock
generated
859
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
20
Cargo.toml
20
Cargo.toml
@@ -1,26 +1,26 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.0.0-beta.7"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
version = "1.0.0-alpha.4"
|
||||
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
|
||||
edition = "2018"
|
||||
license = "MPL"
|
||||
|
||||
[dependencies]
|
||||
deltachat_derive = { path = "./deltachat_derive" }
|
||||
mmime = { version = "0.1.2", path = "./mmime" }
|
||||
|
||||
libc = "0.2.51"
|
||||
pgp = { version = "0.2.3", default-features = false }
|
||||
pgp = { version = "0.2", default-features = false }
|
||||
hex = "0.3.2"
|
||||
sha2 = "0.8.0"
|
||||
rand = "0.6.5"
|
||||
phf = { git = "https://github.com/sfackler/rust-phf", rev = "0d00821", features = ["macros"] }
|
||||
smallvec = "0.6.9"
|
||||
reqwest = "0.9.15"
|
||||
num-derive = "0.2.5"
|
||||
num-traits = "0.2.6"
|
||||
native-tls = "0.2.3"
|
||||
lettre = { git = "https://github.com/deltachat/lettre", branch = "master" }
|
||||
imap = { git = "https://github.com/deltachat/rust-imap", branch = "master" }
|
||||
lettre = "0.9.0"
|
||||
imap = { git = "https://github.com/jonhoo/rust-imap", rev = "281d2eb8ab50dc656ceff2ae749ca5045f334e15" }
|
||||
mmime = { git = "https://github.com/dignifiedquire/mmime", rev = "bccd2c2" }
|
||||
base64 = "0.10"
|
||||
charset = "0.1"
|
||||
percent-encoding = "2.0"
|
||||
@@ -36,8 +36,8 @@ regex = "1.1.6"
|
||||
rusqlite = { version = "0.20", features = ["bundled"] }
|
||||
r2d2_sqlite = "0.12.0"
|
||||
r2d2 = "0.8.5"
|
||||
strum = "0.16.0"
|
||||
strum_macros = "0.16.0"
|
||||
strum = "0.15.0"
|
||||
strum_macros = "0.15.0"
|
||||
thread-local-object = "0.1.0"
|
||||
backtrace = "0.3.33"
|
||||
byteorder = "1.3.1"
|
||||
@@ -48,7 +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"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.0"
|
||||
@@ -60,7 +59,6 @@ proptest = "0.9.4"
|
||||
members = [
|
||||
"deltachat-ffi",
|
||||
"deltachat_derive",
|
||||
"mmime",
|
||||
]
|
||||
|
||||
[[example]]
|
||||
|
||||
6
Xargo.toml
Normal file
6
Xargo.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[dependencies.std]
|
||||
features = ["panic-unwind"]
|
||||
|
||||
# if using `cargo test`
|
||||
[dependencies.test]
|
||||
stage = 1
|
||||
@@ -13,7 +13,7 @@ install:
|
||||
build: false
|
||||
|
||||
test_script:
|
||||
- cargo test --release --all
|
||||
- cargo test --release
|
||||
|
||||
cache:
|
||||
- target
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -31,7 +31,7 @@ fi
|
||||
if [[ $NORUN == "1" ]]; then
|
||||
export CARGO_SUBCMD="build"
|
||||
else
|
||||
export CARGO_SUBCMD="test --all"
|
||||
export CARGO_SUBCMD="test"
|
||||
export OPT="${OPT} "
|
||||
export OPT_RELEASE="${OPT_RELEASE} "
|
||||
export OPT_RELEASE_IGNORED="${OPT_RELEASE} -- --ignored"
|
||||
@@ -41,3 +41,6 @@ fi
|
||||
$CARGO_CMD $CARGO_SUBCMD $OPT
|
||||
$CARGO_CMD $CARGO_SUBCMD $OPT_RELEASE
|
||||
$CARGO_CMD $CARGO_SUBCMD $OPT_RELEASE_IGNORED
|
||||
|
||||
# Build the ffi lib
|
||||
$CARGO_CMD $CARGO_SUBCMD $OPT_FFI_RELEASE
|
||||
22
ci_scripts/ci_run.sh
Executable file
22
ci_scripts/ci_run.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
# perform CI jobs on PRs and after merges to master.
|
||||
# triggered from .circleci/config.yml
|
||||
|
||||
set -e -x
|
||||
|
||||
export BRANCH=${CIRCLE_BRANCH:-test7}
|
||||
|
||||
# run doxygen on c-source (needed by later doc-generation steps).
|
||||
# XXX modifies the host filesystem docs/xml and docs/html directories
|
||||
# XXX which you can then only remove with sudo as they belong to root
|
||||
|
||||
# XXX we don't do doxygen doc generation with Rust anymore, needs to be
|
||||
# substituted with rust-docs
|
||||
#if [ -n "$DOCS" ] ; then
|
||||
# docker run --rm -it -v $PWD:/mnt -w /mnt/docs deltachat/doxygen doxygen
|
||||
#fi
|
||||
|
||||
# run everything else inside docker (TESTS, DOCS, WHEELS)
|
||||
docker run -e DCC_PY_LIVECONFIG -e BRANCH -e TESTS -e DOCS \
|
||||
--rm -it -v $(pwd):/mnt -w /mnt \
|
||||
deltachat/coredeps ci_scripts/run_all.sh
|
||||
|
||||
@@ -7,9 +7,9 @@ fi
|
||||
|
||||
set -xe
|
||||
|
||||
#DOXYDOCDIR=${1:?directory where doxygen docs to be found}
|
||||
PYDOCDIR=${1:?directory with python docs}
|
||||
WHEELHOUSEDIR=${2:?directory with pre-built wheels}
|
||||
DOXYDOCDIR=${3:?directory where doxygen docs to be found}
|
||||
|
||||
export BRANCH=${CIRCLE_BRANCH:?specify branch for uploading purposes}
|
||||
|
||||
@@ -22,11 +22,10 @@ rsync -avz \
|
||||
delta@py.delta.chat:build/${BRANCH}
|
||||
|
||||
# C docs to c.delta.chat
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@c.delta.chat mkdir -p build-c/${BRANCH}
|
||||
rsync -avz \
|
||||
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
|
||||
"$DOXYDOCDIR/html/" \
|
||||
delta@c.delta.chat:build-c/${BRANCH}
|
||||
#rsync -avz \
|
||||
# -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
|
||||
# "$DOXYDOCDIR/html/" \
|
||||
# delta@py.delta.chat:build-c/${BRANCH}
|
||||
|
||||
echo -----------------------
|
||||
echo upload wheels
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
cd deltachat-ffi
|
||||
doxygen
|
||||
|
||||
@@ -36,25 +36,20 @@ if [ -n "$TESTS" ]; then
|
||||
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
|
||||
# run tox
|
||||
# XXX we don't run liveconfig tests because they hang sometimes
|
||||
# see https://github.com/deltachat/deltachat-core-rust/issues/331
|
||||
# unset DCC_PY_LIVECONFIG
|
||||
|
||||
tox --workdir "$TOXWORKDIR" -e lint,py35,py36,py37,auditwheels -- -k "not qr"
|
||||
tox --workdir "$TOXWORKDIR" -e py35,py36,py37 -- -k "qr"
|
||||
popd
|
||||
fi
|
||||
|
||||
|
||||
# if [ -n "$DOCS" ]; then
|
||||
# echo -----------------------
|
||||
# echo generating python docs
|
||||
# echo -----------------------
|
||||
# (cd python && tox --workdir "$TOXWORKDIR" -e doc)
|
||||
# fi
|
||||
if [ -n "$DOCS" ]; then
|
||||
echo -----------------------
|
||||
echo generating python docs
|
||||
echo -----------------------
|
||||
(cd python && tox --workdir "$TOXWORKDIR" -e doc)
|
||||
fi
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.0.0-beta.7"
|
||||
version = "1.0.0-alpha.4"
|
||||
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"
|
||||
@@ -16,7 +16,6 @@ crate-type = ["cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
deltachat = { path = "../", default-features = false }
|
||||
deltachat-provider-database = "0.2.1"
|
||||
libc = "0.2"
|
||||
human-panic = "1.0.1"
|
||||
num-traits = "0.2.6"
|
||||
|
||||
@@ -11,16 +11,6 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct _dc_context dc_context_t;
|
||||
typedef struct _dc_array dc_array_t;
|
||||
typedef struct _dc_chatlist dc_chatlist_t;
|
||||
typedef struct _dc_chat dc_chat_t;
|
||||
typedef struct _dc_msg dc_msg_t;
|
||||
typedef struct _dc_contact dc_contact_t;
|
||||
typedef struct _dc_lot dc_lot_t;
|
||||
typedef struct _dc_provider dc_provider_t;
|
||||
|
||||
|
||||
/**
|
||||
* @mainpage Getting started
|
||||
*
|
||||
@@ -78,7 +68,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 +125,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);
|
||||
@@ -198,6 +189,13 @@ typedef struct _dc_provider dc_provider_t;
|
||||
* SQLite database for offline functionality and for account-related
|
||||
* settings.
|
||||
*/
|
||||
typedef struct _dc_context dc_context_t;
|
||||
typedef struct _dc_array dc_array_t;
|
||||
typedef struct _dc_chatlist dc_chatlist_t;
|
||||
typedef struct _dc_chat dc_chat_t;
|
||||
typedef struct _dc_msg dc_msg_t;
|
||||
typedef struct _dc_contact dc_contact_t;
|
||||
typedef struct _dc_lot dc_lot_t;
|
||||
|
||||
|
||||
/**
|
||||
@@ -320,7 +318,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 +336,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,38 +394,20 @@ int dc_set_config (dc_context_t* context, const char*
|
||||
* @param context The context object as created by dc_context_new(). For querying system values, this can be NULL.
|
||||
* @param key The key to query.
|
||||
* @return Returns current value of "key", if "key" is unset, the default
|
||||
* value is returned. The returned value must be released using dc_str_unref(), NULL is never
|
||||
* value is returned. The returned value must be free()'d, NULL is never
|
||||
* returned. If there is an error an empty string will be returned.
|
||||
*/
|
||||
char* dc_get_config (dc_context_t* context, const char* key);
|
||||
|
||||
/**
|
||||
* Set stock string translation.
|
||||
*
|
||||
* The function will emit warnings if it returns an error state.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object
|
||||
* @param stock_id the integer id of the stock message (DC_STR_*)
|
||||
* @param stock_msg the message to be used
|
||||
* @return int (==0 on error, 1 on success)
|
||||
*/
|
||||
int dc_set_stock_translation(dc_context_t* context, uint32_t stock_id, const char* stock_msg);
|
||||
|
||||
|
||||
/**
|
||||
* Get information about the context.
|
||||
*
|
||||
* The information is returned by a multi-line string
|
||||
* and contains information about the current configuration.
|
||||
*
|
||||
* If the context is not open or configured only a subset of the information
|
||||
* will be available. There is no guarantee about which information will be
|
||||
* included when however.
|
||||
*
|
||||
* @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,12 +438,12 @@ 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);
|
||||
|
||||
|
||||
|
||||
// connect
|
||||
|
||||
/**
|
||||
@@ -630,8 +608,12 @@ void dc_interrupt_imap_idle (dc_context_t* context);
|
||||
|
||||
|
||||
/**
|
||||
* Execute pending mvbox-jobs.
|
||||
* This function and dc_perform_mvbox_fetch() and dc_perform_mvbox_idle()
|
||||
* Fetch new messages from the MVBOX, if any.
|
||||
* The MVBOX is a folder on the account where chat messages are moved to.
|
||||
* The moving is done to not disturb shared accounts that are used by both,
|
||||
* Delta Chat and a classical MUA.
|
||||
*
|
||||
* This function and dc_perform_mvbox_idle()
|
||||
* must be called from the same thread, typically in a loop.
|
||||
*
|
||||
* Example:
|
||||
@@ -639,7 +621,6 @@ void dc_interrupt_imap_idle (dc_context_t* context);
|
||||
* void* mvbox_thread_func(void* context)
|
||||
* {
|
||||
* while (true) {
|
||||
* dc_perform_mvbox_jobs(context);
|
||||
* dc_perform_mvbox_fetch(context);
|
||||
* dc_perform_mvbox_idle(context);
|
||||
* }
|
||||
@@ -653,26 +634,13 @@ void dc_interrupt_imap_idle (dc_context_t* context);
|
||||
*
|
||||
* // network becomes available again -
|
||||
* // the interrupt causes dc_perform_mvbox_idle() in the thread above
|
||||
* // to return so that jobs are executed and messages are fetched.
|
||||
* // to return so that and messages are fetched.
|
||||
* dc_maybe_network(context);
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context as created by dc_context_new().
|
||||
* @return None.
|
||||
*/
|
||||
void dc_perform_mvbox_jobs (dc_context_t* context);
|
||||
|
||||
|
||||
/**
|
||||
* Fetch new messages from the MVBOX, if any.
|
||||
* The MVBOX is a folder on the account where chat messages are moved to.
|
||||
* The moving is done to not disturb shared accounts that are used by both,
|
||||
* Delta Chat and a classical MUA.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context as created by dc_context_new().
|
||||
* @return None.
|
||||
*/
|
||||
void dc_perform_mvbox_fetch (dc_context_t* context);
|
||||
|
||||
|
||||
@@ -710,40 +678,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()
|
||||
* must be called from the same thread, typically in a loop.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* void* sentbox_thread_func(void* context)
|
||||
* {
|
||||
* while (true) {
|
||||
* dc_perform_sentbox_jobs(context);
|
||||
* dc_perform_sentbox_fetch(context);
|
||||
* dc_perform_sentbox_idle(context);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // start sentbox-thread that runs forever
|
||||
* pthread_t sentbox_thread;
|
||||
* pthread_create(&sentbox_thread, NULL, sentbox_thread_func, context);
|
||||
*
|
||||
* ... program runs ...
|
||||
*
|
||||
* // network becomes available again -
|
||||
* // the interrupt causes dc_perform_sentbox_idle() in the thread above
|
||||
* // to return so that jobs are executed and messages are fetched.
|
||||
* dc_maybe_network(context);
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context as created by dc_context_new().
|
||||
* @return None.
|
||||
*/
|
||||
void dc_perform_sentbox_jobs (dc_context_t* context);
|
||||
|
||||
|
||||
/**
|
||||
* Fetch new messages from the Sent folder, if any.
|
||||
* This function and dc_perform_sentbox_idle()
|
||||
@@ -1098,27 +1032,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.
|
||||
@@ -1234,7 +1147,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.
|
||||
@@ -1502,7 +1415,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);
|
||||
|
||||
@@ -1516,7 +1429,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.
|
||||
@@ -1536,16 +1449,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.
|
||||
@@ -1767,7 +1670,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);
|
||||
|
||||
@@ -1903,8 +1806,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);
|
||||
@@ -1952,7 +1854,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);
|
||||
@@ -2057,7 +1959,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);
|
||||
|
||||
@@ -2227,21 +2129,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
|
||||
*
|
||||
@@ -2382,7 +2269,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);
|
||||
|
||||
@@ -2633,22 +2520,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);
|
||||
|
||||
@@ -2664,7 +2553,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);
|
||||
|
||||
@@ -2736,39 +2625,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
|
||||
@@ -3001,7 +2857,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);
|
||||
|
||||
@@ -3015,9 +2871,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);
|
||||
|
||||
@@ -3030,7 +2886,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);
|
||||
|
||||
@@ -3042,8 +2898,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);
|
||||
|
||||
@@ -3151,8 +3006,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);
|
||||
@@ -3297,7 +3151,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);
|
||||
|
||||
@@ -3424,8 +3278,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
|
||||
|
||||
|
||||
@@ -3455,8 +3308,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);
|
||||
|
||||
@@ -3470,8 +3322,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);
|
||||
|
||||
@@ -3485,8 +3336,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);
|
||||
|
||||
@@ -3502,8 +3352,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);
|
||||
|
||||
@@ -3515,8 +3364,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);
|
||||
|
||||
@@ -3530,7 +3378,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);
|
||||
|
||||
@@ -3571,110 +3419,6 @@ int dc_contact_is_blocked (const dc_contact_t* contact);
|
||||
int dc_contact_is_verified (dc_contact_t* contact);
|
||||
|
||||
|
||||
/**
|
||||
* @class dc_provider_t
|
||||
*
|
||||
* Opaque object containing information about one single email provider.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Create a provider struct for the given domain.
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param domain The domain to get provider info for.
|
||||
* @return a dc_provider_t struct which can be used with the dc_provider_get_*
|
||||
* accessor functions. If no provider info is found, NULL will be
|
||||
* returned.
|
||||
*/
|
||||
dc_provider_t* dc_provider_new_from_domain (const char* domain);
|
||||
|
||||
|
||||
/**
|
||||
* Create a provider struct for the given email address.
|
||||
*
|
||||
* The provider is extracted from the email address and it's information is returned.
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param email The user's email address to extract the provider info form.
|
||||
* @return a dc_provider_t struct which can be used with the dc_provider_get_*
|
||||
* accessor functions. If no provider info is found, NULL will be
|
||||
* returned.
|
||||
*/
|
||||
dc_provider_t* dc_provider_new_from_email (const char* email);
|
||||
|
||||
|
||||
/**
|
||||
* URL of the overview page.
|
||||
*
|
||||
* This URL allows linking to the providers page on providers.delta.chat.
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param provider The dc_provider_t struct.
|
||||
* @return A string which must be released using dc_str_unref().
|
||||
*/
|
||||
char* dc_provider_get_overview_page (const dc_provider_t* provider);
|
||||
|
||||
|
||||
/**
|
||||
* The provider's name.
|
||||
*
|
||||
* The name of the provider, e.g. "POSTEO".
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param provider The dc_provider_t struct.
|
||||
* @return A string which must be released using dc_str_unref().
|
||||
*/
|
||||
char* dc_provider_get_name (const dc_provider_t* provider);
|
||||
|
||||
|
||||
/**
|
||||
* The markdown content of the providers page.
|
||||
*
|
||||
* This contains the preparation steps or additional information if the status
|
||||
* is @ref DC_PROVIDER_STATUS_BROKEN.
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param provider The dc_provider_t struct.
|
||||
* @return A string which must be released using dc_str_unref().
|
||||
*/
|
||||
char* dc_provider_get_markdown (const dc_provider_t* provider);
|
||||
|
||||
|
||||
/**
|
||||
* Date of when the state was last checked/updated.
|
||||
*
|
||||
* This is returned as a string.
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param provider The dc_provider_t struct.
|
||||
* @return A string which must be released using dc_str_unref().
|
||||
*/
|
||||
char* dc_provider_get_status_date (const dc_provider_t* provider);
|
||||
|
||||
|
||||
/**
|
||||
* Whether DC works with this provider.
|
||||
*
|
||||
* Can be one of @ref DC_PROVIDER_STATUS_OK, @ref
|
||||
* DC_PROVIDER_STATUS_PREPARATION and @ref DC_PROVIDER_STATUS_BROKEN.
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param provider The dc_provider_t struct.
|
||||
* @return The status as a constant number.
|
||||
*/
|
||||
int dc_provider_get_status (const dc_provider_t* provider);
|
||||
|
||||
|
||||
/**
|
||||
* Free the provider info struct.
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param provider The dc_provider_t struct.
|
||||
*/
|
||||
void dc_provider_unref (const dc_provider_t* provider);
|
||||
|
||||
|
||||
/**
|
||||
* @class dc_lot_t
|
||||
*
|
||||
@@ -3710,9 +3454,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);
|
||||
|
||||
@@ -3721,10 +3463,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);
|
||||
|
||||
@@ -3745,7 +3487,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);
|
||||
@@ -3767,7 +3511,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);
|
||||
@@ -3819,14 +3565,6 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
#define DC_MSG_GIF 21
|
||||
|
||||
|
||||
/**
|
||||
* Message containing a sticker, similar to image.
|
||||
* If possible, the ui should display the image without borders in a transparent way.
|
||||
* A click on a sticker will offer to install the sticker set in some future.
|
||||
*/
|
||||
#define DC_MSG_STICKER 23
|
||||
|
||||
|
||||
/**
|
||||
* Message containing an Audio file.
|
||||
* File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
|
||||
@@ -3944,67 +3682,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
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
@@ -4019,6 +3696,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().
|
||||
@@ -4027,7 +3705,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
|
||||
@@ -4038,7 +3716,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
|
||||
@@ -4049,7 +3727,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
|
||||
@@ -4059,60 +3737,11 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) Info string in english language.
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* Must not be free()'d or modified and is valid only until the callback returns.
|
||||
* @return 0
|
||||
*/
|
||||
#define DC_EVENT_SMTP_MESSAGE_SENT 103
|
||||
|
||||
/**
|
||||
* Emitted when a message was successfully marked as deleted on the IMAP server.
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) Info string in english language.
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @return 0
|
||||
*/
|
||||
#define DC_EVENT_IMAP_MESSAGE_DELETED 104
|
||||
|
||||
/**
|
||||
* Emitted when a message was successfully moved on IMAP.
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) Info string in english language.
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @return 0
|
||||
*/
|
||||
#define DC_EVENT_IMAP_MESSAGE_MOVED 105
|
||||
|
||||
/**
|
||||
* Emitted when 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.
|
||||
* @return 0
|
||||
*/
|
||||
#define DC_EVENT_NEW_BLOB_FILE 150
|
||||
|
||||
/**
|
||||
* Emitted when a blob file was successfully deleted
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) path name
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @return 0
|
||||
*/
|
||||
#define DC_EVENT_DELETED_BLOB_FILE 151
|
||||
|
||||
/**
|
||||
* The library-user should write a warning string to the log.
|
||||
@@ -4122,7 +3751,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
|
||||
@@ -4142,10 +3771,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
|
||||
@@ -4169,7 +3797,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
|
||||
@@ -4184,7 +3812,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
|
||||
*/
|
||||
@@ -4315,7 +3943,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
|
||||
*/
|
||||
@@ -4356,21 +3984,36 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
#define DC_EVENT_SECUREJOIN_JOINER_PROGRESS 2061
|
||||
|
||||
|
||||
// the following events are functions that should be provided by the frontends
|
||||
|
||||
|
||||
/**
|
||||
* Requeste a localized string from the frontend.
|
||||
*
|
||||
* @param data1 (int) ID of the string to request, one of the DC_STR_* constants.
|
||||
* @param data2 (int) The count. If the requested string contains a placeholder for a numeric value,
|
||||
* the ui may use this value to return different strings on different plural forms.
|
||||
* @return (const char*) Null-terminated UTF-8 string.
|
||||
* The string will be free()'d by the core,
|
||||
* so it must be allocated using malloc() or a compatible function.
|
||||
* Return 0 if the ui cannot provide the requested string
|
||||
* the core will use a default string in english language then.
|
||||
*/
|
||||
#define DC_EVENT_GET_STRING 2091
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
|
||||
#define DC_EVENT_FILE_COPIED 2055 // not used anymore
|
||||
#define DC_EVENT_IS_OFFLINE 2081 // not used anymore
|
||||
#define DC_EVENT_GET_STRING 2091 // not used anymore, use dc_set_stock_translation()
|
||||
#define DC_ERROR_SEE_STRING 0 // not used anymore
|
||||
#define DC_ERROR_SELF_NOT_IN_GROUP 1 // not used anymore
|
||||
#define DC_STR_SELFNOTINGRP 21 // not used anymore
|
||||
#define DC_EVENT_FILE_COPIED 2055 // deprecated
|
||||
#define DC_EVENT_IS_OFFLINE 2081 // deprecated
|
||||
#define DC_ERROR_SEE_STRING 0 // deprecated
|
||||
#define DC_ERROR_SELF_NOT_IN_GROUP 1 // deprecated
|
||||
#define DC_STR_SELFNOTINGRP 21 // deprecated
|
||||
#define DC_EVENT_DATA1_IS_STRING(e) ((e)==DC_EVENT_IMEX_FILE_WRITTEN || (e)==DC_EVENT_FILE_COPIED)
|
||||
#define DC_EVENT_DATA2_IS_STRING(e) ((e)>=100 && (e)<=499)
|
||||
#define DC_EVENT_RETURNS_INT(e) ((e)==DC_EVENT_IS_OFFLINE) // not used anymore
|
||||
#define DC_EVENT_RETURNS_STRING(e) ((e)==DC_EVENT_GET_STRING) // not used anymore
|
||||
#define DC_EVENT_RETURNS_INT(e) ((e)==DC_EVENT_IS_OFFLINE)
|
||||
#define DC_EVENT_RETURNS_STRING(e) ((e)==DC_EVENT_GET_STRING)
|
||||
char* dc_get_version_str (void); // deprecated
|
||||
void dc_array_add_id (dc_array_t*, uint32_t); // deprecated
|
||||
|
||||
@@ -4383,41 +4026,6 @@ void dc_array_add_id (dc_array_t*, uint32_t); // depreca
|
||||
#define DC_SHOW_EMAILS_ALL 2
|
||||
|
||||
|
||||
/**
|
||||
* @defgroup DC_PROVIDER_STATUS DC_PROVIDER_STATUS
|
||||
*
|
||||
* These constants are used as return values for dc_provider_get_status().
|
||||
*
|
||||
* @addtogroup DC_PROVIDER_STATUS
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provider status returned by dc_provider_get_status().
|
||||
*
|
||||
* Works right out of the box without any preperation steps needed
|
||||
*/
|
||||
#define DC_PROVIDER_STATUS_OK 1
|
||||
|
||||
/**
|
||||
* Provider status returned by dc_provider_get_status().
|
||||
*
|
||||
* Works, but preparation steps are needed
|
||||
*/
|
||||
#define DC_PROVIDER_STATUS_PREPARATION 2
|
||||
|
||||
/**
|
||||
* Provider status returned by dc_provider_get_status().
|
||||
*
|
||||
* Doesn't work (too unstable to use falls also in this category)
|
||||
*/
|
||||
#define DC_PROVIDER_STATUS_BROKEN 3
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* TODO: Strings need some doumentation about used placeholders.
|
||||
*
|
||||
@@ -4472,8 +4080,10 @@ void dc_array_add_id (dc_array_t*, uint32_t); // depreca
|
||||
#define DC_STR_MSGLOCATIONENABLED 64
|
||||
#define DC_STR_MSGLOCATIONDISABLED 65
|
||||
#define DC_STR_LOCATION 66
|
||||
#define DC_STR_STICKER 67
|
||||
#define DC_STR_COUNT 67
|
||||
#define DC_STR_COUNT 66
|
||||
|
||||
void dc_str_unref (char*);
|
||||
|
||||
|
||||
/*
|
||||
* @}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,93 +0,0 @@
|
||||
extern crate deltachat_provider_database;
|
||||
|
||||
use std::ptr;
|
||||
|
||||
use deltachat::dc_tools::{to_string_lossy, StrExt};
|
||||
use deltachat_provider_database::StatusState;
|
||||
|
||||
#[no_mangle]
|
||||
pub type dc_provider_t = deltachat_provider_database::Provider;
|
||||
|
||||
#[no_mangle]
|
||||
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)) {
|
||||
Some(provider) => provider,
|
||||
None => ptr::null(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
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);
|
||||
match deltachat_provider_database::get_provider_info(domain) {
|
||||
Some(provider) => provider,
|
||||
None => ptr::null(),
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! null_guard {
|
||||
($context:tt) => {
|
||||
if $context.is_null() {
|
||||
return ptr::null_mut() as *mut libc::c_char;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_get_overview_page(
|
||||
provider: *const dc_provider_t,
|
||||
) -> *mut libc::c_char {
|
||||
null_guard!(provider);
|
||||
format!(
|
||||
"{}/{}",
|
||||
deltachat_provider_database::PROVIDER_OVERVIEW_URL,
|
||||
(*provider).overview_page
|
||||
)
|
||||
.strdup()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_get_name(provider: *const dc_provider_t) -> *mut libc::c_char {
|
||||
null_guard!(provider);
|
||||
(*provider).name.strdup()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_get_markdown(
|
||||
provider: *const dc_provider_t,
|
||||
) -> *mut libc::c_char {
|
||||
null_guard!(provider);
|
||||
(*provider).markdown.strdup()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_get_status_date(
|
||||
provider: *const dc_provider_t,
|
||||
) -> *mut libc::c_char {
|
||||
null_guard!(provider);
|
||||
(*provider).status.date.strdup()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_get_status(provider: *const dc_provider_t) -> u32 {
|
||||
if provider.is_null() {
|
||||
return 0;
|
||||
}
|
||||
match (*provider).status.state {
|
||||
StatusState::OK => 1,
|
||||
StatusState::PREPARATION => 2,
|
||||
StatusState::BROKEN => 3,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_unref(_provider: *const dc_provider_t) {
|
||||
()
|
||||
}
|
||||
|
||||
// TODO expose general provider overview url?
|
||||
@@ -1,25 +1,27 @@
|
||||
use std::path::Path;
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
use std::str::FromStr;
|
||||
|
||||
use deltachat::chat::{self, Chat};
|
||||
use deltachat::chatlist::*;
|
||||
use deltachat::config;
|
||||
use deltachat::configure::*;
|
||||
use deltachat::constants::*;
|
||||
use deltachat::contact::*;
|
||||
use deltachat::context::*;
|
||||
use deltachat::dc_imex::*;
|
||||
use deltachat::dc_receive_imf::*;
|
||||
use deltachat::dc_tools::*;
|
||||
use deltachat::error::Error;
|
||||
use deltachat::imex::*;
|
||||
use deltachat::job::*;
|
||||
use deltachat::location;
|
||||
use deltachat::lot::LotState;
|
||||
use deltachat::message::{self, Message, MessageState, MsgId};
|
||||
use deltachat::message::*;
|
||||
use deltachat::peerstate::*;
|
||||
use deltachat::qr::*;
|
||||
use deltachat::sql;
|
||||
use deltachat::x::*;
|
||||
use deltachat::Event;
|
||||
use libc::free;
|
||||
|
||||
/// Reset database tables. This function is called from Core cmdline.
|
||||
/// Argument is a bitmask, executing single or multiple actions in one call.
|
||||
@@ -86,152 +88,189 @@ 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
|
||||
}
|
||||
|
||||
fn dc_poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<(), Error> {
|
||||
let data = dc_read_file(context, filename)?;
|
||||
unsafe fn dc_poke_eml_file(context: &Context, filename: *const libc::c_char) -> libc::c_int {
|
||||
/* mainly for testing, may be called by dc_import_spec() */
|
||||
let mut success: libc::c_int = 0i32;
|
||||
let mut data: *mut libc::c_char = ptr::null_mut();
|
||||
let mut data_bytes = 0;
|
||||
if !(dc_read_file(
|
||||
context,
|
||||
filename,
|
||||
&mut data as *mut *mut libc::c_char as *mut *mut libc::c_void,
|
||||
&mut data_bytes,
|
||||
) == 0i32)
|
||||
{
|
||||
dc_receive_imf(context, data, data_bytes, "import", 0, 0);
|
||||
success = 1;
|
||||
}
|
||||
free(data as *mut libc::c_void);
|
||||
|
||||
unsafe { dc_receive_imf(context, &data, "import", 0, 0) };
|
||||
Ok(())
|
||||
success
|
||||
}
|
||||
|
||||
/// Import a file to the database.
|
||||
/// For testing, import a folder with eml-files, a single eml-file, e-mail plus public key and so on.
|
||||
/// For normal importing, use imex().
|
||||
/// For normal importing, use dc_imex().
|
||||
///
|
||||
/// @private @memberof Context
|
||||
/// @param context The context as created by dc_context_new().
|
||||
/// @param spec The file or directory to import. NULL for the last command.
|
||||
/// @return 1=success, 0=error.
|
||||
fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int {
|
||||
unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int {
|
||||
if !context.sql.is_open() {
|
||||
error!(context, "Import: Database not opened.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
let real_spec: String;
|
||||
let mut read_cnt = 0;
|
||||
let ok_to_continue;
|
||||
let mut success: libc::c_int = 0;
|
||||
let real_spec: *mut libc::c_char;
|
||||
let mut suffix: *mut libc::c_char = ptr::null_mut();
|
||||
let mut read_cnt: libc::c_int = 0;
|
||||
|
||||
/* if `spec` is given, remember it for later usage; if it is not given, try to use the last one */
|
||||
if !spec.is_null() {
|
||||
real_spec = to_string_lossy(spec);
|
||||
real_spec = dc_strdup(spec);
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(context, "import_spec", Some(&real_spec))
|
||||
.set_config(context, "import_spec", Some(as_str(real_spec)))
|
||||
.unwrap();
|
||||
ok_to_continue = true;
|
||||
} else {
|
||||
let rs = context.sql.get_raw_config(context, "import_spec");
|
||||
let rs = context.sql.get_config(context, "import_spec");
|
||||
if rs.is_none() {
|
||||
error!(context, "Import: No file or folder given.");
|
||||
return 0;
|
||||
ok_to_continue = false;
|
||||
} else {
|
||||
ok_to_continue = true;
|
||||
}
|
||||
real_spec = rs.unwrap();
|
||||
real_spec = rs.unwrap_or_default().strdup();
|
||||
}
|
||||
if let Some(suffix) = dc_get_filesuffix_lc(&real_spec) {
|
||||
if suffix == "eml" {
|
||||
if dc_poke_eml_file(context, &real_spec).is_ok() {
|
||||
if ok_to_continue {
|
||||
let ok_to_continue2;
|
||||
suffix = dc_get_filesuffix_lc(as_str(real_spec));
|
||||
if !suffix.is_null() && strcmp(suffix, b"eml\x00" as *const u8 as *const libc::c_char) == 0
|
||||
{
|
||||
if 0 != dc_poke_eml_file(context, real_spec) {
|
||||
read_cnt += 1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* import a directory */
|
||||
let dir_name = std::path::Path::new(&real_spec);
|
||||
let dir = std::fs::read_dir(dir_name);
|
||||
if dir.is_err() {
|
||||
error!(context, "Import: Cannot open directory \"{}\".", &real_spec,);
|
||||
return 0;
|
||||
ok_to_continue2 = true;
|
||||
} else {
|
||||
let dir = dir.unwrap();
|
||||
for entry in dir {
|
||||
if entry.is_err() {
|
||||
break;
|
||||
}
|
||||
let entry = entry.unwrap();
|
||||
let name_f = entry.file_name();
|
||||
let name = name_f.to_string_lossy();
|
||||
if name.ends_with(".eml") {
|
||||
let path_plus_name = format!("{}/{}", &real_spec, name);
|
||||
info!(context, "Import: {}", path_plus_name);
|
||||
if dc_poke_eml_file(context, path_plus_name).is_ok() {
|
||||
read_cnt += 1
|
||||
/* import a directory */
|
||||
let dir_name = std::path::Path::new(as_str(real_spec));
|
||||
let dir = std::fs::read_dir(dir_name);
|
||||
if dir.is_err() {
|
||||
error!(
|
||||
context,
|
||||
"Import: Cannot open directory \"{}\".",
|
||||
as_str(real_spec),
|
||||
);
|
||||
ok_to_continue2 = false;
|
||||
} else {
|
||||
let dir = dir.unwrap();
|
||||
for entry in dir {
|
||||
if entry.is_err() {
|
||||
break;
|
||||
}
|
||||
let entry = entry.unwrap();
|
||||
let name_f = entry.file_name();
|
||||
let name = name_f.to_string_lossy();
|
||||
if name.ends_with(".eml") {
|
||||
let path_plus_name = format!("{}/{}", as_str(real_spec), name);
|
||||
info!(context, "Import: {}", path_plus_name);
|
||||
let path_plus_name_c = CString::yolo(path_plus_name);
|
||||
if 0 != dc_poke_eml_file(context, path_plus_name_c.as_ptr()) {
|
||||
read_cnt += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
ok_to_continue2 = true;
|
||||
}
|
||||
}
|
||||
if ok_to_continue2 {
|
||||
info!(
|
||||
context,
|
||||
"Import: {} items read from \"{}\".",
|
||||
read_cnt,
|
||||
as_str(real_spec)
|
||||
);
|
||||
if read_cnt > 0 {
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id: 0,
|
||||
msg_id: 0,
|
||||
});
|
||||
}
|
||||
success = 1
|
||||
}
|
||||
}
|
||||
info!(
|
||||
context,
|
||||
"Import: {} items read from \"{}\".", read_cnt, &real_spec
|
||||
);
|
||||
if read_cnt > 0 {
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id: 0,
|
||||
msg_id: MsgId::new(0),
|
||||
});
|
||||
}
|
||||
1
|
||||
|
||||
free(real_spec as *mut libc::c_void);
|
||||
free(suffix as *mut libc::c_void);
|
||||
success
|
||||
}
|
||||
|
||||
unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
let contact = Contact::get_by_id(context, msg.get_from_id()).expect("invalid contact");
|
||||
let contact = Contact::get_by_id(context, dc_msg_get_from_id(msg)).expect("invalid contact");
|
||||
let contact_name = contact.get_name();
|
||||
let contact_id = contact.get_id();
|
||||
|
||||
let statestr = match msg.get_state() {
|
||||
let statestr = match dc_msg_get_state(msg) {
|
||||
MessageState::OutPending => " o",
|
||||
MessageState::OutDelivered => " √",
|
||||
MessageState::OutMdnRcvd => " √√",
|
||||
MessageState::OutFailed => " !!",
|
||||
_ => "",
|
||||
};
|
||||
let temp2 = dc_timestamp_to_str(msg.get_timestamp());
|
||||
let msgtext = msg.get_text();
|
||||
let temp2 = dc_timestamp_to_str(dc_msg_get_timestamp(msg));
|
||||
let msgtext = dc_msg_get_text(msg);
|
||||
info!(
|
||||
context,
|
||||
"{}#{}{}{}: {} (Contact#{}): {} {}{}{}{}{} [{}]",
|
||||
"{}#{}{}{}: {} (Contact#{}): {} {}{}{}{} [{}]",
|
||||
prefix.as_ref(),
|
||||
msg.get_id(),
|
||||
if msg.get_showpadlock() { "🔒" } else { "" },
|
||||
if msg.has_location() { "📍" } else { "" },
|
||||
dc_msg_get_id(msg) as libc::c_int,
|
||||
if dc_msg_get_showpadlock(msg) {
|
||||
"🔒"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
if dc_msg_has_location(msg) { "📍" } else { "" },
|
||||
&contact_name,
|
||||
contact_id,
|
||||
msgtext.unwrap_or_default(),
|
||||
if msg.is_starred() { "★" } else { "" },
|
||||
if msg.get_from_id() == 1 as libc::c_uint {
|
||||
as_str(msgtext),
|
||||
if dc_msg_is_starred(msg) { "★" } else { "" },
|
||||
if dc_msg_get_from_id(msg) == 1 as libc::c_uint {
|
||||
""
|
||||
} else if msg.get_state() == MessageState::InSeen {
|
||||
} else if dc_msg_get_state(msg) == MessageState::InSeen {
|
||||
"[SEEN]"
|
||||
} else if msg.get_state() == MessageState::InNoticed {
|
||||
} else if dc_msg_get_state(msg) == MessageState::InNoticed {
|
||||
"[NOTICED]"
|
||||
} else {
|
||||
"[FRESH]"
|
||||
},
|
||||
if msg.is_info() { "[INFO]" } else { "" },
|
||||
if msg.is_forwarded() {
|
||||
"[FORWARDED]"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
if dc_msg_is_info(msg) { "[INFO]" } else { "" },
|
||||
statestr,
|
||||
&temp2,
|
||||
);
|
||||
free(msgtext as *mut libc::c_void);
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -239,7 +278,7 @@ unsafe fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Err
|
||||
);
|
||||
lines_out += 1
|
||||
}
|
||||
let msg = Message::load_from_db(context, msg_id)?;
|
||||
let msg = dc_get_msg(context, msg_id)?;
|
||||
log_msg(context, "Msg", &msg);
|
||||
}
|
||||
}
|
||||
@@ -321,8 +360,12 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
arg1.strdup() as *const _
|
||||
};
|
||||
let arg2 = args.next().unwrap_or_default();
|
||||
let arg2_c = if arg2.is_empty() {
|
||||
std::ptr::null()
|
||||
} else {
|
||||
arg2.strdup() as *const _
|
||||
};
|
||||
|
||||
let blobdir = context.get_blobdir();
|
||||
match arg0 {
|
||||
"help" | "?" => match arg1 {
|
||||
// TODO: reuse commands definition in main.rs.
|
||||
@@ -353,7 +396,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 +421,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,30 +447,35 @@ 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\
|
||||
============================================="
|
||||
),
|
||||
},
|
||||
"initiate-key-transfer" => match initiate_key_transfer(context) {
|
||||
Ok(setup_code) => println!(
|
||||
"Setup code for the transferred setup message: {}",
|
||||
setup_code,
|
||||
),
|
||||
Err(err) => bail!("Failed to generate setup code: {}", err),
|
||||
},
|
||||
"initiate-key-transfer" => {
|
||||
let setup_code = dc_initiate_key_transfer(context);
|
||||
if !setup_code.is_null() {
|
||||
println!(
|
||||
"Setup code for the transferred setup message: {}",
|
||||
as_str(setup_code),
|
||||
);
|
||||
free(setup_code as *mut libc::c_void);
|
||||
} else {
|
||||
bail!("Failed to generate setup code");
|
||||
};
|
||||
}
|
||||
"get-setupcodebegin" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
||||
let msg_id: MsgId = MsgId::new(arg1.parse()?);
|
||||
let msg = Message::load_from_db(context, msg_id)?;
|
||||
if msg.is_setupmessage() {
|
||||
let setupcodebegin = msg.get_setupcodebegin(context);
|
||||
let msg_id: u32 = arg1.parse()?;
|
||||
let msg = dc_get_msg(context, msg_id)?;
|
||||
if dc_msg_is_setupmessage(&msg) {
|
||||
let setupcodebegin = dc_msg_get_setupcodebegin(context, &msg);
|
||||
println!(
|
||||
"The setup code for setup message Msg#{} starts with: {}",
|
||||
msg_id,
|
||||
setupcodebegin.unwrap_or_default(),
|
||||
as_str(setupcodebegin),
|
||||
);
|
||||
free(setupcodebegin as *mut libc::c_void);
|
||||
} else {
|
||||
bail!("Msg#{} is no setup message.", msg_id,);
|
||||
}
|
||||
@@ -439,28 +485,33 @@ 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)?;
|
||||
if !dc_continue_key_transfer(context, arg1.parse()?, arg2_c) {
|
||||
bail!("Continue key transfer failed");
|
||||
}
|
||||
}
|
||||
"has-backup" => {
|
||||
has_backup(context, blobdir)?;
|
||||
let ret = dc_imex_has_backup(context, context.get_blobdir());
|
||||
if ret.is_null() {
|
||||
println!("No backup found.");
|
||||
}
|
||||
}
|
||||
"export-backup" => {
|
||||
imex(context, ImexMode::ExportBackup, Some(blobdir));
|
||||
dc_imex(context, 11, Some(context.get_blobdir()), ptr::null());
|
||||
}
|
||||
"import-backup" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <backup-file> missing.");
|
||||
imex(context, ImexMode::ImportBackup, Some(arg1));
|
||||
dc_imex(context, 12, Some(arg1), ptr::null());
|
||||
}
|
||||
"export-keys" => {
|
||||
imex(context, ImexMode::ExportSelfKeys, Some(blobdir));
|
||||
dc_imex(context, 1, Some(context.get_blobdir()), ptr::null());
|
||||
}
|
||||
"import-keys" => {
|
||||
imex(context, ImexMode::ImportSelfKeys, Some(blobdir));
|
||||
dc_imex(context, 2, Some(context.get_blobdir()), ptr::null());
|
||||
}
|
||||
"export-setup" => {
|
||||
let setup_code = create_setup_code(context);
|
||||
let file_name = blobdir.join("autocrypt-setup-message.html");
|
||||
let file_content = render_setup_file(context, &setup_code)?;
|
||||
let setup_code = dc_create_setup_code(context);
|
||||
let file_name = context.get_blobdir().join("autocrypt-setup-message.html");
|
||||
let file_content = dc_render_setup_file(context, &setup_code)?;
|
||||
std::fs::write(&file_name, file_content)?;
|
||||
println!(
|
||||
"Setup message written to: {}\nSetup code: {}",
|
||||
@@ -478,7 +529,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
ensure!(0 != dc_reset_tables(context, bits), "Reset failed");
|
||||
}
|
||||
"stop" => {
|
||||
context.stop_ongoing();
|
||||
dc_stop_ongoing_process(context);
|
||||
}
|
||||
"set" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <key> missing.");
|
||||
@@ -495,9 +546,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 +570,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 +635,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 +671,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)?;
|
||||
|
||||
@@ -763,7 +800,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.");
|
||||
@@ -788,14 +825,14 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
ensure!(!arg1.is_empty(), "No file given.");
|
||||
|
||||
let mut msg = Message::new(if arg0 == "sendimage" {
|
||||
let mut msg = dc_msg_new(if arg0 == "sendimage" {
|
||||
Viewtype::Image
|
||||
} else {
|
||||
Viewtype::File
|
||||
});
|
||||
msg.set_file(arg1, None);
|
||||
dc_msg_set_file(&mut msg, arg1_c, ptr::null());
|
||||
if !arg2.is_empty() {
|
||||
msg.set_text(Some(arg2.to_string()));
|
||||
dc_msg_set_text(&mut msg, arg2_c);
|
||||
}
|
||||
chat::send_msg(context, sel_chat.as_ref().unwrap().get_id(), &mut msg)?;
|
||||
}
|
||||
@@ -817,8 +854,8 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
|
||||
if !arg1.is_empty() {
|
||||
let mut draft = Message::new(Viewtype::Text);
|
||||
draft.set_text(Some(arg1.to_string()));
|
||||
let mut draft = dc_msg_new(Viewtype::Text);
|
||||
dc_msg_set_text(&mut draft, arg1_c);
|
||||
chat::set_draft(
|
||||
context,
|
||||
sel_chat.as_ref().unwrap().get_id(),
|
||||
@@ -830,15 +867,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.");
|
||||
|
||||
@@ -875,9 +903,9 @@ 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 res = message::get_msg_info(context, id);
|
||||
println!("{}", res);
|
||||
let id = arg1.parse()?;
|
||||
let res = dc_get_msg_info(context, id);
|
||||
println!("{}", as_str(res));
|
||||
}
|
||||
"listfresh" => {
|
||||
let msglist = context.get_fresh_msgs();
|
||||
@@ -891,28 +919,33 @@ 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()?);
|
||||
chat::forward_msgs(context, &msg_ids, chat_id)?;
|
||||
msg_ids[0] = arg1.parse()?;
|
||||
chat::forward_msgs(context, msg_ids.as_mut_ptr(), 1, 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()?);
|
||||
message::markseen_msgs(context, &msg_ids);
|
||||
let mut msg_ids = [0; 1];
|
||||
msg_ids[0] = arg1.parse()?;
|
||||
dc_markseen_msgs(context, msg_ids.as_mut_ptr(), 1);
|
||||
}
|
||||
"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()?);
|
||||
message::star_msgs(context, &msg_ids, arg0 == "star");
|
||||
let mut msg_ids = [0; 1];
|
||||
msg_ids[0] = arg1.parse()?;
|
||||
dc_star_msgs(
|
||||
context,
|
||||
msg_ids.as_mut_ptr(),
|
||||
1,
|
||||
if arg0 == "star" { 1 } else { 0 },
|
||||
);
|
||||
}
|
||||
"delmsg" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
||||
let mut ids = [MsgId::new(0); 1];
|
||||
ids[0] = MsgId::new(arg1.parse()?);
|
||||
message::delete_msgs(context, &ids);
|
||||
let mut ids = [0; 1];
|
||||
ids[0] = arg1.parse()?;
|
||||
dc_delete_msgs(context, ids.as_mut_ptr(), 1);
|
||||
}
|
||||
"listcontacts" | "contacts" | "listverified" => {
|
||||
let contacts = Contact::get_all(
|
||||
@@ -995,23 +1028,19 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
"fileinfo" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <file> missing.");
|
||||
|
||||
if let Ok(buf) = dc_read_file(context, &arg1) {
|
||||
if let Some(buf) = dc_read_file_safe(context, &arg1) {
|
||||
let (width, height) = dc_get_filemeta(&buf)?;
|
||||
println!("width={}, height={}", width, height);
|
||||
} else {
|
||||
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),
|
||||
}
|
||||
|
||||
free(arg1_c as *mut _);
|
||||
free(arg2_c as *mut _);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -23,9 +23,11 @@ use std::sync::{Arc, Mutex, RwLock};
|
||||
use deltachat::config;
|
||||
use deltachat::configure::*;
|
||||
use deltachat::context::*;
|
||||
use deltachat::dc_tools::*;
|
||||
use deltachat::job::*;
|
||||
use deltachat::oauth2::*;
|
||||
use deltachat::securejoin::*;
|
||||
use deltachat::x::*;
|
||||
use deltachat::Event;
|
||||
use rustyline::completion::{Completer, FilenameCompleter, Pair};
|
||||
use rustyline::config::OutputStreamType;
|
||||
@@ -43,6 +45,7 @@ use self::cmdline::*;
|
||||
|
||||
fn receive_event(_context: &Context, event: Event) -> libc::uintptr_t {
|
||||
match event {
|
||||
Event::GetString { .. } => {}
|
||||
Event::Info(msg) => {
|
||||
/* do not show the event as this would fill the screen */
|
||||
println!("{}", msg);
|
||||
@@ -438,6 +441,11 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
|
||||
let mut args = line.splitn(2, ' ');
|
||||
let arg0 = args.next().unwrap_or_default();
|
||||
let arg1 = args.next().unwrap_or_default();
|
||||
let arg1_c = if arg1.is_empty() {
|
||||
std::ptr::null()
|
||||
} else {
|
||||
arg1.strdup()
|
||||
};
|
||||
|
||||
match arg0 {
|
||||
"connect" => {
|
||||
@@ -513,6 +521,8 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
|
||||
_ => dc_cmdline(&ctx.read().unwrap(), line)?,
|
||||
}
|
||||
|
||||
free(arg1_c as *mut _);
|
||||
|
||||
Ok(ExitResult::Continue)
|
||||
}
|
||||
|
||||
|
||||
@@ -35,90 +35,93 @@ fn cb(_ctx: &Context, event: Event) -> usize {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
println!("creating database {:?}", dbfile);
|
||||
let ctx =
|
||||
Context::new(Box::new(cb), "FakeOs".into(), dbfile).expect("Failed to create context");
|
||||
let running = Arc::new(RwLock::new(true));
|
||||
let info = ctx.get_info();
|
||||
let duration = time::Duration::from_millis(4000);
|
||||
println!("info: {:#?}", info);
|
||||
|
||||
let ctx = Arc::new(ctx);
|
||||
let ctx1 = ctx.clone();
|
||||
let r1 = running.clone();
|
||||
let t1 = thread::spawn(move || {
|
||||
while *r1.read().unwrap() {
|
||||
perform_imap_jobs(&ctx1);
|
||||
if *r1.read().unwrap() {
|
||||
perform_imap_fetch(&ctx1);
|
||||
unsafe {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
println!("creating database {:?}", dbfile);
|
||||
let ctx =
|
||||
Context::new(Box::new(cb), "FakeOs".into(), dbfile).expect("Failed to create context");
|
||||
let running = Arc::new(RwLock::new(true));
|
||||
let info = ctx.get_info();
|
||||
let duration = time::Duration::from_millis(4000);
|
||||
println!("info: {:#?}", info);
|
||||
|
||||
let ctx = Arc::new(ctx);
|
||||
let ctx1 = ctx.clone();
|
||||
let r1 = running.clone();
|
||||
let t1 = thread::spawn(move || {
|
||||
while *r1.read().unwrap() {
|
||||
perform_imap_jobs(&ctx1);
|
||||
if *r1.read().unwrap() {
|
||||
perform_imap_idle(&ctx1);
|
||||
perform_imap_fetch(&ctx1);
|
||||
|
||||
if *r1.read().unwrap() {
|
||||
perform_imap_idle(&ctx1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let ctx1 = ctx.clone();
|
||||
let r1 = running.clone();
|
||||
let t2 = thread::spawn(move || {
|
||||
while *r1.read().unwrap() {
|
||||
perform_smtp_jobs(&ctx1);
|
||||
if *r1.read().unwrap() {
|
||||
perform_smtp_idle(&ctx1);
|
||||
let ctx1 = ctx.clone();
|
||||
let r1 = running.clone();
|
||||
let t2 = thread::spawn(move || {
|
||||
while *r1.read().unwrap() {
|
||||
perform_smtp_jobs(&ctx1);
|
||||
if *r1.read().unwrap() {
|
||||
perform_smtp_idle(&ctx1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
println!("configuring");
|
||||
let args = std::env::args().collect::<Vec<String>>();
|
||||
assert_eq!(args.len(), 2, "missing password");
|
||||
let pw = args[1].clone();
|
||||
ctx.set_config(config::Config::Addr, Some("d@testrun.org"))
|
||||
.unwrap();
|
||||
ctx.set_config(config::Config::MailPw, Some(&pw)).unwrap();
|
||||
configure(&ctx);
|
||||
|
||||
thread::sleep(duration);
|
||||
|
||||
println!("sending a message");
|
||||
let contact_id =
|
||||
Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com").unwrap();
|
||||
let chat_id = chat::create_by_contact_id(&ctx, contact_id).unwrap();
|
||||
chat::send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into()).unwrap();
|
||||
|
||||
println!("fetching chats..");
|
||||
let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap();
|
||||
|
||||
for i in 0..chats.len() {
|
||||
let summary = chats.get_summary(&ctx, 0, None);
|
||||
let text1 = summary.get_text1();
|
||||
let text2 = summary.get_text2();
|
||||
println!("chat: {} - {:?} - {:?}", i, text1, text2,);
|
||||
}
|
||||
});
|
||||
|
||||
println!("configuring");
|
||||
let args = std::env::args().collect::<Vec<String>>();
|
||||
assert_eq!(args.len(), 2, "missing password");
|
||||
let pw = args[1].clone();
|
||||
ctx.set_config(config::Config::Addr, Some("d@testrun.org"))
|
||||
.unwrap();
|
||||
ctx.set_config(config::Config::MailPw, Some(&pw)).unwrap();
|
||||
configure(&ctx);
|
||||
thread::sleep(duration);
|
||||
|
||||
thread::sleep(duration);
|
||||
// let msglist = dc_get_chat_msgs(&ctx, chat_id, 0, 0);
|
||||
// for i in 0..dc_array_get_cnt(msglist) {
|
||||
// let msg_id = dc_array_get_id(msglist, i);
|
||||
// let msg = dc_get_msg(context, msg_id);
|
||||
// let text = CStr::from_ptr(dc_msg_get_text(msg)).unwrap();
|
||||
// println!("Message {}: {}\n", i + 1, text.to_str().unwrap());
|
||||
// dc_msg_unref(msg);
|
||||
// }
|
||||
// dc_array_unref(msglist);
|
||||
|
||||
println!("sending a message");
|
||||
let contact_id = Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com").unwrap();
|
||||
let chat_id = chat::create_by_contact_id(&ctx, contact_id).unwrap();
|
||||
chat::send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into()).unwrap();
|
||||
println!("stopping threads");
|
||||
|
||||
println!("fetching chats..");
|
||||
let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap();
|
||||
*running.clone().write().unwrap() = false;
|
||||
deltachat::job::interrupt_imap_idle(&ctx);
|
||||
deltachat::job::interrupt_smtp_idle(&ctx);
|
||||
|
||||
for i in 0..chats.len() {
|
||||
let summary = chats.get_summary(&ctx, 0, None);
|
||||
let text1 = summary.get_text1();
|
||||
let text2 = summary.get_text2();
|
||||
println!("chat: {} - {:?} - {:?}", i, text1, text2,);
|
||||
println!("joining");
|
||||
t1.join().unwrap();
|
||||
t2.join().unwrap();
|
||||
|
||||
println!("closing");
|
||||
}
|
||||
|
||||
thread::sleep(duration);
|
||||
|
||||
// let msglist = dc_get_chat_msgs(&ctx, chat_id, 0, 0);
|
||||
// for i in 0..dc_array_get_cnt(msglist) {
|
||||
// let msg_id = dc_array_get_id(msglist, i);
|
||||
// let msg = dc_get_msg(context, msg_id);
|
||||
// let text = CStr::from_ptr(dc_msg_get_text(msg)).unwrap();
|
||||
// println!("Message {}: {}\n", i + 1, text.to_str().unwrap());
|
||||
// dc_msg_unref(msg);
|
||||
// }
|
||||
// dc_array_unref(msglist);
|
||||
|
||||
println!("stopping threads");
|
||||
|
||||
*running.clone().write().unwrap() = false;
|
||||
deltachat::job::interrupt_imap_idle(&ctx);
|
||||
deltachat::job::interrupt_smtp_idle(&ctx);
|
||||
|
||||
println!("joining");
|
||||
t1.join().unwrap();
|
||||
t2.join().unwrap();
|
||||
|
||||
println!("closing");
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
# copied from http://koushiro.me/2019/04/30/Building-and-Testing-Rust-projects-on-CircleCI/
|
||||
|
||||
version: 2.1
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: ubuntu:18.04
|
||||
|
||||
working_directory: ~/deltachat-core-rust
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Setup build environment
|
||||
command: |
|
||||
apt update
|
||||
apt install -y curl build-essential autoconf libtool git python pkg-config
|
||||
# this will pick default toolchain from `rust-toolchain` file
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --no-modify-path --default-toolchain none -y;
|
||||
source $HOME/.cargo/env
|
||||
no_output_timeout: 1800s
|
||||
|
||||
- run:
|
||||
name: Format
|
||||
command: |
|
||||
export PATH=~/.cargo/bin:$PATH
|
||||
rustup component add rustfmt
|
||||
cargo fmt -- --check
|
||||
|
||||
- run:
|
||||
name: Test
|
||||
command: |
|
||||
export PATH=~/.cargo/bin:$PATH
|
||||
export RUST_BACKTRACE=1
|
||||
cargo test
|
||||
|
||||
workflows:
|
||||
version: 2.1
|
||||
build:
|
||||
jobs:
|
||||
- build
|
||||
@@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "mmime"
|
||||
version = "0.1.2"
|
||||
authors = ["dignifiedquire <dignifiedquire@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT OR Apache-2.0"
|
||||
homepage = "https://github.com/deltachat/deltachat-core-rust"
|
||||
repository = "https://github.com/deltachat/deltachat-core-rust"
|
||||
readme = "README.md"
|
||||
description = "Mime parsing for email"
|
||||
|
||||
|
||||
keywords = ["mail", "mim", "email", "imap", "smtp"]
|
||||
categories = ["std", "email"]
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.54"
|
||||
charset = "0.1.2"
|
||||
memmap = "0.7.0"
|
||||
lazy_static = "1.3.0"
|
||||
rand = "0.6.5"
|
||||
chrono = "0.4.6"
|
||||
hex = "0.3.2"
|
||||
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,23 +0,0 @@
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,4 +0,0 @@
|
||||
This library is primarly distributed under the terms of both the MIT license and
|
||||
the Apache License (Version 2.0).
|
||||
|
||||
See LICENSE-MIT and LICENSE-APACHE for details.
|
||||
@@ -1,16 +0,0 @@
|
||||
# mmime
|
||||
|
||||
[![CircleCI build status][circle-shield]][circle] [![Appveyor build status][appveyor-shield]][appveyor] [![License][license-shield]][license]
|
||||
|
||||
> mmmmmmime parsing
|
||||
|
||||
Base code was compiled using c2rust from libetpan.
|
||||
|
||||
|
||||
|
||||
[circle-shield]: https://img.shields.io/circleci/project/github/dignifiedquire/mmime/master.svg?style=flat-square
|
||||
[circle]: https://circleci.com/gh/dignifiedquire/mmime/
|
||||
[appveyor-shield]: https://ci.appveyor.com/api/projects/status/l26co5rba32knrlu/branch/master?style=flat-square
|
||||
[appveyor]: https://ci.appveyor.com/project/dignifiedquire/mmime/branch/master
|
||||
[license-shield]: https://img.shields.io/badge/License-MIT%2FApache2.0-green.svg?style=flat-square
|
||||
[license]: https://github.com/rpgp/rpgp/blob/master/LICENSE.md
|
||||
@@ -1,32 +0,0 @@
|
||||
use crate::other::*;
|
||||
use libc;
|
||||
use std::ffi::{CStr, CString};
|
||||
|
||||
pub const MAIL_CHARCONV_ERROR_CONV: libc::c_uint = 3;
|
||||
pub const MAIL_CHARCONV_ERROR_MEMORY: libc::c_uint = 2;
|
||||
pub const MAIL_CHARCONV_ERROR_UNKNOWN_CHARSET: libc::c_uint = 1;
|
||||
pub const MAIL_CHARCONV_NO_ERROR: libc::c_uint = 0;
|
||||
|
||||
pub unsafe fn charconv(
|
||||
tocode: *const libc::c_char,
|
||||
fromcode: *const libc::c_char,
|
||||
s: *const libc::c_char,
|
||||
length: size_t,
|
||||
result: *mut *mut libc::c_char,
|
||||
) -> libc::c_int {
|
||||
assert!(!fromcode.is_null(), "invalid fromcode");
|
||||
assert!(!s.is_null(), "invalid input string");
|
||||
if let Some(encoding) =
|
||||
charset::Charset::for_label(CStr::from_ptr(fromcode).to_string_lossy().as_bytes())
|
||||
{
|
||||
let data = std::slice::from_raw_parts(s as *const u8, strlen(s));
|
||||
|
||||
let (res, _, _) = encoding.decode(data);
|
||||
let res_c = CString::new(res.as_bytes()).unwrap_or_default();
|
||||
*result = strdup(res_c.as_ptr()) as *mut _;
|
||||
|
||||
MAIL_CHARCONV_NO_ERROR as libc::c_int
|
||||
} else {
|
||||
MAIL_CHARCONV_ERROR_UNKNOWN_CHARSET as libc::c_int
|
||||
}
|
||||
}
|
||||
@@ -1,427 +0,0 @@
|
||||
use libc;
|
||||
|
||||
use crate::other::*;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct chashdatum {
|
||||
pub data: *mut libc::c_void,
|
||||
pub len: libc::c_uint,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct chash {
|
||||
pub size: libc::c_uint,
|
||||
pub count: libc::c_uint,
|
||||
pub copyvalue: libc::c_int,
|
||||
pub copykey: libc::c_int,
|
||||
pub cells: *mut *mut chashcell,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct chashcell {
|
||||
pub func: libc::c_uint,
|
||||
pub key: chashdatum,
|
||||
pub value: chashdatum,
|
||||
pub next: *mut chashcell,
|
||||
}
|
||||
|
||||
pub type chashiter = chashcell;
|
||||
/* Allocates a new (empty) hash using this initial size and the given flags,
|
||||
specifying which data should be copied in the hash.
|
||||
CHASH_COPYNONE : Keys/Values are not copied.
|
||||
CHASH_COPYKEY : Keys are dupped and freed as needed in the hash.
|
||||
CHASH_COPYVALUE : Values are dupped and freed as needed in the hash.
|
||||
CHASH_COPYALL : Both keys and values are dupped in the hash.
|
||||
*/
|
||||
pub unsafe fn chash_new(mut size: libc::c_uint, mut flags: libc::c_int) -> *mut chash {
|
||||
let mut h: *mut chash = 0 as *mut chash;
|
||||
h = malloc(::std::mem::size_of::<chash>() as libc::size_t) as *mut chash;
|
||||
if h.is_null() {
|
||||
return 0 as *mut chash;
|
||||
}
|
||||
if size < 13i32 as libc::c_uint {
|
||||
size = 13i32 as libc::c_uint
|
||||
}
|
||||
(*h).count = 0i32 as libc::c_uint;
|
||||
(*h).cells = calloc(
|
||||
size as libc::size_t,
|
||||
::std::mem::size_of::<*mut chashcell>() as libc::size_t,
|
||||
) as *mut *mut chashcell;
|
||||
if (*h).cells.is_null() {
|
||||
free(h as *mut libc::c_void);
|
||||
return 0 as *mut chash;
|
||||
}
|
||||
(*h).size = size;
|
||||
(*h).copykey = flags & 1i32;
|
||||
(*h).copyvalue = flags & 2i32;
|
||||
return h;
|
||||
}
|
||||
|
||||
/* Frees a hash */
|
||||
pub unsafe fn chash_free(mut hash: *mut chash) {
|
||||
let mut indx: libc::c_uint = 0;
|
||||
let mut iter: *mut chashiter = 0 as *mut chashiter;
|
||||
let mut next: *mut chashiter = 0 as *mut chashiter;
|
||||
indx = 0i32 as libc::c_uint;
|
||||
while indx < (*hash).size {
|
||||
iter = *(*hash).cells.offset(indx as isize);
|
||||
while !iter.is_null() {
|
||||
next = (*iter).next;
|
||||
if 0 != (*hash).copykey {
|
||||
free((*iter).key.data);
|
||||
}
|
||||
if 0 != (*hash).copyvalue {
|
||||
free((*iter).value.data);
|
||||
}
|
||||
free(iter as *mut libc::c_void);
|
||||
iter = next
|
||||
}
|
||||
indx = indx.wrapping_add(1)
|
||||
}
|
||||
free((*hash).cells as *mut libc::c_void);
|
||||
free(hash as *mut libc::c_void);
|
||||
}
|
||||
|
||||
/* Removes all elements from a hash */
|
||||
pub unsafe fn chash_clear(mut hash: *mut chash) {
|
||||
let mut indx: libc::c_uint = 0;
|
||||
let mut iter: *mut chashiter = 0 as *mut chashiter;
|
||||
let mut next: *mut chashiter = 0 as *mut chashiter;
|
||||
indx = 0i32 as libc::c_uint;
|
||||
while indx < (*hash).size {
|
||||
iter = *(*hash).cells.offset(indx as isize);
|
||||
while !iter.is_null() {
|
||||
next = (*iter).next;
|
||||
if 0 != (*hash).copykey {
|
||||
free((*iter).key.data);
|
||||
}
|
||||
if 0 != (*hash).copyvalue {
|
||||
free((*iter).value.data);
|
||||
}
|
||||
free(iter as *mut libc::c_void);
|
||||
iter = next
|
||||
}
|
||||
indx = indx.wrapping_add(1)
|
||||
}
|
||||
memset(
|
||||
(*hash).cells as *mut libc::c_void,
|
||||
0i32,
|
||||
((*hash).size as libc::size_t)
|
||||
.wrapping_mul(::std::mem::size_of::<*mut chashcell>() as libc::size_t),
|
||||
);
|
||||
(*hash).count = 0i32 as libc::c_uint;
|
||||
}
|
||||
/* Adds an entry in the hash table.
|
||||
Length can be 0 if key/value are strings.
|
||||
If an entry already exists for this key, it is replaced, and its value
|
||||
is returned. Otherwise, the data pointer will be NULL and the length
|
||||
field be set to TRUE or FALSe to indicate success or failure. */
|
||||
pub unsafe fn chash_set(
|
||||
mut hash: *mut chash,
|
||||
mut key: *mut chashdatum,
|
||||
mut value: *mut chashdatum,
|
||||
mut oldvalue: *mut chashdatum,
|
||||
) -> libc::c_int {
|
||||
let mut current_block: u64;
|
||||
let mut func: libc::c_uint = 0;
|
||||
let mut indx: libc::c_uint = 0;
|
||||
let mut iter: *mut chashiter = 0 as *mut chashiter;
|
||||
let mut cell: *mut chashiter = 0 as *mut chashiter;
|
||||
let mut r: libc::c_int = 0;
|
||||
if (*hash).count > (*hash).size.wrapping_mul(3i32 as libc::c_uint) {
|
||||
r = chash_resize(
|
||||
hash,
|
||||
(*hash)
|
||||
.count
|
||||
.wrapping_div(3i32 as libc::c_uint)
|
||||
.wrapping_mul(2i32 as libc::c_uint)
|
||||
.wrapping_add(1i32 as libc::c_uint),
|
||||
);
|
||||
if r < 0i32 {
|
||||
current_block = 17701753836843438419;
|
||||
} else {
|
||||
current_block = 7095457783677275021;
|
||||
}
|
||||
} else {
|
||||
current_block = 7095457783677275021;
|
||||
}
|
||||
match current_block {
|
||||
7095457783677275021 => {
|
||||
func = chash_func((*key).data as *const libc::c_char, (*key).len);
|
||||
indx = func.wrapping_rem((*hash).size);
|
||||
iter = *(*hash).cells.offset(indx as isize);
|
||||
loop {
|
||||
if iter.is_null() {
|
||||
current_block = 17788412896529399552;
|
||||
break;
|
||||
}
|
||||
if (*iter).key.len == (*key).len
|
||||
&& (*iter).func == func
|
||||
&& 0 == memcmp((*iter).key.data, (*key).data, (*key).len as libc::size_t)
|
||||
{
|
||||
/* found, replacing entry */
|
||||
if 0 != (*hash).copyvalue {
|
||||
let mut data: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
data = chash_dup((*value).data, (*value).len);
|
||||
if data.is_null() {
|
||||
current_block = 17701753836843438419;
|
||||
break;
|
||||
}
|
||||
free((*iter).value.data);
|
||||
(*iter).value.data = data as *mut libc::c_void;
|
||||
(*iter).value.len = (*value).len
|
||||
} else {
|
||||
if !oldvalue.is_null() {
|
||||
(*oldvalue).data = (*iter).value.data;
|
||||
(*oldvalue).len = (*iter).value.len
|
||||
}
|
||||
(*iter).value.data = (*value).data;
|
||||
(*iter).value.len = (*value).len
|
||||
}
|
||||
if 0 == (*hash).copykey {
|
||||
(*iter).key.data = (*key).data
|
||||
}
|
||||
if !oldvalue.is_null() {
|
||||
(*oldvalue).data = (*value).data;
|
||||
(*oldvalue).len = (*value).len
|
||||
}
|
||||
return 0i32;
|
||||
} else {
|
||||
iter = (*iter).next
|
||||
}
|
||||
}
|
||||
match current_block {
|
||||
17701753836843438419 => {}
|
||||
_ => {
|
||||
if !oldvalue.is_null() {
|
||||
(*oldvalue).data = 0 as *mut libc::c_void;
|
||||
(*oldvalue).len = 0i32 as libc::c_uint
|
||||
}
|
||||
cell = malloc(::std::mem::size_of::<chashcell>() as libc::size_t)
|
||||
as *mut chashcell;
|
||||
if !cell.is_null() {
|
||||
if 0 != (*hash).copykey {
|
||||
(*cell).key.data =
|
||||
chash_dup((*key).data, (*key).len) as *mut libc::c_void;
|
||||
if (*cell).key.data.is_null() {
|
||||
current_block = 4267898785354516004;
|
||||
} else {
|
||||
current_block = 7226443171521532240;
|
||||
}
|
||||
} else {
|
||||
(*cell).key.data = (*key).data;
|
||||
current_block = 7226443171521532240;
|
||||
}
|
||||
match current_block {
|
||||
7226443171521532240 => {
|
||||
(*cell).key.len = (*key).len;
|
||||
if 0 != (*hash).copyvalue {
|
||||
(*cell).value.data =
|
||||
chash_dup((*value).data, (*value).len) as *mut libc::c_void;
|
||||
if (*cell).value.data.is_null() {
|
||||
if 0 != (*hash).copykey {
|
||||
free((*cell).key.data);
|
||||
}
|
||||
current_block = 4267898785354516004;
|
||||
} else {
|
||||
current_block = 6717214610478484138;
|
||||
}
|
||||
} else {
|
||||
(*cell).value.data = (*value).data;
|
||||
current_block = 6717214610478484138;
|
||||
}
|
||||
match current_block {
|
||||
4267898785354516004 => {}
|
||||
_ => {
|
||||
(*cell).value.len = (*value).len;
|
||||
(*cell).func = func;
|
||||
(*cell).next = *(*hash).cells.offset(indx as isize);
|
||||
let ref mut fresh0 = *(*hash).cells.offset(indx as isize);
|
||||
*fresh0 = cell;
|
||||
(*hash).count = (*hash).count.wrapping_add(1);
|
||||
return 0i32;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
free(cell as *mut libc::c_void);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
return -1i32;
|
||||
}
|
||||
#[inline]
|
||||
unsafe fn chash_dup(mut data: *const libc::c_void, mut len: libc::c_uint) -> *mut libc::c_char {
|
||||
let mut r: *mut libc::c_void = 0 as *mut libc::c_void;
|
||||
r = malloc(len as libc::size_t) as *mut libc::c_char as *mut libc::c_void;
|
||||
if r.is_null() {
|
||||
return 0 as *mut libc::c_char;
|
||||
}
|
||||
memcpy(r, data, len as libc::size_t);
|
||||
return r as *mut libc::c_char;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn chash_func(mut key: *const libc::c_char, mut len: libc::c_uint) -> libc::c_uint {
|
||||
let mut c: libc::c_uint = 5381i32 as libc::c_uint;
|
||||
let mut k: *const libc::c_char = key;
|
||||
loop {
|
||||
let fresh1 = len;
|
||||
len = len.wrapping_sub(1);
|
||||
if !(0 != fresh1) {
|
||||
break;
|
||||
}
|
||||
let fresh2 = k;
|
||||
k = k.offset(1);
|
||||
c = (c << 5i32)
|
||||
.wrapping_add(c)
|
||||
.wrapping_add(*fresh2 as libc::c_uint)
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/* Resizes the hash table to the passed size. */
|
||||
pub unsafe fn chash_resize(mut hash: *mut chash, mut size: libc::c_uint) -> libc::c_int {
|
||||
let mut cells: *mut *mut chashcell = 0 as *mut *mut chashcell;
|
||||
let mut indx: libc::c_uint = 0;
|
||||
let mut nindx: libc::c_uint = 0;
|
||||
let mut iter: *mut chashiter = 0 as *mut chashiter;
|
||||
let mut next: *mut chashiter = 0 as *mut chashiter;
|
||||
if (*hash).size == size {
|
||||
return 0i32;
|
||||
}
|
||||
cells = calloc(
|
||||
size as libc::size_t,
|
||||
::std::mem::size_of::<*mut chashcell>() as libc::size_t,
|
||||
) as *mut *mut chashcell;
|
||||
if cells.is_null() {
|
||||
return -1i32;
|
||||
}
|
||||
indx = 0i32 as libc::c_uint;
|
||||
while indx < (*hash).size {
|
||||
iter = *(*hash).cells.offset(indx as isize);
|
||||
while !iter.is_null() {
|
||||
next = (*iter).next;
|
||||
nindx = (*iter).func.wrapping_rem(size);
|
||||
(*iter).next = *cells.offset(nindx as isize);
|
||||
let ref mut fresh3 = *cells.offset(nindx as isize);
|
||||
*fresh3 = iter;
|
||||
iter = next
|
||||
}
|
||||
indx = indx.wrapping_add(1)
|
||||
}
|
||||
free((*hash).cells as *mut libc::c_void);
|
||||
(*hash).size = size;
|
||||
(*hash).cells = cells;
|
||||
return 0i32;
|
||||
}
|
||||
|
||||
/* Retrieves the data associated to the key if it is found in the hash table.
|
||||
The data pointer and the length will be NULL if not found*/
|
||||
pub unsafe fn chash_get(
|
||||
mut hash: *mut chash,
|
||||
mut key: *mut chashdatum,
|
||||
mut result: *mut chashdatum,
|
||||
) -> libc::c_int {
|
||||
let mut func: libc::c_uint = 0;
|
||||
let mut iter: *mut chashiter = 0 as *mut chashiter;
|
||||
func = chash_func((*key).data as *const libc::c_char, (*key).len);
|
||||
iter = *(*hash)
|
||||
.cells
|
||||
.offset(func.wrapping_rem((*hash).size) as isize);
|
||||
while !iter.is_null() {
|
||||
if (*iter).key.len == (*key).len
|
||||
&& (*iter).func == func
|
||||
&& 0 == memcmp((*iter).key.data, (*key).data, (*key).len as libc::size_t)
|
||||
{
|
||||
*result = (*iter).value;
|
||||
return 0i32;
|
||||
}
|
||||
iter = (*iter).next
|
||||
}
|
||||
return -1i32;
|
||||
}
|
||||
/* Removes the entry associated to this key if it is found in the hash table,
|
||||
and returns its contents if not dupped (otherwise, pointer will be NULL
|
||||
and len TRUE). If entry is not found both pointer and len will be NULL. */
|
||||
pub unsafe fn chash_delete(
|
||||
mut hash: *mut chash,
|
||||
mut key: *mut chashdatum,
|
||||
mut oldvalue: *mut chashdatum,
|
||||
) -> libc::c_int {
|
||||
/* chashdatum result = { NULL, TRUE }; */
|
||||
let mut func: libc::c_uint = 0;
|
||||
let mut indx: libc::c_uint = 0;
|
||||
let mut iter: *mut chashiter = 0 as *mut chashiter;
|
||||
let mut old: *mut chashiter = 0 as *mut chashiter;
|
||||
func = chash_func((*key).data as *const libc::c_char, (*key).len);
|
||||
indx = func.wrapping_rem((*hash).size);
|
||||
old = 0 as *mut chashiter;
|
||||
iter = *(*hash).cells.offset(indx as isize);
|
||||
while !iter.is_null() {
|
||||
if (*iter).key.len == (*key).len
|
||||
&& (*iter).func == func
|
||||
&& 0 == memcmp((*iter).key.data, (*key).data, (*key).len as libc::size_t)
|
||||
{
|
||||
if !old.is_null() {
|
||||
(*old).next = (*iter).next
|
||||
} else {
|
||||
let ref mut fresh4 = *(*hash).cells.offset(indx as isize);
|
||||
*fresh4 = (*iter).next
|
||||
}
|
||||
if 0 != (*hash).copykey {
|
||||
free((*iter).key.data);
|
||||
}
|
||||
if 0 != (*hash).copyvalue {
|
||||
free((*iter).value.data);
|
||||
} else if !oldvalue.is_null() {
|
||||
(*oldvalue).data = (*iter).value.data;
|
||||
(*oldvalue).len = (*iter).value.len
|
||||
}
|
||||
free(iter as *mut libc::c_void);
|
||||
(*hash).count = (*hash).count.wrapping_sub(1);
|
||||
return 0i32;
|
||||
}
|
||||
old = iter;
|
||||
iter = (*iter).next
|
||||
}
|
||||
return -1i32;
|
||||
}
|
||||
/* Returns an iterator to the first non-empty entry of the hash table */
|
||||
pub unsafe fn chash_begin(mut hash: *mut chash) -> *mut chashiter {
|
||||
let mut iter: *mut chashiter = 0 as *mut chashiter;
|
||||
let mut indx: libc::c_uint = 0i32 as libc::c_uint;
|
||||
iter = *(*hash).cells.offset(0isize);
|
||||
while iter.is_null() {
|
||||
indx = indx.wrapping_add(1);
|
||||
if indx >= (*hash).size {
|
||||
return 0 as *mut chashiter;
|
||||
}
|
||||
iter = *(*hash).cells.offset(indx as isize)
|
||||
}
|
||||
return iter;
|
||||
}
|
||||
/* Returns the next non-empty entry of the hash table */
|
||||
pub unsafe fn chash_next(mut hash: *mut chash, mut iter: *mut chashiter) -> *mut chashiter {
|
||||
let mut indx: libc::c_uint = 0;
|
||||
if iter.is_null() {
|
||||
return 0 as *mut chashiter;
|
||||
}
|
||||
indx = (*iter).func.wrapping_rem((*hash).size);
|
||||
iter = (*iter).next;
|
||||
while iter.is_null() {
|
||||
indx = indx.wrapping_add(1);
|
||||
if indx >= (*hash).size {
|
||||
return 0 as *mut chashiter;
|
||||
}
|
||||
iter = *(*hash).cells.offset(indx as isize)
|
||||
}
|
||||
return iter;
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
use libc;
|
||||
|
||||
use crate::other::*;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct clistcell {
|
||||
pub data: *mut libc::c_void,
|
||||
pub previous: *mut clistcell,
|
||||
pub next: *mut clistcell,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[repr(C)]
|
||||
pub struct clist {
|
||||
pub first: *mut clistcell,
|
||||
pub last: *mut clistcell,
|
||||
pub count: libc::c_int,
|
||||
}
|
||||
|
||||
impl Default for clist {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
first: std::ptr::null_mut(),
|
||||
last: std::ptr::null_mut(),
|
||||
count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for clist {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let mut l1 = self.first;
|
||||
while !l1.is_null() {
|
||||
let l2 = (*l1).next;
|
||||
free(l1 as *mut libc::c_void);
|
||||
l1 = l2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type clistiter = clistcell;
|
||||
pub struct CListIterator {
|
||||
cur: *mut clistiter,
|
||||
}
|
||||
impl Iterator for CListIterator {
|
||||
type Item = *mut libc::c_void;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
unsafe {
|
||||
if self.cur.is_null() {
|
||||
None
|
||||
} else {
|
||||
let data = (*self.cur).data;
|
||||
self.cur = (*self.cur).next;
|
||||
Some(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for &clist {
|
||||
type Item = *mut libc::c_void;
|
||||
type IntoIter = CListIterator;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
return CListIterator { cur: self.first };
|
||||
}
|
||||
}
|
||||
|
||||
pub type clist_func =
|
||||
Option<unsafe extern "C" fn(_: *mut libc::c_void, _: *mut libc::c_void) -> ()>;
|
||||
|
||||
/* Allocate a new pointer list */
|
||||
pub fn clist_new() -> *mut clist {
|
||||
Box::into_raw(Box::new(Default::default()))
|
||||
}
|
||||
/* Destroys a list. Data pointed by data pointers is NOT freed. */
|
||||
pub unsafe fn clist_free(mut lst: *mut clist) {
|
||||
Box::from_raw(lst);
|
||||
}
|
||||
/* Inserts this data pointer after the element pointed by the iterator */
|
||||
pub unsafe fn clist_insert_after(
|
||||
mut lst: *mut clist,
|
||||
mut iter: *mut clistiter,
|
||||
mut data: *mut libc::c_void,
|
||||
) -> libc::c_int {
|
||||
let mut c: *mut clistcell = 0 as *mut clistcell;
|
||||
c = malloc(::std::mem::size_of::<clistcell>() as libc::size_t) as *mut clistcell;
|
||||
if c.is_null() {
|
||||
return -1i32;
|
||||
}
|
||||
(*c).data = data;
|
||||
(*lst).count += 1;
|
||||
if (*lst).first == (*lst).last && (*lst).last.is_null() {
|
||||
(*c).next = 0 as *mut clistcell;
|
||||
(*c).previous = (*c).next;
|
||||
(*lst).last = c;
|
||||
(*lst).first = (*lst).last;
|
||||
return 0i32;
|
||||
}
|
||||
if iter.is_null() {
|
||||
(*c).previous = (*lst).last;
|
||||
(*(*c).previous).next = c;
|
||||
(*c).next = 0 as *mut clistcell;
|
||||
(*lst).last = c;
|
||||
return 0i32;
|
||||
}
|
||||
(*c).previous = iter;
|
||||
(*c).next = (*iter).next;
|
||||
if !(*c).next.is_null() {
|
||||
(*(*c).next).previous = c
|
||||
} else {
|
||||
(*lst).last = c
|
||||
}
|
||||
(*(*c).previous).next = c;
|
||||
return 0i32;
|
||||
}
|
||||
/* Deletes the element pointed by the iterator.
|
||||
Returns an iterator to the next element. */
|
||||
pub unsafe fn clist_delete(mut lst: *mut clist, mut iter: *mut clistiter) -> *mut clistiter {
|
||||
let mut ret: *mut clistiter = 0 as *mut clistiter;
|
||||
if iter.is_null() {
|
||||
return 0 as *mut clistiter;
|
||||
}
|
||||
if !(*iter).previous.is_null() {
|
||||
(*(*iter).previous).next = (*iter).next
|
||||
} else {
|
||||
(*lst).first = (*iter).next
|
||||
}
|
||||
if !(*iter).next.is_null() {
|
||||
(*(*iter).next).previous = (*iter).previous;
|
||||
ret = (*iter).next
|
||||
} else {
|
||||
(*lst).last = (*iter).previous;
|
||||
ret = 0 as *mut clistiter
|
||||
}
|
||||
free(iter as *mut libc::c_void);
|
||||
(*lst).count -= 1;
|
||||
return ret;
|
||||
}
|
||||
pub unsafe fn clist_foreach(
|
||||
mut lst: *mut clist,
|
||||
mut func: clist_func,
|
||||
mut data: *mut libc::c_void,
|
||||
) {
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
cur = (*lst).first;
|
||||
while !cur.is_null() {
|
||||
func.expect("non-null function pointer")((*cur).data, data);
|
||||
cur = (*cur).next
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn clist_nth_data(mut lst: *mut clist, mut indx: libc::c_int) -> *mut libc::c_void {
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
cur = internal_clist_nth(lst, indx);
|
||||
if cur.is_null() {
|
||||
return 0 as *mut libc::c_void;
|
||||
}
|
||||
return (*cur).data;
|
||||
}
|
||||
#[inline]
|
||||
unsafe fn internal_clist_nth(mut lst: *mut clist, mut indx: libc::c_int) -> *mut clistiter {
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
cur = (*lst).first;
|
||||
while indx > 0i32 && !cur.is_null() {
|
||||
cur = (*cur).next;
|
||||
indx -= 1
|
||||
}
|
||||
if cur.is_null() {
|
||||
return 0 as *mut clistiter;
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
pub unsafe fn clist_nth(mut lst: *mut clist, mut indx: libc::c_int) -> *mut clistiter {
|
||||
return internal_clist_nth(lst, indx);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::ptr;
|
||||
#[test]
|
||||
fn test_clist_iterator() {
|
||||
unsafe {
|
||||
let mut c = clist_new();
|
||||
assert!(!c.is_null());
|
||||
clist_insert_after(c, ptr::null_mut(), clist_nth as _);
|
||||
assert_eq!((*c).count, 1);
|
||||
|
||||
/* Only one iteration */
|
||||
for data in &*c {
|
||||
assert_eq!(data, clist_nth as _);
|
||||
}
|
||||
assert_eq!((*c).count, 1);
|
||||
|
||||
clist_free(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
pub const MAIL_ERROR_SSL: libc::c_uint = 58;
|
||||
pub const MAIL_ERROR_FOLDER: libc::c_uint = 57;
|
||||
pub const MAIL_ERROR_UNABLE: libc::c_uint = 56;
|
||||
pub const MAIL_ERROR_SYSTEM: libc::c_uint = 55;
|
||||
pub const MAIL_ERROR_COMMAND: libc::c_uint = 54;
|
||||
pub const MAIL_ERROR_SEND: libc::c_uint = 53;
|
||||
pub const MAIL_ERROR_CHAR_ENCODING_FAILED: libc::c_uint = 52;
|
||||
pub const MAIL_ERROR_SUBJECT_NOT_FOUND: libc::c_uint = 51;
|
||||
/* 50 */
|
||||
pub const MAIL_ERROR_PROGRAM_ERROR: libc::c_uint = 50;
|
||||
pub const MAIL_ERROR_NO_PERMISSION: libc::c_uint = 49;
|
||||
pub const MAIL_ERROR_COMMAND_NOT_SUPPORTED: libc::c_uint = 48;
|
||||
pub const MAIL_ERROR_NO_APOP: libc::c_uint = 47;
|
||||
pub const MAIL_ERROR_READONLY: libc::c_uint = 46;
|
||||
pub const MAIL_ERROR_FATAL: libc::c_uint = 45;
|
||||
pub const MAIL_ERROR_CLOSE: libc::c_uint = 44;
|
||||
pub const MAIL_ERROR_CAPABILITY: libc::c_uint = 43;
|
||||
pub const MAIL_ERROR_PROTOCOL: libc::c_uint = 42;
|
||||
/* misc errors */
|
||||
pub const MAIL_ERROR_MISC: libc::c_uint = 41;
|
||||
/* 40 */
|
||||
pub const MAIL_ERROR_EXPUNGE: libc::c_uint = 40;
|
||||
pub const MAIL_ERROR_NO_TLS: libc::c_uint = 39;
|
||||
pub const MAIL_ERROR_CACHE_MISS: libc::c_uint = 38;
|
||||
pub const MAIL_ERROR_STARTTLS: libc::c_uint = 37;
|
||||
pub const MAIL_ERROR_MOVE: libc::c_uint = 36;
|
||||
pub const MAIL_ERROR_FOLDER_NOT_FOUND: libc::c_uint = 35;
|
||||
pub const MAIL_ERROR_REMOVE: libc::c_uint = 34;
|
||||
pub const MAIL_ERROR_PART_NOT_FOUND: libc::c_uint = 33;
|
||||
pub const MAIL_ERROR_INVAL: libc::c_uint = 32;
|
||||
pub const MAIL_ERROR_PARSE: libc::c_uint = 31;
|
||||
/* 30 */
|
||||
pub const MAIL_ERROR_MSG_NOT_FOUND: libc::c_uint = 30;
|
||||
pub const MAIL_ERROR_DISKSPACE: libc::c_uint = 29;
|
||||
pub const MAIL_ERROR_SEARCH: libc::c_uint = 28;
|
||||
pub const MAIL_ERROR_STORE: libc::c_uint = 27;
|
||||
pub const MAIL_ERROR_FETCH: libc::c_uint = 26;
|
||||
pub const MAIL_ERROR_COPY: libc::c_uint = 25;
|
||||
pub const MAIL_ERROR_APPEND: libc::c_uint = 24;
|
||||
pub const MAIL_ERROR_LSUB: libc::c_uint = 23;
|
||||
pub const MAIL_ERROR_LIST: libc::c_uint = 22;
|
||||
pub const MAIL_ERROR_UNSUBSCRIBE: libc::c_uint = 21;
|
||||
/* 20 */
|
||||
pub const MAIL_ERROR_SUBSCRIBE: libc::c_uint = 20;
|
||||
pub const MAIL_ERROR_STATUS: libc::c_uint = 19;
|
||||
pub const MAIL_ERROR_MEMORY: libc::c_uint = 18;
|
||||
pub const MAIL_ERROR_SELECT: libc::c_uint = 17;
|
||||
pub const MAIL_ERROR_EXAMINE: libc::c_uint = 16;
|
||||
pub const MAIL_ERROR_CHECK: libc::c_uint = 15;
|
||||
pub const MAIL_ERROR_RENAME: libc::c_uint = 14;
|
||||
pub const MAIL_ERROR_NOOP: libc::c_uint = 13;
|
||||
pub const MAIL_ERROR_LOGOUT: libc::c_uint = 12;
|
||||
pub const MAIL_ERROR_DELETE: libc::c_uint = 11;
|
||||
/* 10 */
|
||||
pub const MAIL_ERROR_CREATE: libc::c_uint = 10;
|
||||
pub const MAIL_ERROR_LOGIN: libc::c_uint = 9;
|
||||
pub const MAIL_ERROR_STREAM: libc::c_uint = 8;
|
||||
pub const MAIL_ERROR_FILE: libc::c_uint = 7;
|
||||
pub const MAIL_ERROR_BAD_STATE: libc::c_uint = 6;
|
||||
pub const MAIL_ERROR_CONNECT: libc::c_uint = 5;
|
||||
pub const MAIL_ERROR_UNKNOWN: libc::c_uint = 4;
|
||||
pub const MAIL_ERROR_NOT_IMPLEMENTED: libc::c_uint = 3;
|
||||
pub const MAIL_NO_ERROR_NON_AUTHENTICATED: libc::c_uint = 2;
|
||||
pub const MAIL_NO_ERROR_AUTHENTICATED: libc::c_uint = 1;
|
||||
pub const MAIL_NO_ERROR: libc::c_uint = 0;
|
||||
|
||||
pub const MAILIMF_ERROR_FILE: libc::c_uint = 4;
|
||||
pub const MAILIMF_ERROR_INVAL: libc::c_uint = 3;
|
||||
pub const MAILIMF_ERROR_MEMORY: libc::c_uint = 2;
|
||||
pub const MAILIMF_ERROR_PARSE: libc::c_uint = 1;
|
||||
pub const MAILIMF_NO_ERROR: libc::c_uint = 0;
|
||||
@@ -1,386 +0,0 @@
|
||||
use crate::clist::*;
|
||||
|
||||
use crate::mailimf::types::*;
|
||||
use crate::mailmime::types::*;
|
||||
|
||||
use std::ffi::CStr;
|
||||
|
||||
pub unsafe fn display_mime(mut mime: *mut Mailmime) {
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
println!("{}", (*mime).mm_type);
|
||||
|
||||
match (*mime).mm_type as u32 {
|
||||
MAILMIME_SINGLE => {
|
||||
println!("single part");
|
||||
}
|
||||
MAILMIME_MULTIPLE => {
|
||||
println!("multipart");
|
||||
}
|
||||
MAILMIME_MESSAGE => println!("message"),
|
||||
_ => {}
|
||||
}
|
||||
if !(*mime).mm_mime_fields.is_null() {
|
||||
if !(*(*(*mime).mm_mime_fields).fld_list).first.is_null() {
|
||||
print!("MIME headers begin");
|
||||
display_mime_fields((*mime).mm_mime_fields);
|
||||
println!("MIME headers end");
|
||||
}
|
||||
}
|
||||
display_mime_content((*mime).mm_content_type);
|
||||
match (*mime).mm_type as u32 {
|
||||
MAILMIME_SINGLE => {
|
||||
display_mime_data((*mime).mm_data.mm_single);
|
||||
}
|
||||
MAILMIME_MULTIPLE => {
|
||||
cur = (*(*mime).mm_data.mm_multipart.mm_mp_list).first;
|
||||
while !cur.is_null() {
|
||||
display_mime(
|
||||
(if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
}) as *mut Mailmime,
|
||||
);
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
}
|
||||
MAILMIME_MESSAGE => {
|
||||
if !(*mime).mm_data.mm_message.mm_fields.is_null() {
|
||||
if !(*(*(*mime).mm_data.mm_message.mm_fields).fld_list)
|
||||
.first
|
||||
.is_null()
|
||||
{
|
||||
println!("headers begin");
|
||||
display_fields((*mime).mm_data.mm_message.mm_fields);
|
||||
println!("headers end");
|
||||
}
|
||||
if !(*mime).mm_data.mm_message.mm_msg_mime.is_null() {
|
||||
display_mime((*mime).mm_data.mm_message.mm_msg_mime);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
unsafe fn display_mime_content(mut content_type: *mut mailmime_content) {
|
||||
print!("type: ");
|
||||
display_mime_type((*content_type).ct_type);
|
||||
println!(
|
||||
"/{}",
|
||||
CStr::from_ptr((*content_type).ct_subtype).to_str().unwrap()
|
||||
);
|
||||
}
|
||||
unsafe fn display_mime_type(mut type_0: *mut mailmime_type) {
|
||||
match (*type_0).tp_type {
|
||||
1 => {
|
||||
display_mime_discrete_type((*type_0).tp_data.tp_discrete_type);
|
||||
}
|
||||
2 => {
|
||||
display_mime_composite_type((*type_0).tp_data.tp_composite_type);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
unsafe fn display_mime_composite_type(mut ct: *mut mailmime_composite_type) {
|
||||
match (*ct).ct_type {
|
||||
1 => {
|
||||
print!("message");
|
||||
}
|
||||
2 => {
|
||||
print!("multipart");
|
||||
}
|
||||
3 => {
|
||||
print!("{}", CStr::from_ptr((*ct).ct_token).to_str().unwrap());
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
unsafe fn display_mime_discrete_type(mut discrete_type: *mut mailmime_discrete_type) {
|
||||
match (*discrete_type).dt_type {
|
||||
1 => {
|
||||
print!("text");
|
||||
}
|
||||
2 => {
|
||||
print!("image");
|
||||
}
|
||||
3 => {
|
||||
print!("audio");
|
||||
}
|
||||
4 => {
|
||||
print!("video");
|
||||
}
|
||||
5 => {
|
||||
print!("application");
|
||||
}
|
||||
6 => {
|
||||
print!("{}", (*discrete_type).dt_extension as u8 as char);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
pub unsafe fn display_mime_data(mut data: *mut mailmime_data) {
|
||||
match (*data).dt_type {
|
||||
0 => {
|
||||
println!(
|
||||
"data : {} bytes",
|
||||
(*data).dt_data.dt_text.dt_length as libc::c_uint,
|
||||
);
|
||||
}
|
||||
1 => {
|
||||
println!(
|
||||
"data (file) : {}",
|
||||
CStr::from_ptr((*data).dt_data.dt_filename)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
unsafe fn display_mime_dsp_parm(mut param: *mut mailmime_disposition_parm) {
|
||||
match (*param).pa_type {
|
||||
0 => {
|
||||
println!(
|
||||
"filename: {}",
|
||||
CStr::from_ptr((*param).pa_data.pa_filename)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
unsafe fn display_mime_disposition(mut disposition: *mut mailmime_disposition) {
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
cur = (*(*disposition).dsp_parms).first;
|
||||
while !cur.is_null() {
|
||||
let mut param: *mut mailmime_disposition_parm = 0 as *mut mailmime_disposition_parm;
|
||||
param = (if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
}) as *mut mailmime_disposition_parm;
|
||||
display_mime_dsp_parm(param);
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe fn display_mime_field(mut field: *mut mailmime_field) {
|
||||
match (*field).fld_type {
|
||||
1 => {
|
||||
print!("content-type: ");
|
||||
display_mime_content((*field).fld_data.fld_content);
|
||||
println!("");
|
||||
}
|
||||
6 => {
|
||||
display_mime_disposition((*field).fld_data.fld_disposition);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
unsafe fn display_mime_fields(mut fields: *mut mailmime_fields) {
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
cur = (*(*fields).fld_list).first;
|
||||
while !cur.is_null() {
|
||||
let mut field: *mut mailmime_field = 0 as *mut mailmime_field;
|
||||
field = (if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
}) as *mut mailmime_field;
|
||||
display_mime_field(field);
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe fn display_date_time(mut d: *mut mailimf_date_time) {
|
||||
print!(
|
||||
"{:02}/{:02}/{:02} {:02}:{:02}:{:02} +{:04}",
|
||||
(*d).dt_day,
|
||||
(*d).dt_month,
|
||||
(*d).dt_year,
|
||||
(*d).dt_hour,
|
||||
(*d).dt_min,
|
||||
(*d).dt_sec,
|
||||
(*d).dt_zone,
|
||||
);
|
||||
}
|
||||
unsafe fn display_orig_date(mut orig_date: *mut mailimf_orig_date) {
|
||||
display_date_time((*orig_date).dt_date_time);
|
||||
}
|
||||
unsafe fn display_mailbox(mut mb: *mut mailimf_mailbox) {
|
||||
if !(*mb).mb_display_name.is_null() {
|
||||
print!(
|
||||
"{}",
|
||||
CStr::from_ptr((*mb).mb_display_name).to_str().unwrap()
|
||||
);
|
||||
}
|
||||
print!("<{}>", CStr::from_ptr((*mb).mb_addr_spec).to_str().unwrap());
|
||||
}
|
||||
unsafe fn display_mailbox_list(mut mb_list: *mut mailimf_mailbox_list) {
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
cur = (*(*mb_list).mb_list).first;
|
||||
while !cur.is_null() {
|
||||
let mut mb: *mut mailimf_mailbox = 0 as *mut mailimf_mailbox;
|
||||
mb = (if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
}) as *mut mailimf_mailbox;
|
||||
display_mailbox(mb);
|
||||
if !if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
}
|
||||
.is_null()
|
||||
{
|
||||
print!(", ");
|
||||
}
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe fn display_group(mut group: *mut mailimf_group) {
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
print!(
|
||||
"{}: ",
|
||||
CStr::from_ptr((*group).grp_display_name).to_str().unwrap()
|
||||
);
|
||||
cur = (*(*(*group).grp_mb_list).mb_list).first;
|
||||
while !cur.is_null() {
|
||||
let mut mb: *mut mailimf_mailbox = 0 as *mut mailimf_mailbox;
|
||||
mb = (if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
}) as *mut mailimf_mailbox;
|
||||
display_mailbox(mb);
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
print!("; ");
|
||||
}
|
||||
unsafe fn display_address(mut a: *mut mailimf_address) {
|
||||
match (*a).ad_type {
|
||||
2 => {
|
||||
display_group((*a).ad_data.ad_group);
|
||||
}
|
||||
1 => {
|
||||
display_mailbox((*a).ad_data.ad_mailbox);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
unsafe fn display_address_list(mut addr_list: *mut mailimf_address_list) {
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
cur = (*(*addr_list).ad_list).first;
|
||||
while !cur.is_null() {
|
||||
let mut addr: *mut mailimf_address = 0 as *mut mailimf_address;
|
||||
addr = (if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
}) as *mut mailimf_address;
|
||||
display_address(addr);
|
||||
if !if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
}
|
||||
.is_null()
|
||||
{
|
||||
print!(", ");
|
||||
}
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe fn display_from(mut from: *mut mailimf_from) {
|
||||
display_mailbox_list((*from).frm_mb_list);
|
||||
}
|
||||
unsafe fn display_to(mut to: *mut mailimf_to) {
|
||||
display_address_list((*to).to_addr_list);
|
||||
}
|
||||
unsafe fn display_cc(mut cc: *mut mailimf_cc) {
|
||||
display_address_list((*cc).cc_addr_list);
|
||||
}
|
||||
unsafe fn display_subject(mut subject: *mut mailimf_subject) {
|
||||
print!("{}", CStr::from_ptr((*subject).sbj_value).to_str().unwrap());
|
||||
}
|
||||
unsafe fn display_field(mut field: *mut mailimf_field) {
|
||||
match (*field).fld_type {
|
||||
9 => {
|
||||
print!("Date: ");
|
||||
display_orig_date((*field).fld_data.fld_orig_date);
|
||||
println!("");
|
||||
}
|
||||
10 => {
|
||||
print!("From: ");
|
||||
display_from((*field).fld_data.fld_from);
|
||||
println!("");
|
||||
}
|
||||
13 => {
|
||||
print!("To: ");
|
||||
display_to((*field).fld_data.fld_to);
|
||||
println!("");
|
||||
}
|
||||
14 => {
|
||||
print!("Cc: ");
|
||||
display_cc((*field).fld_data.fld_cc);
|
||||
println!("");
|
||||
}
|
||||
19 => {
|
||||
print!("Subject: ");
|
||||
display_subject((*field).fld_data.fld_subject);
|
||||
println!("");
|
||||
}
|
||||
16 => {
|
||||
println!(
|
||||
"Message-ID: {}",
|
||||
CStr::from_ptr((*(*field).fld_data.fld_message_id).mid_value)
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
unsafe fn display_fields(mut fields: *mut mailimf_fields) {
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
cur = (*(*fields).fld_list).first;
|
||||
while !cur.is_null() {
|
||||
let mut f: *mut mailimf_field = 0 as *mut mailimf_field;
|
||||
f = (if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
}) as *mut mailimf_field;
|
||||
display_field(f);
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
#![deny(clippy::correctness)]
|
||||
// TODO: make all of these errors, such that clippy actually passes.
|
||||
#![warn(clippy::all, clippy::perf, clippy::not_unsafe_ptr_arg_deref)]
|
||||
// This is nice, but for now just annoying.
|
||||
#![allow(clippy::unreadable_literal)]
|
||||
#![feature(ptr_wrapping_offset_from)]
|
||||
#![allow(unused_attributes)]
|
||||
#![allow(unused_variables)]
|
||||
#![allow(mutable_transmutes)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(unused_assignments)]
|
||||
#![allow(unused_mut)]
|
||||
#![allow(unused_must_use)]
|
||||
#![feature(extern_types)]
|
||||
#![feature(const_raw_ptr_to_usize_cast)]
|
||||
|
||||
pub mod charconv;
|
||||
pub mod chash;
|
||||
pub mod clist;
|
||||
pub mod display;
|
||||
pub mod mailimf;
|
||||
pub mod mailmime;
|
||||
pub mod mmapstring;
|
||||
pub mod other;
|
||||
|
||||
pub use self::charconv::*;
|
||||
pub use self::chash::*;
|
||||
pub use self::clist::*;
|
||||
pub use self::display::*;
|
||||
pub use self::mailimf::*;
|
||||
pub use self::mailmime::*;
|
||||
pub use self::mmapstring::*;
|
||||
pub use self::other::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn mailmime_parse_test() {
|
||||
unsafe {
|
||||
let data = "MIME-Version: 1.0\
|
||||
Content-Type: multipart/mixed; boundary=frontier\
|
||||
\
|
||||
This is a message with multiple parts in MIME format.\
|
||||
--frontier\
|
||||
Content-Type: text/plain\
|
||||
\
|
||||
This is the body of the message.\
|
||||
--frontier\
|
||||
Content-Type: application/octet-stream\
|
||||
Content-Transfer-Encoding: base64\
|
||||
\
|
||||
PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\
|
||||
Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==\
|
||||
--frontier--";
|
||||
let c_data = std::ffi::CString::new(data).unwrap();
|
||||
|
||||
let mut current_index = 0;
|
||||
let mut mime = std::ptr::null_mut();
|
||||
let res = crate::mailmime::content::mailmime_parse(
|
||||
c_data.as_ptr(),
|
||||
data.len() as usize,
|
||||
&mut current_index,
|
||||
&mut mime,
|
||||
);
|
||||
|
||||
assert_eq!(res, MAIL_NO_ERROR as libc::c_int);
|
||||
assert!(!mime.is_null());
|
||||
|
||||
display_mime(mime);
|
||||
|
||||
mailmime::types::mailmime_free(mime);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,89 +0,0 @@
|
||||
use crate::clist::*;
|
||||
use crate::mailimf::types::*;
|
||||
use crate::other::*;
|
||||
|
||||
/*
|
||||
this function creates a new mailimf_fields structure with no fields
|
||||
*/
|
||||
pub unsafe fn mailimf_fields_new_empty() -> *mut mailimf_fields {
|
||||
let mut list: *mut clist = 0 as *mut clist;
|
||||
let mut fields_list: *mut mailimf_fields = 0 as *mut mailimf_fields;
|
||||
list = clist_new();
|
||||
if list.is_null() {
|
||||
return 0 as *mut mailimf_fields;
|
||||
}
|
||||
fields_list = mailimf_fields_new(list);
|
||||
if fields_list.is_null() {
|
||||
return 0 as *mut mailimf_fields;
|
||||
}
|
||||
return fields_list;
|
||||
}
|
||||
/*
|
||||
this function adds a field to the mailimf_fields structure
|
||||
|
||||
@return MAILIMF_NO_ERROR will be returned on success,
|
||||
other code will be returned otherwise
|
||||
*/
|
||||
pub unsafe fn mailimf_fields_add(
|
||||
mut fields: *mut mailimf_fields,
|
||||
mut field: *mut mailimf_field,
|
||||
) -> libc::c_int {
|
||||
let mut r: libc::c_int = 0;
|
||||
r = clist_insert_after(
|
||||
(*fields).fld_list,
|
||||
(*(*fields).fld_list).last,
|
||||
field as *mut libc::c_void,
|
||||
);
|
||||
if r < 0i32 {
|
||||
return MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
}
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
|
||||
/*
|
||||
mailimf_field_new_custom creates a new field of type optional
|
||||
|
||||
@param name should be allocated with malloc()
|
||||
@param value should be allocated with malloc()
|
||||
*/
|
||||
pub unsafe fn mailimf_field_new_custom(
|
||||
mut name: *mut libc::c_char,
|
||||
mut value: *mut libc::c_char,
|
||||
) -> *mut mailimf_field {
|
||||
let mut opt_field: *mut mailimf_optional_field = 0 as *mut mailimf_optional_field;
|
||||
let mut field: *mut mailimf_field = 0 as *mut mailimf_field;
|
||||
opt_field = mailimf_optional_field_new(name, value);
|
||||
if !opt_field.is_null() {
|
||||
field = mailimf_field_new(
|
||||
MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int,
|
||||
0 as *mut mailimf_return,
|
||||
0 as *mut mailimf_orig_date,
|
||||
0 as *mut mailimf_from,
|
||||
0 as *mut mailimf_sender,
|
||||
0 as *mut mailimf_to,
|
||||
0 as *mut mailimf_cc,
|
||||
0 as *mut mailimf_bcc,
|
||||
0 as *mut mailimf_message_id,
|
||||
0 as *mut mailimf_orig_date,
|
||||
0 as *mut mailimf_from,
|
||||
0 as *mut mailimf_sender,
|
||||
0 as *mut mailimf_reply_to,
|
||||
0 as *mut mailimf_to,
|
||||
0 as *mut mailimf_cc,
|
||||
0 as *mut mailimf_bcc,
|
||||
0 as *mut mailimf_message_id,
|
||||
0 as *mut mailimf_in_reply_to,
|
||||
0 as *mut mailimf_references,
|
||||
0 as *mut mailimf_subject,
|
||||
0 as *mut mailimf_comments,
|
||||
0 as *mut mailimf_keywords,
|
||||
opt_field,
|
||||
);
|
||||
if field.is_null() {
|
||||
mailimf_optional_field_free(opt_field);
|
||||
} else {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
return 0 as *mut mailimf_field;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,860 +0,0 @@
|
||||
use libc;
|
||||
use libc::toupper;
|
||||
|
||||
use crate::charconv::*;
|
||||
use crate::mailimf::*;
|
||||
use crate::mailmime::content::*;
|
||||
use crate::mailmime::types::*;
|
||||
use crate::mmapstring::*;
|
||||
use crate::other::*;
|
||||
|
||||
pub const TYPE_WORD: libc::c_uint = 1;
|
||||
pub const TYPE_ENCODED_WORD: libc::c_uint = 2;
|
||||
pub const MAILMIME_ENCODING_Q: libc::c_uint = 1;
|
||||
pub const MAILMIME_ENCODING_B: libc::c_uint = 0;
|
||||
pub const TYPE_ERROR: libc::c_uint = 0;
|
||||
|
||||
pub unsafe fn mailmime_encoded_phrase_parse(
|
||||
mut default_fromcode: *const libc::c_char,
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut tocode: *const libc::c_char,
|
||||
mut result: *mut *mut libc::c_char,
|
||||
) -> libc::c_int {
|
||||
let mut current_block: u64;
|
||||
let mut gphrase: *mut MMAPString = 0 as *mut MMAPString;
|
||||
let mut word: *mut mailmime_encoded_word = 0 as *mut mailmime_encoded_word;
|
||||
let mut first: libc::c_int = 0;
|
||||
let mut cur_token: size_t = 0;
|
||||
let mut r: libc::c_int = 0;
|
||||
let mut res: libc::c_int = 0;
|
||||
let mut str: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut wordutf8: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut type_0: libc::c_int = 0;
|
||||
let mut missing_closing_quote: libc::c_int = 0;
|
||||
cur_token = *indx;
|
||||
gphrase = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
|
||||
if gphrase.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int
|
||||
} else {
|
||||
first = 1i32;
|
||||
type_0 = TYPE_ERROR as libc::c_int;
|
||||
loop {
|
||||
let mut has_fwd: libc::c_int = 0;
|
||||
word = 0 as *mut mailmime_encoded_word;
|
||||
r = mailmime_encoded_word_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
&mut word,
|
||||
&mut has_fwd,
|
||||
&mut missing_closing_quote,
|
||||
);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
if 0 == first && 0 != has_fwd {
|
||||
if type_0 != TYPE_ENCODED_WORD as libc::c_int {
|
||||
if mmap_string_append_c(gphrase, ' ' as i32 as libc::c_char).is_null() {
|
||||
mailmime_encoded_word_free(word);
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
type_0 = TYPE_ENCODED_WORD as libc::c_int;
|
||||
wordutf8 = 0 as *mut libc::c_char;
|
||||
r = charconv(
|
||||
tocode,
|
||||
(*word).wd_charset,
|
||||
(*word).wd_text,
|
||||
strlen((*word).wd_text),
|
||||
&mut wordutf8,
|
||||
);
|
||||
match r {
|
||||
2 => {
|
||||
mailmime_encoded_word_free(word);
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
}
|
||||
1 => {
|
||||
r = charconv(
|
||||
tocode,
|
||||
b"iso-8859-1\x00" as *const u8 as *const libc::c_char,
|
||||
(*word).wd_text,
|
||||
strlen((*word).wd_text),
|
||||
&mut wordutf8,
|
||||
)
|
||||
}
|
||||
3 => {
|
||||
mailmime_encoded_word_free(word);
|
||||
res = MAILIMF_ERROR_PARSE as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
match r {
|
||||
2 => {
|
||||
mailmime_encoded_word_free(word);
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
}
|
||||
3 => {
|
||||
mailmime_encoded_word_free(word);
|
||||
res = MAILIMF_ERROR_PARSE as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
if !wordutf8.is_null() {
|
||||
if mmap_string_append(gphrase, wordutf8).is_null() {
|
||||
mailmime_encoded_word_free(word);
|
||||
free(wordutf8 as *mut libc::c_void);
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
} else {
|
||||
free(wordutf8 as *mut libc::c_void);
|
||||
}
|
||||
}
|
||||
mailmime_encoded_word_free(word);
|
||||
first = 0i32
|
||||
}
|
||||
}
|
||||
} else if !(r == MAILIMF_ERROR_PARSE as libc::c_int) {
|
||||
/* do nothing */
|
||||
res = r;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
}
|
||||
if !(r == MAILIMF_ERROR_PARSE as libc::c_int) {
|
||||
continue;
|
||||
}
|
||||
let mut raw_word: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
raw_word = 0 as *mut libc::c_char;
|
||||
r = mailmime_non_encoded_word_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
&mut raw_word,
|
||||
&mut has_fwd,
|
||||
);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
if 0 == first && 0 != has_fwd {
|
||||
if mmap_string_append_c(gphrase, ' ' as i32 as libc::c_char).is_null() {
|
||||
free(raw_word as *mut libc::c_void);
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
}
|
||||
}
|
||||
type_0 = TYPE_WORD as libc::c_int;
|
||||
wordutf8 = 0 as *mut libc::c_char;
|
||||
r = charconv(
|
||||
tocode,
|
||||
default_fromcode,
|
||||
raw_word,
|
||||
strlen(raw_word),
|
||||
&mut wordutf8,
|
||||
);
|
||||
match r {
|
||||
2 => {
|
||||
free(raw_word as *mut libc::c_void);
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
}
|
||||
1 | 3 => {
|
||||
free(raw_word as *mut libc::c_void);
|
||||
res = MAILIMF_ERROR_PARSE as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
if mmap_string_append(gphrase, wordutf8).is_null() {
|
||||
free(wordutf8 as *mut libc::c_void);
|
||||
free(raw_word as *mut libc::c_void);
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
} else {
|
||||
free(wordutf8 as *mut libc::c_void);
|
||||
free(raw_word as *mut libc::c_void);
|
||||
first = 0i32
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
r = mailimf_fws_parse(message, length, &mut cur_token);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
current_block = 5005389895767293342;
|
||||
break;
|
||||
}
|
||||
if mmap_string_append_c(gphrase, ' ' as i32 as libc::c_char).is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
} else {
|
||||
first = 0i32;
|
||||
current_block = 5005389895767293342;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
res = r;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
}
|
||||
}
|
||||
match current_block {
|
||||
5005389895767293342 => {
|
||||
if 0 != first {
|
||||
if cur_token != length {
|
||||
res = MAILIMF_ERROR_PARSE as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
} else {
|
||||
current_block = 7072655752890836508;
|
||||
}
|
||||
} else {
|
||||
current_block = 7072655752890836508;
|
||||
}
|
||||
match current_block {
|
||||
13246848547199022064 => {}
|
||||
_ => {
|
||||
str = strdup((*gphrase).str_0);
|
||||
if str.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int
|
||||
} else {
|
||||
mmap_string_free(gphrase);
|
||||
*result = str;
|
||||
*indx = cur_token;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
mmap_string_free(gphrase);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
unsafe fn mailmime_non_encoded_word_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut libc::c_char,
|
||||
mut p_has_fwd: *mut libc::c_int,
|
||||
) -> libc::c_int {
|
||||
let mut end: libc::c_int = 0;
|
||||
let mut cur_token: size_t = 0;
|
||||
let mut res: libc::c_int = 0;
|
||||
let mut text: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut r: libc::c_int = 0;
|
||||
let mut begin: size_t = 0;
|
||||
let mut state: libc::c_int = 0;
|
||||
let mut has_fwd: libc::c_int = 0;
|
||||
cur_token = *indx;
|
||||
has_fwd = 0i32;
|
||||
r = mailimf_fws_parse(message, length, &mut cur_token);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
has_fwd = 1i32
|
||||
}
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
begin = cur_token;
|
||||
state = 0i32;
|
||||
end = 0i32;
|
||||
while !(cur_token >= length) {
|
||||
let mut current_block_17: u64;
|
||||
match *message.offset(cur_token as isize) as libc::c_int {
|
||||
32 | 9 | 13 | 10 => {
|
||||
state = 0i32;
|
||||
end = 1i32;
|
||||
current_block_17 = 16924917904204750491;
|
||||
}
|
||||
61 => {
|
||||
state = 1i32;
|
||||
current_block_17 = 16924917904204750491;
|
||||
}
|
||||
63 => {
|
||||
if state == 1i32 {
|
||||
cur_token = cur_token.wrapping_sub(1);
|
||||
end = 1i32
|
||||
}
|
||||
current_block_17 = 10192508258555769664;
|
||||
}
|
||||
_ => {
|
||||
current_block_17 = 10192508258555769664;
|
||||
}
|
||||
}
|
||||
match current_block_17 {
|
||||
10192508258555769664 => state = 0i32,
|
||||
_ => {}
|
||||
}
|
||||
if 0 != end {
|
||||
break;
|
||||
}
|
||||
cur_token = cur_token.wrapping_add(1)
|
||||
}
|
||||
if cur_token.wrapping_sub(begin) == 0i32 as libc::size_t {
|
||||
res = MAILIMF_ERROR_PARSE as libc::c_int
|
||||
} else {
|
||||
text = malloc(
|
||||
cur_token
|
||||
.wrapping_sub(begin)
|
||||
.wrapping_add(1i32 as libc::size_t),
|
||||
) as *mut libc::c_char;
|
||||
if text.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int
|
||||
} else {
|
||||
memcpy(
|
||||
text as *mut libc::c_void,
|
||||
message.offset(begin as isize) as *const libc::c_void,
|
||||
cur_token.wrapping_sub(begin),
|
||||
);
|
||||
*text.offset(cur_token.wrapping_sub(begin) as isize) =
|
||||
'\u{0}' as i32 as libc::c_char;
|
||||
*indx = cur_token;
|
||||
*result = text;
|
||||
*p_has_fwd = has_fwd;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_encoded_word_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut mailmime_encoded_word,
|
||||
mut p_has_fwd: *mut libc::c_int,
|
||||
mut p_missing_closing_quote: *mut libc::c_int,
|
||||
) -> libc::c_int {
|
||||
let mut current_block: u64;
|
||||
/*
|
||||
Parse the following, when a unicode character encoding is split.
|
||||
=?UTF-8?B?4Lij4Liw4LmA4Lia4Li04LiU4LiE4Lin4Liy4Lih4Lih4Lix4LiZ4Liq4LmM?=
|
||||
=?UTF-8?B?4LmA4LiV4LmH4Lih4Lie4Li04LiB4Lix4LiUIFRSQU5TRk9STUVSUyA0IOC4?=
|
||||
=?UTF-8?B?oeC4seC4meC4quC5jOC4hOC4o+C4muC4l+C4uOC4geC4o+C4sOC4muC4miDg?=
|
||||
=?UTF-8?B?uJfguLXguYjguYDguJTguLXguKLguKfguYPguJnguYDguKHguLfguK3guIfg?=
|
||||
=?UTF-8?B?uYTguJfguKI=?=
|
||||
Expected result:
|
||||
ระเบิดความมันส์เต็มพิกัด TRANSFORMERS 4 มันส์ครบทุกระบบ ที่เดียวในเมืองไทย
|
||||
libetpan result:
|
||||
ระเบิดความมันส์เต็มพิกัด TRANSFORMERS 4 ?ันส์ครบทุกระบบ ??ี่เดียวในเมือง??ทย
|
||||
|
||||
See https://github.com/dinhviethoa/libetpan/pull/211
|
||||
*/
|
||||
let mut cur_token: size_t = 0;
|
||||
let mut charset: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut encoding: libc::c_int = 0;
|
||||
let mut body: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut old_body_len: size_t = 0;
|
||||
let mut text: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut end_encoding: size_t = 0;
|
||||
let mut lookfwd_cur_token: size_t = 0;
|
||||
let mut lookfwd_charset: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut lookfwd_encoding: libc::c_int = 0;
|
||||
let mut copy_len: size_t = 0;
|
||||
let mut decoded_token: size_t = 0;
|
||||
let mut decoded: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut decoded_len: size_t = 0;
|
||||
let mut ew: *mut mailmime_encoded_word = 0 as *mut mailmime_encoded_word;
|
||||
let mut r: libc::c_int = 0;
|
||||
let mut res: libc::c_int = 0;
|
||||
let mut opening_quote: libc::c_int = 0;
|
||||
let mut end: libc::c_int = 0;
|
||||
let mut has_fwd: libc::c_int = 0;
|
||||
let mut missing_closing_quote: libc::c_int = 0;
|
||||
cur_token = *indx;
|
||||
text = 0 as *mut libc::c_char;
|
||||
lookfwd_charset = 0 as *mut libc::c_char;
|
||||
missing_closing_quote = 0i32;
|
||||
has_fwd = 0i32;
|
||||
r = mailimf_fws_parse(message, length, &mut cur_token);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
has_fwd = 1i32
|
||||
}
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
opening_quote = 0i32;
|
||||
r = mailimf_char_parse(message, length, &mut cur_token, '\"' as i32 as libc::c_char);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
opening_quote = 1i32;
|
||||
current_block = 17788412896529399552;
|
||||
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
current_block = 17788412896529399552;
|
||||
} else {
|
||||
/* do nothing */
|
||||
res = r;
|
||||
current_block = 7995813543095296079;
|
||||
}
|
||||
match current_block {
|
||||
7995813543095296079 => {}
|
||||
_ => {
|
||||
r = mailimf_token_case_insensitive_len_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
b"=?\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
|
||||
strlen(b"=?\x00" as *const u8 as *const libc::c_char),
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
r = mailmime_charset_parse(message, length, &mut cur_token, &mut charset);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
r = mailimf_char_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
'?' as i32 as libc::c_char,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
r = mailmime_encoding_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
&mut encoding,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
r = mailimf_char_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
'?' as i32 as libc::c_char,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
lookfwd_cur_token = cur_token;
|
||||
body = 0 as *mut libc::c_char;
|
||||
old_body_len = 0i32 as size_t;
|
||||
loop {
|
||||
let mut has_base64_padding: libc::c_int = 0;
|
||||
end = 0i32;
|
||||
has_base64_padding = 0i32;
|
||||
end_encoding = cur_token;
|
||||
while !(end_encoding >= length) {
|
||||
if end_encoding.wrapping_add(1i32 as libc::size_t)
|
||||
< length
|
||||
{
|
||||
if *message.offset(end_encoding as isize)
|
||||
as libc::c_int
|
||||
== '?' as i32
|
||||
&& *message.offset(
|
||||
end_encoding
|
||||
.wrapping_add(1i32 as libc::size_t)
|
||||
as isize,
|
||||
)
|
||||
as libc::c_int
|
||||
== '=' as i32
|
||||
{
|
||||
end = 1i32
|
||||
}
|
||||
}
|
||||
if 0 != end {
|
||||
break;
|
||||
}
|
||||
end_encoding = end_encoding.wrapping_add(1)
|
||||
}
|
||||
copy_len = end_encoding.wrapping_sub(lookfwd_cur_token);
|
||||
if copy_len > 0i32 as libc::size_t {
|
||||
if encoding == MAILMIME_ENCODING_B as libc::c_int {
|
||||
if end_encoding >= 1i32 as libc::size_t {
|
||||
if *message.offset(
|
||||
end_encoding
|
||||
.wrapping_sub(1i32 as libc::size_t)
|
||||
as isize,
|
||||
)
|
||||
as libc::c_int
|
||||
== '=' as i32
|
||||
{
|
||||
has_base64_padding = 1i32
|
||||
}
|
||||
}
|
||||
}
|
||||
body = realloc(
|
||||
body as *mut libc::c_void,
|
||||
old_body_len
|
||||
.wrapping_add(copy_len)
|
||||
.wrapping_add(1i32 as libc::size_t),
|
||||
)
|
||||
as *mut libc::c_char;
|
||||
if body.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 13900684162107791171;
|
||||
break;
|
||||
} else {
|
||||
memcpy(
|
||||
body.offset(old_body_len as isize)
|
||||
as *mut libc::c_void,
|
||||
&*message.offset(cur_token as isize)
|
||||
as *const libc::c_char
|
||||
as *const libc::c_void,
|
||||
copy_len,
|
||||
);
|
||||
*body
|
||||
.offset(old_body_len.wrapping_add(copy_len)
|
||||
as isize) = '\u{0}' as i32 as libc::c_char;
|
||||
old_body_len = (old_body_len as libc::size_t)
|
||||
.wrapping_add(copy_len)
|
||||
as size_t
|
||||
as size_t
|
||||
}
|
||||
}
|
||||
cur_token = end_encoding;
|
||||
r = mailimf_token_case_insensitive_len_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
b"?=\x00" as *const u8 as *const libc::c_char
|
||||
as *mut libc::c_char,
|
||||
strlen(b"?=\x00" as *const u8 as *const libc::c_char),
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
current_block = 2652804691515851435;
|
||||
break;
|
||||
}
|
||||
if 0 != has_base64_padding {
|
||||
current_block = 2652804691515851435;
|
||||
break;
|
||||
}
|
||||
lookfwd_cur_token = cur_token;
|
||||
r = mailimf_fws_parse(
|
||||
message,
|
||||
length,
|
||||
&mut lookfwd_cur_token,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int
|
||||
&& r != MAILIMF_ERROR_PARSE as libc::c_int
|
||||
{
|
||||
current_block = 2652804691515851435;
|
||||
break;
|
||||
}
|
||||
r = mailimf_token_case_insensitive_len_parse(
|
||||
message,
|
||||
length,
|
||||
&mut lookfwd_cur_token,
|
||||
b"=?\x00" as *const u8 as *const libc::c_char
|
||||
as *mut libc::c_char,
|
||||
strlen(b"=?\x00" as *const u8 as *const libc::c_char),
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
current_block = 2652804691515851435;
|
||||
break;
|
||||
}
|
||||
r = mailmime_charset_parse(
|
||||
message,
|
||||
length,
|
||||
&mut lookfwd_cur_token,
|
||||
&mut lookfwd_charset,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
current_block = 2652804691515851435;
|
||||
break;
|
||||
}
|
||||
r = mailimf_char_parse(
|
||||
message,
|
||||
length,
|
||||
&mut lookfwd_cur_token,
|
||||
'?' as i32 as libc::c_char,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
current_block = 2652804691515851435;
|
||||
break;
|
||||
}
|
||||
r = mailmime_encoding_parse(
|
||||
message,
|
||||
length,
|
||||
&mut lookfwd_cur_token,
|
||||
&mut lookfwd_encoding,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
current_block = 2652804691515851435;
|
||||
break;
|
||||
}
|
||||
r = mailimf_char_parse(
|
||||
message,
|
||||
length,
|
||||
&mut lookfwd_cur_token,
|
||||
'?' as i32 as libc::c_char,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
current_block = 2652804691515851435;
|
||||
break;
|
||||
}
|
||||
if strcasecmp(charset, lookfwd_charset) == 0i32
|
||||
&& encoding == lookfwd_encoding
|
||||
{
|
||||
cur_token = lookfwd_cur_token;
|
||||
mailmime_charset_free(lookfwd_charset);
|
||||
lookfwd_charset = 0 as *mut libc::c_char
|
||||
} else {
|
||||
/* the next charset is not matched with the current one,
|
||||
therefore exit the loop to decode the body appended so far */
|
||||
current_block = 2652804691515851435;
|
||||
break;
|
||||
}
|
||||
}
|
||||
match current_block {
|
||||
2652804691515851435 => {
|
||||
if !lookfwd_charset.is_null() {
|
||||
mailmime_charset_free(lookfwd_charset);
|
||||
lookfwd_charset = 0 as *mut libc::c_char
|
||||
}
|
||||
if body.is_null() {
|
||||
body = strdup(
|
||||
b"\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
if body.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 13900684162107791171;
|
||||
} else {
|
||||
current_block = 16778110326724371720;
|
||||
}
|
||||
} else {
|
||||
current_block = 16778110326724371720;
|
||||
}
|
||||
match current_block {
|
||||
13900684162107791171 => {}
|
||||
_ => {
|
||||
decoded_token = 0i32 as size_t;
|
||||
decoded_len = 0i32 as size_t;
|
||||
decoded = 0 as *mut libc::c_char;
|
||||
match encoding {
|
||||
0 => {
|
||||
r = mailmime_base64_body_parse(
|
||||
body,
|
||||
strlen(body),
|
||||
&mut decoded_token,
|
||||
&mut decoded,
|
||||
&mut decoded_len,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int
|
||||
{
|
||||
res = r;
|
||||
current_block =
|
||||
13900684162107791171;
|
||||
} else {
|
||||
current_block = 7337917895049117968;
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
r =
|
||||
mailmime_quoted_printable_body_parse(body,
|
||||
strlen(body),
|
||||
&mut decoded_token,
|
||||
&mut decoded,
|
||||
&mut decoded_len,
|
||||
1i32);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int
|
||||
{
|
||||
res = r;
|
||||
current_block =
|
||||
13900684162107791171;
|
||||
} else {
|
||||
current_block = 7337917895049117968;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
current_block = 7337917895049117968;
|
||||
}
|
||||
}
|
||||
match current_block {
|
||||
13900684162107791171 => {}
|
||||
_ => {
|
||||
text =
|
||||
malloc(decoded_len.wrapping_add(
|
||||
1i32 as libc::size_t,
|
||||
))
|
||||
as *mut libc::c_char;
|
||||
if text.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY
|
||||
as libc::c_int
|
||||
} else {
|
||||
if decoded_len
|
||||
> 0i32 as libc::size_t
|
||||
{
|
||||
memcpy(
|
||||
text as *mut libc::c_void,
|
||||
decoded
|
||||
as *const libc::c_void,
|
||||
decoded_len,
|
||||
);
|
||||
}
|
||||
*text
|
||||
.offset(decoded_len as isize) =
|
||||
'\u{0}' as i32 as libc::c_char;
|
||||
if 0 != opening_quote {
|
||||
r = mailimf_char_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
'\"' as i32 as libc::c_char,
|
||||
);
|
||||
if r == MAILIMF_ERROR_PARSE
|
||||
as libc::c_int
|
||||
{
|
||||
missing_closing_quote = 1i32
|
||||
}
|
||||
}
|
||||
if strcasecmp(
|
||||
charset,
|
||||
b"utf8\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
free(
|
||||
charset
|
||||
as *mut libc::c_void,
|
||||
);
|
||||
charset = strdup(
|
||||
b"utf-8\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
)
|
||||
}
|
||||
ew = mailmime_encoded_word_new(
|
||||
charset, text,
|
||||
);
|
||||
if ew.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY
|
||||
as libc::c_int
|
||||
} else {
|
||||
*result = ew;
|
||||
*indx = cur_token;
|
||||
*p_has_fwd = has_fwd;
|
||||
*p_missing_closing_quote =
|
||||
missing_closing_quote;
|
||||
mailmime_decoded_part_free(
|
||||
decoded,
|
||||
);
|
||||
free(body as *mut libc::c_void);
|
||||
return MAILIMF_NO_ERROR
|
||||
as libc::c_int;
|
||||
}
|
||||
}
|
||||
mailmime_decoded_part_free(decoded);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
free(body as *mut libc::c_void);
|
||||
mailmime_encoded_text_free(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
mailmime_charset_free(charset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
unsafe fn mailmime_encoding_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut libc::c_int,
|
||||
) -> libc::c_int {
|
||||
let mut cur_token: size_t = 0;
|
||||
let mut encoding: libc::c_int = 0;
|
||||
cur_token = *indx;
|
||||
if cur_token >= length {
|
||||
return MAILIMF_ERROR_PARSE as libc::c_int;
|
||||
}
|
||||
match toupper(*message.offset(cur_token as isize) as libc::c_uchar as libc::c_int)
|
||||
as libc::c_char as libc::c_int
|
||||
{
|
||||
81 => encoding = MAILMIME_ENCODING_Q as libc::c_int,
|
||||
66 => encoding = MAILMIME_ENCODING_B as libc::c_int,
|
||||
_ => return MAILIMF_ERROR_INVAL as libc::c_int,
|
||||
}
|
||||
cur_token = cur_token.wrapping_add(1);
|
||||
*result = encoding;
|
||||
*indx = cur_token;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
|
||||
/*
|
||||
* libEtPan! -- a mail stuff library
|
||||
*
|
||||
* Copyright (C) 2001, 2005 - DINH Viet Hoa
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the libEtPan! project nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
/*
|
||||
* $Id: mailmime_decode.c,v 1.37 2010/11/16 20:52:28 hoa Exp $
|
||||
*/
|
||||
/*
|
||||
RFC 2047 : MIME (Multipurpose Internet Mail Extensions) Part Three:
|
||||
Message Header Extensions for Non-ASCII Text
|
||||
*/
|
||||
unsafe fn mailmime_charset_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut charset: *mut *mut libc::c_char,
|
||||
) -> libc::c_int {
|
||||
return mailmime_etoken_parse(message, length, indx, charset);
|
||||
}
|
||||
unsafe fn mailmime_etoken_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut libc::c_char,
|
||||
) -> libc::c_int {
|
||||
return mailimf_custom_string_parse(message, length, indx, result, Some(is_etoken_char));
|
||||
}
|
||||
|
||||
unsafe fn is_etoken_char(mut ch: libc::c_char) -> libc::c_int {
|
||||
let mut uch: libc::c_uchar = ch as libc::c_uchar;
|
||||
if (uch as libc::c_int) < 31i32 {
|
||||
return 0i32;
|
||||
}
|
||||
match uch as libc::c_int {
|
||||
32 | 40 | 41 | 60 | 62 | 64 | 44 | 59 | 58 | 34 | 47 | 91 | 93 | 63 | 61 => return 0i32,
|
||||
_ => {}
|
||||
}
|
||||
return 1i32;
|
||||
}
|
||||
@@ -1,583 +0,0 @@
|
||||
use libc::{self, toupper};
|
||||
|
||||
use crate::clist::*;
|
||||
use crate::mailimf::*;
|
||||
use crate::mailmime::types::*;
|
||||
use crate::mailmime::*;
|
||||
use crate::other::*;
|
||||
|
||||
pub const MAILMIME_DISPOSITION_TYPE_EXTENSION: libc::c_uint = 3;
|
||||
pub const MAILMIME_DISPOSITION_TYPE_ATTACHMENT: libc::c_uint = 2;
|
||||
pub const MAILMIME_DISPOSITION_TYPE_INLINE: libc::c_uint = 1;
|
||||
pub const MAILMIME_DISPOSITION_TYPE_ERROR: libc::c_uint = 0;
|
||||
|
||||
pub unsafe fn mailmime_disposition_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut mailmime_disposition,
|
||||
) -> libc::c_int {
|
||||
let mut current_block: u64;
|
||||
let mut final_token: size_t = 0;
|
||||
let mut cur_token: size_t = 0;
|
||||
let mut dsp_type: *mut mailmime_disposition_type = 0 as *mut mailmime_disposition_type;
|
||||
let mut list: *mut clist = 0 as *mut clist;
|
||||
let mut dsp: *mut mailmime_disposition = 0 as *mut mailmime_disposition;
|
||||
let mut r: libc::c_int = 0;
|
||||
let mut res: libc::c_int = 0;
|
||||
cur_token = *indx;
|
||||
r = mailmime_disposition_type_parse(message, length, &mut cur_token, &mut dsp_type);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
list = clist_new();
|
||||
if list.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int
|
||||
} else {
|
||||
loop {
|
||||
let mut param: *mut mailmime_disposition_parm = 0 as *mut mailmime_disposition_parm;
|
||||
final_token = cur_token;
|
||||
r = mailimf_unstrict_char_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
';' as i32 as libc::c_char,
|
||||
);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
param = 0 as *mut mailmime_disposition_parm;
|
||||
r = mailmime_disposition_parm_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
&mut param,
|
||||
);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
r = clist_insert_after(list, (*list).last, param as *mut libc::c_void);
|
||||
if !(r < 0i32) {
|
||||
continue;
|
||||
}
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 18290070879695007868;
|
||||
break;
|
||||
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
cur_token = final_token;
|
||||
current_block = 652864300344834934;
|
||||
break;
|
||||
} else {
|
||||
res = r;
|
||||
current_block = 18290070879695007868;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/* do nothing */
|
||||
if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
current_block = 652864300344834934;
|
||||
break;
|
||||
}
|
||||
res = r;
|
||||
current_block = 18290070879695007868;
|
||||
break;
|
||||
}
|
||||
}
|
||||
match current_block {
|
||||
652864300344834934 => {
|
||||
dsp = mailmime_disposition_new(dsp_type, list);
|
||||
if dsp.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int
|
||||
} else {
|
||||
*result = dsp;
|
||||
*indx = cur_token;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
clist_foreach(
|
||||
list,
|
||||
::std::mem::transmute::<
|
||||
Option<unsafe fn(_: *mut mailmime_disposition_parm) -> ()>,
|
||||
clist_func,
|
||||
>(Some(mailmime_disposition_parm_free)),
|
||||
0 as *mut libc::c_void,
|
||||
);
|
||||
clist_free(list);
|
||||
}
|
||||
mailmime_disposition_type_free(dsp_type);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
/*
|
||||
* libEtPan! -- a mail stuff library
|
||||
*
|
||||
* Copyright (C) 2001, 2005 - DINH Viet Hoa
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the libEtPan! project nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
/*
|
||||
* $Id: mailmime_disposition.c,v 1.17 2011/05/03 16:30:22 hoa Exp $
|
||||
*/
|
||||
unsafe fn mailmime_disposition_parm_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut mailmime_disposition_parm,
|
||||
) -> libc::c_int {
|
||||
let mut current_block: u64;
|
||||
let mut filename: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut creation_date: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut modification_date: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut read_date: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut size: size_t = 0;
|
||||
let mut parameter: *mut mailmime_parameter = 0 as *mut mailmime_parameter;
|
||||
let mut cur_token: size_t = 0;
|
||||
let mut dsp_parm: *mut mailmime_disposition_parm = 0 as *mut mailmime_disposition_parm;
|
||||
let mut type_0: libc::c_int = 0;
|
||||
let mut guessed_type: libc::c_int = 0;
|
||||
let mut r: libc::c_int = 0;
|
||||
let mut res: libc::c_int = 0;
|
||||
cur_token = *indx;
|
||||
filename = 0 as *mut libc::c_char;
|
||||
creation_date = 0 as *mut libc::c_char;
|
||||
modification_date = 0 as *mut libc::c_char;
|
||||
read_date = 0 as *mut libc::c_char;
|
||||
size = 0i32 as size_t;
|
||||
parameter = 0 as *mut mailmime_parameter;
|
||||
r = mailimf_cfws_parse(message, length, &mut cur_token);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
guessed_type = mailmime_disposition_guess_type(message, length, cur_token);
|
||||
type_0 = MAILMIME_DISPOSITION_PARM_PARAMETER as libc::c_int;
|
||||
match guessed_type {
|
||||
0 => {
|
||||
r = mailmime_filename_parm_parse(message, length, &mut cur_token, &mut filename);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
type_0 = guessed_type;
|
||||
current_block = 13826291924415791078;
|
||||
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
current_block = 13826291924415791078;
|
||||
} else {
|
||||
/* do nothing */
|
||||
res = r;
|
||||
current_block = 9120900589700563584;
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
r = mailmime_creation_date_parm_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
&mut creation_date,
|
||||
);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
type_0 = guessed_type;
|
||||
current_block = 13826291924415791078;
|
||||
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
current_block = 13826291924415791078;
|
||||
} else {
|
||||
/* do nothing */
|
||||
res = r;
|
||||
current_block = 9120900589700563584;
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
r = mailmime_modification_date_parm_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
&mut modification_date,
|
||||
);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
type_0 = guessed_type;
|
||||
current_block = 13826291924415791078;
|
||||
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
current_block = 13826291924415791078;
|
||||
} else {
|
||||
/* do nothing */
|
||||
res = r;
|
||||
current_block = 9120900589700563584;
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
r = mailmime_read_date_parm_parse(message, length, &mut cur_token, &mut read_date);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
type_0 = guessed_type;
|
||||
current_block = 13826291924415791078;
|
||||
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
current_block = 13826291924415791078;
|
||||
} else {
|
||||
/* do nothing */
|
||||
res = r;
|
||||
current_block = 9120900589700563584;
|
||||
}
|
||||
}
|
||||
4 => {
|
||||
r = mailmime_size_parm_parse(message, length, &mut cur_token, &mut size);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
type_0 = guessed_type;
|
||||
current_block = 13826291924415791078;
|
||||
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
current_block = 13826291924415791078;
|
||||
} else {
|
||||
/* do nothing */
|
||||
res = r;
|
||||
current_block = 9120900589700563584;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
current_block = 13826291924415791078;
|
||||
}
|
||||
}
|
||||
match current_block {
|
||||
9120900589700563584 => {}
|
||||
_ => {
|
||||
if type_0 == MAILMIME_DISPOSITION_PARM_PARAMETER as libc::c_int {
|
||||
r = mailmime_parameter_parse(message, length, &mut cur_token, &mut parameter);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
type_0 = guessed_type;
|
||||
res = r;
|
||||
current_block = 9120900589700563584;
|
||||
} else {
|
||||
current_block = 6721012065216013753;
|
||||
}
|
||||
} else {
|
||||
current_block = 6721012065216013753;
|
||||
}
|
||||
match current_block {
|
||||
9120900589700563584 => {}
|
||||
_ => {
|
||||
dsp_parm = mailmime_disposition_parm_new(
|
||||
type_0,
|
||||
filename,
|
||||
creation_date,
|
||||
modification_date,
|
||||
read_date,
|
||||
size,
|
||||
parameter,
|
||||
);
|
||||
if dsp_parm.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
if !filename.is_null() {
|
||||
mailmime_filename_parm_free(filename);
|
||||
}
|
||||
if !creation_date.is_null() {
|
||||
mailmime_creation_date_parm_free(creation_date);
|
||||
}
|
||||
if !modification_date.is_null() {
|
||||
mailmime_modification_date_parm_free(modification_date);
|
||||
}
|
||||
if !read_date.is_null() {
|
||||
mailmime_read_date_parm_free(read_date);
|
||||
}
|
||||
if !parameter.is_null() {
|
||||
mailmime_parameter_free(parameter);
|
||||
}
|
||||
} else {
|
||||
*result = dsp_parm;
|
||||
*indx = cur_token;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
unsafe fn mailmime_size_parm_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut size_t,
|
||||
) -> libc::c_int {
|
||||
let mut value: uint32_t = 0;
|
||||
let mut cur_token: size_t = 0;
|
||||
let mut r: libc::c_int = 0;
|
||||
cur_token = *indx;
|
||||
r = mailimf_token_case_insensitive_len_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
b"size\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
|
||||
strlen(b"size\x00" as *const u8 as *const libc::c_char),
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_unstrict_char_parse(message, length, &mut cur_token, '=' as i32 as libc::c_char);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_cfws_parse(message, length, &mut cur_token);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_number_parse(message, length, &mut cur_token, &mut value);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
*indx = cur_token;
|
||||
*result = value as size_t;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
unsafe fn mailmime_read_date_parm_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut libc::c_char,
|
||||
) -> libc::c_int {
|
||||
let mut value: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut cur_token: size_t = 0;
|
||||
let mut r: libc::c_int = 0;
|
||||
cur_token = *indx;
|
||||
r = mailimf_token_case_insensitive_len_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
b"read-date\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
|
||||
strlen(b"read-date\x00" as *const u8 as *const libc::c_char),
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_unstrict_char_parse(message, length, &mut cur_token, '=' as i32 as libc::c_char);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_cfws_parse(message, length, &mut cur_token);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailmime_quoted_date_time_parse(message, length, &mut cur_token, &mut value);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
*indx = cur_token;
|
||||
*result = value;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
unsafe fn mailmime_quoted_date_time_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut libc::c_char,
|
||||
) -> libc::c_int {
|
||||
return mailimf_quoted_string_parse(message, length, indx, result);
|
||||
}
|
||||
unsafe fn mailmime_modification_date_parm_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut libc::c_char,
|
||||
) -> libc::c_int {
|
||||
let mut value: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut cur_token: size_t = 0;
|
||||
let mut r: libc::c_int = 0;
|
||||
cur_token = *indx;
|
||||
r = mailimf_token_case_insensitive_len_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
b"modification-date\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
|
||||
strlen(b"modification-date\x00" as *const u8 as *const libc::c_char),
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_unstrict_char_parse(message, length, &mut cur_token, '=' as i32 as libc::c_char);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_cfws_parse(message, length, &mut cur_token);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailmime_quoted_date_time_parse(message, length, &mut cur_token, &mut value);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
*indx = cur_token;
|
||||
*result = value;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
unsafe fn mailmime_creation_date_parm_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut libc::c_char,
|
||||
) -> libc::c_int {
|
||||
let mut value: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut r: libc::c_int = 0;
|
||||
let mut cur_token: size_t = 0;
|
||||
cur_token = *indx;
|
||||
r = mailimf_token_case_insensitive_len_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
b"creation-date\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
|
||||
strlen(b"creation-date\x00" as *const u8 as *const libc::c_char),
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_unstrict_char_parse(message, length, &mut cur_token, '=' as i32 as libc::c_char);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_cfws_parse(message, length, &mut cur_token);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailmime_quoted_date_time_parse(message, length, &mut cur_token, &mut value);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
*indx = cur_token;
|
||||
*result = value;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
unsafe fn mailmime_filename_parm_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut libc::c_char,
|
||||
) -> libc::c_int {
|
||||
let mut value: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut r: libc::c_int = 0;
|
||||
let mut cur_token: size_t = 0;
|
||||
cur_token = *indx;
|
||||
r = mailimf_token_case_insensitive_len_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
b"filename\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
|
||||
strlen(b"filename\x00" as *const u8 as *const libc::c_char),
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_unstrict_char_parse(message, length, &mut cur_token, '=' as i32 as libc::c_char);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_cfws_parse(message, length, &mut cur_token);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailmime_value_parse(message, length, &mut cur_token, &mut value);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
*indx = cur_token;
|
||||
*result = value;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_disposition_guess_type(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: size_t,
|
||||
) -> libc::c_int {
|
||||
if indx >= length {
|
||||
return MAILMIME_DISPOSITION_PARM_PARAMETER as libc::c_int;
|
||||
}
|
||||
match toupper(*message.offset(indx as isize) as libc::c_uchar as libc::c_int) as libc::c_char
|
||||
as libc::c_int
|
||||
{
|
||||
70 => return MAILMIME_DISPOSITION_PARM_FILENAME as libc::c_int,
|
||||
67 => return MAILMIME_DISPOSITION_PARM_CREATION_DATE as libc::c_int,
|
||||
77 => return MAILMIME_DISPOSITION_PARM_MODIFICATION_DATE as libc::c_int,
|
||||
82 => return MAILMIME_DISPOSITION_PARM_READ_DATE as libc::c_int,
|
||||
83 => return MAILMIME_DISPOSITION_PARM_SIZE as libc::c_int,
|
||||
_ => return MAILMIME_DISPOSITION_PARM_PARAMETER as libc::c_int,
|
||||
};
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_disposition_type_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut mailmime_disposition_type,
|
||||
) -> libc::c_int {
|
||||
let mut cur_token: size_t = 0;
|
||||
let mut type_0: libc::c_int = 0;
|
||||
let mut extension: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut dsp_type: *mut mailmime_disposition_type = 0 as *mut mailmime_disposition_type;
|
||||
let mut r: libc::c_int = 0;
|
||||
let mut res: libc::c_int = 0;
|
||||
cur_token = *indx;
|
||||
r = mailimf_cfws_parse(message, length, &mut cur_token);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
type_0 = MAILMIME_DISPOSITION_TYPE_ERROR as libc::c_int;
|
||||
extension = 0 as *mut libc::c_char;
|
||||
r = mailimf_token_case_insensitive_len_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
b"inline\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
|
||||
strlen(b"inline\x00" as *const u8 as *const libc::c_char),
|
||||
);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
type_0 = MAILMIME_DISPOSITION_TYPE_INLINE as libc::c_int
|
||||
}
|
||||
if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
r = mailimf_token_case_insensitive_len_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
b"attachment\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
|
||||
strlen(b"attachment\x00" as *const u8 as *const libc::c_char),
|
||||
);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
type_0 = MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int
|
||||
}
|
||||
}
|
||||
if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
r = mailmime_extension_token_parse(message, length, &mut cur_token, &mut extension);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
type_0 = MAILMIME_DISPOSITION_TYPE_EXTENSION as libc::c_int
|
||||
}
|
||||
}
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
dsp_type = mailmime_disposition_type_new(type_0, extension);
|
||||
if dsp_type.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
if !extension.is_null() {
|
||||
free(extension as *mut libc::c_void);
|
||||
}
|
||||
} else {
|
||||
*result = dsp_type;
|
||||
*indx = cur_token;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,891 +0,0 @@
|
||||
use crate::clist::*;
|
||||
use crate::mailimf::types::*;
|
||||
use crate::mmapstring::*;
|
||||
use crate::other::*;
|
||||
|
||||
pub const MAILMIME_MECHANISM_TOKEN: libc::c_uint = 6;
|
||||
pub const MAILMIME_MECHANISM_BASE64: libc::c_uint = 5;
|
||||
pub const MAILMIME_MECHANISM_QUOTED_PRINTABLE: libc::c_uint = 4;
|
||||
pub const MAILMIME_MECHANISM_BINARY: libc::c_uint = 3;
|
||||
pub const MAILMIME_MECHANISM_8BIT: libc::c_uint = 2;
|
||||
pub const MAILMIME_MECHANISM_7BIT: libc::c_uint = 1;
|
||||
pub const MAILMIME_MECHANISM_ERROR: libc::c_uint = 0;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_composite_type {
|
||||
pub ct_type: libc::c_int,
|
||||
pub ct_token: *mut libc::c_char,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_content {
|
||||
pub ct_type: *mut mailmime_type,
|
||||
pub ct_subtype: *mut libc::c_char,
|
||||
pub ct_parameters: *mut clist,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_type {
|
||||
pub tp_type: libc::c_int,
|
||||
pub tp_data: unnamed,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub union unnamed {
|
||||
pub tp_discrete_type: *mut mailmime_discrete_type,
|
||||
pub tp_composite_type: *mut mailmime_composite_type,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_discrete_type {
|
||||
pub dt_type: libc::c_int,
|
||||
pub dt_extension: *mut libc::c_char,
|
||||
}
|
||||
pub type unnamed_0 = libc::c_uint;
|
||||
pub const MAILMIME_FIELD_LOCATION: unnamed_0 = 8;
|
||||
pub const MAILMIME_FIELD_LANGUAGE: unnamed_0 = 7;
|
||||
pub const MAILMIME_FIELD_DISPOSITION: unnamed_0 = 6;
|
||||
pub const MAILMIME_FIELD_VERSION: unnamed_0 = 5;
|
||||
pub const MAILMIME_FIELD_DESCRIPTION: unnamed_0 = 4;
|
||||
pub const MAILMIME_FIELD_ID: unnamed_0 = 3;
|
||||
pub const MAILMIME_FIELD_TRANSFER_ENCODING: unnamed_0 = 2;
|
||||
pub const MAILMIME_FIELD_TYPE: unnamed_0 = 1;
|
||||
pub const MAILMIME_FIELD_NONE: unnamed_0 = 0;
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_field {
|
||||
pub fld_type: libc::c_int,
|
||||
pub fld_data: unnamed_1,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub union unnamed_1 {
|
||||
pub fld_content: *mut mailmime_content,
|
||||
pub fld_encoding: *mut mailmime_mechanism,
|
||||
pub fld_id: *mut libc::c_char,
|
||||
pub fld_description: *mut libc::c_char,
|
||||
pub fld_version: uint32_t,
|
||||
pub fld_disposition: *mut mailmime_disposition,
|
||||
pub fld_language: *mut mailmime_language,
|
||||
pub fld_location: *mut libc::c_char,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_language {
|
||||
pub lg_list: *mut clist,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_disposition {
|
||||
pub dsp_type: *mut mailmime_disposition_type,
|
||||
pub dsp_parms: *mut clist,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_disposition_type {
|
||||
pub dsp_type: libc::c_int,
|
||||
pub dsp_extension: *mut libc::c_char,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_mechanism {
|
||||
pub enc_type: libc::c_int,
|
||||
pub enc_token: *mut libc::c_char,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_fields {
|
||||
pub fld_list: *mut clist,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_parameter {
|
||||
pub pa_name: *mut libc::c_char,
|
||||
pub pa_value: *mut libc::c_char,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_disposition_parm {
|
||||
pub pa_type: libc::c_int,
|
||||
pub pa_data: unnamed_3,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub union unnamed_3 {
|
||||
pub pa_filename: *mut libc::c_char,
|
||||
pub pa_creation_date: *mut libc::c_char,
|
||||
pub pa_modification_date: *mut libc::c_char,
|
||||
pub pa_read_date: *mut libc::c_char,
|
||||
pub pa_size: size_t,
|
||||
pub pa_parameter: *mut mailmime_parameter,
|
||||
}
|
||||
pub const MAILMIME_DISPOSITION_PARM_PARAMETER: unnamed_11 = 5;
|
||||
pub const MAILMIME_DISPOSITION_PARM_READ_DATE: unnamed_11 = 3;
|
||||
pub const MAILMIME_DISPOSITION_PARM_MODIFICATION_DATE: unnamed_11 = 2;
|
||||
pub const MAILMIME_DISPOSITION_PARM_CREATION_DATE: unnamed_11 = 1;
|
||||
pub const MAILMIME_DISPOSITION_PARM_FILENAME: unnamed_11 = 0;
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_multipart_body {
|
||||
pub bd_list: *mut clist,
|
||||
}
|
||||
pub type unnamed_4 = libc::c_uint;
|
||||
pub const MAILMIME_DATA_FILE: unnamed_4 = 1;
|
||||
pub const MAILMIME_DATA_TEXT: unnamed_4 = 0;
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_data {
|
||||
pub dt_type: libc::c_int,
|
||||
pub dt_encoding: libc::c_int,
|
||||
pub dt_encoded: libc::c_int,
|
||||
pub dt_data: unnamed_5,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub union unnamed_5 {
|
||||
pub dt_text: unnamed_6,
|
||||
pub dt_filename: *mut libc::c_char,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct unnamed_6 {
|
||||
pub dt_data: *const libc::c_char,
|
||||
pub dt_length: size_t,
|
||||
}
|
||||
pub type unnamed_7 = libc::c_uint;
|
||||
pub const MAILMIME_MESSAGE: unnamed_7 = 3;
|
||||
pub const MAILMIME_MULTIPLE: unnamed_7 = 2;
|
||||
pub const MAILMIME_SINGLE: unnamed_7 = 1;
|
||||
pub const MAILMIME_NONE: unnamed_7 = 0;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct Mailmime {
|
||||
pub mm_parent_type: libc::c_int,
|
||||
pub mm_parent: *mut Mailmime,
|
||||
pub mm_multipart_pos: *mut clistiter,
|
||||
pub mm_type: libc::c_int,
|
||||
pub mm_mime_start: *const libc::c_char,
|
||||
pub mm_length: size_t,
|
||||
pub mm_mime_fields: *mut mailmime_fields,
|
||||
pub mm_content_type: *mut mailmime_content,
|
||||
pub mm_body: *mut mailmime_data,
|
||||
pub mm_data: unnamed_8,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub union unnamed_8 {
|
||||
pub mm_single: *mut mailmime_data,
|
||||
pub mm_multipart: unnamed_10,
|
||||
pub mm_message: unnamed_9,
|
||||
}
|
||||
/* message */
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct unnamed_9 {
|
||||
pub mm_fields: *mut mailimf_fields,
|
||||
pub mm_msg_mime: *mut Mailmime,
|
||||
}
|
||||
/* multi-part */
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct unnamed_10 {
|
||||
pub mm_preamble: *mut mailmime_data,
|
||||
pub mm_epilogue: *mut mailmime_data,
|
||||
pub mm_mp_list: *mut clist,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_encoded_word {
|
||||
pub wd_charset: *mut libc::c_char,
|
||||
pub wd_text: *mut libc::c_char,
|
||||
}
|
||||
pub type unnamed_11 = libc::c_uint;
|
||||
pub const MAILMIME_DISPOSITION_PARM_SIZE: unnamed_11 = 4;
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_section {
|
||||
pub sec_list: *mut clist,
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_attribute_free(mut attribute: *mut libc::c_char) {
|
||||
mailmime_token_free(attribute);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_token_free(mut token: *mut libc::c_char) {
|
||||
free(token as *mut libc::c_void);
|
||||
}
|
||||
pub unsafe fn mailmime_composite_type_new(
|
||||
mut ct_type: libc::c_int,
|
||||
mut ct_token: *mut libc::c_char,
|
||||
) -> *mut mailmime_composite_type {
|
||||
let mut ct: *mut mailmime_composite_type = 0 as *mut mailmime_composite_type;
|
||||
ct = malloc(::std::mem::size_of::<mailmime_composite_type>() as libc::size_t)
|
||||
as *mut mailmime_composite_type;
|
||||
if ct.is_null() {
|
||||
return 0 as *mut mailmime_composite_type;
|
||||
}
|
||||
(*ct).ct_type = ct_type;
|
||||
(*ct).ct_token = ct_token;
|
||||
return ct;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_composite_type_free(mut ct: *mut mailmime_composite_type) {
|
||||
if !(*ct).ct_token.is_null() {
|
||||
mailmime_extension_token_free((*ct).ct_token);
|
||||
}
|
||||
free(ct as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_extension_token_free(mut extension: *mut libc::c_char) {
|
||||
mailmime_token_free(extension);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_content_new(
|
||||
mut ct_type: *mut mailmime_type,
|
||||
mut ct_subtype: *mut libc::c_char,
|
||||
mut ct_parameters: *mut clist,
|
||||
) -> *mut mailmime_content {
|
||||
let mut content: *mut mailmime_content = 0 as *mut mailmime_content;
|
||||
content =
|
||||
malloc(::std::mem::size_of::<mailmime_content>() as libc::size_t) as *mut mailmime_content;
|
||||
if content.is_null() {
|
||||
return 0 as *mut mailmime_content;
|
||||
}
|
||||
(*content).ct_type = ct_type;
|
||||
(*content).ct_subtype = ct_subtype;
|
||||
(*content).ct_parameters = ct_parameters;
|
||||
return content;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_content_free(mut content: *mut mailmime_content) {
|
||||
mailmime_type_free((*content).ct_type);
|
||||
mailmime_subtype_free((*content).ct_subtype);
|
||||
if !(*content).ct_parameters.is_null() {
|
||||
clist_foreach(
|
||||
(*content).ct_parameters,
|
||||
::std::mem::transmute::<Option<unsafe fn(_: *mut mailmime_parameter) -> ()>, clist_func>(
|
||||
Some(mailmime_parameter_free),
|
||||
),
|
||||
0 as *mut libc::c_void,
|
||||
);
|
||||
clist_free((*content).ct_parameters);
|
||||
}
|
||||
free(content as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_parameter_free(mut parameter: *mut mailmime_parameter) {
|
||||
mailmime_attribute_free((*parameter).pa_name);
|
||||
mailmime_value_free((*parameter).pa_value);
|
||||
free(parameter as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_value_free(mut value: *mut libc::c_char) {
|
||||
free(value as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_subtype_free(mut subtype: *mut libc::c_char) {
|
||||
mailmime_extension_token_free(subtype);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_type_free(mut type_0: *mut mailmime_type) {
|
||||
match (*type_0).tp_type {
|
||||
1 => {
|
||||
mailmime_discrete_type_free((*type_0).tp_data.tp_discrete_type);
|
||||
}
|
||||
2 => {
|
||||
mailmime_composite_type_free((*type_0).tp_data.tp_composite_type);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
free(type_0 as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_discrete_type_free(mut discrete_type: *mut mailmime_discrete_type) {
|
||||
if !(*discrete_type).dt_extension.is_null() {
|
||||
mailmime_extension_token_free((*discrete_type).dt_extension);
|
||||
}
|
||||
free(discrete_type as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_description_free(mut description: *mut libc::c_char) {
|
||||
free(description as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_location_free(mut location: *mut libc::c_char) {
|
||||
free(location as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_discrete_type_new(
|
||||
mut dt_type: libc::c_int,
|
||||
mut dt_extension: *mut libc::c_char,
|
||||
) -> *mut mailmime_discrete_type {
|
||||
let mut discrete_type: *mut mailmime_discrete_type = 0 as *mut mailmime_discrete_type;
|
||||
discrete_type = malloc(::std::mem::size_of::<mailmime_discrete_type>() as libc::size_t)
|
||||
as *mut mailmime_discrete_type;
|
||||
if discrete_type.is_null() {
|
||||
return 0 as *mut mailmime_discrete_type;
|
||||
}
|
||||
(*discrete_type).dt_type = dt_type;
|
||||
(*discrete_type).dt_extension = dt_extension;
|
||||
return discrete_type;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_encoding_free(mut encoding: *mut mailmime_mechanism) {
|
||||
mailmime_mechanism_free(encoding);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_mechanism_free(mut mechanism: *mut mailmime_mechanism) {
|
||||
if !(*mechanism).enc_token.is_null() {
|
||||
mailmime_token_free((*mechanism).enc_token);
|
||||
}
|
||||
free(mechanism as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_id_free(mut id: *mut libc::c_char) {
|
||||
mailimf_msg_id_free(id);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_mechanism_new(
|
||||
mut enc_type: libc::c_int,
|
||||
mut enc_token: *mut libc::c_char,
|
||||
) -> *mut mailmime_mechanism {
|
||||
let mut mechanism: *mut mailmime_mechanism = 0 as *mut mailmime_mechanism;
|
||||
mechanism = malloc(::std::mem::size_of::<mailmime_mechanism>() as libc::size_t)
|
||||
as *mut mailmime_mechanism;
|
||||
if mechanism.is_null() {
|
||||
return 0 as *mut mailmime_mechanism;
|
||||
}
|
||||
(*mechanism).enc_type = enc_type;
|
||||
(*mechanism).enc_token = enc_token;
|
||||
return mechanism;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_parameter_new(
|
||||
mut pa_name: *mut libc::c_char,
|
||||
mut pa_value: *mut libc::c_char,
|
||||
) -> *mut mailmime_parameter {
|
||||
let mut parameter: *mut mailmime_parameter = 0 as *mut mailmime_parameter;
|
||||
parameter = malloc(::std::mem::size_of::<mailmime_parameter>() as libc::size_t)
|
||||
as *mut mailmime_parameter;
|
||||
if parameter.is_null() {
|
||||
return 0 as *mut mailmime_parameter;
|
||||
}
|
||||
(*parameter).pa_name = pa_name;
|
||||
(*parameter).pa_value = pa_value;
|
||||
return parameter;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_type_new(
|
||||
mut tp_type: libc::c_int,
|
||||
mut tp_discrete_type: *mut mailmime_discrete_type,
|
||||
mut tp_composite_type: *mut mailmime_composite_type,
|
||||
) -> *mut mailmime_type {
|
||||
let mut mime_type: *mut mailmime_type = 0 as *mut mailmime_type;
|
||||
mime_type =
|
||||
malloc(::std::mem::size_of::<mailmime_type>() as libc::size_t) as *mut mailmime_type;
|
||||
if mime_type.is_null() {
|
||||
return 0 as *mut mailmime_type;
|
||||
}
|
||||
(*mime_type).tp_type = tp_type;
|
||||
match tp_type {
|
||||
1 => (*mime_type).tp_data.tp_discrete_type = tp_discrete_type,
|
||||
2 => (*mime_type).tp_data.tp_composite_type = tp_composite_type,
|
||||
_ => {}
|
||||
}
|
||||
return mime_type;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_language_new(mut lg_list: *mut clist) -> *mut mailmime_language {
|
||||
let mut lang: *mut mailmime_language = 0 as *mut mailmime_language;
|
||||
lang = malloc(::std::mem::size_of::<mailmime_language>() as libc::size_t)
|
||||
as *mut mailmime_language;
|
||||
if lang.is_null() {
|
||||
return 0 as *mut mailmime_language;
|
||||
}
|
||||
(*lang).lg_list = lg_list;
|
||||
return lang;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_language_free(mut lang: *mut mailmime_language) {
|
||||
clist_foreach(
|
||||
(*lang).lg_list,
|
||||
::std::mem::transmute::<Option<unsafe fn(_: *mut libc::c_char) -> ()>, clist_func>(Some(
|
||||
mailimf_atom_free,
|
||||
)),
|
||||
0 as *mut libc::c_void,
|
||||
);
|
||||
clist_free((*lang).lg_list);
|
||||
free(lang as *mut libc::c_void);
|
||||
}
|
||||
/*
|
||||
void mailmime_x_token_free(gchar * x_token);
|
||||
*/
|
||||
pub unsafe fn mailmime_field_new(
|
||||
mut fld_type: libc::c_int,
|
||||
mut fld_content: *mut mailmime_content,
|
||||
mut fld_encoding: *mut mailmime_mechanism,
|
||||
mut fld_id: *mut libc::c_char,
|
||||
mut fld_description: *mut libc::c_char,
|
||||
mut fld_version: uint32_t,
|
||||
mut fld_disposition: *mut mailmime_disposition,
|
||||
mut fld_language: *mut mailmime_language,
|
||||
mut fld_location: *mut libc::c_char,
|
||||
) -> *mut mailmime_field {
|
||||
let mut field: *mut mailmime_field = 0 as *mut mailmime_field;
|
||||
field = malloc(::std::mem::size_of::<mailmime_field>() as libc::size_t) as *mut mailmime_field;
|
||||
if field.is_null() {
|
||||
return 0 as *mut mailmime_field;
|
||||
}
|
||||
(*field).fld_type = fld_type;
|
||||
match fld_type {
|
||||
1 => (*field).fld_data.fld_content = fld_content,
|
||||
2 => (*field).fld_data.fld_encoding = fld_encoding,
|
||||
3 => (*field).fld_data.fld_id = fld_id,
|
||||
4 => (*field).fld_data.fld_description = fld_description,
|
||||
5 => (*field).fld_data.fld_version = fld_version,
|
||||
6 => (*field).fld_data.fld_disposition = fld_disposition,
|
||||
7 => (*field).fld_data.fld_language = fld_language,
|
||||
8 => (*field).fld_data.fld_location = fld_location,
|
||||
_ => {}
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_field_free(mut field: *mut mailmime_field) {
|
||||
match (*field).fld_type {
|
||||
1 => {
|
||||
if !(*field).fld_data.fld_content.is_null() {
|
||||
mailmime_content_free((*field).fld_data.fld_content);
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
if !(*field).fld_data.fld_encoding.is_null() {
|
||||
mailmime_encoding_free((*field).fld_data.fld_encoding);
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
if !(*field).fld_data.fld_id.is_null() {
|
||||
mailmime_id_free((*field).fld_data.fld_id);
|
||||
}
|
||||
}
|
||||
4 => {
|
||||
if !(*field).fld_data.fld_description.is_null() {
|
||||
mailmime_description_free((*field).fld_data.fld_description);
|
||||
}
|
||||
}
|
||||
6 => {
|
||||
if !(*field).fld_data.fld_disposition.is_null() {
|
||||
mailmime_disposition_free((*field).fld_data.fld_disposition);
|
||||
}
|
||||
}
|
||||
7 => {
|
||||
if !(*field).fld_data.fld_language.is_null() {
|
||||
mailmime_language_free((*field).fld_data.fld_language);
|
||||
}
|
||||
}
|
||||
8 => {
|
||||
if !(*field).fld_data.fld_location.is_null() {
|
||||
mailmime_location_free((*field).fld_data.fld_location);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
free(field as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_disposition_free(mut dsp: *mut mailmime_disposition) {
|
||||
mailmime_disposition_type_free((*dsp).dsp_type);
|
||||
clist_foreach(
|
||||
(*dsp).dsp_parms,
|
||||
::std::mem::transmute::<
|
||||
Option<unsafe fn(_: *mut mailmime_disposition_parm) -> ()>,
|
||||
clist_func,
|
||||
>(Some(mailmime_disposition_parm_free)),
|
||||
0 as *mut libc::c_void,
|
||||
);
|
||||
clist_free((*dsp).dsp_parms);
|
||||
free(dsp as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_disposition_parm_free(mut dsp_parm: *mut mailmime_disposition_parm) {
|
||||
match (*dsp_parm).pa_type {
|
||||
0 => {
|
||||
mailmime_filename_parm_free((*dsp_parm).pa_data.pa_filename);
|
||||
}
|
||||
1 => {
|
||||
mailmime_creation_date_parm_free((*dsp_parm).pa_data.pa_creation_date);
|
||||
}
|
||||
2 => {
|
||||
mailmime_modification_date_parm_free((*dsp_parm).pa_data.pa_modification_date);
|
||||
}
|
||||
3 => {
|
||||
mailmime_read_date_parm_free((*dsp_parm).pa_data.pa_read_date);
|
||||
}
|
||||
5 => {
|
||||
mailmime_parameter_free((*dsp_parm).pa_data.pa_parameter);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
free(dsp_parm as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_read_date_parm_free(mut date: *mut libc::c_char) {
|
||||
mailmime_quoted_date_time_free(date);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_quoted_date_time_free(mut date: *mut libc::c_char) {
|
||||
mailimf_quoted_string_free(date);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_modification_date_parm_free(mut date: *mut libc::c_char) {
|
||||
mailmime_quoted_date_time_free(date);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_creation_date_parm_free(mut date: *mut libc::c_char) {
|
||||
mailmime_quoted_date_time_free(date);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_filename_parm_free(mut filename: *mut libc::c_char) {
|
||||
mailmime_value_free(filename);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_disposition_type_free(mut dsp_type: *mut mailmime_disposition_type) {
|
||||
if !(*dsp_type).dsp_extension.is_null() {
|
||||
free((*dsp_type).dsp_extension as *mut libc::c_void);
|
||||
}
|
||||
free(dsp_type as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_fields_new(mut fld_list: *mut clist) -> *mut mailmime_fields {
|
||||
let mut fields: *mut mailmime_fields = 0 as *mut mailmime_fields;
|
||||
fields =
|
||||
malloc(::std::mem::size_of::<mailmime_fields>() as libc::size_t) as *mut mailmime_fields;
|
||||
if fields.is_null() {
|
||||
return 0 as *mut mailmime_fields;
|
||||
}
|
||||
(*fields).fld_list = fld_list;
|
||||
return fields;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_fields_free(mut fields: *mut mailmime_fields) {
|
||||
clist_foreach(
|
||||
(*fields).fld_list,
|
||||
::std::mem::transmute::<Option<unsafe fn(_: *mut mailmime_field) -> ()>, clist_func>(Some(
|
||||
mailmime_field_free,
|
||||
)),
|
||||
0 as *mut libc::c_void,
|
||||
);
|
||||
clist_free((*fields).fld_list);
|
||||
free(fields as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_multipart_body_new(mut bd_list: *mut clist) -> *mut mailmime_multipart_body {
|
||||
let mut mp_body: *mut mailmime_multipart_body = 0 as *mut mailmime_multipart_body;
|
||||
mp_body = malloc(::std::mem::size_of::<mailmime_multipart_body>() as libc::size_t)
|
||||
as *mut mailmime_multipart_body;
|
||||
if mp_body.is_null() {
|
||||
return 0 as *mut mailmime_multipart_body;
|
||||
}
|
||||
(*mp_body).bd_list = bd_list;
|
||||
return mp_body;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_multipart_body_free(mut mp_body: *mut mailmime_multipart_body) {
|
||||
clist_foreach(
|
||||
(*mp_body).bd_list,
|
||||
::std::mem::transmute::<Option<unsafe fn(_: *mut mailimf_body) -> ()>, clist_func>(Some(
|
||||
mailimf_body_free,
|
||||
)),
|
||||
0 as *mut libc::c_void,
|
||||
);
|
||||
clist_free((*mp_body).bd_list);
|
||||
free(mp_body as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_data_new(
|
||||
mut dt_type: libc::c_int,
|
||||
mut dt_encoding: libc::c_int,
|
||||
mut dt_encoded: libc::c_int,
|
||||
mut dt_data: *const libc::c_char,
|
||||
mut dt_length: size_t,
|
||||
mut dt_filename: *mut libc::c_char,
|
||||
) -> *mut mailmime_data {
|
||||
let mut mime_data: *mut mailmime_data = 0 as *mut mailmime_data;
|
||||
mime_data =
|
||||
malloc(::std::mem::size_of::<mailmime_data>() as libc::size_t) as *mut mailmime_data;
|
||||
if mime_data.is_null() {
|
||||
return 0 as *mut mailmime_data;
|
||||
}
|
||||
(*mime_data).dt_type = dt_type;
|
||||
(*mime_data).dt_encoding = dt_encoding;
|
||||
(*mime_data).dt_encoded = dt_encoded;
|
||||
match dt_type {
|
||||
0 => {
|
||||
(*mime_data).dt_data.dt_text.dt_data = dt_data;
|
||||
(*mime_data).dt_data.dt_text.dt_length = dt_length
|
||||
}
|
||||
1 => (*mime_data).dt_data.dt_filename = dt_filename,
|
||||
_ => {}
|
||||
}
|
||||
return mime_data;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_data_free(mut mime_data: *mut mailmime_data) {
|
||||
match (*mime_data).dt_type {
|
||||
1 => {
|
||||
free((*mime_data).dt_data.dt_filename as *mut libc::c_void);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
free(mime_data as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_new(
|
||||
mut mm_type: libc::c_int,
|
||||
mut mm_mime_start: *const libc::c_char,
|
||||
mut mm_length: size_t,
|
||||
mut mm_mime_fields: *mut mailmime_fields,
|
||||
mut mm_content_type: *mut mailmime_content,
|
||||
mut mm_body: *mut mailmime_data,
|
||||
mut mm_preamble: *mut mailmime_data,
|
||||
mut mm_epilogue: *mut mailmime_data,
|
||||
mut mm_mp_list: *mut clist,
|
||||
mut mm_fields: *mut mailimf_fields,
|
||||
mut mm_msg_mime: *mut Mailmime,
|
||||
) -> *mut Mailmime {
|
||||
let mut mime: *mut Mailmime = 0 as *mut Mailmime;
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
mime = malloc(::std::mem::size_of::<Mailmime>() as libc::size_t) as *mut Mailmime;
|
||||
if mime.is_null() {
|
||||
return 0 as *mut Mailmime;
|
||||
}
|
||||
(*mime).mm_parent = 0 as *mut Mailmime;
|
||||
(*mime).mm_parent_type = MAILMIME_NONE as libc::c_int;
|
||||
(*mime).mm_multipart_pos = 0 as *mut clistiter;
|
||||
(*mime).mm_type = mm_type;
|
||||
(*mime).mm_mime_start = mm_mime_start;
|
||||
(*mime).mm_length = mm_length;
|
||||
(*mime).mm_mime_fields = mm_mime_fields;
|
||||
(*mime).mm_content_type = mm_content_type;
|
||||
(*mime).mm_body = mm_body;
|
||||
match mm_type {
|
||||
1 => (*mime).mm_data.mm_single = mm_body,
|
||||
2 => {
|
||||
(*mime).mm_data.mm_multipart.mm_preamble = mm_preamble;
|
||||
(*mime).mm_data.mm_multipart.mm_epilogue = mm_epilogue;
|
||||
(*mime).mm_data.mm_multipart.mm_mp_list = mm_mp_list;
|
||||
cur = (*mm_mp_list).first;
|
||||
while !cur.is_null() {
|
||||
let mut submime: *mut Mailmime = 0 as *mut Mailmime;
|
||||
submime = (if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
}) as *mut Mailmime;
|
||||
(*submime).mm_parent = mime;
|
||||
(*submime).mm_parent_type = MAILMIME_MULTIPLE as libc::c_int;
|
||||
(*submime).mm_multipart_pos = cur;
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
(*mime).mm_data.mm_message.mm_fields = mm_fields;
|
||||
(*mime).mm_data.mm_message.mm_msg_mime = mm_msg_mime;
|
||||
if !mm_msg_mime.is_null() {
|
||||
(*mm_msg_mime).mm_parent = mime;
|
||||
(*mm_msg_mime).mm_parent_type = MAILMIME_MESSAGE as libc::c_int
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
return mime;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_new_simple(
|
||||
mut mm_type: libc::c_int,
|
||||
mut mm_mime_fields: *mut mailmime_fields,
|
||||
mut mm_content_type: *mut mailmime_content,
|
||||
mut mm_fields: *mut mailimf_fields,
|
||||
mut mm_msg_mime: *mut Mailmime,
|
||||
) -> *mut Mailmime {
|
||||
mailmime_new(
|
||||
mm_type,
|
||||
std::ptr::null(),
|
||||
0,
|
||||
mm_mime_fields,
|
||||
mm_content_type,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
mm_fields,
|
||||
mm_msg_mime,
|
||||
)
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_free(mut mime: *mut Mailmime) {
|
||||
match (*mime).mm_type {
|
||||
1 => {
|
||||
if (*mime).mm_body.is_null() && !(*mime).mm_data.mm_single.is_null() {
|
||||
mailmime_data_free((*mime).mm_data.mm_single);
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
/* do nothing */
|
||||
if !(*mime).mm_data.mm_multipart.mm_preamble.is_null() {
|
||||
mailmime_data_free((*mime).mm_data.mm_multipart.mm_preamble);
|
||||
}
|
||||
if !(*mime).mm_data.mm_multipart.mm_epilogue.is_null() {
|
||||
mailmime_data_free((*mime).mm_data.mm_multipart.mm_epilogue);
|
||||
}
|
||||
clist_foreach(
|
||||
(*mime).mm_data.mm_multipart.mm_mp_list,
|
||||
::std::mem::transmute::<Option<unsafe fn(_: *mut Mailmime) -> ()>, clist_func>(
|
||||
Some(mailmime_free),
|
||||
),
|
||||
0 as *mut libc::c_void,
|
||||
);
|
||||
clist_free((*mime).mm_data.mm_multipart.mm_mp_list);
|
||||
}
|
||||
3 => {
|
||||
if !(*mime).mm_data.mm_message.mm_fields.is_null() {
|
||||
mailimf_fields_free((*mime).mm_data.mm_message.mm_fields);
|
||||
}
|
||||
if !(*mime).mm_data.mm_message.mm_msg_mime.is_null() {
|
||||
mailmime_free((*mime).mm_data.mm_message.mm_msg_mime);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if !(*mime).mm_body.is_null() {
|
||||
mailmime_data_free((*mime).mm_body);
|
||||
}
|
||||
if !(*mime).mm_mime_fields.is_null() {
|
||||
mailmime_fields_free((*mime).mm_mime_fields);
|
||||
}
|
||||
if !(*mime).mm_content_type.is_null() {
|
||||
mailmime_content_free((*mime).mm_content_type);
|
||||
}
|
||||
free(mime as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_encoded_word_new(
|
||||
mut wd_charset: *mut libc::c_char,
|
||||
mut wd_text: *mut libc::c_char,
|
||||
) -> *mut mailmime_encoded_word {
|
||||
let mut ew: *mut mailmime_encoded_word = 0 as *mut mailmime_encoded_word;
|
||||
ew = malloc(::std::mem::size_of::<mailmime_encoded_word>() as libc::size_t)
|
||||
as *mut mailmime_encoded_word;
|
||||
if ew.is_null() {
|
||||
return 0 as *mut mailmime_encoded_word;
|
||||
}
|
||||
(*ew).wd_charset = wd_charset;
|
||||
(*ew).wd_text = wd_text;
|
||||
return ew;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_encoded_word_free(mut ew: *mut mailmime_encoded_word) {
|
||||
mailmime_charset_free((*ew).wd_charset);
|
||||
mailmime_encoded_text_free((*ew).wd_text);
|
||||
free(ew as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_encoded_text_free(mut text: *mut libc::c_char) {
|
||||
free(text as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_charset_free(mut charset: *mut libc::c_char) {
|
||||
free(charset as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_disposition_new(
|
||||
mut dsp_type: *mut mailmime_disposition_type,
|
||||
mut dsp_parms: *mut clist,
|
||||
) -> *mut mailmime_disposition {
|
||||
let mut dsp: *mut mailmime_disposition = 0 as *mut mailmime_disposition;
|
||||
dsp = malloc(::std::mem::size_of::<mailmime_disposition>() as libc::size_t)
|
||||
as *mut mailmime_disposition;
|
||||
if dsp.is_null() {
|
||||
return 0 as *mut mailmime_disposition;
|
||||
}
|
||||
(*dsp).dsp_type = dsp_type;
|
||||
(*dsp).dsp_parms = dsp_parms;
|
||||
return dsp;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_disposition_type_new(
|
||||
mut dsp_type: libc::c_int,
|
||||
mut dsp_extension: *mut libc::c_char,
|
||||
) -> *mut mailmime_disposition_type {
|
||||
let mut m_dsp_type: *mut mailmime_disposition_type = 0 as *mut mailmime_disposition_type;
|
||||
m_dsp_type = malloc(::std::mem::size_of::<mailmime_disposition_type>() as libc::size_t)
|
||||
as *mut mailmime_disposition_type;
|
||||
if m_dsp_type.is_null() {
|
||||
return 0 as *mut mailmime_disposition_type;
|
||||
}
|
||||
(*m_dsp_type).dsp_type = dsp_type;
|
||||
(*m_dsp_type).dsp_extension = dsp_extension;
|
||||
return m_dsp_type;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_disposition_parm_new(
|
||||
mut pa_type: libc::c_int,
|
||||
mut pa_filename: *mut libc::c_char,
|
||||
mut pa_creation_date: *mut libc::c_char,
|
||||
mut pa_modification_date: *mut libc::c_char,
|
||||
mut pa_read_date: *mut libc::c_char,
|
||||
mut pa_size: size_t,
|
||||
mut pa_parameter: *mut mailmime_parameter,
|
||||
) -> *mut mailmime_disposition_parm {
|
||||
let mut dsp_parm: *mut mailmime_disposition_parm = 0 as *mut mailmime_disposition_parm;
|
||||
dsp_parm = malloc(::std::mem::size_of::<mailmime_disposition_parm>() as libc::size_t)
|
||||
as *mut mailmime_disposition_parm;
|
||||
if dsp_parm.is_null() {
|
||||
return 0 as *mut mailmime_disposition_parm;
|
||||
}
|
||||
(*dsp_parm).pa_type = pa_type;
|
||||
match pa_type {
|
||||
0 => (*dsp_parm).pa_data.pa_filename = pa_filename,
|
||||
1 => (*dsp_parm).pa_data.pa_creation_date = pa_creation_date,
|
||||
2 => (*dsp_parm).pa_data.pa_modification_date = pa_modification_date,
|
||||
3 => (*dsp_parm).pa_data.pa_read_date = pa_read_date,
|
||||
4 => (*dsp_parm).pa_data.pa_size = pa_size,
|
||||
5 => (*dsp_parm).pa_data.pa_parameter = pa_parameter,
|
||||
_ => {}
|
||||
}
|
||||
return dsp_parm;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_section_new(mut sec_list: *mut clist) -> *mut mailmime_section {
|
||||
let mut section: *mut mailmime_section = 0 as *mut mailmime_section;
|
||||
section =
|
||||
malloc(::std::mem::size_of::<mailmime_section>() as libc::size_t) as *mut mailmime_section;
|
||||
if section.is_null() {
|
||||
return 0 as *mut mailmime_section;
|
||||
}
|
||||
(*section).sec_list = sec_list;
|
||||
return section;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_section_free(mut section: *mut mailmime_section) {
|
||||
clist_foreach(
|
||||
(*section).sec_list,
|
||||
::std::mem::transmute::<Option<unsafe extern "C" fn(_: *mut libc::c_void) -> ()>, clist_func>(
|
||||
Some(free),
|
||||
),
|
||||
0 as *mut libc::c_void,
|
||||
);
|
||||
clist_free((*section).sec_list);
|
||||
free(section as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_decoded_part_free(mut part: *mut libc::c_char) {
|
||||
mmap_string_unref(part);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,82 +0,0 @@
|
||||
use crate::mailmime::types::*;
|
||||
use crate::mailmime::write_generic::*;
|
||||
use crate::mmapstring::*;
|
||||
use crate::other::*;
|
||||
|
||||
unsafe fn do_write(
|
||||
mut data: *mut libc::c_void,
|
||||
mut str: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
) -> libc::c_int {
|
||||
let mut f: *mut MMAPString = 0 as *mut MMAPString;
|
||||
f = data as *mut MMAPString;
|
||||
if mmap_string_append_len(f, str, length).is_null() {
|
||||
return 0i32;
|
||||
} else {
|
||||
return length as libc::c_int;
|
||||
};
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_content_write_mem(
|
||||
mut f: *mut MMAPString,
|
||||
mut col: *mut libc::c_int,
|
||||
mut content: *mut mailmime_content,
|
||||
) -> libc::c_int {
|
||||
return mailmime_content_write_driver(Some(do_write), f as *mut libc::c_void, col, content);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_content_type_write_mem(
|
||||
mut f: *mut MMAPString,
|
||||
mut col: *mut libc::c_int,
|
||||
mut content: *mut mailmime_content,
|
||||
) -> libc::c_int {
|
||||
return mailmime_content_type_write_driver(
|
||||
Some(do_write),
|
||||
f as *mut libc::c_void,
|
||||
col,
|
||||
content,
|
||||
);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_write_mem(
|
||||
mut f: *mut MMAPString,
|
||||
mut col: *mut libc::c_int,
|
||||
mut build_info: *mut Mailmime,
|
||||
) -> libc::c_int {
|
||||
return mailmime_write_driver(Some(do_write), f as *mut libc::c_void, col, build_info);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_quoted_printable_write_mem(
|
||||
mut f: *mut MMAPString,
|
||||
mut col: *mut libc::c_int,
|
||||
mut istext: libc::c_int,
|
||||
mut text: *const libc::c_char,
|
||||
mut size: size_t,
|
||||
) -> libc::c_int {
|
||||
return mailmime_quoted_printable_write_driver(
|
||||
Some(do_write),
|
||||
f as *mut libc::c_void,
|
||||
col,
|
||||
istext,
|
||||
text,
|
||||
size,
|
||||
);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_base64_write_mem(
|
||||
mut f: *mut MMAPString,
|
||||
mut col: *mut libc::c_int,
|
||||
mut text: *const libc::c_char,
|
||||
mut size: size_t,
|
||||
) -> libc::c_int {
|
||||
return mailmime_base64_write_driver(Some(do_write), f as *mut libc::c_void, col, text, size);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_data_write_mem(
|
||||
mut f: *mut MMAPString,
|
||||
mut col: *mut libc::c_int,
|
||||
mut data: *mut mailmime_data,
|
||||
mut istext: libc::c_int,
|
||||
) -> libc::c_int {
|
||||
return mailmime_data_write_driver(Some(do_write), f as *mut libc::c_void, col, data, istext);
|
||||
}
|
||||
@@ -1,397 +0,0 @@
|
||||
use std::sync::Mutex;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use libc;
|
||||
|
||||
use crate::chash::*;
|
||||
use crate::other::*;
|
||||
|
||||
lazy_static! {
|
||||
static ref mmapstring_lock: Mutex<()> = Mutex::new(());
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct MMAPString {
|
||||
pub str_0: *mut libc::c_char,
|
||||
pub len: size_t,
|
||||
pub allocated_len: size_t,
|
||||
pub fd: libc::c_int,
|
||||
pub mmapped_size: size_t,
|
||||
}
|
||||
|
||||
pub const TMPDIR: &'static str = "/tmp";
|
||||
|
||||
pub unsafe fn mmap_string_new(mut init: *const libc::c_char) -> *mut MMAPString {
|
||||
let mut string: *mut MMAPString = 0 as *mut MMAPString;
|
||||
string = mmap_string_sized_new(if !init.is_null() {
|
||||
strlen(init).wrapping_add(2i32 as libc::size_t)
|
||||
} else {
|
||||
2i32 as libc::size_t
|
||||
});
|
||||
if string.is_null() {
|
||||
return 0 as *mut MMAPString;
|
||||
}
|
||||
if !init.is_null() {
|
||||
mmap_string_append(string, init);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_append(
|
||||
mut string: *mut MMAPString,
|
||||
mut val: *const libc::c_char,
|
||||
) -> *mut MMAPString {
|
||||
return mmap_string_insert_len(string, (*string).len, val, strlen(val));
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_insert_len(
|
||||
mut string: *mut MMAPString,
|
||||
mut pos: size_t,
|
||||
mut val: *const libc::c_char,
|
||||
mut len: size_t,
|
||||
) -> *mut MMAPString {
|
||||
if mmap_string_maybe_expand(string, len).is_null() {
|
||||
return 0 as *mut MMAPString;
|
||||
}
|
||||
if pos < (*string).len {
|
||||
memmove(
|
||||
(*string).str_0.offset(pos as isize).offset(len as isize) as *mut libc::c_void,
|
||||
(*string).str_0.offset(pos as isize) as *const libc::c_void,
|
||||
(*string).len.wrapping_sub(pos),
|
||||
);
|
||||
}
|
||||
memmove(
|
||||
(*string).str_0.offset(pos as isize) as *mut libc::c_void,
|
||||
val as *const libc::c_void,
|
||||
len,
|
||||
);
|
||||
(*string).len = ((*string).len as libc::size_t).wrapping_add(len) as size_t as size_t;
|
||||
*(*string).str_0.offset((*string).len as isize) = 0i32 as libc::c_char;
|
||||
return string;
|
||||
}
|
||||
unsafe fn mmap_string_maybe_expand(
|
||||
mut string: *mut MMAPString,
|
||||
mut len: size_t,
|
||||
) -> *mut MMAPString {
|
||||
if (*string).len.wrapping_add(len) >= (*string).allocated_len {
|
||||
let mut old_size: size_t = 0;
|
||||
let mut newstring: *mut MMAPString = 0 as *mut MMAPString;
|
||||
old_size = (*string).allocated_len;
|
||||
(*string).allocated_len = nearest_power(
|
||||
1i32 as size_t,
|
||||
(*string)
|
||||
.len
|
||||
.wrapping_add(len)
|
||||
.wrapping_add(1i32 as libc::size_t),
|
||||
);
|
||||
newstring = mmap_string_realloc_memory(string);
|
||||
if newstring.is_null() {
|
||||
(*string).allocated_len = old_size
|
||||
}
|
||||
return newstring;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
/* Strings.
|
||||
*/
|
||||
/* SEB */
|
||||
unsafe fn mmap_string_realloc_memory(mut string: *mut MMAPString) -> *mut MMAPString {
|
||||
let mut tmp: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
tmp = realloc(
|
||||
(*string).str_0 as *mut libc::c_void,
|
||||
(*string).allocated_len,
|
||||
) as *mut libc::c_char;
|
||||
if tmp.is_null() {
|
||||
string = 0 as *mut MMAPString
|
||||
} else {
|
||||
(*string).str_0 = tmp
|
||||
}
|
||||
return string;
|
||||
}
|
||||
/* MMAPString */
|
||||
#[inline]
|
||||
unsafe fn nearest_power(mut base: size_t, mut num: size_t) -> size_t {
|
||||
if num > (-1i32 as size_t).wrapping_div(2i32 as libc::size_t) {
|
||||
return -1i32 as size_t;
|
||||
} else {
|
||||
let mut n: size_t = base;
|
||||
while n < num {
|
||||
n <<= 1i32
|
||||
}
|
||||
return n;
|
||||
};
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_sized_new(mut dfl_size: size_t) -> *mut MMAPString {
|
||||
let mut string: *mut MMAPString = 0 as *mut MMAPString;
|
||||
string = malloc(::std::mem::size_of::<MMAPString>() as libc::size_t) as *mut MMAPString;
|
||||
if string.is_null() {
|
||||
return 0 as *mut MMAPString;
|
||||
}
|
||||
(*string).allocated_len = 0i32 as size_t;
|
||||
(*string).len = 0i32 as size_t;
|
||||
(*string).str_0 = 0 as *mut libc::c_char;
|
||||
(*string).fd = -1i32;
|
||||
(*string).mmapped_size = 0i32 as size_t;
|
||||
if mmap_string_maybe_expand(
|
||||
string,
|
||||
if dfl_size > 2i32 as libc::size_t {
|
||||
dfl_size
|
||||
} else {
|
||||
2i32 as libc::size_t
|
||||
},
|
||||
)
|
||||
.is_null()
|
||||
{
|
||||
free(string as *mut libc::c_void);
|
||||
return 0 as *mut MMAPString;
|
||||
}
|
||||
*(*string).str_0.offset(0isize) = 0i32 as libc::c_char;
|
||||
return string;
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_new_len(
|
||||
mut init: *const libc::c_char,
|
||||
mut len: size_t,
|
||||
) -> *mut MMAPString {
|
||||
let mut string: *mut MMAPString = 0 as *mut MMAPString;
|
||||
if len <= 0i32 as libc::size_t {
|
||||
return mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
|
||||
} else {
|
||||
string = mmap_string_sized_new(len);
|
||||
if string.is_null() {
|
||||
return string;
|
||||
}
|
||||
if !init.is_null() {
|
||||
mmap_string_append_len(string, init, len);
|
||||
}
|
||||
return string;
|
||||
};
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_append_len(
|
||||
mut string: *mut MMAPString,
|
||||
mut val: *const libc::c_char,
|
||||
mut len: size_t,
|
||||
) -> *mut MMAPString {
|
||||
return mmap_string_insert_len(string, (*string).len, val, len);
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_free(mut string: *mut MMAPString) {
|
||||
if string.is_null() {
|
||||
return;
|
||||
}
|
||||
free((*string).str_0 as *mut libc::c_void);
|
||||
free(string as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_assign(
|
||||
mut string: *mut MMAPString,
|
||||
mut rval: *const libc::c_char,
|
||||
) -> *mut MMAPString {
|
||||
mmap_string_truncate(string, 0i32 as size_t);
|
||||
if mmap_string_append(string, rval).is_null() {
|
||||
return 0 as *mut MMAPString;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_truncate(
|
||||
mut string: *mut MMAPString,
|
||||
mut len: size_t,
|
||||
) -> *mut MMAPString {
|
||||
(*string).len = if len < (*string).len {
|
||||
len
|
||||
} else {
|
||||
(*string).len
|
||||
};
|
||||
*(*string).str_0.offset((*string).len as isize) = 0i32 as libc::c_char;
|
||||
return string;
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_set_size(
|
||||
mut string: *mut MMAPString,
|
||||
mut len: size_t,
|
||||
) -> *mut MMAPString {
|
||||
if len >= (*string).allocated_len {
|
||||
if mmap_string_maybe_expand(string, len.wrapping_sub((*string).len)).is_null() {
|
||||
return 0 as *mut MMAPString;
|
||||
}
|
||||
}
|
||||
(*string).len = len;
|
||||
*(*string).str_0.offset(len as isize) = 0i32 as libc::c_char;
|
||||
return string;
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_append_c(
|
||||
mut string: *mut MMAPString,
|
||||
mut c: libc::c_char,
|
||||
) -> *mut MMAPString {
|
||||
return mmap_string_insert_c(string, (*string).len, c);
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_insert_c(
|
||||
mut string: *mut MMAPString,
|
||||
mut pos: size_t,
|
||||
mut c: libc::c_char,
|
||||
) -> *mut MMAPString {
|
||||
if mmap_string_maybe_expand(string, 1i32 as size_t).is_null() {
|
||||
return 0 as *mut MMAPString;
|
||||
}
|
||||
if pos < (*string).len {
|
||||
memmove(
|
||||
(*string).str_0.offset(pos as isize).offset(1isize) as *mut libc::c_void,
|
||||
(*string).str_0.offset(pos as isize) as *const libc::c_void,
|
||||
(*string).len.wrapping_sub(pos),
|
||||
);
|
||||
}
|
||||
*(*string).str_0.offset(pos as isize) = c;
|
||||
(*string).len =
|
||||
((*string).len as libc::size_t).wrapping_add(1i32 as libc::size_t) as size_t as size_t;
|
||||
*(*string).str_0.offset((*string).len as isize) = 0i32 as libc::c_char;
|
||||
return string;
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_prepend(
|
||||
mut string: *mut MMAPString,
|
||||
mut val: *const libc::c_char,
|
||||
) -> *mut MMAPString {
|
||||
return mmap_string_insert_len(string, 0i32 as size_t, val, strlen(val));
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_prepend_c(
|
||||
mut string: *mut MMAPString,
|
||||
mut c: libc::c_char,
|
||||
) -> *mut MMAPString {
|
||||
return mmap_string_insert_c(string, 0i32 as size_t, c);
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_prepend_len(
|
||||
mut string: *mut MMAPString,
|
||||
mut val: *const libc::c_char,
|
||||
mut len: size_t,
|
||||
) -> *mut MMAPString {
|
||||
return mmap_string_insert_len(string, 0i32 as size_t, val, len);
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_insert(
|
||||
mut string: *mut MMAPString,
|
||||
mut pos: size_t,
|
||||
mut val: *const libc::c_char,
|
||||
) -> *mut MMAPString {
|
||||
return mmap_string_insert_len(string, pos, val, strlen(val));
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_erase(
|
||||
mut string: *mut MMAPString,
|
||||
mut pos: size_t,
|
||||
mut len: size_t,
|
||||
) -> *mut MMAPString {
|
||||
if pos.wrapping_add(len) < (*string).len {
|
||||
memmove(
|
||||
(*string).str_0.offset(pos as isize) as *mut libc::c_void,
|
||||
(*string).str_0.offset(pos as isize).offset(len as isize) as *const libc::c_void,
|
||||
(*string).len.wrapping_sub(pos.wrapping_add(len)),
|
||||
);
|
||||
}
|
||||
(*string).len = ((*string).len as libc::size_t).wrapping_sub(len) as size_t as size_t;
|
||||
*(*string).str_0.offset((*string).len as isize) = 0i32 as libc::c_char;
|
||||
return string;
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_set_ceil(mut ceil: size_t) {
|
||||
mmap_string_ceil = ceil;
|
||||
}
|
||||
static mut mmap_string_ceil: size_t = (8i32 * 1024i32 * 1024i32) as size_t;
|
||||
|
||||
pub unsafe fn mmap_string_ref(mut string: *mut MMAPString) -> libc::c_int {
|
||||
let mut ht: *mut chash = 0 as *mut chash;
|
||||
let mut r: libc::c_int = 0;
|
||||
let mut key: chashdatum = chashdatum {
|
||||
data: 0 as *mut libc::c_void,
|
||||
len: 0,
|
||||
};
|
||||
let mut data: chashdatum = chashdatum {
|
||||
data: 0 as *mut libc::c_void,
|
||||
len: 0,
|
||||
};
|
||||
mmapstring_lock.lock().unwrap();
|
||||
if mmapstring_hashtable.is_null() {
|
||||
mmapstring_hashtable_init();
|
||||
}
|
||||
ht = mmapstring_hashtable;
|
||||
if ht.is_null() {
|
||||
return -1i32;
|
||||
}
|
||||
key.data = &mut (*string).str_0 as *mut *mut libc::c_char as *mut libc::c_void;
|
||||
key.len = ::std::mem::size_of::<*mut libc::c_char>() as libc::size_t as libc::c_uint;
|
||||
data.data = string as *mut libc::c_void;
|
||||
data.len = 0i32 as libc::c_uint;
|
||||
r = chash_set(
|
||||
mmapstring_hashtable,
|
||||
&mut key,
|
||||
&mut data,
|
||||
0 as *mut chashdatum,
|
||||
);
|
||||
|
||||
if r < 0i32 {
|
||||
return r;
|
||||
}
|
||||
return 0i32;
|
||||
}
|
||||
|
||||
static mut mmapstring_hashtable: *mut chash = 0 as *const chash as *mut chash;
|
||||
unsafe fn mmapstring_hashtable_init() {
|
||||
mmapstring_hashtable = chash_new(13i32 as libc::c_uint, 1i32);
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_unref(mut str: *mut libc::c_char) -> libc::c_int {
|
||||
let mut string: *mut MMAPString = 0 as *mut MMAPString;
|
||||
let mut ht: *mut chash = 0 as *mut chash;
|
||||
let mut key: chashdatum = chashdatum {
|
||||
data: 0 as *mut libc::c_void,
|
||||
len: 0,
|
||||
};
|
||||
let mut data: chashdatum = chashdatum {
|
||||
data: 0 as *mut libc::c_void,
|
||||
len: 0,
|
||||
};
|
||||
let mut r: libc::c_int = 0;
|
||||
if str.is_null() {
|
||||
return -1i32;
|
||||
}
|
||||
mmapstring_lock.lock().unwrap();
|
||||
ht = mmapstring_hashtable;
|
||||
if ht.is_null() {
|
||||
return -1i32;
|
||||
}
|
||||
key.data = &mut str as *mut *mut libc::c_char as *mut libc::c_void;
|
||||
key.len = ::std::mem::size_of::<*mut libc::c_char>() as libc::size_t as libc::c_uint;
|
||||
r = chash_get(ht, &mut key, &mut data);
|
||||
if r < 0i32 {
|
||||
string = 0 as *mut MMAPString
|
||||
} else {
|
||||
string = data.data as *mut MMAPString
|
||||
}
|
||||
if !string.is_null() {
|
||||
chash_delete(ht, &mut key, 0 as *mut chashdatum);
|
||||
if chash_count(ht) == 0i32 as libc::c_uint {
|
||||
chash_free(ht);
|
||||
mmapstring_hashtable = 0 as *mut chash
|
||||
}
|
||||
}
|
||||
if !string.is_null() {
|
||||
mmap_string_free(string);
|
||||
return 0i32;
|
||||
} else {
|
||||
return -1i32;
|
||||
};
|
||||
}
|
||||
#[inline]
|
||||
unsafe fn chash_count(mut hash: *mut chash) -> libc::c_uint {
|
||||
return (*hash).count;
|
||||
}
|
||||
|
||||
pub unsafe fn mmapstring_init_lock() {}
|
||||
pub unsafe fn mmapstring_uninit_lock() {}
|
||||
1728
mmime/src/other.rs
1728
mmime/src/other.rs
File diff suppressed because it is too large
Load Diff
@@ -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\'<\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"
|
||||
@@ -5,4 +5,3 @@
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc 679506fe9ac59df773f8cfa800fdab5f0a32fe49d2ab370394000a1aa5bc2a72 # shrinks to buf = "%0A"
|
||||
cc e34960438edb2426904b44fb4215154e7e2880f2fd1c3183b98bfcc76fec4882 # shrinks to input = " 0"
|
||||
|
||||
@@ -97,7 +97,7 @@ If you want to run "liveconfig" functional tests you can set
|
||||
chat devs.
|
||||
|
||||
- or the path of a file that contains two lines, each describing
|
||||
via "addr=... mail_pw=..." a test account login that will
|
||||
via "addr=... mail_pwd=..." a test account login that will
|
||||
be used for the live tests.
|
||||
|
||||
With ``DCC_PY_LIVECONFIG`` set pytest invocations will use real
|
||||
|
||||
1
python/doc/_templates/globaltoc.html
vendored
1
python/doc/_templates/globaltoc.html
vendored
@@ -6,6 +6,7 @@
|
||||
<li><a href="{{ pathto('install') }}">install</a></li>
|
||||
<li><a href="{{ pathto('api') }}">high level API</a></li>
|
||||
<li><a href="{{ pathto('lapi') }}">low level API</a></li>
|
||||
<li><a href="{{ pathto('capi') }}">C deltachat.h</a></li>
|
||||
</ul>
|
||||
<b>external links:</b>
|
||||
<ul>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
low level API reference
|
||||
===================================
|
||||
|
||||
for full doxygen-generated C-docs, defines and functions please checkout
|
||||
|
||||
https://c.delta.chat
|
||||
|
||||
|
||||
Python low-level capi calls
|
||||
---------------------------
|
||||
for full C-docs, defines and function checkout :doc:`capi`
|
||||
|
||||
|
||||
.. automodule:: deltachat.capi.lib
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ["DCC_RS_TARGET"] = target = "release"
|
||||
@@ -21,5 +21,5 @@ if __name__ == "__main__":
|
||||
subprocess.check_call("rm -rf build/ src/deltachat/*.so" , shell=True)
|
||||
|
||||
subprocess.check_call([
|
||||
sys.executable, "-m", "pip", "install", "-e", "."
|
||||
"pip", "install", "-e", "."
|
||||
])
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
""" Account class implementation. """
|
||||
|
||||
from __future__ import print_function
|
||||
import atexit
|
||||
import threading
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from array import array
|
||||
@@ -15,17 +15,15 @@ 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):
|
||||
def __init__(self, db_path, logid=None, eventlogging=True):
|
||||
""" initialize account object.
|
||||
|
||||
:param db_path: a path to the account database. The database
|
||||
@@ -33,14 +31,13 @@ class Account(object):
|
||||
:param logid: an optional logging prefix that should be used with
|
||||
the default internal logging.
|
||||
:param eventlogging: if False no eventlogging and no context callback will be configured
|
||||
:param debug: turn on debug logging for events.
|
||||
"""
|
||||
self._dc_context = ffi.gc(
|
||||
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
||||
_destroy_dc_context,
|
||||
)
|
||||
if eventlogging:
|
||||
self._evlogger = EventLogger(self._dc_context, logid, debug)
|
||||
self._evlogger = EventLogger(self._dc_context, logid)
|
||||
deltachat.set_context_callback(self._dc_context, self._process_event)
|
||||
self._threads = IOThreads(self._dc_context, self._evlogger._log_event)
|
||||
else:
|
||||
@@ -51,11 +48,10 @@ class Account(object):
|
||||
if not lib.dc_open(self._dc_context, db_path, ffi.NULL):
|
||||
raise ValueError("Could not dc_open: {}".format(db_path))
|
||||
self._configkeys = self.get_config("sys.config_keys").split()
|
||||
self._imex_events = Queue()
|
||||
atexit.register(self.shutdown)
|
||||
self._imex_completed = threading.Event()
|
||||
|
||||
# def __del__(self):
|
||||
# self.shutdown()
|
||||
def __del__(self):
|
||||
self.shutdown()
|
||||
|
||||
def _check_config_key(self, name):
|
||||
if name not in self._configkeys:
|
||||
@@ -73,18 +69,6 @@ class Account(object):
|
||||
d[key.lower()] = value
|
||||
return d
|
||||
|
||||
def set_stock_translation(self, id, string):
|
||||
""" set stock translation string.
|
||||
|
||||
:param id: id of stock string (const.DC_STR_*)
|
||||
:param value: string to set as new transalation
|
||||
:returns: None
|
||||
"""
|
||||
string = string.encode("utf8")
|
||||
res = lib.dc_set_stock_translation(self._dc_context, id, string)
|
||||
if res == 0:
|
||||
raise ValueError("could not set translation string")
|
||||
|
||||
def set_config(self, name, value):
|
||||
""" set configuration values.
|
||||
|
||||
@@ -137,30 +121,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 +135,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 +149,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 +175,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 +193,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 +210,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,16 +228,16 @@ 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)
|
||||
chat_id = lib.dc_create_group_chat(self._dc_context, verified, bytes_name)
|
||||
return Chat(self, chat_id)
|
||||
|
||||
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 +255,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 +274,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]
|
||||
@@ -339,64 +289,31 @@ class Account(object):
|
||||
msg_ids = [msg.id for msg in messages]
|
||||
lib.dc_delete_msgs(self._dc_context, msg_ids, len(msg_ids))
|
||||
|
||||
def export_self_keys(self, path):
|
||||
""" export public and private keys to the specified directory. """
|
||||
return self._export(path, imex_cmd=1)
|
||||
|
||||
def export_all(self, path):
|
||||
"""return new file containing a backup of all database state
|
||||
(chats, contacts, keys, media, ...). The file is created in the
|
||||
the `path` directory.
|
||||
def export_to_dir(self, backupdir):
|
||||
"""return after all delta chat state is exported to a new file in
|
||||
the specified directory.
|
||||
"""
|
||||
export_files = self._export(path, 11)
|
||||
if len(export_files) != 1:
|
||||
raise RuntimeError("found more than one new file")
|
||||
return export_files[0]
|
||||
|
||||
def _imex_events_clear(self):
|
||||
try:
|
||||
while True:
|
||||
self._imex_events.get_nowait()
|
||||
except Empty:
|
||||
pass
|
||||
|
||||
def _export(self, path, imex_cmd):
|
||||
self._imex_events_clear()
|
||||
lib.dc_imex(self._dc_context, imex_cmd, as_dc_charpointer(path), ffi.NULL)
|
||||
snap_files = os.listdir(backupdir)
|
||||
self._imex_completed.clear()
|
||||
lib.dc_imex(self._dc_context, 11, as_dc_charpointer(backupdir), ffi.NULL)
|
||||
if not self._threads.is_started():
|
||||
lib.dc_perform_imap_jobs(self._dc_context)
|
||||
files_written = []
|
||||
while True:
|
||||
ev = self._imex_events.get()
|
||||
if isinstance(ev, str):
|
||||
files_written.append(ev)
|
||||
elif isinstance(ev, bool):
|
||||
if not ev:
|
||||
raise ValueError("export failed, exp-files: {}".format(files_written))
|
||||
return files_written
|
||||
self._imex_completed.wait()
|
||||
for x in os.listdir(backupdir):
|
||||
if x not in snap_files:
|
||||
return os.path.join(backupdir, x)
|
||||
|
||||
def import_self_keys(self, path):
|
||||
""" Import private keys found in the `path` directory.
|
||||
The last imported key is made the default keys unless its name
|
||||
contains the string legacy. Public keys are not imported.
|
||||
"""
|
||||
self._import(path, imex_cmd=2)
|
||||
|
||||
def import_all(self, path):
|
||||
"""import delta chat state from the specified backup `path` (a file).
|
||||
def import_from_file(self, path):
|
||||
"""import delta chat state from the specified backup file.
|
||||
|
||||
The account must be in unconfigured state for import to attempted.
|
||||
"""
|
||||
assert not self.is_configured(), "cannot import into configured account"
|
||||
self._import(path, imex_cmd=12)
|
||||
|
||||
def _import(self, path, imex_cmd):
|
||||
self._imex_events_clear()
|
||||
lib.dc_imex(self._dc_context, imex_cmd, as_dc_charpointer(path), ffi.NULL)
|
||||
self._imex_completed.clear()
|
||||
lib.dc_imex(self._dc_context, 12, as_dc_charpointer(path), ffi.NULL)
|
||||
if not self._threads.is_started():
|
||||
lib.dc_perform_imap_jobs(self._dc_context)
|
||||
if not self._imex_events.get():
|
||||
raise ValueError("import from path '{}' failed".format(path))
|
||||
self._imex_completed.wait()
|
||||
|
||||
def initiate_key_transfer(self):
|
||||
"""return setup code after a Autocrypt setup message
|
||||
@@ -436,7 +353,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 +367,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)
|
||||
@@ -461,19 +378,7 @@ class Account(object):
|
||||
raise ValueError("could not join group")
|
||||
return Chat(self, chat_id)
|
||||
|
||||
def stop_ongoing(self):
|
||||
lib.dc_stop_ongoing_process(self._dc_context)
|
||||
|
||||
#
|
||||
# meta API for start/stop and event based processing
|
||||
#
|
||||
|
||||
def wait_next_incoming_message(self):
|
||||
""" wait for and return next incoming message. """
|
||||
ev = self._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
|
||||
return self.get_message_by_id(ev[2])
|
||||
|
||||
def start_threads(self, mvbox=False, sentbox=False):
|
||||
def start_threads(self):
|
||||
""" start IMAP/SMTP threads (and configure account if it hasn't happened).
|
||||
|
||||
:raises: ValueError if 'addr' or 'mail_pw' are not configured.
|
||||
@@ -481,13 +386,12 @@ class Account(object):
|
||||
"""
|
||||
if not self.is_configured():
|
||||
self.configure()
|
||||
self._threads.start(mvbox=mvbox, sentbox=sentbox)
|
||||
self._threads.start()
|
||||
|
||||
def stop_threads(self, wait=True):
|
||||
""" stop IMAP/SMTP threads. """
|
||||
if self._threads.is_started():
|
||||
self.stop_ongoing()
|
||||
self._threads.stop(wait=wait)
|
||||
lib.dc_stop_ongoing_process(self._dc_context)
|
||||
self._threads.stop(wait=wait)
|
||||
|
||||
def shutdown(self, wait=True):
|
||||
""" stop threads and close and remove underlying dc_context and callbacks. """
|
||||
@@ -498,7 +402,6 @@ class Account(object):
|
||||
self.stop_threads(wait=wait) # to wait for threads
|
||||
deltachat.clear_context_callback(self._dc_context)
|
||||
del self._dc_context
|
||||
atexit.unregister(self.shutdown)
|
||||
|
||||
def _process_event(self, ctx, evt_name, data1, data2):
|
||||
assert ctx == self._dc_context
|
||||
@@ -511,26 +414,7 @@ class Account(object):
|
||||
|
||||
def on_dc_event_imex_progress(self, data1, data2):
|
||||
if data1 == 1000:
|
||||
self._imex_events.put(True)
|
||||
elif data1 == 0:
|
||||
self._imex_events.put(False)
|
||||
|
||||
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")
|
||||
self._imex_completed.set()
|
||||
|
||||
|
||||
class IOThreads:
|
||||
@@ -543,14 +427,10 @@ class IOThreads:
|
||||
def is_started(self):
|
||||
return len(self._name2thread) > 0
|
||||
|
||||
def start(self, imap=True, smtp=True, mvbox=False, sentbox=False):
|
||||
def start(self, imap=True, smtp=True):
|
||||
assert not self.is_started()
|
||||
if imap:
|
||||
self._start_one_thread("inbox", self.imap_thread_run)
|
||||
if mvbox:
|
||||
self._start_one_thread("mvbox", self.mvbox_thread_run)
|
||||
if sentbox:
|
||||
self._start_one_thread("sentbox", self.sentbox_thread_run)
|
||||
self._start_one_thread("imap", self.imap_thread_run)
|
||||
if smtp:
|
||||
self._start_one_thread("smtp", self.smtp_thread_run)
|
||||
|
||||
@@ -563,35 +443,17 @@ class IOThreads:
|
||||
self._thread_quitflag = True
|
||||
lib.dc_interrupt_imap_idle(self._dc_context)
|
||||
lib.dc_interrupt_smtp_idle(self._dc_context)
|
||||
lib.dc_interrupt_mvbox_idle(self._dc_context)
|
||||
lib.dc_interrupt_sentbox_idle(self._dc_context)
|
||||
if wait:
|
||||
for name, thread in self._name2thread.items():
|
||||
thread.join()
|
||||
|
||||
def imap_thread_run(self):
|
||||
self._log_event("py-bindings-info", 0, "INBOX THREAD START")
|
||||
self._log_event("py-bindings-info", 0, "IMAP THREAD START")
|
||||
while not self._thread_quitflag:
|
||||
lib.dc_perform_imap_jobs(self._dc_context)
|
||||
lib.dc_perform_imap_fetch(self._dc_context)
|
||||
lib.dc_perform_imap_idle(self._dc_context)
|
||||
self._log_event("py-bindings-info", 0, "INBOX THREAD FINISHED")
|
||||
|
||||
def mvbox_thread_run(self):
|
||||
self._log_event("py-bindings-info", 0, "MVBOX THREAD START")
|
||||
while not self._thread_quitflag:
|
||||
lib.dc_perform_mvbox_jobs(self._dc_context)
|
||||
lib.dc_perform_mvbox_fetch(self._dc_context)
|
||||
lib.dc_perform_mvbox_idle(self._dc_context)
|
||||
self._log_event("py-bindings-info", 0, "MVBOX THREAD FINISHED")
|
||||
|
||||
def sentbox_thread_run(self):
|
||||
self._log_event("py-bindings-info", 0, "SENTBOX THREAD START")
|
||||
while not self._thread_quitflag:
|
||||
lib.dc_perform_sentbox_jobs(self._dc_context)
|
||||
lib.dc_perform_sentbox_fetch(self._dc_context)
|
||||
lib.dc_perform_sentbox_idle(self._dc_context)
|
||||
self._log_event("py-bindings-info", 0, "SENTBOX THREAD FINISHED")
|
||||
self._log_event("py-bindings-info", 0, "IMAP THREAD FINISHED")
|
||||
|
||||
def smtp_thread_run(self):
|
||||
self._log_event("py-bindings-info", 0, "SMTP THREAD START")
|
||||
@@ -643,11 +505,11 @@ class EventLogger:
|
||||
else:
|
||||
assert not rex.match(ev[0]), "event found {}".format(ev)
|
||||
|
||||
def get_matching(self, event_name_regex, check_error=True, timeout=None):
|
||||
def get_matching(self, event_name_regex, check_error=True):
|
||||
self._log("-- waiting for event with regex: {} --".format(event_name_regex))
|
||||
rex = re.compile("(?:{}).*".format(event_name_regex))
|
||||
while 1:
|
||||
ev = self.get(timeout=timeout, check_error=check_error)
|
||||
ev = self.get()
|
||||
if rex.match(ev[0]):
|
||||
return ev
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -66,13 +109,6 @@ class Chat(object):
|
||||
"""
|
||||
return not lib.dc_chat_is_unpromoted(self._dc_chat)
|
||||
|
||||
def is_verified(self):
|
||||
""" return True if this chat is a verified group.
|
||||
|
||||
:returns: True if chat is verified, False otherwise.
|
||||
"""
|
||||
return lib.dc_chat_is_verified(self._dc_chat)
|
||||
|
||||
def get_name(self):
|
||||
""" return name of this chat.
|
||||
|
||||
@@ -269,10 +305,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 +358,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__
|
||||
@@ -47,40 +47,19 @@ 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
|
||||
DC_MSG_GIF = 21
|
||||
DC_MSG_STICKER = 23
|
||||
DC_MSG_AUDIO = 40
|
||||
DC_MSG_VOICE = 41
|
||||
DC_MSG_VIDEO = 50
|
||||
DC_MSG_FILE = 60
|
||||
DC_LP_AUTH_OAUTH2 = 0x2
|
||||
DC_LP_AUTH_NORMAL = 0x4
|
||||
DC_LP_IMAP_SOCKET_STARTTLS = 0x100
|
||||
DC_LP_IMAP_SOCKET_SSL = 0x200
|
||||
DC_LP_IMAP_SOCKET_PLAIN = 0x400
|
||||
DC_LP_SMTP_SOCKET_STARTTLS = 0x10000
|
||||
DC_LP_SMTP_SOCKET_SSL = 0x20000
|
||||
DC_LP_SMTP_SOCKET_PLAIN = 0x40000
|
||||
DC_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
|
||||
DC_EVENT_ERROR = 400
|
||||
DC_EVENT_ERROR_NETWORK = 401
|
||||
@@ -98,65 +77,14 @@ DC_EVENT_IMEX_PROGRESS = 2051
|
||||
DC_EVENT_IMEX_FILE_WRITTEN = 2052
|
||||
DC_EVENT_SECUREJOIN_INVITER_PROGRESS = 2060
|
||||
DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061
|
||||
DC_EVENT_GET_STRING = 2091
|
||||
DC_EVENT_FILE_COPIED = 2055
|
||||
DC_EVENT_IS_OFFLINE = 2081
|
||||
DC_EVENT_GET_STRING = 2091
|
||||
DC_STR_SELFNOTINGRP = 21
|
||||
DC_PROVIDER_STATUS_OK = 1
|
||||
DC_PROVIDER_STATUS_PREPARATION = 2
|
||||
DC_PROVIDER_STATUS_BROKEN = 3
|
||||
DC_STR_NOMESSAGES = 1
|
||||
DC_STR_SELF = 2
|
||||
DC_STR_DRAFT = 3
|
||||
DC_STR_MEMBER = 4
|
||||
DC_STR_CONTACT = 6
|
||||
DC_STR_VOICEMESSAGE = 7
|
||||
DC_STR_DEADDROP = 8
|
||||
DC_STR_IMAGE = 9
|
||||
DC_STR_VIDEO = 10
|
||||
DC_STR_AUDIO = 11
|
||||
DC_STR_FILE = 12
|
||||
DC_STR_STATUSLINE = 13
|
||||
DC_STR_NEWGROUPDRAFT = 14
|
||||
DC_STR_MSGGRPNAME = 15
|
||||
DC_STR_MSGGRPIMGCHANGED = 16
|
||||
DC_STR_MSGADDMEMBER = 17
|
||||
DC_STR_MSGDELMEMBER = 18
|
||||
DC_STR_MSGGROUPLEFT = 19
|
||||
DC_STR_GIF = 23
|
||||
DC_STR_ENCRYPTEDMSG = 24
|
||||
DC_STR_E2E_AVAILABLE = 25
|
||||
DC_STR_ENCR_TRANSP = 27
|
||||
DC_STR_ENCR_NONE = 28
|
||||
DC_STR_CANTDECRYPT_MSG_BODY = 29
|
||||
DC_STR_FINGERPRINTS = 30
|
||||
DC_STR_READRCPT = 31
|
||||
DC_STR_READRCPT_MAILBODY = 32
|
||||
DC_STR_MSGGRPIMGDELETED = 33
|
||||
DC_STR_E2E_PREFERRED = 34
|
||||
DC_STR_CONTACT_VERIFIED = 35
|
||||
DC_STR_CONTACT_NOT_VERIFIED = 36
|
||||
DC_STR_CONTACT_SETUP_CHANGED = 37
|
||||
DC_STR_ARCHIVEDCHATS = 40
|
||||
DC_STR_STARREDMSGS = 41
|
||||
DC_STR_AC_SETUP_MSG_SUBJECT = 42
|
||||
DC_STR_AC_SETUP_MSG_BODY = 43
|
||||
DC_STR_SELFTALK_SUBTITLE = 50
|
||||
DC_STR_CANNOT_LOGIN = 60
|
||||
DC_STR_SERVER_RESPONSE = 61
|
||||
DC_STR_MSGACTIONBYUSER = 62
|
||||
DC_STR_MSGACTIONBYME = 63
|
||||
DC_STR_MSGLOCATIONENABLED = 64
|
||||
DC_STR_MSGLOCATIONDISABLED = 65
|
||||
DC_STR_LOCATION = 66
|
||||
DC_STR_STICKER = 67
|
||||
DC_STR_COUNT = 67
|
||||
# end const generated
|
||||
|
||||
|
||||
def read_event_defines(f):
|
||||
rex = re.compile(r'#define\s+((?:DC_EVENT|DC_QR|DC_MSG|DC_LP|DC_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_CONTACT_ID_|DC_GCL|DC_CHAT)\S+)\s+([x\d]+).*')
|
||||
for line in f:
|
||||
m = rex.match(line)
|
||||
if m:
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -101,18 +101,6 @@ class Message(object):
|
||||
""" return True if this message is a setup message. """
|
||||
return lib.dc_msg_is_setupmessage(self._dc_msg)
|
||||
|
||||
def get_setupcodebegin(self):
|
||||
""" return the first characters of a setup code in a setup message. """
|
||||
return from_dc_charpointer(lib.dc_msg_get_setupcodebegin(self._dc_msg))
|
||||
|
||||
def is_encrypted(self):
|
||||
""" 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 +157,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)
|
||||
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
"""Provider info class."""
|
||||
|
||||
from .capi import ffi, lib
|
||||
from .cutil import as_dc_charpointer, from_dc_charpointer
|
||||
|
||||
|
||||
class ProviderNotFoundError(Exception):
|
||||
"""The provider information was not found."""
|
||||
|
||||
|
||||
class Provider(object):
|
||||
"""Provider information.
|
||||
|
||||
:param domain: The domain to get the provider info for, this is
|
||||
normally the part following the `@` of the domain.
|
||||
"""
|
||||
|
||||
def __init__(self, domain):
|
||||
provider = ffi.gc(
|
||||
lib.dc_provider_new_from_domain(as_dc_charpointer(domain)),
|
||||
lib.dc_provider_unref,
|
||||
)
|
||||
if provider == ffi.NULL:
|
||||
raise ProviderNotFoundError("Provider not found")
|
||||
self._provider = provider
|
||||
|
||||
@classmethod
|
||||
def from_email(cls, email):
|
||||
"""Create provider info from an email address.
|
||||
|
||||
:param email: Email address to get provider info for.
|
||||
"""
|
||||
return cls(email.split('@')[-1])
|
||||
|
||||
@property
|
||||
def overview_page(self):
|
||||
"""URL to the overview page of the provider on providers.delta.chat."""
|
||||
return from_dc_charpointer(
|
||||
lib.dc_provider_get_overview_page(self._provider))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name of the provider."""
|
||||
return from_dc_charpointer(lib.dc_provider_get_name(self._provider))
|
||||
|
||||
@property
|
||||
def markdown(self):
|
||||
"""Content of the information page, formatted as markdown."""
|
||||
return from_dc_charpointer(
|
||||
lib.dc_provider_get_markdown(self._provider))
|
||||
|
||||
@property
|
||||
def status_date(self):
|
||||
"""The date the provider info was last updated, as a string."""
|
||||
return from_dc_charpointer(
|
||||
lib.dc_provider_get_status_date(self._provider))
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""The status of the provider information.
|
||||
|
||||
This is one of the
|
||||
:attr:`deltachat.const.DC_PROVIDER_STATUS_OK`,
|
||||
:attr:`deltachat.const.DC_PROVIDER_STATUS_PREPARATION` or
|
||||
:attr:`deltachat.const.DC_PROVIDER_STATUS_BROKEN` constants.
|
||||
"""
|
||||
return lib.dc_provider_get_status(self._provider)
|
||||
@@ -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,41 +150,21 @@ 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):
|
||||
def get_online_configuring_account(self):
|
||||
if not session_liveconfig:
|
||||
pytest.skip("specify DCC_PY_LIVECONFIG or --liveconfig")
|
||||
configdict = session_liveconfig.get(self.live_count)
|
||||
self.live_count += 1
|
||||
if "e2ee_enabled" not in configdict:
|
||||
configdict["e2ee_enabled"] = "1"
|
||||
|
||||
# Enable strict certificate checks for online accounts
|
||||
configdict["imap_certificate_checks"] = str(const.DC_CERTCK_STRICT)
|
||||
configdict["smtp_certificate_checks"] = str(const.DC_CERTCK_STRICT)
|
||||
|
||||
tmpdb = tmpdir.join("livedb%d" % self.live_count)
|
||||
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.live_count))
|
||||
ac._evlogger.init_time = self.init_time
|
||||
ac._evlogger.set_timeout(30)
|
||||
return ac, dict(configdict)
|
||||
|
||||
def get_online_configuring_account(self, mvbox=False, sentbox=False):
|
||||
ac, configdict = self.get_online_config()
|
||||
ac.configure(**configdict)
|
||||
ac.start_threads(mvbox=mvbox, sentbox=sentbox)
|
||||
ac.start_threads()
|
||||
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()
|
||||
@@ -227,13 +206,12 @@ def lp():
|
||||
return Printer()
|
||||
|
||||
|
||||
def wait_configuration_progress(account, min_target, max_target=1001):
|
||||
min_target = min(min_target, max_target)
|
||||
def wait_configuration_progress(account, target):
|
||||
while 1:
|
||||
evt_name, data1, data2 = \
|
||||
account._evlogger.get_matching("DC_EVENT_CONFIGURE_PROGRESS")
|
||||
if data1 >= min_target and data1 <= max_target:
|
||||
print("** CONFIG PROGRESS {}".format(min_target), account)
|
||||
if data1 >= target:
|
||||
print("** CONFIG PROGRESS {}".format(target), account)
|
||||
break
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,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
|
||||
@@ -21,7 +19,6 @@ class TestOfflineAccountBasic:
|
||||
d = ac1.get_info()
|
||||
assert d["arch"]
|
||||
assert d["number_of_chats"] == "0"
|
||||
assert d["bcc_self"] == "1"
|
||||
|
||||
def test_is_not_configured(self, acfactory):
|
||||
ac1 = acfactory.get_unconfigured_account()
|
||||
@@ -40,11 +37,6 @@ class TestOfflineAccountBasic:
|
||||
ac1 = acfactory.get_unconfigured_account()
|
||||
assert "save_mime_headers" in ac1.get_config("sys.config_keys").split()
|
||||
|
||||
def test_has_bccself(self, acfactory):
|
||||
ac1 = acfactory.get_unconfigured_account()
|
||||
assert "bcc_self" in ac1.get_config("sys.config_keys").split()
|
||||
assert ac1.get_config("bcc_self") == "1"
|
||||
|
||||
def test_selfcontact_if_unconfigured(self, acfactory):
|
||||
ac1 = acfactory.get_unconfigured_account()
|
||||
with pytest.raises(ValueError):
|
||||
@@ -101,9 +93,8 @@ class TestOfflineContact:
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
contact1 = ac1.create_contact(email="some1@example.com", name="some1")
|
||||
chat = ac1.create_chat_by_contact(contact1)
|
||||
msg = chat.send_text("one message")
|
||||
chat.send_text("one messae")
|
||||
assert not ac1.delete_contact(contact1)
|
||||
assert not msg.filemime
|
||||
|
||||
|
||||
class TestOfflineChat:
|
||||
@@ -115,19 +106,13 @@ class TestOfflineChat:
|
||||
def chat1(self, ac1):
|
||||
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||
chat = ac1.create_chat_by_contact(contact1)
|
||||
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL, chat.id
|
||||
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL, chat.id
|
||||
return chat
|
||||
|
||||
def test_display(self, chat1):
|
||||
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)
|
||||
@@ -155,27 +140,6 @@ class TestOfflineChat:
|
||||
chat.set_name("title2")
|
||||
assert chat.get_name() == "title2"
|
||||
|
||||
def test_group_chat_creation_with_translation(self, ac1):
|
||||
ac1.set_stock_translation(const.DC_STR_NEWGROUPDRAFT, "xyz %1$s")
|
||||
ac1._evlogger.consume_events()
|
||||
with pytest.raises(ValueError):
|
||||
ac1.set_stock_translation(const.DC_STR_NEWGROUPDRAFT, "xyz %2$s")
|
||||
ac1._evlogger.get_matching("DC_EVENT_WARNING")
|
||||
with pytest.raises(ValueError):
|
||||
ac1.set_stock_translation(500, "xyz %1$s")
|
||||
ac1._evlogger.get_matching("DC_EVENT_WARNING")
|
||||
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||
contact2 = ac1.create_contact("some2@hello.com", name="some2")
|
||||
chat = ac1.create_group_chat(name="title1")
|
||||
chat.add_contact(contact1)
|
||||
chat.add_contact(contact2)
|
||||
assert chat.get_name() == "title1"
|
||||
assert contact1 in chat.get_contacts()
|
||||
assert contact2 in chat.get_contacts()
|
||||
assert not chat.is_promoted()
|
||||
msg = chat.get_draft()
|
||||
assert msg.text == "xyz title1"
|
||||
|
||||
@pytest.mark.parametrize("verified", [True, False])
|
||||
def test_group_chat_qr(self, acfactory, ac1, verified):
|
||||
ac2 = acfactory.get_configured_offline_account()
|
||||
@@ -257,9 +221,7 @@ class TestOfflineChat:
|
||||
chat1.send_image(path="notexists")
|
||||
fn = data.get_path("d.png")
|
||||
lp.sec("sending image")
|
||||
chat1.account._evlogger.consume_events()
|
||||
msg = chat1.send_image(fn)
|
||||
chat1.account._evlogger.get_matching("DC_EVENT_NEW_BLOB_FILE")
|
||||
assert msg.is_image()
|
||||
assert msg
|
||||
assert msg.id > 0
|
||||
@@ -331,10 +293,10 @@ class TestOfflineChat:
|
||||
assert contact == ac1.get_self_contact()
|
||||
assert not backupdir.listdir()
|
||||
|
||||
path = ac1.export_all(backupdir.strpath)
|
||||
path = ac1.export_to_dir(backupdir.strpath)
|
||||
assert os.path.exists(path)
|
||||
ac2 = acfactory.get_unconfigured_account()
|
||||
ac2.import_all(path)
|
||||
ac2.import_from_file(path)
|
||||
contacts = ac2.get_contacts(query="some1")
|
||||
assert len(contacts) == 1
|
||||
contact2 = contacts[0]
|
||||
@@ -372,81 +334,36 @@ 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")))
|
||||
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||
return chat
|
||||
|
||||
def test_configure_canceled(self, acfactory):
|
||||
def test_one_account_send(self, acfactory):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
wait_configuration_progress(ac1, 200)
|
||||
ac1.stop_ongoing()
|
||||
wait_configuration_progress(ac1, 0, 0)
|
||||
|
||||
def test_export_import_self_keys(self, acfactory, tmpdir):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
dir = tmpdir.mkdir("exportdir")
|
||||
export_files = ac1.export_self_keys(dir.strpath)
|
||||
assert len(export_files) == 2
|
||||
for x in export_files:
|
||||
assert x.startswith(dir.strpath)
|
||||
ac1._evlogger.consume_events()
|
||||
ac1.import_self_keys(dir.strpath)
|
||||
|
||||
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=ac1.get_config("addr"))
|
||||
chat = ac1.create_chat_by_contact(c2)
|
||||
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||
wait_successful_IMAP_SMTP_connection(ac1)
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
|
||||
lp.sec("send out message with bcc to ourselves")
|
||||
msg_out = chat.send_text("message2")
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[2] == msg_out.id
|
||||
# wait for send out (BCC)
|
||||
assert ac1.get_config("bcc_self") == "1"
|
||||
self_addr = ac1.get_config("addr")
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
|
||||
assert self_addr in ev[2]
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_DELETED_BLOB_FILE")
|
||||
# wait for own account to receive
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[1] == msg_out.id
|
||||
|
||||
ac1._evlogger.consume_events()
|
||||
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")
|
||||
assert self_addr not in ev[2]
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_DELETED_BLOB_FILE")
|
||||
|
||||
def test_mvbox_sentbox_threads(self, acfactory):
|
||||
ac1 = acfactory.get_online_configuring_account(mvbox=True, sentbox=True)
|
||||
ac2 = acfactory.get_online_configuring_account()
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
def test_two_accounts_send_receive(self, acfactory):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
chat.send_text("message1")
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
|
||||
def test_move_works(self, acfactory):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.get_online_configuring_account(mvbox=True)
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
chat.send_text("message1")
|
||||
msg_out = chat.send_text("message1")
|
||||
|
||||
# wait for other account to receive
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
assert ev[2] == msg_out.id
|
||||
msg_in = ac2.get_message_by_id(msg_out.id)
|
||||
assert msg_in.text == "message1"
|
||||
|
||||
def test_forward_messages(self, acfactory):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
@@ -463,7 +380,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,48 +387,10 @@ 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):
|
||||
def test_send_and_receive_message(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
lp.sec("ac1: create chat with ac2")
|
||||
@@ -531,7 +409,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
|
||||
@@ -553,19 +430,11 @@ class TestOnlineAccount:
|
||||
ac2.mark_seen_messages([msg_in])
|
||||
lp.step("1")
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_READ")
|
||||
assert ev[1] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
assert ev[2] > const.DC_MSG_ID_LAST_SPECIAL
|
||||
assert ev[1] >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||
assert ev[2] >= const.DC_MSG_ID_LAST_SPECIAL
|
||||
lp.step("2")
|
||||
assert msg_out.is_out_mdn_received()
|
||||
|
||||
lp.sec("check that a second call to mark_seen does not create change or smtp job")
|
||||
ac2._evlogger.consume_events()
|
||||
ac2.mark_seen_messages([msg_in])
|
||||
try:
|
||||
ac2._evlogger.get_matching("DC_EVENT_MSG_READ", timeout=0.01)
|
||||
except queue.Empty:
|
||||
pass # mark_seen_messages() has generated events before it returns
|
||||
|
||||
def test_send_and_receive_will_encrypt_decrypt(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
@@ -574,7 +443,6 @@ class TestOnlineAccount:
|
||||
|
||||
lp.sec("sending text message from ac1 to ac2")
|
||||
msg_out = chat.send_text("message1")
|
||||
assert not msg_out.is_encrypted()
|
||||
|
||||
lp.sec("wait for ac2 to receive message")
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
@@ -592,15 +460,6 @@ class TestOnlineAccount:
|
||||
assert ev[2] > msg_out.id
|
||||
msg_back = ac1.get_message_by_id(ev[2])
|
||||
assert msg_back.text == "message-back"
|
||||
assert msg_back.is_encrypted()
|
||||
|
||||
lp.sec("create group chat with two members, one of which has no encrypt state")
|
||||
chat = ac1.create_group_chat("encryption test")
|
||||
chat.add_contact(ac1.create_contact(ac2.get_config("addr")))
|
||||
chat.add_contact(ac1.create_contact("notexisting@testrun.org"))
|
||||
msg = chat.send_text("test not encrypt")
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
|
||||
assert not msg.is_encrypted()
|
||||
|
||||
def test_saved_mime_on_received_message(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
@@ -642,30 +501,19 @@ 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_twice(self, acfactory, tmpdir, lp):
|
||||
def test_import_export_online(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)
|
||||
path = ac1.export_to_dir(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)
|
||||
ac2.import_from_file(path)
|
||||
contacts = ac2.get_contacts(query="some1")
|
||||
assert len(contacts) == 1
|
||||
contact2 = contacts[0]
|
||||
@@ -675,18 +523,7 @@ 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):
|
||||
def test_ac_setup_message(self, acfactory):
|
||||
# note that the receiving account needs to be configured and running
|
||||
# before ther setup message is send. DC does not read old messages
|
||||
# as of Jul2019
|
||||
@@ -694,44 +531,20 @@ class TestOnlineAccount:
|
||||
ac2 = acfactory.clone_online_account(ac1)
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
lp.sec("trigger ac setup message and return setupcode")
|
||||
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
|
||||
setup_code = ac1.initiate_key_transfer()
|
||||
ac2._evlogger.set_timeout(30)
|
||||
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_code[:2]
|
||||
lp.sec("try a bad setup code")
|
||||
# first try a bad setup code
|
||||
with pytest.raises(ValueError):
|
||||
msg.continue_key_transfer(str(reversed(setup_code)))
|
||||
lp.sec("try a good setup code")
|
||||
print("*************** Incoming ASM File at: ", msg.filename)
|
||||
print("*************** Setup Code: ", setup_code)
|
||||
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")
|
||||
@@ -749,43 +562,8 @@ class TestOnlineAccount:
|
||||
lp.sec("ac2: start QR-code based join-group protocol")
|
||||
ch = ac2.qr_join_chat(qr)
|
||||
assert ch.id >= 10
|
||||
# check that at least some of the handshake messages are deleted
|
||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
||||
ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
||||
wait_securejoin_inviter_progress(ac1, 1000)
|
||||
|
||||
def test_qr_verified_group_and_chatting(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
lp.sec("ac1: create verified-group QR, ac2 scans and joins")
|
||||
chat1 = ac1.create_group_chat("hello", verified=True)
|
||||
assert chat1.is_verified()
|
||||
qr = chat1.get_join_qr()
|
||||
lp.sec("ac2: start QR-code based join-group protocol")
|
||||
chat2 = ac2.qr_join_chat(qr)
|
||||
assert chat2.id >= 10
|
||||
wait_securejoin_inviter_progress(ac1, 1000)
|
||||
|
||||
lp.sec("ac2: read member added message")
|
||||
msg = ac2.wait_next_incoming_message()
|
||||
assert msg.is_encrypted()
|
||||
assert "added" in msg.text.lower()
|
||||
|
||||
lp.sec("ac1: send message")
|
||||
msg_out = chat1.send_text("hello")
|
||||
assert msg_out.is_encrypted()
|
||||
|
||||
lp.sec("ac2: read message and check it's verified chat")
|
||||
msg = ac2.wait_next_incoming_message()
|
||||
assert msg.text == "hello"
|
||||
assert msg.chat.is_verified()
|
||||
assert msg.is_encrypted()
|
||||
|
||||
lp.sec("ac2: send message and let ac1 read it")
|
||||
chat2.send_text("world")
|
||||
msg = ac1.wait_next_incoming_message()
|
||||
assert msg.text == "world"
|
||||
assert msg.is_encrypted()
|
||||
|
||||
def test_set_get_profile_image(self, acfactory, data, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
@@ -838,80 +616,3 @@ class TestOnlineAccount:
|
||||
chat1b = ac1.create_chat_by_message(ev[2])
|
||||
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):
|
||||
ac1, configdict = acfactory.get_online_config()
|
||||
ac1.configure(addr=configdict["addr"], mail_pw="123")
|
||||
ac1.start_threads()
|
||||
wait_configuration_progress(ac1, 500)
|
||||
ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK")
|
||||
assert "authentication failed" in ev1[2].lower()
|
||||
wait_configuration_progress(ac1, 0, 0)
|
||||
|
||||
def test_invalid_user(self, acfactory):
|
||||
ac1, configdict = acfactory.get_online_config()
|
||||
ac1.configure(addr="x" + configdict["addr"], mail_pw=configdict["mail_pw"])
|
||||
ac1.start_threads()
|
||||
wait_configuration_progress(ac1, 500)
|
||||
ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK")
|
||||
assert "authentication failed" in ev1[2].lower()
|
||||
wait_configuration_progress(ac1, 0, 0)
|
||||
|
||||
def test_invalid_domain(self, acfactory):
|
||||
ac1, configdict = acfactory.get_online_config()
|
||||
ac1.configure(addr=configdict["addr"] + "x", mail_pw=configdict["mail_pw"])
|
||||
ac1.start_threads()
|
||||
wait_configuration_progress(ac1, 500)
|
||||
ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK")
|
||||
assert "could not connect" in ev1[2].lower()
|
||||
wait_configuration_progress(ac1, 0, 0)
|
||||
|
||||
@@ -4,7 +4,7 @@ from deltachat import const
|
||||
from conftest import wait_configuration_progress, wait_msgs_changed
|
||||
|
||||
|
||||
class TestOnlineInCreation:
|
||||
class TestInCreation:
|
||||
def test_forward_increation(self, acfactory, data, lp):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.get_online_configuring_account()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from __future__ import print_function
|
||||
from deltachat import capi, cutil, const, set_context_callback, clear_context_callback
|
||||
from deltachat import capi, const, set_context_callback, clear_context_callback
|
||||
from deltachat.capi import ffi
|
||||
from deltachat.capi import lib
|
||||
from deltachat.account import EventLogger
|
||||
@@ -59,16 +59,6 @@ def test_wrong_db(tmpdir):
|
||||
assert not lib.dc_open(dc_context, p.strpath.encode("ascii"), ffi.NULL)
|
||||
|
||||
|
||||
def test_empty_blobdir(tmpdir):
|
||||
# Apparently some client code expects this to be the same as passing NULL.
|
||||
ctx = ffi.gc(
|
||||
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
||||
lib.dc_context_unref,
|
||||
)
|
||||
db_fname = tmpdir.join("hello.db")
|
||||
assert lib.dc_open(ctx, db_fname.strpath.encode("ascii"), b"")
|
||||
|
||||
|
||||
def test_event_defines():
|
||||
assert const.DC_EVENT_INFO == 100
|
||||
assert const.DC_CONTACT_ID_SELF
|
||||
@@ -93,65 +83,3 @@ def test_markseen_invalid_message_ids(acfactory):
|
||||
msg_ids = [9]
|
||||
lib.dc_markseen_msgs(ac1._dc_context, msg_ids, len(msg_ids))
|
||||
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(
|
||||
lib.dc_provider_get_overview_page(provider)
|
||||
) == "https://providers.delta.chat/example.com"
|
||||
assert cutil.from_dc_charpointer(lib.dc_provider_get_name(provider)) == "Example"
|
||||
assert cutil.from_dc_charpointer(lib.dc_provider_get_markdown(provider)) == "\n..."
|
||||
assert cutil.from_dc_charpointer(lib.dc_provider_get_status_date(provider)) == "2018-09"
|
||||
assert lib.dc_provider_get_status(provider) == const.DC_PROVIDER_STATUS_PREPARATION
|
||||
|
||||
|
||||
def test_provider_info_none():
|
||||
assert lib.dc_provider_new_from_email(cutil.as_dc_charpointer("email@unexistent.no")) == ffi.NULL
|
||||
|
||||
|
||||
def test_get_info_closed():
|
||||
ctx = ffi.gc(
|
||||
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
||||
lib.dc_context_unref,
|
||||
)
|
||||
info = cutil.from_dc_charpointer(lib.dc_get_info(ctx))
|
||||
assert 'deltachat_core_version' in info
|
||||
assert 'database_dir' not in info
|
||||
|
||||
|
||||
def test_get_info_open(tmpdir):
|
||||
ctx = ffi.gc(
|
||||
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
||||
lib.dc_context_unref,
|
||||
)
|
||||
db_fname = tmpdir.join("test.db")
|
||||
lib.dc_open(ctx, db_fname.strpath.encode("ascii"), ffi.NULL)
|
||||
info = cutil.from_dc_charpointer(lib.dc_get_info(ctx))
|
||||
assert 'deltachat_core_version' in info
|
||||
assert 'database_dir' in info
|
||||
|
||||
|
||||
def test_is_open_closed():
|
||||
ctx = ffi.gc(
|
||||
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
||||
lib.dc_context_unref,
|
||||
)
|
||||
assert lib.dc_is_open(ctx) == 0
|
||||
|
||||
|
||||
def test_is_open_actually_open(tmpdir):
|
||||
ctx = ffi.gc(
|
||||
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
||||
lib.dc_context_unref,
|
||||
)
|
||||
db_fname = tmpdir.join("test.db")
|
||||
lib.dc_open(ctx, db_fname.strpath.encode("ascii"), ffi.NULL)
|
||||
assert lib.dc_is_open(ctx) == 1
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from deltachat import const
|
||||
from deltachat import provider
|
||||
|
||||
|
||||
def test_provider_info_from_email():
|
||||
example = provider.Provider.from_email("email@example.com")
|
||||
assert example.overview_page == "https://providers.delta.chat/example.com"
|
||||
assert example.name == "Example"
|
||||
assert example.markdown == "\n..."
|
||||
assert example.status_date == "2018-09"
|
||||
assert example.status == const.DC_PROVIDER_STATUS_PREPARATION
|
||||
|
||||
|
||||
def test_provider_info_from_domain():
|
||||
example = provider.Provider("example.com")
|
||||
assert example.overview_page == "https://providers.delta.chat/example.com"
|
||||
assert example.name == "Example"
|
||||
assert example.markdown == "\n..."
|
||||
assert example.status_date == "2018-09"
|
||||
assert example.status == const.DC_PROVIDER_STATUS_PREPARATION
|
||||
|
||||
|
||||
def test_provider_info_none():
|
||||
with pytest.raises(provider.ProviderNotFoundError):
|
||||
provider.Provider.from_email("email@unexistent.no")
|
||||
@@ -19,12 +19,12 @@ deps =
|
||||
pytest-rerunfailures
|
||||
pytest-timeout
|
||||
pytest-xdist
|
||||
auditwheel
|
||||
pdbpp
|
||||
requests
|
||||
|
||||
[testenv:auditwheels]
|
||||
skipsdist = True
|
||||
deps = auditwheel
|
||||
commands =
|
||||
python tests/auditwheels.py {toxworkdir}/wheelhouse
|
||||
|
||||
@@ -45,7 +45,7 @@ commands =
|
||||
[testenv:doc]
|
||||
basepython = python3.5
|
||||
deps =
|
||||
sphinx==2.2.0
|
||||
sphinx==2.0.1
|
||||
breathe
|
||||
|
||||
changedir = doc
|
||||
|
||||
@@ -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 "$@"
|
||||
|
||||
@@ -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("")
|
||||
201
spec.md
201
spec.md
@@ -2,9 +2,7 @@
|
||||
|
||||
Version 0.19.0
|
||||
|
||||
This document describes how emails can be used
|
||||
to implement typical messenger functions
|
||||
while staying compatible to existing MUAs.
|
||||
This document describes how emails can be used to implement typical messenger functions while staying compatible to existing MUAs.
|
||||
|
||||
- [Encryption](#encryption)
|
||||
- [Outgoing messages](#outgoing-messages)
|
||||
@@ -22,14 +20,10 @@ while staying compatible to existing MUAs.
|
||||
|
||||
# Encryption
|
||||
|
||||
Messages SHOULD be encrypted by the
|
||||
[Autocrypt](https://autocrypt.org/level1.html) standard;
|
||||
`prefer-encrypt=mutual` MAY be set by default.
|
||||
Messages SHOULD be encrypted by the [Autocrypt](https://autocrypt.org/level1.html) standard; `prefer-encrypt=mutual` MAY be set by default.
|
||||
|
||||
Meta data (at least the subject and all chat-headers) SHOULD be encrypted
|
||||
by the [Memoryhole](https://github.com/autocrypt/memoryhole) standard.
|
||||
If Memoryhole is not used,
|
||||
the subject of encrypted messages SHOULD be replaced by the string
|
||||
Meta data (at least the subject and all chat-headers) SHOULD be encrypted by the [Memoryhole](https://github.com/autocrypt/memoryhole) standard.
|
||||
If Memoryhole is not used, the subject of encrypted messages SHOULD be replaced by the string
|
||||
`Chat: Encrypted message` where the part after the colon MAY be localized.
|
||||
|
||||
|
||||
@@ -37,8 +31,7 @@ the subject of encrypted messages SHOULD be replaced by the string
|
||||
|
||||
Messengers MUST add a `Chat-Version: 1.0` header to outgoing messages.
|
||||
For filtering and smart appearance of the messages in normal MUAs,
|
||||
the `Subject` header SHOULD start with the characters `Chat:`
|
||||
and SHOULD be an excerpt of the message.
|
||||
the `Subject` header SHOULD start with the characters `Chat:` and SHOULD be an excerpt of the message.
|
||||
Replies to messages MAY follow the typical `Re:`-format.
|
||||
|
||||
The body MAY contain text which MUST have the content type `text/plain`
|
||||
@@ -48,8 +41,8 @@ The text MAY be divided into a user-text-part and a footer-part using the
|
||||
line `-- ` (minus, minus, space, lineend).
|
||||
|
||||
The user-text-part MUST contain only user generated content.
|
||||
User generated content are eg. texts a user has actually typed
|
||||
or pasted or forwarded from another user.
|
||||
User generated content are eg. texts a user has actually typed or pasted or
|
||||
forwarded from another user.
|
||||
Full quotes, footers or sth. like that MUST NOT go to the user-text-part.
|
||||
|
||||
From: sender@domain
|
||||
@@ -63,19 +56,14 @@ Full quotes, footers or sth. like that MUST NOT go to the user-text-part.
|
||||
|
||||
# Incoming messages
|
||||
|
||||
The `Chat-Version` header MAY be used
|
||||
to detect if a messages comes from a compatible messenger.
|
||||
The `Chat-Version` header MAY be used to detect if a messages comes from a compatible messenger.
|
||||
|
||||
The `Subject` header MUST NOT be used
|
||||
to detect compatible messengers, groups or whatever.
|
||||
The `Subject` header MUST NOT be used to detect compatible messengers, groups or whatever.
|
||||
|
||||
Messenger SHOULD show the `Subject`
|
||||
if the message comes from a normal MUA together with the email-body.
|
||||
The email-body SHOULD be converted
|
||||
to plain text, full-quotes and similar regions SHOULD be cut.
|
||||
Messenger SHOULD show the `Subject` if the message comes from a normal MUA together with the email-body.
|
||||
The email-body SHOULD be converted to plain text, full-quotes and similar regions SHOULD be cut.
|
||||
|
||||
Attachments SHOULD be shown where possible.
|
||||
If an attachment cannot be shown, a non-distracting warning SHOULD be printed.
|
||||
Attachments SHOULD be shown where possible. If an attachment cannot be shown, a non-distracting warning SHOULD be printed.
|
||||
|
||||
|
||||
# Forwarded messages
|
||||
@@ -102,26 +90,21 @@ which SHOULD be anonymized or just a placeholder.
|
||||
Hello world!
|
||||
|
||||
Incoming forwarded messages are detected by the header.
|
||||
The messenger SHOULD mark these messages in a way that
|
||||
it becomes obvious that the message is not created by the sender.
|
||||
Note that most messengers do not show the original sender of forwarded messages
|
||||
The messenger SHOULD mark these messages in a way that it becomes obvious
|
||||
that the message is not created by the sender.
|
||||
Note that most messengers do not show the original sender with forwarded messages
|
||||
but MUAs typically expose the sender in the UI.
|
||||
|
||||
|
||||
# Groups
|
||||
|
||||
Groups are chats with usually more than one recipient,
|
||||
each defined by an email-address.
|
||||
Groups are chats with usually more than one recipient, each defined by an email-address.
|
||||
The sender plus the recipients are the group members.
|
||||
|
||||
To allow different groups with the same members,
|
||||
groups are identified by a group-id.
|
||||
The group-id MUST be created only from the characters
|
||||
`0`-`9`, `A`-`Z`, `a`-`z` `_` and `-`
|
||||
and MUST have a length of at least 11 characters.
|
||||
To allow different groups with the same members, groups are identified by a group-id.
|
||||
The group-id MUST be created only from the characters `0`-`9`, `A`-`Z`, `a`-`z` `_` and `-`.
|
||||
|
||||
Groups MUST have a group-name.
|
||||
The group-name is any non-zero-length UTF-8 string.
|
||||
Groups MUST have a group-name. The group-name is any non-zero-length UTF-8 string.
|
||||
|
||||
Groups MAY have a group-image.
|
||||
|
||||
@@ -130,77 +113,57 @@ Groups MAY have a group-image.
|
||||
|
||||
All group members MUST be added to the `From`/`To` headers.
|
||||
The group-id MUST be written to the `Chat-Group-ID` header.
|
||||
The group-name MUST be written to `Chat-Group-Name` header
|
||||
(the forced presence of this header makes it easier
|
||||
to join a group chat on a second device any time).
|
||||
The group-name MUST be written to `Chat-Group-Name` header (the forced presence of this header makes it easier to join a group chat on a second device any time).
|
||||
|
||||
The `Subject` header of outgoing group messages
|
||||
SHOULD start with the characters `Chat:`
|
||||
followed by the group-name and a colon followed by an excerpt of the message.
|
||||
The `Subject` header of outgoing group messages SHOULD start with the characters `Chat:` followed by the group-name and a colon followed by an excerpt of the message.
|
||||
|
||||
To identify the group-id on replies from normal MUAs,
|
||||
the group-id MUST also be added to the message-id of outgoing messages.
|
||||
The message-id MUST have the format `Gr.<group-id>.<unique data>`.
|
||||
To identify the group-id on replies from normal MUAs, the group-id MUST also be added to
|
||||
the message-id of outgoing messages. The message-id MUST have the
|
||||
format `Gr.<group-id>.<unique data>`.
|
||||
|
||||
From: member1@domain
|
||||
To: member2@domain, member3@domain
|
||||
Chat-Version: 1.0
|
||||
Chat-Group-ID: 12345uvwxyZ
|
||||
Chat-Group-ID: 1234xyZ
|
||||
Chat-Group-Name: My Group
|
||||
Message-ID: Gr.12345uvwxyZ.0001@domain
|
||||
Message-ID: Gr.1234xyZ.0001@domain
|
||||
Subject: Chat: My Group: Hello group ...
|
||||
|
||||
Hello group - this group contains three members
|
||||
|
||||
Messengers adding the member list in the form `Name <email-address>`
|
||||
MUST take care only to spread the names authorized by the contacts themselves.
|
||||
Otherwise, names as _Daddy_ or _Honey_ may be spread
|
||||
(this issue is also true for normal MUAs, however,
|
||||
for more contact- and chat-centralized apps
|
||||
Messengers adding the member list in the form `Name <email-address>` MUST take care only to spread the names authorized by the contacts themselves.
|
||||
Otherwise, names as _Daddy_ or _Honey_ may be spread (this issue is also true for normal MUAs, however, for more contact- and chat-centralized apps
|
||||
such situations happen more frequently).
|
||||
|
||||
|
||||
## Incoming group messages
|
||||
|
||||
The messenger MUST search incoming messages for the group-id
|
||||
in the following headers: `Chat-Group-ID`,
|
||||
The messenger MUST search incoming messages for the group-id in the following headers: `Chat-Group-ID`,
|
||||
`Message-ID`, `In-Reply-To` and `References` (in this order).
|
||||
|
||||
If the messenger finds a valid and existent group-id,
|
||||
the message SHOULD be assigned to the given group.
|
||||
If the messenger finds a valid but not existent group-id,
|
||||
the messenger MAY create a new group.
|
||||
If no group-id is found,
|
||||
the message MAY be assigned
|
||||
to a normal single-user chat with the email-address given in `From`.
|
||||
If the messenger finds a valid and existent group-id, the message SHOULD be assigned to the given group.
|
||||
If the messenger finds a valid but not existent group-id, the messenger MAY create a new group.
|
||||
If no group-id is found, the message MAY be assigned to a normal single-user chat with the email-address given in `From`.
|
||||
|
||||
|
||||
## Add and remove members
|
||||
|
||||
Messenger clients MUST construct the member list
|
||||
from the `From`/`To` headers only on the first group message
|
||||
or if they see a `Chat-Group-Member-Added`
|
||||
or `Chat-Group-Member-Removed` action header.
|
||||
Both headers MUST have the email-address
|
||||
of the added or removed member as the value.
|
||||
Messenger clients MUST NOT construct the member list
|
||||
on other group messages
|
||||
(this is to avoid accidentally altered To-lists in normal MUAs;
|
||||
the user does not expect adding a user to a _message_
|
||||
will also add him to the _group_ "forever").
|
||||
Messenger clients MUST construct the member list from the `From`/`To` headers only on the first group message or if they see a `Chat-Group-Member-Added` or `Chat-Group-Member-Removed` action header.
|
||||
Both headers MUST have the email-address of the added or removed member as the value.
|
||||
Messenger clients MUST NOT construct the member list on other group messages (this is to avoid accidentally altered To-lists in normal MUAs; the user
|
||||
does not expect adding a user to a _message_ will also add him to the _group_ "forever").
|
||||
|
||||
The messenger SHOULD send an explicit mail for each added or removed member.
|
||||
The body of the message SHOULD contain
|
||||
a localized description about what happened
|
||||
The body of the message SHOULD contain a localized description about what happened
|
||||
and the message SHOULD appear as a message or action from the sender.
|
||||
|
||||
From: member1@domain
|
||||
To: member2@domain, member3@domain, member4@domain
|
||||
Chat-Version: 1.0
|
||||
Chat-Group-ID: 12345uvwxyZ
|
||||
Chat-Group-ID: 1234xyZ
|
||||
Chat-Group-Name: My Group
|
||||
Chat-Group-Member-Added: member4@domain
|
||||
Message-ID: Gr.12345uvwxyZ.0002@domain
|
||||
Message-ID: Gr.1234xyZ.0002@domain
|
||||
Subject: Chat: My Group: Hello, ...
|
||||
|
||||
Hello, I've added member4@domain to our group. Now we have 4 members.
|
||||
@@ -210,10 +173,10 @@ To remove a member:
|
||||
From: member1@domain
|
||||
To: member2@domain, member3@domain
|
||||
Chat-Version: 1.0
|
||||
Chat-Group-ID: 12345uvwxyZ
|
||||
Chat-Group-ID: 1234xyZ
|
||||
Chat-Group-Name: My Group
|
||||
Chat-Group-Member-Removed: member4@domain
|
||||
Message-ID: Gr.12345uvwxyZ.0003@domain
|
||||
Message-ID: Gr.1234xyZ.0003@domain
|
||||
Subject: Chat: My Group: Hello, ...
|
||||
|
||||
Hello, I've removed member4@domain from our group. Now we have 3 members.
|
||||
@@ -227,17 +190,16 @@ with the value set to the old group name to all group members.
|
||||
The new group name goes to the header `Chat-Group-Name`.
|
||||
|
||||
The messenger SHOULD send an explicit mail for each name change.
|
||||
The body of the message SHOULD contain
|
||||
a localized description about what happened
|
||||
The body of the message SHOULD contain a localized description about what happened
|
||||
and the message SHOULD appear as a message or action from the sender.
|
||||
|
||||
From: member1@domain
|
||||
To: member2@domain, member3@domain
|
||||
Chat-Version: 1.0
|
||||
Chat-Group-ID: 12345uvwxyZ
|
||||
Chat-Group-ID: 1234xyZ
|
||||
Chat-Group-Name: Our Group
|
||||
Chat-Group-Name-Changed: My Group
|
||||
Message-ID: Gr.12345uvwxyZ.0004@domain
|
||||
Message-ID: Gr.1234xyZ.0004@domain
|
||||
Subject: Chat: Our Group: Hello, ...
|
||||
|
||||
Hello, I've changed the group name from "My Group" to "Our Group".
|
||||
@@ -246,27 +208,23 @@ and the message SHOULD appear as a message or action from the sender.
|
||||
## Set group image
|
||||
|
||||
A group MAY have a group-image.
|
||||
To change or set the group-image,
|
||||
the messenger MUST attach an image file to a message
|
||||
and MUST add the header `Chat-Group-Image`
|
||||
with the value set to the image name.
|
||||
To change or set the group-image, the messenger MUST attach an image file to a message and MUST add the header `Chat-Group-Image` with the
|
||||
value set to the image name.
|
||||
|
||||
To remove the group-image,
|
||||
the messenger MUST add the header `Chat-Group-Image: 0`.
|
||||
To remove the group-image, the messenger MUST add the header `Chat-Group-Image: 0`.
|
||||
|
||||
The messenger SHOULD send an explicit mail for each group image change.
|
||||
The body of the message SHOULD contain
|
||||
a localized description about what happened
|
||||
The body of the message SHOULD contain a localized description about what happened
|
||||
and the message SHOULD appear as a message or action from the sender.
|
||||
|
||||
|
||||
From: member1@domain
|
||||
To: member2@domain, member3@domain
|
||||
Chat-Version: 1.0
|
||||
Chat-Group-ID: 12345uvwxyZ
|
||||
Chat-Group-ID: 1234xyZ
|
||||
Chat-Group-Name: Our Group
|
||||
Chat-Group-Image: image.jpg
|
||||
Message-ID: Gr.12345uvwxyZ.0005@domain
|
||||
Message-ID: Gr.1234xyZ.0005@domain
|
||||
Subject: Chat: Our Group: Hello, ...
|
||||
Content-Type: multipart/mixed; boundary="==break=="
|
||||
|
||||
@@ -281,29 +239,20 @@ and the message SHOULD appear as a message or action from the sender.
|
||||
/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBw ...
|
||||
--==break==--
|
||||
|
||||
The image format SHOULD be image/jpeg or image/png.
|
||||
To save data, it is RECOMMENDED
|
||||
to add a `Chat-Group-Image` only on image changes.
|
||||
The image format SHOULD be image/jpeg or image/png. To save data, it is RECOMMENDED to add a `Chat-Group-Image` only on image changes.
|
||||
|
||||
|
||||
# Set profile image
|
||||
|
||||
A user MAY have a profile-image that MAY be spread to his contacts.
|
||||
To change or set the profile-image,
|
||||
the messenger MUST attach an image file to a message
|
||||
and MUST add the header `Chat-Profile-Image`
|
||||
with the value set to the image name.
|
||||
To change or set the profile-image, the messenger MUST attach an image file to a message and MUST add the header `Chat-Profile-Image` with the
|
||||
value set to the image name.
|
||||
|
||||
To remove the profile-image,
|
||||
the messenger MUST add the header `Chat-Profile-Image: 0`.
|
||||
To remove the profile-image, the messenger MUST add the header `Chat-Profile-Image: 0`.
|
||||
|
||||
To spread the image,
|
||||
the messenger MAY send the profile image
|
||||
together with the next mail to a given contact
|
||||
(to do this only once,
|
||||
the messenger has to keep a `profile_image_update_state` somewhere).
|
||||
Alternatively, the messenger MAY send an explicit mail
|
||||
for each profile-image change to all contacts using a compatible messenger.
|
||||
To spread the image, the messenger MAY send the profile image together with the next mail to a given contact
|
||||
(to do this only once, the messenger has to keep a `profile_image_update_state` somewhere).
|
||||
Alternatively, the messenger MAY send an explicit mail for each profile-image change to all contacts using a compatible messenger.
|
||||
The messenger SHOULD NOT send an explicit mail to normal MUAs.
|
||||
|
||||
From: sender@domain
|
||||
@@ -324,47 +273,35 @@ The messenger SHOULD NOT send an explicit mail to normal MUAs.
|
||||
AKCgkJi3j4l5kjoldfUAKCgkJi3j4lldfHjgWICwgIEBQYFBA ...
|
||||
--==break==--
|
||||
|
||||
The image format SHOULD be image/jpeg or image/png.
|
||||
Note that `Chat-Profile-Image` may appear together with all other headers,
|
||||
eg. there may be a `Chat-Profile-Image` and a `Chat-Group-Image` header
|
||||
in the same message.
|
||||
To save data, it is RECOMMENDED to add a `Chat-Profile-Image` header
|
||||
only on image changes.
|
||||
The image format SHOULD be image/jpeg or image/png. Note that `Chat-Profile-Image` may appear together with all other headers, eg. there may be a
|
||||
`Chat-Profile-Image` and a `Chat-Group-Image` header in the same message. To save data, it is RECOMMENDED to add a `Chat-Profile-Image` header 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.
|
||||
Messengers SHOULD add a `Chat-Voice-message: 1` header if an attached audio file is a voice message.
|
||||
|
||||
Messengers MAY add a `Chat-Duration` header
|
||||
to specify the duration of attached audio or video files.
|
||||
Messengers MAY add a `Chat-Duration` header 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
|
||||
|
||||
Messengers MAY send and receive Message Disposition Notifications
|
||||
(MDNs, [RFC 8098](https://tools.ietf.org/html/rfc8098),
|
||||
[RFC 3503](https://tools.ietf.org/html/rfc3503))
|
||||
using the `Chat-Disposition-Notification-To` header
|
||||
instead of the `Disposition-Notification-To`
|
||||
(which unfortunately forces many other MUAs
|
||||
to send weird mails not following any standard).
|
||||
Messengers MAY send and receive Message Disposition Notifications (MDNs, [RFC 8098](https://tools.ietf.org/html/rfc8098), [RFC 3503](https://tools.ietf.org/html/rfc3503))
|
||||
using the `Chat-Disposition-Notification-To` header instead of the `Disposition-Notification-To` (which unfortunately forces many other MUAs to send weird mails not following any
|
||||
standard).
|
||||
|
||||
|
||||
## Sync messages
|
||||
|
||||
If some action is required by a message header,
|
||||
the action should only be performed if the _effective date_ is newer
|
||||
than the date the last action was performed.
|
||||
If some action is required by a message header, the action should only be performed if the _effective date_ is newer than the date the last action was performed.
|
||||
|
||||
We define the effective date of a message
|
||||
as the sending time of the message as indicated by its Date header,
|
||||
We define the effective date of a message as the sending time of the message as indicated by its Date header,
|
||||
or the time of first receipt if that date is in the future or unavailable.
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::ffi::CStr;
|
||||
use std::str::FromStr;
|
||||
use std::{fmt, str};
|
||||
|
||||
use mmime::mailimf::types::*;
|
||||
use mmime::mailimf_types::*;
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
@@ -79,21 +79,27 @@ impl Aheader {
|
||||
let optional_field = unsafe { (*field).fld_data.fld_optional_field };
|
||||
if !optional_field.is_null()
|
||||
&& unsafe { !(*optional_field).fld_name.is_null() }
|
||||
&& unsafe { CStr::from_ptr((*optional_field).fld_name).to_string_lossy() }
|
||||
&& unsafe { CStr::from_ptr((*optional_field).fld_name).to_str().unwrap() }
|
||||
== "Autocrypt"
|
||||
{
|
||||
let value =
|
||||
unsafe { CStr::from_ptr((*optional_field).fld_value).to_string_lossy() };
|
||||
let value = unsafe {
|
||||
CStr::from_ptr((*optional_field).fld_value)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
if let Ok(test) = Self::from_str(&value) {
|
||||
if addr_cmp(&test.addr, wanted_from) {
|
||||
if fine_header.is_none() {
|
||||
fine_header = Some(test);
|
||||
} else {
|
||||
// TODO: figure out what kind of error case this is
|
||||
return None;
|
||||
match Self::from_str(value) {
|
||||
Ok(test) => {
|
||||
if addr_cmp(&test.addr, wanted_from) {
|
||||
if fine_header.is_none() {
|
||||
fine_header = Some(test);
|
||||
} else {
|
||||
// TODO: figure out what kind of error case this is
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,9 +131,9 @@ impl str::FromStr for Aheader {
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut attributes: BTreeMap<String, String> = s
|
||||
.split(';')
|
||||
.split(";")
|
||||
.filter_map(|a| {
|
||||
let attribute: Vec<&str> = a.trim().splitn(2, '=').collect();
|
||||
let attribute: Vec<&str> = a.trim().splitn(2, "=").collect();
|
||||
if attribute.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
@@ -172,7 +178,7 @@ impl str::FromStr for Aheader {
|
||||
|
||||
// Autocrypt-Level0: unknown attributes starting with an underscore can be safely ignored
|
||||
// Autocrypt-Level0: unknown attribute, treat the header as invalid
|
||||
if attributes.keys().any(|k| !k.starts_with('_')) {
|
||||
if attributes.keys().find(|k| !k.starts_with("_")).is_some() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
|
||||
674
src/blob.rs
674
src/blob.rs
@@ -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"));
|
||||
}
|
||||
}
|
||||
1135
src/chat.rs
1135
src/chat.rs
File diff suppressed because it is too large
Load Diff
228
src/chatlist.rs
228
src/chatlist.rs
@@ -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::*;
|
||||
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 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,
|
||||
)?
|
||||
"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 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;"
|
||||
),
|
||||
"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 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;"
|
||||
),
|
||||
"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 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;"
|
||||
),
|
||||
"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) {
|
||||
ids.insert(0, (DC_CHAT_ID_DEADDROP, last_deaddrop_fresh_msg_id));
|
||||
let last_deaddrop_fresh_msg_id = get_last_deaddrop_fresh_msg(context);
|
||||
if last_deaddrop_fresh_msg_id > 0 {
|
||||
ids.push((1, last_deaddrop_fresh_msg_id));
|
||||
}
|
||||
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.
|
||||
@@ -284,24 +258,30 @@ impl Chatlist {
|
||||
let chat_loaded: Chat;
|
||||
let chat = if let Some(chat) = chat {
|
||||
chat
|
||||
} else if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0) {
|
||||
chat_loaded = chat;
|
||||
&chat_loaded
|
||||
} else {
|
||||
return ret;
|
||||
if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0) {
|
||||
chat_loaded = chat;
|
||||
&chat_loaded
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
let lastmsg_id = self.ids[index].1;
|
||||
let mut lastcontact = None;
|
||||
|
||||
let 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) = dc_msg_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
|
||||
};
|
||||
@@ -330,21 +310,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()
|
||||
}
|
||||
|
||||
@@ -19,12 +19,10 @@ pub enum Config {
|
||||
MailUser,
|
||||
MailPw,
|
||||
MailPort,
|
||||
ImapCertificateChecks,
|
||||
SendServer,
|
||||
SendUser,
|
||||
SendPw,
|
||||
SendPort,
|
||||
SmtpCertificateChecks,
|
||||
ServerFlags,
|
||||
#[strum(props(default = "INBOX"))]
|
||||
ImapFolder,
|
||||
@@ -32,8 +30,6 @@ pub enum Config {
|
||||
Selfstatus,
|
||||
Selfavatar,
|
||||
#[strum(props(default = "1"))]
|
||||
BccSelf,
|
||||
#[strum(props(default = "1"))]
|
||||
E2eeEnabled,
|
||||
#[strum(props(default = "1"))]
|
||||
MdnsEnabled,
|
||||
@@ -45,7 +41,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,
|
||||
@@ -54,12 +50,10 @@ pub enum Config {
|
||||
ConfiguredMailPw,
|
||||
ConfiguredMailPort,
|
||||
ConfiguredMailSecurity,
|
||||
ConfiguredImapCertificateChecks,
|
||||
ConfiguredSendServer,
|
||||
ConfiguredSendUser,
|
||||
ConfiguredSendPw,
|
||||
ConfiguredSendPort,
|
||||
ConfiguredSmtpCertificateChecks,
|
||||
ConfiguredServerFlags,
|
||||
ConfiguredSendSecurity,
|
||||
ConfiguredE2EEEnabled,
|
||||
@@ -78,13 +72,13 @@ impl Context {
|
||||
pub fn get_config(&self, key: Config) -> Option<String> {
|
||||
let value = match key {
|
||||
Config::Selfavatar => {
|
||||
let rel_path = self.sql.get_raw_config(self, key);
|
||||
rel_path.map(|p| dc_get_abs_path(self, &p).to_string_lossy().into_owned())
|
||||
let rel_path = self.sql.get_config(self, key);
|
||||
rel_path.map(|p| dc_get_abs_path(self, &p).to_str().unwrap().to_string())
|
||||
}
|
||||
Config::SysVersion => Some((&*DC_VERSION_STR).clone()),
|
||||
Config::SysMsgsizeMaxRecommended => Some(format!("{}", 24 * 1024 * 1024 / 4 * 3)),
|
||||
Config::SysConfigKeys => Some(get_config_keys_string()),
|
||||
_ => self.sql.get_raw_config(self, key),
|
||||
_ => self.sql.get_config(self, key),
|
||||
};
|
||||
|
||||
if value.is_some() {
|
||||
@@ -98,16 +92,6 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_config_int(&self, key: Config) -> i32 {
|
||||
self.get_config(key)
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn get_config_bool(&self, key: Config) -> bool {
|
||||
self.get_config_int(key) != 0
|
||||
}
|
||||
|
||||
/// Set the given config key.
|
||||
/// If `None` is passed as a value the value is cleared and set to the default if there is one.
|
||||
pub fn set_config(&self, key: Config, value: Option<&str>) -> Result<(), Error> {
|
||||
@@ -115,20 +99,20 @@ impl Context {
|
||||
Config::Selfavatar if value.is_some() => {
|
||||
let rel_path = std::fs::canonicalize(value.unwrap())?;
|
||||
self.sql
|
||||
.set_raw_config(self, key, Some(&rel_path.to_string_lossy()))
|
||||
.set_config(self, key, Some(&rel_path.to_string_lossy()))
|
||||
}
|
||||
Config::InboxWatch => {
|
||||
let ret = self.sql.set_raw_config(self, key, value);
|
||||
let ret = self.sql.set_config(self, key, value);
|
||||
interrupt_imap_idle(self);
|
||||
ret
|
||||
}
|
||||
Config::SentboxWatch => {
|
||||
let ret = self.sql.set_raw_config(self, key, value);
|
||||
let ret = self.sql.set_config(self, key, value);
|
||||
interrupt_sentbox_idle(self);
|
||||
ret
|
||||
}
|
||||
Config::MvboxWatch => {
|
||||
let ret = self.sql.set_raw_config(self, key, value);
|
||||
let ret = self.sql.set_config(self, key, value);
|
||||
interrupt_mvbox_idle(self);
|
||||
ret
|
||||
}
|
||||
@@ -140,9 +124,9 @@ impl Context {
|
||||
value
|
||||
};
|
||||
|
||||
self.sql.set_raw_config(self, key, val)
|
||||
self.sql.set_config(self, key, val)
|
||||
}
|
||||
_ => self.sql.set_raw_config(self, key, value),
|
||||
_ => self.sql.set_config(self, key, value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,64 +3,61 @@ use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
use crate::dc_tools::*;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::x::*;
|
||||
|
||||
use super::read_autoconf_file;
|
||||
/* ******************************************************************************
|
||||
* Thunderbird's Autoconfigure
|
||||
******************************************************************************/
|
||||
/* documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
|
||||
struct MozAutoconfigure<'a> {
|
||||
pub in_emailaddr: &'a str,
|
||||
#[repr(C)]
|
||||
struct moz_autoconfigure_t<'a> {
|
||||
pub in_0: &'a LoginParam,
|
||||
pub in_emaildomain: &'a str,
|
||||
pub in_emaillocalpart: &'a str,
|
||||
pub out: LoginParam,
|
||||
pub out_imap_set: bool,
|
||||
pub out_smtp_set: bool,
|
||||
pub tag_server: MozServer,
|
||||
pub tag_config: MozConfigTag,
|
||||
pub out_imap_set: libc::c_int,
|
||||
pub out_smtp_set: libc::c_int,
|
||||
pub tag_server: libc::c_int,
|
||||
pub tag_config: libc::c_int,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum MozServer {
|
||||
Undefined,
|
||||
Imap,
|
||||
Smtp,
|
||||
}
|
||||
|
||||
enum MozConfigTag {
|
||||
Undefined,
|
||||
Hostname,
|
||||
Port,
|
||||
Sockettype,
|
||||
Username,
|
||||
}
|
||||
|
||||
pub fn moz_parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Error> {
|
||||
let mut reader = quick_xml::Reader::from_str(xml_raw);
|
||||
reader.trim_text(true);
|
||||
pub unsafe fn moz_autoconfigure(
|
||||
context: &Context,
|
||||
url: &str,
|
||||
param_in: &LoginParam,
|
||||
) -> Option<LoginParam> {
|
||||
let xml_raw = read_autoconf_file(context, url);
|
||||
if xml_raw.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Split address into local part and domain part.
|
||||
let p = match in_emailaddr.find('@') {
|
||||
Some(i) => i,
|
||||
None => bail!("Email address {} does not contain @", in_emailaddr),
|
||||
};
|
||||
let (in_emaillocalpart, in_emaildomain) = in_emailaddr.split_at(p);
|
||||
let p = param_in.addr.find("@");
|
||||
if p.is_none() {
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
return None;
|
||||
}
|
||||
let (in_emaillocalpart, in_emaildomain) = param_in.addr.split_at(p.unwrap());
|
||||
let in_emaildomain = &in_emaildomain[1..];
|
||||
|
||||
let mut moz_ac = MozAutoconfigure {
|
||||
in_emailaddr,
|
||||
let mut reader = quick_xml::Reader::from_str(as_str(xml_raw));
|
||||
reader.trim_text(true);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
|
||||
let mut moz_ac = moz_autoconfigure_t {
|
||||
in_0: param_in,
|
||||
in_emaildomain,
|
||||
in_emaillocalpart,
|
||||
out: LoginParam::new(),
|
||||
out_imap_set: false,
|
||||
out_smtp_set: false,
|
||||
tag_server: MozServer::Undefined,
|
||||
tag_config: MozConfigTag::Undefined,
|
||||
out_imap_set: 0,
|
||||
out_smtp_set: 0,
|
||||
tag_server: 0,
|
||||
tag_config: 0,
|
||||
};
|
||||
|
||||
let mut buf = Vec::new();
|
||||
loop {
|
||||
match reader.read_event(&mut buf) {
|
||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||
@@ -71,7 +68,8 @@ pub fn moz_parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Er
|
||||
moz_autoconfigure_text_cb(e, &mut moz_ac, &reader)
|
||||
}
|
||||
Err(e) => {
|
||||
bail!(
|
||||
error!(
|
||||
context,
|
||||
"Configure xml: Error at position {}: {:?}",
|
||||
reader.buffer_position(),
|
||||
e
|
||||
@@ -89,36 +87,23 @@ pub fn moz_parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Er
|
||||
|| moz_ac.out.send_port == 0
|
||||
{
|
||||
let r = moz_ac.out.to_string();
|
||||
bail!("Bad or incomplete autoconfig: {}", r,);
|
||||
warn!(context, "Bad or incomplete autoconfig: {}", r,);
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
return None;
|
||||
}
|
||||
|
||||
Ok(moz_ac.out)
|
||||
}
|
||||
|
||||
pub fn moz_autoconfigure(
|
||||
context: &Context,
|
||||
url: &str,
|
||||
param_in: &LoginParam,
|
||||
) -> Option<LoginParam> {
|
||||
let xml_raw = read_autoconf_file(context, url)?;
|
||||
|
||||
match moz_parse_xml(¶m_in.addr, &xml_raw) {
|
||||
Err(err) => {
|
||||
warn!(context, "{}", err);
|
||||
None
|
||||
}
|
||||
Ok(lp) => Some(lp),
|
||||
}
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
Some(moz_ac.out)
|
||||
}
|
||||
|
||||
fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
||||
event: &BytesText,
|
||||
moz_ac: &mut MozAutoconfigure,
|
||||
moz_ac: &mut moz_autoconfigure_t,
|
||||
reader: &quick_xml::Reader<B>,
|
||||
) {
|
||||
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
||||
|
||||
let addr = moz_ac.in_emailaddr;
|
||||
let addr = &moz_ac.in_0.addr;
|
||||
let email_local = moz_ac.in_emaillocalpart;
|
||||
let email_domain = moz_ac.in_emaildomain;
|
||||
|
||||
@@ -128,12 +113,12 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
||||
.replace("%EMAILLOCALPART%", email_local)
|
||||
.replace("%EMAILDOMAIN%", email_domain);
|
||||
|
||||
match moz_ac.tag_server {
|
||||
MozServer::Imap => match moz_ac.tag_config {
|
||||
MozConfigTag::Hostname => moz_ac.out.mail_server = val,
|
||||
MozConfigTag::Port => moz_ac.out.mail_port = val.parse().unwrap_or_default(),
|
||||
MozConfigTag::Username => moz_ac.out.mail_user = val,
|
||||
MozConfigTag::Sockettype => {
|
||||
if moz_ac.tag_server == 1 {
|
||||
match moz_ac.tag_config {
|
||||
10 => moz_ac.out.mail_server = val,
|
||||
11 => moz_ac.out.mail_port = val.parse().unwrap_or_default(),
|
||||
12 => moz_ac.out.mail_user = val,
|
||||
13 => {
|
||||
let val_lower = val.to_lowercase();
|
||||
if val_lower == "ssl" {
|
||||
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
|
||||
@@ -146,12 +131,13 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
MozServer::Smtp => match moz_ac.tag_config {
|
||||
MozConfigTag::Hostname => moz_ac.out.send_server = val,
|
||||
MozConfigTag::Port => moz_ac.out.send_port = val.parse().unwrap_or_default(),
|
||||
MozConfigTag::Username => moz_ac.out.send_user = val,
|
||||
MozConfigTag::Sockettype => {
|
||||
}
|
||||
} else if moz_ac.tag_server == 2 {
|
||||
match moz_ac.tag_config {
|
||||
10 => moz_ac.out.send_server = val,
|
||||
11 => moz_ac.out.send_port = val.parse().unwrap_or_default(),
|
||||
12 => moz_ac.out.send_user = val,
|
||||
13 => {
|
||||
let val_lower = val.to_lowercase();
|
||||
if val_lower == "ssl" {
|
||||
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
|
||||
@@ -164,34 +150,29 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
MozServer::Undefined => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn moz_autoconfigure_endtag_cb(event: &BytesEnd, moz_ac: &mut MozAutoconfigure) {
|
||||
fn moz_autoconfigure_endtag_cb(event: &BytesEnd, moz_ac: &mut moz_autoconfigure_t) {
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
|
||||
if tag == "incomingserver" {
|
||||
if moz_ac.tag_server == MozServer::Imap {
|
||||
moz_ac.out_imap_set = true;
|
||||
}
|
||||
moz_ac.tag_server = MozServer::Undefined;
|
||||
moz_ac.tag_config = MozConfigTag::Undefined;
|
||||
moz_ac.tag_server = 0;
|
||||
moz_ac.tag_config = 0;
|
||||
moz_ac.out_imap_set = 1;
|
||||
} else if tag == "outgoingserver" {
|
||||
if moz_ac.tag_server == MozServer::Smtp {
|
||||
moz_ac.out_smtp_set = true;
|
||||
}
|
||||
moz_ac.tag_server = MozServer::Undefined;
|
||||
moz_ac.tag_config = MozConfigTag::Undefined;
|
||||
moz_ac.tag_server = 0;
|
||||
moz_ac.tag_config = 0;
|
||||
moz_ac.out_smtp_set = 1;
|
||||
} else {
|
||||
moz_ac.tag_config = MozConfigTag::Undefined;
|
||||
moz_ac.tag_config = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn moz_autoconfigure_starttag_cb<B: std::io::BufRead>(
|
||||
event: &BytesStart,
|
||||
moz_ac: &mut MozAutoconfigure,
|
||||
moz_ac: &mut moz_autoconfigure_t,
|
||||
reader: &quick_xml::Reader<B>,
|
||||
) {
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
@@ -208,115 +189,25 @@ fn moz_autoconfigure_starttag_cb<B: std::io::BufRead>(
|
||||
.unwrap_or_default()
|
||||
.to_lowercase();
|
||||
|
||||
if typ == "imap" && !moz_ac.out_imap_set {
|
||||
MozServer::Imap
|
||||
if typ == "imap" && moz_ac.out_imap_set == 0 {
|
||||
1
|
||||
} else {
|
||||
MozServer::Undefined
|
||||
0
|
||||
}
|
||||
} else {
|
||||
MozServer::Undefined
|
||||
0
|
||||
};
|
||||
moz_ac.tag_config = MozConfigTag::Undefined;
|
||||
moz_ac.tag_config = 0;
|
||||
} else if tag == "outgoingserver" {
|
||||
moz_ac.tag_server = if !moz_ac.out_smtp_set {
|
||||
MozServer::Smtp
|
||||
} else {
|
||||
MozServer::Undefined
|
||||
};
|
||||
moz_ac.tag_config = MozConfigTag::Undefined;
|
||||
moz_ac.tag_server = if moz_ac.out_smtp_set == 0 { 2 } else { 0 };
|
||||
moz_ac.tag_config = 0;
|
||||
} else if tag == "hostname" {
|
||||
moz_ac.tag_config = MozConfigTag::Hostname;
|
||||
moz_ac.tag_config = 10;
|
||||
} else if tag == "port" {
|
||||
moz_ac.tag_config = MozConfigTag::Port;
|
||||
moz_ac.tag_config = 11;
|
||||
} else if tag == "sockettype" {
|
||||
moz_ac.tag_config = MozConfigTag::Sockettype;
|
||||
moz_ac.tag_config = 13;
|
||||
} else if tag == "username" {
|
||||
moz_ac.tag_config = MozConfigTag::Username;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_outlook_autoconfig() {
|
||||
// Copied from https://autoconfig.thunderbird.net/v1.1/outlook.com on 2019-10-11
|
||||
let xml_raw =
|
||||
"<clientConfig version=\"1.1\">
|
||||
<emailProvider id=\"outlook.com\">
|
||||
<domain>hotmail.com</domain>
|
||||
<domain>hotmail.co.uk</domain>
|
||||
<domain>hotmail.co.jp</domain>
|
||||
<domain>hotmail.com.br</domain>
|
||||
<domain>hotmail.de</domain>
|
||||
<domain>hotmail.fr</domain>
|
||||
<domain>hotmail.it</domain>
|
||||
<domain>hotmail.es</domain>
|
||||
<domain>live.com</domain>
|
||||
<domain>live.co.uk</domain>
|
||||
<domain>live.co.jp</domain>
|
||||
<domain>live.de</domain>
|
||||
<domain>live.fr</domain>
|
||||
<domain>live.it</domain>
|
||||
<domain>live.jp</domain>
|
||||
<domain>msn.com</domain>
|
||||
<domain>outlook.com</domain>
|
||||
<displayName>Outlook.com (Microsoft)</displayName>
|
||||
<displayShortName>Outlook</displayShortName>
|
||||
<incomingServer type=\"exchange\">
|
||||
<hostname>outlook.office365.com</hostname>
|
||||
<port>443</port>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
<socketType>SSL</socketType>
|
||||
<authentication>OAuth2</authentication>
|
||||
<owaURL>https://outlook.office365.com/owa/</owaURL>
|
||||
<ewsURL>https://outlook.office365.com/ews/exchange.asmx</ewsURL>
|
||||
<useGlobalPreferredServer>true</useGlobalPreferredServer>
|
||||
</incomingServer>
|
||||
<incomingServer type=\"imap\">
|
||||
<hostname>outlook.office365.com</hostname>
|
||||
<port>993</port>
|
||||
<socketType>SSL</socketType>
|
||||
<authentication>password-cleartext</authentication>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
</incomingServer>
|
||||
<incomingServer type=\"pop3\">
|
||||
<hostname>outlook.office365.com</hostname>
|
||||
<port>995</port>
|
||||
<socketType>SSL</socketType>
|
||||
<authentication>password-cleartext</authentication>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
<pop3>
|
||||
<leaveMessagesOnServer>true</leaveMessagesOnServer>
|
||||
<!-- Outlook.com docs specifically mention that POP3 deletes have effect on the main inbox on webmail and IMAP -->
|
||||
</pop3>
|
||||
</incomingServer>
|
||||
<outgoingServer type=\"smtp\">
|
||||
<hostname>smtp.office365.com</hostname>
|
||||
<port>587</port>
|
||||
<socketType>STARTTLS</socketType>
|
||||
<authentication>password-cleartext</authentication>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
</outgoingServer>
|
||||
<documentation url=\"http://windows.microsoft.com/en-US/windows/outlook/send-receive-from-app\">
|
||||
<descr lang=\"en\">Set up an email app with Outlook.com</descr>
|
||||
</documentation>
|
||||
</emailProvider>
|
||||
<webMail>
|
||||
<loginPage url=\"https://www.outlook.com/\"/>
|
||||
<loginPageInfo url=\"https://www.outlook.com/\">
|
||||
<username>%EMAILADDRESS%</username>
|
||||
<usernameField id=\"i0116\" name=\"login\"/>
|
||||
<passwordField id=\"i0118\" name=\"passwd\"/>
|
||||
<loginButton id=\"idSIButton9\" name=\"SI\"/>
|
||||
</loginPageInfo>
|
||||
</webMail>
|
||||
</clientConfig>";
|
||||
let res = moz_parse_xml("example@outlook.com", xml_raw).expect("XML parsing failed");
|
||||
assert_eq!(res.mail_server, "outlook.office365.com");
|
||||
assert_eq!(res.mail_port, 993);
|
||||
assert_eq!(res.send_server, "smtp.office365.com");
|
||||
assert_eq!(res.send_port, 587);
|
||||
moz_ac.tag_config = 12;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,249 +1,213 @@
|
||||
use quick_xml;
|
||||
use quick_xml::events::BytesEnd;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
use crate::dc_tools::*;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::x::*;
|
||||
use std::ptr;
|
||||
|
||||
use super::read_autoconf_file;
|
||||
|
||||
/// Outlook's Autodiscover
|
||||
struct OutlookAutodiscover {
|
||||
/* ******************************************************************************
|
||||
* Outlook's Autodiscover
|
||||
******************************************************************************/
|
||||
#[repr(C)]
|
||||
struct outlk_autodiscover_t<'a> {
|
||||
pub in_0: &'a LoginParam,
|
||||
pub out: LoginParam,
|
||||
pub out_imap_set: bool,
|
||||
pub out_smtp_set: bool,
|
||||
pub config_type: Option<String>,
|
||||
pub config_server: String,
|
||||
pub config_port: i32,
|
||||
pub config_ssl: String,
|
||||
pub config_redirecturl: Option<String>,
|
||||
pub out_imap_set: libc::c_int,
|
||||
pub out_smtp_set: libc::c_int,
|
||||
pub tag_config: libc::c_int,
|
||||
pub config: [*mut libc::c_char; 6],
|
||||
pub redirect: *mut libc::c_char,
|
||||
}
|
||||
|
||||
enum ParsingResult {
|
||||
LoginParam(LoginParam),
|
||||
RedirectUrl(String),
|
||||
}
|
||||
|
||||
fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
|
||||
let mut outlk_ad = OutlookAutodiscover {
|
||||
pub unsafe fn outlk_autodiscover(
|
||||
context: &Context,
|
||||
url__: &str,
|
||||
param_in: &LoginParam,
|
||||
) -> Option<LoginParam> {
|
||||
let mut xml_raw: *mut libc::c_char = ptr::null_mut();
|
||||
let mut url = url__.strdup();
|
||||
let mut outlk_ad = outlk_autodiscover_t {
|
||||
in_0: param_in,
|
||||
out: LoginParam::new(),
|
||||
out_imap_set: false,
|
||||
out_smtp_set: false,
|
||||
config_type: None,
|
||||
config_server: String::new(),
|
||||
config_port: 0,
|
||||
config_ssl: String::new(),
|
||||
config_redirecturl: None,
|
||||
out_imap_set: 0,
|
||||
out_smtp_set: 0,
|
||||
tag_config: 0,
|
||||
config: [ptr::null_mut(); 6],
|
||||
redirect: ptr::null_mut(),
|
||||
};
|
||||
|
||||
let mut reader = quick_xml::Reader::from_str(&xml_raw);
|
||||
reader.trim_text(true);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
|
||||
let mut current_tag: Option<String> = None;
|
||||
|
||||
let ok_to_continue;
|
||||
let mut i = 0;
|
||||
loop {
|
||||
match reader.read_event(&mut buf) {
|
||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||
let tag = String::from_utf8_lossy(e.name()).trim().to_lowercase();
|
||||
|
||||
if tag == "protocol" {
|
||||
outlk_ad.config_type = None;
|
||||
outlk_ad.config_server = String::new();
|
||||
outlk_ad.config_port = 0;
|
||||
outlk_ad.config_ssl = String::new();
|
||||
outlk_ad.config_redirecturl = None;
|
||||
|
||||
current_tag = None;
|
||||
} else {
|
||||
current_tag = Some(tag);
|
||||
}
|
||||
}
|
||||
Ok(quick_xml::events::Event::End(ref e)) => {
|
||||
outlk_autodiscover_endtag_cb(e, &mut outlk_ad);
|
||||
current_tag = None;
|
||||
}
|
||||
Ok(quick_xml::events::Event::Text(ref e)) => {
|
||||
let val = e.unescape_and_decode(&reader).unwrap_or_default();
|
||||
|
||||
if let Some(ref tag) = current_tag {
|
||||
match tag.as_str() {
|
||||
"type" => {
|
||||
outlk_ad.config_type = Some(val.trim().to_lowercase().to_string())
|
||||
}
|
||||
"server" => outlk_ad.config_server = val.trim().to_string(),
|
||||
"port" => outlk_ad.config_port = val.trim().parse().unwrap_or_default(),
|
||||
"ssl" => outlk_ad.config_ssl = val.trim().to_string(),
|
||||
"redirecturl" => outlk_ad.config_redirecturl = Some(val.trim().to_string()),
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
bail!(
|
||||
"Configure xml: Error at position {}: {:?}",
|
||||
reader.buffer_position(),
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(quick_xml::events::Event::Eof) => break,
|
||||
_ => (),
|
||||
if !(i < 10) {
|
||||
ok_to_continue = true;
|
||||
break;
|
||||
}
|
||||
buf.clear();
|
||||
memset(
|
||||
&mut outlk_ad as *mut outlk_autodiscover_t as *mut libc::c_void,
|
||||
0,
|
||||
::std::mem::size_of::<outlk_autodiscover_t>(),
|
||||
);
|
||||
xml_raw = read_autoconf_file(context, as_str(url));
|
||||
if xml_raw.is_null() {
|
||||
ok_to_continue = false;
|
||||
break;
|
||||
}
|
||||
|
||||
let mut reader = quick_xml::Reader::from_str(as_str(xml_raw));
|
||||
reader.trim_text(true);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
|
||||
loop {
|
||||
match reader.read_event(&mut buf) {
|
||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||
outlk_autodiscover_starttag_cb(e, &mut outlk_ad)
|
||||
}
|
||||
Ok(quick_xml::events::Event::End(ref e)) => {
|
||||
outlk_autodiscover_endtag_cb(e, &mut outlk_ad)
|
||||
}
|
||||
Ok(quick_xml::events::Event::Text(ref e)) => {
|
||||
outlk_autodiscover_text_cb(e, &mut outlk_ad, &reader)
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
context,
|
||||
"Configure xml: Error at position {}: {:?}",
|
||||
reader.buffer_position(),
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(quick_xml::events::Event::Eof) => break,
|
||||
_ => (),
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
if !(!outlk_ad.config[5].is_null()
|
||||
&& 0 != *outlk_ad.config[5usize].offset(0isize) as libc::c_int)
|
||||
{
|
||||
ok_to_continue = true;
|
||||
break;
|
||||
}
|
||||
free(url as *mut libc::c_void);
|
||||
url = dc_strdup(outlk_ad.config[5usize]);
|
||||
|
||||
outlk_clean_config(&mut outlk_ad);
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
xml_raw = ptr::null_mut();
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// XML redirect via redirecturl
|
||||
if outlk_ad.config_redirecturl.is_none()
|
||||
|| outlk_ad.config_redirecturl.as_ref().unwrap().is_empty()
|
||||
{
|
||||
if ok_to_continue {
|
||||
if outlk_ad.out.mail_server.is_empty()
|
||||
|| outlk_ad.out.mail_port == 0
|
||||
|| outlk_ad.out.send_server.is_empty()
|
||||
|| outlk_ad.out.send_port == 0
|
||||
{
|
||||
let r = outlk_ad.out.to_string();
|
||||
bail!("Bad or incomplete autoconfig: {}", r,);
|
||||
}
|
||||
Ok(ParsingResult::LoginParam(outlk_ad.out))
|
||||
} else {
|
||||
Ok(ParsingResult::RedirectUrl(
|
||||
outlk_ad.config_redirecturl.unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
warn!(context, "Bad or incomplete autoconfig: {}", r,);
|
||||
free(url as *mut libc::c_void);
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
outlk_clean_config(&mut outlk_ad);
|
||||
|
||||
pub fn outlk_autodiscover(
|
||||
context: &Context,
|
||||
url: &str,
|
||||
_param_in: &LoginParam,
|
||||
) -> Option<LoginParam> {
|
||||
let mut url = url.to_string();
|
||||
/* Follow up to 10 xml-redirects (http-redirects are followed in read_autoconf_file() */
|
||||
for _i in 0..10 {
|
||||
if let Some(xml_raw) = read_autoconf_file(context, &url) {
|
||||
match outlk_parse_xml(&xml_raw) {
|
||||
Err(err) => {
|
||||
warn!(context, "{}", err);
|
||||
return None;
|
||||
}
|
||||
Ok(res) => match res {
|
||||
ParsingResult::RedirectUrl(redirect_url) => url = redirect_url,
|
||||
ParsingResult::LoginParam(login_param) => return Some(login_param),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
None
|
||||
free(url as *mut libc::c_void);
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
outlk_clean_config(&mut outlk_ad);
|
||||
Some(outlk_ad.out)
|
||||
}
|
||||
|
||||
fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut OutlookAutodiscover) {
|
||||
unsafe fn outlk_clean_config(mut outlk_ad: *mut outlk_autodiscover_t) {
|
||||
for i in 0..6 {
|
||||
free((*outlk_ad).config[i] as *mut libc::c_void);
|
||||
(*outlk_ad).config[i] = ptr::null_mut();
|
||||
}
|
||||
}
|
||||
|
||||
fn outlk_autodiscover_text_cb<B: std::io::BufRead>(
|
||||
event: &BytesText,
|
||||
outlk_ad: &mut outlk_autodiscover_t,
|
||||
reader: &quick_xml::Reader<B>,
|
||||
) {
|
||||
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
||||
|
||||
unsafe {
|
||||
free(outlk_ad.config[outlk_ad.tag_config as usize].cast());
|
||||
outlk_ad.config[outlk_ad.tag_config as usize] = val.trim().strdup();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut outlk_autodiscover_t) {
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
|
||||
if tag == "protocol" {
|
||||
if let Some(type_) = &outlk_ad.config_type {
|
||||
let port = outlk_ad.config_port;
|
||||
let ssl_on = outlk_ad.config_ssl == "on";
|
||||
let ssl_off = outlk_ad.config_ssl == "off";
|
||||
if type_ == "imap" && !outlk_ad.out_imap_set {
|
||||
outlk_ad.out.mail_server =
|
||||
std::mem::replace(&mut outlk_ad.config_server, String::new());
|
||||
if !outlk_ad.config[1].is_null() {
|
||||
let port = dc_atoi_null_is_0(outlk_ad.config[3]);
|
||||
let ssl_on = (!outlk_ad.config[4].is_null()
|
||||
&& strcasecmp(
|
||||
outlk_ad.config[4],
|
||||
b"on\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0) as libc::c_int;
|
||||
let ssl_off = (!outlk_ad.config[4].is_null()
|
||||
&& strcasecmp(
|
||||
outlk_ad.config[4],
|
||||
b"off\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0) as libc::c_int;
|
||||
if strcasecmp(
|
||||
outlk_ad.config[1],
|
||||
b"imap\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0
|
||||
&& outlk_ad.out_imap_set == 0
|
||||
{
|
||||
outlk_ad.out.mail_server = to_string(outlk_ad.config[2]);
|
||||
outlk_ad.out.mail_port = port;
|
||||
if ssl_on {
|
||||
if 0 != ssl_on {
|
||||
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
|
||||
} else if ssl_off {
|
||||
} else if 0 != ssl_off {
|
||||
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
|
||||
}
|
||||
outlk_ad.out_imap_set = true
|
||||
} else if type_ == "smtp" && !outlk_ad.out_smtp_set {
|
||||
outlk_ad.out.send_server =
|
||||
std::mem::replace(&mut outlk_ad.config_server, String::new());
|
||||
outlk_ad.out.send_port = outlk_ad.config_port;
|
||||
if ssl_on {
|
||||
outlk_ad.out_imap_set = 1
|
||||
} else if strcasecmp(
|
||||
outlk_ad.config[1usize],
|
||||
b"smtp\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0
|
||||
&& outlk_ad.out_smtp_set == 0
|
||||
{
|
||||
outlk_ad.out.send_server = to_string(outlk_ad.config[2]);
|
||||
outlk_ad.out.send_port = port;
|
||||
if 0 != ssl_on {
|
||||
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
|
||||
} else if ssl_off {
|
||||
} else if 0 != ssl_off {
|
||||
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
|
||||
}
|
||||
outlk_ad.out_smtp_set = true
|
||||
outlk_ad.out_smtp_set = 1
|
||||
}
|
||||
}
|
||||
outlk_clean_config(outlk_ad);
|
||||
}
|
||||
outlk_ad.tag_config = 0;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
fn outlk_autodiscover_starttag_cb(event: &BytesStart, outlk_ad: &mut outlk_autodiscover_t) {
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
|
||||
#[test]
|
||||
fn test_parse_redirect() {
|
||||
let res = outlk_parse_xml("
|
||||
<?xml version=\"1.0\" encoding=\"utf-8\"?>
|
||||
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
|
||||
<Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">
|
||||
<Account>
|
||||
<AccountType>email</AccountType>
|
||||
<Action>redirectUrl</Action>
|
||||
<RedirectUrl>https://mail.example.com/autodiscover/autodiscover.xml</RedirectUrl>
|
||||
</Account>
|
||||
</Response>
|
||||
</Autodiscover>
|
||||
").expect("XML is not parsed successfully");
|
||||
match res {
|
||||
ParsingResult::LoginParam(_lp) => {
|
||||
panic!("redirecturl is not found");
|
||||
}
|
||||
ParsingResult::RedirectUrl(url) => {
|
||||
assert_eq!(
|
||||
url,
|
||||
"https://mail.example.com/autodiscover/autodiscover.xml"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_loginparam() {
|
||||
let res = outlk_parse_xml(
|
||||
"\
|
||||
<?xml version=\"1.0\" encoding=\"utf-8\"?>
|
||||
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
|
||||
<Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">
|
||||
<Account>
|
||||
<AccountType>email</AccountType>
|
||||
<Action>settings</Action>
|
||||
<Protocol>
|
||||
<Type>IMAP</Type>
|
||||
<Server>example.com</Server>
|
||||
<Port>993</Port>
|
||||
<SSL>on</SSL>
|
||||
<AuthRequired>on</AuthRequired>
|
||||
</Protocol>
|
||||
<Protocol>
|
||||
<Type>SMTP</Type>
|
||||
<Server>smtp.example.com</Server>
|
||||
<Port>25</Port>
|
||||
<SSL>off</SSL>
|
||||
<AuthRequired>on</AuthRequired>
|
||||
</Protocol>
|
||||
</Account>
|
||||
</Response>
|
||||
</Autodiscover>",
|
||||
)
|
||||
.expect("XML is not parsed successfully");
|
||||
|
||||
match res {
|
||||
ParsingResult::LoginParam(lp) => {
|
||||
assert_eq!(lp.mail_server, "example.com");
|
||||
assert_eq!(lp.mail_port, 993);
|
||||
assert_eq!(lp.send_server, "smtp.example.com");
|
||||
assert_eq!(lp.send_port, 25);
|
||||
}
|
||||
ParsingResult::RedirectUrl(_) => {
|
||||
panic!("RedirectUrl is not expected");
|
||||
}
|
||||
}
|
||||
}
|
||||
if tag == "protocol" {
|
||||
unsafe { outlk_clean_config(outlk_ad) };
|
||||
} else if tag == "type" {
|
||||
outlk_ad.tag_config = 1
|
||||
} else if tag == "server" {
|
||||
outlk_ad.tag_config = 2
|
||||
} else if tag == "port" {
|
||||
outlk_ad.tag_config = 3
|
||||
} else if tag == "ssl" {
|
||||
outlk_ad.tag_config = 4
|
||||
} else if tag == "redirecturl" {
|
||||
outlk_ad.tag_config = 5
|
||||
};
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,7 @@ impl Default for MoveState {
|
||||
|
||||
// some defaults
|
||||
const DC_E2EE_DEFAULT_ENABLED: i32 = 1;
|
||||
pub const DC_MDNS_DEFAULT_ENABLED: i32 = 1;
|
||||
const DC_INBOX_WATCH_DEFAULT: i32 = 1;
|
||||
const DC_SENTBOX_WATCH_DEFAULT: i32 = 1;
|
||||
const DC_MVBOX_WATCH_DEFAULT: i32 = 1;
|
||||
@@ -44,20 +45,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,14 +55,19 @@ 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;
|
||||
|
||||
// values for DC_PARAM_FORCE_PLAINTEXT
|
||||
pub(crate) const DC_FP_NO_AUTOCRYPT_HEADER: i32 = 2;
|
||||
pub(crate) const DC_FP_ADD_AUTOCRYPT_HEADER: i32 = 1;
|
||||
/// param1 is a directory where the keys are written to
|
||||
const DC_IMEX_EXPORT_SELF_KEYS: usize = 1;
|
||||
/// param1 is a directory where the keys are searched in and read from
|
||||
const DC_IMEX_IMPORT_SELF_KEYS: usize = 2;
|
||||
/// param1 is a directory where the backup is written to
|
||||
const DC_IMEX_EXPORT_BACKUP: usize = 11;
|
||||
/// param1 is the file with the backup to import
|
||||
const DC_IMEX_IMPORT_BACKUP: usize = 12;
|
||||
|
||||
/// virtual chat showing all messages belonging to chats flagged with chats.blocked=2
|
||||
pub(crate) const DC_CHAT_ID_DEADDROP: u32 = 1;
|
||||
@@ -120,7 +112,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 +122,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;
|
||||
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.
|
||||
@@ -149,23 +135,23 @@ pub const DC_EMPTY_INBOX: u32 = 0x02;
|
||||
/// Force OAuth2 authorization. This flag does not skip automatic configuration.
|
||||
/// Before calling configure() with DC_LP_AUTH_OAUTH2 set,
|
||||
/// the user has to confirm access at the URL returned by dc_get_oauth2_url().
|
||||
pub const DC_LP_AUTH_OAUTH2: i32 = 0x2;
|
||||
pub const DC_LP_AUTH_OAUTH2: usize = 0x2;
|
||||
|
||||
/// Force NORMAL authorization, this is the default.
|
||||
/// If this flag is set, automatic configuration is skipped.
|
||||
pub const DC_LP_AUTH_NORMAL: i32 = 0x4;
|
||||
pub const DC_LP_AUTH_NORMAL: usize = 0x4;
|
||||
|
||||
/// Connect to IMAP via STARTTLS.
|
||||
/// If this flag is set, automatic configuration is skipped.
|
||||
pub const DC_LP_IMAP_SOCKET_STARTTLS: i32 = 0x100;
|
||||
pub const DC_LP_IMAP_SOCKET_STARTTLS: usize = 0x100;
|
||||
|
||||
/// Connect to IMAP via SSL.
|
||||
/// If this flag is set, automatic configuration is skipped.
|
||||
pub const DC_LP_IMAP_SOCKET_SSL: i32 = 0x200;
|
||||
pub const DC_LP_IMAP_SOCKET_SSL: usize = 0x200;
|
||||
|
||||
/// Connect to IMAP unencrypted, this should not be used.
|
||||
/// If this flag is set, automatic configuration is skipped.
|
||||
pub const DC_LP_IMAP_SOCKET_PLAIN: i32 = 0x400;
|
||||
pub const DC_LP_IMAP_SOCKET_PLAIN: usize = 0x400;
|
||||
|
||||
/// Connect to SMTP via STARTTLS.
|
||||
/// If this flag is set, automatic configuration is skipped.
|
||||
@@ -180,9 +166,9 @@ pub const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
|
||||
pub const DC_LP_SMTP_SOCKET_PLAIN: usize = 0x40000;
|
||||
|
||||
/// if none of these flags are set, the default is chosen
|
||||
pub const DC_LP_AUTH_FLAGS: i32 = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
|
||||
pub const DC_LP_AUTH_FLAGS: usize = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
|
||||
/// if none of these flags are set, the default is chosen
|
||||
pub const DC_LP_IMAP_SOCKET_FLAGS: i32 =
|
||||
pub const DC_LP_IMAP_SOCKET_FLAGS: usize =
|
||||
(DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_SSL | DC_LP_IMAP_SOCKET_PLAIN);
|
||||
/// if none of these flags are set, the default is chosen
|
||||
pub const DC_LP_SMTP_SOCKET_FLAGS: usize =
|
||||
@@ -214,11 +200,6 @@ pub enum Viewtype {
|
||||
/// and retrieved via dc_msg_get_file(), dc_msg_get_width(), dc_msg_get_height().
|
||||
Gif = 21,
|
||||
|
||||
/// Message containing a sticker, similar to image.
|
||||
/// If possible, the ui should display the image without borders in a transparent way.
|
||||
/// A click on a sticker will offer to install the sticker set in some future.
|
||||
Sticker = 23,
|
||||
|
||||
/// Message containing an Audio file.
|
||||
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
|
||||
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration().
|
||||
@@ -271,8 +252,13 @@ 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
|
||||
// These constants are used to request strings using #DC_EVENT_GET_STRING.
|
||||
|
||||
const DC_STR_NOMESSAGES: usize = 1;
|
||||
const DC_STR_SELF: usize = 2;
|
||||
@@ -318,8 +304,7 @@ const DC_STR_MSGACTIONBYME: usize = 63;
|
||||
const DC_STR_MSGLOCATIONENABLED: usize = 64;
|
||||
const DC_STR_MSGLOCATIONDISABLED: usize = 65;
|
||||
const DC_STR_LOCATION: usize = 66;
|
||||
const DC_STR_STICKER: usize = 67;
|
||||
const DC_STR_COUNT: usize = 67;
|
||||
const DC_STR_COUNT: usize = 66;
|
||||
|
||||
pub const DC_JOB_DELETE_MSG_ON_IMAP: i32 = 110;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
@@ -399,18 +390,20 @@ impl Contact {
|
||||
}
|
||||
sth_modified = Modifier::Modified;
|
||||
}
|
||||
} else if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO contacts (name, addr, origin) VALUES(?, ?, ?);",
|
||||
params![name.as_ref(), addr, origin,],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
|
||||
sth_modified = Modifier::Created;
|
||||
} else {
|
||||
error!(context, "Cannot add contact.");
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO contacts (name, addr, origin) VALUES(?, ?, ?);",
|
||||
params![name.as_ref(), addr, origin,],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
|
||||
sth_modified = Modifier::Created;
|
||||
} else {
|
||||
error!(context, "Cannot add contact.");
|
||||
}
|
||||
}
|
||||
|
||||
Ok((row_id, sth_modified))
|
||||
@@ -811,14 +804,14 @@ impl Contact {
|
||||
}
|
||||
|
||||
if let Some(peerstate) = peerstate {
|
||||
if peerstate.verified_key.is_some() {
|
||||
if peerstate.verified_key().is_some() {
|
||||
return VerifiedStatus::BidirectVerified;
|
||||
}
|
||||
}
|
||||
|
||||
let peerstate = Peerstate::from_addr(context, &context.sql, &self.addr);
|
||||
if let Some(ps) = peerstate {
|
||||
if ps.verified_key.is_some() {
|
||||
if ps.verified_key().is_some() {
|
||||
return VerifiedStatus::BidirectVerified;
|
||||
}
|
||||
}
|
||||
@@ -834,7 +827,7 @@ impl Contact {
|
||||
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
|
||||
if !contact.addr.is_empty() {
|
||||
let normalized_addr = addr_normalize(addr.as_ref());
|
||||
if contact.addr == normalized_addr {
|
||||
if &contact.addr == &normalized_addr {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -970,9 +963,9 @@ pub fn normalize_name(full_name: impl AsRef<str>) -> String {
|
||||
if len > 0 {
|
||||
let firstchar = full_name.as_bytes()[0];
|
||||
let lastchar = full_name.as_bytes()[len - 1];
|
||||
if firstchar == b'\'' && lastchar == b'\''
|
||||
|| firstchar == b'\"' && lastchar == b'\"'
|
||||
|| firstchar == b'<' && lastchar == b'>'
|
||||
if firstchar == '\'' as u8 && lastchar == '\'' as u8
|
||||
|| firstchar == '\"' as u8 && lastchar == '\"' as u8
|
||||
|| firstchar == '<' as u8 && lastchar == '>' as u8
|
||||
{
|
||||
full_name = &full_name[1..len - 1];
|
||||
}
|
||||
|
||||
269
src/context.rs
269
src/context.rs
@@ -6,7 +6,6 @@ use std::sync::{Arc, Condvar, Mutex, RwLock};
|
||||
use libc::uintptr_t;
|
||||
|
||||
use crate::chat::*;
|
||||
use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::error::*;
|
||||
@@ -17,7 +16,7 @@ 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::*;
|
||||
use crate::param::Params;
|
||||
use crate::smtp::*;
|
||||
use crate::sql::Sql;
|
||||
@@ -59,37 +58,12 @@ pub struct Context {
|
||||
pub running_state: Arc<RwLock<RunningState>>,
|
||||
/// Mutex to avoid generating the key for the user more than once.
|
||||
pub generating_key_mutex: Mutex<()>,
|
||||
pub translated_stockstrings: RwLock<HashMap<usize, String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct RunningState {
|
||||
pub ongoing_running: bool,
|
||||
shall_stop_ongoing: bool,
|
||||
}
|
||||
|
||||
/// Return some info about deltachat-core
|
||||
///
|
||||
/// This contains information mostly about the library itself, the
|
||||
/// actual keys and their values which will be present are not
|
||||
/// guaranteed. Calling [Context::get_info] also includes information
|
||||
/// about the context on top of the information here.
|
||||
pub fn get_info() -> HashMap<&'static str, String> {
|
||||
let mut res = HashMap::new();
|
||||
res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR));
|
||||
res.insert("sqlite_version", rusqlite::version().to_string());
|
||||
res.insert(
|
||||
"sqlite_thread_safe",
|
||||
unsafe { rusqlite::ffi::sqlite3_threadsafe() }.to_string(),
|
||||
);
|
||||
res.insert(
|
||||
"arch",
|
||||
(::std::mem::size_of::<*mut libc::c_void>())
|
||||
.wrapping_mul(8)
|
||||
.to_string(),
|
||||
);
|
||||
res.insert("level", "awesome".into());
|
||||
res
|
||||
pub shall_stop_ongoing: bool,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
@@ -142,11 +116,10 @@ impl Context {
|
||||
probe_imap_network: Arc::new(RwLock::new(false)),
|
||||
perform_inbox_jobs_needed: Arc::new(RwLock::new(false)),
|
||||
generating_key_mutex: Mutex::new(()),
|
||||
translated_stockstrings: RwLock::new(HashMap::new()),
|
||||
};
|
||||
|
||||
ensure!(
|
||||
ctx.sql.open(&ctx, &ctx.dbfile, false),
|
||||
ctx.sql.open(&ctx, &ctx.dbfile, 0),
|
||||
"Failed opening sqlite database"
|
||||
);
|
||||
|
||||
@@ -165,84 +138,31 @@ impl Context {
|
||||
(*self.cb)(self, event)
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Ongoing process allocation/free/check
|
||||
******************************************************************************/
|
||||
|
||||
pub fn alloc_ongoing(&self) -> bool {
|
||||
if self.has_ongoing() {
|
||||
warn!(self, "There is already another ongoing process running.",);
|
||||
|
||||
false
|
||||
} else {
|
||||
let s_a = self.running_state.clone();
|
||||
let mut s = s_a.write().unwrap();
|
||||
|
||||
s.ongoing_running = true;
|
||||
s.shall_stop_ongoing = false;
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free_ongoing(&self) {
|
||||
let s_a = self.running_state.clone();
|
||||
let mut s = s_a.write().unwrap();
|
||||
|
||||
s.ongoing_running = false;
|
||||
s.shall_stop_ongoing = true;
|
||||
}
|
||||
|
||||
pub fn has_ongoing(&self) -> bool {
|
||||
let s_a = self.running_state.clone();
|
||||
let s = s_a.read().unwrap();
|
||||
|
||||
s.ongoing_running || !s.shall_stop_ongoing
|
||||
}
|
||||
|
||||
/// Signal an ongoing process to stop.
|
||||
pub fn stop_ongoing(&self) {
|
||||
let s_a = self.running_state.clone();
|
||||
let mut s = s_a.write().unwrap();
|
||||
|
||||
if s.ongoing_running && !s.shall_stop_ongoing {
|
||||
info!(self, "Signaling the ongoing process to stop ASAP.",);
|
||||
s.shall_stop_ongoing = true;
|
||||
} else {
|
||||
info!(self, "No ongoing process to stop.",);
|
||||
};
|
||||
}
|
||||
|
||||
pub fn shall_stop_ongoing(&self) -> bool {
|
||||
self.running_state
|
||||
.clone()
|
||||
.read()
|
||||
.unwrap()
|
||||
.shall_stop_ongoing
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* UI chat/message related API
|
||||
******************************************************************************/
|
||||
|
||||
pub fn get_info(&self) -> HashMap<&'static str, String> {
|
||||
let unset = "0";
|
||||
let l = LoginParam::from_database(self, "");
|
||||
let l2 = LoginParam::from_database(self, "configured_");
|
||||
let displayname = self.get_config(Config::Displayname);
|
||||
let displayname = self.sql.get_config(self, "displayname");
|
||||
let chats = get_chat_cnt(self) as usize;
|
||||
let real_msgs = message::get_real_msg_cnt(self) as usize;
|
||||
let deaddrop_msgs = message::get_deaddrop_msg_cnt(self) as usize;
|
||||
let real_msgs = dc_get_real_msg_cnt(self) as usize;
|
||||
let deaddrop_msgs = dc_get_deaddrop_msg_cnt(self) as usize;
|
||||
let contacts = Contact::get_real_cnt(self) as usize;
|
||||
let is_configured = self.get_config_int(Config::Configured);
|
||||
let is_configured = self
|
||||
.sql
|
||||
.get_config_int(self, "configured")
|
||||
.unwrap_or_default();
|
||||
let dbversion = self
|
||||
.sql
|
||||
.get_raw_config_int(self, "dbversion")
|
||||
.get_config_int(self, "dbversion")
|
||||
.unwrap_or_default();
|
||||
|
||||
let e2ee_enabled = self.get_config_int(Config::E2eeEnabled);
|
||||
let mdns_enabled = self.get_config_int(Config::MdnsEnabled);
|
||||
let bcc_self = self.get_config_int(Config::BccSelf);
|
||||
let e2ee_enabled = self
|
||||
.sql
|
||||
.get_config_int(self, "e2ee_enabled")
|
||||
.unwrap_or_else(|| 1);
|
||||
let mdns_enabled = self
|
||||
.sql
|
||||
.get_config_int(self, "mdns_enabled")
|
||||
.unwrap_or_else(|| 1);
|
||||
|
||||
let prv_key_cnt: Option<isize> =
|
||||
self.sql
|
||||
@@ -260,25 +180,48 @@ impl Context {
|
||||
"<Not yet calculated>".into()
|
||||
};
|
||||
|
||||
let inbox_watch = self.get_config_int(Config::InboxWatch);
|
||||
let sentbox_watch = self.get_config_int(Config::SentboxWatch);
|
||||
let mvbox_watch = self.get_config_int(Config::MvboxWatch);
|
||||
let mvbox_move = self.get_config_int(Config::MvboxMove);
|
||||
let inbox_watch = self
|
||||
.sql
|
||||
.get_config_int(self, "inbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
let sentbox_watch = self
|
||||
.sql
|
||||
.get_config_int(self, "sentbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
let mvbox_watch = self
|
||||
.sql
|
||||
.get_config_int(self, "mvbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
let mvbox_move = self
|
||||
.sql
|
||||
.get_config_int(self, "mvbox_move")
|
||||
.unwrap_or_else(|| 1);
|
||||
let folders_configured = self
|
||||
.sql
|
||||
.get_raw_config_int(self, "folders_configured")
|
||||
.get_config_int(self, "folders_configured")
|
||||
.unwrap_or_default();
|
||||
|
||||
let configured_sentbox_folder = self
|
||||
.sql
|
||||
.get_raw_config(self, "configured_sentbox_folder")
|
||||
.get_config(self, "configured_sentbox_folder")
|
||||
.unwrap_or_else(|| "<unset>".to_string());
|
||||
let configured_mvbox_folder = self
|
||||
.sql
|
||||
.get_raw_config(self, "configured_mvbox_folder")
|
||||
.get_config(self, "configured_mvbox_folder")
|
||||
.unwrap_or_else(|| "<unset>".to_string());
|
||||
|
||||
let mut res = get_info();
|
||||
let mut res = HashMap::new();
|
||||
res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR));
|
||||
res.insert("sqlite_version", rusqlite::version().to_string());
|
||||
res.insert(
|
||||
"sqlite_thread_safe",
|
||||
unsafe { rusqlite::ffi::sqlite3_threadsafe() }.to_string(),
|
||||
);
|
||||
res.insert(
|
||||
"arch",
|
||||
(::std::mem::size_of::<*mut libc::c_void>())
|
||||
.wrapping_mul(8)
|
||||
.to_string(),
|
||||
);
|
||||
res.insert("number_of_chats", chats.to_string());
|
||||
res.insert("number_of_chat_messages", real_msgs.to_string());
|
||||
res.insert("messages_in_contact_requests", deaddrop_msgs.to_string());
|
||||
@@ -299,7 +242,6 @@ impl Context {
|
||||
res.insert("configured_mvbox_folder", configured_mvbox_folder);
|
||||
res.insert("mdns_enabled", mdns_enabled.to_string());
|
||||
res.insert("e2ee_enabled", e2ee_enabled.to_string());
|
||||
res.insert("bcc_self", bcc_self.to_string());
|
||||
res.insert(
|
||||
"private_key_count",
|
||||
prv_key_cnt.unwrap_or_default().to_string(),
|
||||
@@ -309,43 +251,38 @@ impl Context {
|
||||
pub_key_cnt.unwrap_or_default().to_string(),
|
||||
);
|
||||
res.insert("fingerprint", fingerprint_str);
|
||||
res.insert("level", "awesome".into());
|
||||
|
||||
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)
|
||||
},
|
||||
)
|
||||
.unwrap_or_default()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[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 +291,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)
|
||||
},
|
||||
@@ -403,7 +322,7 @@ impl Context {
|
||||
}
|
||||
|
||||
pub fn is_sentbox(&self, folder_name: impl AsRef<str>) -> bool {
|
||||
let sentbox_name = self.sql.get_raw_config(self, "configured_sentbox_folder");
|
||||
let sentbox_name = self.sql.get_config(self, "configured_sentbox_folder");
|
||||
if let Some(name) = sentbox_name {
|
||||
name == folder_name.as_ref()
|
||||
} else {
|
||||
@@ -412,7 +331,7 @@ impl Context {
|
||||
}
|
||||
|
||||
pub fn is_mvbox(&self, folder_name: impl AsRef<str>) -> bool {
|
||||
let mvbox_name = self.sql.get_raw_config(self, "configured_mvbox_folder");
|
||||
let mvbox_name = self.sql.get_config(self, "configured_mvbox_folder");
|
||||
|
||||
if let Some(name) = mvbox_name {
|
||||
name == folder_name.as_ref()
|
||||
@@ -421,8 +340,13 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn do_heuristics_moves(&self, folder: &str, msg_id: MsgId) {
|
||||
if !self.get_config_bool(Config::MvboxMove) {
|
||||
pub fn do_heuristics_moves(&self, folder: &str, msg_id: u32) {
|
||||
if self
|
||||
.sql
|
||||
.get_config_int(self, "mvbox_move")
|
||||
.unwrap_or_else(|| 1)
|
||||
== 0
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -430,15 +354,15 @@ impl Context {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(msg) = Message::load_from_db(self, msg_id) {
|
||||
if msg.is_setupmessage() {
|
||||
if let Ok(msg) = dc_msg_new_load(self, msg_id) {
|
||||
if dc_msg_is_setupmessage(&msg) {
|
||||
// do not move setup messages;
|
||||
// there may be a non-delta device that wants to handle it
|
||||
return;
|
||||
}
|
||||
|
||||
if self.is_mvbox(folder) {
|
||||
message::update_msg_move_state(self, &msg.rfc724_mid, MoveState::Stay);
|
||||
dc_update_msg_move_state(self, msg.rfc724_mid, MoveState::Stay);
|
||||
}
|
||||
|
||||
// 1 = dc message, 2 = reply to dc message
|
||||
@@ -446,11 +370,11 @@ impl Context {
|
||||
job_add(
|
||||
self,
|
||||
Action::MoveMsg,
|
||||
msg.id.to_u32() as i32,
|
||||
msg.id as libc::c_int,
|
||||
Params::new(),
|
||||
0,
|
||||
);
|
||||
message::update_msg_move_state(self, &msg.rfc724_mid, MoveState::Moving);
|
||||
dc_update_msg_move_state(self, msg.rfc724_mid, MoveState::Moving);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -551,15 +475,6 @@ mod tests {
|
||||
assert!(dbfile2.is_file());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_empty_blobdir() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let blobdir = PathBuf::new();
|
||||
let res = Context::with_blobdir(Box::new(|_, _| 0), "FakeOS".into(), dbfile, blobdir);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_blobdir_not_exists() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
@@ -580,14 +495,6 @@ mod tests {
|
||||
let t = dummy_context();
|
||||
|
||||
let info = t.ctx.get_info();
|
||||
assert!(info.get("database_dir").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_info_no_context() {
|
||||
let info = get_info();
|
||||
assert!(info.get("deltachat_core_version").is_some());
|
||||
assert!(info.get("database_dir").is_none());
|
||||
assert_eq!(info.get("level").unwrap(), "awesome");
|
||||
}
|
||||
}
|
||||
|
||||
1089
src/dc_imex.rs
Normal file
1089
src/dc_imex.rs
Normal file
File diff suppressed because it is too large
Load Diff
1294
src/dc_mimefactory.rs
Normal file
1294
src/dc_mimefactory.rs
Normal file
File diff suppressed because it is too large
Load Diff
1537
src/dc_mimeparser.rs
1537
src/dc_mimeparser.rs
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -10,16 +10,18 @@ pub struct Simplify {
|
||||
///
|
||||
/// Also return whether not-standard (rfc3676, §4.3) footer is found.
|
||||
fn find_message_footer(lines: &[&str]) -> (usize, bool) {
|
||||
for (ix, &line) in lines.iter().enumerate() {
|
||||
for ix in 0..lines.len() {
|
||||
let line = lines[ix];
|
||||
|
||||
// quoted-printable may encode `-- ` to `-- =20` which is converted
|
||||
// back to `-- `
|
||||
match line {
|
||||
match line.as_ref() {
|
||||
"-- " | "-- " => return (ix, false),
|
||||
"--" | "---" | "----" => return (ix, true),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
(lines.len(), false)
|
||||
return (lines.len(), false);
|
||||
}
|
||||
|
||||
impl Simplify {
|
||||
@@ -101,8 +103,10 @@ impl Simplify {
|
||||
if let Some(last_quoted_line) = l_lastQuotedLine {
|
||||
l_last = last_quoted_line;
|
||||
is_cut_at_end = true;
|
||||
if l_last > 1 && is_empty_line(lines[l_last - 1]) {
|
||||
l_last -= 1
|
||||
if l_last > 1 {
|
||||
if is_empty_line(lines[l_last - 1]) {
|
||||
l_last -= 1
|
||||
}
|
||||
}
|
||||
if l_last > 1 {
|
||||
let line = lines[l_last - 1];
|
||||
@@ -142,8 +146,8 @@ impl Simplify {
|
||||
ret += "[...]";
|
||||
}
|
||||
/* we write empty lines only in case and non-empty line follows */
|
||||
let mut pending_linebreaks = 0;
|
||||
let mut content_lines_added = 0;
|
||||
let mut pending_linebreaks: libc::c_int = 0i32;
|
||||
let mut content_lines_added: libc::c_int = 0i32;
|
||||
for l in l_first..l_last {
|
||||
let line = lines[l];
|
||||
if is_empty_line(line) {
|
||||
@@ -201,7 +205,7 @@ fn is_quoted_headline(buf: &str) -> bool {
|
||||
}
|
||||
|
||||
fn is_plain_quote(buf: &str) -> bool {
|
||||
buf.starts_with('>')
|
||||
buf.starts_with(">")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use itertools::Itertools;
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
use charset::Charset;
|
||||
use libc::free;
|
||||
use mmime::mailmime::decode::mailmime_encoded_phrase_parse;
|
||||
use mmime::mailmime_decode::*;
|
||||
use mmime::mmapstring::*;
|
||||
use mmime::other::*;
|
||||
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
|
||||
|
||||
use crate::dc_tools::*;
|
||||
use crate::x::*;
|
||||
|
||||
/**
|
||||
* Encode non-ascii-strings as `=?UTF-8?Q?Bj=c3=b6rn_Petersen?=`.
|
||||
@@ -25,50 +25,222 @@ use crate::dc_tools::*;
|
||||
* @return Returns the encoded string which must be free()'d when no longed needed.
|
||||
* On errors, NULL is returned.
|
||||
*/
|
||||
pub fn dc_encode_header_words(input: impl AsRef<str>) -> String {
|
||||
let mut result = String::default();
|
||||
for (_, group) in &input.as_ref().chars().group_by(|c| c.is_whitespace()) {
|
||||
let word: String = group.collect();
|
||||
result.push_str("e_word(&word.as_bytes()));
|
||||
pub unsafe fn dc_encode_header_words(to_encode_r: impl AsRef<str>) -> *mut libc::c_char {
|
||||
let to_encode =
|
||||
CString::new(to_encode_r.as_ref().as_bytes()).expect("invalid cstring to_encode");
|
||||
|
||||
let mut ok_to_continue = true;
|
||||
let mut ret_str: *mut libc::c_char = ptr::null_mut();
|
||||
let mut cur: *const libc::c_char = to_encode.as_ptr();
|
||||
let mmapstr: *mut MMAPString = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
|
||||
if mmapstr.is_null() {
|
||||
ok_to_continue = false;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn must_encode(byte: u8) -> bool {
|
||||
static SPECIALS: &[u8] = b",:!\"#$@[\\]^`{|}~=?_";
|
||||
|
||||
SPECIALS.into_iter().any(|b| *b == byte)
|
||||
}
|
||||
|
||||
fn quote_word(word: &[u8]) -> String {
|
||||
let mut result = String::default();
|
||||
let mut encoded = false;
|
||||
|
||||
for byte in word {
|
||||
let byte = *byte;
|
||||
if byte >= 128 || must_encode(byte) {
|
||||
result.push_str(&format!("={:2X}", byte));
|
||||
encoded = true;
|
||||
} else if byte == b' ' {
|
||||
result.push('_');
|
||||
encoded = true;
|
||||
loop {
|
||||
if !ok_to_continue {
|
||||
if !mmapstr.is_null() {
|
||||
mmap_string_free(mmapstr);
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
result.push(byte as _);
|
||||
if *cur as libc::c_int != '\u{0}' as i32 {
|
||||
let begin: *const libc::c_char;
|
||||
let mut end: *const libc::c_char;
|
||||
let mut do_quote: bool;
|
||||
let mut quote_words: libc::c_int;
|
||||
begin = cur;
|
||||
end = begin;
|
||||
quote_words = 0i32;
|
||||
do_quote = true;
|
||||
while *cur as libc::c_int != '\u{0}' as i32 {
|
||||
get_word(cur, &mut cur, &mut do_quote);
|
||||
if !do_quote {
|
||||
break;
|
||||
}
|
||||
quote_words = 1i32;
|
||||
end = cur;
|
||||
if *cur as libc::c_int != '\u{0}' as i32 {
|
||||
cur = cur.offset(1isize)
|
||||
}
|
||||
}
|
||||
if 0 != quote_words {
|
||||
if !quote_word(
|
||||
b"utf-8\x00" as *const u8 as *const libc::c_char,
|
||||
mmapstr,
|
||||
begin,
|
||||
end.wrapping_offset_from(begin) as libc::size_t,
|
||||
) {
|
||||
ok_to_continue = false;
|
||||
continue;
|
||||
}
|
||||
if *end as libc::c_int == ' ' as i32 || *end as libc::c_int == '\t' as i32 {
|
||||
if mmap_string_append_c(mmapstr, *end).is_null() {
|
||||
ok_to_continue = false;
|
||||
continue;
|
||||
}
|
||||
end = end.offset(1isize)
|
||||
}
|
||||
if *end as libc::c_int != '\u{0}' as i32 {
|
||||
if mmap_string_append_len(
|
||||
mmapstr,
|
||||
end,
|
||||
cur.wrapping_offset_from(end) as libc::size_t,
|
||||
)
|
||||
.is_null()
|
||||
{
|
||||
ok_to_continue = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else if mmap_string_append_len(
|
||||
mmapstr,
|
||||
begin,
|
||||
cur.wrapping_offset_from(begin) as libc::size_t,
|
||||
)
|
||||
.is_null()
|
||||
{
|
||||
ok_to_continue = false;
|
||||
continue;
|
||||
}
|
||||
if !(*cur as libc::c_int == ' ' as i32 || *cur as libc::c_int == '\t' as i32) {
|
||||
continue;
|
||||
}
|
||||
if mmap_string_append_c(mmapstr, *cur).is_null() {
|
||||
ok_to_continue = false;
|
||||
continue;
|
||||
}
|
||||
cur = cur.offset(1isize);
|
||||
} else {
|
||||
ret_str = strdup((*mmapstr).str_0);
|
||||
ok_to_continue = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if encoded {
|
||||
result = format!("=?utf-8?Q?{}?=", &result);
|
||||
ret_str
|
||||
}
|
||||
|
||||
unsafe fn quote_word(
|
||||
display_charset: *const libc::c_char,
|
||||
mmapstr: *mut MMAPString,
|
||||
word: *const libc::c_char,
|
||||
size: libc::size_t,
|
||||
) -> bool {
|
||||
let mut cur: *const libc::c_char;
|
||||
let mut i = 0;
|
||||
let mut hex: [libc::c_char; 4] = [0; 4];
|
||||
// let mut col: libc::c_int = 0i32;
|
||||
if mmap_string_append(mmapstr, b"=?\x00" as *const u8 as *const libc::c_char).is_null() {
|
||||
return false;
|
||||
}
|
||||
result
|
||||
if mmap_string_append(mmapstr, display_charset).is_null() {
|
||||
return false;
|
||||
}
|
||||
if mmap_string_append(mmapstr, b"?Q?\x00" as *const u8 as *const libc::c_char).is_null() {
|
||||
return false;
|
||||
}
|
||||
// col = (*mmapstr).len as libc::c_int;
|
||||
cur = word;
|
||||
while i < size {
|
||||
let mut do_quote_char = false;
|
||||
match *cur as u8 as char {
|
||||
',' | ':' | '!' | '"' | '#' | '$' | '@' | '[' | '\\' | ']' | '^' | '`' | '{' | '|'
|
||||
| '}' | '~' | '=' | '?' | '_' => do_quote_char = true,
|
||||
_ => {
|
||||
if *cur as u8 >= 128 {
|
||||
do_quote_char = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if do_quote_char {
|
||||
print_hex(hex.as_mut_ptr(), cur);
|
||||
if mmap_string_append(mmapstr, hex.as_mut_ptr()).is_null() {
|
||||
return false;
|
||||
}
|
||||
// col += 3i32
|
||||
} else {
|
||||
if *cur as libc::c_int == ' ' as i32 {
|
||||
if mmap_string_append_c(mmapstr, '_' as i32 as libc::c_char).is_null() {
|
||||
return false;
|
||||
}
|
||||
} else if mmap_string_append_c(mmapstr, *cur).is_null() {
|
||||
return false;
|
||||
}
|
||||
// col += 3i32
|
||||
}
|
||||
cur = cur.offset(1isize);
|
||||
i = i.wrapping_add(1)
|
||||
}
|
||||
if mmap_string_append(mmapstr, b"?=\x00" as *const u8 as *const libc::c_char).is_null() {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
unsafe fn get_word(
|
||||
begin: *const libc::c_char,
|
||||
pend: *mut *const libc::c_char,
|
||||
pto_be_quoted: *mut bool,
|
||||
) {
|
||||
let mut cur: *const libc::c_char = begin;
|
||||
while *cur as libc::c_int != ' ' as i32
|
||||
&& *cur as libc::c_int != '\t' as i32
|
||||
&& *cur as libc::c_int != '\u{0}' as i32
|
||||
{
|
||||
cur = cur.offset(1isize)
|
||||
}
|
||||
*pto_be_quoted = to_be_quoted(begin, cur.wrapping_offset_from(begin) as libc::size_t);
|
||||
*pend = cur;
|
||||
}
|
||||
|
||||
/* ******************************************************************************
|
||||
* Encode/decode header words, RFC 2047
|
||||
******************************************************************************/
|
||||
|
||||
pub(crate) fn dc_decode_header_words(input: &str) -> String {
|
||||
/* see comment below */
|
||||
unsafe fn to_be_quoted(word: *const libc::c_char, size: libc::size_t) -> bool {
|
||||
let mut cur: *const libc::c_char = word;
|
||||
let mut i = 0;
|
||||
while i < size {
|
||||
match *cur as libc::c_int {
|
||||
44 | 58 | 33 | 34 | 35 | 36 | 64 | 91 | 92 | 93 | 94 | 96 | 123 | 124 | 125 | 126
|
||||
| 61 | 63 | 95 => return true,
|
||||
_ => {
|
||||
if *cur as libc::c_uchar as libc::c_int >= 128i32 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
cur = cur.offset(1isize);
|
||||
i = i.wrapping_add(1)
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_char {
|
||||
if in_0.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let mut out: *mut libc::c_char = ptr::null_mut();
|
||||
let mut cur_token = 0;
|
||||
let r: libc::c_int = mailmime_encoded_phrase_parse(
|
||||
b"iso-8859-1\x00" as *const u8 as *const libc::c_char,
|
||||
in_0,
|
||||
strlen(in_0),
|
||||
&mut cur_token,
|
||||
b"utf-8\x00" as *const u8 as *const libc::c_char,
|
||||
&mut out,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int || out.is_null() {
|
||||
out = dc_strdup(in_0)
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
pub fn dc_decode_header_words_safe(input: &str) -> String {
|
||||
static FROM_ENCODING: &[u8] = b"iso-8859-1\x00";
|
||||
static TO_ENCODING: &[u8] = b"utf-8\x00";
|
||||
let mut out = ptr::null_mut();
|
||||
@@ -86,7 +258,7 @@ pub(crate) fn dc_decode_header_words(input: &str) -> String {
|
||||
if r as u32 != MAILIMF_NO_ERROR || out.is_null() {
|
||||
input.to_string()
|
||||
} else {
|
||||
let res = to_string_lossy(out);
|
||||
let res = to_string(out);
|
||||
free(out.cast());
|
||||
res
|
||||
}
|
||||
@@ -152,35 +324,77 @@ pub fn dc_decode_ext_header(to_decode: &[u8]) -> Cow<str> {
|
||||
String::from_utf8_lossy(to_decode)
|
||||
}
|
||||
|
||||
unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) {
|
||||
assert!(!target.is_null());
|
||||
assert!(!cur.is_null());
|
||||
|
||||
let bytes = std::slice::from_raw_parts(cur as *const _, strlen(cur));
|
||||
let raw = CString::yolo(format!("={}", &hex::encode_upper(bytes)[..2]));
|
||||
libc::memcpy(target as *mut _, raw.as_ptr() as *const _, 4);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::ffi::CStr;
|
||||
|
||||
#[test]
|
||||
fn test_dc_decode_header_words() {
|
||||
assert_eq!(
|
||||
dc_decode_header_words("=?utf-8?B?dGVzdMOkw7bDvC50eHQ=?="),
|
||||
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
|
||||
);
|
||||
|
||||
assert_eq!(dc_decode_header_words("just ascii test"), "just ascii test");
|
||||
|
||||
assert_eq!(dc_encode_header_words("abcdef"), "abcdef");
|
||||
|
||||
let r = dc_encode_header_words(
|
||||
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
|
||||
);
|
||||
assert!(r.starts_with("=?utf-8"));
|
||||
|
||||
assert_eq!(
|
||||
dc_decode_header_words(&r),
|
||||
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
dc_decode_header_words("=?ISO-8859-1?Q?attachment=3B=0D=0A_filename=3D?= =?ISO-8859-1?Q?=22test=E4=F6=FC=2Etxt=22=3B=0D=0A_size=3D39?="),
|
||||
std::string::String::from_utf8(b"attachment;\r\n filename=\"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\";\r\n size=39".to_vec()).unwrap(),
|
||||
unsafe {
|
||||
let mut buf1: *mut libc::c_char = dc_decode_header_words(
|
||||
b"=?utf-8?B?dGVzdMOkw7bDvC50eHQ=?=\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
buf1,
|
||||
b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\x00" as *const u8 as *const libc::c_char
|
||||
),
|
||||
0
|
||||
);
|
||||
free(buf1 as *mut libc::c_void);
|
||||
|
||||
buf1 =
|
||||
dc_decode_header_words(b"just ascii test\x00" as *const u8 as *const libc::c_char);
|
||||
assert_eq!(CStr::from_ptr(buf1).to_str().unwrap(), "just ascii test");
|
||||
free(buf1 as *mut libc::c_void);
|
||||
|
||||
buf1 = dc_encode_header_words("abcdef");
|
||||
assert_eq!(CStr::from_ptr(buf1).to_str().unwrap(), "abcdef");
|
||||
free(buf1 as *mut libc::c_void);
|
||||
|
||||
buf1 = dc_encode_header_words(
|
||||
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec())
|
||||
.unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
strncmp(buf1, b"=?utf-8\x00" as *const u8 as *const libc::c_char, 7),
|
||||
0
|
||||
);
|
||||
|
||||
let buf2: *mut libc::c_char = dc_decode_header_words(buf1);
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
buf2,
|
||||
b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\x00" as *const u8 as *const libc::c_char
|
||||
),
|
||||
0
|
||||
);
|
||||
free(buf1 as *mut libc::c_void);
|
||||
free(buf2 as *mut libc::c_void);
|
||||
|
||||
buf1 = dc_decode_header_words(
|
||||
b"=?ISO-8859-1?Q?attachment=3B=0D=0A_filename=3D?= =?ISO-8859-1?Q?=22test=E4=F6=FC=2Etxt=22=3B=0D=0A_size=3D39?=\x00" as *const u8 as *const libc::c_char
|
||||
);
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
buf1,
|
||||
b"attachment;\r\n filename=\"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\";\r\n size=39\x00" as *const u8 as *const libc::c_char,
|
||||
|
||||
),
|
||||
0
|
||||
);
|
||||
free(buf1 as *mut libc::c_void);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -224,6 +438,18 @@ mod tests {
|
||||
assert_eq!(dc_needs_ext_header("a b"), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_print_hex() {
|
||||
let mut hex: [libc::c_char; 4] = [0; 4];
|
||||
let cur = b"helloworld" as *const u8 as *const libc::c_char;
|
||||
unsafe { print_hex(hex.as_mut_ptr(), cur) };
|
||||
assert_eq!(to_string(hex.as_ptr() as *const _), "=68");
|
||||
|
||||
let cur = b":" as *const u8 as *const libc::c_char;
|
||||
unsafe { print_hex(hex.as_mut_ptr(), cur) };
|
||||
assert_eq!(to_string(hex.as_ptr() as *const _), "=3A");
|
||||
}
|
||||
|
||||
use proptest::prelude::*;
|
||||
|
||||
proptest! {
|
||||
@@ -239,13 +465,5 @@ mod tests {
|
||||
// make sure this never panics
|
||||
let _decoded = dc_decode_ext_header(&buf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_header_roundtrip(input: String) {
|
||||
let encoded = dc_encode_header_words(&input);
|
||||
let decoded = dc_decode_header_words(&encoded);
|
||||
|
||||
assert_eq!(input, decoded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1206
src/dc_tools.rs
1206
src/dc_tools.rs
File diff suppressed because it is too large
Load Diff
1349
src/e2ee.rs
1349
src/e2ee.rs
File diff suppressed because it is too large
Load Diff
40
src/error.rs
40
src/error.rs
@@ -24,16 +24,6 @@ pub enum Error {
|
||||
Utf8(std::str::Utf8Error),
|
||||
#[fail(display = "{:?}", _0)]
|
||||
CStringError(crate::dc_tools::CStringError),
|
||||
#[fail(display = "PGP: {:?}", _0)]
|
||||
Pgp(pgp::errors::Error),
|
||||
#[fail(display = "Base64Decode: {:?}", _0)]
|
||||
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>;
|
||||
@@ -44,12 +34,6 @@ impl From<rusqlite::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<base64::DecodeError> for Error {
|
||||
fn from(err: base64::DecodeError) -> Error {
|
||||
Error::Base64Decode(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<failure::Error> for Error {
|
||||
fn from(err: failure::Error) -> Error {
|
||||
Error::Failure(err)
|
||||
@@ -86,30 +70,6 @@ impl From<crate::dc_tools::CStringError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<pgp::errors::Error> for Error {
|
||||
fn from(err: pgp::errors::Error) -> Error {
|
||||
Error::Pgp(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::string::FromUtf8Error> for Error {
|
||||
fn from(err: std::string::FromUtf8Error) -> Error {
|
||||
Error::FromUtf8(err)
|
||||
}
|
||||
}
|
||||
|
||||
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) => {
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::path::PathBuf;
|
||||
|
||||
use strum::EnumProperty;
|
||||
|
||||
use crate::message::MsgId;
|
||||
use crate::stock::StockMessage;
|
||||
|
||||
impl Event {
|
||||
/// Returns the corresponding Event id.
|
||||
@@ -42,36 +42,6 @@ pub enum Event {
|
||||
#[strum(props(id = "103"))]
|
||||
SmtpMessageSent(String),
|
||||
|
||||
/// Emitted when an IMAP message has been marked as deleted
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "104"))]
|
||||
ImapMessageDeleted(String),
|
||||
|
||||
/// Emitted when an IMAP message has been moved
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "105"))]
|
||||
ImapMessageMoved(String),
|
||||
|
||||
/// Emitted when an IMAP folder was emptied
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "106"))]
|
||||
ImapFolderEmptied(String),
|
||||
|
||||
/// Emitted when an new file in the $BLOBDIR was created
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "150"))]
|
||||
NewBlobFile(String),
|
||||
|
||||
/// Emitted when an new file in the $BLOBDIR was created
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "151"))]
|
||||
DeletedBlobFile(String),
|
||||
|
||||
/// The library-user should write a warning string to the log.
|
||||
/// Passed to the callback given to dc_context_new().
|
||||
///
|
||||
@@ -133,7 +103,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 +112,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.
|
||||
@@ -197,7 +167,7 @@ pub enum Event {
|
||||
#[strum(props(id = "2041"))]
|
||||
ConfigureProgress(usize),
|
||||
|
||||
/// Inform about the import/export progress started by imex().
|
||||
/// Inform about the import/export progress started by dc_imex().
|
||||
///
|
||||
/// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
|
||||
/// @param data2 0
|
||||
@@ -205,8 +175,8 @@ pub enum Event {
|
||||
#[strum(props(id = "2051"))]
|
||||
ImexProgress(usize),
|
||||
|
||||
/// A file has been exported. A file has been written by imex().
|
||||
/// This event may be sent multiple times by a single call to imex().
|
||||
/// A file has been exported. A file has been written by dc_imex().
|
||||
/// This event may be sent multiple times by a single call to dc_imex().
|
||||
///
|
||||
/// A typical purpose for a handler of this event may be to make the file public to some system
|
||||
/// services.
|
||||
@@ -243,4 +213,17 @@ pub enum Event {
|
||||
/// @return 0
|
||||
#[strum(props(id = "2061"))]
|
||||
SecurejoinJoinerProgress { contact_id: u32, progress: usize },
|
||||
|
||||
// the following events are functions that should be provided by the frontends
|
||||
/// Requeste a localized string from the frontend.
|
||||
/// @param data1 (int) ID of the string to request, one of the DC_STR_/// constants.
|
||||
/// @param data2 (int) The count. If the requested string contains a placeholder for a numeric value,
|
||||
/// the ui may use this value to return different strings on different plural forms.
|
||||
/// @return (const char*) Null-terminated UTF-8 string.
|
||||
/// The string will be free()'d by the core,
|
||||
/// so it must be allocated using malloc() or a compatible function.
|
||||
/// Return 0 if the ui cannot provide the requested string
|
||||
/// the core will use a default string in english language then.
|
||||
#[strum(props(id = "2091"))]
|
||||
GetString { id: StockMessage, count: usize },
|
||||
}
|
||||
|
||||
861
src/imap.rs
861
src/imap.rs
File diff suppressed because it is too large
Load Diff
829
src/imex.rs
829
src/imex.rs
@@ -1,829 +0,0 @@
|
||||
use core::cmp::{max, min};
|
||||
use std::path::Path;
|
||||
|
||||
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::param::*;
|
||||
use crate::pgp;
|
||||
use crate::sql::{self, Sql};
|
||||
use crate::stock::StockMessage;
|
||||
|
||||
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
||||
#[repr(i32)]
|
||||
pub enum ImexMode {
|
||||
/// Export all private keys and all public keys of the user to the
|
||||
/// directory given as `param1`. The default key is written to the files `public-key-default.asc`
|
||||
/// and `private-key-default.asc`, if there are more keys, they are written to files as
|
||||
/// `public-key-<id>.asc` and `private-key-<id>.asc`
|
||||
ExportSelfKeys = 1,
|
||||
/// Import private keys found in the directory given as `param1`.
|
||||
/// The last imported key is made the default keys unless its name contains the string `legacy`.
|
||||
/// Public keys are not imported.
|
||||
ImportSelfKeys = 2,
|
||||
/// Export a backup to the directory given as `param1`.
|
||||
/// The backup contains all contacts, chats, images and other data and device independent settings.
|
||||
/// The backup does not contain device dependent settings as ringtones or LED notification settings.
|
||||
/// The name of the backup is typically `delta-chat.<day>.bak`, if more than one backup is create on a day,
|
||||
/// the format is `delta-chat.<day>-<number>.bak`
|
||||
ExportBackup = 11,
|
||||
/// `param1` is the file (not: directory) to import. The file is normally
|
||||
/// created by DC_IMEX_EXPORT_BACKUP and detected by dc_imex_has_backup(). Importing a backup
|
||||
/// is only possible as long as the context is not configured or used in another way.
|
||||
ImportBackup = 12,
|
||||
}
|
||||
|
||||
/// Import/export things.
|
||||
/// For this purpose, the function creates a job that is executed in the IMAP-thread then;
|
||||
/// this requires to call dc_perform_imap_jobs() regularly.
|
||||
///
|
||||
/// What to do is defined by the _what_ parameter.
|
||||
///
|
||||
/// While dc_imex() returns immediately, the started job may take a while,
|
||||
/// you can stop it using dc_stop_ongoing_process(). During execution of the job,
|
||||
/// some events are sent out:
|
||||
///
|
||||
/// - A number of #DC_EVENT_IMEX_PROGRESS events are sent and may be used to create
|
||||
/// a progress bar or stuff like that. Moreover, you'll be informed when the imex-job is done.
|
||||
///
|
||||
/// - For each file written on export, the function sends #DC_EVENT_IMEX_FILE_WRITTEN
|
||||
///
|
||||
/// Only one import-/export-progress can run at the same time.
|
||||
/// To cancel an import-/export-progress, use dc_stop_ongoing_process().
|
||||
pub fn imex(context: &Context, what: ImexMode, param1: Option<impl AsRef<Path>>) {
|
||||
let mut param = Params::new();
|
||||
param.set_int(Param::Cmd, what as i32);
|
||||
if let Some(param1) = param1 {
|
||||
param.set(Param::Arg, param1.as_ref().to_string_lossy());
|
||||
}
|
||||
|
||||
job_kill_action(context, Action::ImexImap);
|
||||
job_add(context, Action::ImexImap, 0, param, 0);
|
||||
}
|
||||
|
||||
/// Returns the filename of the backup found (otherwise an error)
|
||||
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)?;
|
||||
let mut newest_backup_time = 0;
|
||||
let mut newest_backup_path: Option<std::path::PathBuf> = None;
|
||||
for dirent in dir_iter {
|
||||
match dirent {
|
||||
Ok(dirent) => {
|
||||
let path = dirent.path();
|
||||
let name = dirent.file_name();
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => (),
|
||||
}
|
||||
}
|
||||
match newest_backup_path {
|
||||
Some(path) => Ok(path.to_string_lossy().into_owned()),
|
||||
None => bail!("no backup found in {}", dir_name.display()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initiate_key_transfer(context: &Context) -> Result<String> {
|
||||
ensure!(context.alloc_ongoing(), "could not allocate ongoing");
|
||||
let res = do_initiate_key_transfer(context);
|
||||
context.free_ongoing();
|
||||
res
|
||||
}
|
||||
|
||||
fn do_initiate_key_transfer(context: &Context) -> Result<String> {
|
||||
let mut msg: Message;
|
||||
let setup_code = create_setup_code(context);
|
||||
/* this may require a keypair to be created. this may take a second ... */
|
||||
ensure!(!context.shall_stop_ongoing(), "canceled");
|
||||
let setup_file_content = render_setup_file(context, &setup_code)?;
|
||||
/* encrypting may also take a while ... */
|
||||
ensure!(!context.shall_stop_ongoing(), "canceled");
|
||||
let setup_file_blob = BlobObject::create(
|
||||
context,
|
||||
"autocrypt-setup-message.html",
|
||||
setup_file_content.as_bytes(),
|
||||
)?;
|
||||
|
||||
let chat_id = chat::create_by_contact_id(context, DC_CONTACT_ID_SELF)?;
|
||||
msg = Message::default();
|
||||
msg.type_0 = Viewtype::File;
|
||||
msg.param.set(Param::File, setup_file_blob.as_name());
|
||||
|
||||
msg.param
|
||||
.set(Param::MimeType, "application/autocrypt-setup");
|
||||
msg.param.set_cmd(SystemMessage::AutocryptSetupMessage);
|
||||
msg.param
|
||||
.set_int(Param::ForcePlaintext, DC_FP_NO_AUTOCRYPT_HEADER);
|
||||
|
||||
ensure!(!context.shall_stop_ongoing(), "canceled");
|
||||
let msg_id = chat::send_msg(context, chat_id, &mut msg)?;
|
||||
info!(context, "Wait for setup message being sent ...",);
|
||||
while !context.shall_stop_ongoing() {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
if let Ok(msg) = Message::load_from_db(context, msg_id) {
|
||||
if msg.is_sent() {
|
||||
info!(context, "... setup message sent.",);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(setup_code)
|
||||
}
|
||||
|
||||
/// Renders HTML body of a setup file message.
|
||||
///
|
||||
/// The `passphrase` must be at least 2 characters long.
|
||||
pub fn render_setup_file(context: &Context, passphrase: &str) -> Result<String> {
|
||||
ensure!(
|
||||
passphrase.len() >= 2,
|
||||
"Passphrase must be at least 2 chars long."
|
||||
);
|
||||
let self_addr = e2ee::ensure_secret_key_exists(context)?;
|
||||
let private_key = Key::from_self_private(context, self_addr, &context.sql)
|
||||
.ok_or(format_err!("Failed to get private key."))?;
|
||||
let ac_headers = match context.get_config_bool(Config::E2eeEnabled) {
|
||||
false => None,
|
||||
true => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
|
||||
};
|
||||
let private_key_asc = private_key.to_asc(ac_headers);
|
||||
let encr = pgp::symm_encrypt(&passphrase, private_key_asc.as_bytes())?;
|
||||
|
||||
let replacement = format!(
|
||||
concat!(
|
||||
"-----BEGIN PGP MESSAGE-----\r\n",
|
||||
"Passphrase-Format: numeric9x4\r\n",
|
||||
"Passphrase-Begin: {}"
|
||||
),
|
||||
&passphrase[..2]
|
||||
);
|
||||
let pgp_msg = encr.replace("-----BEGIN PGP MESSAGE-----", &replacement);
|
||||
|
||||
let msg_subj = context.stock_str(StockMessage::AcSetupMsgSubject);
|
||||
let msg_body = context.stock_str(StockMessage::AcSetupMsgBody);
|
||||
let msg_body_html = msg_body.replace("\r", "").replace("\n", "<br>");
|
||||
Ok(format!(
|
||||
concat!(
|
||||
"<!DOCTYPE html>\r\n",
|
||||
"<html>\r\n",
|
||||
" <head>\r\n",
|
||||
" <title>{}</title>\r\n",
|
||||
" </head>\r\n",
|
||||
" <body>\r\n",
|
||||
" <h1>{}</h1>\r\n",
|
||||
" <p>{}</p>\r\n",
|
||||
" <pre>\r\n{}\r\n</pre>\r\n",
|
||||
" </body>\r\n",
|
||||
"</html>\r\n"
|
||||
),
|
||||
msg_subj, msg_subj, msg_body_html, pgp_msg
|
||||
))
|
||||
}
|
||||
|
||||
pub fn create_setup_code(_context: &Context) -> String {
|
||||
let mut random_val: u16;
|
||||
let mut rng = thread_rng();
|
||||
let mut ret = String::new();
|
||||
|
||||
for i in 0..9 {
|
||||
loop {
|
||||
random_val = rng.gen();
|
||||
if !(random_val as usize > 60000) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
random_val = (random_val as usize % 10000) as u16;
|
||||
ret += &format!(
|
||||
"{}{:04}",
|
||||
if 0 != i { "-" } else { "" },
|
||||
random_val as usize
|
||||
);
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn continue_key_transfer(context: &Context, msg_id: MsgId, setup_code: &str) -> Result<()> {
|
||||
ensure!(!msg_id.is_special(), "wrong id");
|
||||
|
||||
let msg = Message::load_from_db(context, msg_id)?;
|
||||
ensure!(
|
||||
msg.is_setupmessage(),
|
||||
"Message is no Autocrypt Setup Message."
|
||||
);
|
||||
|
||||
if let Some(filename) = msg.get_file(context) {
|
||||
let file = dc_open_file(context, filename)?;
|
||||
let sc = normalize_setup_code(setup_code);
|
||||
let armored_key = decrypt_setup_file(context, &sc, file)?;
|
||||
set_self_key(context, &armored_key, true, true)?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
bail!("Message is no Autocrypt Setup Message.");
|
||||
}
|
||||
}
|
||||
|
||||
fn set_self_key(
|
||||
context: &Context,
|
||||
armored: &str,
|
||||
set_default: bool,
|
||||
prefer_encrypt_required: bool,
|
||||
) -> Result<()> {
|
||||
// try hard to only modify key-state
|
||||
let keys = Key::from_armored_string(armored, KeyType::Private)
|
||||
.and_then(|(k, h)| if k.verify() { Some((k, h)) } else { None })
|
||||
.and_then(|(k, h)| k.split_key().map(|pub_key| (k, pub_key, h)));
|
||||
|
||||
ensure!(keys.is_some(), "Not a valid private key");
|
||||
|
||||
let (private_key, public_key, header) = keys.unwrap();
|
||||
let preferencrypt = header.get("Autocrypt-Prefer-Encrypt");
|
||||
match preferencrypt.map(|s| s.as_str()) {
|
||||
Some(headerval) => {
|
||||
let e2ee_enabled = match headerval {
|
||||
"nopreference" => 0,
|
||||
"mutual" => 1,
|
||||
_ => {
|
||||
bail!("invalid Autocrypt-Prefer-Encrypt header: {:?}", header);
|
||||
}
|
||||
};
|
||||
context
|
||||
.sql
|
||||
.set_raw_config_int(context, "e2ee_enabled", e2ee_enabled)?;
|
||||
}
|
||||
None => {
|
||||
if prefer_encrypt_required {
|
||||
bail!("missing Autocrypt-Prefer-Encrypt header");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let self_addr = context.get_config(Config::ConfiguredAddr);
|
||||
ensure!(self_addr.is_some(), "Missing self addr");
|
||||
|
||||
// XXX maybe better make dc_key_save_self_keypair delete things
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"DELETE FROM keypairs WHERE public_key=? OR private_key=?;",
|
||||
params![public_key.to_bytes(), private_key.to_bytes()],
|
||||
)?;
|
||||
|
||||
if set_default {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE keypairs SET is_default=0;",
|
||||
params![],
|
||||
)?;
|
||||
}
|
||||
|
||||
if !dc_key_save_self_keypair(
|
||||
context,
|
||||
&public_key,
|
||||
&private_key,
|
||||
self_addr.unwrap_or_default(),
|
||||
set_default,
|
||||
&context.sql,
|
||||
) {
|
||||
bail!("Cannot save keypair, internal key-state possibly corrupted now!");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decrypt_setup_file<T: std::io::Read + std::io::Seek>(
|
||||
_context: &Context,
|
||||
passphrase: &str,
|
||||
file: T,
|
||||
) -> Result<String> {
|
||||
let plain_bytes = pgp::symm_decrypt(passphrase, file)?;
|
||||
let plain_text = std::string::String::from_utf8(plain_bytes)?;
|
||||
|
||||
Ok(plain_text)
|
||||
}
|
||||
|
||||
pub fn normalize_setup_code(s: &str) -> String {
|
||||
let mut out = String::new();
|
||||
for c in s.chars() {
|
||||
if c >= '0' && c <= '9' {
|
||||
out.push(c);
|
||||
if let 4 | 9 | 14 | 19 | 24 | 29 | 34 | 39 = out.len() {
|
||||
out += "-"
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) -> Result<()> {
|
||||
ensure!(context.alloc_ongoing(), "could not allocate ongoing");
|
||||
let what: Option<ImexMode> = job.param.get_int(Param::Cmd).and_then(ImexMode::from_i32);
|
||||
let param = job.param.get(Param::Arg).unwrap_or_default();
|
||||
|
||||
ensure!(!param.is_empty(), "No Import/export dir/file given.");
|
||||
info!(context, "Import/export process started.");
|
||||
context.call_cb(Event::ImexProgress(10));
|
||||
|
||||
ensure!(context.sql.is_open(), "Database not opened.");
|
||||
if what == Some(ImexMode::ExportBackup) || what == Some(ImexMode::ExportSelfKeys) {
|
||||
// before we export anything, make sure the private key exists
|
||||
if e2ee::ensure_secret_key_exists(context).is_err() {
|
||||
context.free_ongoing();
|
||||
bail!("Cannot create private key or private key not available.");
|
||||
} else {
|
||||
dc_create_folder(context, ¶m);
|
||||
}
|
||||
}
|
||||
let path = Path::new(param);
|
||||
let success = match what {
|
||||
Some(ImexMode::ExportSelfKeys) => export_self_keys(context, path),
|
||||
Some(ImexMode::ImportSelfKeys) => import_self_keys(context, path),
|
||||
Some(ImexMode::ExportBackup) => export_backup(context, path),
|
||||
Some(ImexMode::ImportBackup) => import_backup(context, path),
|
||||
None => {
|
||||
bail!("unknown IMEX type");
|
||||
}
|
||||
};
|
||||
context.free_ongoing();
|
||||
match success {
|
||||
Ok(()) => {
|
||||
info!(context, "IMEX successfully completed");
|
||||
context.call_cb(Event::ImexProgress(1000));
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
context.call_cb(Event::ImexProgress(0));
|
||||
bail!("IMEX FAILED to complete: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Import Backup
|
||||
fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Result<()> {
|
||||
info!(
|
||||
context,
|
||||
"Import \"{}\" to \"{}\".",
|
||||
backup_to_import.as_ref().display(),
|
||||
context.get_dbfile().display()
|
||||
);
|
||||
|
||||
ensure!(
|
||||
!dc_is_configured(context),
|
||||
"Cannot import backups to accounts in use."
|
||||
);
|
||||
context.sql.close(&context);
|
||||
dc_delete_file(context, context.get_dbfile());
|
||||
ensure!(
|
||||
!context.get_dbfile().exists(),
|
||||
"Cannot delete old database."
|
||||
);
|
||||
|
||||
ensure!(
|
||||
dc_copy_file(context, backup_to_import.as_ref(), context.get_dbfile()),
|
||||
"could not copy file"
|
||||
);
|
||||
/* error already logged */
|
||||
/* re-open copied database file */
|
||||
ensure!(
|
||||
context.sql.open(&context, &context.get_dbfile(), false),
|
||||
"could not re-open db"
|
||||
);
|
||||
|
||||
let total_files_cnt = context
|
||||
.sql
|
||||
.query_get_value::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![])
|
||||
.unwrap_or_default() as usize;
|
||||
info!(
|
||||
context,
|
||||
"***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt,
|
||||
);
|
||||
|
||||
let res = context.sql.query_map(
|
||||
"SELECT file_name, file_content FROM backup_blobs ORDER BY id;",
|
||||
params![],
|
||||
|row| {
|
||||
let name: String = row.get(0)?;
|
||||
let blob: Vec<u8> = row.get(1)?;
|
||||
|
||||
Ok((name, blob))
|
||||
},
|
||||
|files| {
|
||||
for (processed_files_cnt, file) in files.enumerate() {
|
||||
let (file_name, file_blob) = file?;
|
||||
ensure!(!context.shall_stop_ongoing(), "received stop signal");
|
||||
let mut permille = processed_files_cnt * 1000 / total_files_cnt;
|
||||
if permille < 10 {
|
||||
permille = 10
|
||||
}
|
||||
if permille > 990 {
|
||||
permille = 990
|
||||
}
|
||||
context.call_cb(Event::ImexProgress(permille));
|
||||
if file_blob.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let path_filename = context.get_blobdir().join(file_name);
|
||||
if dc_write_file(context, &path_filename, &file_blob) {
|
||||
continue;
|
||||
}
|
||||
bail!(
|
||||
"Storage full? Cannot write file {} with {} bytes.",
|
||||
path_filename.display(),
|
||||
file_blob.len(),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
|
||||
res.and_then(|_| {
|
||||
// only delete backup_blobs if all files were successfully extracted
|
||||
sql::execute(context, &context.sql, "DROP TABLE backup_blobs;", params![])?;
|
||||
sql::try_execute(context, &context.sql, "VACUUM;").ok();
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Export backup
|
||||
******************************************************************************/
|
||||
/* the FILE_PROGRESS macro calls the callback with the permille of files processed.
|
||||
The macro avoids weird values of 0% or 100% while still working. */
|
||||
fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
// get a fine backup file name (the name includes the date so that multiple backup instances are possible)
|
||||
// FIXME: we should write to a temporary file first and rename it on success. this would guarantee the backup is complete.
|
||||
// let dest_path_filename = dc_get_next_backup_file(context, dir, res);
|
||||
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 '{}'.",
|
||||
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);
|
||||
|
||||
if !copied {
|
||||
bail!(
|
||||
"could not copy file from '{}' to '{}'",
|
||||
context.get_dbfile().display(),
|
||||
dest_path_string
|
||||
);
|
||||
}
|
||||
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) {
|
||||
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.call_cb(Event::ImexFileWritten(dest_path_filename.clone()));
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
dest_sql.close(context);
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn add_files_to_export(context: &Context, sql: &Sql) -> 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)
|
||||
if !sql.table_exists("backup_blobs") {
|
||||
sql::execute(
|
||||
context,
|
||||
&sql,
|
||||
"CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);",
|
||||
params![],
|
||||
)?
|
||||
}
|
||||
// copy all files from BLOBDIR into backup-db
|
||||
let mut total_files_cnt = 0;
|
||||
let dir = context.get_blobdir();
|
||||
let dir_handle = std::fs::read_dir(&dir)?;
|
||||
total_files_cnt += dir_handle.filter(|r| r.is_ok()).count();
|
||||
|
||||
info!(context, "EXPORT: total_files_cnt={}", total_files_cnt);
|
||||
// scan directory, pass 2: copy files
|
||||
let dir_handle = std::fs::read_dir(&dir)?;
|
||||
sql.prepare(
|
||||
"INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);",
|
||||
|mut stmt, _| {
|
||||
let mut processed_files_cnt = 0;
|
||||
for entry in dir_handle {
|
||||
let entry = entry?;
|
||||
ensure!(
|
||||
!context.shall_stop_ongoing(),
|
||||
"canceled during export-files"
|
||||
);
|
||||
processed_files_cnt += 1;
|
||||
let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10);
|
||||
context.call_cb(Event::ImexProgress(permille));
|
||||
|
||||
let name_f = entry.file_name();
|
||||
let name = name_f.to_string_lossy();
|
||||
if name.starts_with("delta-chat") && name.ends_with(".bak") {
|
||||
continue;
|
||||
}
|
||||
info!(context, "EXPORT: copying filename={}", name);
|
||||
let curr_path_filename = context.get_blobdir().join(entry.file_name());
|
||||
if let Ok(buf) = dc_read_file(context, &curr_path_filename) {
|
||||
if buf.is_empty() {
|
||||
continue;
|
||||
}
|
||||
// bail out if we can't insert
|
||||
stmt.execute(params![name, buf])?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Classic key import
|
||||
******************************************************************************/
|
||||
fn import_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
/* hint: even if we switch to import Autocrypt Setup Files, we should leave the possibility to import
|
||||
plain ASC keys, at least keys without a password, if we do not want to implement a password entry function.
|
||||
Importing ASC keys is useful to use keys in Delta Chat used by any other non-Autocrypt-PGP implementation.
|
||||
|
||||
Maybe we should make the "default" key handlong also a little bit smarter
|
||||
(currently, the last imported key is the standard key unless it contains the string "legacy" in its name) */
|
||||
let mut set_default: bool;
|
||||
let mut imported_cnt = 0;
|
||||
|
||||
let dir_name = dir.as_ref().to_string_lossy();
|
||||
let dir_handle = std::fs::read_dir(&dir)?;
|
||||
for entry in dir_handle {
|
||||
let entry_fn = entry?.file_name();
|
||||
let name_f = entry_fn.to_string_lossy();
|
||||
let path_plus_name = dir.as_ref().join(&entry_fn);
|
||||
match dc_get_filesuffix_lc(&name_f) {
|
||||
Some(suffix) => {
|
||||
if suffix != "asc" {
|
||||
continue;
|
||||
}
|
||||
set_default = if name_f.contains("legacy") {
|
||||
info!(context, "found legacy key '{}'", path_plus_name.display());
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
None => {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
match dc_read_file(context, &path_plus_name) {
|
||||
Ok(buf) => {
|
||||
let armored = std::string::String::from_utf8_lossy(&buf);
|
||||
if let Err(err) = set_self_key(context, &armored, set_default, false) {
|
||||
error!(context, "set_self_key: {}", err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(_) => continue,
|
||||
}
|
||||
imported_cnt += 1;
|
||||
}
|
||||
ensure!(
|
||||
imported_cnt > 0,
|
||||
"No private keys found in \"{}\".",
|
||||
dir_name
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
let mut export_errors = 0;
|
||||
|
||||
context.sql.query_map(
|
||||
"SELECT id, public_key, private_key, is_default FROM keypairs;",
|
||||
params![],
|
||||
|row| {
|
||||
let id = row.get(0)?;
|
||||
let public_key_blob: Vec<u8> = row.get(1)?;
|
||||
let public_key = Key::from_slice(&public_key_blob, KeyType::Public);
|
||||
let private_key_blob: Vec<u8> = row.get(2)?;
|
||||
let private_key = Key::from_slice(&private_key_blob, KeyType::Private);
|
||||
let is_default: i32 = row.get(3)?;
|
||||
|
||||
Ok((id, public_key, private_key, is_default))
|
||||
},
|
||||
|keys| {
|
||||
for key_pair in keys {
|
||||
let (id, public_key, private_key, is_default) = key_pair?;
|
||||
let id = Some(id).filter(|_| is_default != 0);
|
||||
if let Some(key) = public_key {
|
||||
if !export_key_to_asc_file(context, &dir, id, &key) {
|
||||
export_errors += 1;
|
||||
}
|
||||
} else {
|
||||
export_errors += 1;
|
||||
}
|
||||
if let Some(key) = private_key {
|
||||
if !export_key_to_asc_file(context, &dir, id, &key) {
|
||||
export_errors += 1;
|
||||
}
|
||||
} else {
|
||||
export_errors += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
|
||||
ensure!(export_errors == 0, "errors while exporting keys");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Classic key export
|
||||
******************************************************************************/
|
||||
fn export_key_to_asc_file(
|
||||
context: &Context,
|
||||
dir: impl AsRef<Path>,
|
||||
id: Option<i64>,
|
||||
key: &Key,
|
||||
) -> bool {
|
||||
let mut success = false;
|
||||
let file_name = {
|
||||
let kind = if key.is_public() { "public" } else { "private" };
|
||||
let id = id.map_or("default".into(), |i| i.to_string());
|
||||
|
||||
dir.as_ref().join(format!("{}-key-{}.asc", kind, &id))
|
||||
};
|
||||
info!(context, "Exporting key {}", file_name.display());
|
||||
dc_delete_file(context, &file_name);
|
||||
|
||||
if !key.write_asc_to_file(&file_name, context) {
|
||||
error!(context, "Cannot write key to {}", file_name.display());
|
||||
} else {
|
||||
context.call_cb(Event::ImexFileWritten(file_name));
|
||||
success = true;
|
||||
}
|
||||
|
||||
success
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::pgp::{split_armored_data, HEADER_AUTOCRYPT, HEADER_SETUPCODE};
|
||||
use crate::test_utils::*;
|
||||
use ::pgp::armor::BlockType;
|
||||
|
||||
#[test]
|
||||
fn test_render_setup_file() {
|
||||
let t = test_context(Some(Box::new(logging_cb)));
|
||||
|
||||
configure_alice_keypair(&t.ctx);
|
||||
let msg = render_setup_file(&t.ctx, "hello").unwrap();
|
||||
println!("{}", &msg);
|
||||
// Check some substrings, indicating things got substituted.
|
||||
// In particular note the mixing of `\r\n` and `\n` depending
|
||||
// on who generated the stings.
|
||||
assert!(msg.contains("<title>Autocrypt Setup Message</title"));
|
||||
assert!(msg.contains("<h1>Autocrypt Setup Message</h1>"));
|
||||
assert!(msg.contains("<p>This is the Autocrypt Setup Message used to"));
|
||||
assert!(msg.contains("-----BEGIN PGP MESSAGE-----\r\n"));
|
||||
assert!(msg.contains("Passphrase-Format: numeric9x4\r\n"));
|
||||
assert!(msg.contains("Passphrase-Begin: he\n"));
|
||||
assert!(msg.contains("==\n"));
|
||||
assert!(msg.contains("-----END PGP MESSAGE-----\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render_setup_file_newline_replace() {
|
||||
let t = dummy_context();
|
||||
t.ctx
|
||||
.set_stock_translation(StockMessage::AcSetupMsgBody, "hello\r\nthere".to_string())
|
||||
.unwrap();
|
||||
configure_alice_keypair(&t.ctx);
|
||||
let msg = render_setup_file(&t.ctx, "pw").unwrap();
|
||||
println!("{}", &msg);
|
||||
assert!(msg.contains("<p>hello<br>there</p>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_setup_code() {
|
||||
let t = dummy_context();
|
||||
let setupcode = create_setup_code(&t.ctx);
|
||||
assert_eq!(setupcode.len(), 44);
|
||||
assert_eq!(setupcode.chars().nth(4).unwrap(), '-');
|
||||
assert_eq!(setupcode.chars().nth(9).unwrap(), '-');
|
||||
assert_eq!(setupcode.chars().nth(14).unwrap(), '-');
|
||||
assert_eq!(setupcode.chars().nth(19).unwrap(), '-');
|
||||
assert_eq!(setupcode.chars().nth(24).unwrap(), '-');
|
||||
assert_eq!(setupcode.chars().nth(29).unwrap(), '-');
|
||||
assert_eq!(setupcode.chars().nth(34).unwrap(), '-');
|
||||
assert_eq!(setupcode.chars().nth(39).unwrap(), '-');
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_export_key_to_asc_file() {
|
||||
let context = dummy_context();
|
||||
let base64 = include_str!("../test-data/key/public.asc");
|
||||
let key = Key::from_base64(base64, KeyType::Public).unwrap();
|
||||
let blobdir = "$BLOBDIR";
|
||||
assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key));
|
||||
let blobdir = context.ctx.get_blobdir().to_str().unwrap();
|
||||
let filename = format!("{}/public-key-default.asc", blobdir);
|
||||
let bytes = std::fs::read(&filename).unwrap();
|
||||
|
||||
assert_eq!(bytes, key.to_asc(None).into_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_setup_code() {
|
||||
let norm = normalize_setup_code("123422343234423452346234723482349234");
|
||||
assert_eq!(norm, "1234-2234-3234-4234-5234-6234-7234-8234-9234");
|
||||
|
||||
let norm =
|
||||
normalize_setup_code("\t1 2 3422343234- foo bar-- 423-45 2 34 6234723482349234 ");
|
||||
assert_eq!(norm, "1234-2234-3234-4234-5234-6234-7234-8234-9234");
|
||||
}
|
||||
|
||||
/* S_EM_SETUPFILE is a AES-256 symm. encrypted setup message created by Enigmail
|
||||
with an "encrypted session key", see RFC 4880. The code is in S_EM_SETUPCODE */
|
||||
const S_EM_SETUPCODE: &str = "1742-0185-6197-1303-7016-8412-3581-4441-0597";
|
||||
const S_EM_SETUPFILE: &str = include_str!("../test-data/message/stress.txt");
|
||||
|
||||
#[test]
|
||||
fn test_split_and_decrypt() {
|
||||
let ctx = dummy_context();
|
||||
let context = &ctx.ctx;
|
||||
|
||||
let buf_1 = S_EM_SETUPFILE.as_bytes().to_vec();
|
||||
let (typ, headers, base64) = split_armored_data(&buf_1).unwrap();
|
||||
assert_eq!(typ, BlockType::Message);
|
||||
assert!(S_EM_SETUPCODE.starts_with(headers.get(HEADER_SETUPCODE).unwrap()));
|
||||
assert!(headers.get(HEADER_AUTOCRYPT).is_none());
|
||||
|
||||
assert!(!base64.is_empty());
|
||||
|
||||
let setup_file = S_EM_SETUPFILE.to_string();
|
||||
let decrypted = decrypt_setup_file(
|
||||
context,
|
||||
S_EM_SETUPCODE,
|
||||
std::io::Cursor::new(setup_file.as_bytes()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (typ, headers, _base64) = split_armored_data(decrypted.as_bytes()).unwrap();
|
||||
|
||||
assert_eq!(typ, BlockType::PrivateKey);
|
||||
assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string()));
|
||||
assert!(headers.get(HEADER_SETUPCODE).is_none());
|
||||
}
|
||||
}
|
||||
677
src/job.rs
677
src/job.rs
@@ -1,26 +1,26 @@
|
||||
use std::ffi::CStr;
|
||||
use std::ptr;
|
||||
use std::time::Duration;
|
||||
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
use mmime::clist::*;
|
||||
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_imex::*;
|
||||
use crate::dc_mimefactory::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
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::message::*;
|
||||
use crate::param::*;
|
||||
use crate::sql;
|
||||
use crate::x::*;
|
||||
|
||||
/// Thread IDs
|
||||
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
|
||||
@@ -44,7 +44,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 +75,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,21 +133,22 @@ 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 Some(body) = dc_read_file_safe(context, filename) {
|
||||
if let Some(recipients) = self.param.get(Param::Recipients) {
|
||||
let recipients_list = recipients
|
||||
.split('\x1e')
|
||||
.split("\x1e")
|
||||
.filter_map(|addr| match lettre::EmailAddress::new(addr.to_string()) {
|
||||
Ok(addr) => Some(addr),
|
||||
Err(err) => {
|
||||
warn!(context, "invalid recipient: {} {:?}", addr, err);
|
||||
eprintln!("WARNING: invalid recipient: {} {:?}", addr, err);
|
||||
None
|
||||
}
|
||||
})
|
||||
@@ -158,50 +157,42 @@ 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 && !dc_msg_exists(context, self.foreign_id) {
|
||||
warn!(
|
||||
context,
|
||||
"Not sending Message {} as it was deleted", self.foreign_id
|
||||
"Message {} for job {} does not exist", self.foreign_id, self.job_id,
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
// hold the smtp lock during sending of a job and
|
||||
// its ok/error response processing. Note that if a message
|
||||
// was sent we need to mark it in the database ASAP as we
|
||||
// was sent we need to mark it in the database as we
|
||||
// otherwise might send it twice.
|
||||
let mut smtp = context.smtp.lock().unwrap();
|
||||
match smtp.send(context, recipients_list, body, self.job_id) {
|
||||
Err(err) => {
|
||||
smtp.disconnect();
|
||||
warn!(context, "smtp failed: {}", err);
|
||||
self.try_again_later(-1, Some(err.to_string()));
|
||||
}
|
||||
Ok(()) => {
|
||||
// smtp success, update db ASAP, then delete smtp file
|
||||
if 0 != self.foreign_id {
|
||||
message::update_msg_state(
|
||||
let mut sock = context.smtp.lock().unwrap();
|
||||
if 0 == sock.send(context, recipients_list, body) {
|
||||
sock.disconnect();
|
||||
self.try_again_later(-1i32, sock.error.clone());
|
||||
} else {
|
||||
dc_delete_file(context, filename);
|
||||
if 0 != self.foreign_id {
|
||||
dc_update_msg_state(
|
||||
context,
|
||||
self.foreign_id,
|
||||
MessageState::OutDelivered,
|
||||
);
|
||||
let chat_id: i32 = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
context,
|
||||
MsgId::new(self.foreign_id),
|
||||
MessageState::OutDelivered,
|
||||
);
|
||||
let chat_id: i32 = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT chat_id FROM msgs WHERE id=?",
|
||||
params![self.foreign_id as i32],
|
||||
)
|
||||
.unwrap_or_default();
|
||||
context.call_cb(Event::MsgDelivered {
|
||||
chat_id: chat_id as u32,
|
||||
msg_id: MsgId::new(self.foreign_id),
|
||||
});
|
||||
}
|
||||
// now also delete the generated file
|
||||
dc_delete_file(context, filename);
|
||||
"SELECT chat_id FROM msgs WHERE id=?",
|
||||
params![self.foreign_id as i32],
|
||||
)
|
||||
.unwrap_or_default();
|
||||
context.call_cb(Event::MsgDelivered {
|
||||
chat_id: chat_id as u32,
|
||||
msg_id: self.foreign_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -212,51 +203,60 @@ 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;
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn do_DC_JOB_MOVE_MSG(&mut self, context: &Context) {
|
||||
let ok_to_continue;
|
||||
let mut dest_uid = 0;
|
||||
|
||||
let inbox = context.inbox.read().unwrap();
|
||||
|
||||
if let Ok(msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
|
||||
if context
|
||||
.sql
|
||||
.get_raw_config_int(context, "folders_configured")
|
||||
.unwrap_or_default()
|
||||
< 3
|
||||
{
|
||||
inbox.configure_folders(context, 0x1i32);
|
||||
if !inbox.is_connected() {
|
||||
connect_to_inbox(context, &inbox);
|
||||
if !inbox.is_connected() {
|
||||
self.try_again_later(3, None);
|
||||
ok_to_continue = false;
|
||||
} else {
|
||||
ok_to_continue = true;
|
||||
}
|
||||
let dest_folder = context
|
||||
.sql
|
||||
.get_raw_config(context, "configured_mvbox_folder");
|
||||
} else {
|
||||
ok_to_continue = true;
|
||||
}
|
||||
if ok_to_continue {
|
||||
if let Ok(msg) = dc_msg_load_from_db(context, self.foreign_id) {
|
||||
if context
|
||||
.sql
|
||||
.get_config_int(context, "folders_configured")
|
||||
.unwrap_or_default()
|
||||
< 3
|
||||
{
|
||||
inbox.configure_folders(context, 0x1i32);
|
||||
}
|
||||
let dest_folder = context.sql.get_config(context, "configured_mvbox_folder");
|
||||
|
||||
if let Some(dest_folder) = dest_folder {
|
||||
let server_folder = msg.server_folder.as_ref().unwrap();
|
||||
let mut dest_uid = 0;
|
||||
if let Some(dest_folder) = dest_folder {
|
||||
let server_folder = msg.server_folder.as_ref().unwrap();
|
||||
|
||||
match inbox.mv(
|
||||
context,
|
||||
server_folder,
|
||||
msg.server_uid,
|
||||
&dest_folder,
|
||||
&mut dest_uid,
|
||||
) {
|
||||
ImapResult::RetryLater => {
|
||||
self.try_again_later(3i32, None);
|
||||
match inbox.mv(
|
||||
context,
|
||||
server_folder,
|
||||
msg.server_uid,
|
||||
&dest_folder,
|
||||
&mut dest_uid,
|
||||
) as libc::c_uint
|
||||
{
|
||||
1 => {
|
||||
self.try_again_later(3i32, None);
|
||||
}
|
||||
3 => {
|
||||
dc_update_server_uid(context, msg.rfc724_mid, &dest_folder, dest_uid);
|
||||
}
|
||||
0 | 2 | _ => {}
|
||||
}
|
||||
ImapResult::Success => {
|
||||
message::update_server_uid(
|
||||
context,
|
||||
&msg.rfc724_mid,
|
||||
&dest_folder,
|
||||
dest_uid,
|
||||
);
|
||||
}
|
||||
ImapResult::Failed | ImapResult::AlreadyDone => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -264,69 +264,101 @@ impl Job {
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn do_DC_JOB_DELETE_MSG_ON_IMAP(&mut self, context: &Context) {
|
||||
let mut delete_from_server = 1;
|
||||
let inbox = context.inbox.read().unwrap();
|
||||
|
||||
if let Ok(mut msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
|
||||
if !msg.rfc724_mid.is_empty() {
|
||||
if let Ok(mut msg) = dc_msg_load_from_db(context, self.foreign_id) {
|
||||
if !(msg.rfc724_mid.is_null()
|
||||
|| unsafe { *msg.rfc724_mid.offset(0isize) as libc::c_int == 0 })
|
||||
{
|
||||
let ok_to_continue1;
|
||||
/* eg. device messages have no Message-ID */
|
||||
if message::rfc724_mid_cnt(context, &msg.rfc724_mid) > 1 {
|
||||
if dc_rfc724_mid_cnt(context, msg.rfc724_mid) != 1 {
|
||||
info!(
|
||||
context,
|
||||
"The message is deleted from the server when all parts are deleted.",
|
||||
);
|
||||
} else {
|
||||
/* if this is the last existing part of the message,
|
||||
we delete the message from the server */
|
||||
let mid = msg.rfc724_mid;
|
||||
let server_folder = msg.server_folder.as_ref().unwrap();
|
||||
let res = inbox.delete_msg(context, &mid, server_folder, &mut msg.server_uid);
|
||||
if res == ImapResult::RetryLater {
|
||||
self.try_again_later(-1i32, None);
|
||||
return;
|
||||
}
|
||||
delete_from_server = 0i32
|
||||
}
|
||||
/* if this is the last existing part of the message, we delete the message from the server */
|
||||
if 0 != delete_from_server {
|
||||
let ok_to_continue;
|
||||
if !inbox.is_connected() {
|
||||
connect_to_inbox(context, &inbox);
|
||||
if !inbox.is_connected() {
|
||||
self.try_again_later(3i32, None);
|
||||
ok_to_continue = false;
|
||||
} else {
|
||||
ok_to_continue = true;
|
||||
}
|
||||
} else {
|
||||
ok_to_continue = true;
|
||||
}
|
||||
if ok_to_continue {
|
||||
let mid = unsafe { CStr::from_ptr(msg.rfc724_mid).to_str().unwrap() };
|
||||
let server_folder = msg.server_folder.as_ref().unwrap();
|
||||
if 0 == inbox.delete_msg(context, mid, server_folder, &mut msg.server_uid) {
|
||||
self.try_again_later(-1i32, None);
|
||||
ok_to_continue1 = false;
|
||||
} else {
|
||||
ok_to_continue1 = true;
|
||||
}
|
||||
} else {
|
||||
ok_to_continue1 = false;
|
||||
}
|
||||
} else {
|
||||
ok_to_continue1 = true;
|
||||
}
|
||||
if ok_to_continue1 {
|
||||
dc_delete_msg_from_db(context, msg.id);
|
||||
}
|
||||
Message::delete_from_db(context, msg.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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 ok_to_continue;
|
||||
let inbox = context.inbox.read().unwrap();
|
||||
|
||||
if let Ok(msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
|
||||
let folder = msg.server_folder.as_ref().unwrap();
|
||||
match inbox.set_seen(context, folder, msg.server_uid) {
|
||||
ImapResult::RetryLater => {
|
||||
self.try_again_later(3i32, None);
|
||||
}
|
||||
ImapResult::AlreadyDone => {}
|
||||
ImapResult::Success | ImapResult::Failed => {
|
||||
// XXX the message might just have been moved
|
||||
// we want to send out an MDN anyway
|
||||
// The job will not be retried so locally
|
||||
// there is no risk of double-sending MDNs.
|
||||
if 0 != msg.param.get_int(Param::WantsMdn).unwrap_or_default()
|
||||
&& context.get_config_bool(Config::MdnsEnabled)
|
||||
{
|
||||
if let Err(err) = send_mdn(context, msg.id) {
|
||||
warn!(context, "could not send out mdn for {}: {}", msg.id, err);
|
||||
if !inbox.is_connected() {
|
||||
connect_to_inbox(context, &inbox);
|
||||
if !inbox.is_connected() {
|
||||
self.try_again_later(3i32, None);
|
||||
ok_to_continue = false;
|
||||
} else {
|
||||
ok_to_continue = true;
|
||||
}
|
||||
} else {
|
||||
ok_to_continue = true;
|
||||
}
|
||||
if ok_to_continue {
|
||||
if let Ok(msg) = dc_msg_load_from_db(context, self.foreign_id) {
|
||||
let server_folder = msg.server_folder.as_ref().unwrap();
|
||||
match inbox.set_seen(context, server_folder, msg.server_uid) as libc::c_uint {
|
||||
0 => {}
|
||||
1 => {
|
||||
self.try_again_later(3i32, None);
|
||||
}
|
||||
_ => {
|
||||
if 0 != msg.param.get_int(Param::WantsMdn).unwrap_or_default()
|
||||
&& 0 != context
|
||||
.sql
|
||||
.get_config_int(context, "mdns_enabled")
|
||||
.unwrap_or_else(|| 1)
|
||||
{
|
||||
let folder = msg.server_folder.as_ref().unwrap();
|
||||
|
||||
match inbox.set_mdnsent(context, folder, msg.server_uid) as libc::c_uint
|
||||
{
|
||||
1 => {
|
||||
self.try_again_later(3i32, None);
|
||||
}
|
||||
3 => {
|
||||
send_mdn(context, msg.id);
|
||||
}
|
||||
0 | 2 | _ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -336,35 +368,47 @@ impl Job {
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn do_DC_JOB_MARKSEEN_MDN_ON_IMAP(&mut self, context: &Context) {
|
||||
let ok_to_continue;
|
||||
let folder = self
|
||||
.param
|
||||
.get(Param::ServerFolder)
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32;
|
||||
let mut dest_uid = 0;
|
||||
let inbox = context.inbox.read().unwrap();
|
||||
if inbox.set_seen(context, &folder, uid) == ImapResult::RetryLater {
|
||||
self.try_again_later(3i32, None);
|
||||
return;
|
||||
}
|
||||
if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() {
|
||||
if context
|
||||
.sql
|
||||
.get_raw_config_int(context, "folders_configured")
|
||||
.unwrap_or_default()
|
||||
< 3
|
||||
{
|
||||
inbox.configure_folders(context, 0x1i32);
|
||||
|
||||
if !inbox.is_connected() {
|
||||
connect_to_inbox(context, &inbox);
|
||||
if !inbox.is_connected() {
|
||||
self.try_again_later(3, None);
|
||||
ok_to_continue = false;
|
||||
} else {
|
||||
ok_to_continue = true;
|
||||
}
|
||||
let dest_folder = context
|
||||
.sql
|
||||
.get_raw_config(context, "configured_mvbox_folder");
|
||||
if let Some(dest_folder) = dest_folder {
|
||||
let mut dest_uid = 0;
|
||||
if ImapResult::RetryLater
|
||||
== inbox.mv(context, &folder, uid, &dest_folder, &mut dest_uid)
|
||||
} else {
|
||||
ok_to_continue = true;
|
||||
}
|
||||
if ok_to_continue {
|
||||
if inbox.set_seen(context, &folder, uid) == 0 {
|
||||
self.try_again_later(3i32, None);
|
||||
}
|
||||
if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() {
|
||||
if context
|
||||
.sql
|
||||
.get_config_int(context, "folders_configured")
|
||||
.unwrap_or_default()
|
||||
< 3
|
||||
{
|
||||
self.try_again_later(3, None);
|
||||
inbox.configure_folders(context, 0x1i32);
|
||||
}
|
||||
let dest_folder = context.sql.get_config(context, "configured_mvbox_folder");
|
||||
if let Some(dest_folder) = dest_folder {
|
||||
if 1 == inbox.mv(context, folder, uid, dest_folder, &mut dest_uid)
|
||||
as libc::c_uint
|
||||
{
|
||||
self.try_again_later(3, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -389,7 +433,12 @@ pub fn perform_imap_fetch(context: &Context) {
|
||||
if 0 == connect_to_inbox(context, &inbox) {
|
||||
return;
|
||||
}
|
||||
if !context.get_config_bool(Config::InboxWatch) {
|
||||
if context
|
||||
.sql
|
||||
.get_config_int(context, "inbox_watch")
|
||||
.unwrap_or_else(|| 1)
|
||||
== 0
|
||||
{
|
||||
info!(context, "INBOX-watch disabled.",);
|
||||
return;
|
||||
}
|
||||
@@ -402,7 +451,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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -424,23 +473,29 @@ pub fn perform_imap_idle(context: &Context) {
|
||||
}
|
||||
|
||||
pub fn perform_mvbox_fetch(context: &Context) {
|
||||
let use_network = context.get_config_bool(Config::MvboxWatch);
|
||||
let use_network = context
|
||||
.sql
|
||||
.get_config_int(context, "mvbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
|
||||
context
|
||||
.mvbox_thread
|
||||
.write()
|
||||
.unwrap()
|
||||
.fetch(context, use_network);
|
||||
.fetch(context, use_network == 1);
|
||||
}
|
||||
|
||||
pub fn perform_mvbox_idle(context: &Context) {
|
||||
let use_network = context.get_config_bool(Config::MvboxWatch);
|
||||
let use_network = context
|
||||
.sql
|
||||
.get_config_int(context, "mvbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
|
||||
context
|
||||
.mvbox_thread
|
||||
.read()
|
||||
.unwrap()
|
||||
.idle(context, use_network);
|
||||
.idle(context, use_network == 1);
|
||||
}
|
||||
|
||||
pub fn interrupt_mvbox_idle(context: &Context) {
|
||||
@@ -448,23 +503,29 @@ pub fn interrupt_mvbox_idle(context: &Context) {
|
||||
}
|
||||
|
||||
pub fn perform_sentbox_fetch(context: &Context) {
|
||||
let use_network = context.get_config_bool(Config::SentboxWatch);
|
||||
let use_network = context
|
||||
.sql
|
||||
.get_config_int(context, "sentbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
|
||||
context
|
||||
.sentbox_thread
|
||||
.write()
|
||||
.unwrap()
|
||||
.fetch(context, use_network);
|
||||
.fetch(context, use_network == 1);
|
||||
}
|
||||
|
||||
pub fn perform_sentbox_idle(context: &Context) {
|
||||
let use_network = context.get_config_bool(Config::SentboxWatch);
|
||||
let use_network = context
|
||||
.sql
|
||||
.get_config_int(context, "sentbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
|
||||
context
|
||||
.sentbox_thread
|
||||
.read()
|
||||
.unwrap()
|
||||
.idle(context, use_network);
|
||||
.idle(context, use_network == 1);
|
||||
}
|
||||
|
||||
pub fn interrupt_sentbox_idle(context: &Context) {
|
||||
@@ -581,104 +642,114 @@ 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> {
|
||||
let mut mimefactory = MimeFactory::load_msg(context, msg_id)?;
|
||||
pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
|
||||
let mut success = 0;
|
||||
|
||||
if chat::msgtype_has_file(mimefactory.msg.type_0) {
|
||||
let file_param = mimefactory.msg.param.get_path(Param::File, context)?;
|
||||
if let Some(pathNfilename) = file_param {
|
||||
if (mimefactory.msg.type_0 == Viewtype::Image
|
||||
|| mimefactory.msg.type_0 == Viewtype::Gif)
|
||||
&& !mimefactory.msg.param.exists(Param::Width)
|
||||
{
|
||||
mimefactory.msg.param.set_int(Param::Width, 0);
|
||||
mimefactory.msg.param.set_int(Param::Height, 0);
|
||||
|
||||
if let Ok(buf) = dc_read_file(context, pathNfilename) {
|
||||
if let Ok((width, height)) = dc_get_filemeta(&buf) {
|
||||
mimefactory.msg.param.set_int(Param::Width, width as i32);
|
||||
mimefactory.msg.param.set_int(Param::Height, height as i32);
|
||||
}
|
||||
}
|
||||
mimefactory.msg.save_param_to_disk(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* create message */
|
||||
if let Err(msg) = unsafe { mimefactory.render() } {
|
||||
let e = msg.to_string();
|
||||
message::set_msg_failed(context, msg_id, Some(e));
|
||||
return Err(msg);
|
||||
}
|
||||
if 0 != mimefactory
|
||||
.msg
|
||||
.param
|
||||
.get_int(Param::GuaranteeE2ee)
|
||||
.unwrap_or_default()
|
||||
&& !mimefactory.out_encrypted
|
||||
{
|
||||
/* unrecoverable */
|
||||
message::set_msg_failed(
|
||||
context,
|
||||
msg_id,
|
||||
Some("End-to-end-encryption unavailable unexpectedly."),
|
||||
);
|
||||
bail!(
|
||||
"e2e encryption unavailable {} - {:?}",
|
||||
msg_id,
|
||||
mimefactory.msg.param.get_int(Param::GuaranteeE2ee),
|
||||
);
|
||||
}
|
||||
if context.get_config_bool(Config::BccSelf)
|
||||
&& !vec_contains_lowercase(&mimefactory.recipients_addr, &mimefactory.from_addr)
|
||||
{
|
||||
mimefactory.recipients_names.push("".to_string());
|
||||
mimefactory
|
||||
.recipients_addr
|
||||
.push(mimefactory.from_addr.to_string());
|
||||
}
|
||||
|
||||
if mimefactory.recipients_addr.is_empty() {
|
||||
/* load message data */
|
||||
let mimefactory = dc_mimefactory_load_msg(context, msg_id);
|
||||
if mimefactory.is_err() || mimefactory.as_ref().unwrap().from_addr.is_null() {
|
||||
warn!(
|
||||
context,
|
||||
"message {} has no recipient, skipping smtp-send", msg_id
|
||||
"Cannot load data to send, maybe the message is deleted in between.",
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
let mut mimefactory = mimefactory.unwrap();
|
||||
// no redo, no IMAP. moreover, as the data does not exist, there is no need in calling dc_set_msg_failed()
|
||||
if chat::msgtype_has_file(mimefactory.msg.type_0) {
|
||||
let file_param = mimefactory
|
||||
.msg
|
||||
.param
|
||||
.get(Param::File)
|
||||
.map(|s| s.to_string());
|
||||
if let Some(pathNfilename) = file_param {
|
||||
if (mimefactory.msg.type_0 == Viewtype::Image
|
||||
|| mimefactory.msg.type_0 == Viewtype::Gif)
|
||||
&& !mimefactory.msg.param.exists(Param::Width)
|
||||
{
|
||||
mimefactory.msg.param.set_int(Param::Width, 0);
|
||||
mimefactory.msg.param.set_int(Param::Height, 0);
|
||||
|
||||
if mimefactory.out_gossiped {
|
||||
chat::set_gossiped_timestamp(context, mimefactory.msg.chat_id, time());
|
||||
}
|
||||
if 0 != mimefactory.out_last_added_location_id {
|
||||
if let Err(err) = location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
|
||||
{
|
||||
error!(context, "Failed to set kml sent_timestamp: {:?}", err);
|
||||
}
|
||||
if !mimefactory.msg.hidden {
|
||||
if let Err(err) = location::set_msg_location_id(
|
||||
context,
|
||||
mimefactory.msg.id,
|
||||
mimefactory.out_last_added_location_id,
|
||||
) {
|
||||
error!(context, "Failed to set msg_location_id: {:?}", err);
|
||||
if let Some(buf) = dc_read_file_safe(context, pathNfilename) {
|
||||
if let Ok((width, height)) = dc_get_filemeta(&buf) {
|
||||
mimefactory.msg.param.set_int(Param::Width, width as i32);
|
||||
mimefactory.msg.param.set_int(Param::Height, height as i32);
|
||||
}
|
||||
}
|
||||
dc_msg_save_param_to_disk(context, &mut mimefactory.msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* create message */
|
||||
if !dc_mimefactory_render(context, &mut mimefactory) {
|
||||
dc_set_msg_failed(context, msg_id, as_opt_str(mimefactory.error));
|
||||
} else if 0
|
||||
!= mimefactory
|
||||
.msg
|
||||
.param
|
||||
.get_int(Param::GuranteeE2ee)
|
||||
.unwrap_or_default()
|
||||
&& 0 == mimefactory.out_encrypted
|
||||
{
|
||||
warn!(
|
||||
context,
|
||||
"e2e encryption unavailable {} - {:?}",
|
||||
msg_id,
|
||||
mimefactory.msg.param.get_int(Param::GuranteeE2ee),
|
||||
);
|
||||
dc_set_msg_failed(
|
||||
context,
|
||||
msg_id,
|
||||
Some("End-to-end-encryption unavailable unexpectedly."),
|
||||
);
|
||||
} else {
|
||||
/* unrecoverable */
|
||||
if !clist_search_string_nocase(mimefactory.recipients_addr, mimefactory.from_addr) {
|
||||
clist_insert_after(
|
||||
mimefactory.recipients_names,
|
||||
(*mimefactory.recipients_names).last,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
clist_insert_after(
|
||||
mimefactory.recipients_addr,
|
||||
(*mimefactory.recipients_addr).last,
|
||||
dc_strdup(mimefactory.from_addr) as *mut libc::c_void,
|
||||
);
|
||||
}
|
||||
if 0 != mimefactory.out_gossiped {
|
||||
chat::set_gossiped_timestamp(context, mimefactory.msg.chat_id, time());
|
||||
}
|
||||
if 0 != mimefactory.out_last_added_location_id {
|
||||
if let Err(err) =
|
||||
location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
|
||||
{
|
||||
error!(context, "Failed to set kml sent_timestamp: {:?}", err);
|
||||
}
|
||||
if !mimefactory.msg.hidden {
|
||||
if let Err(err) = location::set_msg_location_id(
|
||||
context,
|
||||
mimefactory.msg.id,
|
||||
mimefactory.out_last_added_location_id,
|
||||
) {
|
||||
error!(context, "Failed to set msg_location_id: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
if 0 != mimefactory.out_encrypted
|
||||
&& mimefactory
|
||||
.msg
|
||||
.param
|
||||
.get_int(Param::GuranteeE2ee)
|
||||
.unwrap_or_default()
|
||||
== 0
|
||||
{
|
||||
mimefactory.msg.param.set_int(Param::GuranteeE2ee, 1);
|
||||
dc_msg_save_param_to_disk(context, &mut mimefactory.msg);
|
||||
}
|
||||
success = add_smtp_job(context, Action::SendMsgToSmtp, &mut mimefactory);
|
||||
}
|
||||
}
|
||||
if mimefactory.out_encrypted
|
||||
&& mimefactory
|
||||
.msg
|
||||
.param
|
||||
.get_int(Param::GuaranteeE2ee)
|
||||
.unwrap_or_default()
|
||||
== 0
|
||||
{
|
||||
mimefactory.msg.param.set_int(Param::GuaranteeE2ee, 1);
|
||||
mimefactory.msg.save_param_to_disk(context);
|
||||
}
|
||||
add_smtp_job(context, Action::SendMsgToSmtp, &mut mimefactory)?;
|
||||
|
||||
Ok(())
|
||||
success
|
||||
}
|
||||
|
||||
pub fn perform_imap_jobs(context: &Context) {
|
||||
@@ -692,14 +763,6 @@ pub fn perform_imap_jobs(context: &Context) {
|
||||
info!(context, "dc_perform_imap_jobs ended.",);
|
||||
}
|
||||
|
||||
pub fn perform_mvbox_jobs(context: &Context) {
|
||||
info!(context, "dc_perform_mbox_jobs EMPTY (for now).",);
|
||||
}
|
||||
|
||||
pub fn perform_sentbox_jobs(context: &Context) {
|
||||
info!(context, "dc_perform_sentbox_jobs EMPTY (for now).",);
|
||||
}
|
||||
|
||||
fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
let query = if !probe_network {
|
||||
// processing for first-try and after backoff-timeouts:
|
||||
@@ -768,13 +831,13 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
// - they can be re-executed one time AT_ONCE, but they are not save in the database for later execution
|
||||
if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
|
||||
job_kill_action(context, job.action);
|
||||
context
|
||||
&context
|
||||
.sentbox_thread
|
||||
.clone()
|
||||
.read()
|
||||
.unwrap()
|
||||
.suspend(context);
|
||||
context
|
||||
&context
|
||||
.mvbox_thread
|
||||
.clone()
|
||||
.read()
|
||||
@@ -793,19 +856,13 @@ 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),
|
||||
Action::MoveMsg => job.do_DC_JOB_MOVE_MSG(context),
|
||||
Action::SendMdn => job.do_DC_JOB_SEND(context),
|
||||
Action::ConfigureImap => dc_job_do_DC_JOB_CONFIGURE_IMAP(context),
|
||||
Action::ImexImap => match job_do_DC_JOB_IMEX_IMAP(context, &job) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
error!(context, "{}", err);
|
||||
}
|
||||
},
|
||||
Action::ConfigureImap => unsafe { dc_job_do_DC_JOB_CONFIGURE_IMAP(context, &job) },
|
||||
Action::ImexImap => unsafe { dc_job_do_DC_JOB_IMEX_IMAP(context, &job) },
|
||||
Action::MaybeSendLocations => {
|
||||
location::job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context, &job)
|
||||
}
|
||||
@@ -879,11 +936,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(),
|
||||
);
|
||||
dc_set_msg_failed(context, job.foreign_id, job.pending_error.as_ref());
|
||||
}
|
||||
job.delete(context);
|
||||
}
|
||||
@@ -904,7 +957,8 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
#[allow(non_snake_case)]
|
||||
fn get_backoff_time_offset(c_tries: libc::c_int) -> i64 {
|
||||
// results in ~3 weeks for the last backoff timespan
|
||||
let N = 2_i32.pow((c_tries - 1) as u32) * 60;
|
||||
let mut N = 2_i32.pow((c_tries - 1) as u32);
|
||||
N = N * 60;
|
||||
let mut rng = thread_rng();
|
||||
let n: i32 = rng.gen();
|
||||
let mut seconds = n % (N + 1);
|
||||
@@ -926,7 +980,7 @@ fn suspend_smtp_thread(context: &Context, suspend: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
|
||||
fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
|
||||
let ret_connected = dc_connect_to_configured_imap(context, inbox);
|
||||
if 0 != ret_connected {
|
||||
inbox.set_watch_folder("INBOX".into());
|
||||
@@ -934,44 +988,62 @@ pub fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
|
||||
ret_connected
|
||||
}
|
||||
|
||||
fn send_mdn(context: &Context, msg_id: MsgId) -> Result<(), Error> {
|
||||
let mut mimefactory = MimeFactory::load_mdn(context, msg_id)?;
|
||||
unsafe { mimefactory.render()? };
|
||||
add_smtp_job(context, Action::SendMdn, &mut mimefactory)?;
|
||||
|
||||
Ok(())
|
||||
fn send_mdn(context: &Context, msg_id: u32) {
|
||||
if let Ok(mut mimefactory) = unsafe { dc_mimefactory_load_mdn(context, msg_id) } {
|
||||
if unsafe { dc_mimefactory_render(context, &mut mimefactory) } {
|
||||
add_smtp_job(context, Action::SendMdn, &mut mimefactory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn add_smtp_job(context: &Context, action: Action, mimefactory: &MimeFactory) -> Result<(), Error> {
|
||||
ensure!(
|
||||
!mimefactory.recipients_addr.is_empty(),
|
||||
"no recipients for smtp job set"
|
||||
);
|
||||
fn add_smtp_job(context: &Context, action: Action, mimefactory: &dc_mimefactory_t) -> libc::c_int {
|
||||
let mut success: libc::c_int = 0i32;
|
||||
let mut recipients: *mut libc::c_char = ptr::null_mut();
|
||||
let mut param = Params::new();
|
||||
let path_filename =
|
||||
dc_get_fine_path_filename(context, "$BLOBDIR", as_str(mimefactory.rfc724_mid));
|
||||
let bytes = unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
(*mimefactory.out).str_0 as *const u8,
|
||||
(*mimefactory.out).len,
|
||||
)
|
||||
};
|
||||
let blob = BlobObject::create(context, &mimefactory.rfc724_mid, bytes)?;
|
||||
let recipients = mimefactory.recipients_addr.join("\x1e");
|
||||
param.set(Param::File, blob.as_name());
|
||||
param.set(Param::Recipients, &recipients);
|
||||
job_add(
|
||||
context,
|
||||
action,
|
||||
(if mimefactory.loaded == Loaded::Message {
|
||||
mimefactory.msg.id.to_u32() as i32
|
||||
} else {
|
||||
0
|
||||
}) as libc::c_int,
|
||||
param,
|
||||
0,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
if !dc_write_file(context, &path_filename, bytes) {
|
||||
error!(
|
||||
context,
|
||||
"Could not write message <{}> to \"{}\".",
|
||||
to_string(mimefactory.rfc724_mid),
|
||||
path_filename.display(),
|
||||
);
|
||||
} else {
|
||||
recipients = unsafe {
|
||||
dc_str_from_clist(
|
||||
mimefactory.recipients_addr,
|
||||
b"\x1e\x00" as *const u8 as *const libc::c_char,
|
||||
)
|
||||
};
|
||||
param.set(Param::File, path_filename.to_string_lossy());
|
||||
param.set(Param::Recipients, as_str(recipients));
|
||||
job_add(
|
||||
context,
|
||||
action,
|
||||
(if mimefactory.loaded as libc::c_uint
|
||||
== DC_MF_MSG_LOADED as libc::c_int as libc::c_uint
|
||||
{
|
||||
mimefactory.msg.id
|
||||
} else {
|
||||
0
|
||||
}) as libc::c_int,
|
||||
param,
|
||||
0,
|
||||
);
|
||||
success = 1;
|
||||
}
|
||||
unsafe {
|
||||
free(recipients.cast());
|
||||
}
|
||||
success
|
||||
}
|
||||
|
||||
pub fn job_add(
|
||||
@@ -1022,9 +1094,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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -116,14 +116,14 @@ impl JobThread {
|
||||
if ret_connected {
|
||||
if context
|
||||
.sql
|
||||
.get_raw_config_int(context, "folders_configured")
|
||||
.get_config_int(context, "folders_configured")
|
||||
.unwrap_or_default()
|
||||
< 3
|
||||
{
|
||||
self.imap.configure_folders(context, 0x1);
|
||||
}
|
||||
|
||||
if let Some(mvbox_name) = context.sql.get_raw_config(context, self.folder_config_name) {
|
||||
if let Some(mvbox_name) = context.sql.get_config(context, self.folder_config_name) {
|
||||
self.imap.set_watch_folder(mvbox_name);
|
||||
} else {
|
||||
self.imap.disconnect(context);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
31
src/key.rs
31
src/key.rs
@@ -2,6 +2,7 @@ use std::collections::BTreeMap;
|
||||
use std::io::Cursor;
|
||||
use std::path::Path;
|
||||
|
||||
use libc;
|
||||
use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey};
|
||||
use pgp::ser::Serialize;
|
||||
use pgp::types::{KeyTrait, SecretKeyTrait};
|
||||
@@ -163,8 +164,8 @@ impl Key {
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
match self {
|
||||
Key::Public(k) => k.to_bytes().unwrap_or_default(),
|
||||
Key::Secret(k) => k.to_bytes().unwrap_or_default(),
|
||||
Key::Public(k) => k.to_bytes().unwrap(),
|
||||
Key::Secret(k) => k.to_bytes().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,15 +181,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()
|
||||
}
|
||||
|
||||
@@ -218,10 +219,10 @@ impl Key {
|
||||
let file_content = self.to_asc(None).into_bytes();
|
||||
|
||||
if dc_write_file(context, &file, &file_content) {
|
||||
true
|
||||
return true;
|
||||
} else {
|
||||
error!(context, "Cannot write key to {}", file.as_ref().display());
|
||||
false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,14 +254,14 @@ pub fn dc_key_save_self_keypair(
|
||||
public_key: &Key,
|
||||
private_key: &Key,
|
||||
addr: impl AsRef<str>,
|
||||
is_default: bool,
|
||||
is_default: libc::c_int,
|
||||
sql: &Sql,
|
||||
) -> bool {
|
||||
sql::execute(
|
||||
context,
|
||||
sql,
|
||||
"INSERT INTO keypairs (addr, is_default, public_key, private_key, created) VALUES (?,?,?,?,?);",
|
||||
params![addr.as_ref(), is_default as i32, public_key.to_bytes(), private_key.to_bytes(), time()],
|
||||
params![addr.as_ref(), is_default, public_key.to_bytes(), private_key.to_bytes(), time()],
|
||||
).is_ok()
|
||||
}
|
||||
|
||||
@@ -381,7 +382,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
||||
#[test]
|
||||
#[ignore] // is too expensive
|
||||
fn test_from_slice_roundtrip() {
|
||||
let (public_key, private_key) = crate::pgp::create_keypair("hello").unwrap();
|
||||
let (public_key, private_key) = crate::pgp::dc_pgp_create_keypair("hello").unwrap();
|
||||
|
||||
let binary = public_key.to_bytes();
|
||||
let public_key2 = Key::from_slice(&binary, KeyType::Public).expect("invalid public key");
|
||||
@@ -416,7 +417,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
||||
#[test]
|
||||
#[ignore] // is too expensive
|
||||
fn test_ascii_roundtrip() {
|
||||
let (public_key, private_key) = crate::pgp::create_keypair("hello").unwrap();
|
||||
let (public_key, private_key) = crate::pgp::dc_pgp_create_keypair("hello").unwrap();
|
||||
|
||||
let s = public_key.to_armored_string(None).unwrap();
|
||||
let (public_key2, _) =
|
||||
|
||||
16
src/lib.rs
16
src/lib.rs
@@ -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,36 +39,35 @@ pub mod contact;
|
||||
pub mod context;
|
||||
mod e2ee;
|
||||
mod imap;
|
||||
pub mod imex;
|
||||
pub mod job;
|
||||
mod job_thread;
|
||||
pub mod key;
|
||||
pub mod keyring;
|
||||
pub mod location;
|
||||
mod login_param;
|
||||
pub mod lot;
|
||||
pub mod message;
|
||||
mod mimefactory;
|
||||
pub mod oauth2;
|
||||
mod param;
|
||||
pub mod peerstate;
|
||||
pub mod pgp;
|
||||
pub mod qr;
|
||||
pub mod securejoin;
|
||||
mod smtp;
|
||||
pub mod sql;
|
||||
pub mod stock;
|
||||
mod token;
|
||||
#[macro_use]
|
||||
mod wrapmime;
|
||||
mod stock;
|
||||
pub mod x;
|
||||
|
||||
pub mod dc_array;
|
||||
mod dc_dehtml;
|
||||
pub mod dc_imex;
|
||||
mod dc_mimefactory;
|
||||
pub mod dc_mimeparser;
|
||||
pub mod dc_receive_imf;
|
||||
mod dc_simplify;
|
||||
mod dc_strencode;
|
||||
pub mod dc_tools;
|
||||
mod login_param;
|
||||
pub mod securejoin;
|
||||
mod token;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_utils;
|
||||
|
||||
108
src/location.rs
108
src/location.rs
@@ -3,15 +3,13 @@ use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::chat;
|
||||
use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
use crate::context::*;
|
||||
use crate::dc_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::*;
|
||||
use crate::param::*;
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
@@ -62,11 +60,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() <= (1 * 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 +194,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 +214,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 = dc_msg_new(Viewtype::Text);
|
||||
msg.text =
|
||||
Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0));
|
||||
msg.param.set_cmd(SystemMessage::LocationStreamingEnabled);
|
||||
chat::send_msg(context, chat_id, &mut msg).unwrap_or_default();
|
||||
msg.param.set_int(Param::Cmd, 8);
|
||||
chat::send_msg(context, chat_id, &mut msg).unwrap();
|
||||
} else if 0 == seconds && is_sending_locations_before {
|
||||
let stock_str =
|
||||
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
||||
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 +240,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 +256,9 @@ pub fn is_sending_locations_to_chat(context: &Context, chat_id: u32) -> bool {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> bool {
|
||||
pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> libc::c_int {
|
||||
if latitude == 0.0 && longitude == 0.0 {
|
||||
return true;
|
||||
return 1;
|
||||
}
|
||||
let mut continue_streaming = false;
|
||||
|
||||
@@ -275,7 +278,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 +287,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(
|
||||
@@ -356,7 +359,7 @@ pub fn get_range(
|
||||
}
|
||||
|
||||
fn is_marker(txt: &str) -> bool {
|
||||
txt.len() == 1 && !txt.starts_with(' ')
|
||||
txt.len() == 1 && txt.chars().next().unwrap() != ' '
|
||||
}
|
||||
|
||||
pub fn delete_all(context: &Context) -> Result<(), Error> {
|
||||
@@ -366,10 +369,14 @@ 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
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.sql
|
||||
.get_config(context, "configured_addr")
|
||||
.unwrap_or_default();
|
||||
|
||||
let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row(
|
||||
@@ -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.
|
||||
@@ -605,9 +601,9 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
|
||||
// the easiest way to determine this, is to check for an empty message queue.
|
||||
// (might not be 100%, however, as positions are sent combined later
|
||||
// and dc_set_location() is typically called periodically, this is ok)
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
let mut msg = dc_msg_new(Viewtype::Text);
|
||||
msg.hidden = true;
|
||||
msg.param.set_cmd(SystemMessage::LocationOnly);
|
||||
msg.param.set_int(Param::Cmd, 9);
|
||||
Some((chat_id, msg))
|
||||
}
|
||||
})
|
||||
@@ -619,11 +615,11 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
|
||||
|
||||
for (chat_id, mut msg) in msgs.into_iter() {
|
||||
// TODO: better error handling
|
||||
chat::send_msg(context, chat_id as u32, &mut msg).unwrap_or_default();
|
||||
chat::send_msg(context, chat_id as u32, &mut msg).unwrap();
|
||||
}
|
||||
}
|
||||
if 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",);
|
||||
|
||||
@@ -4,22 +4,6 @@ use std::fmt;
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Display, FromPrimitive)]
|
||||
#[repr(i32)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum CertificateChecks {
|
||||
Automatic = 0,
|
||||
Strict = 1,
|
||||
AcceptInvalidHostnames = 2,
|
||||
AcceptInvalidCertificates = 3,
|
||||
}
|
||||
|
||||
impl Default for CertificateChecks {
|
||||
fn default() -> Self {
|
||||
Self::Automatic
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct LoginParam {
|
||||
pub addr: String,
|
||||
@@ -27,14 +11,10 @@ pub struct LoginParam {
|
||||
pub mail_user: String,
|
||||
pub mail_pw: String,
|
||||
pub mail_port: i32,
|
||||
/// IMAP TLS options: whether to allow invalid certificates and/or invalid hostnames
|
||||
pub imap_certificate_checks: CertificateChecks,
|
||||
pub send_server: String,
|
||||
pub send_user: String,
|
||||
pub send_pw: String,
|
||||
pub send_port: i32,
|
||||
/// SMTP TLS options: whether to allow invalid certificates and/or invalid hostnames
|
||||
pub smtp_certificate_checks: CertificateChecks,
|
||||
pub server_flags: i32,
|
||||
}
|
||||
|
||||
@@ -51,53 +31,37 @@ impl LoginParam {
|
||||
|
||||
let key = format!("{}addr", prefix);
|
||||
let addr = sql
|
||||
.get_raw_config(context, key)
|
||||
.get_config(context, key)
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
let key = format!("{}mail_server", prefix);
|
||||
let mail_server = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
let mail_server = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}mail_port", prefix);
|
||||
let mail_port = sql.get_raw_config_int(context, key).unwrap_or_default();
|
||||
let mail_port = sql.get_config_int(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}mail_user", prefix);
|
||||
let mail_user = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
let mail_user = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}mail_pw", prefix);
|
||||
let mail_pw = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}imap_certificate_checks", prefix);
|
||||
let imap_certificate_checks =
|
||||
if let Some(certificate_checks) = sql.get_raw_config_int(context, key) {
|
||||
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
let mail_pw = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_server", prefix);
|
||||
let send_server = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
let send_server = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_port", prefix);
|
||||
let send_port = sql.get_raw_config_int(context, key).unwrap_or_default();
|
||||
let send_port = sql.get_config_int(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_user", prefix);
|
||||
let send_user = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
let send_user = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_pw", prefix);
|
||||
let send_pw = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}smtp_certificate_checks", prefix);
|
||||
let smtp_certificate_checks =
|
||||
if let Some(certificate_checks) = sql.get_raw_config_int(context, key) {
|
||||
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
let send_pw = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}server_flags", prefix);
|
||||
let server_flags = sql.get_raw_config_int(context, key).unwrap_or_default();
|
||||
let server_flags = sql.get_config_int(context, key).unwrap_or_default();
|
||||
|
||||
LoginParam {
|
||||
addr: addr.to_string(),
|
||||
@@ -105,12 +69,10 @@ impl LoginParam {
|
||||
mail_user,
|
||||
mail_pw,
|
||||
mail_port,
|
||||
imap_certificate_checks,
|
||||
send_server,
|
||||
send_user,
|
||||
send_pw,
|
||||
send_port,
|
||||
smtp_certificate_checks,
|
||||
server_flags,
|
||||
}
|
||||
}
|
||||
@@ -129,40 +91,34 @@ impl LoginParam {
|
||||
let sql = &context.sql;
|
||||
|
||||
let key = format!("{}addr", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.addr))?;
|
||||
sql.set_config(context, key, Some(&self.addr))?;
|
||||
|
||||
let key = format!("{}mail_server", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.mail_server))?;
|
||||
sql.set_config(context, key, Some(&self.mail_server))?;
|
||||
|
||||
let key = format!("{}mail_port", prefix);
|
||||
sql.set_raw_config_int(context, key, self.mail_port)?;
|
||||
sql.set_config_int(context, key, self.mail_port)?;
|
||||
|
||||
let key = format!("{}mail_user", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.mail_user))?;
|
||||
sql.set_config(context, key, Some(&self.mail_user))?;
|
||||
|
||||
let key = format!("{}mail_pw", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.mail_pw))?;
|
||||
|
||||
let key = format!("{}imap_certificate_checks", prefix);
|
||||
sql.set_raw_config_int(context, key, self.imap_certificate_checks as i32)?;
|
||||
sql.set_config(context, key, Some(&self.mail_pw))?;
|
||||
|
||||
let key = format!("{}send_server", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.send_server))?;
|
||||
sql.set_config(context, key, Some(&self.send_server))?;
|
||||
|
||||
let key = format!("{}send_port", prefix);
|
||||
sql.set_raw_config_int(context, key, self.send_port)?;
|
||||
sql.set_config_int(context, key, self.send_port)?;
|
||||
|
||||
let key = format!("{}send_user", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.send_user))?;
|
||||
sql.set_config(context, key, Some(&self.send_user))?;
|
||||
|
||||
let key = format!("{}send_pw", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.send_pw))?;
|
||||
|
||||
let key = format!("{}smtp_certificate_checks", prefix);
|
||||
sql.set_raw_config_int(context, key, self.smtp_certificate_checks as i32)?;
|
||||
sql.set_config(context, key, Some(&self.send_pw))?;
|
||||
|
||||
let key = format!("{}server_flags", prefix);
|
||||
sql.set_raw_config_int(context, key, self.server_flags)?;
|
||||
sql.set_config_int(context, key, self.server_flags)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -177,18 +133,16 @@ impl fmt::Display for LoginParam {
|
||||
|
||||
write!(
|
||||
f,
|
||||
"{} imap:{}:{}:{}:{}:cert_{} smtp:{}:{}:{}:{}:cert_{} {}",
|
||||
"{} {}:{}:{}:{} {}:{}:{}:{} {}",
|
||||
unset_empty(&self.addr),
|
||||
unset_empty(&self.mail_user),
|
||||
if !self.mail_pw.is_empty() { pw } else { unset },
|
||||
unset_empty(&self.mail_server),
|
||||
self.mail_port,
|
||||
self.imap_certificate_checks,
|
||||
unset_empty(&self.send_user),
|
||||
if !self.send_pw.is_empty() { pw } else { unset },
|
||||
unset_empty(&self.send_server),
|
||||
self.send_port,
|
||||
self.smtp_certificate_checks,
|
||||
flags_readable,
|
||||
)
|
||||
}
|
||||
@@ -250,41 +204,3 @@ fn get_readable_flags(flags: i32) -> String {
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub fn dc_build_tls(
|
||||
certificate_checks: CertificateChecks,
|
||||
) -> Result<native_tls::TlsConnector, native_tls::Error> {
|
||||
let mut tls_builder = native_tls::TlsConnector::builder();
|
||||
match certificate_checks {
|
||||
CertificateChecks::Automatic => {
|
||||
// Same as AcceptInvalidCertificates for now.
|
||||
// TODO: use provider database when it becomes available
|
||||
tls_builder
|
||||
.danger_accept_invalid_hostnames(true)
|
||||
.danger_accept_invalid_certs(true)
|
||||
}
|
||||
CertificateChecks::Strict => &mut tls_builder,
|
||||
CertificateChecks::AcceptInvalidHostnames => {
|
||||
tls_builder.danger_accept_invalid_hostnames(true)
|
||||
}
|
||||
CertificateChecks::AcceptInvalidCertificates => tls_builder
|
||||
.danger_accept_invalid_hostnames(true)
|
||||
.danger_accept_invalid_certs(true),
|
||||
}
|
||||
.build()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_certificate_checks_display() {
|
||||
use std::string::ToString;
|
||||
|
||||
assert_eq!(
|
||||
"accept_invalid_hostnames".to_string(),
|
||||
CertificateChecks::AcceptInvalidHostnames.to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
1550
src/message.rs
1550
src/message.rs
File diff suppressed because it is too large
Load Diff
@@ -1,915 +0,0 @@
|
||||
use std::ptr;
|
||||
|
||||
use chrono::TimeZone;
|
||||
use mmime::clist::*;
|
||||
use mmime::mailimf::types::*;
|
||||
use mmime::mailimf::types_helper::*;
|
||||
use mmime::mailmime::disposition::*;
|
||||
use mmime::mailmime::types::*;
|
||||
use mmime::mailmime::types_helper::*;
|
||||
use mmime::mailmime::write_mem::*;
|
||||
use mmime::mmapstring::*;
|
||||
use mmime::other::*;
|
||||
|
||||
use crate::chat::{self, Chat};
|
||||
use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::{get_version_str, Context};
|
||||
use crate::dc_mimeparser::SystemMessage;
|
||||
use crate::dc_strencode::*;
|
||||
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;
|
||||
use crate::wrapmime;
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum Loaded {
|
||||
Nothing,
|
||||
Message,
|
||||
MDN, // TODO: invent more descriptive name
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MimeFactory<'a> {
|
||||
pub from_addr: String,
|
||||
pub from_displayname: String,
|
||||
pub selfstatus: String,
|
||||
pub recipients_names: Vec<String>,
|
||||
pub recipients_addr: Vec<String>,
|
||||
pub timestamp: i64,
|
||||
pub rfc724_mid: String,
|
||||
pub loaded: Loaded,
|
||||
pub msg: Message,
|
||||
pub chat: Option<Chat>,
|
||||
pub increation: bool,
|
||||
pub in_reply_to: String,
|
||||
pub references: String,
|
||||
pub req_mdn: bool,
|
||||
pub out: *mut MMAPString,
|
||||
pub out_encrypted: bool,
|
||||
pub out_gossiped: bool,
|
||||
pub out_last_added_location_id: u32,
|
||||
pub context: &'a Context,
|
||||
}
|
||||
|
||||
impl<'a> MimeFactory<'a> {
|
||||
fn new(context: &'a Context, msg: Message) -> Self {
|
||||
MimeFactory {
|
||||
from_addr: context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_default(),
|
||||
from_displayname: context.get_config(Config::Displayname).unwrap_or_default(),
|
||||
selfstatus: context
|
||||
.get_config(Config::Selfstatus)
|
||||
.unwrap_or_else(|| context.stock_str(StockMessage::StatusLine).to_string()),
|
||||
recipients_names: Vec::with_capacity(5),
|
||||
recipients_addr: Vec::with_capacity(5),
|
||||
timestamp: 0,
|
||||
rfc724_mid: String::default(),
|
||||
loaded: Loaded::Nothing,
|
||||
msg,
|
||||
chat: None,
|
||||
increation: false,
|
||||
in_reply_to: String::default(),
|
||||
references: String::default(),
|
||||
req_mdn: false,
|
||||
out: ptr::null_mut(),
|
||||
out_encrypted: false,
|
||||
out_gossiped: false,
|
||||
out_last_added_location_id: 0,
|
||||
context,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize_mime_message(
|
||||
&mut self,
|
||||
message: *mut Mailmime,
|
||||
encrypted: bool,
|
||||
gossiped: bool,
|
||||
) -> Result<(), Error> {
|
||||
unsafe {
|
||||
assert!(self.out.is_null()); // guard against double-calls
|
||||
self.out = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
|
||||
let mut col: libc::c_int = 0;
|
||||
ensure_eq!(
|
||||
mailmime_write_mem(self.out, &mut col, message),
|
||||
0,
|
||||
"mem-error"
|
||||
);
|
||||
}
|
||||
self.out_encrypted = encrypted;
|
||||
self.out_gossiped = encrypted && gossiped;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_mdn(context: &'a Context, msg_id: MsgId) -> Result<MimeFactory, Error> {
|
||||
if !context.get_config_bool(Config::MdnsEnabled) {
|
||||
// MDNs not enabled - check this is late, in the job. the
|
||||
// user may have changed its choice while offline ...
|
||||
bail!("MDNs meanwhile disabled")
|
||||
}
|
||||
|
||||
let msg = Message::load_from_db(context, msg_id)?;
|
||||
let mut factory = MimeFactory::new(context, msg);
|
||||
let contact = Contact::load_from_db(factory.context, factory.msg.from_id)?;
|
||||
|
||||
// Do not send MDNs trash etc.; chats.blocked is already checked by the caller
|
||||
// in dc_markseen_msgs()
|
||||
ensure!(!contact.is_blocked(), "Contact blocked");
|
||||
ensure!(
|
||||
factory.msg.chat_id > DC_CHAT_ID_LAST_SPECIAL,
|
||||
"Invalid chat id"
|
||||
);
|
||||
|
||||
factory
|
||||
.recipients_names
|
||||
.push(contact.get_authname().to_string());
|
||||
factory.recipients_addr.push(contact.get_addr().to_string());
|
||||
factory.timestamp = dc_create_smeared_timestamp(factory.context);
|
||||
factory.rfc724_mid = dc_create_outgoing_rfc724_mid(None, &factory.from_addr);
|
||||
factory.loaded = Loaded::MDN;
|
||||
|
||||
Ok(factory)
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Render a basic email
|
||||
******************************************************************************/
|
||||
// XXX restrict unsafe to parts, introduce wrapmime helpers where appropriate
|
||||
pub unsafe fn render(&mut self) -> Result<(), Error> {
|
||||
if self.loaded == Loaded::Nothing || !self.out.is_null() {
|
||||
bail!("Invalid use of mimefactory-object.");
|
||||
}
|
||||
let context = &self.context;
|
||||
let from = wrapmime::new_mailbox_list(&self.from_displayname, &self.from_addr);
|
||||
|
||||
let to = mailimf_address_list_new_empty();
|
||||
let name_iter = self.recipients_names.iter();
|
||||
let addr_iter = self.recipients_addr.iter();
|
||||
for (name, addr) in name_iter.zip(addr_iter) {
|
||||
mailimf_address_list_add(
|
||||
to,
|
||||
mailimf_address_new(
|
||||
MAILIMF_ADDRESS_MAILBOX as libc::c_int,
|
||||
mailimf_mailbox_new(
|
||||
if !name.is_empty() {
|
||||
dc_encode_header_words(&name).strdup()
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
},
|
||||
addr.strdup(),
|
||||
),
|
||||
ptr::null_mut(),
|
||||
),
|
||||
);
|
||||
}
|
||||
let references_list = if !self.references.is_empty() {
|
||||
dc_str_to_clist(&self.references, " ")
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
};
|
||||
let in_reply_to_list = if !self.in_reply_to.is_empty() {
|
||||
dc_str_to_clist(&self.in_reply_to, " ")
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
};
|
||||
|
||||
let imf_fields = mailimf_fields_new_with_data_all(
|
||||
mailimf_get_date(self.timestamp as i64),
|
||||
from,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
to,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
self.rfc724_mid.strdup(),
|
||||
in_reply_to_list,
|
||||
references_list,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
|
||||
let os_name = &self.context.os_name;
|
||||
let os_part = os_name
|
||||
.as_ref()
|
||||
.map(|s| format!("/{}", s))
|
||||
.unwrap_or_default();
|
||||
let version = get_version_str();
|
||||
let headerval = format!("Delta Chat Core {}{}", version, os_part);
|
||||
|
||||
/* Add a X-Mailer header.
|
||||
This is only informational for debugging and may be removed in the release.
|
||||
We do not rely on this header as it may be removed by MTAs. */
|
||||
wrapmime::new_custom_field(imf_fields, "X-Mailer", &headerval);
|
||||
wrapmime::new_custom_field(imf_fields, "Chat-Version", "1.0");
|
||||
if self.req_mdn {
|
||||
/* we use "Chat-Disposition-Notification-To"
|
||||
because replies to "Disposition-Notification-To" are weird in many cases
|
||||
eg. are just freetext and/or do not follow any standard. */
|
||||
wrapmime::new_custom_field(
|
||||
imf_fields,
|
||||
"Chat-Disposition-Notification-To",
|
||||
&self.from_addr,
|
||||
);
|
||||
}
|
||||
|
||||
let cleanup = |message: *mut Mailmime| {
|
||||
if !message.is_null() {
|
||||
mailmime_free(message);
|
||||
}
|
||||
};
|
||||
let message = mailmime_new_message_data(0 as *mut Mailmime);
|
||||
ensure!(!message.is_null(), "could not create mime message data");
|
||||
|
||||
mailmime_set_imf_fields(message, imf_fields);
|
||||
|
||||
// 1=add Autocrypt-header (needed eg. for handshaking), 2=no Autocrypte-header (used for MDN)
|
||||
let mut e2ee_guaranteed = false;
|
||||
let mut min_verified: libc::c_int = 0;
|
||||
let mut do_gossip = false;
|
||||
let mut grpimage = None;
|
||||
let force_plaintext: libc::c_int;
|
||||
let subject_str = match self.loaded {
|
||||
Loaded::Message => {
|
||||
/* Render a normal message
|
||||
*********************************************************************/
|
||||
let chat = self.chat.as_ref().unwrap();
|
||||
let mut meta_part: *mut Mailmime = ptr::null_mut();
|
||||
let mut placeholdertext = None;
|
||||
|
||||
if chat.typ == Chattype::VerifiedGroup {
|
||||
wrapmime::new_custom_field(imf_fields, "Chat-Verified", "1");
|
||||
force_plaintext = 0;
|
||||
e2ee_guaranteed = true;
|
||||
min_verified = 2
|
||||
} else {
|
||||
force_plaintext = self
|
||||
.msg
|
||||
.param
|
||||
.get_int(Param::ForcePlaintext)
|
||||
.unwrap_or_default();
|
||||
if force_plaintext == 0 {
|
||||
e2ee_guaranteed = self
|
||||
.msg
|
||||
.param
|
||||
.get_int(Param::GuaranteeE2ee)
|
||||
.unwrap_or_default()
|
||||
!= 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* beside key- and member-changes, force re-gossip every 48 hours */
|
||||
if chat.gossiped_timestamp == 0
|
||||
|| (chat.gossiped_timestamp + (2 * 24 * 60 * 60)) < time()
|
||||
{
|
||||
do_gossip = true
|
||||
}
|
||||
|
||||
/* build header etc. */
|
||||
let command = self.msg.param.get_cmd();
|
||||
if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
|
||||
wrapmime::new_custom_field(imf_fields, "Chat-Group-ID", &chat.grpid);
|
||||
|
||||
let encoded = dc_encode_header_words(&chat.name);
|
||||
wrapmime::new_custom_field(imf_fields, "Chat-Group-Name", &encoded);
|
||||
|
||||
match command {
|
||||
SystemMessage::MemberRemovedFromGroup => {
|
||||
let email_to_remove =
|
||||
self.msg.param.get(Param::Arg).unwrap_or_default();
|
||||
if !email_to_remove.is_empty() {
|
||||
wrapmime::new_custom_field(
|
||||
imf_fields,
|
||||
"Chat-Group-Member-Removed",
|
||||
&email_to_remove,
|
||||
);
|
||||
}
|
||||
}
|
||||
SystemMessage::MemberAddedToGroup => {
|
||||
let msg = &self.msg;
|
||||
do_gossip = true;
|
||||
let email_to_add = msg.param.get(Param::Arg).unwrap_or_default();
|
||||
if !email_to_add.is_empty() {
|
||||
wrapmime::new_custom_field(
|
||||
imf_fields,
|
||||
"Chat-Group-Member-Added",
|
||||
&email_to_add,
|
||||
);
|
||||
grpimage = chat.param.get(Param::ProfileImage);
|
||||
}
|
||||
if 0 != msg.param.get_int(Param::Arg2).unwrap_or_default() & 0x1 {
|
||||
info!(
|
||||
context,
|
||||
"sending secure-join message \'{}\' >>>>>>>>>>>>>>>>>>>>>>>>>",
|
||||
"vg-member-added",
|
||||
);
|
||||
wrapmime::new_custom_field(
|
||||
imf_fields,
|
||||
"Secure-Join",
|
||||
"vg-member-added",
|
||||
);
|
||||
}
|
||||
}
|
||||
SystemMessage::GroupNameChanged => {
|
||||
let msg = &self.msg;
|
||||
let value_to_add = msg.param.get(Param::Arg).unwrap_or_default();
|
||||
|
||||
wrapmime::new_custom_field(
|
||||
imf_fields,
|
||||
"Chat-Group-Name-Changed",
|
||||
&value_to_add,
|
||||
);
|
||||
}
|
||||
SystemMessage::GroupImageChanged => {
|
||||
let msg = &self.msg;
|
||||
grpimage = msg.param.get(Param::Arg);
|
||||
if grpimage.is_none() {
|
||||
wrapmime::new_custom_field(imf_fields, "Chat-Group-Image", "0");
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match command {
|
||||
SystemMessage::LocationStreamingEnabled => {
|
||||
wrapmime::new_custom_field(
|
||||
imf_fields,
|
||||
"Chat-Content",
|
||||
"location-streaming-enabled",
|
||||
);
|
||||
}
|
||||
SystemMessage::AutocryptSetupMessage => {
|
||||
wrapmime::new_custom_field(imf_fields, "Autocrypt-Setup-Message", "v1");
|
||||
placeholdertext = Some(
|
||||
self.context
|
||||
.stock_str(StockMessage::AcSetupMsgBody)
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
SystemMessage::SecurejoinMessage => {
|
||||
let msg = &self.msg;
|
||||
let step = msg.param.get(Param::Arg).unwrap_or_default();
|
||||
if !step.is_empty() {
|
||||
info!(
|
||||
context,
|
||||
"sending secure-join message \'{}\' >>>>>>>>>>>>>>>>>>>>>>>>>",
|
||||
step,
|
||||
);
|
||||
wrapmime::new_custom_field(imf_fields, "Secure-Join", &step);
|
||||
let param2 = msg.param.get(Param::Arg2).unwrap_or_default();
|
||||
if !param2.is_empty() {
|
||||
wrapmime::new_custom_field(
|
||||
imf_fields,
|
||||
if step == "vg-request-with-auth"
|
||||
|| step == "vc-request-with-auth"
|
||||
{
|
||||
"Secure-Join-Auth"
|
||||
} else {
|
||||
"Secure-Join-Invitenumber"
|
||||
},
|
||||
param2,
|
||||
)
|
||||
}
|
||||
let fingerprint = msg.param.get(Param::Arg3).unwrap_or_default();
|
||||
if !fingerprint.is_empty() {
|
||||
wrapmime::new_custom_field(
|
||||
imf_fields,
|
||||
"Secure-Join-Fingerprint",
|
||||
&fingerprint,
|
||||
);
|
||||
}
|
||||
if let Some(id) = msg.param.get(Param::Arg4) {
|
||||
wrapmime::new_custom_field(imf_fields, "Secure-Join-Group", &id);
|
||||
};
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(grpimage) = grpimage {
|
||||
info!(self.context, "setting group image '{}'", grpimage);
|
||||
let mut meta = Message::default();
|
||||
meta.type_0 = Viewtype::Image;
|
||||
meta.param.set(Param::File, grpimage);
|
||||
|
||||
let res = build_body_file(context, &meta, "group-image")?;
|
||||
meta_part = res.0;
|
||||
let filename_as_sent = res.1;
|
||||
if !meta_part.is_null() {
|
||||
wrapmime::new_custom_field(
|
||||
imf_fields,
|
||||
"Chat-Group-Image",
|
||||
&filename_as_sent,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if self.msg.type_0 == Viewtype::Sticker {
|
||||
wrapmime::new_custom_field(imf_fields, "Chat-Content", "sticker");
|
||||
}
|
||||
|
||||
if self.msg.type_0 == Viewtype::Voice
|
||||
|| self.msg.type_0 == Viewtype::Audio
|
||||
|| self.msg.type_0 == Viewtype::Video
|
||||
{
|
||||
if self.msg.type_0 == Viewtype::Voice {
|
||||
wrapmime::new_custom_field(imf_fields, "Chat-Voice-Message", "1");
|
||||
}
|
||||
let duration_ms = self.msg.param.get_int(Param::Duration).unwrap_or_default();
|
||||
if duration_ms > 0 {
|
||||
let dur = duration_ms.to_string();
|
||||
wrapmime::new_custom_field(imf_fields, "Chat-Duration", &dur);
|
||||
}
|
||||
}
|
||||
|
||||
/* add text part - we even add empty text and force a MIME-multipart-message as:
|
||||
- some Apps have problems with Non-text in the main part (eg. "Mail" from stock Android)
|
||||
- we can add "forward hints" this way
|
||||
- it looks better */
|
||||
let afwd_email = self.msg.param.exists(Param::Forwarded);
|
||||
let fwdhint = if afwd_email {
|
||||
Some(
|
||||
"---------- Forwarded message ----------\r\nFrom: Delta Chat\r\n\r\n"
|
||||
.to_string(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let final_text = {
|
||||
if let Some(ref text) = placeholdertext {
|
||||
text
|
||||
} else if let Some(ref text) = self.msg.text {
|
||||
text
|
||||
} else {
|
||||
""
|
||||
}
|
||||
};
|
||||
|
||||
let footer = &self.selfstatus;
|
||||
let message_text = format!(
|
||||
"{}{}{}{}{}",
|
||||
fwdhint.unwrap_or_default(),
|
||||
&final_text,
|
||||
if !final_text.is_empty() && !footer.is_empty() {
|
||||
"\r\n\r\n"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
if !footer.is_empty() { "-- \r\n" } else { "" },
|
||||
footer
|
||||
);
|
||||
let text_part = wrapmime::build_body_text(&message_text)?;
|
||||
mailmime_smart_add_part(message, text_part);
|
||||
|
||||
/* add attachment part */
|
||||
if chat::msgtype_has_file(self.msg.type_0) {
|
||||
if !is_file_size_okay(context, &self.msg) {
|
||||
cleanup(message);
|
||||
bail!(
|
||||
"Message exceeds the recommended {} MB.",
|
||||
24 * 1024 * 1024 / 4 * 3 / 1000 / 1000,
|
||||
);
|
||||
} else {
|
||||
let (file_part, _) = build_body_file(context, &self.msg, "")?;
|
||||
mailmime_smart_add_part(message, file_part);
|
||||
}
|
||||
}
|
||||
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(
|
||||
self.msg.timestamp_sort,
|
||||
param.get_float(Param::SetLatitude).unwrap_or_default(),
|
||||
param.get_float(Param::SetLongitude).unwrap_or_default(),
|
||||
);
|
||||
wrapmime::add_filename_part(
|
||||
message,
|
||||
"message.kml",
|
||||
"application/vnd.google-earth.kml+xml",
|
||||
&kml_file,
|
||||
)?;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
get_subject(context, self.chat.as_ref(), &mut self.msg, afwd_email)
|
||||
}
|
||||
Loaded::MDN => {
|
||||
/* Render a MDN
|
||||
*********************************************************************/
|
||||
/* RFC 6522, this also requires the `report-type` parameter which is equal
|
||||
to the MIME subtype of the second body part of the multipart/report */
|
||||
let multipart = mailmime_multiple_new(
|
||||
b"multipart/report\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
wrapmime::append_ct_param(
|
||||
(*multipart).mm_content_type,
|
||||
"report-type",
|
||||
"disposition-notification",
|
||||
)?;
|
||||
|
||||
mailmime_add_part(message, multipart);
|
||||
|
||||
/* first body part: always human-readable, always REQUIRED by RFC 6522 */
|
||||
let p1 = if 0
|
||||
!= self
|
||||
.msg
|
||||
.param
|
||||
.get_int(Param::GuaranteeE2ee)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
self.context
|
||||
.stock_str(StockMessage::EncryptedMsg)
|
||||
.into_owned()
|
||||
} else {
|
||||
self.msg.get_summarytext(context, 32)
|
||||
};
|
||||
let p2 = self
|
||||
.context
|
||||
.stock_string_repl_str(StockMessage::ReadRcptMailBody, p1);
|
||||
let message_text = format!("{}\r\n", p2);
|
||||
let human_mime_part = wrapmime::build_body_text(&message_text)?;
|
||||
mailmime_add_part(multipart, human_mime_part);
|
||||
|
||||
/* second body part: machine-readable, always REQUIRED by RFC 6522 */
|
||||
let version = get_version_str();
|
||||
let message_text2 = format!(
|
||||
"Reporting-UA: Delta Chat {}\r\nOriginal-Recipient: rfc822;{}\r\nFinal-Recipient: rfc822;{}\r\nOriginal-Message-ID: <{}>\r\nDisposition: manual-action/MDN-sent-automatically; displayed\r\n",
|
||||
version,
|
||||
self.from_addr,
|
||||
self.from_addr,
|
||||
self.msg.rfc724_mid
|
||||
);
|
||||
|
||||
let content_type_0 =
|
||||
wrapmime::new_content_type("message/disposition-notification")?;
|
||||
let mime_fields_0: *mut mailmime_fields =
|
||||
mailmime_fields_new_encoding(MAILMIME_MECHANISM_8BIT as libc::c_int);
|
||||
let mach_mime_part: *mut Mailmime =
|
||||
mailmime_new_empty(content_type_0, mime_fields_0);
|
||||
wrapmime::set_body_text(mach_mime_part, &message_text2)?;
|
||||
mailmime_add_part(multipart, mach_mime_part);
|
||||
force_plaintext = DC_FP_NO_AUTOCRYPT_HEADER;
|
||||
/* currently, we do not send MDNs encrypted:
|
||||
- in a multi-device-setup that is not set up properly, MDNs would disturb the communication as they
|
||||
are send automatically which may lead to spreading outdated Autocrypt headers.
|
||||
- they do not carry any information but the Message-ID
|
||||
- this save some KB
|
||||
- in older versions, we did not encrypt messages to ourself when they to to SMTP - however, if these messages
|
||||
are forwarded for any reasons (eg. gmail always forwards to IMAP), we have no chance to decrypt them;
|
||||
this issue is fixed with 0.9.4 */
|
||||
let e = self.context.stock_str(StockMessage::ReadRcpt);
|
||||
format!("Chat: {}", e).to_string()
|
||||
}
|
||||
_ => {
|
||||
cleanup(message);
|
||||
bail!("No message loaded.");
|
||||
}
|
||||
};
|
||||
|
||||
/* Create the mime message
|
||||
*************************************************************************/
|
||||
|
||||
mailimf_fields_add(
|
||||
imf_fields,
|
||||
mailimf_field_new(
|
||||
MAILIMF_FIELD_SUBJECT as libc::c_int,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
mailimf_subject_new(dc_encode_header_words(subject_str).strdup()),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
),
|
||||
);
|
||||
|
||||
/*just a pointer into mailmime structure, must not be freed*/
|
||||
let imffields_unprotected = wrapmime::mailmime_find_mailimf_fields(message);
|
||||
ensure!(
|
||||
!imffields_unprotected.is_null(),
|
||||
"could not find mime fields"
|
||||
);
|
||||
|
||||
let mut encrypt_helper = EncryptHelper::new(&context)?;
|
||||
if force_plaintext != DC_FP_NO_AUTOCRYPT_HEADER {
|
||||
// unless determined otherwise we add Autocrypt header
|
||||
let aheader = encrypt_helper.get_aheader().to_string();
|
||||
wrapmime::new_custom_field(imffields_unprotected, "Autocrypt", &aheader);
|
||||
}
|
||||
let finalized = if force_plaintext == 0 {
|
||||
encrypt_helper.try_encrypt(
|
||||
self,
|
||||
e2ee_guaranteed,
|
||||
min_verified,
|
||||
do_gossip,
|
||||
message,
|
||||
imffields_unprotected,
|
||||
)?
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !finalized {
|
||||
self.finalize_mime_message(message, false, false)?;
|
||||
}
|
||||
cleanup(message);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_msg(context: &Context, msg_id: MsgId) -> Result<MimeFactory, Error> {
|
||||
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);
|
||||
factory.chat = Some(chat);
|
||||
|
||||
// just set the chat above
|
||||
let chat = factory.chat.as_ref().unwrap();
|
||||
|
||||
if chat.is_self_talk() {
|
||||
factory
|
||||
.recipients_names
|
||||
.push(factory.from_displayname.to_string());
|
||||
factory.recipients_addr.push(factory.from_addr.to_string());
|
||||
} else {
|
||||
context.sql.query_map(
|
||||
"SELECT c.authname, c.addr \
|
||||
FROM chats_contacts cc \
|
||||
LEFT JOIN contacts c ON cc.contact_id=c.id \
|
||||
WHERE cc.chat_id=? AND cc.contact_id>9;",
|
||||
params![factory.msg.chat_id as i32],
|
||||
|row| {
|
||||
let authname: String = row.get(0)?;
|
||||
let addr: String = row.get(1)?;
|
||||
Ok((authname, addr))
|
||||
},
|
||||
|rows| {
|
||||
for row in rows {
|
||||
let (authname, addr) = row?;
|
||||
if !vec_contains_lowercase(&factory.recipients_addr, &addr) {
|
||||
factory.recipients_addr.push(addr);
|
||||
factory.recipients_names.push(authname);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
|
||||
let command = factory.msg.param.get_cmd();
|
||||
let msg = &factory.msg;
|
||||
|
||||
/* for added members, the list is just fine */
|
||||
if command == SystemMessage::MemberRemovedFromGroup {
|
||||
let email_to_remove = msg.param.get(Param::Arg).unwrap_or_default();
|
||||
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_default();
|
||||
|
||||
if !email_to_remove.is_empty() && email_to_remove != self_addr {
|
||||
if !vec_contains_lowercase(&factory.recipients_addr, &email_to_remove) {
|
||||
factory.recipients_names.push("".to_string());
|
||||
factory.recipients_addr.push(email_to_remove.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
if command != SystemMessage::AutocryptSetupMessage
|
||||
&& command != SystemMessage::SecurejoinMessage
|
||||
&& context.get_config_bool(Config::MdnsEnabled)
|
||||
{
|
||||
factory.req_mdn = true;
|
||||
}
|
||||
}
|
||||
let row = context.sql.query_row(
|
||||
"SELECT mime_in_reply_to, mime_references FROM msgs WHERE id=?",
|
||||
params![factory.msg.id],
|
||||
|row| {
|
||||
let in_reply_to: String = row.get(0)?;
|
||||
let references: String = row.get(1)?;
|
||||
|
||||
Ok((in_reply_to, references))
|
||||
},
|
||||
);
|
||||
match row {
|
||||
Ok((in_reply_to, references)) => {
|
||||
factory.in_reply_to = in_reply_to;
|
||||
factory.references = references;
|
||||
}
|
||||
Err(err) => {
|
||||
error!(
|
||||
context,
|
||||
"mimefactory: failed to load mime_in_reply_to: {:?}", err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
factory.loaded = Loaded::Message;
|
||||
factory.timestamp = factory.msg.timestamp_sort;
|
||||
factory.rfc724_mid = factory.msg.rfc724_mid.clone();
|
||||
factory.increation = factory.msg.is_increation();
|
||||
|
||||
Ok(factory)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for MimeFactory<'a> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if !self.out.is_null() {
|
||||
mmap_string_free(self.out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_subject(
|
||||
context: &Context,
|
||||
chat: Option<&Chat>,
|
||||
msg: &mut Message,
|
||||
afwd_email: bool,
|
||||
) -> String {
|
||||
if chat.is_none() {
|
||||
return String::default();
|
||||
}
|
||||
|
||||
let chat = chat.unwrap();
|
||||
let raw_subject =
|
||||
message::get_summarytext_by_raw(msg.type_0, msg.text.as_ref(), &mut msg.param, 32, context);
|
||||
let fwd = if afwd_email { "Fwd: " } else { "" };
|
||||
|
||||
if msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage {
|
||||
/* do not add the "Chat:" prefix for setup messages */
|
||||
context
|
||||
.stock_str(StockMessage::AcSetupMsgSubject)
|
||||
.into_owned()
|
||||
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
|
||||
format!("Chat: {}: {}{}", chat.name, fwd, raw_subject,)
|
||||
} else {
|
||||
format!("Chat: {}{}", fwd, raw_subject)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn build_body_file(
|
||||
context: &Context,
|
||||
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");
|
||||
|
||||
// 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 {
|
||||
Viewtype::Voice => chrono::Utc
|
||||
.timestamp(msg.timestamp_sort as i64, 0)
|
||||
.format(&format!("voice-message_%Y-%m-%d_%H-%M-%S.{}", &suffix))
|
||||
.to_string(),
|
||||
Viewtype::Image | Viewtype::Gif => format!(
|
||||
"{}.{}",
|
||||
if base_name.is_empty() {
|
||||
"image"
|
||||
} else {
|
||||
base_name
|
||||
},
|
||||
&suffix,
|
||||
),
|
||||
Viewtype::Video => format!("video.{}", &suffix),
|
||||
_ => blob.as_file_name().to_string(),
|
||||
};
|
||||
|
||||
/* 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()) {
|
||||
res.1
|
||||
} else {
|
||||
"application/octet-stream"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let needs_ext = dc_needs_ext_header(&filename_to_send);
|
||||
|
||||
unsafe {
|
||||
/* create mime part, for Content-Disposition, see RFC 2183.
|
||||
`Content-Disposition: attachment` seems not to make a difference to `Content-Disposition: inline` at least on tested Thunderbird and Gma'l in 2017.
|
||||
But I've heard about problems with inline and outl'k, so we just use the attachment-type until we run into other problems ... */
|
||||
let mime_fields = mailmime_fields_new_filename(
|
||||
MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int,
|
||||
if needs_ext {
|
||||
ptr::null_mut()
|
||||
} else {
|
||||
filename_to_send.strdup()
|
||||
},
|
||||
MAILMIME_MECHANISM_BASE64 as libc::c_int,
|
||||
);
|
||||
if needs_ext {
|
||||
for cur_data in (*(*mime_fields).fld_list).into_iter() {
|
||||
let field: *mut mailmime_field = cur_data as *mut _;
|
||||
if (*field).fld_type == MAILMIME_FIELD_DISPOSITION as libc::c_int
|
||||
&& !(*field).fld_data.fld_disposition.is_null()
|
||||
{
|
||||
let file_disposition = (*field).fld_data.fld_disposition;
|
||||
if !file_disposition.is_null() {
|
||||
let parm = mailmime_disposition_parm_new(
|
||||
MAILMIME_DISPOSITION_PARM_PARAMETER as libc::c_int,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
0 as libc::size_t,
|
||||
mailmime_parameter_new(
|
||||
strdup(b"filename*\x00" as *const u8 as *const libc::c_char),
|
||||
dc_encode_ext_header(&filename_to_send).strdup(),
|
||||
),
|
||||
);
|
||||
if !parm.is_null() {
|
||||
clist_insert_after(
|
||||
(*file_disposition).dsp_parms,
|
||||
(*(*file_disposition).dsp_parms).last,
|
||||
parm as *mut libc::c_void,
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let content = wrapmime::new_content_type(&mimetype)?;
|
||||
let filename_encoded = dc_encode_header_words(&filename_to_send);
|
||||
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()?;
|
||||
mailmime_set_body_file(mime_sub, dc_strdup(abs_path.as_ptr()));
|
||||
Ok((mime_sub, filename_to_send))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn vec_contains_lowercase(vec: &[String], part: &str) -> bool {
|
||||
let partlc = part.to_lowercase();
|
||||
for cur in vec.iter() {
|
||||
if cur.to_lowercase() == partlc {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
|
||||
const OAUTH2_GMAIL: Oauth2 = Oauth2 {
|
||||
// see https://developers.google.com/identity/protocols/OAuth2InstalledApp
|
||||
client_id: "959970109878-4mvtgf6feshskf7695nfln6002mom908.apps.googleusercontent.com",
|
||||
get_code: "https://accounts.google.com/o/oauth2/auth?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&response_type=code&scope=https%3A%2F%2Fmail.google.com%2F%20email&access_type=offline",
|
||||
init_token: "https://accounts.google.com/o/oauth2/token?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&code=$CODE&grant_type=authorization_code",
|
||||
@@ -16,7 +15,6 @@ const OAUTH2_GMAIL: Oauth2 = Oauth2 {
|
||||
};
|
||||
|
||||
const OAUTH2_YANDEX: Oauth2 = Oauth2 {
|
||||
// see https://tech.yandex.com/oauth/doc/dg/reference/auto-code-client-docpage/
|
||||
client_id: "c4d0b6735fc8420a816d7e1303469341",
|
||||
get_code: "https://oauth.yandex.com/authorize?client_id=$CLIENT_ID&response_type=code&scope=mail%3Aimap_full%20mail%3Asmtp&force_confirm=true",
|
||||
init_token: "https://oauth.yandex.com/token?grant_type=authorization_code&code=$CODE&client_id=$CLIENT_ID&client_secret=58b8c6e94cf44fbe952da8511955dacf",
|
||||
@@ -52,7 +50,7 @@ pub fn dc_get_oauth2_url(
|
||||
if let Some(oauth2) = Oauth2::from_address(addr) {
|
||||
if context
|
||||
.sql
|
||||
.set_raw_config(
|
||||
.set_config(
|
||||
context,
|
||||
"oauth2_pending_redirect_uri",
|
||||
Some(redirect_uri.as_ref()),
|
||||
@@ -76,26 +74,25 @@ pub fn dc_get_oauth2_access_token(
|
||||
context: &Context,
|
||||
addr: impl AsRef<str>,
|
||||
code: impl AsRef<str>,
|
||||
regenerate: bool,
|
||||
flags: usize,
|
||||
) -> Option<String> {
|
||||
if let Some(oauth2) = Oauth2::from_address(addr) {
|
||||
let lock = context.oauth2_critical.clone();
|
||||
let _l = lock.lock().unwrap();
|
||||
|
||||
// read generated token
|
||||
if !regenerate && !is_expired(context) {
|
||||
let access_token = context.sql.get_raw_config(context, "oauth2_access_token");
|
||||
if 0 == flags & 0x1 && !is_expired(context) {
|
||||
let access_token = context.sql.get_config(context, "oauth2_access_token");
|
||||
if access_token.is_some() {
|
||||
// success
|
||||
return access_token;
|
||||
}
|
||||
}
|
||||
|
||||
// generate new token: build & call auth url
|
||||
let refresh_token = context.sql.get_raw_config(context, "oauth2_refresh_token");
|
||||
let refresh_token = context.sql.get_config(context, "oauth2_refresh_token");
|
||||
let refresh_token_for = context
|
||||
.sql
|
||||
.get_raw_config(context, "oauth2_refresh_token_for")
|
||||
.get_config(context, "oauth2_refresh_token_for")
|
||||
.unwrap_or_else(|| "unset".into());
|
||||
|
||||
let (redirect_uri, token_url, update_redirect_uri_on_success) =
|
||||
@@ -104,7 +101,7 @@ pub fn dc_get_oauth2_access_token(
|
||||
(
|
||||
context
|
||||
.sql
|
||||
.get_raw_config(context, "oauth2_pending_redirect_uri")
|
||||
.get_config(context, "oauth2_pending_redirect_uri")
|
||||
.unwrap_or_else(|| "unset".into()),
|
||||
oauth2.init_token,
|
||||
true,
|
||||
@@ -117,43 +114,20 @@ pub fn dc_get_oauth2_access_token(
|
||||
(
|
||||
context
|
||||
.sql
|
||||
.get_raw_config(context, "oauth2_redirect_uri")
|
||||
.get_config(context, "oauth2_redirect_uri")
|
||||
.unwrap_or_else(|| "unset".into()),
|
||||
oauth2.refresh_token,
|
||||
false,
|
||||
)
|
||||
};
|
||||
|
||||
// to allow easier specification of different configurations,
|
||||
// token_url is in GET-method-format, sth. as https://domain?param1=val1¶m2=val2 -
|
||||
// convert this to POST-format ...
|
||||
let mut parts = token_url.splitn(2, '?');
|
||||
let post_url = parts.next().unwrap_or_default();
|
||||
let post_args = parts.next().unwrap_or_default();
|
||||
let mut post_param = HashMap::new();
|
||||
for key_value_pair in post_args.split('&') {
|
||||
let mut parts = key_value_pair.splitn(2, '=');
|
||||
let key = parts.next().unwrap_or_default();
|
||||
let mut value = parts.next().unwrap_or_default();
|
||||
|
||||
if value == "$CLIENT_ID" {
|
||||
value = oauth2.client_id;
|
||||
} else if value == "$REDIRECT_URI" {
|
||||
value = &redirect_uri;
|
||||
} else if value == "$CODE" {
|
||||
value = code.as_ref();
|
||||
} else if value == "$REFRESH_TOKEN" && refresh_token.is_some() {
|
||||
value = refresh_token.as_ref().unwrap();
|
||||
}
|
||||
|
||||
post_param.insert(key, value);
|
||||
let mut token_url = replace_in_uri(&token_url, "$CLIENT_ID", oauth2.client_id);
|
||||
token_url = replace_in_uri(&token_url, "$REDIRECT_URI", &redirect_uri);
|
||||
token_url = replace_in_uri(&token_url, "$CODE", code.as_ref());
|
||||
if let Some(ref token) = refresh_token {
|
||||
token_url = replace_in_uri(&token_url, "$REFRESH_TOKEN", token);
|
||||
}
|
||||
|
||||
// ... and POST
|
||||
let response = reqwest::Client::new()
|
||||
.post(post_url)
|
||||
.form(&post_param)
|
||||
.send();
|
||||
let response = reqwest::Client::new().post(&token_url).send();
|
||||
if response.is_err() {
|
||||
warn!(
|
||||
context,
|
||||
@@ -165,14 +139,13 @@ pub fn dc_get_oauth2_access_token(
|
||||
if !response.status().is_success() {
|
||||
warn!(
|
||||
context,
|
||||
"Unsuccessful response when calling OAuth2 at {}: {:?}",
|
||||
"Error calling OAuth2 at {}: {:?}",
|
||||
token_url,
|
||||
response.status()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
// generate new token: parse returned json
|
||||
let parsed: reqwest::Result<Response> = response.json();
|
||||
if parsed.is_err() {
|
||||
warn!(
|
||||
@@ -182,17 +155,15 @@ pub fn dc_get_oauth2_access_token(
|
||||
return None;
|
||||
}
|
||||
println!("response: {:?}", &parsed);
|
||||
|
||||
// update refresh_token if given, typically on the first round, but we update it later as well.
|
||||
let response = parsed.unwrap();
|
||||
if let Some(ref token) = response.refresh_token {
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(context, "oauth2_refresh_token", Some(token))
|
||||
.set_config(context, "oauth2_refresh_token", Some(token))
|
||||
.ok();
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(context, "oauth2_refresh_token_for", Some(code.as_ref()))
|
||||
.set_config(context, "oauth2_refresh_token_for", Some(code.as_ref()))
|
||||
.ok();
|
||||
}
|
||||
|
||||
@@ -201,7 +172,7 @@ pub fn dc_get_oauth2_access_token(
|
||||
if let Some(ref token) = response.access_token {
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(context, "oauth2_access_token", Some(token))
|
||||
.set_config(context, "oauth2_access_token", Some(token))
|
||||
.ok();
|
||||
let expires_in = response
|
||||
.expires_in
|
||||
@@ -210,13 +181,13 @@ pub fn dc_get_oauth2_access_token(
|
||||
.unwrap_or_else(|| 0);
|
||||
context
|
||||
.sql
|
||||
.set_raw_config_int64(context, "oauth2_timestamp_expires", expires_in)
|
||||
.set_config_int64(context, "oauth2_timestamp_expires", expires_in)
|
||||
.ok();
|
||||
|
||||
if update_redirect_uri_on_success {
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref()))
|
||||
.set_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref()))
|
||||
.ok();
|
||||
}
|
||||
} else {
|
||||
@@ -236,16 +207,21 @@ pub fn dc_get_oauth2_addr(
|
||||
addr: impl AsRef<str>,
|
||||
code: impl AsRef<str>,
|
||||
) -> Option<String> {
|
||||
let oauth2 = Oauth2::from_address(addr.as_ref())?;
|
||||
oauth2.get_userinfo?;
|
||||
let oauth2 = Oauth2::from_address(addr.as_ref());
|
||||
if oauth2.is_none() {
|
||||
return None;
|
||||
}
|
||||
let oauth2 = oauth2.unwrap();
|
||||
if oauth2.get_userinfo.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(access_token) =
|
||||
dc_get_oauth2_access_token(context, addr.as_ref(), code.as_ref(), false)
|
||||
if let Some(access_token) = dc_get_oauth2_access_token(context, addr.as_ref(), code.as_ref(), 0)
|
||||
{
|
||||
let addr_out = oauth2.get_addr(context, access_token);
|
||||
if addr_out.is_none() {
|
||||
// regenerate
|
||||
if let Some(access_token) = dc_get_oauth2_access_token(context, addr, code, true) {
|
||||
if let Some(access_token) = dc_get_oauth2_access_token(context, addr, code, 0x1) {
|
||||
oauth2.get_addr(context, access_token)
|
||||
} else {
|
||||
None
|
||||
@@ -297,7 +273,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 +282,11 @@ impl Oauth2 {
|
||||
return None;
|
||||
}
|
||||
if let Ok(response) = parsed {
|
||||
// serde_json::Value.as_str() removes the quotes of json-strings
|
||||
let addr = response.get("email");
|
||||
if addr.is_none() {
|
||||
warn!(context, "E-mail missing in userinfo.");
|
||||
return None;
|
||||
}
|
||||
let addr = addr.unwrap().as_str();
|
||||
|
||||
addr.map(|addr| addr.to_string())
|
||||
} else {
|
||||
warn!(context, "Failed to parse userinfo.");
|
||||
@@ -324,7 +298,7 @@ impl Oauth2 {
|
||||
fn is_expired(context: &Context) -> bool {
|
||||
let expire_timestamp = context
|
||||
.sql
|
||||
.get_raw_config_int64(context, "oauth2_timestamp_expires")
|
||||
.get_config_int64(context, "oauth2_timestamp_expires")
|
||||
.unwrap_or_default();
|
||||
|
||||
if expire_timestamp <= 0 {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user