Compare commits
30 Commits
less_noise
...
hpk-gh-pyt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e454ede4c | ||
|
|
029f60df27 | ||
|
|
20c82b324a | ||
|
|
e7ebb40cd1 | ||
|
|
bca8094fcb | ||
|
|
7ab5d36b5b | ||
|
|
58ad14d9c3 | ||
|
|
e539bddc3b | ||
|
|
2fc1d21959 | ||
|
|
b43d9d2ffe | ||
|
|
5b73951b9b | ||
|
|
8595b92fcf | ||
|
|
6054b90975 | ||
|
|
b8427ab56e | ||
|
|
02f72eea61 | ||
|
|
a5a20078f0 | ||
|
|
7383094b33 | ||
|
|
e225a6fb17 | ||
|
|
6bdc207277 | ||
|
|
101141c67a | ||
|
|
d07afe5bd6 | ||
|
|
00e22d4339 | ||
|
|
91c8f48c21 | ||
|
|
3d76d21925 | ||
|
|
3349c0e9dc | ||
|
|
7b35104b83 | ||
|
|
22b6e8f6e2 | ||
|
|
ad118aa0df | ||
|
|
9044b80b9f | ||
|
|
b948e973c5 |
@@ -36,6 +36,12 @@ jobs:
|
|||||||
executor: default
|
executor: default
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
|
- run:
|
||||||
|
name: Update submodules
|
||||||
|
command: git submodule update --init --recursive
|
||||||
|
- run:
|
||||||
|
name: Calculate dependencies
|
||||||
|
command: cargo generate-lockfile
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- cargo-v3-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
- cargo-v3-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||||
@@ -43,6 +49,7 @@ jobs:
|
|||||||
- run: rustup default $(cat rust-toolchain)
|
- run: rustup default $(cat rust-toolchain)
|
||||||
- run: rustup component add --toolchain $(cat rust-toolchain) rustfmt
|
- run: rustup component add --toolchain $(cat rust-toolchain) rustfmt
|
||||||
- run: rustup component add --toolchain $(cat rust-toolchain) clippy-preview
|
- run: rustup component add --toolchain $(cat rust-toolchain) clippy-preview
|
||||||
|
- run: cargo update
|
||||||
- run: cargo fetch
|
- run: cargo fetch
|
||||||
- run: rustc +stable --version
|
- run: rustc +stable --version
|
||||||
- run: rustc +$(cat rust-toolchain) --version
|
- run: rustc +$(cat rust-toolchain) --version
|
||||||
@@ -84,6 +91,7 @@ jobs:
|
|||||||
curl https://sh.rustup.rs -sSf | sh -s -- -y
|
curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||||
- run: rustup install $(cat rust-toolchain)
|
- run: rustup install $(cat rust-toolchain)
|
||||||
- run: rustup default $(cat rust-toolchain)
|
- run: rustup default $(cat rust-toolchain)
|
||||||
|
- run: cargo update
|
||||||
- run: cargo fetch
|
- run: cargo fetch
|
||||||
- run:
|
- run:
|
||||||
name: Test
|
name: Test
|
||||||
@@ -120,47 +128,32 @@ jobs:
|
|||||||
paths:
|
paths:
|
||||||
- c-docs
|
- c-docs
|
||||||
|
|
||||||
# build_test_docs_wheel:
|
build_test_docs_wheel:
|
||||||
# docker:
|
docker:
|
||||||
# - image: deltachat/coredeps
|
- image: deltachat/coredeps
|
||||||
# environment:
|
environment:
|
||||||
# TESTS: 1
|
TESTS: 1
|
||||||
# DOCS: 1
|
DOCS: 1
|
||||||
# working_directory: /mnt/crate
|
working_directory: /mnt/crate
|
||||||
# steps:
|
|
||||||
# - *restore-workspace
|
|
||||||
# - *restore-cache
|
|
||||||
# - run:
|
|
||||||
# name: build docs, run tests and build wheels
|
|
||||||
# command: ci_scripts/run-python.sh
|
|
||||||
# - run:
|
|
||||||
# name: copying docs and wheels to workspace
|
|
||||||
# command: |
|
|
||||||
# mkdir -p workspace/python
|
|
||||||
# # 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:
|
|
||||||
# # - c-docs
|
|
||||||
# - py-docs
|
|
||||||
# - wheelhouse
|
|
||||||
|
|
||||||
remote_tests_rust:
|
|
||||||
machine: true
|
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- *restore-workspace
|
||||||
- run: ci_scripts/remote_tests_rust.sh
|
- *restore-cache
|
||||||
|
- run:
|
||||||
remote_tests_python:
|
name: build docs, run tests and build wheels
|
||||||
machine: true
|
command: ci_scripts/run-python.sh
|
||||||
steps:
|
- run:
|
||||||
- checkout
|
name: copying docs and wheels to workspace
|
||||||
#- attach_workspace:
|
command: |
|
||||||
# at: workspace
|
mkdir -p workspace/python
|
||||||
- run: ci_scripts/remote_tests_python.sh
|
# cp -av docs workspace/c-docs
|
||||||
# workspace/py-docs workspace/wheelhouse workspace/c-docs
|
cp -av python/.docker-tox/wheelhouse workspace/
|
||||||
|
cp -av python/doc/_build/ workspace/py-docs
|
||||||
|
- persist_to_workspace:
|
||||||
|
root: workspace
|
||||||
|
paths:
|
||||||
|
# - c-docs
|
||||||
|
- py-docs
|
||||||
|
- wheelhouse
|
||||||
|
|
||||||
upload_docs_wheels:
|
upload_docs_wheels:
|
||||||
machine: true
|
machine: true
|
||||||
@@ -179,7 +172,7 @@ jobs:
|
|||||||
- *restore-cache
|
- *restore-cache
|
||||||
- run:
|
- run:
|
||||||
name: Run cargo clippy
|
name: Run cargo clippy
|
||||||
command: cargo clippy
|
command: cargo clippy --all
|
||||||
|
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
@@ -187,29 +180,27 @@ workflows:
|
|||||||
|
|
||||||
test:
|
test:
|
||||||
jobs:
|
jobs:
|
||||||
# - cargo_fetch
|
- cargo_fetch
|
||||||
|
|
||||||
- remote_tests_rust
|
|
||||||
|
|
||||||
- remote_tests_python
|
|
||||||
|
|
||||||
# - upload_docs_wheels:
|
|
||||||
# requires:
|
|
||||||
# - build_test_docs_wheel
|
|
||||||
# - build_doxygen
|
|
||||||
# - rustfmt:
|
|
||||||
# requires:
|
|
||||||
# - cargo_fetch
|
|
||||||
# - clippy:
|
|
||||||
# requires:
|
|
||||||
# - cargo_fetch
|
|
||||||
|
|
||||||
- build_doxygen
|
- build_doxygen
|
||||||
|
|
||||||
|
- build_test_docs_wheel:
|
||||||
|
requires:
|
||||||
|
- cargo_fetch
|
||||||
|
- upload_docs_wheels:
|
||||||
|
requires:
|
||||||
|
- build_test_docs_wheel
|
||||||
|
- build_doxygen
|
||||||
|
- rustfmt:
|
||||||
|
requires:
|
||||||
|
- cargo_fetch
|
||||||
|
- clippy:
|
||||||
|
requires:
|
||||||
|
- cargo_fetch
|
||||||
|
|
||||||
# Linux Desktop 64bit
|
# Linux Desktop 64bit
|
||||||
# - test_x86_64-unknown-linux-gnu:
|
- test_x86_64-unknown-linux-gnu:
|
||||||
# requires:
|
requires:
|
||||||
# - cargo_fetch
|
- cargo_fetch
|
||||||
|
|
||||||
# Linux Desktop 32bit
|
# Linux Desktop 32bit
|
||||||
# - test_i686-unknown-linux-gnu:
|
# - test_i686-unknown-linux-gnu:
|
||||||
|
|||||||
47
.github/workflows/code-quality.yml
vendored
@@ -1,47 +0,0 @@
|
|||||||
on: push
|
|
||||||
name: Code Quality
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
check:
|
|
||||||
name: Check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: nightly-2019-11-06
|
|
||||||
override: true
|
|
||||||
- uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: check
|
|
||||||
|
|
||||||
fmt:
|
|
||||||
name: Rustfmt
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: nightly-2019-11-06
|
|
||||||
override: true
|
|
||||||
- run: rustup component add rustfmt
|
|
||||||
- uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: fmt
|
|
||||||
args: --all -- --check
|
|
||||||
|
|
||||||
run_clippy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: nightly-2019-11-06
|
|
||||||
components: clippy
|
|
||||||
override: true
|
|
||||||
- uses: actions-rs/clippy-check@v1
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
args: --all-features
|
|
||||||
30
.github/workflows/rust.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
|
||||||
|
env:
|
||||||
|
RUSTFLAGS: -Dwarnings
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: 3.7 python tests against core
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@master
|
||||||
|
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: nightly
|
||||||
|
override: true
|
||||||
|
components: rustfmt
|
||||||
|
|
||||||
|
- name: Setup python
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: 3.x
|
||||||
|
architecture: x64
|
||||||
|
|
||||||
|
- run: bash ci_scripts/run-python.sh
|
||||||
2
.gitignore
vendored
@@ -23,5 +23,3 @@ python/liveconfig*
|
|||||||
# ignore doxgen generated files
|
# ignore doxgen generated files
|
||||||
deltachat-ffi/html
|
deltachat-ffi/html
|
||||||
deltachat-ffi/xml
|
deltachat-ffi/xml
|
||||||
|
|
||||||
.rsynclist
|
|
||||||
|
|||||||
188
CHANGELOG.md
@@ -1,193 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 1.0.0-beta.18
|
|
||||||
|
|
||||||
- #1056 avoid panicking when we couldn't read imap-server's greeting
|
|
||||||
message
|
|
||||||
|
|
||||||
- #1055 avoid panicking when we don't have a selected folder
|
|
||||||
|
|
||||||
- #1052 #1049 #1051 improve logging to add thread-id/name and
|
|
||||||
file/lineno to each info/warn message.
|
|
||||||
|
|
||||||
- #1050 allow python bindings to initialize Account with "os_name".
|
|
||||||
|
|
||||||
|
|
||||||
## 1.0.0-beta.17
|
|
||||||
|
|
||||||
- #1044 implement avatar recoding to 192x192 in core to keep file sizes small.
|
|
||||||
|
|
||||||
- #1024 fix #1021 SQL/injection malformed Chat-Group-Name breakage
|
|
||||||
|
|
||||||
- #1036 fix smtp crash by pulling in a fixed async-smtp
|
|
||||||
|
|
||||||
- #1039 fix read-receipts appearing as normal messages when you change
|
|
||||||
MDN settings
|
|
||||||
|
|
||||||
- #1040 do not panic on SystemTimeDifference
|
|
||||||
|
|
||||||
- #1043 avoid potential crashes in malformed From/Chat-Disposition... headers
|
|
||||||
|
|
||||||
- #1045 #1041 #1038 #1035 #1034 #1029 #1025 various cleanups and doc
|
|
||||||
improvments
|
|
||||||
|
|
||||||
## 1.0.0-beta.16
|
|
||||||
|
|
||||||
- alleviate login problems with providers which only
|
|
||||||
support RSA1024 keys by switching back from Rustls
|
|
||||||
to native-tls, by using the new async-email/async-native-tls
|
|
||||||
crate from @dignifiedquire. thanks @link2xt.
|
|
||||||
|
|
||||||
- introduce per-contact profile images to send out
|
|
||||||
own profile image heuristically, and fix sending
|
|
||||||
out of profile images in "in-prepare" groups.
|
|
||||||
this also extends the Chat-spec that is maintained
|
|
||||||
in core to specify Chat-Group-Image and Chat-Group-Avatar
|
|
||||||
headers. thanks @r10s and @hpk42.
|
|
||||||
|
|
||||||
- fix merging of protected headers from the encrypted
|
|
||||||
to the unencrypted parts, now not happening recursively
|
|
||||||
anymore. thanks @hpk and @r10s
|
|
||||||
|
|
||||||
- fix/optimize autocrypt gossip headers to only get
|
|
||||||
sent when there are more than 2 people in a chat.
|
|
||||||
thanks @link2xt
|
|
||||||
|
|
||||||
- fix displayname to use the authenticated name
|
|
||||||
when available (displayname as coming from contacts
|
|
||||||
themselves). thanks @simon-laux
|
|
||||||
|
|
||||||
- introduce preliminary support for offline autoconfig
|
|
||||||
for nauta provider. thanks @hpk42 @r10s
|
|
||||||
|
|
||||||
## 1.0.0-beta.15
|
|
||||||
|
|
||||||
- fix #994 attachment appeared doubled in chats (and where actually
|
|
||||||
downloaded after smtp-send). @hpk42
|
|
||||||
|
|
||||||
## 1.0.0-beta.14
|
|
||||||
|
|
||||||
- fix packaging issue with our rust-email fork, now we are tracking
|
|
||||||
master again there. hpk42
|
|
||||||
|
|
||||||
## 1.0.0-beta.13
|
|
||||||
|
|
||||||
- fix #976 -- unicode-issues in display-name of email addresses. @hpk42
|
|
||||||
|
|
||||||
- fix #985 group add/remove member bugs resulting in broken groups. @hpk42
|
|
||||||
|
|
||||||
- fix hanging IMAP connections -- we now detect with a 15second timeout
|
|
||||||
if we cannot terminate the IDLE IMAP protocol. @hpk42 @link2xt
|
|
||||||
|
|
||||||
- fix incoming multipart/mixed containing html, to show up as
|
|
||||||
attachments again. Fixes usage for simplebot which sends html
|
|
||||||
files for users to interact with the bot. @adbenitez @hpk42
|
|
||||||
|
|
||||||
- refinements to internal autocrypt-handling code, do not send
|
|
||||||
prefer-encrypt=nopreference as it is the default if no attribute
|
|
||||||
is present. @linkxt
|
|
||||||
|
|
||||||
- simplify, modularize and rustify several parts
|
|
||||||
of dc-core (general WIP). @link2xt @flub @hpk42 @r10s
|
|
||||||
|
|
||||||
- use async-email/async-smtp to handle SMTP connections, might
|
|
||||||
fix connection/reconnection issues. @link2xt
|
|
||||||
|
|
||||||
- more tests and refinements for dealing with blobstorage @flub @hpk42
|
|
||||||
|
|
||||||
- use a dedicated build-server for CI testing of core PRs
|
|
||||||
|
|
||||||
|
|
||||||
## 1.0.0-beta.12
|
|
||||||
|
|
||||||
- fix python bindings to use core for copying attachments to blobdir
|
|
||||||
and fix core to actually do it. @hpk42
|
|
||||||
|
|
||||||
## 1.0.0-beta.11
|
|
||||||
|
|
||||||
- trigger reconnect more often on imap error states. Should fix an
|
|
||||||
issue observed when trying to empty a folder. @hpk42
|
|
||||||
|
|
||||||
- un-split qr tests: we fixed qr-securejoin protocol flakyness
|
|
||||||
last weeks. @hpk42
|
|
||||||
|
|
||||||
## 1.0.0-beta.10
|
|
||||||
|
|
||||||
- fix grpid-determination from in-reply-to and references headers. @hpk42
|
|
||||||
|
|
||||||
- only send Autocrypt-gossip headers on encrypted messages. @dignifiedquire
|
|
||||||
|
|
||||||
- fix reply-to-encrypted message to also be encrypted. @hpk42
|
|
||||||
|
|
||||||
- remove last unsafe code from dc_receive_imf :) @hpk42
|
|
||||||
|
|
||||||
- add experimental new dc_chat_get_info_json FFI/API so that desktop devs
|
|
||||||
can play with using it. @jikstra
|
|
||||||
|
|
||||||
- fix encoding of subjects and attachment-filenames @hpk42
|
|
||||||
@dignifiedquire .
|
|
||||||
|
|
||||||
## 1.0.0-beta.9
|
|
||||||
|
|
||||||
- historic: we now use the mailparse crate and lettre-email to generate mime
|
|
||||||
messages. This got rid of mmime completely, the C2rust generated port of the libetpan
|
|
||||||
mime-parse -- IOW 22KLocs of cumbersome code removed! see
|
|
||||||
https://github.com/deltachat/deltachat-core-rust/pull/904#issuecomment-561163330
|
|
||||||
many thanks @dignifiedquire for making everybody's life easier
|
|
||||||
and @jonhoo (from rust-imap fame) for suggesting to use the mailparse crate :)
|
|
||||||
|
|
||||||
- lots of improvements and better error handling in many rust modules
|
|
||||||
thanks @link2xt @flub @r10s, @hpk42 and @dignifiedquire
|
|
||||||
|
|
||||||
- @r10s introduced a new device chat which has an initial
|
|
||||||
welcome message. See
|
|
||||||
https://c.delta.chat/classdc__context__t.html#a1a2aad98bd23c1d21ee42374e241f389
|
|
||||||
for the main new FFI-API.
|
|
||||||
|
|
||||||
- fix moving self-sent messages, thanks @r10s, @flub, @hpk42
|
|
||||||
|
|
||||||
- fix flakyness/sometimes-failing verified/join-protocols,
|
|
||||||
thanks @flub, @r10s, @hpk42
|
|
||||||
|
|
||||||
- fix reply-to-encrypted message to keep encryption
|
|
||||||
|
|
||||||
- new DC_EVENT_SECUREJOIN_MEMBER_ADDED event
|
|
||||||
|
|
||||||
- many little fixes and rustifications (@link2xt, @flub, @hpk42)
|
|
||||||
|
|
||||||
|
|
||||||
## 1.0.0-beta.8
|
|
||||||
|
|
||||||
- now uses async-email/async-imap as the new base
|
|
||||||
which makes imap-idle interruptible and thus fixes
|
|
||||||
several issues around the imap thread being in zombie state .
|
|
||||||
thanks @dignifiedquire, @hpk42 and @link2xt.
|
|
||||||
|
|
||||||
- fixes imap-protocol parsing bugs that lead to infinitely
|
|
||||||
repeated crashing while trying to receive messages with
|
|
||||||
a subjec that contained non-utf8. thanks @link2xt
|
|
||||||
|
|
||||||
- fixed logic to find encryption subkey -- previously
|
|
||||||
delta chat would use the primary key for encryption
|
|
||||||
(which works with RSA but not ECC). thanks @link2xt
|
|
||||||
|
|
||||||
- introduce a new device chat where core and UIs can
|
|
||||||
add "device" messages. Android uses it for an initial
|
|
||||||
welcome message. thanks @r10s
|
|
||||||
|
|
||||||
- fix time smearing (when two message are virtually send
|
|
||||||
in the same second, there would be misbehaviour because
|
|
||||||
we didn't persist smeared time). thanks @r10s
|
|
||||||
|
|
||||||
- fix double-dotted extensions like .html.zip or .tar.gz
|
|
||||||
to not mangle them when creating blobfiles. thanks @flub
|
|
||||||
|
|
||||||
- fix backup/exports where the wrong sql file would be modified,
|
|
||||||
leading to problems when exporting twice. thanks @hpk42
|
|
||||||
|
|
||||||
- several other little fixes and improvements
|
|
||||||
|
|
||||||
|
|
||||||
## 1.0.0-beta.7
|
## 1.0.0-beta.7
|
||||||
|
|
||||||
- fix location-streaming #782
|
- fix location-streaming #782
|
||||||
|
|||||||
1493
Cargo.lock
generated
42
Cargo.toml
@@ -1,32 +1,28 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat"
|
name = "deltachat"
|
||||||
version = "1.0.0-beta.18"
|
version = "1.0.0-beta.7"
|
||||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "MPL"
|
license = "MPL"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
deltachat_derive = { path = "./deltachat_derive" }
|
deltachat_derive = { path = "./deltachat_derive" }
|
||||||
|
mmime = { version = "0.1.2", path = "./mmime" }
|
||||||
|
|
||||||
libc = "0.2.51"
|
libc = "0.2.51"
|
||||||
pgp = { version = "0.4.0", default-features = false }
|
pgp = { version = "0.2.3", default-features = false }
|
||||||
hex = "0.4.0"
|
hex = "0.3.2"
|
||||||
sha2 = "0.8.0"
|
sha2 = "0.8.0"
|
||||||
rand = "0.7.0"
|
rand = "0.6.5"
|
||||||
smallvec = "1.0.0"
|
smallvec = "0.6.9"
|
||||||
reqwest = { version = "0.9.15" }
|
reqwest = { version = "0.9.15", default-features = false, features = ["rustls-tls"] }
|
||||||
num-derive = "0.3.0"
|
num-derive = "0.2.5"
|
||||||
num-traits = "0.2.6"
|
num-traits = "0.2.6"
|
||||||
async-smtp = { git = "https://github.com/async-email/async-smtp", branch = "master" }
|
lettre = { git = "https://github.com/deltachat/lettre", branch = "feat/rustls" }
|
||||||
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
|
async-imap = "0.1"
|
||||||
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "native_tls" }
|
async-tls = "0.6"
|
||||||
|
|
||||||
# XXX newer commits of async-imap lead to import-export tests hanging
|
|
||||||
async-imap = { git = "https://github.com/async-email/async-imap", branch = "dcc-stable" }
|
|
||||||
|
|
||||||
async-native-tls = "0.1.1"
|
|
||||||
async-std = { version = "1.0", features = ["unstable"] }
|
async-std = { version = "1.0", features = ["unstable"] }
|
||||||
base64 = "0.11"
|
base64 = "0.10"
|
||||||
charset = "0.1"
|
charset = "0.1"
|
||||||
percent-encoding = "2.0"
|
percent-encoding = "2.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
@@ -34,7 +30,6 @@ serde_json = "1.0"
|
|||||||
chrono = "0.4.6"
|
chrono = "0.4.6"
|
||||||
failure = "0.1.5"
|
failure = "0.1.5"
|
||||||
failure_derive = "0.1.5"
|
failure_derive = "0.1.5"
|
||||||
indexmap = "1.3.0"
|
|
||||||
# TODO: make optional
|
# TODO: make optional
|
||||||
rustyline = "4.1.0"
|
rustyline = "4.1.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
@@ -49,16 +44,16 @@ backtrace = "0.3.33"
|
|||||||
byteorder = "1.3.1"
|
byteorder = "1.3.1"
|
||||||
itertools = "0.8.0"
|
itertools = "0.8.0"
|
||||||
image-meta = "0.1.0"
|
image-meta = "0.1.0"
|
||||||
quick-xml = "0.17.1"
|
quick-xml = "0.15.0"
|
||||||
escaper = "0.1.0"
|
escaper = "0.1.0"
|
||||||
bitflags = "1.1.0"
|
bitflags = "1.1.0"
|
||||||
|
jetscii = "0.4.4"
|
||||||
debug_stub_derive = "0.3.0"
|
debug_stub_derive = "0.3.0"
|
||||||
sanitize-filename = "0.2.1"
|
sanitize-filename = "0.2.1"
|
||||||
stop-token = { version = "0.1.1", features = ["unstable"] }
|
stop-token = { version = "0.1.1", features = ["unstable"] }
|
||||||
mailparse = "0.10.1"
|
rustls = "0.16.0"
|
||||||
encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" }
|
webpki-roots = "0.18.0"
|
||||||
native-tls = "0.2.3"
|
webpki = "0.21.0"
|
||||||
image = "0.22.3"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.0"
|
tempfile = "3.0"
|
||||||
@@ -70,6 +65,7 @@ proptest = "0.9.4"
|
|||||||
members = [
|
members = [
|
||||||
"deltachat-ffi",
|
"deltachat-ffi",
|
||||||
"deltachat_derive",
|
"deltachat_derive",
|
||||||
|
"mmime",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
@@ -83,6 +79,6 @@ path = "examples/repl/main.rs"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["nightly", "ringbuf"]
|
default = ["nightly", "ringbuf"]
|
||||||
vendored = ["native-tls/vendored", "reqwest/default-tls-vendored"]
|
vendored = []
|
||||||
nightly = ["pgp/nightly"]
|
nightly = ["pgp/nightly"]
|
||||||
ringbuf = ["pgp/ringbuf"]
|
ringbuf = ["pgp/ringbuf"]
|
||||||
|
|||||||
15
README.md
@@ -1,9 +1,11 @@
|
|||||||
# Delta Chat Rust
|
# Delta Chat Rust
|
||||||
|
|
||||||
> Deltachat-core written in Rust
|
> Project porting deltachat-core to rust
|
||||||
|
|
||||||
[![CircleCI build status][circle-shield]][circle] [![Appveyor build status][appveyor-shield]][appveyor]
|
[![CircleCI build status][circle-shield]][circle] [![Appveyor build status][appveyor-shield]][appveyor]
|
||||||
|
|
||||||
|
Current commit on deltachat/deltachat-core: `12ef73c8e76185f9b78e844ea673025f56a959ab`.
|
||||||
|
|
||||||
## Installing Rust and Cargo
|
## Installing Rust and Cargo
|
||||||
|
|
||||||
To download and install the official compiler for the Rust programming language, and the Cargo package manager, run the command in your user environment:
|
To download and install the official compiler for the Rust programming language, and the Cargo package manager, run the command in your user environment:
|
||||||
@@ -14,7 +16,7 @@ curl https://sh.rustup.rs -sSf | sh
|
|||||||
|
|
||||||
## Using the CLI client
|
## Using the CLI client
|
||||||
|
|
||||||
Compile and run Delta Chat Core command line utility, using `cargo`:
|
Compile and run Delta Chat Core using `cargo`:
|
||||||
|
|
||||||
```
|
```
|
||||||
cargo run --example repl -- /path/to/db
|
cargo run --example repl -- /path/to/db
|
||||||
@@ -87,15 +89,6 @@ $ cargo test --all
|
|||||||
$ cargo build -p deltachat_ffi --release
|
$ cargo build -p deltachat_ffi --release
|
||||||
```
|
```
|
||||||
|
|
||||||
## Debugging environment variables
|
|
||||||
|
|
||||||
- `DCC_IMAP_DEBUG`: if set IMAP protocol commands and responses will be
|
|
||||||
printed
|
|
||||||
|
|
||||||
- `DCC_MIME_DEBUG`: if set outgoing and incoming message will be printed
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Expensive tests
|
### Expensive tests
|
||||||
|
|
||||||
Some tests are expensive and marked with `#[ignore]`, to run these
|
Some tests are expensive and marked with `#[ignore]`, to run these
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ install:
|
|||||||
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
||||||
- rustc -vV
|
- rustc -vV
|
||||||
- cargo -vV
|
- cargo -vV
|
||||||
|
- cargo update
|
||||||
|
|
||||||
build: false
|
build: false
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 3.6 KiB |
@@ -1,83 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
inkscape:export-ydpi="409.60001"
|
|
||||||
inkscape:export-xdpi="409.60001"
|
|
||||||
inkscape:export-filename="/Users/bpetersen/projects/deltachat-core-rust/assets/icon-device.png"
|
|
||||||
version="1.0"
|
|
||||||
width="60"
|
|
||||||
height="60"
|
|
||||||
viewBox="0 0 45 45"
|
|
||||||
preserveAspectRatio="xMidYMid meet"
|
|
||||||
id="svg4344"
|
|
||||||
sodipodi:docname="icon-device.svg"
|
|
||||||
inkscape:version="1.0beta1 (32d4812, 2019-09-19)">
|
|
||||||
<defs
|
|
||||||
id="defs4348" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
inkscape:snap-global="false"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
inkscape:document-rotation="0"
|
|
||||||
borderopacity="1"
|
|
||||||
objecttolerance="10"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:window-width="1600"
|
|
||||||
inkscape:window-height="1035"
|
|
||||||
id="namedview4346"
|
|
||||||
showgrid="false"
|
|
||||||
units="px"
|
|
||||||
inkscape:zoom="3.959798"
|
|
||||||
inkscape:cx="28.322498"
|
|
||||||
inkscape:cy="24.898474"
|
|
||||||
inkscape:window-x="45"
|
|
||||||
inkscape:window-y="23"
|
|
||||||
inkscape:window-maximized="0"
|
|
||||||
inkscape:current-layer="svg4344" />
|
|
||||||
<metadata
|
|
||||||
id="metadata4336">
|
|
||||||
Created by potrace 1.15, written by Peter Selinger 2001-2017
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title />
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<rect
|
|
||||||
y="-4.4408921e-16"
|
|
||||||
x="0"
|
|
||||||
height="45"
|
|
||||||
width="45"
|
|
||||||
id="rect860"
|
|
||||||
style="opacity:1;fill:#76868b;fill-opacity:1;stroke-width:0.819271" />
|
|
||||||
<g
|
|
||||||
fill="#000000"
|
|
||||||
stroke="none"
|
|
||||||
style="fill:#ffffff;fill-opacity:1"
|
|
||||||
transform="matrix(0.00255113,0,0,-0.00255113,5.586152,38.200477)"
|
|
||||||
id="g4342">
|
|
||||||
<path
|
|
||||||
style="fill:#ffffff;fill-opacity:1"
|
|
||||||
d="m 8175,12765 c -703,-114 -1248,-608 -1387,-1258 -17,-82 -21,-136 -22,-277 0,-202 15,-307 70,-470 149,-446 499,-733 1009,-828 142,-26 465,-23 619,6 691,131 1201,609 1328,1244 31,158 31,417 0,565 -114,533 -482,889 -1038,1004 -133,27 -448,35 -579,14 z"
|
|
||||||
id="path4338"
|
|
||||||
inkscape:connector-curvature="0" />
|
|
||||||
<path
|
|
||||||
style="fill:#ffffff;fill-opacity:1"
|
|
||||||
d="m 7070,9203 c -212,-20 -275,-27 -397,-48 -691,-117 -1400,-444 -2038,-940 -182,-142 -328,-270 -585,-517 -595,-571 -911,-974 -927,-1181 -6,-76 11,-120 69,-184 75,-80 159,-108 245,-79 109,37 263,181 632,595 539,606 774,826 1035,969 135,75 231,105 341,106 82,1 94,-2 138,-27 116,-68 161,-209 122,-376 -9,-36 -349,-868 -757,-1850 -407,-982 -785,-1892 -838,-2021 -287,-694 -513,-1389 -615,-1889 -70,-342 -90,-683 -52,-874 88,-440 381,-703 882,-792 124,-23 401,-30 562,-16 783,69 1674,461 2561,1125 796,596 1492,1354 1607,1751 43,146 -33,308 -168,360 -61,23 -100,15 -173,-36 -105,-74 -202,-170 -539,-529 -515,-551 -762,-783 -982,-927 -251,-164 -437,-186 -543,-65 -56,64 -74,131 -67,247 13,179 91,434 249,815 135,324 1588,4102 1646,4280 106,325 151,561 159,826 9,281 -22,463 -112,652 -58,122 -114,199 -211,292 -245,233 -582,343 -1044,338 -91,-1 -181,-3 -200,-5 z"
|
|
||||||
id="path4340"
|
|
||||||
inkscape:connector-curvature="0" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
@@ -1,71 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
inkscape:export-ydpi="409.60001"
|
|
||||||
inkscape:export-xdpi="409.60001"
|
|
||||||
inkscape:export-filename="/home/kerle/test-icon.png"
|
|
||||||
version="1.0"
|
|
||||||
width="60"
|
|
||||||
height="60"
|
|
||||||
viewBox="0 0 45 45"
|
|
||||||
preserveAspectRatio="xMidYMid meet"
|
|
||||||
id="svg4344"
|
|
||||||
sodipodi:docname="icon-saved-messages.svg"
|
|
||||||
inkscape:version="1.0beta1 (32d4812, 2019-09-19)">
|
|
||||||
<defs
|
|
||||||
id="defs4348" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
inkscape:document-rotation="0"
|
|
||||||
borderopacity="1"
|
|
||||||
objecttolerance="10"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:window-width="1395"
|
|
||||||
inkscape:window-height="855"
|
|
||||||
id="namedview4346"
|
|
||||||
showgrid="false"
|
|
||||||
units="px"
|
|
||||||
inkscape:zoom="4"
|
|
||||||
inkscape:cx="29.308676"
|
|
||||||
inkscape:cy="49.03624"
|
|
||||||
inkscape:window-x="89"
|
|
||||||
inkscape:window-y="108"
|
|
||||||
inkscape:window-maximized="0"
|
|
||||||
inkscape:current-layer="svg4344"
|
|
||||||
inkscape:lockguides="false" />
|
|
||||||
<metadata
|
|
||||||
id="metadata4336">
|
|
||||||
Created by potrace 1.15, written by Peter Selinger 2001-2017
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title />
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<rect
|
|
||||||
y="0"
|
|
||||||
x="0"
|
|
||||||
height="45"
|
|
||||||
width="45"
|
|
||||||
id="rect1420"
|
|
||||||
style="fill:#87aade;fill-opacity:1;stroke:none;stroke-width:0.968078" />
|
|
||||||
<path
|
|
||||||
id="rect846"
|
|
||||||
style="fill:#ffffff;stroke-width:0.58409804"
|
|
||||||
d="M 13.5,7.5 V 39 h 0.08654 L 22.533801,29.370239 31.482419,39 h 0.01758 V 7.5 Z m 9.004056,4.108698 1.879508,4.876388 5.039514,0.359779 -3.879358,3.363728 1.227764,5.095749 -4.276893,-2.796643 -4.280949,2.788618 1.237229,-5.093073 -3.873949,-3.371754 5.040866,-0.350417 z"
|
|
||||||
inkscape:connector-curvature="0" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 113 KiB |
@@ -1,46 +1,52 @@
|
|||||||
|
|
||||||
# Continuous Integration Scripts for Delta Chat
|
# Continuous Integration Scripts for Delta Chat
|
||||||
|
|
||||||
Continuous Integration, run through CircleCI and an own build machine.
|
Continuous Integration is run through CircleCI
|
||||||
|
but is largely independent of it.
|
||||||
## Description of scripts
|
|
||||||
|
|
||||||
- `../.circleci/config.yml` describing the build jobs that are run
|
|
||||||
by Circle-CI
|
|
||||||
|
|
||||||
- `remote_tests_python.sh` rsyncs to a build machine and runs
|
|
||||||
`run-python-test.sh` remotely on the build machine.
|
|
||||||
|
|
||||||
- `remote_tests_rust.sh` rsyncs to the build machine and runs
|
|
||||||
`run-rust-test.sh` remotely on the build machine.
|
|
||||||
|
|
||||||
- `doxygen/Dockerfile` specifies an image that contains
|
|
||||||
the doxygen tool which is used by `run-doxygen.sh`
|
|
||||||
to generate C-docs which are then uploaded
|
|
||||||
via `ci_upload.sh` to `https://c.delta.chat/_unofficial_unreleased_docs/<BRANCH>`
|
|
||||||
(and the master branch is linked to https://c.delta.chat proper).
|
|
||||||
|
|
||||||
|
|
||||||
## Triggering runs on the build machine locally (fast!)
|
## Generating docker containers for performing build step work
|
||||||
|
|
||||||
There is experimental support for triggering a remote Python or Rust test run
|
All tests, docs and wheel building is run in docker containers:
|
||||||
from your local checkout/branch. You will need to be authorized to login to
|
|
||||||
the build machine (ask your friendly sysadmin on #deltachat freenode) to type::
|
|
||||||
|
|
||||||
ci_scripts/manual_remote_tests.sh rust
|
- **coredeps/Dockerfile** specifies an image that contains all
|
||||||
ci_scripts/manual_remote_tests.sh python
|
of Delta Chat's core dependencies as linkable libraries.
|
||||||
|
It also serves to run python tests and build wheels
|
||||||
|
(binary packages for Python).
|
||||||
|
|
||||||
This will **rsync** your current checkout to the remote build machine
|
- **doxygen/Dockerfile** specifies an image that contains
|
||||||
(no need to commit before) and then run either rust or python tests.
|
the doxygen tool which is used to generate C-docs.
|
||||||
|
|
||||||
# Outdated files (for later re-use)
|
To run tests locally you can pull existing images from "docker.io",
|
||||||
|
the hub for sharing Docker images::
|
||||||
|
|
||||||
`coredeps/Dockerfile` specifies an image that contains all
|
docker pull deltachat/coredeps
|
||||||
of Delta Chat's core dependencies. It used to run
|
docker pull deltachat/doxygen
|
||||||
python tests and build wheels (binary packages for Python)
|
|
||||||
|
|
||||||
You can build the docker images yourself locally
|
or you can build the docker images yourself locally
|
||||||
to avoid the relatively large download::
|
to avoid the relatively large download::
|
||||||
|
|
||||||
cd ci_scripts # where all CI things are
|
cd ci_scripts # where all CI things are
|
||||||
docker build -t deltachat/coredeps docker-coredeps
|
docker build -t deltachat/coredeps docker-coredeps
|
||||||
docker build -t deltachat/doxygen docker-doxygen
|
docker build -t deltachat/doxygen docker-doxygen
|
||||||
|
|
||||||
|
## ci_run.sh (main entrypoint called by circle-ci)
|
||||||
|
|
||||||
|
Once you have the docker images available
|
||||||
|
you can run python testing, documentation generation
|
||||||
|
and building binary wheels::
|
||||||
|
|
||||||
|
sh DOCS=1 TESTS=1 ci_scripts/ci_run.sh
|
||||||
|
|
||||||
|
## ci_upload.sh (uploading artifacts on success)
|
||||||
|
|
||||||
|
- python docs to `https://py.delta.chat/_unofficial_unreleased_docs/<BRANCH>`
|
||||||
|
|
||||||
|
- doxygen docs to `https://c.delta.chat/_unofficial_unreleased_docs/<BRANCH>`
|
||||||
|
|
||||||
|
- python wheels to `https://m.devpi.net/dc/<BRANCH>`
|
||||||
|
so that you install fully self-contained wheels like this:
|
||||||
|
`pip install -U -i https://m.devpi.net/dc/<BRANCH> deltachat`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,24 +14,20 @@ DOXYDOCDIR=${3:?directory where doxygen docs to be found}
|
|||||||
export BRANCH=${CIRCLE_BRANCH:?specify branch for uploading purposes}
|
export BRANCH=${CIRCLE_BRANCH:?specify branch for uploading purposes}
|
||||||
|
|
||||||
|
|
||||||
# DISABLED: python docs to py.delta.chat
|
# python docs to py.delta.chat
|
||||||
#ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@py.delta.chat mkdir -p build/${BRANCH}
|
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@py.delta.chat mkdir -p build/${BRANCH}
|
||||||
#rsync -avz \
|
rsync -avz \
|
||||||
# -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
|
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
|
||||||
# "$PYDOCDIR/html/" \
|
"$PYDOCDIR/html/" \
|
||||||
# delta@py.delta.chat:build/${BRANCH}
|
delta@py.delta.chat:build/${BRANCH}
|
||||||
|
|
||||||
# C docs to c.delta.chat
|
# C docs to c.delta.chat
|
||||||
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@c.delta.chat mkdir -p build-c/${BRANCH}
|
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@c.delta.chat mkdir -p build-c/${BRANCH}
|
||||||
rsync -avz \
|
rsync -avz \
|
||||||
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
|
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
|
||||||
"$DOXYDOCDIR/html/" \
|
"$DOXYDOCDIR/html/" \
|
||||||
delta@c.delta.chat:build-c/${BRANCH}
|
delta@c.delta.chat:build-c/${BRANCH}
|
||||||
|
|
||||||
exit 0
|
|
||||||
|
|
||||||
# OUTDATED -- for re-use from python release-scripts
|
|
||||||
|
|
||||||
echo -----------------------
|
echo -----------------------
|
||||||
echo upload wheels
|
echo upload wheels
|
||||||
echo -----------------------
|
echo -----------------------
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -xe
|
|
||||||
export CIRCLE_JOB=remote_tests_${1:?need to specify 'rust' or 'python'}
|
|
||||||
export CIRCLE_BUILD_NUM=$USER
|
|
||||||
export CIRCLE_BRANCH=`git branch | grep \* | cut -d ' ' -f2`
|
|
||||||
export CIRCLE_PROJECT_REPONAME=$(basename `git rev-parse --show-toplevel`)
|
|
||||||
|
|
||||||
time bash ci_scripts/$CIRCLE_JOB.sh
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
|
|
||||||
env:
|
|
||||||
RUSTFLAGS: -Dwarnings
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build_and_test:
|
|
||||||
name: Build and test
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
|
||||||
rust: [nightly]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@master
|
|
||||||
|
|
||||||
- name: Install ${{ matrix.rust }}
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
override: true
|
|
||||||
|
|
||||||
- name: check
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
if: matrix.rust == 'nightly'
|
|
||||||
with:
|
|
||||||
command: check
|
|
||||||
args: --all --bins --examples --tests
|
|
||||||
|
|
||||||
- name: tests
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: test
|
|
||||||
args: --all
|
|
||||||
|
|
||||||
- name: tests ignored
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: test
|
|
||||||
args: --all --release -- --ignored
|
|
||||||
|
|
||||||
check_fmt:
|
|
||||||
name: Checking fmt and docs
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@master
|
|
||||||
|
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: nightly
|
|
||||||
override: true
|
|
||||||
components: rustfmt
|
|
||||||
|
|
||||||
- name: fmt
|
|
||||||
run: cargo fmt --all -- --check
|
|
||||||
|
|
||||||
# clippy_check:
|
|
||||||
# name: Clippy check
|
|
||||||
# runs-on: ubuntu-latest
|
|
||||||
#
|
|
||||||
# steps:
|
|
||||||
# - uses: actions/checkout@v1
|
|
||||||
# - uses: actions-rs/toolchain@v1
|
|
||||||
# with:
|
|
||||||
# profile: minimal
|
|
||||||
# toolchain: nightly
|
|
||||||
# override: true
|
|
||||||
# components: clippy
|
|
||||||
#
|
|
||||||
# - name: clippy
|
|
||||||
# run: cargo clippy --all
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
#
|
|
||||||
# Build the Delta Chat C/Rust library typically run in a docker
|
|
||||||
# container that contains all library deps but should also work
|
|
||||||
# outside if you have the dependencies installed on your system.
|
|
||||||
|
|
||||||
set -e -x
|
|
||||||
|
|
||||||
# Perform clean build of core and install.
|
|
||||||
export TOXWORKDIR=.docker-tox
|
|
||||||
|
|
||||||
# install core lib
|
|
||||||
|
|
||||||
export PATH=/root/.cargo/bin:$PATH
|
|
||||||
cargo build --release -p deltachat_ffi
|
|
||||||
# cargo test --all --all-features
|
|
||||||
|
|
||||||
# Statically link against libdeltachat.a.
|
|
||||||
export DCC_RS_DEV=$(pwd)
|
|
||||||
|
|
||||||
# Configure access to a base python and to several python interpreters
|
|
||||||
# needed by tox below.
|
|
||||||
export PATH=$PATH:/opt/python/cp35-cp35m/bin
|
|
||||||
export PYTHONDONTWRITEBYTECODE=1
|
|
||||||
pushd /bin
|
|
||||||
ln -s /opt/python/cp27-cp27m/bin/python2.7
|
|
||||||
ln -s /opt/python/cp36-cp36m/bin/python3.6
|
|
||||||
ln -s /opt/python/cp37-cp37m/bin/python3.7
|
|
||||||
popd
|
|
||||||
|
|
||||||
if [ -n "$TESTS" ]; then
|
|
||||||
|
|
||||||
pushd python
|
|
||||||
# prepare a clean tox run
|
|
||||||
rm -rf tests/__pycache__
|
|
||||||
rm -rf src/deltachat/__pycache__
|
|
||||||
export PYTHONDONTWRITEBYTECODE=1
|
|
||||||
|
|
||||||
# run tox. The circle-ci project env-var-setting DCC_PY_LIVECONFIG
|
|
||||||
# allows running of "liveconfig" tests but for speed reasons
|
|
||||||
# we run them only for the highest python version we support
|
|
||||||
|
|
||||||
# we split out qr-tests run to minimize likelyness of flaky tests
|
|
||||||
# (some qr tests are pretty heavy in terms of send/received
|
|
||||||
# messages and rust's imap code likely has concurrency problems)
|
|
||||||
tox --workdir "$TOXWORKDIR" -e py37 -- --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
|
|
||||||
popd
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
# if [ -n "$DOCS" ]; then
|
|
||||||
# echo -----------------------
|
|
||||||
# echo generating python docs
|
|
||||||
# echo -----------------------
|
|
||||||
# (cd python && tox --workdir "$TOXWORKDIR" -e doc)
|
|
||||||
# fi
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
export BRANCH=${CIRCLE_BRANCH:?branch to build}
|
|
||||||
export REPONAME=${CIRCLE_PROJECT_REPONAME:?repository name}
|
|
||||||
export SSHTARGET=${SSHTARGET-ci@b1.delta.chat}
|
|
||||||
|
|
||||||
# we construct the BUILDDIR such that we can easily share the
|
|
||||||
# CARGO_TARGET_DIR between runs ("..")
|
|
||||||
export BUILDDIR=ci_builds/$REPONAME/$BRANCH/${CIRCLE_JOB:?jobname}/${CIRCLE_BUILD_NUM:?circle-build-number}
|
|
||||||
|
|
||||||
echo "--- Copying files to $SSHTARGET:$BUILDDIR"
|
|
||||||
|
|
||||||
set -xe
|
|
||||||
|
|
||||||
ssh -oBatchMode=yes -oStrictHostKeyChecking=no $SSHTARGET mkdir -p "$BUILDDIR"
|
|
||||||
git ls-files >.rsynclist
|
|
||||||
# we seem to need .git for setuptools_scm versioning
|
|
||||||
find .git >>.rsynclist
|
|
||||||
rsync --delete --files-from=.rsynclist -az ./ "$SSHTARGET:$BUILDDIR"
|
|
||||||
|
|
||||||
set +x
|
|
||||||
|
|
||||||
echo "--- Running $CIRCLE_JOB remotely"
|
|
||||||
|
|
||||||
ssh $SSHTARGET <<_HERE
|
|
||||||
set +x -e
|
|
||||||
cd $BUILDDIR
|
|
||||||
# let's share the target dir with our last run on this branch/job-type
|
|
||||||
# cargo will make sure to block/unblock us properly
|
|
||||||
export CARGO_TARGET_DIR=\`pwd\`/../target
|
|
||||||
export TARGET=release
|
|
||||||
export DCC_PY_LIVECONFIG=$DCC_PY_LIVECONFIG
|
|
||||||
|
|
||||||
#we rely on tox/virtualenv being available in the host
|
|
||||||
#rm -rf virtualenv venv
|
|
||||||
#virtualenv -q -p python3.7 venv
|
|
||||||
#source venv/bin/activate
|
|
||||||
#pip install -q tox virtualenv
|
|
||||||
|
|
||||||
set -x
|
|
||||||
which python
|
|
||||||
source \$HOME/venv/bin/activate
|
|
||||||
which python
|
|
||||||
|
|
||||||
bash ci_scripts/run-python-test.sh
|
|
||||||
_HERE
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
export BRANCH=${CIRCLE_BRANCH:?branch to build}
|
|
||||||
export REPONAME=${CIRCLE_PROJECT_REPONAME:?repository name}
|
|
||||||
export SSHTARGET=${SSHTARGET-ci@b1.delta.chat}
|
|
||||||
|
|
||||||
# we construct the BUILDDIR such that we can easily share the
|
|
||||||
# CARGO_TARGET_DIR between runs ("..")
|
|
||||||
export BUILDDIR=ci_builds/$REPONAME/$BRANCH/${CIRCLE_JOB:?jobname}/${CIRCLE_BUILD_NUM:?circle-build-number}
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "--- Copying files to $SSHTARGET:$BUILDDIR"
|
|
||||||
|
|
||||||
ssh -oBatchMode=yes -oStrictHostKeyChecking=no $SSHTARGET mkdir -p "$BUILDDIR"
|
|
||||||
git ls-files >.rsynclist
|
|
||||||
rsync --delete --files-from=.rsynclist -az ./ "$SSHTARGET:$BUILDDIR"
|
|
||||||
|
|
||||||
echo "--- Running $CIRCLE_JOB remotely"
|
|
||||||
|
|
||||||
ssh $SSHTARGET <<_HERE
|
|
||||||
set +x -e
|
|
||||||
cd $BUILDDIR
|
|
||||||
# let's share the target dir with our last run on this branch/job-type
|
|
||||||
# cargo will make sure to block/unblock us properly
|
|
||||||
export CARGO_TARGET_DIR=\`pwd\`/../target
|
|
||||||
export TARGET=x86_64-unknown-linux-gnu
|
|
||||||
export RUSTC_WRAPPER=sccache
|
|
||||||
|
|
||||||
bash ci_scripts/run-rust-test.sh
|
|
||||||
_HERE
|
|
||||||
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
#
|
|
||||||
# Run functional tests for Delta Chat core using the python bindings
|
|
||||||
# and tox/pytest.
|
|
||||||
|
|
||||||
set -e -x
|
|
||||||
|
|
||||||
# for core-building and python install step
|
|
||||||
export DCC_RS_TARGET=release
|
|
||||||
export DCC_RS_DEV=`pwd`
|
|
||||||
|
|
||||||
cd python
|
|
||||||
|
|
||||||
python install_python_bindings.py onlybuild
|
|
||||||
|
|
||||||
# remove and inhibit writing PYC files
|
|
||||||
rm -rf tests/__pycache__
|
|
||||||
rm -rf src/deltachat/__pycache__
|
|
||||||
export PYTHONDONTWRITEBYTECODE=1
|
|
||||||
|
|
||||||
# run python tests (tox invokes pytest to run tests in python/tests)
|
|
||||||
#TOX_PARALLEL_NO_SPINNER=1 tox -e lint,doc
|
|
||||||
tox -e lint
|
|
||||||
tox -e doc,py37
|
|
||||||
50
ci_scripts/run-python.sh
Executable file
@@ -0,0 +1,50 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Build the Delta Chat C/Rust library typically run in a docker
|
||||||
|
# container that contains all library deps but should also work
|
||||||
|
# outside if you have the dependencies installed on your system.
|
||||||
|
|
||||||
|
set -e -x
|
||||||
|
|
||||||
|
# Perform clean build of core and install.
|
||||||
|
export TOXWORKDIR=.docker-tox
|
||||||
|
|
||||||
|
# install core lib
|
||||||
|
|
||||||
|
export PATH=/root/.cargo/bin:$PATH
|
||||||
|
cargo build --release -p deltachat_ffi
|
||||||
|
|
||||||
|
# Statically link against libdeltachat.a.
|
||||||
|
export DCC_RS_DEV=$(pwd)
|
||||||
|
|
||||||
|
# Configure access to a base python and to several python interpreters
|
||||||
|
# needed by tox below.
|
||||||
|
export PATH=$PATH:/opt/python/cp35-cp35m/bin
|
||||||
|
export PYTHONDONTWRITEBYTECODE=1
|
||||||
|
|
||||||
|
pushd python
|
||||||
|
# prepare a clean tox run
|
||||||
|
rm -rf tests/__pycache__
|
||||||
|
rm -rf src/deltachat/__pycache__
|
||||||
|
export PYTHONDONTWRITEBYTECODE=1
|
||||||
|
|
||||||
|
# run tox. The circle-ci project env-var-setting DCC_PY_LIVECONFIG
|
||||||
|
# allows running of "liveconfig" tests but for speed reasons
|
||||||
|
# we run them only for the highest python version we support
|
||||||
|
|
||||||
|
# we split out qr-tests run to minimize likelyness of flaky tests
|
||||||
|
# (some qr tests are pretty heavy in terms of send/received
|
||||||
|
# messages and rust's imap code likely has concurrency problems)
|
||||||
|
tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "not qr"
|
||||||
|
tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "qr"
|
||||||
|
unset DCC_PY_LIVECONFIG
|
||||||
|
#tox --workdir "$TOXWORKDIR" -p4 -e lint,py35,py36,doc
|
||||||
|
#tox --workdir "$TOXWORKDIR" -e auditwheels
|
||||||
|
popd
|
||||||
|
|
||||||
|
# if [ -n "$DOCS" ]; then
|
||||||
|
# echo -----------------------
|
||||||
|
# echo generating python docs
|
||||||
|
# echo -----------------------
|
||||||
|
# (cd python && tox --workdir "$TOXWORKDIR" -e doc)
|
||||||
|
# fi
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
#export RUST_TEST_THREADS=1
|
export RUST_TEST_THREADS=1
|
||||||
export RUST_BACKTRACE=1
|
export RUST_BACKTRACE=1
|
||||||
export RUSTFLAGS='--deny warnings'
|
export RUSTFLAGS='--deny warnings'
|
||||||
export OPT="--target=$TARGET"
|
export OPT="--target=$TARGET"
|
||||||
@@ -37,9 +37,7 @@ else
|
|||||||
export OPT_RELEASE_IGNORED="${OPT_RELEASE} -- --ignored"
|
export OPT_RELEASE_IGNORED="${OPT_RELEASE} -- --ignored"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run all the test configurations
|
# Run all the test configurations:
|
||||||
# RUSTC_WRAPPER=SCCACHE seems to destroy parallelism / prolong the test
|
|
||||||
unset RUSTC_WRAPPER
|
|
||||||
$CARGO_CMD $CARGO_SUBCMD $OPT
|
$CARGO_CMD $CARGO_SUBCMD $OPT
|
||||||
$CARGO_CMD $CARGO_SUBCMD $OPT_RELEASE
|
$CARGO_CMD $CARGO_SUBCMD $OPT_RELEASE
|
||||||
$CARGO_CMD $CARGO_SUBCMD $OPT_RELEASE_IGNORED
|
$CARGO_CMD $CARGO_SUBCMD $OPT_RELEASE_IGNORED
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat_ffi"
|
name = "deltachat_ffi"
|
||||||
version = "1.0.0-beta.18"
|
version = "1.0.0-beta.7"
|
||||||
description = "Deltachat FFI"
|
description = "Deltachat FFI"
|
||||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@@ -20,7 +20,6 @@ deltachat-provider-database = "0.2.1"
|
|||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
human-panic = "1.0.1"
|
human-panic = "1.0.1"
|
||||||
num-traits = "0.2.6"
|
num-traits = "0.2.6"
|
||||||
failure = "0.1.6"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["vendored", "nightly", "ringbuf"]
|
default = ["vendored", "nightly", "ringbuf"]
|
||||||
|
|||||||
@@ -994,20 +994,11 @@ uint32_t dc_get_chat_id_by_contact_id (dc_context_t* context, uint32_t co
|
|||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* ~~~
|
* ~~~
|
||||||
* char* blobdir = dc_get_blobdir(context);
|
|
||||||
* char* file_to_send = mprintf("%s/%s", blobdir, "send.mp4")
|
|
||||||
*
|
|
||||||
* dc_msg_t* msg = dc_msg_new(context, DC_MSG_VIDEO);
|
* dc_msg_t* msg = dc_msg_new(context, DC_MSG_VIDEO);
|
||||||
* dc_msg_set_file(msg, file_to_send, NULL);
|
* dc_msg_set_file(msg, "/file/to/send.mp4", NULL);
|
||||||
* dc_prepare_msg(context, chat_id, msg);
|
* dc_prepare_msg(context, chat_id, msg);
|
||||||
*
|
* // ... after /file/to/send.mp4 is ready:
|
||||||
* // ... create the file ...
|
|
||||||
*
|
|
||||||
* dc_send_msg(context, chat_id, msg);
|
* dc_send_msg(context, chat_id, msg);
|
||||||
*
|
|
||||||
* dc_msg_unref(msg);
|
|
||||||
* free(file_to_send);
|
|
||||||
* dc_str_unref(file_to_send);
|
|
||||||
* ~~~
|
* ~~~
|
||||||
*
|
*
|
||||||
* @memberof dc_context_t
|
* @memberof dc_context_t
|
||||||
@@ -1033,11 +1024,8 @@ uint32_t dc_prepare_msg (dc_context_t* context, uint32_t ch
|
|||||||
* Example:
|
* Example:
|
||||||
* ~~~
|
* ~~~
|
||||||
* dc_msg_t* msg = dc_msg_new(context, DC_MSG_IMAGE);
|
* dc_msg_t* msg = dc_msg_new(context, DC_MSG_IMAGE);
|
||||||
*
|
|
||||||
* dc_msg_set_file(msg, "/file/to/send.jpg", NULL);
|
* dc_msg_set_file(msg, "/file/to/send.jpg", NULL);
|
||||||
* dc_send_msg(context, chat_id, msg);
|
* dc_send_msg(context, chat_id, msg);
|
||||||
*
|
|
||||||
* dc_msg_unref(msg);
|
|
||||||
* ~~~
|
* ~~~
|
||||||
*
|
*
|
||||||
* @memberof dc_context_t
|
* @memberof dc_context_t
|
||||||
@@ -1108,9 +1096,9 @@ void dc_set_draft (dc_context_t* context, uint32_t ch
|
|||||||
* Add a message to the device-chat.
|
* Add a message to the device-chat.
|
||||||
* Device-messages usually contain update information
|
* Device-messages usually contain update information
|
||||||
* and some hints that are added during the program runs, multi-device etc.
|
* and some hints that are added during the program runs, multi-device etc.
|
||||||
* The device-message may be defined by a label;
|
*
|
||||||
* if a message with the same label was added or skipped before,
|
* Device-messages may be added from the core,
|
||||||
* the message is not added again, even if the message was deleted in between.
|
* however, with this function, this can be done from the ui as well.
|
||||||
* If needed, the device-chat is created before.
|
* If needed, the device-chat is created before.
|
||||||
*
|
*
|
||||||
* Sends the event #DC_EVENT_MSGS_CHANGED on success.
|
* Sends the event #DC_EVENT_MSGS_CHANGED on success.
|
||||||
@@ -1118,71 +1106,11 @@ void dc_set_draft (dc_context_t* context, uint32_t ch
|
|||||||
*
|
*
|
||||||
* @memberof dc_context_t
|
* @memberof dc_context_t
|
||||||
* @param context The context as created by dc_context_new().
|
* @param context The context as created by dc_context_new().
|
||||||
* @param label A unique name for the message to add.
|
|
||||||
* The label is typically not displayed to the user and
|
|
||||||
* must be created from the characters `A-Z`, `a-z`, `0-9`, `_` or `-`.
|
|
||||||
* If you pass NULL here, the message is added unconditionally.
|
|
||||||
* @param msg Message to be added to the device-chat.
|
* @param msg Message to be added to the device-chat.
|
||||||
* The message appears to the user as an incoming message.
|
* The message appears to the user as an incoming message.
|
||||||
* If you pass NULL here, only the given label will be added
|
* @return The ID of the added message.
|
||||||
* and block adding messages with that label in the future.
|
|
||||||
* @return The ID of the just added message,
|
|
||||||
* if the message was already added or no message to add is given, 0 is returned.
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
* ~~~
|
|
||||||
* dc_msg_t* welcome_msg = dc_msg_new(DC_MSG_TEXT);
|
|
||||||
* dc_msg_set_text(welcome_msg, "great that you give this app a try!");
|
|
||||||
*
|
|
||||||
* dc_msg_t* changelog_msg = dc_msg_new(DC_MSG_TEXT);
|
|
||||||
* dc_msg_set_text(changelog_msg, "we have added 3 new emojis :)");
|
|
||||||
*
|
|
||||||
* if (dc_add_device_msg(context, "welcome", welcome_msg)) {
|
|
||||||
* // do not add the changelog on a new installations -
|
|
||||||
* // not now and not when this code is executed again
|
|
||||||
* dc_add_device_msg(context, "update-123", NULL);
|
|
||||||
* } else {
|
|
||||||
* // welcome message was not added now, this is an oder installation,
|
|
||||||
* // add a changelog
|
|
||||||
* dc_add_device_msg(context, "update-123", changelog_msg);
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* dc_msg_unref(changelog_msg);
|
|
||||||
* dc_msg_unref(welome_msg);
|
|
||||||
* ~~~
|
|
||||||
*/
|
*/
|
||||||
uint32_t dc_add_device_msg (dc_context_t* context, const char* label, dc_msg_t* msg);
|
uint32_t dc_add_device_msg (dc_context_t* context, dc_msg_t* msg);
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Init device-messages and saved-messages chat.
|
|
||||||
* This function adds the device-chat and saved-messages chat
|
|
||||||
* and adds one or more welcome or update-messages.
|
|
||||||
* The ui can add messages on its own using dc_add_device_msg() -
|
|
||||||
* for ordering, either before or after or even without calling this function.
|
|
||||||
*
|
|
||||||
* Chat and message creation is done only once.
|
|
||||||
* So if the user has manually deleted things, they won't be re-created
|
|
||||||
* (however, not seen device messages are added and may re-create the device-chat).
|
|
||||||
*
|
|
||||||
* @memberof dc_context_t
|
|
||||||
* @param context The context as created by dc_context_new().
|
|
||||||
* @return None.
|
|
||||||
*/
|
|
||||||
void dc_update_device_chats (dc_context_t* context);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a device-message with a given label was ever added.
|
|
||||||
* Device-messages can be added dc_add_device_msg().
|
|
||||||
*
|
|
||||||
* @memberof dc_context_t
|
|
||||||
* @param context The context as created by dc_context_new().
|
|
||||||
* @param label Label of the message to check.
|
|
||||||
* @return 1=A message with this label was added at some point,
|
|
||||||
* 0=A message with this label was never added.
|
|
||||||
*/
|
|
||||||
int dc_was_device_msg_ever_added (dc_context_t* context, const char* label);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1962,7 +1890,7 @@ void dc_imex (dc_context_t* context, int what, c
|
|||||||
* }
|
* }
|
||||||
* while (!configure_succeeded())
|
* while (!configure_succeeded())
|
||||||
* }
|
* }
|
||||||
* dc_str_unref(file);
|
* free(file);
|
||||||
* }
|
* }
|
||||||
* ~~~
|
* ~~~
|
||||||
*
|
*
|
||||||
@@ -2617,25 +2545,6 @@ dc_lot_t* dc_chatlist_get_summary (const dc_chatlist_t* chatlist, siz
|
|||||||
dc_context_t* dc_chatlist_get_context (dc_chatlist_t* chatlist);
|
dc_context_t* dc_chatlist_get_context (dc_chatlist_t* chatlist);
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get info summary for a chat, in json format.
|
|
||||||
*
|
|
||||||
* The returned json string has the following key/values:
|
|
||||||
*
|
|
||||||
* id: chat id
|
|
||||||
* name: chat/group name
|
|
||||||
* color: color of this chat
|
|
||||||
* last-message-from: who sent the last message
|
|
||||||
* last-message-text: message (truncated)
|
|
||||||
* last-message-state: DC_STATE* constant
|
|
||||||
* last-message-date:
|
|
||||||
* avatar-path: path-to-blobfile
|
|
||||||
* is_verified: yes/no
|
|
||||||
|
|
||||||
* @return a utf8-encoded json string containing all requested info. Must be freed using dc_str_unref(). NULL is never returned.
|
|
||||||
*/
|
|
||||||
char* dc_chat_get_info_json (dc_context_t* context, size_t chat_id);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class dc_chat_t
|
* @class dc_chat_t
|
||||||
*
|
*
|
||||||
@@ -2829,7 +2738,9 @@ int dc_chat_is_self_talk (const dc_chat_t* chat);
|
|||||||
* From the ui view, device-talks are not very special,
|
* From the ui view, device-talks are not very special,
|
||||||
* the user can delete and forward messages, archive the chat, set notifications etc.
|
* the user can delete and forward messages, archive the chat, set notifications etc.
|
||||||
*
|
*
|
||||||
* Messages can be added to the device-talk using dc_add_device_msg()
|
* 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
|
* @memberof dc_chat_t
|
||||||
* @param chat The chat object.
|
* @param chat The chat object.
|
||||||
@@ -3645,15 +3556,11 @@ int dc_contact_is_blocked (const dc_contact_t* contact);
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a contact was verified. E.g. by a secure-join QR code scan
|
* Same as dc_contact_is_verified() but allows speeding up things
|
||||||
* and if the key has not changed since this verification.
|
* by adding the peerstate belonging to the contact.
|
||||||
|
* If you do not have the peerstate available, it is loaded automatically.
|
||||||
*
|
*
|
||||||
* The UI may draw a checkbox or something like that beside verified contacts.
|
* @private @memberof dc_context_t
|
||||||
*
|
|
||||||
* @memberof dc_contact_t
|
|
||||||
* @param contact The contact object.
|
|
||||||
* @return 0: contact is not verified.
|
|
||||||
* 2: SELF and contact have verified their fingerprints in both directions; in the UI typically checkmarks are shown.
|
|
||||||
*/
|
*/
|
||||||
int dc_contact_is_verified (dc_contact_t* contact);
|
int dc_contact_is_verified (dc_contact_t* contact);
|
||||||
|
|
||||||
@@ -4054,6 +3961,11 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
*/
|
*/
|
||||||
#define DC_CERTCK_STRICT 1
|
#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
|
* Accept invalid certificates, including self-signed ones
|
||||||
* or having incorrect hostname.
|
* or having incorrect hostname.
|
||||||
@@ -4438,16 +4350,6 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
#define DC_EVENT_SECUREJOIN_JOINER_PROGRESS 2061
|
#define DC_EVENT_SECUREJOIN_JOINER_PROGRESS 2061
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This event is sent out to the inviter when a joiner successfully joined a group.
|
|
||||||
*
|
|
||||||
* @param data1 (int) chat_id
|
|
||||||
* @param data2 (int) contact_id
|
|
||||||
* @return 0
|
|
||||||
*/
|
|
||||||
#define DC_EVENT_SECUREJOIN_MEMBER_ADDED 2062
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @}
|
* @}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -25,15 +25,13 @@ use num_traits::{FromPrimitive, ToPrimitive};
|
|||||||
use deltachat::constants::DC_MSG_ID_LAST_SPECIAL;
|
use deltachat::constants::DC_MSG_ID_LAST_SPECIAL;
|
||||||
use deltachat::contact::Contact;
|
use deltachat::contact::Contact;
|
||||||
use deltachat::context::Context;
|
use deltachat::context::Context;
|
||||||
|
use deltachat::dc_tools::{
|
||||||
|
as_path, dc_strdup, to_opt_string_lossy, to_string_lossy, OsStrExt, StrExt,
|
||||||
|
};
|
||||||
use deltachat::message::MsgId;
|
use deltachat::message::MsgId;
|
||||||
use deltachat::stock::StockMessage;
|
use deltachat::stock::StockMessage;
|
||||||
use deltachat::*;
|
use deltachat::*;
|
||||||
|
|
||||||
mod dc_array;
|
|
||||||
|
|
||||||
mod string;
|
|
||||||
use self::string::*;
|
|
||||||
|
|
||||||
// as C lacks a good and portable error handling,
|
// as C lacks a good and portable error handling,
|
||||||
// in general, the C Interface is forgiving wrt to bad parameters.
|
// in general, the C Interface is forgiving wrt to bad parameters.
|
||||||
// - objects returned by some functions
|
// - objects returned by some functions
|
||||||
@@ -177,15 +175,6 @@ impl ContextWrapper {
|
|||||||
contact_id as uintptr_t,
|
contact_id as uintptr_t,
|
||||||
progress as uintptr_t,
|
progress as uintptr_t,
|
||||||
),
|
),
|
||||||
Event::SecurejoinMemberAdded {
|
|
||||||
chat_id,
|
|
||||||
contact_id,
|
|
||||||
} => ffi_cb(
|
|
||||||
self,
|
|
||||||
event_id,
|
|
||||||
chat_id as uintptr_t,
|
|
||||||
contact_id as uintptr_t,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => 0,
|
None => 0,
|
||||||
@@ -472,7 +461,7 @@ pub unsafe extern "C" fn dc_perform_imap_jobs(context: *mut dc_context_t) {
|
|||||||
}
|
}
|
||||||
let ffi_context = &*context;
|
let ffi_context = &*context;
|
||||||
ffi_context
|
ffi_context
|
||||||
.with_inner(|ctx| job::perform_inbox_jobs(ctx))
|
.with_inner(|ctx| job::perform_imap_jobs(ctx))
|
||||||
.unwrap_or(())
|
.unwrap_or(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,20 +473,19 @@ pub unsafe extern "C" fn dc_perform_imap_fetch(context: *mut dc_context_t) {
|
|||||||
}
|
}
|
||||||
let ffi_context = &*context;
|
let ffi_context = &*context;
|
||||||
ffi_context
|
ffi_context
|
||||||
.with_inner(|ctx| job::perform_inbox_fetch(ctx))
|
.with_inner(|ctx| job::perform_imap_fetch(ctx))
|
||||||
.unwrap_or(())
|
.unwrap_or(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_perform_imap_idle(context: *mut dc_context_t) {
|
pub unsafe extern "C" fn dc_perform_imap_idle(context: *mut dc_context_t) {
|
||||||
// TODO rename function in co-ordination with UIs
|
|
||||||
if context.is_null() {
|
if context.is_null() {
|
||||||
eprintln!("ignoring careless call to dc_perform_imap_idle()");
|
eprintln!("ignoring careless call to dc_perform_imap_idle()");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let ffi_context = &*context;
|
let ffi_context = &*context;
|
||||||
ffi_context
|
ffi_context
|
||||||
.with_inner(|ctx| job::perform_inbox_idle(ctx))
|
.with_inner(|ctx| job::perform_imap_idle(ctx))
|
||||||
.unwrap_or(())
|
.unwrap_or(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -509,7 +497,7 @@ pub unsafe extern "C" fn dc_interrupt_imap_idle(context: *mut dc_context_t) {
|
|||||||
}
|
}
|
||||||
let ffi_context = &*context;
|
let ffi_context = &*context;
|
||||||
ffi_context
|
ffi_context
|
||||||
.with_inner(|ctx| job::interrupt_inbox_idle(ctx, true))
|
.with_inner(|ctx| job::interrupt_imap_idle(ctx))
|
||||||
.unwrap_or(())
|
.unwrap_or(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -824,65 +812,19 @@ pub unsafe extern "C" fn dc_set_draft(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_add_device_msg(
|
pub unsafe extern "C" fn dc_add_device_msg(context: *mut dc_context_t, msg: *mut dc_msg_t) -> u32 {
|
||||||
context: *mut dc_context_t,
|
if context.is_null() || msg.is_null() {
|
||||||
label: *const libc::c_char,
|
|
||||||
msg: *mut dc_msg_t,
|
|
||||||
) -> u32 {
|
|
||||||
if context.is_null() || (label.is_null() && msg.is_null()) {
|
|
||||||
eprintln!("ignoring careless call to dc_add_device_msg()");
|
eprintln!("ignoring careless call to dc_add_device_msg()");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
let ffi_context = &mut *context;
|
let ffi_context = &mut *context;
|
||||||
let msg = if msg.is_null() {
|
let ffi_msg = &mut *msg;
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let ffi_msg: &mut MessageWrapper = &mut *msg;
|
|
||||||
Some(&mut ffi_msg.message)
|
|
||||||
};
|
|
||||||
ffi_context
|
ffi_context
|
||||||
.with_inner(|ctx| {
|
.with_inner(|ctx| {
|
||||||
chat::add_device_msg(
|
chat::add_device_msg(ctx, &mut ffi_msg.message)
|
||||||
ctx,
|
|
||||||
to_opt_string_lossy(label).as_ref().map(|x| x.as_str()),
|
|
||||||
msg,
|
|
||||||
)
|
|
||||||
.unwrap_or_log_default(ctx, "Failed to add device message")
|
|
||||||
})
|
|
||||||
.map(|msg_id| msg_id.to_u32())
|
|
||||||
.unwrap_or(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn dc_update_device_chats(context: *mut dc_context_t) {
|
|
||||||
if context.is_null() {
|
|
||||||
eprintln!("ignoring careless call to dc_update_device_chats()");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let ffi_context = &mut *context;
|
|
||||||
ffi_context
|
|
||||||
.with_inner(|ctx| {
|
|
||||||
ctx.update_device_chats()
|
|
||||||
.unwrap_or_log_default(ctx, "Failed to add device message")
|
.unwrap_or_log_default(ctx, "Failed to add device message")
|
||||||
})
|
})
|
||||||
.unwrap_or(())
|
.map(|msg_id| msg_id.to_u32())
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn dc_was_device_msg_ever_added(
|
|
||||||
context: *mut dc_context_t,
|
|
||||||
label: *const libc::c_char,
|
|
||||||
) -> libc::c_int {
|
|
||||||
if context.is_null() || label.is_null() {
|
|
||||||
eprintln!("ignoring careless call to dc_was_device_msg_ever_added()");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
let ffi_context = &mut *context;
|
|
||||||
ffi_context
|
|
||||||
.with_inner(|ctx| {
|
|
||||||
chat::was_device_msg_ever_added(ctx, &to_string_lossy(label)).unwrap_or(false)
|
|
||||||
as libc::c_int
|
|
||||||
})
|
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2379,27 +2321,6 @@ pub unsafe extern "C" fn dc_chat_is_sending_locations(chat: *mut dc_chat_t) -> l
|
|||||||
ffi_chat.chat.is_sending_locations() as libc::c_int
|
ffi_chat.chat.is_sending_locations() as libc::c_int
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn dc_chat_get_info_json(
|
|
||||||
context: *mut dc_context_t,
|
|
||||||
chat_id: u32,
|
|
||||||
) -> *mut libc::c_char {
|
|
||||||
if context.is_null() {
|
|
||||||
eprintln!("ignoring careless call to dc_chat_get_info_json()");
|
|
||||||
return "".strdup();
|
|
||||||
}
|
|
||||||
let ffi_context = &*context;
|
|
||||||
ffi_context
|
|
||||||
.with_inner(|ctx| match chat::get_info_json(ctx, chat_id) {
|
|
||||||
Ok(s) => s.strdup(),
|
|
||||||
Err(err) => {
|
|
||||||
error!(ctx, "get_info_json({}) returned: {}", chat_id, err);
|
|
||||||
return "".strdup();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|_| "".strdup())
|
|
||||||
}
|
|
||||||
|
|
||||||
// dc_msg_t
|
// dc_msg_t
|
||||||
|
|
||||||
/// FFI struct for [dc_msg_t]
|
/// FFI struct for [dc_msg_t]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ extern crate deltachat_provider_database;
|
|||||||
|
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
use crate::string::{to_string_lossy, StrExt};
|
use deltachat::dc_tools::{to_string_lossy, StrExt};
|
||||||
use deltachat_provider_database::StatusState;
|
use deltachat_provider_database::StatusState;
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
|||||||
@@ -1,350 +0,0 @@
|
|||||||
use failure::Fail;
|
|
||||||
use std::ffi::{CStr, CString};
|
|
||||||
|
|
||||||
/// Duplicates a string
|
|
||||||
///
|
|
||||||
/// returns an empty string if NULL is given, never returns NULL (exits on errors)
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust,norun
|
|
||||||
/// use deltachat::dc_tools::{dc_strdup, to_string_lossy};
|
|
||||||
/// unsafe {
|
|
||||||
/// let str_a = b"foobar\x00" as *const u8 as *const libc::c_char;
|
|
||||||
/// let str_a_copy = dc_strdup(str_a);
|
|
||||||
/// assert_eq!(to_string_lossy(str_a_copy), "foobar");
|
|
||||||
/// assert_ne!(str_a, str_a_copy);
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
|
|
||||||
let ret: *mut libc::c_char;
|
|
||||||
if !s.is_null() {
|
|
||||||
ret = libc::strdup(s);
|
|
||||||
assert!(!ret.is_null());
|
|
||||||
} else {
|
|
||||||
ret = libc::calloc(1, 1) as *mut libc::c_char;
|
|
||||||
assert!(!ret.is_null());
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Error type for the [OsStrExt] trait
|
|
||||||
#[derive(Debug, Fail, PartialEq)]
|
|
||||||
pub enum CStringError {
|
|
||||||
/// The string contains an interior null byte
|
|
||||||
#[fail(display = "String contains an interior null byte")]
|
|
||||||
InteriorNullByte,
|
|
||||||
/// The string is not valid Unicode
|
|
||||||
#[fail(display = "String is not valid unicode")]
|
|
||||||
NotUnicode,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extra convenience methods on [std::ffi::OsStr] to work with `*libc::c_char`.
|
|
||||||
///
|
|
||||||
/// The primary function of this trait is to more easily convert
|
|
||||||
/// [OsStr], [OsString] or [Path] into pointers to C strings. This always
|
|
||||||
/// allocates a new string since it is very common for the source
|
|
||||||
/// string not to have the required terminal null byte.
|
|
||||||
///
|
|
||||||
/// It is implemented for `AsRef<std::ffi::OsStr>>` trait, which
|
|
||||||
/// allows any type which implements this trait to transparently use
|
|
||||||
/// this. This is how the conversion for [Path] works.
|
|
||||||
///
|
|
||||||
/// [OsStr]: std::ffi::OsStr
|
|
||||||
/// [OsString]: std::ffi::OsString
|
|
||||||
/// [Path]: std::path::Path
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use deltachat::dc_tools::{dc_strdup, OsStrExt};
|
|
||||||
/// let path = std::path::Path::new("/some/path");
|
|
||||||
/// let path_c = path.to_c_string().unwrap();
|
|
||||||
/// unsafe {
|
|
||||||
/// let mut c_ptr: *mut libc::c_char = dc_strdup(path_c.as_ptr());
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub trait OsStrExt {
|
|
||||||
/// Convert a [std::ffi::OsStr] to an [std::ffi::CString]
|
|
||||||
///
|
|
||||||
/// This is useful to convert e.g. a [std::path::Path] to
|
|
||||||
/// [*libc::c_char] by using
|
|
||||||
/// [Path::as_os_str()](std::path::Path::as_os_str) and
|
|
||||||
/// [CStr::as_ptr()](std::ffi::CStr::as_ptr).
|
|
||||||
///
|
|
||||||
/// This returns [CString] and not [&CStr] because not all [OsStr]
|
|
||||||
/// slices end with a null byte, particularly those coming from
|
|
||||||
/// [Path] do not have a null byte and having to handle this as
|
|
||||||
/// the caller would defeat the point of this function.
|
|
||||||
///
|
|
||||||
/// On Windows this requires that the [OsStr] contains valid
|
|
||||||
/// unicode, which should normally be the case for a [Path].
|
|
||||||
///
|
|
||||||
/// [CString]: std::ffi::CString
|
|
||||||
/// [CStr]: std::ffi::CStr
|
|
||||||
/// [OsStr]: std::ffi::OsStr
|
|
||||||
/// [Path]: std::path::Path
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// Since a C `*char` is terminated by a NULL byte this conversion
|
|
||||||
/// will fail, when the [OsStr] has an interior null byte. The
|
|
||||||
/// function will return
|
|
||||||
/// `[Err]([CStringError::InteriorNullByte])`. When converting
|
|
||||||
/// from a [Path] it should be safe to
|
|
||||||
/// [`.unwrap()`](std::result::Result::unwrap) this anyway since a
|
|
||||||
/// [Path] should not contain interior null bytes.
|
|
||||||
///
|
|
||||||
/// On windows when the string contains invalid Unicode
|
|
||||||
/// `[Err]([CStringError::NotUnicode])` is returned.
|
|
||||||
fn to_c_string(&self) -> Result<CString, CStringError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: AsRef<std::ffi::OsStr>> OsStrExt for T {
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
fn to_c_string(&self) -> Result<CString, CStringError> {
|
|
||||||
use std::os::unix::ffi::OsStrExt;
|
|
||||||
CString::new(self.as_ref().as_bytes()).map_err(|err| match err {
|
|
||||||
std::ffi::NulError { .. } => CStringError::InteriorNullByte,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn to_c_string(&self) -> Result<CString, CStringError> {
|
|
||||||
os_str_to_c_string_unicode(&self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation for os_str_to_c_string on windows.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn os_str_to_c_string_unicode(
|
|
||||||
os_str: &dyn AsRef<std::ffi::OsStr>,
|
|
||||||
) -> Result<CString, CStringError> {
|
|
||||||
match os_str.as_ref().to_str() {
|
|
||||||
Some(val) => CString::new(val.as_bytes()).map_err(|err| match err {
|
|
||||||
std::ffi::NulError { .. } => CStringError::InteriorNullByte,
|
|
||||||
}),
|
|
||||||
None => Err(CStringError::NotUnicode),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convenience methods/associated functions for working with [CString]
|
|
||||||
///
|
|
||||||
/// This is helps transitioning from unsafe code.
|
|
||||||
pub trait CStringExt {
|
|
||||||
/// Create a new [CString], yolo style
|
|
||||||
///
|
|
||||||
/// This unwrap the result, panicking when there are embedded NULL
|
|
||||||
/// bytes.
|
|
||||||
fn yolo<T: Into<Vec<u8>>>(t: T) -> CString {
|
|
||||||
CString::new(t).expect("String contains null byte, can not be CString")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CStringExt for CString {}
|
|
||||||
|
|
||||||
/// Convenience methods to make transitioning from raw C strings easier.
|
|
||||||
///
|
|
||||||
/// To interact with (legacy) C APIs we often need to convert from
|
|
||||||
/// Rust strings to raw C strings. This can be clumsy to do correctly
|
|
||||||
/// and the compiler sometimes allows it in an unsafe way. These
|
|
||||||
/// methods make it more succinct and help you get it right.
|
|
||||||
pub trait StrExt {
|
|
||||||
/// Allocate a new raw C `*char` version of this string.
|
|
||||||
///
|
|
||||||
/// This allocates a new raw C string which must be freed using
|
|
||||||
/// `free`. It takes care of some common pitfalls with using
|
|
||||||
/// [CString.as_ptr].
|
|
||||||
///
|
|
||||||
/// [CString.as_ptr]: std::ffi::CString.as_ptr
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// This function will panic when the original string contains an
|
|
||||||
/// interior null byte as this can not be represented in raw C
|
|
||||||
/// strings.
|
|
||||||
unsafe fn strdup(&self) -> *mut libc::c_char;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: AsRef<str>> StrExt for T {
|
|
||||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
|
||||||
let tmp = CString::yolo(self.as_ref());
|
|
||||||
dc_strdup(tmp.as_ptr())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_string_lossy(s: *const libc::c_char) -> String {
|
|
||||||
if s.is_null() {
|
|
||||||
return "".into();
|
|
||||||
}
|
|
||||||
|
|
||||||
let cstr = unsafe { CStr::from_ptr(s) };
|
|
||||||
|
|
||||||
cstr.to_string_lossy().to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
|
|
||||||
if s.is_null() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(to_string_lossy(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert a C `*char` pointer to a [std::path::Path] slice.
|
|
||||||
///
|
|
||||||
/// This converts a `*libc::c_char` pointer to a [Path] slice. This
|
|
||||||
/// essentially has to convert the pointer to [std::ffi::OsStr] to do
|
|
||||||
/// so and thus is the inverse of [OsStrExt::to_c_string]. Just like
|
|
||||||
/// [OsStrExt::to_c_string] requires valid Unicode on Windows, this
|
|
||||||
/// requires that the pointer contains valid UTF-8 on Windows.
|
|
||||||
///
|
|
||||||
/// Because this returns a reference the [Path] silce can not outlive
|
|
||||||
/// the original pointer.
|
|
||||||
///
|
|
||||||
/// [Path]: std::path::Path
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
|
||||||
assert!(!s.is_null(), "cannot be used on null pointers");
|
|
||||||
use std::os::unix::ffi::OsStrExt;
|
|
||||||
unsafe {
|
|
||||||
let c_str = std::ffi::CStr::from_ptr(s).to_bytes();
|
|
||||||
let os_str = std::ffi::OsStr::from_bytes(c_str);
|
|
||||||
std::path::Path::new(os_str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// as_path() implementation for windows, documented above.
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
|
||||||
as_path_unicode(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation for as_path() on Windows.
|
|
||||||
//
|
|
||||||
// Having this as a separate function means it can be tested on unix
|
|
||||||
// too.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn as_path_unicode<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
|
||||||
assert!(!s.is_null(), "cannot be used on null pointers");
|
|
||||||
|
|
||||||
let cstr = unsafe { CStr::from_ptr(s) };
|
|
||||||
let str = cstr.to_str().unwrap_or_else(|err| panic!("{}", err));
|
|
||||||
|
|
||||||
std::path::Path::new(str)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use libc::{free, strcmp};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_os_str_to_c_string_cwd() {
|
|
||||||
let some_dir = std::env::current_dir().unwrap();
|
|
||||||
some_dir.as_os_str().to_c_string().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_os_str_to_c_string_unicode() {
|
|
||||||
let some_str = String::from("/some/valid/utf8");
|
|
||||||
let some_dir = std::path::Path::new(&some_str);
|
|
||||||
assert_eq!(
|
|
||||||
some_dir.as_os_str().to_c_string().unwrap(),
|
|
||||||
CString::new("/some/valid/utf8").unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_os_str_to_c_string_nul() {
|
|
||||||
let some_str = std::ffi::OsString::from("foo\x00bar");
|
|
||||||
assert_eq!(
|
|
||||||
some_str.to_c_string().err().unwrap(),
|
|
||||||
CStringError::InteriorNullByte
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_path_to_c_string_cwd() {
|
|
||||||
let some_dir = std::env::current_dir().unwrap();
|
|
||||||
some_dir.to_c_string().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_path_to_c_string_unicode() {
|
|
||||||
let some_str = String::from("/some/valid/utf8");
|
|
||||||
let some_dir = std::path::Path::new(&some_str);
|
|
||||||
assert_eq!(
|
|
||||||
some_dir.as_os_str().to_c_string().unwrap(),
|
|
||||||
CString::new("/some/valid/utf8").unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_os_str_to_c_string_unicode_fn() {
|
|
||||||
let some_str = std::ffi::OsString::from("foo");
|
|
||||||
assert_eq!(
|
|
||||||
os_str_to_c_string_unicode(&some_str).unwrap(),
|
|
||||||
CString::new("foo").unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_path_to_c_string_unicode_fn() {
|
|
||||||
let some_str = String::from("/some/path");
|
|
||||||
let some_path = std::path::Path::new(&some_str);
|
|
||||||
assert_eq!(
|
|
||||||
os_str_to_c_string_unicode(&some_path).unwrap(),
|
|
||||||
CString::new("/some/path").unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_os_str_to_c_string_unicode_fn_nul() {
|
|
||||||
let some_str = std::ffi::OsString::from("fooz\x00bar");
|
|
||||||
assert_eq!(
|
|
||||||
os_str_to_c_string_unicode(&some_str).err().unwrap(),
|
|
||||||
CStringError::InteriorNullByte
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_as_path() {
|
|
||||||
let some_path = CString::new("/some/path").unwrap();
|
|
||||||
let ptr = some_path.as_ptr();
|
|
||||||
assert_eq!(as_path(ptr), std::ffi::OsString::from("/some/path"))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_as_path_unicode_fn() {
|
|
||||||
let some_path = CString::new("/some/path").unwrap();
|
|
||||||
let ptr = some_path.as_ptr();
|
|
||||||
assert_eq!(as_path_unicode(ptr), std::ffi::OsString::from("/some/path"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cstring_yolo() {
|
|
||||||
assert_eq!(CString::new("hello").unwrap(), CString::yolo("hello"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_strdup_str() {
|
|
||||||
unsafe {
|
|
||||||
let s = "hello".strdup();
|
|
||||||
let cmp = strcmp(s, b"hello\x00" as *const u8 as *const libc::c_char);
|
|
||||||
free(s as *mut libc::c_void);
|
|
||||||
assert_eq!(cmp, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_strdup_string() {
|
|
||||||
unsafe {
|
|
||||||
let s = String::from("hello").strdup();
|
|
||||||
let cmp = strcmp(s, b"hello\x00" as *const u8 as *const libc::c_char);
|
|
||||||
free(s as *mut libc::c_void);
|
|
||||||
assert_eq!(cmp, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,11 +19,12 @@ use deltachat::peerstate::*;
|
|||||||
use deltachat::qr::*;
|
use deltachat::qr::*;
|
||||||
use deltachat::sql;
|
use deltachat::sql;
|
||||||
use deltachat::Event;
|
use deltachat::Event;
|
||||||
|
use libc::free;
|
||||||
|
|
||||||
/// Reset database tables. This function is called from Core cmdline.
|
/// Reset database tables. This function is called from Core cmdline.
|
||||||
/// Argument is a bitmask, executing single or multiple actions in one call.
|
/// Argument is a bitmask, executing single or multiple actions in one call.
|
||||||
/// e.g. bitmask 7 triggers actions definded with bits 1, 2 and 4.
|
/// e.g. bitmask 7 triggers actions definded with bits 1, 2 and 4.
|
||||||
pub fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
|
pub unsafe fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
|
||||||
info!(context, "Resetting tables ({})...", bits);
|
info!(context, "Resetting tables ({})...", bits);
|
||||||
if 0 != bits & 1 {
|
if 0 != bits & 1 {
|
||||||
sql::execute(context, &context.sql, "DELETE FROM jobs;", params![]).unwrap();
|
sql::execute(context, &context.sql, "DELETE FROM jobs;", params![]).unwrap();
|
||||||
@@ -94,9 +95,7 @@ pub fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
|
|||||||
fn dc_poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<(), Error> {
|
fn dc_poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<(), Error> {
|
||||||
let data = dc_read_file(context, filename)?;
|
let data = dc_read_file(context, filename)?;
|
||||||
|
|
||||||
if let Err(err) = dc_receive_imf(context, &data, "import", 0, 0) {
|
unsafe { dc_receive_imf(context, &data, "import", 0, 0) };
|
||||||
println!("dc_receive_imf errored: {:?}", err);
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,19 +107,18 @@ fn dc_poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<(),
|
|||||||
/// @param context The context as created by dc_context_new().
|
/// @param context The context as created by dc_context_new().
|
||||||
/// @param spec The file or directory to import. NULL for the last command.
|
/// @param spec The file or directory to import. NULL for the last command.
|
||||||
/// @return 1=success, 0=error.
|
/// @return 1=success, 0=error.
|
||||||
fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int {
|
fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int {
|
||||||
if !context.sql.is_open() {
|
if !context.sql.is_open() {
|
||||||
error!(context, "Import: Database not opened.");
|
error!(context, "Import: Database not opened.");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let real_spec: String;
|
||||||
let mut read_cnt = 0;
|
let mut read_cnt = 0;
|
||||||
|
|
||||||
let real_spec: String;
|
|
||||||
|
|
||||||
/* if `spec` is given, remember it for later usage; if it is not given, try to use the last one */
|
/* if `spec` is given, remember it for later usage; if it is not given, try to use the last one */
|
||||||
if let Some(spec) = spec {
|
if !spec.is_null() {
|
||||||
real_spec = spec.to_string();
|
real_spec = to_string_lossy(spec);
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.set_raw_config(context, "import_spec", Some(&real_spec))
|
.set_raw_config(context, "import_spec", Some(&real_spec))
|
||||||
@@ -134,8 +132,10 @@ fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int {
|
|||||||
real_spec = rs.unwrap();
|
real_spec = rs.unwrap();
|
||||||
}
|
}
|
||||||
if let Some(suffix) = dc_get_filesuffix_lc(&real_spec) {
|
if let Some(suffix) = dc_get_filesuffix_lc(&real_spec) {
|
||||||
if suffix == "eml" && dc_poke_eml_file(context, &real_spec).is_ok() {
|
if suffix == "eml" {
|
||||||
read_cnt += 1
|
if dc_poke_eml_file(context, &real_spec).is_ok() {
|
||||||
|
read_cnt += 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* import a directory */
|
/* import a directory */
|
||||||
@@ -176,7 +176,7 @@ fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int {
|
|||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
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, msg.get_from_id()).expect("invalid contact");
|
||||||
let contact_name = contact.get_name();
|
let contact_name = contact.get_name();
|
||||||
let contact_id = contact.get_id();
|
let contact_id = contact.get_id();
|
||||||
@@ -221,7 +221,7 @@ fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Error> {
|
unsafe fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Error> {
|
||||||
let mut lines_out = 0;
|
let mut lines_out = 0;
|
||||||
for &msg_id in msglist {
|
for &msg_id in msglist {
|
||||||
if msg_id.is_daymarker() {
|
if msg_id.is_daymarker() {
|
||||||
@@ -252,7 +252,7 @@ fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn log_contactlist(context: &Context, contacts: &Vec<u32>) {
|
unsafe fn log_contactlist(context: &Context, contacts: &Vec<u32>) {
|
||||||
let mut contacts = contacts.clone();
|
let mut contacts = contacts.clone();
|
||||||
if !contacts.contains(&1) {
|
if !contacts.contains(&1) {
|
||||||
contacts.push(1);
|
contacts.push(1);
|
||||||
@@ -304,7 +304,7 @@ fn chat_prefix(chat: &Chat) -> &'static str {
|
|||||||
chat.typ.into()
|
chat.typ.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
||||||
let chat_id = *context.cmdline_sel_chat_id.read().unwrap();
|
let chat_id = *context.cmdline_sel_chat_id.read().unwrap();
|
||||||
let mut sel_chat = if chat_id > 0 {
|
let mut sel_chat = if chat_id > 0 {
|
||||||
Chat::load_from_db(context, chat_id).ok()
|
Chat::load_from_db(context, chat_id).ok()
|
||||||
@@ -315,6 +315,11 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
|||||||
let mut args = line.splitn(3, ' ');
|
let mut args = line.splitn(3, ' ');
|
||||||
let arg0 = args.next().unwrap_or_default();
|
let arg0 = args.next().unwrap_or_default();
|
||||||
let arg1 = 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() as *const _
|
||||||
|
};
|
||||||
let arg2 = args.next().unwrap_or_default();
|
let arg2 = args.next().unwrap_or_default();
|
||||||
|
|
||||||
let blobdir = context.get_blobdir();
|
let blobdir = context.get_blobdir();
|
||||||
@@ -464,7 +469,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
"poke" => {
|
"poke" => {
|
||||||
ensure!(0 != poke_spec(context, Some(arg1)), "Poke failed");
|
ensure!(0 != poke_spec(context, arg1_c), "Poke failed");
|
||||||
}
|
}
|
||||||
"reset" => {
|
"reset" => {
|
||||||
ensure!(!arg1.is_empty(), "Argument <bits> missing: 1=jobs, 2=peerstates, 4=private keys, 8=rest but server config");
|
ensure!(!arg1.is_empty(), "Argument <bits> missing: 1=jobs, 2=peerstates, 4=private keys, 8=rest but server config");
|
||||||
@@ -491,7 +496,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
|||||||
println!("{:#?}", context.get_info());
|
println!("{:#?}", context.get_info());
|
||||||
}
|
}
|
||||||
"interrupt" => {
|
"interrupt" => {
|
||||||
interrupt_inbox_idle(context, true);
|
interrupt_imap_idle(context);
|
||||||
}
|
}
|
||||||
"maybenetwork" => {
|
"maybenetwork" => {
|
||||||
maybe_network(context);
|
maybe_network(context);
|
||||||
@@ -583,7 +588,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
|||||||
let members = chat::get_chat_contacts(context, sel_chat.id);
|
let members = chat::get_chat_contacts(context, sel_chat.id);
|
||||||
let subtitle = if sel_chat.is_device_talk() {
|
let subtitle = if sel_chat.is_device_talk() {
|
||||||
"device-talk".to_string()
|
"device-talk".to_string()
|
||||||
} else if sel_chat.get_type() == Chattype::Single && !members.is_empty() {
|
} else if sel_chat.get_type() == Chattype::Single && members.len() >= 1 {
|
||||||
let contact = Contact::get_by_id(context, members[0])?;
|
let contact = Contact::get_by_id(context, members[0])?;
|
||||||
contact.get_addr().to_string()
|
contact.get_addr().to_string()
|
||||||
} else {
|
} else {
|
||||||
@@ -832,10 +837,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
|||||||
);
|
);
|
||||||
let mut msg = Message::new(Viewtype::Text);
|
let mut msg = Message::new(Viewtype::Text);
|
||||||
msg.set_text(Some(arg1.to_string()));
|
msg.set_text(Some(arg1.to_string()));
|
||||||
chat::add_device_msg(context, None, Some(&mut msg))?;
|
chat::add_device_msg(context, &mut msg)?;
|
||||||
}
|
|
||||||
"updatedevicechats" => {
|
|
||||||
context.update_device_chats()?;
|
|
||||||
}
|
}
|
||||||
"listmedia" => {
|
"listmedia" => {
|
||||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||||
@@ -860,7 +862,11 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
|||||||
"archive" | "unarchive" => {
|
"archive" | "unarchive" => {
|
||||||
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
|
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
|
||||||
let chat_id = arg1.parse()?;
|
let chat_id = arg1.parse()?;
|
||||||
chat::archive(context, chat_id, arg0 == "archive")?;
|
chat::archive(
|
||||||
|
context,
|
||||||
|
chat_id,
|
||||||
|
if arg0 == "archive" { true } else { false },
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
"delchat" => {
|
"delchat" => {
|
||||||
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
|
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
|
||||||
@@ -881,7 +887,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
|||||||
}
|
}
|
||||||
"forward" => {
|
"forward" => {
|
||||||
ensure!(
|
ensure!(
|
||||||
!arg1.is_empty() && !arg2.is_empty(),
|
!arg1.is_empty() && arg2.is_empty(),
|
||||||
"Arguments <msg-id> <chat-id> expected"
|
"Arguments <msg-id> <chat-id> expected"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -938,14 +944,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
|||||||
let contact = Contact::get_by_id(context, contact_id)?;
|
let contact = Contact::get_by_id(context, contact_id)?;
|
||||||
let name_n_addr = contact.get_name_n_addr();
|
let name_n_addr = contact.get_name_n_addr();
|
||||||
|
|
||||||
let mut res = format!(
|
let mut res = format!("Contact info for: {}:\n\n", name_n_addr);
|
||||||
"Contact info for: {}:\nIcon: {}\n",
|
|
||||||
name_n_addr,
|
|
||||||
match contact.get_profile_image(context) {
|
|
||||||
Some(image) => image.to_str().unwrap().to_string(),
|
|
||||||
None => "NoIcon".to_string(),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
res += &Contact::get_encrinfo(context, contact_id)?;
|
res += &Contact::get_encrinfo(context, contact_id)?;
|
||||||
|
|
||||||
@@ -1012,5 +1011,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
|||||||
_ => bail!("Unknown command: \"{}\" type ? for help.", arg0),
|
_ => bail!("Unknown command: \"{}\" type ? for help.", arg0),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(arg1_c as *mut _);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,11 +150,11 @@ fn start_threads(c: Arc<RwLock<Context>>) {
|
|||||||
let ctx = c.clone();
|
let ctx = c.clone();
|
||||||
let handle_imap = std::thread::spawn(move || loop {
|
let handle_imap = std::thread::spawn(move || loop {
|
||||||
while_running!({
|
while_running!({
|
||||||
perform_inbox_jobs(&ctx.read().unwrap());
|
perform_imap_jobs(&ctx.read().unwrap());
|
||||||
perform_inbox_fetch(&ctx.read().unwrap());
|
perform_imap_fetch(&ctx.read().unwrap());
|
||||||
while_running!({
|
while_running!({
|
||||||
let context = ctx.read().unwrap();
|
let context = ctx.read().unwrap();
|
||||||
perform_inbox_idle(&context);
|
perform_imap_idle(&context);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -202,7 +202,7 @@ fn stop_threads(context: &Context) {
|
|||||||
println!("Stopping threads");
|
println!("Stopping threads");
|
||||||
IS_RUNNING.store(false, Ordering::Relaxed);
|
IS_RUNNING.store(false, Ordering::Relaxed);
|
||||||
|
|
||||||
interrupt_inbox_idle(context, true);
|
interrupt_imap_idle(context);
|
||||||
interrupt_mvbox_idle(context);
|
interrupt_mvbox_idle(context);
|
||||||
interrupt_sentbox_idle(context);
|
interrupt_sentbox_idle(context);
|
||||||
interrupt_smtp_idle(context);
|
interrupt_smtp_idle(context);
|
||||||
@@ -235,7 +235,7 @@ impl Completer for DcHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const IMEX_COMMANDS: [&str; 12] = [
|
const IMEX_COMMANDS: [&'static str; 12] = [
|
||||||
"initiate-key-transfer",
|
"initiate-key-transfer",
|
||||||
"get-setupcodebegin",
|
"get-setupcodebegin",
|
||||||
"continue-key-transfer",
|
"continue-key-transfer",
|
||||||
@@ -250,7 +250,7 @@ const IMEX_COMMANDS: [&str; 12] = [
|
|||||||
"stop",
|
"stop",
|
||||||
];
|
];
|
||||||
|
|
||||||
const DB_COMMANDS: [&str; 11] = [
|
const DB_COMMANDS: [&'static str; 11] = [
|
||||||
"info",
|
"info",
|
||||||
"open",
|
"open",
|
||||||
"close",
|
"close",
|
||||||
@@ -264,7 +264,7 @@ const DB_COMMANDS: [&str; 11] = [
|
|||||||
"housekeeping",
|
"housekeeping",
|
||||||
];
|
];
|
||||||
|
|
||||||
const CHAT_COMMANDS: [&str; 24] = [
|
const CHAT_COMMANDS: [&'static str; 24] = [
|
||||||
"listchats",
|
"listchats",
|
||||||
"listarchived",
|
"listarchived",
|
||||||
"chat",
|
"chat",
|
||||||
@@ -290,7 +290,7 @@ const CHAT_COMMANDS: [&str; 24] = [
|
|||||||
"unarchive",
|
"unarchive",
|
||||||
"delchat",
|
"delchat",
|
||||||
];
|
];
|
||||||
const MESSAGE_COMMANDS: [&str; 8] = [
|
const MESSAGE_COMMANDS: [&'static str; 8] = [
|
||||||
"listmsgs",
|
"listmsgs",
|
||||||
"msginfo",
|
"msginfo",
|
||||||
"listfresh",
|
"listfresh",
|
||||||
@@ -300,7 +300,7 @@ const MESSAGE_COMMANDS: [&str; 8] = [
|
|||||||
"unstar",
|
"unstar",
|
||||||
"delmsg",
|
"delmsg",
|
||||||
];
|
];
|
||||||
const CONTACT_COMMANDS: [&str; 6] = [
|
const CONTACT_COMMANDS: [&'static str; 6] = [
|
||||||
"listcontacts",
|
"listcontacts",
|
||||||
"listverified",
|
"listverified",
|
||||||
"addcontact",
|
"addcontact",
|
||||||
@@ -308,7 +308,7 @@ const CONTACT_COMMANDS: [&str; 6] = [
|
|||||||
"delcontact",
|
"delcontact",
|
||||||
"cleanupcontacts",
|
"cleanupcontacts",
|
||||||
];
|
];
|
||||||
const MISC_COMMANDS: [&str; 9] = [
|
const MISC_COMMANDS: [&'static str; 9] = [
|
||||||
"getqr", "getbadqr", "checkqr", "event", "fileinfo", "clear", "exit", "quit", "help",
|
"getqr", "getbadqr", "checkqr", "event", "fileinfo", "clear", "exit", "quit", "help",
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -334,8 +334,8 @@ impl Hinter for DcHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static COLORED_PROMPT: &str = "\x1b[1;32m> \x1b[0m";
|
static COLORED_PROMPT: &'static str = "\x1b[1;32m> \x1b[0m";
|
||||||
static PROMPT: &str = "> ";
|
static PROMPT: &'static str = "> ";
|
||||||
|
|
||||||
impl Highlighter for DcHelper {
|
impl Highlighter for DcHelper {
|
||||||
fn highlight_prompt<'p>(&self, prompt: &'p str) -> Cow<'p, str> {
|
fn highlight_prompt<'p>(&self, prompt: &'p str) -> Cow<'p, str> {
|
||||||
@@ -403,7 +403,7 @@ fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
|
|||||||
// TODO: ignore "set mail_pw"
|
// TODO: ignore "set mail_pw"
|
||||||
rl.add_history_entry(line.as_str());
|
rl.add_history_entry(line.as_str());
|
||||||
let ctx = ctx.clone();
|
let ctx = ctx.clone();
|
||||||
match handle_cmd(line.trim(), ctx) {
|
match unsafe { handle_cmd(line.trim(), ctx) } {
|
||||||
Ok(ExitResult::Continue) => {}
|
Ok(ExitResult::Continue) => {}
|
||||||
Ok(ExitResult::Exit) => break,
|
Ok(ExitResult::Exit) => break,
|
||||||
Err(err) => println!("Error: {}", err),
|
Err(err) => println!("Error: {}", err),
|
||||||
@@ -434,7 +434,7 @@ enum ExitResult {
|
|||||||
Exit,
|
Exit,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult, failure::Error> {
|
unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult, failure::Error> {
|
||||||
let mut args = line.splitn(2, ' ');
|
let mut args = line.splitn(2, ' ');
|
||||||
let arg0 = args.next().unwrap_or_default();
|
let arg0 = args.next().unwrap_or_default();
|
||||||
let arg1 = args.next().unwrap_or_default();
|
let arg1 = args.next().unwrap_or_default();
|
||||||
@@ -455,9 +455,9 @@ fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult, failu
|
|||||||
}
|
}
|
||||||
"imap-jobs" => {
|
"imap-jobs" => {
|
||||||
if HANDLE.clone().lock().unwrap().is_some() {
|
if HANDLE.clone().lock().unwrap().is_some() {
|
||||||
println!("inbox-jobs are already running in a thread.");
|
println!("imap-jobs are already running in a thread.");
|
||||||
} else {
|
} else {
|
||||||
perform_inbox_jobs(&ctx.read().unwrap());
|
perform_imap_jobs(&ctx.read().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"configure" => {
|
"configure" => {
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ use deltachat::configure::*;
|
|||||||
use deltachat::contact::*;
|
use deltachat::contact::*;
|
||||||
use deltachat::context::*;
|
use deltachat::context::*;
|
||||||
use deltachat::job::{
|
use deltachat::job::{
|
||||||
perform_inbox_fetch, perform_inbox_idle, perform_inbox_jobs, perform_smtp_idle,
|
perform_imap_fetch, perform_imap_idle, perform_imap_jobs, perform_smtp_idle, perform_smtp_jobs,
|
||||||
perform_smtp_jobs,
|
|
||||||
};
|
};
|
||||||
use deltachat::Event;
|
use deltachat::Event;
|
||||||
|
|
||||||
@@ -51,12 +50,12 @@ fn main() {
|
|||||||
let r1 = running.clone();
|
let r1 = running.clone();
|
||||||
let t1 = thread::spawn(move || {
|
let t1 = thread::spawn(move || {
|
||||||
while *r1.read().unwrap() {
|
while *r1.read().unwrap() {
|
||||||
perform_inbox_jobs(&ctx1);
|
perform_imap_jobs(&ctx1);
|
||||||
if *r1.read().unwrap() {
|
if *r1.read().unwrap() {
|
||||||
perform_inbox_fetch(&ctx1);
|
perform_imap_fetch(&ctx1);
|
||||||
|
|
||||||
if *r1.read().unwrap() {
|
if *r1.read().unwrap() {
|
||||||
perform_inbox_idle(&ctx1);
|
perform_imap_idle(&ctx1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,10 +100,20 @@ fn main() {
|
|||||||
|
|
||||||
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!("stopping threads");
|
println!("stopping threads");
|
||||||
|
|
||||||
*running.write().unwrap() = false;
|
*running.clone().write().unwrap() = false;
|
||||||
deltachat::job::interrupt_inbox_idle(&ctx, true);
|
deltachat::job::interrupt_imap_idle(&ctx);
|
||||||
deltachat::job::interrupt_smtp_idle(&ctx);
|
deltachat::job::interrupt_smtp_idle(&ctx);
|
||||||
|
|
||||||
println!("joining");
|
println!("joining");
|
||||||
|
|||||||
42
mmime/.circleci/config.yml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# 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
|
||||||
23
mmime/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
[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"
|
||||||
201
mmime/LICENSE-APACHE
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
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.
|
||||||
23
mmime/LICENSE-MIT
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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.
|
||||||
4
mmime/LICENSE.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
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.
|
||||||
16
mmime/README.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# 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
|
||||||
32
mmime/src/charconv.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
427
mmime/src/chash.rs
Normal file
@@ -0,0 +1,427 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
202
mmime/src/clist.rs
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
71
mmime/src/constants.rs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
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;
|
||||||
386
mmime/src/display.rs
Normal file
@@ -0,0 +1,386 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
78
mmime/src/lib.rs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
#![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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5921
mmime/src/mailimf/mod.rs
Normal file
1196
mmime/src/mailimf/types.rs
Normal file
89
mmime/src/mailimf/types_helper.rs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
1985
mmime/src/mailimf/write_generic.rs
Normal file
2357
mmime/src/mailmime/content.rs
Normal file
860
mmime/src/mailmime/decode.rs
Normal file
@@ -0,0 +1,860 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
583
mmime/src/mailmime/disposition.rs
Normal file
@@ -0,0 +1,583 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
1143
mmime/src/mailmime/mod.rs
Normal file
891
mmime/src/mailmime/types.rs
Normal file
@@ -0,0 +1,891 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
1445
mmime/src/mailmime/types_helper.rs
Normal file
1979
mmime/src/mailmime/write_generic.rs
Normal file
82
mmime/src/mailmime/write_mem.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
397
mmime/src/mmapstring.rs
Normal file
@@ -0,0 +1,397 @@
|
|||||||
|
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
Normal file
@@ -9,23 +9,17 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
target = os.environ.get("DCC_RS_TARGET")
|
os.environ["DCC_RS_TARGET"] = target = "release"
|
||||||
if target is None:
|
|
||||||
os.environ["DCC_RS_TARGET"] = target = "release"
|
|
||||||
if "DCC_RS_DEV" not in os.environ:
|
if "DCC_RS_DEV" not in os.environ:
|
||||||
dn = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
dn = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
os.environ["DCC_RS_DEV"] = dn
|
os.environ["DCC_RS_DEV"] = dn
|
||||||
|
|
||||||
# build the core library in release + debug mode because
|
|
||||||
# as of Nov 2019 rPGP generates RSA keys which take
|
|
||||||
# prohibitively long for non-release installs
|
|
||||||
os.environ["RUSTFLAGS"] = "-g"
|
os.environ["RUSTFLAGS"] = "-g"
|
||||||
subprocess.check_call([
|
subprocess.check_call([
|
||||||
"cargo", "build", "-p", "deltachat_ffi", "--" + target
|
"cargo", "build", "-p", "deltachat_ffi", "--" + target
|
||||||
])
|
])
|
||||||
subprocess.check_call("rm -rf build/ src/deltachat/*.so" , shell=True)
|
subprocess.check_call("rm -rf build/ src/deltachat/*.so" , shell=True)
|
||||||
|
|
||||||
if len(sys.argv) <= 1 or sys.argv[1] != "onlybuild":
|
subprocess.check_call([
|
||||||
subprocess.check_call([
|
sys.executable, "-m", "pip", "install", "-e", "."
|
||||||
sys.executable, "-m", "pip", "install", "-e", "."
|
])
|
||||||
])
|
|
||||||
|
|||||||
@@ -29,10 +29,7 @@ def ffibuilder():
|
|||||||
extra_link_args = []
|
extra_link_args = []
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError("Compilation not supported yet on Windows, can you help?")
|
raise NotImplementedError("Compilation not supported yet on Windows, can you help?")
|
||||||
target_dir = os.environ.get("CARGO_TARGET_DIR")
|
objs = [os.path.join(projdir, 'target', target, 'libdeltachat.a')]
|
||||||
if target_dir is None:
|
|
||||||
target_dir = os.path.join(projdir, 'target')
|
|
||||||
objs = [os.path.join(target_dir, target, 'libdeltachat.a')]
|
|
||||||
assert os.path.exists(objs[0]), objs
|
assert os.path.exists(objs[0]), objs
|
||||||
incs = [os.path.join(projdir, 'deltachat-ffi')]
|
incs = [os.path.join(projdir, 'deltachat-ffi')]
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import atexit
|
import atexit
|
||||||
import threading
|
import threading
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
from array import array
|
from array import array
|
||||||
@@ -26,7 +25,7 @@ class Account(object):
|
|||||||
by the underlying deltachat core library. All public Account methods are
|
by the underlying deltachat core library. All public Account methods are
|
||||||
meant to be memory-safe and return memory-safe objects.
|
meant to be memory-safe and return memory-safe objects.
|
||||||
"""
|
"""
|
||||||
def __init__(self, db_path, logid=None, eventlogging=True, os_name=None, debug=True):
|
def __init__(self, db_path, logid=None, eventlogging=True, debug=True):
|
||||||
""" initialize account object.
|
""" initialize account object.
|
||||||
|
|
||||||
:param db_path: a path to the account database. The database
|
:param db_path: a path to the account database. The database
|
||||||
@@ -34,11 +33,10 @@ class Account(object):
|
|||||||
:param logid: an optional logging prefix that should be used with
|
:param logid: an optional logging prefix that should be used with
|
||||||
the default internal logging.
|
the default internal logging.
|
||||||
:param eventlogging: if False no eventlogging and no context callback will be configured
|
:param eventlogging: if False no eventlogging and no context callback will be configured
|
||||||
:param os_name: this will be put to the X-Mailer header in outgoing messages
|
|
||||||
:param debug: turn on debug logging for events.
|
:param debug: turn on debug logging for events.
|
||||||
"""
|
"""
|
||||||
self._dc_context = ffi.gc(
|
self._dc_context = ffi.gc(
|
||||||
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, as_dc_charpointer(os_name)),
|
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
||||||
_destroy_dc_context,
|
_destroy_dc_context,
|
||||||
)
|
)
|
||||||
if eventlogging:
|
if eventlogging:
|
||||||
@@ -96,12 +94,9 @@ class Account(object):
|
|||||||
"""
|
"""
|
||||||
self._check_config_key(name)
|
self._check_config_key(name)
|
||||||
name = name.encode("utf8")
|
name = name.encode("utf8")
|
||||||
|
value = value.encode("utf8")
|
||||||
if name == b"addr" and self.is_configured():
|
if name == b"addr" and self.is_configured():
|
||||||
raise ValueError("can not change 'addr' after account is configured.")
|
raise ValueError("can not change 'addr' after account is configured.")
|
||||||
if value is not None:
|
|
||||||
value = value.encode("utf8")
|
|
||||||
else:
|
|
||||||
value = ffi.NULL
|
|
||||||
lib.dc_set_config(self._dc_context, name, value)
|
lib.dc_set_config(self._dc_context, name, value)
|
||||||
|
|
||||||
def get_config(self, name):
|
def get_config(self, name):
|
||||||
@@ -137,18 +132,6 @@ class Account(object):
|
|||||||
"""
|
"""
|
||||||
return lib.dc_is_configured(self._dc_context)
|
return lib.dc_is_configured(self._dc_context)
|
||||||
|
|
||||||
def set_avatar(self, img_path):
|
|
||||||
"""Set self avatar.
|
|
||||||
|
|
||||||
:raises ValueError: if profile image could not be set
|
|
||||||
:returns: None
|
|
||||||
"""
|
|
||||||
if img_path is None:
|
|
||||||
self.set_config("selfavatar", None)
|
|
||||||
else:
|
|
||||||
assert os.path.exists(img_path), img_path
|
|
||||||
self.set_config("selfavatar", img_path)
|
|
||||||
|
|
||||||
def check_is_configured(self):
|
def check_is_configured(self):
|
||||||
""" Raise ValueError if this account is not configured. """
|
""" Raise ValueError if this account is not configured. """
|
||||||
if not self.is_configured():
|
if not self.is_configured():
|
||||||
@@ -590,10 +573,8 @@ class IOThreads:
|
|||||||
self._log_event("py-bindings-info", 0, "INBOX THREAD START")
|
self._log_event("py-bindings-info", 0, "INBOX THREAD START")
|
||||||
while not self._thread_quitflag:
|
while not self._thread_quitflag:
|
||||||
lib.dc_perform_imap_jobs(self._dc_context)
|
lib.dc_perform_imap_jobs(self._dc_context)
|
||||||
if not self._thread_quitflag:
|
lib.dc_perform_imap_fetch(self._dc_context)
|
||||||
lib.dc_perform_imap_fetch(self._dc_context)
|
lib.dc_perform_imap_idle(self._dc_context)
|
||||||
if not self._thread_quitflag:
|
|
||||||
lib.dc_perform_imap_idle(self._dc_context)
|
|
||||||
self._log_event("py-bindings-info", 0, "INBOX THREAD FINISHED")
|
self._log_event("py-bindings-info", 0, "INBOX THREAD FINISHED")
|
||||||
|
|
||||||
def mvbox_thread_run(self):
|
def mvbox_thread_run(self):
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import calendar
|
import calendar
|
||||||
import json
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import os
|
import os
|
||||||
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
|
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
|
||||||
@@ -109,30 +108,6 @@ class Chat(object):
|
|||||||
|
|
||||||
# ------ chat messaging API ------------------------------
|
# ------ chat messaging API ------------------------------
|
||||||
|
|
||||||
def send_msg(self, msg):
|
|
||||||
"""send a message by using a ready Message object.
|
|
||||||
|
|
||||||
:param msg: a :class:`deltachat.message.Message` instance
|
|
||||||
previously returned by
|
|
||||||
e.g. :meth:`deltachat.message.Message.new_empty` or
|
|
||||||
:meth:`prepare_file`.
|
|
||||||
:raises ValueError: if message can not be sent.
|
|
||||||
|
|
||||||
:returns: a :class:`deltachat.message.Message` instance as
|
|
||||||
sent out. This is the same object as was passed in, which
|
|
||||||
has been modified with the new state of the core.
|
|
||||||
"""
|
|
||||||
if msg.is_out_preparing():
|
|
||||||
assert msg.id != 0
|
|
||||||
# get a fresh copy of dc_msg, the core needs it
|
|
||||||
msg = Message.from_db(self.account, msg.id)
|
|
||||||
sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
|
|
||||||
if sent_id == 0:
|
|
||||||
raise ValueError("message could not be sent")
|
|
||||||
# modify message in place to avoid bad state for the caller
|
|
||||||
msg._dc_msg = Message.from_db(self.account, sent_id)._dc_msg
|
|
||||||
return msg
|
|
||||||
|
|
||||||
def send_text(self, text):
|
def send_text(self, text):
|
||||||
""" send a text message and return the resulting Message instance.
|
""" send a text message and return the resulting Message instance.
|
||||||
|
|
||||||
@@ -154,12 +129,9 @@ class Chat(object):
|
|||||||
:raises ValueError: if message can not be send/chat does not exist.
|
:raises ValueError: if message can not be send/chat does not exist.
|
||||||
:returns: the resulting :class:`deltachat.message.Message` instance
|
:returns: the resulting :class:`deltachat.message.Message` instance
|
||||||
"""
|
"""
|
||||||
msg = Message.new_empty(self.account, view_type="file")
|
msg = self.prepare_message_file(path=path, mime_type=mime_type)
|
||||||
msg.set_file(path, mime_type)
|
self.send_prepared(msg)
|
||||||
sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
|
return msg
|
||||||
if sent_id == 0:
|
|
||||||
raise ValueError("message could not be sent")
|
|
||||||
return Message.from_db(self.account, sent_id)
|
|
||||||
|
|
||||||
def send_image(self, path):
|
def send_image(self, path):
|
||||||
""" send an image message and return the resulting Message instance.
|
""" send an image message and return the resulting Message instance.
|
||||||
@@ -169,12 +141,9 @@ class Chat(object):
|
|||||||
:returns: the resulting :class:`deltachat.message.Message` instance
|
:returns: the resulting :class:`deltachat.message.Message` instance
|
||||||
"""
|
"""
|
||||||
mime_type = mimetypes.guess_type(path)[0]
|
mime_type = mimetypes.guess_type(path)[0]
|
||||||
msg = Message.new_empty(self.account, view_type="image")
|
msg = self.prepare_message_file(path=path, mime_type=mime_type, view_type="image")
|
||||||
msg.set_file(path, mime_type)
|
self.send_prepared(msg)
|
||||||
sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
|
return msg
|
||||||
if sent_id == 0:
|
|
||||||
raise ValueError("message could not be sent")
|
|
||||||
return Message.from_db(self.account, sent_id)
|
|
||||||
|
|
||||||
def prepare_message(self, msg):
|
def prepare_message(self, msg):
|
||||||
""" create a new prepared message.
|
""" create a new prepared message.
|
||||||
@@ -273,12 +242,6 @@ class Chat(object):
|
|||||||
"""
|
"""
|
||||||
return lib.dc_marknoticed_chat(self._dc_context, self.id)
|
return lib.dc_marknoticed_chat(self._dc_context, self.id)
|
||||||
|
|
||||||
def get_summary(self):
|
|
||||||
""" return dictionary with summary information. """
|
|
||||||
dc_res = lib.dc_chat_get_info_json(self._dc_context, self.id)
|
|
||||||
s = from_dc_charpointer(dc_res)
|
|
||||||
return json.loads(s)
|
|
||||||
|
|
||||||
# ------ group management API ------------------------------
|
# ------ group management API ------------------------------
|
||||||
|
|
||||||
def add_contact(self, contact):
|
def add_contact(self, contact):
|
||||||
@@ -361,18 +324,6 @@ class Chat(object):
|
|||||||
return None
|
return None
|
||||||
return from_dc_charpointer(dc_res)
|
return from_dc_charpointer(dc_res)
|
||||||
|
|
||||||
def get_color(self):
|
|
||||||
"""return the color of the chat.
|
|
||||||
:returns: color as 0x00rrggbb
|
|
||||||
"""
|
|
||||||
return lib.dc_chat_get_color(self._dc_chat)
|
|
||||||
|
|
||||||
def get_subtitle(self):
|
|
||||||
"""return the subtitle of the chat
|
|
||||||
:returns: the subtitle
|
|
||||||
"""
|
|
||||||
return from_dc_charpointer(lib.dc_chat_get_subtitle(self._dc_chat))
|
|
||||||
|
|
||||||
# ------ location streaming API ------------------------------
|
# ------ location streaming API ------------------------------
|
||||||
|
|
||||||
def is_sending_locations(self):
|
def is_sending_locations(self):
|
||||||
@@ -381,12 +332,6 @@ class Chat(object):
|
|||||||
"""
|
"""
|
||||||
return lib.dc_is_sending_locations_to_chat(self._dc_context, self.id)
|
return lib.dc_is_sending_locations_to_chat(self._dc_context, self.id)
|
||||||
|
|
||||||
def is_archived(self):
|
|
||||||
"""return True if this chat is archived.
|
|
||||||
:returns: True if archived.
|
|
||||||
"""
|
|
||||||
return lib.dc_chat_get_archived(self._dc_chat)
|
|
||||||
|
|
||||||
def enable_sending_locations(self, seconds):
|
def enable_sending_locations(self, seconds):
|
||||||
"""enable sending locations for this chat.
|
"""enable sending locations for this chat.
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ DC_LP_SMTP_SOCKET_SSL = 0x20000
|
|||||||
DC_LP_SMTP_SOCKET_PLAIN = 0x40000
|
DC_LP_SMTP_SOCKET_PLAIN = 0x40000
|
||||||
DC_CERTCK_AUTO = 0
|
DC_CERTCK_AUTO = 0
|
||||||
DC_CERTCK_STRICT = 1
|
DC_CERTCK_STRICT = 1
|
||||||
|
DC_CERTCK_ACCEPT_INVALID_HOSTNAMES = 2
|
||||||
DC_CERTCK_ACCEPT_INVALID_CERTIFICATES = 3
|
DC_CERTCK_ACCEPT_INVALID_CERTIFICATES = 3
|
||||||
DC_EMPTY_MVBOX = 0x01
|
DC_EMPTY_MVBOX = 0x01
|
||||||
DC_EMPTY_INBOX = 0x02
|
DC_EMPTY_INBOX = 0x02
|
||||||
@@ -97,7 +98,6 @@ DC_EVENT_IMEX_PROGRESS = 2051
|
|||||||
DC_EVENT_IMEX_FILE_WRITTEN = 2052
|
DC_EVENT_IMEX_FILE_WRITTEN = 2052
|
||||||
DC_EVENT_SECUREJOIN_INVITER_PROGRESS = 2060
|
DC_EVENT_SECUREJOIN_INVITER_PROGRESS = 2060
|
||||||
DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061
|
DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061
|
||||||
DC_EVENT_SECUREJOIN_MEMBER_ADDED = 2062
|
|
||||||
DC_EVENT_FILE_COPIED = 2055
|
DC_EVENT_FILE_COPIED = 2055
|
||||||
DC_EVENT_IS_OFFLINE = 2081
|
DC_EVENT_IS_OFFLINE = 2081
|
||||||
DC_EVENT_GET_STRING = 2091
|
DC_EVENT_GET_STRING = 2091
|
||||||
@@ -150,8 +150,7 @@ DC_STR_MSGLOCATIONENABLED = 64
|
|||||||
DC_STR_MSGLOCATIONDISABLED = 65
|
DC_STR_MSGLOCATIONDISABLED = 65
|
||||||
DC_STR_LOCATION = 66
|
DC_STR_LOCATION = 66
|
||||||
DC_STR_STICKER = 67
|
DC_STR_STICKER = 67
|
||||||
DC_STR_DEVICE_MESSAGES = 68
|
DC_STR_COUNT = 67
|
||||||
DC_STR_COUNT = 68
|
|
||||||
# end const generated
|
# end const generated
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -47,13 +47,3 @@ class Contact(object):
|
|||||||
def is_verified(self):
|
def is_verified(self):
|
||||||
""" Return True if the contact is verified. """
|
""" Return True if the contact is verified. """
|
||||||
return lib.dc_contact_is_verified(self._dc_contact)
|
return lib.dc_contact_is_verified(self._dc_contact)
|
||||||
|
|
||||||
def get_profile_image(self):
|
|
||||||
"""Get contact profile image.
|
|
||||||
|
|
||||||
:returns: path to profile image, None if no profile image exists.
|
|
||||||
"""
|
|
||||||
dc_res = lib.dc_contact_get_profile_image(self._dc_contact)
|
|
||||||
if dc_res == ffi.NULL:
|
|
||||||
return None
|
|
||||||
return from_dc_charpointer(dc_res)
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
""" The Message object. """
|
""" The Message object. """
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
from . import props
|
from . import props
|
||||||
from .cutil import from_dc_charpointer, as_dc_charpointer
|
from .cutil import from_dc_charpointer, as_dc_charpointer
|
||||||
from .capi import lib, ffi
|
from .capi import lib, ffi
|
||||||
@@ -57,6 +58,8 @@ class Message(object):
|
|||||||
|
|
||||||
def set_text(self, text):
|
def set_text(self, text):
|
||||||
"""set text of this message. """
|
"""set text of this message. """
|
||||||
|
assert self.id > 0, "message not prepared"
|
||||||
|
assert self.is_out_preparing()
|
||||||
lib.dc_msg_set_text(self._dc_msg, as_dc_charpointer(text))
|
lib.dc_msg_set_text(self._dc_msg, as_dc_charpointer(text))
|
||||||
|
|
||||||
@props.with_doc
|
@props.with_doc
|
||||||
@@ -69,6 +72,19 @@ class Message(object):
|
|||||||
mtype = ffi.NULL if mime_type is None else as_dc_charpointer(mime_type)
|
mtype = ffi.NULL if mime_type is None else as_dc_charpointer(mime_type)
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
raise ValueError("path does not exist: {!r}".format(path))
|
raise ValueError("path does not exist: {!r}".format(path))
|
||||||
|
blobdir = self.account.get_blobdir()
|
||||||
|
if not path.startswith(blobdir):
|
||||||
|
for i in range(50):
|
||||||
|
ext = "" if i == 0 else "-" + str(i)
|
||||||
|
dest = os.path.join(blobdir, os.path.basename(path) + ext)
|
||||||
|
if os.path.exists(dest):
|
||||||
|
continue
|
||||||
|
shutil.copyfile(path, dest)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise ValueError("could not create blobdir-path for {}".format(path))
|
||||||
|
path = dest
|
||||||
|
assert path.startswith(blobdir), path
|
||||||
lib.dc_msg_set_file(self._dc_msg, as_dc_charpointer(path), mtype)
|
lib.dc_msg_set_file(self._dc_msg, as_dc_charpointer(path), mtype)
|
||||||
|
|
||||||
@props.with_doc
|
@props.with_doc
|
||||||
@@ -146,7 +162,7 @@ class Message(object):
|
|||||||
if mime_headers:
|
if mime_headers:
|
||||||
s = ffi.string(ffi.gc(mime_headers, lib.dc_str_unref))
|
s = ffi.string(ffi.gc(mime_headers, lib.dc_str_unref))
|
||||||
if isinstance(s, bytes):
|
if isinstance(s, bytes):
|
||||||
return email.message_from_bytes(s)
|
s = s.decode("ascii")
|
||||||
return email.message_from_string(s)
|
return email.message_from_string(s)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -174,7 +190,7 @@ class Message(object):
|
|||||||
@property
|
@property
|
||||||
def _msgstate(self):
|
def _msgstate(self):
|
||||||
if self.id == 0:
|
if self.id == 0:
|
||||||
dc_msg = self._dc_msg
|
dc_msg = self.message._dc_msg
|
||||||
else:
|
else:
|
||||||
# load message from db to get a fresh/current state
|
# load message from db to get a fresh/current state
|
||||||
dc_msg = ffi.gc(
|
dc_msg = ffi.gc(
|
||||||
|
|||||||
@@ -85,21 +85,16 @@ class SessionLiveConfigFromFile:
|
|||||||
class SessionLiveConfigFromURL:
|
class SessionLiveConfigFromURL:
|
||||||
def __init__(self, url, create_token):
|
def __init__(self, url, create_token):
|
||||||
self.configlist = []
|
self.configlist = []
|
||||||
self.url = url
|
for i in range(2):
|
||||||
self.create_token = create_token
|
res = requests.post(url, json={"token_create_user": int(create_token)})
|
||||||
|
|
||||||
def get(self, index):
|
|
||||||
try:
|
|
||||||
return self.configlist[index]
|
|
||||||
except IndexError:
|
|
||||||
assert index == len(self.configlist), index
|
|
||||||
res = requests.post(self.url, json={"token_create_user": int(self.create_token)})
|
|
||||||
if res.status_code != 200:
|
if res.status_code != 200:
|
||||||
pytest.skip("creating newtmpuser failed {!r}".format(res))
|
pytest.skip("creating newtmpuser failed {!r}".format(res))
|
||||||
d = res.json()
|
d = res.json()
|
||||||
config = dict(addr=d["email"], mail_pw=d["password"])
|
config = dict(addr=d["email"], mail_pw=d["password"])
|
||||||
self.configlist.append(config)
|
self.configlist.append(config)
|
||||||
return config
|
|
||||||
|
def get(self, index):
|
||||||
|
return self.configlist[index]
|
||||||
|
|
||||||
def exists(self):
|
def exists(self):
|
||||||
return bool(self.configlist)
|
return bool(self.configlist)
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -16,20 +16,12 @@ class TestOfflineAccountBasic:
|
|||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Account(p.strpath)
|
Account(p.strpath)
|
||||||
|
|
||||||
def test_os_name(self, tmpdir):
|
|
||||||
p = tmpdir.join("hello.db")
|
|
||||||
# we can't easily test if os_name is used in X-Mailer
|
|
||||||
# outgoing messages without a full Online test
|
|
||||||
# but we at least check Account accepts the arg
|
|
||||||
ac1 = Account(p.strpath, os_name="solarpunk")
|
|
||||||
ac1.get_info()
|
|
||||||
|
|
||||||
def test_getinfo(self, acfactory):
|
def test_getinfo(self, acfactory):
|
||||||
ac1 = acfactory.get_unconfigured_account()
|
ac1 = acfactory.get_unconfigured_account()
|
||||||
d = ac1.get_info()
|
d = ac1.get_info()
|
||||||
assert d["arch"]
|
assert d["arch"]
|
||||||
assert d["number_of_chats"] == "0"
|
assert d["number_of_chats"] == "0"
|
||||||
assert d["bcc_self"] == "0"
|
assert d["bcc_self"] == "1"
|
||||||
|
|
||||||
def test_is_not_configured(self, acfactory):
|
def test_is_not_configured(self, acfactory):
|
||||||
ac1 = acfactory.get_unconfigured_account()
|
ac1 = acfactory.get_unconfigured_account()
|
||||||
@@ -51,7 +43,7 @@ class TestOfflineAccountBasic:
|
|||||||
def test_has_bccself(self, acfactory):
|
def test_has_bccself(self, acfactory):
|
||||||
ac1 = acfactory.get_unconfigured_account()
|
ac1 = acfactory.get_unconfigured_account()
|
||||||
assert "bcc_self" in ac1.get_config("sys.config_keys").split()
|
assert "bcc_self" in ac1.get_config("sys.config_keys").split()
|
||||||
assert ac1.get_config("bcc_self") == "0"
|
assert ac1.get_config("bcc_self") == "1"
|
||||||
|
|
||||||
def test_selfcontact_if_unconfigured(self, acfactory):
|
def test_selfcontact_if_unconfigured(self, acfactory):
|
||||||
ac1 = acfactory.get_unconfigured_account()
|
ac1 = acfactory.get_unconfigured_account()
|
||||||
@@ -163,18 +155,6 @@ class TestOfflineChat:
|
|||||||
chat.set_name("title2")
|
chat.set_name("title2")
|
||||||
assert chat.get_name() == "title2"
|
assert chat.get_name() == "title2"
|
||||||
|
|
||||||
d = chat.get_summary()
|
|
||||||
print(d)
|
|
||||||
assert d["id"] == chat.id
|
|
||||||
assert d["type"] == chat.get_type()
|
|
||||||
assert d["name"] == chat.get_name()
|
|
||||||
assert d["archived"] == chat.is_archived()
|
|
||||||
# assert d["param"] == chat.param
|
|
||||||
assert d["color"] == chat.get_color()
|
|
||||||
assert d["profile_image"] == "" if chat.get_profile_image() is None else chat.get_profile_image()
|
|
||||||
assert d["subtitle"] == chat.get_subtitle()
|
|
||||||
assert d["draft"] == "" if chat.get_draft() is None else chat.get_draft()
|
|
||||||
|
|
||||||
def test_group_chat_creation_with_translation(self, ac1):
|
def test_group_chat_creation_with_translation(self, ac1):
|
||||||
ac1.set_stock_translation(const.DC_STR_NEWGROUPDRAFT, "xyz %1$s")
|
ac1.set_stock_translation(const.DC_STR_NEWGROUPDRAFT, "xyz %1$s")
|
||||||
ac1._evlogger.consume_events()
|
ac1._evlogger.consume_events()
|
||||||
@@ -390,23 +370,6 @@ class TestOfflineChat:
|
|||||||
assert not res.is_ask_verifygroup()
|
assert not res.is_ask_verifygroup()
|
||||||
assert res.contact_id == 10
|
assert res.contact_id == 10
|
||||||
|
|
||||||
def test_group_chat_many_members_add_remove(self, ac1, lp):
|
|
||||||
lp.sec("ac1: creating group chat with 10 other members")
|
|
||||||
chat = ac1.create_group_chat(name="title1")
|
|
||||||
contacts = []
|
|
||||||
for i in range(10):
|
|
||||||
contact = ac1.create_contact("some{}@example.org".format(i))
|
|
||||||
contacts.append(contact)
|
|
||||||
chat.add_contact(contact)
|
|
||||||
|
|
||||||
num_contacts = len(chat.get_contacts())
|
|
||||||
assert num_contacts == 11
|
|
||||||
|
|
||||||
lp.sec("ac1: removing two contacts and checking things are right")
|
|
||||||
chat.remove_contact(contacts[9])
|
|
||||||
chat.remove_contact(contacts[3])
|
|
||||||
assert len(chat.get_contacts()) == 9
|
|
||||||
|
|
||||||
|
|
||||||
class TestOnlineAccount:
|
class TestOnlineAccount:
|
||||||
def get_chat(self, ac1, ac2, both_created=False):
|
def get_chat(self, ac1, ac2, both_created=False):
|
||||||
@@ -442,9 +405,6 @@ class TestOnlineAccount:
|
|||||||
wait_successful_IMAP_SMTP_connection(ac1)
|
wait_successful_IMAP_SMTP_connection(ac1)
|
||||||
wait_configuration_progress(ac1, 1000)
|
wait_configuration_progress(ac1, 1000)
|
||||||
|
|
||||||
lp.sec("ac1: setting bcc_self=1")
|
|
||||||
ac1.set_config("bcc_self", "1")
|
|
||||||
|
|
||||||
lp.sec("send out message with bcc to ourselves")
|
lp.sec("send out message with bcc to ourselves")
|
||||||
msg_out = chat.send_text("message2")
|
msg_out = chat.send_text("message2")
|
||||||
ev = ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
ev = ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||||
@@ -467,81 +427,15 @@ class TestOnlineAccount:
|
|||||||
assert self_addr not in ev[2]
|
assert self_addr not in ev[2]
|
||||||
ev = ac1._evlogger.get_matching("DC_EVENT_DELETED_BLOB_FILE")
|
ev = ac1._evlogger.get_matching("DC_EVENT_DELETED_BLOB_FILE")
|
||||||
|
|
||||||
def test_send_file_twice_unicode_filename_mangling(self, tmpdir, acfactory, lp):
|
def test_mvbox_sentbox_threads(self, acfactory):
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
|
||||||
chat = self.get_chat(ac1, ac2)
|
|
||||||
|
|
||||||
basename = "somedäüta.html.zip"
|
|
||||||
p = os.path.join(tmpdir.strpath, basename)
|
|
||||||
with open(p, "w") as f:
|
|
||||||
f.write("some data")
|
|
||||||
|
|
||||||
def send_and_receive_message():
|
|
||||||
lp.sec("ac1: prepare and send attachment + text to ac2")
|
|
||||||
msg1 = Message.new_empty(ac1, "file")
|
|
||||||
msg1.set_text("withfile")
|
|
||||||
msg1.set_file(p)
|
|
||||||
chat.send_msg(msg1)
|
|
||||||
|
|
||||||
lp.sec("ac2: receive message")
|
|
||||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
|
||||||
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
|
|
||||||
return ac2.get_message_by_id(ev[2])
|
|
||||||
|
|
||||||
msg = send_and_receive_message()
|
|
||||||
assert msg.text == "withfile"
|
|
||||||
assert open(msg.filename).read() == "some data"
|
|
||||||
assert msg.filename.endswith(basename)
|
|
||||||
|
|
||||||
msg2 = send_and_receive_message()
|
|
||||||
assert msg2.text == "withfile"
|
|
||||||
assert open(msg2.filename).read() == "some data"
|
|
||||||
assert msg2.filename.endswith("html.zip")
|
|
||||||
assert msg.filename != msg2.filename
|
|
||||||
|
|
||||||
def test_send_file_html_attachment(self, tmpdir, acfactory, lp):
|
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
|
||||||
chat = self.get_chat(ac1, ac2)
|
|
||||||
|
|
||||||
basename = "test.html"
|
|
||||||
content = "<html><body>text</body>data"
|
|
||||||
|
|
||||||
p = os.path.join(tmpdir.strpath, basename)
|
|
||||||
with open(p, "w") as f:
|
|
||||||
# write wrong html to see if core tries to parse it
|
|
||||||
# (it shouldn't as it's a file attachment)
|
|
||||||
f.write(content)
|
|
||||||
|
|
||||||
lp.sec("ac1: prepare and send attachment + text to ac2")
|
|
||||||
chat.send_file(p, mime_type="text/html")
|
|
||||||
|
|
||||||
lp.sec("ac2: receive message")
|
|
||||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
|
||||||
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
|
|
||||||
msg = ac2.get_message_by_id(ev[2])
|
|
||||||
|
|
||||||
assert open(msg.filename).read() == content
|
|
||||||
assert msg.filename.endswith(basename)
|
|
||||||
|
|
||||||
def test_mvbox_sentbox_threads(self, acfactory, lp):
|
|
||||||
lp.sec("ac1: start with mvbox thread")
|
|
||||||
ac1 = acfactory.get_online_configuring_account(mvbox=True, sentbox=True)
|
ac1 = acfactory.get_online_configuring_account(mvbox=True, sentbox=True)
|
||||||
|
|
||||||
lp.sec("ac2: start without mvbox/sentbox threads")
|
|
||||||
ac2 = acfactory.get_online_configuring_account()
|
ac2 = acfactory.get_online_configuring_account()
|
||||||
|
|
||||||
lp.sec("ac2: waiting for configuration")
|
|
||||||
wait_configuration_progress(ac2, 1000)
|
wait_configuration_progress(ac2, 1000)
|
||||||
|
|
||||||
lp.sec("ac1: waiting for configuration")
|
|
||||||
wait_configuration_progress(ac1, 1000)
|
wait_configuration_progress(ac1, 1000)
|
||||||
|
|
||||||
lp.sec("ac1: send message and wait for ac2 to receive it")
|
|
||||||
chat = self.get_chat(ac1, ac2)
|
chat = self.get_chat(ac1, ac2)
|
||||||
chat.send_text("message1")
|
chat.send_text("message1")
|
||||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||||
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
|
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
lp.sec("test finished")
|
|
||||||
|
|
||||||
def test_move_works(self, acfactory):
|
def test_move_works(self, acfactory):
|
||||||
ac1 = acfactory.get_online_configuring_account()
|
ac1 = acfactory.get_online_configuring_account()
|
||||||
@@ -554,46 +448,27 @@ class TestOnlineAccount:
|
|||||||
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
|
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
ev = ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
ev = ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||||
|
|
||||||
def test_move_works_on_self_sent(self, acfactory):
|
def test_forward_messages(self, acfactory):
|
||||||
ac1 = acfactory.get_online_configuring_account(mvbox=True)
|
|
||||||
ac1.set_config("bcc_self", "1")
|
|
||||||
ac2 = acfactory.get_online_configuring_account()
|
|
||||||
wait_configuration_progress(ac2, 1000)
|
|
||||||
wait_configuration_progress(ac1, 1000)
|
|
||||||
chat = self.get_chat(ac1, ac2)
|
|
||||||
chat.send_text("message1")
|
|
||||||
chat.send_text("message2")
|
|
||||||
chat.send_text("message3")
|
|
||||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
|
||||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
|
||||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
|
||||||
|
|
||||||
def test_forward_messages(self, acfactory, lp):
|
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||||
chat = self.get_chat(ac1, ac2)
|
chat = self.get_chat(ac1, ac2)
|
||||||
|
|
||||||
lp.sec("ac1: send message to ac2")
|
|
||||||
msg_out = chat.send_text("message2")
|
msg_out = chat.send_text("message2")
|
||||||
|
|
||||||
lp.sec("ac2: wait for receive")
|
# wait for other account to receive
|
||||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||||
assert ev[2] == msg_out.id
|
assert ev[2] == msg_out.id
|
||||||
msg_in = ac2.get_message_by_id(msg_out.id)
|
msg_in = ac2.get_message_by_id(msg_out.id)
|
||||||
assert msg_in.text == "message2"
|
assert msg_in.text == "message2"
|
||||||
|
|
||||||
lp.sec("ac2: check that the message arrive in deaddrop")
|
# check the message arrived in contact-requests/deaddrop
|
||||||
chat2 = msg_in.chat
|
chat2 = msg_in.chat
|
||||||
assert msg_in in chat2.get_messages()
|
assert msg_in in chat2.get_messages()
|
||||||
assert not msg_in.is_forwarded()
|
assert not msg_in.is_forwarded()
|
||||||
assert chat2.is_deaddrop()
|
assert chat2.is_deaddrop()
|
||||||
assert chat2 == ac2.get_deaddrop_chat()
|
assert chat2 == ac2.get_deaddrop_chat()
|
||||||
|
|
||||||
lp.sec("ac2: create new chat and forward message to it")
|
|
||||||
chat3 = ac2.create_group_chat("newgroup")
|
chat3 = ac2.create_group_chat("newgroup")
|
||||||
assert not chat3.is_promoted()
|
assert not chat3.is_promoted()
|
||||||
ac2.forward_messages([msg_in], chat3)
|
ac2.forward_messages([msg_in], chat3)
|
||||||
|
|
||||||
lp.sec("ac2: check new chat has a forwarded message")
|
|
||||||
assert chat3.is_promoted()
|
assert chat3.is_promoted()
|
||||||
messages = chat3.get_messages()
|
messages = chat3.get_messages()
|
||||||
msg = messages[-1]
|
msg = messages[-1]
|
||||||
@@ -640,9 +515,6 @@ class TestOnlineAccount:
|
|||||||
def test_send_and_receive_message_markseen(self, acfactory, lp):
|
def test_send_and_receive_message_markseen(self, acfactory, lp):
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||||
|
|
||||||
# make DC's life harder wrt to encodings
|
|
||||||
ac1.set_config("displayname", "ä name")
|
|
||||||
|
|
||||||
lp.sec("ac1: create chat with ac2")
|
lp.sec("ac1: create chat with ac2")
|
||||||
chat = self.get_chat(ac1, ac2)
|
chat = self.get_chat(ac1, ac2)
|
||||||
|
|
||||||
@@ -660,7 +532,6 @@ class TestOnlineAccount:
|
|||||||
msg_in = ac2.get_message_by_id(msg_out.id)
|
msg_in = ac2.get_message_by_id(msg_out.id)
|
||||||
assert msg_in.text == "message1"
|
assert msg_in.text == "message1"
|
||||||
assert not msg_in.is_forwarded()
|
assert not msg_in.is_forwarded()
|
||||||
assert msg_in.get_sender_contact().display_name == ac1.get_config("displayname")
|
|
||||||
|
|
||||||
lp.sec("check the message arrived in contact-requets/deaddrop")
|
lp.sec("check the message arrived in contact-requets/deaddrop")
|
||||||
chat2 = msg_in.chat
|
chat2 = msg_in.chat
|
||||||
@@ -695,39 +566,6 @@ class TestOnlineAccount:
|
|||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
pass # mark_seen_messages() has generated events before it returns
|
pass # mark_seen_messages() has generated events before it returns
|
||||||
|
|
||||||
def test_mdn_asymetric(self, acfactory, lp):
|
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
|
||||||
|
|
||||||
lp.sec("ac1: create chat with ac2")
|
|
||||||
chat = self.get_chat(ac1, ac2, both_created=True)
|
|
||||||
|
|
||||||
# make sure mdns are enabled (usually enabled by default already)
|
|
||||||
ac1.set_config("mdns_enabled", "1")
|
|
||||||
ac2.set_config("mdns_enabled", "1")
|
|
||||||
|
|
||||||
lp.sec("sending text message from ac1 to ac2")
|
|
||||||
msg_out = chat.send_text("message1")
|
|
||||||
|
|
||||||
assert len(chat.get_messages()) == 1
|
|
||||||
|
|
||||||
lp.sec("disable ac1 MDNs")
|
|
||||||
ac1.set_config("mdns_enabled", "0")
|
|
||||||
|
|
||||||
lp.sec("wait for ac2 to receive message")
|
|
||||||
msg = ac2.wait_next_incoming_message()
|
|
||||||
|
|
||||||
assert len(msg.chat.get_messages()) == 1
|
|
||||||
|
|
||||||
lp.sec("ac2: mark incoming message as seen")
|
|
||||||
ac2.mark_seen_messages([msg])
|
|
||||||
|
|
||||||
lp.sec("ac1: waiting for incoming activity")
|
|
||||||
# wait for MOVED event because even ignored read-receipts should be moved
|
|
||||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
|
||||||
|
|
||||||
assert len(chat.get_messages()) == 1
|
|
||||||
assert not msg_out.is_out_mdn_received()
|
|
||||||
|
|
||||||
def test_send_and_receive_will_encrypt_decrypt(self, acfactory, lp):
|
def test_send_and_receive_will_encrypt_decrypt(self, acfactory, lp):
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||||
|
|
||||||
@@ -756,12 +594,6 @@ class TestOnlineAccount:
|
|||||||
assert msg_back.text == "message-back"
|
assert msg_back.text == "message-back"
|
||||||
assert msg_back.is_encrypted()
|
assert msg_back.is_encrypted()
|
||||||
|
|
||||||
# Test that we do not gossip peer keys in 1-to-1 chat,
|
|
||||||
# as it makes no sense to gossip to peers their own keys.
|
|
||||||
# Gossip is only sent in encrypted messages,
|
|
||||||
# and we sent encrypted msg_back right above.
|
|
||||||
assert chat2b.get_summary()["gossiped_timestamp"] == 0
|
|
||||||
|
|
||||||
lp.sec("create group chat with two members, one of which has no encrypt state")
|
lp.sec("create group chat with two members, one of which has no encrypt state")
|
||||||
chat = ac1.create_group_chat("encryption test")
|
chat = ac1.create_group_chat("encryption test")
|
||||||
chat.add_contact(ac1.create_contact(ac2.get_config("addr")))
|
chat.add_contact(ac1.create_contact(ac2.get_config("addr")))
|
||||||
@@ -770,71 +602,6 @@ class TestOnlineAccount:
|
|||||||
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
|
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
|
||||||
assert not msg.is_encrypted()
|
assert not msg.is_encrypted()
|
||||||
|
|
||||||
def test_send_first_message_as_long_unicode_with_cr(self, acfactory, lp):
|
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
|
||||||
ac2.set_config("save_mime_headers", "1")
|
|
||||||
|
|
||||||
lp.sec("ac1: create chat with ac2")
|
|
||||||
chat = self.get_chat(ac1, ac2, both_created=True)
|
|
||||||
|
|
||||||
lp.sec("sending multi-line non-unicode message from ac1 to ac2")
|
|
||||||
text1 = "hello\nworld"
|
|
||||||
msg_out = chat.send_text(text1)
|
|
||||||
assert not msg_out.is_encrypted()
|
|
||||||
|
|
||||||
lp.sec("sending multi-line unicode text message from ac1 to ac2")
|
|
||||||
text2 = "äalis\nthis is ßßÄ"
|
|
||||||
msg_out = chat.send_text(text2)
|
|
||||||
assert not msg_out.is_encrypted()
|
|
||||||
|
|
||||||
lp.sec("wait for ac2 to receive multi-line non-unicode message")
|
|
||||||
msg_in = ac2.wait_next_incoming_message()
|
|
||||||
assert msg_in.text == text1
|
|
||||||
|
|
||||||
lp.sec("wait for ac2 to receive multi-line unicode message")
|
|
||||||
msg_in = ac2.wait_next_incoming_message()
|
|
||||||
assert msg_in.text == text2
|
|
||||||
assert ac1.get_config("addr") in msg_in.chat.get_name()
|
|
||||||
|
|
||||||
def test_reply_encrypted(self, acfactory, lp):
|
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
|
||||||
|
|
||||||
lp.sec("ac1: create chat with ac2")
|
|
||||||
chat = self.get_chat(ac1, ac2)
|
|
||||||
|
|
||||||
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")
|
|
||||||
msg_in = ac2.get_message_by_id(msg_out.id)
|
|
||||||
assert msg_in.text == "message1"
|
|
||||||
assert not msg_in.is_encrypted()
|
|
||||||
|
|
||||||
lp.sec("create new chat with contact and send back (encrypted) message")
|
|
||||||
chat2b = ac2.create_chat_by_message(msg_in)
|
|
||||||
chat2b.send_text("message-back")
|
|
||||||
|
|
||||||
lp.sec("wait for ac1 to receive message")
|
|
||||||
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
|
|
||||||
assert ev[1] == chat.id
|
|
||||||
msg_back = ac1.get_message_by_id(ev[2])
|
|
||||||
assert msg_back.text == "message-back"
|
|
||||||
assert msg_back.is_encrypted()
|
|
||||||
|
|
||||||
lp.sec("ac1: e2ee_enabled=0 and see if reply is encrypted")
|
|
||||||
print("ac1: e2ee_enabled={}".format(ac1.get_config("e2ee_enabled")))
|
|
||||||
print("ac2: e2ee_enabled={}".format(ac2.get_config("e2ee_enabled")))
|
|
||||||
ac1.set_config("e2ee_enabled", "0")
|
|
||||||
chat.send_text("message2 -- should be encrypted")
|
|
||||||
|
|
||||||
lp.sec("wait for ac2 to receive message")
|
|
||||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
|
|
||||||
msg_in = ac2.get_message_by_id(ev[2])
|
|
||||||
assert msg_in.text == "message2 -- should be encrypted"
|
|
||||||
assert msg_in.is_encrypted()
|
|
||||||
|
|
||||||
def test_saved_mime_on_received_message(self, acfactory, lp):
|
def test_saved_mime_on_received_message(self, acfactory, lp):
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||||
|
|
||||||
@@ -950,7 +717,6 @@ class TestOnlineAccount:
|
|||||||
ac2._evlogger.set_timeout(30)
|
ac2._evlogger.set_timeout(30)
|
||||||
wait_configuration_progress(ac2, 1000)
|
wait_configuration_progress(ac2, 1000)
|
||||||
wait_configuration_progress(ac1, 1000)
|
wait_configuration_progress(ac1, 1000)
|
||||||
|
|
||||||
lp.sec("trigger ac setup message but ignore")
|
lp.sec("trigger ac setup message but ignore")
|
||||||
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
|
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
|
||||||
ac1.initiate_key_transfer()
|
ac1.initiate_key_transfer()
|
||||||
@@ -962,7 +728,6 @@ class TestOnlineAccount:
|
|||||||
msg = ac2.get_message_by_id(ev[2])
|
msg = ac2.get_message_by_id(ev[2])
|
||||||
assert msg.is_setup_message()
|
assert msg.is_setup_message()
|
||||||
assert msg.get_setupcodebegin() == setup_code2[:2]
|
assert msg.get_setupcodebegin() == setup_code2[:2]
|
||||||
|
|
||||||
lp.sec("process second setup message")
|
lp.sec("process second setup message")
|
||||||
msg.continue_key_transfer(setup_code2)
|
msg.continue_key_transfer(setup_code2)
|
||||||
assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"]
|
assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"]
|
||||||
@@ -988,7 +753,6 @@ class TestOnlineAccount:
|
|||||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
||||||
ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
||||||
wait_securejoin_inviter_progress(ac1, 1000)
|
wait_securejoin_inviter_progress(ac1, 1000)
|
||||||
ac1._evlogger.get_matching("DC_EVENT_SECUREJOIN_MEMBER_ADDED")
|
|
||||||
|
|
||||||
def test_qr_verified_group_and_chatting(self, acfactory, lp):
|
def test_qr_verified_group_and_chatting(self, acfactory, lp):
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||||
@@ -1000,7 +764,6 @@ class TestOnlineAccount:
|
|||||||
chat2 = ac2.qr_join_chat(qr)
|
chat2 = ac2.qr_join_chat(qr)
|
||||||
assert chat2.id >= 10
|
assert chat2.id >= 10
|
||||||
wait_securejoin_inviter_progress(ac1, 1000)
|
wait_securejoin_inviter_progress(ac1, 1000)
|
||||||
ac1._evlogger.get_matching("DC_EVENT_SECUREJOIN_MEMBER_ADDED")
|
|
||||||
|
|
||||||
lp.sec("ac2: read member added message")
|
lp.sec("ac2: read member added message")
|
||||||
msg = ac2.wait_next_incoming_message()
|
msg = ac2.wait_next_incoming_message()
|
||||||
@@ -1023,54 +786,7 @@ class TestOnlineAccount:
|
|||||||
assert msg.text == "world"
|
assert msg.text == "world"
|
||||||
assert msg.is_encrypted()
|
assert msg.is_encrypted()
|
||||||
|
|
||||||
def test_set_get_contact_avatar(self, acfactory, data, lp):
|
def test_set_get_profile_image(self, acfactory, data, lp):
|
||||||
lp.sec("configuring ac1 and ac2")
|
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
|
||||||
|
|
||||||
lp.sec("ac1: set own profile image")
|
|
||||||
p = data.get_path("d.png")
|
|
||||||
ac1.set_avatar(p)
|
|
||||||
|
|
||||||
lp.sec("ac1: create 1:1 chat with ac2")
|
|
||||||
chat = self.get_chat(ac1, ac2, both_created=True)
|
|
||||||
|
|
||||||
msg = chat.send_text("hi -- do you see my brand new avatar?")
|
|
||||||
assert not msg.is_encrypted()
|
|
||||||
|
|
||||||
lp.sec("ac2: wait for receiving message and avatar from ac1")
|
|
||||||
msg1 = ac2.wait_next_incoming_message()
|
|
||||||
assert not msg1.chat.is_deaddrop()
|
|
||||||
received_path = msg1.get_sender_contact().get_profile_image()
|
|
||||||
assert open(received_path, "rb").read() == open(p, "rb").read()
|
|
||||||
|
|
||||||
lp.sec("ac2: set own profile image")
|
|
||||||
p = data.get_path("d.png")
|
|
||||||
ac2.set_avatar(p)
|
|
||||||
|
|
||||||
lp.sec("ac2: send back message")
|
|
||||||
m = msg1.chat.send_text("yes, i received your avatar -- how do you like mine?")
|
|
||||||
assert m.is_encrypted()
|
|
||||||
|
|
||||||
lp.sec("ac1: wait for receiving message and avatar from ac2")
|
|
||||||
msg2 = ac1.wait_next_incoming_message()
|
|
||||||
received_path = msg2.get_sender_contact().get_profile_image()
|
|
||||||
assert received_path is not None, "did not get avatar through encrypted message"
|
|
||||||
assert open(received_path, "rb").read() == open(p, "rb").read()
|
|
||||||
|
|
||||||
ac2._evlogger.consume_events()
|
|
||||||
ac1._evlogger.consume_events()
|
|
||||||
|
|
||||||
# XXX not sure if the following is correct / possible. you may remove it
|
|
||||||
lp.sec("ac1: delete profile image from chat, and send message to ac2")
|
|
||||||
ac1.set_avatar(None)
|
|
||||||
m = msg2.chat.send_text("i don't like my avatar anymore and removed it")
|
|
||||||
assert m.is_encrypted()
|
|
||||||
|
|
||||||
lp.sec("ac2: wait for message along with avatar deletion of ac1")
|
|
||||||
msg3 = ac2.wait_next_incoming_message()
|
|
||||||
assert msg3.get_sender_contact().get_profile_image() is None
|
|
||||||
|
|
||||||
def test_set_get_group_image(self, acfactory, data, lp):
|
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||||
|
|
||||||
lp.sec("create unpromoted group chat")
|
lp.sec("create unpromoted group chat")
|
||||||
@@ -1172,52 +888,6 @@ class TestOnlineAccount:
|
|||||||
assert not locations3
|
assert not locations3
|
||||||
|
|
||||||
|
|
||||||
class TestGroupStressTests:
|
|
||||||
def test_group_many_members_add_leave_remove(self, acfactory, lp):
|
|
||||||
lp.sec("creating and configuring five accounts")
|
|
||||||
ac1 = acfactory.get_online_configuring_account()
|
|
||||||
accounts = [acfactory.get_online_configuring_account() for i in range(3)]
|
|
||||||
wait_configuration_progress(ac1, 1000)
|
|
||||||
for acc in accounts:
|
|
||||||
wait_configuration_progress(acc, 1000)
|
|
||||||
|
|
||||||
lp.sec("ac1: creating group chat with 3 other members")
|
|
||||||
chat = ac1.create_group_chat("title1")
|
|
||||||
contacts = []
|
|
||||||
chars = list("äöüsr")
|
|
||||||
for acc in accounts:
|
|
||||||
contact = ac1.create_contact(acc.get_config("addr"), name=chars.pop())
|
|
||||||
contacts.append(contact)
|
|
||||||
chat.add_contact(contact)
|
|
||||||
# make sure the other side accepts our messages
|
|
||||||
c1 = acc.create_contact(ac1.get_config("addr"), "ä member")
|
|
||||||
acc.create_chat_by_contact(c1)
|
|
||||||
|
|
||||||
assert not chat.is_promoted()
|
|
||||||
|
|
||||||
lp.sec("ac1: send mesage to new group chat")
|
|
||||||
chat.send_text("hello")
|
|
||||||
assert chat.is_promoted()
|
|
||||||
|
|
||||||
num_contacts = len(chat.get_contacts())
|
|
||||||
assert num_contacts == 3 + 1
|
|
||||||
|
|
||||||
lp.sec("ac2: checking that the chat arrived correctly")
|
|
||||||
ac2 = accounts[0]
|
|
||||||
msg = ac2.wait_next_incoming_message()
|
|
||||||
assert msg.text == "hello"
|
|
||||||
print("chat is", msg.chat)
|
|
||||||
assert len(msg.chat.get_contacts()) == 4
|
|
||||||
|
|
||||||
lp.sec("ac1: removing one contacts and checking things are right")
|
|
||||||
to_remove = msg.chat.get_contacts()[-1]
|
|
||||||
msg.chat.remove_contact(to_remove)
|
|
||||||
|
|
||||||
sysmsg = ac1.wait_next_incoming_message()
|
|
||||||
assert to_remove.addr in sysmsg.text
|
|
||||||
assert len(sysmsg.chat.get_contacts()) == 3
|
|
||||||
|
|
||||||
|
|
||||||
class TestOnlineConfigureFails:
|
class TestOnlineConfigureFails:
|
||||||
def test_invalid_password(self, acfactory):
|
def test_invalid_password(self, acfactory):
|
||||||
ac1, configdict = acfactory.get_online_config()
|
ac1, configdict = acfactory.get_online_config()
|
||||||
|
|||||||
@@ -1,49 +1,10 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import os.path
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from filecmp import cmp
|
from filecmp import cmp
|
||||||
|
|
||||||
from conftest import wait_configuration_progress, wait_msgs_changed
|
|
||||||
from deltachat import const
|
from deltachat import const
|
||||||
|
from conftest import wait_configuration_progress, wait_msgs_changed
|
||||||
|
|
||||||
|
|
||||||
class TestOnlineInCreation:
|
class TestOnlineInCreation:
|
||||||
def test_increation_not_blobdir(self, tmpdir, acfactory, lp):
|
|
||||||
ac1 = acfactory.get_online_configuring_account()
|
|
||||||
ac2 = acfactory.get_online_configuring_account()
|
|
||||||
wait_configuration_progress(ac1, 1000)
|
|
||||||
wait_configuration_progress(ac2, 1000)
|
|
||||||
|
|
||||||
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
|
||||||
chat = ac1.create_chat_by_contact(c2)
|
|
||||||
|
|
||||||
lp.sec("Creating in-creation file outside of blobdir")
|
|
||||||
assert tmpdir.strpath != ac1.get_blobdir()
|
|
||||||
src = tmpdir.join('file.txt').ensure(file=1)
|
|
||||||
with pytest.raises(Exception):
|
|
||||||
chat.prepare_message_file(src.strpath)
|
|
||||||
|
|
||||||
def test_no_increation_copies_to_blobdir(self, tmpdir, acfactory, lp):
|
|
||||||
ac1 = acfactory.get_online_configuring_account()
|
|
||||||
ac2 = acfactory.get_online_configuring_account()
|
|
||||||
wait_configuration_progress(ac1, 1000)
|
|
||||||
wait_configuration_progress(ac2, 1000)
|
|
||||||
|
|
||||||
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
|
||||||
chat = ac1.create_chat_by_contact(c2)
|
|
||||||
|
|
||||||
lp.sec("Creating file outside of blobdir")
|
|
||||||
assert tmpdir.strpath != ac1.get_blobdir()
|
|
||||||
src = tmpdir.join('file.txt')
|
|
||||||
src.write("hello there\n")
|
|
||||||
chat.send_file(src.strpath)
|
|
||||||
|
|
||||||
blob_src = os.path.join(ac1.get_blobdir(), 'file.txt')
|
|
||||||
assert os.path.exists(blob_src), "file.txt not copied to blobdir"
|
|
||||||
|
|
||||||
def test_forward_increation(self, acfactory, data, lp):
|
def test_forward_increation(self, acfactory, data, lp):
|
||||||
ac1 = acfactory.get_online_configuring_account()
|
ac1 = acfactory.get_online_configuring_account()
|
||||||
ac2 = acfactory.get_online_configuring_account()
|
ac2 = acfactory.get_online_configuring_account()
|
||||||
@@ -56,10 +17,7 @@ class TestOnlineInCreation:
|
|||||||
wait_msgs_changed(ac1, 0, 0) # why no chat id?
|
wait_msgs_changed(ac1, 0, 0) # why no chat id?
|
||||||
|
|
||||||
lp.sec("create a message with a file in creation")
|
lp.sec("create a message with a file in creation")
|
||||||
orig = data.get_path("d.png")
|
path = data.get_path("d.png")
|
||||||
path = os.path.join(ac1.get_blobdir(), 'd.png')
|
|
||||||
with open(path, "x") as fp:
|
|
||||||
fp.write("preparing")
|
|
||||||
prepared_original = chat.prepare_message_file(path)
|
prepared_original = chat.prepare_message_file(path)
|
||||||
assert prepared_original.is_out_preparing()
|
assert prepared_original.is_out_preparing()
|
||||||
wait_msgs_changed(ac1, chat.id, prepared_original.id)
|
wait_msgs_changed(ac1, chat.id, prepared_original.id)
|
||||||
@@ -80,7 +38,6 @@ class TestOnlineInCreation:
|
|||||||
|
|
||||||
lp.sec("finish creating the file and send it")
|
lp.sec("finish creating the file and send it")
|
||||||
assert prepared_original.is_out_preparing()
|
assert prepared_original.is_out_preparing()
|
||||||
shutil.copyfile(orig, path)
|
|
||||||
chat.send_prepared(prepared_original)
|
chat.send_prepared(prepared_original)
|
||||||
assert prepared_original.is_out_pending() or prepared_original.is_out_delivered()
|
assert prepared_original.is_out_pending() or prepared_original.is_out_delivered()
|
||||||
wait_msgs_changed(ac1, chat.id, prepared_original.id)
|
wait_msgs_changed(ac1, chat.id, prepared_original.id)
|
||||||
@@ -100,13 +57,13 @@ class TestOnlineInCreation:
|
|||||||
|
|
||||||
lp.sec("wait1 for original or forwarded messages to arrive")
|
lp.sec("wait1 for original or forwarded messages to arrive")
|
||||||
ev1 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
ev1 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||||
assert ev1[1] > const.DC_CHAT_ID_LAST_SPECIAL
|
assert ev1[1] >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
received_original = ac2.get_message_by_id(ev1[2])
|
received_original = ac2.get_message_by_id(ev1[2])
|
||||||
assert cmp(received_original.filename, orig, shallow=False)
|
assert cmp(received_original.filename, path, False)
|
||||||
|
|
||||||
lp.sec("wait2 for original or forwarded messages to arrive")
|
lp.sec("wait2 for original or forwarded messages to arrive")
|
||||||
ev2 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
ev2 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||||
assert ev2[1] > const.DC_CHAT_ID_LAST_SPECIAL
|
assert ev2[1] >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
assert ev2[1] != ev1[1]
|
assert ev2[1] != ev1[1]
|
||||||
received_copy = ac2.get_message_by_id(ev2[2])
|
received_copy = ac2.get_message_by_id(ev2[2])
|
||||||
assert cmp(received_copy.filename, orig, shallow=False)
|
assert cmp(received_copy.filename, path, False)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ def test_dc_close_events(tmpdir):
|
|||||||
else:
|
else:
|
||||||
print("skipping event", *ev)
|
print("skipping event", *ev)
|
||||||
|
|
||||||
find("disconnecting inbox-thread")
|
find("disconnecting INBOX-watch")
|
||||||
find("disconnecting sentbox-thread")
|
find("disconnecting sentbox-thread")
|
||||||
find("disconnecting mvbox-thread")
|
find("disconnecting mvbox-thread")
|
||||||
find("disconnecting SMTP")
|
find("disconnecting SMTP")
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
[tox]
|
[tox]
|
||||||
# make sure to update environment list in travis.yml and appveyor.yml
|
# make sure to update environment list in travis.yml and appveyor.yml
|
||||||
envlist =
|
envlist =
|
||||||
py37
|
py35
|
||||||
lint
|
lint
|
||||||
auditwheels
|
auditwheels
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
commands =
|
commands =
|
||||||
pytest -n6 --reruns 2 --reruns-delay 5 -v -rsXx {posargs:tests}
|
pytest -v -rsXx {posargs:tests}
|
||||||
# python tests/package_wheels.py {toxworkdir}/wheelhouse
|
python tests/package_wheels.py {toxworkdir}/wheelhouse
|
||||||
passenv =
|
passenv =
|
||||||
TRAVIS
|
TRAVIS
|
||||||
DCC_RS_DEV
|
DCC_RS_DEV
|
||||||
DCC_RS_TARGET
|
DCC_RS_TARGET
|
||||||
DCC_PY_LIVECONFIG
|
DCC_PY_LIVECONFIG
|
||||||
CARGO_TARGET_DIR
|
|
||||||
RUSTC_WRAPPER
|
|
||||||
deps =
|
deps =
|
||||||
pytest
|
pytest
|
||||||
pytest-rerunfailures
|
pytest-rerunfailures
|
||||||
@@ -30,9 +28,10 @@ deps = auditwheel
|
|||||||
commands =
|
commands =
|
||||||
python tests/auditwheels.py {toxworkdir}/wheelhouse
|
python tests/auditwheels.py {toxworkdir}/wheelhouse
|
||||||
|
|
||||||
|
|
||||||
[testenv:lint]
|
[testenv:lint]
|
||||||
skipsdist = True
|
skipsdist = True
|
||||||
skip_install = True
|
usedevelop = True
|
||||||
deps =
|
deps =
|
||||||
flake8
|
flake8
|
||||||
# pygments required by rst-lint
|
# pygments required by rst-lint
|
||||||
@@ -44,29 +43,19 @@ commands =
|
|||||||
rst-lint --encoding 'utf-8' README.rst
|
rst-lint --encoding 'utf-8' README.rst
|
||||||
|
|
||||||
[testenv:doc]
|
[testenv:doc]
|
||||||
changedir=doc
|
basepython = python3.5
|
||||||
deps =
|
deps =
|
||||||
sphinx==2.2.0
|
sphinx==2.2.0
|
||||||
breathe
|
breathe
|
||||||
|
|
||||||
|
changedir = doc
|
||||||
commands =
|
commands =
|
||||||
sphinx-build -Q -w toxdoc-warnings.log -b html . _build/html
|
sphinx-build -w docker-toxdoc-warnings.log -b html . _build/html
|
||||||
|
|
||||||
|
|
||||||
[testenv:lintdoc]
|
|
||||||
skipsdist = True
|
|
||||||
usedevelop = True
|
|
||||||
deps =
|
|
||||||
{[testenv:lint]deps}
|
|
||||||
{[testenv:doc]deps}
|
|
||||||
commands =
|
|
||||||
{[testenv:lint]commands}
|
|
||||||
{[testenv:doc]commands}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[pytest]
|
[pytest]
|
||||||
addopts = -v -ra
|
addopts = -v -rs --reruns 3 --reruns-delay 2
|
||||||
python_files = tests/test_*.py
|
python_files = tests/test_*.py
|
||||||
norecursedirs = .tox
|
norecursedirs = .tox
|
||||||
xfail_strict=true
|
xfail_strict=true
|
||||||
timeout = 60
|
timeout = 60
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ def replace_toml_version(relpath, newversion):
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
if len(sys.argv) < 2:
|
if len(sys.argv) < 2:
|
||||||
for x in ("Cargo.toml", "deltachat-ffi/Cargo.toml"):
|
|
||||||
print("{}: {}".format(x, read_toml_version(x)))
|
|
||||||
raise SystemExit("need argument: new version, example 1.0.0-beta.27")
|
raise SystemExit("need argument: new version, example 1.0.0-beta.27")
|
||||||
newversion = sys.argv[1]
|
newversion = sys.argv[1]
|
||||||
if newversion.count(".") < 2:
|
if newversion.count(".") < 2:
|
||||||
@@ -55,9 +53,7 @@ if __name__ == "__main__":
|
|||||||
replace_toml_version("Cargo.toml", newversion)
|
replace_toml_version("Cargo.toml", newversion)
|
||||||
replace_toml_version("deltachat-ffi/Cargo.toml", newversion)
|
replace_toml_version("deltachat-ffi/Cargo.toml", newversion)
|
||||||
|
|
||||||
subprocess.call(["cargo", "check"])
|
subprocess.call(["cargo", "update", "-p", "deltachat"])
|
||||||
subprocess.call(["git", "add", "-u"])
|
|
||||||
# subprocess.call(["cargo", "update", "-p", "deltachat"])
|
|
||||||
|
|
||||||
print("after commit make sure to: ")
|
print("after commit make sure to: ")
|
||||||
print("")
|
print("")
|
||||||
|
|||||||
26
spec.md
@@ -1,6 +1,6 @@
|
|||||||
# Chat-over-Email specification
|
# Chat-over-Email specification
|
||||||
|
|
||||||
Version 0.20.0
|
Version 0.19.0
|
||||||
|
|
||||||
This document describes how emails can be used
|
This document describes how emails can be used
|
||||||
to implement typical messenger functions
|
to implement typical messenger functions
|
||||||
@@ -248,11 +248,11 @@ and the message SHOULD appear as a message or action from the sender.
|
|||||||
A group MAY have a group-image.
|
A group MAY have a group-image.
|
||||||
To change or set the group-image,
|
To change or set the group-image,
|
||||||
the messenger MUST attach an image file to a message
|
the messenger MUST attach an image file to a message
|
||||||
and MUST add the header `Chat-Group-Avatar`
|
and MUST add the header `Chat-Group-Image`
|
||||||
with the value set to the image name.
|
with the value set to the image name.
|
||||||
|
|
||||||
To remove the group-image,
|
To remove the group-image,
|
||||||
the messenger MUST add the header `Chat-Group-Avatar: 0`.
|
the messenger MUST add the header `Chat-Group-Image: 0`.
|
||||||
|
|
||||||
The messenger SHOULD send an explicit mail for each group image change.
|
The messenger SHOULD send an explicit mail for each group image change.
|
||||||
The body of the message SHOULD contain
|
The body of the message SHOULD contain
|
||||||
@@ -265,7 +265,7 @@ and the message SHOULD appear as a message or action from the sender.
|
|||||||
Chat-Version: 1.0
|
Chat-Version: 1.0
|
||||||
Chat-Group-ID: 12345uvwxyZ
|
Chat-Group-ID: 12345uvwxyZ
|
||||||
Chat-Group-Name: Our Group
|
Chat-Group-Name: Our Group
|
||||||
Chat-Group-Avatar: image.jpg
|
Chat-Group-Image: image.jpg
|
||||||
Message-ID: Gr.12345uvwxyZ.0005@domain
|
Message-ID: Gr.12345uvwxyZ.0005@domain
|
||||||
Subject: Chat: Our Group: Hello, ...
|
Subject: Chat: Our Group: Hello, ...
|
||||||
Content-Type: multipart/mixed; boundary="==break=="
|
Content-Type: multipart/mixed; boundary="==break=="
|
||||||
@@ -283,25 +283,25 @@ and the message SHOULD appear as a message or action from the sender.
|
|||||||
|
|
||||||
The image format SHOULD be image/jpeg or image/png.
|
The image format SHOULD be image/jpeg or image/png.
|
||||||
To save data, it is RECOMMENDED
|
To save data, it is RECOMMENDED
|
||||||
to add a `Chat-Group-Avatar` only on image changes.
|
to add a `Chat-Group-Image` only on image changes.
|
||||||
|
|
||||||
|
|
||||||
# Set profile image
|
# Set profile image
|
||||||
|
|
||||||
A user MAY have a profile-image that MAY be spread to their contacts.
|
A user MAY have a profile-image that MAY be spread to his contacts.
|
||||||
To change or set the profile-image,
|
To change or set the profile-image,
|
||||||
the messenger MUST attach an image file to a message
|
the messenger MUST attach an image file to a message
|
||||||
and MUST add the header `Chat-User-Avatar`
|
and MUST add the header `Chat-Profile-Image`
|
||||||
with the value set to the image name.
|
with the value set to the image name.
|
||||||
|
|
||||||
To remove the profile-image,
|
To remove the profile-image,
|
||||||
the messenger MUST add the header `Chat-User-Avatar: 0`.
|
the messenger MUST add the header `Chat-Profile-Image: 0`.
|
||||||
|
|
||||||
To spread the image,
|
To spread the image,
|
||||||
the messenger MAY send the profile image
|
the messenger MAY send the profile image
|
||||||
together with the next mail to a given contact
|
together with the next mail to a given contact
|
||||||
(to do this only once,
|
(to do this only once,
|
||||||
the messenger has to keep a `user_avatar_update_state` somewhere).
|
the messenger has to keep a `profile_image_update_state` somewhere).
|
||||||
Alternatively, the messenger MAY send an explicit mail
|
Alternatively, the messenger MAY send an explicit mail
|
||||||
for each profile-image change to all contacts using a compatible messenger.
|
for each profile-image change to all contacts using a compatible messenger.
|
||||||
The messenger SHOULD NOT send an explicit mail to normal MUAs.
|
The messenger SHOULD NOT send an explicit mail to normal MUAs.
|
||||||
@@ -309,7 +309,7 @@ The messenger SHOULD NOT send an explicit mail to normal MUAs.
|
|||||||
From: sender@domain
|
From: sender@domain
|
||||||
To: rcpt@domain
|
To: rcpt@domain
|
||||||
Chat-Version: 1.0
|
Chat-Version: 1.0
|
||||||
Chat-User-Avatar: photo.jpg
|
Chat-Profile-Image: photo.jpg
|
||||||
Subject: Chat: Hello, ...
|
Subject: Chat: Hello, ...
|
||||||
Content-Type: multipart/mixed; boundary="==break=="
|
Content-Type: multipart/mixed; boundary="==break=="
|
||||||
|
|
||||||
@@ -325,10 +325,10 @@ The messenger SHOULD NOT send an explicit mail to normal MUAs.
|
|||||||
--==break==--
|
--==break==--
|
||||||
|
|
||||||
The image format SHOULD be image/jpeg or image/png.
|
The image format SHOULD be image/jpeg or image/png.
|
||||||
Note that `Chat-User-Avatar` may appear together with all other headers,
|
Note that `Chat-Profile-Image` may appear together with all other headers,
|
||||||
eg. there may be a `Chat-User-Avatar` and a `Chat-Group-Avatar` header
|
eg. there may be a `Chat-Profile-Image` and a `Chat-Group-Image` header
|
||||||
in the same message.
|
in the same message.
|
||||||
To save data, it is RECOMMENDED to add a `Chat-User-Avatar` header
|
To save data, it is RECOMMENDED to add a `Chat-Profile-Image` header
|
||||||
only on image changes.
|
only on image changes.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
189
src/aheader.rs
@@ -1,14 +1,12 @@
|
|||||||
//! # Autocrypt header module
|
|
||||||
//!
|
|
||||||
//! Parse and create [Autocrypt-headers](https://autocrypt.org/en/latest/level1.html#the-autocrypt-header).
|
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::ffi::CStr;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{fmt, str};
|
use std::{fmt, str};
|
||||||
|
|
||||||
|
use mmime::mailimf::types::*;
|
||||||
|
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::contact::*;
|
use crate::contact::*;
|
||||||
use crate::context::Context;
|
|
||||||
use crate::key::*;
|
use crate::key::*;
|
||||||
|
|
||||||
/// Possible values for encryption preference
|
/// Possible values for encryption preference
|
||||||
@@ -42,13 +40,13 @@ impl str::FromStr for EncryptPreference {
|
|||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match s {
|
match s {
|
||||||
"mutual" => Ok(EncryptPreference::Mutual),
|
"mutual" => Ok(EncryptPreference::Mutual),
|
||||||
"nopreference" => Ok(EncryptPreference::NoPreference),
|
"reset" => Ok(EncryptPreference::Reset),
|
||||||
_ => Err(()),
|
_ => Ok(EncryptPreference::NoPreference),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Autocrypt header
|
/// Parse and create [Autocrypt-headers](https://autocrypt.org/en/latest/level1.html#the-autocrypt-header).
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Aheader {
|
pub struct Aheader {
|
||||||
pub addr: String,
|
pub addr: String,
|
||||||
@@ -57,7 +55,6 @@ pub struct Aheader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Aheader {
|
impl Aheader {
|
||||||
/// Creates new autocrypt header
|
|
||||||
pub fn new(addr: String, public_key: Key, prefer_encrypt: EncryptPreference) -> Self {
|
pub fn new(addr: String, public_key: Key, prefer_encrypt: EncryptPreference) -> Self {
|
||||||
Aheader {
|
Aheader {
|
||||||
addr,
|
addr,
|
||||||
@@ -66,54 +63,60 @@ impl Aheader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_headers(
|
pub fn from_imffields(wanted_from: &str, header: *const mailimf_fields) -> Option<Self> {
|
||||||
context: &Context,
|
if header.is_null() {
|
||||||
wanted_from: &str,
|
return None;
|
||||||
headers: &[mailparse::MailHeader<'_>],
|
|
||||||
) -> Option<Self> {
|
|
||||||
use mailparse::MailHeaderMap;
|
|
||||||
|
|
||||||
if let Ok(Some(value)) = headers.get_first_value("Autocrypt") {
|
|
||||||
match Self::from_str(&value) {
|
|
||||||
Ok(header) => {
|
|
||||||
if addr_cmp(&header.addr, wanted_from) {
|
|
||||||
return Some(header);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
warn!(
|
|
||||||
context,
|
|
||||||
"found invalid autocrypt header {}: {:?}", value, err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
let mut fine_header = None;
|
||||||
|
let mut cur = unsafe { (*(*header).fld_list).first };
|
||||||
|
|
||||||
|
while !cur.is_null() {
|
||||||
|
let field = unsafe { (*cur).data as *mut mailimf_field };
|
||||||
|
if !field.is_null()
|
||||||
|
&& unsafe { (*field).fld_type } == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int
|
||||||
|
{
|
||||||
|
let optional_field = unsafe { (*field).fld_data.fld_optional_field };
|
||||||
|
if !optional_field.is_null()
|
||||||
|
&& unsafe { !(*optional_field).fld_name.is_null() }
|
||||||
|
&& unsafe { CStr::from_ptr((*optional_field).fld_name).to_string_lossy() }
|
||||||
|
== "Autocrypt"
|
||||||
|
{
|
||||||
|
let value =
|
||||||
|
unsafe { CStr::from_ptr((*optional_field).fld_value).to_string_lossy() };
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cur = unsafe { (*cur).next };
|
||||||
|
}
|
||||||
|
|
||||||
|
fine_header
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Aheader {
|
impl fmt::Display for Aheader {
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(fmt, "addr={};", self.addr)?;
|
// TODO replace 78 with enum /rtn
|
||||||
if self.prefer_encrypt == EncryptPreference::Mutual {
|
// adds a whitespace every 78 characters, this allows libEtPan to
|
||||||
write!(fmt, " prefer-encrypt=mutual;")?;
|
// wrap the lines according to RFC 5322
|
||||||
}
|
|
||||||
|
|
||||||
// adds a whitespace every 78 characters, this allows
|
|
||||||
// email crate to wrap the lines according to RFC 5322
|
|
||||||
// (which may insert a linebreak before every whitespace)
|
// (which may insert a linebreak before every whitespace)
|
||||||
let keydata = self.public_key.to_base64().chars().enumerate().fold(
|
let keydata = self.public_key.to_base64(78);
|
||||||
String::new(),
|
write!(
|
||||||
|mut res, (i, c)| {
|
fmt,
|
||||||
if i % 78 == 78 - "keydata=".len() {
|
"addr={}; prefer-encrypt={}; keydata={}",
|
||||||
res.push(' ')
|
self.addr, self.prefer_encrypt, keydata
|
||||||
}
|
)
|
||||||
res.push(c);
|
|
||||||
res
|
|
||||||
},
|
|
||||||
);
|
|
||||||
write!(fmt, " keydata={}", keydata)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,10 +162,13 @@ impl str::FromStr for Aheader {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let prefer_encrypt = attributes
|
let prefer_encrypt = match attributes
|
||||||
.remove("prefer-encrypt")
|
.remove("prefer-encrypt")
|
||||||
.and_then(|raw| raw.parse().ok())
|
.and_then(|raw| raw.parse().ok())
|
||||||
.unwrap_or_default();
|
{
|
||||||
|
Some(pref) => pref,
|
||||||
|
None => EncryptPreference::NoPreference,
|
||||||
|
};
|
||||||
|
|
||||||
// Autocrypt-Level0: unknown attributes starting with an underscore can be safely ignored
|
// Autocrypt-Level0: unknown attributes starting with an underscore can be safely ignored
|
||||||
// Autocrypt-Level0: unknown attribute, treat the header as invalid
|
// Autocrypt-Level0: unknown attribute, treat the header as invalid
|
||||||
@@ -182,13 +188,15 @@ impl str::FromStr for Aheader {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
const RAWKEY: &str = "xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJgWL7O3y/g4HdDW/dN2z26Y6W13IMzx9gLInn1KQZChtqWAcr/ReUucXcymwcfg1mdkBGk3TSLeLihN6CJx8Wsv8ig+kgAzte4f5rqEEAJVQ9WZHuti7UiYs6oRzqTo06CRe9owVXxzdMf0VDQtf7ZFm9dpzKKbhH7Lu8880iiotQ9/yRCkDGp9fNThsrLdZiK6OIAcIBAqi2rI89aS1dAmnRbktQieCx5izzyYkR1KvVL3gTTllHOzfKVEC2asmtWu2e4se/+O4WMIS1eGrn7GeWVb0Vwc5ABEBAAHNETxhQEBiLmV4YW1wbGUuZGU+wsCJBBABCAAzAhkBBQJcxt5FAhsDBAsJCAcGFQgJCgsCAxYCARYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiML938H/18F+3Wf9/JaAy/8hCO1v4S2PVBhxaKCokaNFtkfaMRne2l087LscCFPiFNyb4mv6Z3YeK8Xpxlp2sI0ecvdiqLUOGfnxS6tQrj+83EjtIrZ/hXOk1h121QFWH9Zg2VNHtODXjAgdLDC0NWUrclR0ZOqEDQHeo0ibTILdokVfXFN25wakPmGaYJP2y729cb1ve7RzvIvwn+Dddfxo3ao72rBfLi7l4NQ4S0KsY4cw+/6l5bRCKYCP77wZtvCwUvfVVosLdT43agtSiBI49+ayqvZ8OCvSJa61i+v81brTiEy9GBod4eAp45Ibsuemkw+gon4ZOvUXHTjwFB+h63MrozOwE0EXMbePQEIAL/vauf1zK8JgCu3V+G+SOX0iWw5xUlCPX+ERpBbWfwu3uAqn4wYXD3JDE/fVAF668xiV4eTPtlSUd5h0mn+G7uXMMOtkb+20SoEt50f8zw8TrL9t+ZsV11GKZWJpCar5AhXWsn6EEi8I2hLL5vn55ZZmHuGgN4jjmkRl3ToKCLhaXwTBjCJem7N5EH7F75wErEITa55v4Lb4Nfca7vnvtYrI1OA446xa8gHra0SINelTD09/JM/Fw4sWVPBaRZmJK/Tnu79N23No9XBUubmFPv1pNexZsQclicnTpt/BEWhiun7d6lfGB63K1aoHRTR1pcrWvBuALuuz0gqar2zlI0AEQEAAcLAdgQYAQgAIAUCXMbeRQIbDBYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiMLKSEIAIyLCRO2OyZ0IYRvRPpMn4p7E+7Pfcz/0mSkOy+1hshgJnqivXurm8zwGrwdMqeV4eslKR9H1RUdWGUQJNbtwmmjrt5DHpIhYHl5t3FpCBaGbV20Omo00Q38lBl9MtrmZkZw+ktEk6X+0xCKssMF+2MADkSOIufbR5HrDVB89VZOHCO9DeXvCUUAw2hyJiL/LHmLzJ40zYoTmb+F//f0k0j+tRdbkefyRoCmwG7YGiT+2hnCdgcezswnzah5J3ZKlrg7jOGo1LxtbvNUzxNBbC6S/aNgwm6qxo7xegRhmEl5uZ16zwyj4qz+xkjGy25Of5mWfUDoNw7OT7sjUbHOOMc=";
|
fn rawkey() -> String {
|
||||||
|
"xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJgWL7O3y/g4HdDW/dN2z26Y6W13IMzx9gLInn1KQZChtqWAcr/ReUucXcymwcfg1mdkBGk3TSLeLihN6CJx8Wsv8ig+kgAzte4f5rqEEAJVQ9WZHuti7UiYs6oRzqTo06CRe9owVXxzdMf0VDQtf7ZFm9dpzKKbhH7Lu8880iiotQ9/yRCkDGp9fNThsrLdZiK6OIAcIBAqi2rI89aS1dAmnRbktQieCx5izzyYkR1KvVL3gTTllHOzfKVEC2asmtWu2e4se/+O4WMIS1eGrn7GeWVb0Vwc5ABEBAAHNETxhQEBiLmV4YW1wbGUuZGU+wsCJBBABCAAzAhkBBQJcxt5FAhsDBAsJCAcGFQgJCgsCAxYCARYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiML938H/18F+3Wf9/JaAy/8hCO1v4S2PVBhxaKCokaNFtkfaMRne2l087LscCFPiFNyb4mv6Z3YeK8Xpxlp2sI0ecvdiqLUOGfnxS6tQrj+83EjtIrZ/hXOk1h121QFWH9Zg2VNHtODXjAgdLDC0NWUrclR0ZOqEDQHeo0ibTILdokVfXFN25wakPmGaYJP2y729cb1ve7RzvIvwn+Dddfxo3ao72rBfLi7l4NQ4S0KsY4cw+/6l5bRCKYCP77wZtvCwUvfVVosLdT43agtSiBI49+ayqvZ8OCvSJa61i+v81brTiEy9GBod4eAp45Ibsuemkw+gon4ZOvUXHTjwFB+h63MrozOwE0EXMbePQEIAL/vauf1zK8JgCu3V+G+SOX0iWw5xUlCPX+ERpBbWfwu3uAqn4wYXD3JDE/fVAF668xiV4eTPtlSUd5h0mn+G7uXMMOtkb+20SoEt50f8zw8TrL9t+ZsV11GKZWJpCar5AhXWsn6EEi8I2hLL5vn55ZZmHuGgN4jjmkRl3ToKCLhaXwTBjCJem7N5EH7F75wErEITa55v4Lb4Nfca7vnvtYrI1OA446xa8gHra0SINelTD09/JM/Fw4sWVPBaRZmJK/Tnu79N23No9XBUubmFPv1pNexZsQclicnTpt/BEWhiun7d6lfGB63K1aoHRTR1pcrWvBuALuuz0gqar2zlI0AEQEAAcLAdgQYAQgAIAUCXMbeRQIbDBYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiMLKSEIAIyLCRO2OyZ0IYRvRPpMn4p7E+7Pfcz/0mSkOy+1hshgJnqivXurm8zwGrwdMqeV4eslKR9H1RUdWGUQJNbtwmmjrt5DHpIhYHl5t3FpCBaGbV20Omo00Q38lBl9MtrmZkZw+ktEk6X+0xCKssMF+2MADkSOIufbR5HrDVB89VZOHCO9DeXvCUUAw2hyJiL/LHmLzJ40zYoTmb+F//f0k0j+tRdbkefyRoCmwG7YGiT+2hnCdgcezswnzah5J3ZKlrg7jOGo1LxtbvNUzxNBbC6S/aNgwm6qxo7xegRhmEl5uZ16zwyj4qz+xkjGy25Of5mWfUDoNw7OT7sjUbHOOMc=".into()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_str() {
|
fn test_from_str() {
|
||||||
let h: Aheader = format!(
|
let h: Aheader = format!(
|
||||||
"addr=me@mail.com; prefer-encrypt=mutual; keydata={}",
|
"addr=me@mail.com; prefer-encrypt=mutual; keydata={}",
|
||||||
RAWKEY
|
rawkey()
|
||||||
)
|
)
|
||||||
.parse()
|
.parse()
|
||||||
.expect("failed to parse");
|
.expect("failed to parse");
|
||||||
@@ -197,22 +205,9 @@ mod tests {
|
|||||||
assert_eq!(h.prefer_encrypt, EncryptPreference::Mutual);
|
assert_eq!(h.prefer_encrypt, EncryptPreference::Mutual);
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptPreference::Reset is an internal value, parser should never return it
|
|
||||||
#[test]
|
|
||||||
fn test_from_str_reset() {
|
|
||||||
let raw = format!(
|
|
||||||
"addr=reset@example.com; prefer-encrypt=reset; keydata={}",
|
|
||||||
RAWKEY
|
|
||||||
);
|
|
||||||
let h: Aheader = raw.parse().expect("failed to parse");
|
|
||||||
|
|
||||||
assert_eq!(h.addr, "reset@example.com");
|
|
||||||
assert_eq!(h.prefer_encrypt, EncryptPreference::NoPreference);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_str_non_critical() {
|
fn test_from_str_non_critical() {
|
||||||
let raw = format!("addr=me@mail.com; _foo=one; _bar=two; keydata={}", RAWKEY);
|
let raw = format!("addr=me@mail.com; _foo=one; _bar=two; keydata={}", rawkey());
|
||||||
let h: Aheader = raw.parse().expect("failed to parse");
|
let h: Aheader = raw.parse().expect("failed to parse");
|
||||||
|
|
||||||
assert_eq!(h.addr, "me@mail.com");
|
assert_eq!(h.addr, "me@mail.com");
|
||||||
@@ -223,57 +218,33 @@ mod tests {
|
|||||||
fn test_from_str_superflous_critical() {
|
fn test_from_str_superflous_critical() {
|
||||||
let raw = format!(
|
let raw = format!(
|
||||||
"addr=me@mail.com; _foo=one; _bar=two; other=me; keydata={}",
|
"addr=me@mail.com; _foo=one; _bar=two; other=me; keydata={}",
|
||||||
RAWKEY
|
rawkey()
|
||||||
);
|
);
|
||||||
assert!(raw.parse::<Aheader>().is_err());
|
assert!(raw.parse::<Aheader>().is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_good_headers() {
|
fn test_good_headers() {
|
||||||
let fixed_header = concat!(
|
let fixed_header = "addr=a@b.example.org; prefer-encrypt=mutual; keydata=xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJgWL7O3y/g 4HdDW/dN2z26Y6W13IMzx9gLInn1KQZChtqWAcr/ReUucXcymwcfg1mdkBGk3TSLeLihN6CJx8Wsv8 ig+kgAzte4f5rqEEAJVQ9WZHuti7UiYs6oRzqTo06CRe9owVXxzdMf0VDQtf7ZFm9dpzKKbhH7Lu88 80iiotQ9/yRCkDGp9fNThsrLdZiK6OIAcIBAqi2rI89aS1dAmnRbktQieCx5izzyYkR1KvVL3gTTll HOzfKVEC2asmtWu2e4se/+O4WMIS1eGrn7GeWVb0Vwc5ABEBAAHNETxhQEBiLmV4YW1wbGUuZGU+ws CJBBABCAAzAhkBBQJcxt5FAhsDBAsJCAcGFQgJCgsCAxYCARYhBI4xxYKBgH3ANh5cufaKrc9mtiML AAoJEPaKrc9mtiML938H/18F+3Wf9/JaAy/8hCO1v4S2PVBhxaKCokaNFtkfaMRne2l087LscCFPiF Nyb4mv6Z3YeK8Xpxlp2sI0ecvdiqLUOGfnxS6tQrj+83EjtIrZ/hXOk1h121QFWH9Zg2VNHtODXjAg dLDC0NWUrclR0ZOqEDQHeo0ibTILdokVfXFN25wakPmGaYJP2y729cb1ve7RzvIvwn+Dddfxo3ao72 rBfLi7l4NQ4S0KsY4cw+/6l5bRCKYCP77wZtvCwUvfVVosLdT43agtSiBI49+ayqvZ8OCvSJa61i+v 81brTiEy9GBod4eAp45Ibsuemkw+gon4ZOvUXHTjwFB+h63MrozOwE0EXMbePQEIAL/vauf1zK8JgC u3V+G+SOX0iWw5xUlCPX+ERpBbWfwu3uAqn4wYXD3JDE/fVAF668xiV4eTPtlSUd5h0mn+G7uXMMOt kb+20SoEt50f8zw8TrL9t+ZsV11GKZWJpCar5AhXWsn6EEi8I2hLL5vn55ZZmHuGgN4jjmkRl3ToKC LhaXwTBjCJem7N5EH7F75wErEITa55v4Lb4Nfca7vnvtYrI1OA446xa8gHra0SINelTD09/JM/Fw4s WVPBaRZmJK/Tnu79N23No9XBUubmFPv1pNexZsQclicnTpt/BEWhiun7d6lfGB63K1aoHRTR1pcrWv BuALuuz0gqar2zlI0AEQEAAcLAdgQYAQgAIAUCXMbeRQIbDBYhBI4xxYKBgH3ANh5cufaKrc9mtiML AAoJEPaKrc9mtiMLKSEIAIyLCRO2OyZ0IYRvRPpMn4p7E+7Pfcz/0mSkOy+1hshgJnqivXurm8zwGr wdMqeV4eslKR9H1RUdWGUQJNbtwmmjrt5DHpIhYHl5t3FpCBaGbV20Omo00Q38lBl9MtrmZkZw+ktE k6X+0xCKssMF+2MADkSOIufbR5HrDVB89VZOHCO9DeXvCUUAw2hyJiL/LHmLzJ40zYoTmb+F//f0k0 j+tRdbkefyRoCmwG7YGiT+2hnCdgcezswnzah5J3ZKlrg7jOGo1LxtbvNUzxNBbC6S/aNgwm6qxo7x egRhmEl5uZ16zwyj4qz+xkjGy25Of5mWfUDoNw7OT7sjUbHOOMc=";
|
||||||
"addr=a@b.example.org; prefer-encrypt=mutual; ",
|
|
||||||
"keydata=xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJg",
|
|
||||||
" WL7O3y/g4HdDW/dN2z26Y6W13IMzx9gLInn1KQZChtqWAcr/ReUucXcymwcfg1mdkBGk3TSLeLihN6",
|
|
||||||
" CJx8Wsv8ig+kgAzte4f5rqEEAJVQ9WZHuti7UiYs6oRzqTo06CRe9owVXxzdMf0VDQtf7ZFm9dpzKK",
|
|
||||||
" bhH7Lu8880iiotQ9/yRCkDGp9fNThsrLdZiK6OIAcIBAqi2rI89aS1dAmnRbktQieCx5izzyYkR1Kv",
|
|
||||||
" VL3gTTllHOzfKVEC2asmtWu2e4se/+O4WMIS1eGrn7GeWVb0Vwc5ABEBAAHNETxhQEBiLmV4YW1wbG",
|
|
||||||
" UuZGU+wsCJBBABCAAzAhkBBQJcxt5FAhsDBAsJCAcGFQgJCgsCAxYCARYhBI4xxYKBgH3ANh5cufaK",
|
|
||||||
" rc9mtiMLAAoJEPaKrc9mtiML938H/18F+3Wf9/JaAy/8hCO1v4S2PVBhxaKCokaNFtkfaMRne2l087",
|
|
||||||
" LscCFPiFNyb4mv6Z3YeK8Xpxlp2sI0ecvdiqLUOGfnxS6tQrj+83EjtIrZ/hXOk1h121QFWH9Zg2VN",
|
|
||||||
" HtODXjAgdLDC0NWUrclR0ZOqEDQHeo0ibTILdokVfXFN25wakPmGaYJP2y729cb1ve7RzvIvwn+Ddd",
|
|
||||||
" fxo3ao72rBfLi7l4NQ4S0KsY4cw+/6l5bRCKYCP77wZtvCwUvfVVosLdT43agtSiBI49+ayqvZ8OCv",
|
|
||||||
" SJa61i+v81brTiEy9GBod4eAp45Ibsuemkw+gon4ZOvUXHTjwFB+h63MrozOwE0EXMbePQEIAL/vau",
|
|
||||||
" f1zK8JgCu3V+G+SOX0iWw5xUlCPX+ERpBbWfwu3uAqn4wYXD3JDE/fVAF668xiV4eTPtlSUd5h0mn+",
|
|
||||||
" G7uXMMOtkb+20SoEt50f8zw8TrL9t+ZsV11GKZWJpCar5AhXWsn6EEi8I2hLL5vn55ZZmHuGgN4jjm",
|
|
||||||
" kRl3ToKCLhaXwTBjCJem7N5EH7F75wErEITa55v4Lb4Nfca7vnvtYrI1OA446xa8gHra0SINelTD09",
|
|
||||||
" /JM/Fw4sWVPBaRZmJK/Tnu79N23No9XBUubmFPv1pNexZsQclicnTpt/BEWhiun7d6lfGB63K1aoHR",
|
|
||||||
" TR1pcrWvBuALuuz0gqar2zlI0AEQEAAcLAdgQYAQgAIAUCXMbeRQIbDBYhBI4xxYKBgH3ANh5cufaK",
|
|
||||||
" rc9mtiMLAAoJEPaKrc9mtiMLKSEIAIyLCRO2OyZ0IYRvRPpMn4p7E+7Pfcz/0mSkOy+1hshgJnqivX",
|
|
||||||
" urm8zwGrwdMqeV4eslKR9H1RUdWGUQJNbtwmmjrt5DHpIhYHl5t3FpCBaGbV20Omo00Q38lBl9Mtrm",
|
|
||||||
" ZkZw+ktEk6X+0xCKssMF+2MADkSOIufbR5HrDVB89VZOHCO9DeXvCUUAw2hyJiL/LHmLzJ40zYoTmb",
|
|
||||||
" +F//f0k0j+tRdbkefyRoCmwG7YGiT+2hnCdgcezswnzah5J3ZKlrg7jOGo1LxtbvNUzxNBbC6S/aNg",
|
|
||||||
" wm6qxo7xegRhmEl5uZ16zwyj4qz+xkjGy25Of5mWfUDoNw7OT7sjUbHOOMc="
|
|
||||||
);
|
|
||||||
|
|
||||||
let ah = Aheader::from_str(fixed_header).expect("failed to parse");
|
let ah = Aheader::from_str(fixed_header).expect("failed to parse");
|
||||||
assert_eq!(ah.addr, "a@b.example.org");
|
assert_eq!(ah.addr, "a@b.example.org");
|
||||||
assert_eq!(ah.prefer_encrypt, EncryptPreference::Mutual);
|
assert_eq!(ah.prefer_encrypt, EncryptPreference::Mutual);
|
||||||
assert_eq!(format!("{}", ah), fixed_header);
|
|
||||||
|
|
||||||
let rendered = ah.to_string();
|
let rendered = ah.to_string();
|
||||||
assert_eq!(rendered, fixed_header);
|
assert_eq!(rendered, fixed_header);
|
||||||
|
|
||||||
let ah = Aheader::from_str(&format!(" _foo; __FOO=BAR ;;; addr = a@b.example.org ;\r\n prefer-encrypt = mutual ; keydata = {}", RAWKEY)).expect("failed to parse");
|
let ah = Aheader::from_str(&format!(" _foo; __FOO=BAR ;;; addr = a@b.example.org ;\r\n prefer-encrypt = mutual ; keydata = {}", rawkey())).expect("failed to parse");
|
||||||
assert_eq!(ah.addr, "a@b.example.org");
|
assert_eq!(ah.addr, "a@b.example.org");
|
||||||
assert_eq!(ah.prefer_encrypt, EncryptPreference::Mutual);
|
assert_eq!(ah.prefer_encrypt, EncryptPreference::Mutual);
|
||||||
|
|
||||||
Aheader::from_str(&format!(
|
Aheader::from_str(&format!(
|
||||||
"addr=a@b.example.org; prefer-encrypt=ignoreUnknownValues; keydata={}",
|
"addr=a@b.example.org; prefer-encrypt=ignoreUnknownValues; keydata={}",
|
||||||
RAWKEY
|
rawkey()
|
||||||
))
|
))
|
||||||
.expect("failed to parse");
|
.expect("failed to parse");
|
||||||
|
|
||||||
Aheader::from_str(&format!("addr=a@b.example.org; keydata={}", RAWKEY))
|
Aheader::from_str(&format!("addr=a@b.example.org; keydata={}", rawkey()))
|
||||||
.expect("failed to parse");
|
.expect("failed to parse");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,30 +256,4 @@ mod tests {
|
|||||||
assert!(Aheader::from_str(" ;;").is_err());
|
assert!(Aheader::from_str(" ;;").is_err());
|
||||||
assert!(Aheader::from_str("addr=a@t.de; unknwon=1; keydata=jau").is_err());
|
assert!(Aheader::from_str("addr=a@t.de; unknwon=1; keydata=jau").is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_display_aheader() {
|
|
||||||
assert!(format!(
|
|
||||||
"{}",
|
|
||||||
Aheader::new(
|
|
||||||
"test@example.com".to_string(),
|
|
||||||
Key::from_base64(RAWKEY, KeyType::Public).unwrap(),
|
|
||||||
EncryptPreference::Mutual
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.contains("prefer-encrypt=mutual;"));
|
|
||||||
|
|
||||||
// According to Autocrypt Level 1 specification,
|
|
||||||
// only "prefer-encrypt=mutual;" can be used.
|
|
||||||
// If the setting is nopreference, the whole attribute is omitted.
|
|
||||||
assert!(!format!(
|
|
||||||
"{}",
|
|
||||||
Aheader::new(
|
|
||||||
"test@example.com".to_string(),
|
|
||||||
Key::from_base64(RAWKEY, KeyType::Public).unwrap(),
|
|
||||||
EncryptPreference::NoPreference
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.contains("prefer-encrypt"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
366
src/blob.rs
@@ -1,18 +1,12 @@
|
|||||||
//! # Blob directory management
|
|
||||||
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use self::image::GenericImageView;
|
|
||||||
use crate::constants::AVATAR_SIZE;
|
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::events::Event;
|
use crate::events::Event;
|
||||||
|
|
||||||
extern crate image;
|
|
||||||
|
|
||||||
/// Represents a file in the blob directory.
|
/// Represents a file in the blob directory.
|
||||||
///
|
///
|
||||||
/// The object has a name, which will always be valid UTF-8. Having a
|
/// The object has a name, which will always be valid UTF-8. Having a
|
||||||
@@ -36,11 +30,11 @@ impl<'a> BlobObject<'a> {
|
|||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// [BlobError::CreateFailure] is used when the file could not
|
/// [BlobErrorKind::CreateFailure] is used when the file could not
|
||||||
/// be created. You can expect [BlobError.cause] to contain an
|
/// be created. You can expect [BlobError.cause] to contain an
|
||||||
/// underlying error.
|
/// underlying error.
|
||||||
///
|
///
|
||||||
/// [BlobError::WriteFailure] is used when the file could not
|
/// [BlobErrorKind::WriteFailure] is used when the file could not
|
||||||
/// be written to. You can expect [BlobError.cause] to contain an
|
/// be written to. You can expect [BlobError.cause] to contain an
|
||||||
/// underlying error.
|
/// underlying error.
|
||||||
pub fn create(
|
pub fn create(
|
||||||
@@ -52,12 +46,7 @@ impl<'a> BlobObject<'a> {
|
|||||||
let (stem, ext) = BlobObject::sanitise_name(suggested_name.as_ref());
|
let (stem, ext) = BlobObject::sanitise_name(suggested_name.as_ref());
|
||||||
let (name, mut file) = BlobObject::create_new_file(&blobdir, &stem, &ext)?;
|
let (name, mut file) = BlobObject::create_new_file(&blobdir, &stem, &ext)?;
|
||||||
file.write_all(data)
|
file.write_all(data)
|
||||||
.map_err(|err| BlobError::WriteFailure {
|
.map_err(|err| BlobError::new_write_failure(blobdir, &name, err))?;
|
||||||
blobdir: blobdir.to_path_buf(),
|
|
||||||
blobname: name.clone(),
|
|
||||||
cause: err,
|
|
||||||
backtrace: failure::Backtrace::new(),
|
|
||||||
})?;
|
|
||||||
let blob = BlobObject {
|
let blob = BlobObject {
|
||||||
blobdir,
|
blobdir,
|
||||||
name: format!("$BLOBDIR/{}", name),
|
name: format!("$BLOBDIR/{}", name),
|
||||||
@@ -80,25 +69,18 @@ impl<'a> BlobObject<'a> {
|
|||||||
Ok(file) => return Ok((name, file)),
|
Ok(file) => return Ok((name, file)),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if attempt == max_attempt {
|
if attempt == max_attempt {
|
||||||
return Err(BlobError::CreateFailure {
|
return Err(BlobError::new_create_failure(dir, &name, err));
|
||||||
blobdir: dir.to_path_buf(),
|
|
||||||
blobname: name,
|
|
||||||
cause: err,
|
|
||||||
backtrace: failure::Backtrace::new(),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
name = format!("{}-{}{}", stem, rand::random::<u32>(), ext);
|
name = format!("{}-{}{}", stem, rand::random::<u32>(), ext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// This is supposed to be unreachable, but the compiler doesn't know.
|
Err(BlobError::new_create_failure(
|
||||||
Err(BlobError::CreateFailure {
|
dir,
|
||||||
blobdir: dir.to_path_buf(),
|
&name,
|
||||||
blobname: name,
|
format_err!("Unreachable code - supposedly"),
|
||||||
cause: std::io::Error::new(std::io::ErrorKind::Other, "supposedly unreachable"),
|
))
|
||||||
backtrace: failure::Backtrace::new(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new blob object with unique name by copying an existing file.
|
/// Creates a new blob object with unique name by copying an existing file.
|
||||||
@@ -111,35 +93,24 @@ impl<'a> BlobObject<'a> {
|
|||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// In addition to the errors in [BlobObject::create] the
|
/// In addition to the errors in [BlobObject::create] the
|
||||||
/// [BlobError::CopyFailure] is used when the data can not be
|
/// [BlobErrorKind::CopyFailure] is used when the data can not be
|
||||||
/// copied.
|
/// copied.
|
||||||
pub fn create_and_copy(
|
pub fn create_and_copy(
|
||||||
context: &'a Context,
|
context: &'a Context,
|
||||||
src: impl AsRef<Path>,
|
src: impl AsRef<Path>,
|
||||||
) -> std::result::Result<BlobObject<'a>, BlobError> {
|
) -> std::result::Result<BlobObject<'a>, BlobError> {
|
||||||
let mut src_file = fs::File::open(src.as_ref()).map_err(|err| BlobError::CopyFailure {
|
let mut src_file = fs::File::open(src.as_ref()).map_err(|err| {
|
||||||
blobdir: context.get_blobdir().to_path_buf(),
|
BlobError::new_copy_failure(context.get_blobdir(), "", src.as_ref(), err)
|
||||||
blobname: String::from(""),
|
|
||||||
src: src.as_ref().to_path_buf(),
|
|
||||||
cause: err,
|
|
||||||
backtrace: failure::Backtrace::new(),
|
|
||||||
})?;
|
})?;
|
||||||
let (stem, ext) = BlobObject::sanitise_name(&src.as_ref().to_string_lossy());
|
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)?;
|
let (name, mut dst_file) = BlobObject::create_new_file(context.get_blobdir(), &stem, &ext)?;
|
||||||
let name_for_err = name.clone();
|
|
||||||
std::io::copy(&mut src_file, &mut dst_file).map_err(|err| {
|
std::io::copy(&mut src_file, &mut dst_file).map_err(|err| {
|
||||||
{
|
{
|
||||||
// Attempt to remove the failed file, swallow errors resulting from that.
|
// Attempt to remove the failed file, swallow errors resulting from that.
|
||||||
let path = context.get_blobdir().join(&name_for_err);
|
let path = context.get_blobdir().join(&name);
|
||||||
fs::remove_file(path).ok();
|
fs::remove_file(path).ok();
|
||||||
}
|
}
|
||||||
BlobError::CopyFailure {
|
BlobError::new_copy_failure(context.get_blobdir(), &name, src.as_ref(), err)
|
||||||
blobdir: context.get_blobdir().to_path_buf(),
|
|
||||||
blobname: name_for_err,
|
|
||||||
src: src.as_ref().to_path_buf(),
|
|
||||||
cause: err,
|
|
||||||
backtrace: failure::Backtrace::new(),
|
|
||||||
}
|
|
||||||
})?;
|
})?;
|
||||||
let blob = BlobObject {
|
let blob = BlobObject {
|
||||||
blobdir: context.get_blobdir(),
|
blobdir: context.get_blobdir(),
|
||||||
@@ -163,14 +134,13 @@ impl<'a> BlobObject<'a> {
|
|||||||
/// This merely delegates to the [BlobObject::create_and_copy] and
|
/// This merely delegates to the [BlobObject::create_and_copy] and
|
||||||
/// the [BlobObject::from_path] methods. See those for possible
|
/// the [BlobObject::from_path] methods. See those for possible
|
||||||
/// errors.
|
/// errors.
|
||||||
pub fn new_from_path(
|
pub fn create_from_path(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
src: impl AsRef<Path>,
|
src: impl AsRef<Path>,
|
||||||
) -> std::result::Result<BlobObject, BlobError> {
|
) -> std::result::Result<BlobObject, BlobError> {
|
||||||
if src.as_ref().starts_with(context.get_blobdir()) {
|
match src.as_ref().starts_with(context.get_blobdir()) {
|
||||||
BlobObject::from_path(context, src)
|
true => BlobObject::from_path(context, src),
|
||||||
} else {
|
false => BlobObject::create_and_copy(context, src),
|
||||||
BlobObject::create_and_copy(context, src)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,10 +153,10 @@ impl<'a> BlobObject<'a> {
|
|||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// [BlobError::WrongBlobdir] is used if the path is not in
|
/// [BlobErrorKind::WrongBlobdir] is used if the path is not in
|
||||||
/// the blob directory.
|
/// the blob directory.
|
||||||
///
|
///
|
||||||
/// [BlobError::WrongName] is used if the file name does not
|
/// [BlobErrorKind::WrongName] is used if the file name does not
|
||||||
/// remain identical after sanitisation.
|
/// remain identical after sanitisation.
|
||||||
pub fn from_path(
|
pub fn from_path(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
@@ -195,21 +165,13 @@ impl<'a> BlobObject<'a> {
|
|||||||
let rel_path = path
|
let rel_path = path
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.strip_prefix(context.get_blobdir())
|
.strip_prefix(context.get_blobdir())
|
||||||
.map_err(|_| BlobError::WrongBlobdir {
|
.map_err(|_| BlobError::new_wrong_blobdir(context.get_blobdir(), path.as_ref()))?;
|
||||||
blobdir: context.get_blobdir().to_path_buf(),
|
|
||||||
src: path.as_ref().to_path_buf(),
|
|
||||||
backtrace: failure::Backtrace::new(),
|
|
||||||
})?;
|
|
||||||
if !BlobObject::is_acceptible_blob_name(&rel_path) {
|
if !BlobObject::is_acceptible_blob_name(&rel_path) {
|
||||||
return Err(BlobError::WrongName {
|
return Err(BlobError::new_wrong_name(path.as_ref()));
|
||||||
blobname: path.as_ref().to_path_buf(),
|
|
||||||
backtrace: failure::Backtrace::new(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
let name = rel_path.to_str().ok_or_else(|| BlobError::WrongName {
|
let name = rel_path
|
||||||
blobname: path.as_ref().to_path_buf(),
|
.to_str()
|
||||||
backtrace: failure::Backtrace::new(),
|
.ok_or_else(|| BlobError::new_wrong_name(path.as_ref()))?;
|
||||||
})?;
|
|
||||||
BlobObject::from_name(context, name.to_string())
|
BlobObject::from_name(context, name.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +184,7 @@ impl<'a> BlobObject<'a> {
|
|||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// [BlobError::WrongName] is used if the name is not a valid
|
/// [BlobErrorKind::WrongName] is used if the name is not a valid
|
||||||
/// blobname, i.e. if [BlobObject::sanitise_name] does modify the
|
/// blobname, i.e. if [BlobObject::sanitise_name] does modify the
|
||||||
/// provided name.
|
/// provided name.
|
||||||
pub fn from_name(
|
pub fn from_name(
|
||||||
@@ -234,10 +196,7 @@ impl<'a> BlobObject<'a> {
|
|||||||
false => name,
|
false => name,
|
||||||
};
|
};
|
||||||
if !BlobObject::is_acceptible_blob_name(&name) {
|
if !BlobObject::is_acceptible_blob_name(&name) {
|
||||||
return Err(BlobError::WrongName {
|
return Err(BlobError::new_wrong_name(name));
|
||||||
blobname: PathBuf::from(name),
|
|
||||||
backtrace: failure::Backtrace::new(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
Ok(BlobObject {
|
Ok(BlobObject {
|
||||||
blobdir: context.get_blobdir(),
|
blobdir: context.get_blobdir(),
|
||||||
@@ -303,13 +262,13 @@ impl<'a> BlobObject<'a> {
|
|||||||
fn sanitise_name(name: &str) -> (String, String) {
|
fn sanitise_name(name: &str) -> (String, String) {
|
||||||
let mut name = name.to_string();
|
let mut name = name.to_string();
|
||||||
for part in name.rsplit('/') {
|
for part in name.rsplit('/') {
|
||||||
if !part.is_empty() {
|
if part.len() > 0 {
|
||||||
name = part.to_string();
|
name = part.to_string();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for part in name.rsplit('\\') {
|
for part in name.rsplit('\\') {
|
||||||
if !part.is_empty() {
|
if part.len() > 0 {
|
||||||
name = part.to_string();
|
name = part.to_string();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -321,13 +280,13 @@ impl<'a> BlobObject<'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let clean = sanitize_filename::sanitize_with_options(name, opts);
|
let clean = sanitize_filename::sanitize_with_options(name, opts);
|
||||||
let mut iter = clean.splitn(2, '.');
|
let mut iter = clean.rsplitn(2, '.');
|
||||||
let mut stem = iter.next().unwrap_or_default().to_string();
|
|
||||||
let mut ext = iter.next().unwrap_or_default().to_string();
|
let mut ext = iter.next().unwrap_or_default().to_string();
|
||||||
stem.truncate(64);
|
let mut stem = iter.next().unwrap_or_default().to_string();
|
||||||
ext.truncate(32);
|
ext.truncate(32);
|
||||||
match ext.len() {
|
stem.truncate(64);
|
||||||
0 => (stem, "".to_string()),
|
match stem.len() {
|
||||||
|
0 => (ext, "".to_string()),
|
||||||
_ => (stem, format!(".{}", ext).to_lowercase()),
|
_ => (stem, format!(".{}", ext).to_lowercase()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,31 +312,6 @@ impl<'a> BlobObject<'a> {
|
|||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recode_to_avatar_size(&self, context: &Context) -> Result<(), BlobError> {
|
|
||||||
let blob_abs = self.to_abs_path();
|
|
||||||
let img = image::open(&blob_abs).map_err(|err| BlobError::RecodeFailure {
|
|
||||||
blobdir: context.get_blobdir().to_path_buf(),
|
|
||||||
blobname: blob_abs.to_str().unwrap_or_default().to_string(),
|
|
||||||
cause: err,
|
|
||||||
backtrace: failure::Backtrace::new(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if img.width() <= AVATAR_SIZE && img.height() <= AVATAR_SIZE {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let img = img.thumbnail(AVATAR_SIZE, AVATAR_SIZE);
|
|
||||||
|
|
||||||
img.save(&blob_abs).map_err(|err| BlobError::WriteFailure {
|
|
||||||
blobdir: context.get_blobdir().to_path_buf(),
|
|
||||||
blobname: blob_abs.to_str().unwrap_or_default().to_string(),
|
|
||||||
cause: err,
|
|
||||||
backtrace: failure::Backtrace::new(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> fmt::Display for BlobObject<'a> {
|
impl<'a> fmt::Display for BlobObject<'a> {
|
||||||
@@ -387,57 +321,171 @@ impl<'a> fmt::Display for BlobObject<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Errors for the [BlobObject].
|
/// Errors for the [BlobObject].
|
||||||
#[derive(Fail, Debug)]
|
///
|
||||||
pub enum BlobError {
|
/// 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 {
|
CreateFailure {
|
||||||
blobdir: PathBuf,
|
blobdir: PathBuf,
|
||||||
blobname: String,
|
blobname: String,
|
||||||
#[cause]
|
cause: failure::Error,
|
||||||
cause: std::io::Error,
|
|
||||||
backtrace: failure::Backtrace,
|
|
||||||
},
|
},
|
||||||
WriteFailure {
|
WriteFailure {
|
||||||
blobdir: PathBuf,
|
blobdir: PathBuf,
|
||||||
blobname: String,
|
blobname: String,
|
||||||
#[cause]
|
cause: failure::Error,
|
||||||
cause: std::io::Error,
|
|
||||||
backtrace: failure::Backtrace,
|
|
||||||
},
|
},
|
||||||
CopyFailure {
|
CopyFailure {
|
||||||
blobdir: PathBuf,
|
blobdir: PathBuf,
|
||||||
blobname: String,
|
blobname: String,
|
||||||
src: PathBuf,
|
src: PathBuf,
|
||||||
#[cause]
|
cause: failure::Error,
|
||||||
cause: std::io::Error,
|
|
||||||
backtrace: failure::Backtrace,
|
|
||||||
},
|
|
||||||
RecodeFailure {
|
|
||||||
blobdir: PathBuf,
|
|
||||||
blobname: String,
|
|
||||||
#[cause]
|
|
||||||
cause: image::ImageError,
|
|
||||||
backtrace: failure::Backtrace,
|
|
||||||
},
|
},
|
||||||
WrongBlobdir {
|
WrongBlobdir {
|
||||||
blobdir: PathBuf,
|
blobdir: PathBuf,
|
||||||
src: PathBuf,
|
src: PathBuf,
|
||||||
backtrace: failure::Backtrace,
|
|
||||||
},
|
},
|
||||||
WrongName {
|
WrongName {
|
||||||
blobname: PathBuf,
|
blobname: PathBuf,
|
||||||
backtrace: failure::Backtrace,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implementing Display is done by hand because the failure
|
impl BlobError {
|
||||||
// #[fail(display = "...")] syntax does not allow using
|
pub fn kind(&self) -> BlobErrorKind {
|
||||||
// `blobdir.display()`.
|
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 {
|
impl fmt::Display for BlobError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
// Match on the data rather than kind, they are equivalent for
|
// Match on the data rather than kind, they are equivalent for
|
||||||
// identifying purposes but contain the actual data we need.
|
// identifying purposes but contain the actual data we need.
|
||||||
match &self {
|
match &self.inner.data {
|
||||||
BlobError::CreateFailure {
|
BlobErrorData::CreateFailure {
|
||||||
blobdir, blobname, ..
|
blobdir, blobname, ..
|
||||||
} => write!(
|
} => write!(
|
||||||
f,
|
f,
|
||||||
@@ -445,7 +493,7 @@ impl fmt::Display for BlobError {
|
|||||||
blobname,
|
blobname,
|
||||||
blobdir.display()
|
blobdir.display()
|
||||||
),
|
),
|
||||||
BlobError::WriteFailure {
|
BlobErrorData::WriteFailure {
|
||||||
blobdir, blobname, ..
|
blobdir, blobname, ..
|
||||||
} => write!(
|
} => write!(
|
||||||
f,
|
f,
|
||||||
@@ -453,7 +501,7 @@ impl fmt::Display for BlobError {
|
|||||||
blobname,
|
blobname,
|
||||||
blobdir.display()
|
blobdir.display()
|
||||||
),
|
),
|
||||||
BlobError::CopyFailure {
|
BlobErrorData::CopyFailure {
|
||||||
blobdir,
|
blobdir,
|
||||||
blobname,
|
blobname,
|
||||||
src,
|
src,
|
||||||
@@ -465,22 +513,34 @@ impl fmt::Display for BlobError {
|
|||||||
blobname,
|
blobname,
|
||||||
blobdir.display(),
|
blobdir.display(),
|
||||||
),
|
),
|
||||||
BlobError::RecodeFailure {
|
BlobErrorData::WrongBlobdir { blobdir, src } => write!(
|
||||||
blobdir, blobname, ..
|
|
||||||
} => write!(f, "Failed to recode {} in {}", blobname, blobdir.display(),),
|
|
||||||
BlobError::WrongBlobdir { blobdir, src, .. } => write!(
|
|
||||||
f,
|
f,
|
||||||
"File path {} is not in blobdir {}",
|
"File path {} is not in blobdir {}",
|
||||||
src.display(),
|
src.display(),
|
||||||
blobdir.display(),
|
blobdir.display(),
|
||||||
),
|
),
|
||||||
BlobError::WrongName { blobname, .. } => {
|
BlobErrorData::WrongName { blobname } => {
|
||||||
write!(f, "Blob has a bad name: {}", blobname.display(),)
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -522,23 +582,23 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_suffix() {
|
fn test_suffix() {
|
||||||
let t = dummy_context();
|
let t = dummy_context();
|
||||||
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
|
let foo = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
|
||||||
assert_eq!(blob.suffix(), Some("txt"));
|
assert_eq!(foo.suffix(), Some("txt"));
|
||||||
let blob = BlobObject::create(&t.ctx, "bar", b"world").unwrap();
|
let bar = BlobObject::create(&t.ctx, "bar", b"world").unwrap();
|
||||||
assert_eq!(blob.suffix(), None);
|
assert_eq!(bar.suffix(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_dup() {
|
fn test_create_dup() {
|
||||||
let t = dummy_context();
|
let t = dummy_context();
|
||||||
BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
|
BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
|
||||||
let foo_path = t.ctx.get_blobdir().join("foo.txt");
|
let foo = t.ctx.get_blobdir().join("foo.txt");
|
||||||
assert!(foo_path.exists());
|
assert!(foo.exists());
|
||||||
BlobObject::create(&t.ctx, "foo.txt", b"world").unwrap();
|
BlobObject::create(&t.ctx, "foo.txt", b"world").unwrap();
|
||||||
for dirent in fs::read_dir(t.ctx.get_blobdir()).unwrap() {
|
for dirent in fs::read_dir(t.ctx.get_blobdir()).unwrap() {
|
||||||
let fname = dirent.unwrap().file_name();
|
let fname = dirent.unwrap().file_name();
|
||||||
if fname == foo_path.file_name().unwrap() {
|
if fname == foo.file_name().unwrap() {
|
||||||
assert_eq!(fs::read(&foo_path).unwrap(), b"hello");
|
assert_eq!(fs::read(&foo).unwrap(), b"hello");
|
||||||
} else {
|
} else {
|
||||||
let name = fname.to_str().unwrap();
|
let name = fname.to_str().unwrap();
|
||||||
assert!(name.starts_with("foo"));
|
assert!(name.starts_with("foo"));
|
||||||
@@ -547,26 +607,6 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_double_ext_preserved() {
|
|
||||||
let t = dummy_context();
|
|
||||||
BlobObject::create(&t.ctx, "foo.tar.gz", b"hello").unwrap();
|
|
||||||
let foo_path = t.ctx.get_blobdir().join("foo.tar.gz");
|
|
||||||
assert!(foo_path.exists());
|
|
||||||
BlobObject::create(&t.ctx, "foo.tar.gz", b"world").unwrap();
|
|
||||||
for dirent in fs::read_dir(t.ctx.get_blobdir()).unwrap() {
|
|
||||||
let fname = dirent.unwrap().file_name();
|
|
||||||
if fname == foo_path.file_name().unwrap() {
|
|
||||||
assert_eq!(fs::read(&foo_path).unwrap(), b"hello");
|
|
||||||
} else {
|
|
||||||
let name = fname.to_str().unwrap();
|
|
||||||
println!("{}", name);
|
|
||||||
assert!(name.starts_with("foo"));
|
|
||||||
assert!(name.ends_with(".tar.gz"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_long_names() {
|
fn test_create_long_names() {
|
||||||
let t = dummy_context();
|
let t = dummy_context();
|
||||||
@@ -598,14 +638,14 @@ mod tests {
|
|||||||
|
|
||||||
let src_ext = t.dir.path().join("external");
|
let src_ext = t.dir.path().join("external");
|
||||||
fs::write(&src_ext, b"boo").unwrap();
|
fs::write(&src_ext, b"boo").unwrap();
|
||||||
let blob = BlobObject::new_from_path(&t.ctx, &src_ext).unwrap();
|
let blob = BlobObject::create_from_path(&t.ctx, &src_ext).unwrap();
|
||||||
assert_eq!(blob.as_name(), "$BLOBDIR/external");
|
assert_eq!(blob.as_name(), "$BLOBDIR/external");
|
||||||
let data = fs::read(blob.to_abs_path()).unwrap();
|
let data = fs::read(blob.to_abs_path()).unwrap();
|
||||||
assert_eq!(data, b"boo");
|
assert_eq!(data, b"boo");
|
||||||
|
|
||||||
let src_int = t.ctx.get_blobdir().join("internal");
|
let src_int = t.ctx.get_blobdir().join("internal");
|
||||||
fs::write(&src_int, b"boo").unwrap();
|
fs::write(&src_int, b"boo").unwrap();
|
||||||
let blob = BlobObject::new_from_path(&t.ctx, &src_int).unwrap();
|
let blob = BlobObject::create_from_path(&t.ctx, &src_int).unwrap();
|
||||||
assert_eq!(blob.as_name(), "$BLOBDIR/internal");
|
assert_eq!(blob.as_name(), "$BLOBDIR/internal");
|
||||||
let data = fs::read(blob.to_abs_path()).unwrap();
|
let data = fs::read(blob.to_abs_path()).unwrap();
|
||||||
assert_eq!(data, b"boo");
|
assert_eq!(data, b"boo");
|
||||||
@@ -615,7 +655,7 @@ mod tests {
|
|||||||
let t = dummy_context();
|
let t = dummy_context();
|
||||||
let src_ext = t.dir.path().join("autocrypt-setup-message-4137848473.html");
|
let src_ext = t.dir.path().join("autocrypt-setup-message-4137848473.html");
|
||||||
fs::write(&src_ext, b"boo").unwrap();
|
fs::write(&src_ext, b"boo").unwrap();
|
||||||
let blob = BlobObject::new_from_path(&t.ctx, &src_ext).unwrap();
|
let blob = BlobObject::create_from_path(&t.ctx, &src_ext).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
blob.as_name(),
|
blob.as_name(),
|
||||||
"$BLOBDIR/autocrypt-setup-message-4137848473.html"
|
"$BLOBDIR/autocrypt-setup-message-4137848473.html"
|
||||||
|
|||||||
776
src/chat.rs
191
src/chatlist.rs
@@ -1,12 +1,10 @@
|
|||||||
//! # Chat list module
|
|
||||||
|
|
||||||
use crate::chat::*;
|
use crate::chat::*;
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::contact::*;
|
use crate::contact::*;
|
||||||
use crate::context::*;
|
use crate::context::*;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::lot::Lot;
|
use crate::lot::Lot;
|
||||||
use crate::message::{Message, MessageState, MsgId};
|
use crate::message::{Message, MsgId};
|
||||||
use crate::stock::StockMessage;
|
use crate::stock::StockMessage;
|
||||||
|
|
||||||
/// An object representing a single chatlist in memory.
|
/// An object representing a single chatlist in memory.
|
||||||
@@ -60,7 +58,7 @@ impl Chatlist {
|
|||||||
/// or "Not now".
|
/// or "Not now".
|
||||||
/// The UI can also offer a "Close" button that calls dc_marknoticed_contact() then.
|
/// The UI can also offer a "Close" button that calls dc_marknoticed_contact() then.
|
||||||
/// - DC_CHAT_ID_ARCHIVED_LINK (6) - this special chat is present if the user has
|
/// - DC_CHAT_ID_ARCHIVED_LINK (6) - this special chat is present if the user has
|
||||||
/// archived *any* chat using dc_archive_chat(). The UI should show a link as
|
/// archived _any_ chat using dc_archive_chat(). The UI should show a link as
|
||||||
/// "Show archived chats", if the user clicks this item, the UI should show a
|
/// "Show archived chats", if the user clicks this item, the UI should show a
|
||||||
/// list of all archived chats that can be created by this function hen using
|
/// list of all archived chats that can be created by this function hen using
|
||||||
/// the DC_GCL_ARCHIVED_ONLY flag.
|
/// the DC_GCL_ARCHIVED_ONLY flag.
|
||||||
@@ -71,7 +69,7 @@ impl Chatlist {
|
|||||||
/// The `listflags` is a combination of flags:
|
/// The `listflags` is a combination of flags:
|
||||||
/// - if the flag DC_GCL_ARCHIVED_ONLY is set, only archived chats are returned.
|
/// - if the flag DC_GCL_ARCHIVED_ONLY is set, only archived chats are returned.
|
||||||
/// if DC_GCL_ARCHIVED_ONLY is not set, only unarchived chats are returned and
|
/// if DC_GCL_ARCHIVED_ONLY is not set, only unarchived chats are returned and
|
||||||
/// the pseudo-chat DC_CHAT_ID_ARCHIVED_LINK is added if there are *any* archived
|
/// the pseudo-chat DC_CHAT_ID_ARCHIVED_LINK is added if there are _any_ archived
|
||||||
/// chats
|
/// chats
|
||||||
/// - if the flag DC_GCL_NO_SPECIALS is set, deaddrop and archive link are not added
|
/// - if the flag DC_GCL_NO_SPECIALS is set, deaddrop and archive link are not added
|
||||||
/// to the list (may be used eg. for selecting chats on forwarding, the flag is
|
/// to the list (may be used eg. for selecting chats on forwarding, the flag is
|
||||||
@@ -119,42 +117,46 @@ impl Chatlist {
|
|||||||
let mut ids = if let Some(query_contact_id) = query_contact_id {
|
let mut ids = if let Some(query_contact_id) = query_contact_id {
|
||||||
// show chats shared with a given contact
|
// show chats shared with a given contact
|
||||||
context.sql.query_map(
|
context.sql.query_map(
|
||||||
"SELECT c.id, m.id
|
concat!(
|
||||||
FROM chats c
|
"SELECT c.id, m.id",
|
||||||
LEFT JOIN msgs m
|
" FROM chats c",
|
||||||
ON c.id=m.chat_id
|
" LEFT JOIN msgs m",
|
||||||
AND m.timestamp=(
|
" ON c.id=m.chat_id",
|
||||||
SELECT MAX(timestamp)
|
" AND m.timestamp=(",
|
||||||
FROM msgs
|
" SELECT MAX(timestamp)",
|
||||||
WHERE chat_id=c.id
|
" FROM msgs",
|
||||||
AND (hidden=0 OR state=?))
|
" WHERE chat_id=c.id",
|
||||||
WHERE c.id>9
|
" AND hidden=0)",
|
||||||
AND c.blocked=0
|
" WHERE c.id>9",
|
||||||
AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)
|
" AND c.blocked=0",
|
||||||
GROUP BY c.id
|
" AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
|
||||||
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
" GROUP BY c.id",
|
||||||
params![MessageState::OutDraft, query_contact_id as i32],
|
" ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;"
|
||||||
|
),
|
||||||
|
params![query_contact_id as i32],
|
||||||
process_row,
|
process_row,
|
||||||
process_rows,
|
process_rows,
|
||||||
)?
|
)?
|
||||||
} else if 0 != listflags & DC_GCL_ARCHIVED_ONLY {
|
} else if 0 != listflags & DC_GCL_ARCHIVED_ONLY {
|
||||||
// show archived chats
|
// show archived chats
|
||||||
context.sql.query_map(
|
context.sql.query_map(
|
||||||
"SELECT c.id, m.id
|
concat!(
|
||||||
FROM chats c
|
"SELECT c.id, m.id",
|
||||||
LEFT JOIN msgs m
|
" FROM chats c",
|
||||||
ON c.id=m.chat_id
|
" LEFT JOIN msgs m",
|
||||||
AND m.timestamp=(
|
" ON c.id=m.chat_id",
|
||||||
SELECT MAX(timestamp)
|
" AND m.timestamp=(",
|
||||||
FROM msgs
|
" SELECT MAX(timestamp)",
|
||||||
WHERE chat_id=c.id
|
" FROM msgs",
|
||||||
AND (hidden=0 OR state=?))
|
" WHERE chat_id=c.id",
|
||||||
WHERE c.id>9
|
" AND hidden=0)",
|
||||||
AND c.blocked=0
|
" WHERE c.id>9",
|
||||||
AND c.archived=1
|
" AND c.blocked=0",
|
||||||
GROUP BY c.id
|
" AND c.archived=1",
|
||||||
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
" GROUP BY c.id",
|
||||||
params![MessageState::OutDraft],
|
" ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;"
|
||||||
|
),
|
||||||
|
params![],
|
||||||
process_row,
|
process_row,
|
||||||
process_rows,
|
process_rows,
|
||||||
)?
|
)?
|
||||||
@@ -164,42 +166,46 @@ impl Chatlist {
|
|||||||
|
|
||||||
let str_like_cmd = format!("%{}%", query);
|
let str_like_cmd = format!("%{}%", query);
|
||||||
context.sql.query_map(
|
context.sql.query_map(
|
||||||
"SELECT c.id, m.id
|
concat!(
|
||||||
FROM chats c
|
"SELECT c.id, m.id",
|
||||||
LEFT JOIN msgs m
|
" FROM chats c",
|
||||||
ON c.id=m.chat_id
|
" LEFT JOIN msgs m",
|
||||||
AND m.timestamp=(
|
" ON c.id=m.chat_id",
|
||||||
SELECT MAX(timestamp)
|
" AND m.timestamp=(",
|
||||||
FROM msgs
|
" SELECT MAX(timestamp)",
|
||||||
WHERE chat_id=c.id
|
" FROM msgs",
|
||||||
AND (hidden=0 OR state=?))
|
" WHERE chat_id=c.id",
|
||||||
WHERE c.id>9
|
" AND hidden=0)",
|
||||||
AND c.blocked=0
|
" WHERE c.id>9",
|
||||||
AND c.name LIKE ?
|
" AND c.blocked=0",
|
||||||
GROUP BY c.id
|
" AND c.name LIKE ?",
|
||||||
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
" GROUP BY c.id",
|
||||||
params![MessageState::OutDraft, str_like_cmd],
|
" ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;"
|
||||||
|
),
|
||||||
|
params![str_like_cmd],
|
||||||
process_row,
|
process_row,
|
||||||
process_rows,
|
process_rows,
|
||||||
)?
|
)?
|
||||||
} else {
|
} else {
|
||||||
// show normal chatlist
|
// show normal chatlist
|
||||||
let mut ids = context.sql.query_map(
|
let mut ids = context.sql.query_map(
|
||||||
"SELECT c.id, m.id
|
concat!(
|
||||||
FROM chats c
|
"SELECT c.id, m.id",
|
||||||
LEFT JOIN msgs m
|
" FROM chats c",
|
||||||
ON c.id=m.chat_id
|
" LEFT JOIN msgs m",
|
||||||
AND m.timestamp=(
|
" ON c.id=m.chat_id",
|
||||||
SELECT MAX(timestamp)
|
" AND m.timestamp=(",
|
||||||
FROM msgs
|
" SELECT MAX(timestamp)",
|
||||||
WHERE chat_id=c.id
|
" FROM msgs",
|
||||||
AND (hidden=0 OR state=?))
|
" WHERE chat_id=c.id",
|
||||||
WHERE c.id>9
|
" AND hidden=0)",
|
||||||
AND c.blocked=0
|
" WHERE c.id>9",
|
||||||
AND c.archived=0
|
" AND c.blocked=0",
|
||||||
GROUP BY c.id
|
" AND c.archived=0",
|
||||||
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
" GROUP BY c.id",
|
||||||
params![MessageState::OutDraft],
|
" ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;"
|
||||||
|
),
|
||||||
|
params![],
|
||||||
process_row,
|
process_row,
|
||||||
process_rows,
|
process_rows,
|
||||||
)?;
|
)?;
|
||||||
@@ -227,7 +233,6 @@ impl Chatlist {
|
|||||||
self.ids.len()
|
self.ids.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if chatlist is empty.
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.ids.is_empty()
|
self.ids.is_empty()
|
||||||
}
|
}
|
||||||
@@ -289,7 +294,7 @@ impl Chatlist {
|
|||||||
let lastmsg_id = self.ids[index].1;
|
let lastmsg_id = self.ids[index].1;
|
||||||
let mut lastcontact = None;
|
let mut lastcontact = None;
|
||||||
|
|
||||||
let lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
|
let mut lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
|
||||||
if lastmsg.from_id != DC_CONTACT_ID_SELF
|
if lastmsg.from_id != DC_CONTACT_ID_SELF
|
||||||
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
|
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
|
||||||
{
|
{
|
||||||
@@ -301,6 +306,16 @@ impl Chatlist {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Ok(draft) = get_draft(context, chat.id) {
|
||||||
|
if draft.is_some()
|
||||||
|
&& (lastmsg.is_none()
|
||||||
|
|| draft.as_ref().unwrap().timestamp_sort
|
||||||
|
> lastmsg.as_ref().unwrap().timestamp_sort)
|
||||||
|
{
|
||||||
|
lastmsg = draft;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if chat.id == DC_CHAT_ID_ARCHIVED_LINK {
|
if chat.id == DC_CHAT_ID_ARCHIVED_LINK {
|
||||||
ret.text2 = None;
|
ret.text2 = None;
|
||||||
} else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED
|
} else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED
|
||||||
@@ -314,7 +329,6 @@ impl Chatlist {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of archived chats
|
|
||||||
pub fn dc_get_archived_cnt(context: &Context) -> u32 {
|
pub fn dc_get_archived_cnt(context: &Context) -> u32 {
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
@@ -344,44 +358,3 @@ fn get_last_deaddrop_fresh_msg(context: &Context) -> Option<MsgId> {
|
|||||||
params![],
|
params![],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use crate::chat;
|
|
||||||
use crate::test_utils::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_try_load() {
|
|
||||||
let t = dummy_context();
|
|
||||||
let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat").unwrap();
|
|
||||||
let chat_id2 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "b chat").unwrap();
|
|
||||||
let chat_id3 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "c chat").unwrap();
|
|
||||||
|
|
||||||
// check that the chatlist starts with the most recent message
|
|
||||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
|
|
||||||
assert_eq!(chats.len(), 3);
|
|
||||||
assert_eq!(chats.get_chat_id(0), chat_id3);
|
|
||||||
assert_eq!(chats.get_chat_id(1), chat_id2);
|
|
||||||
assert_eq!(chats.get_chat_id(2), chat_id1);
|
|
||||||
|
|
||||||
// drafts are sorted to the top
|
|
||||||
let mut msg = Message::new(Viewtype::Text);
|
|
||||||
msg.set_text(Some("hello".to_string()));
|
|
||||||
set_draft(&t.ctx, chat_id2, Some(&mut msg));
|
|
||||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
|
|
||||||
assert_eq!(chats.get_chat_id(0), chat_id2);
|
|
||||||
|
|
||||||
// check chatlist query and archive functionality
|
|
||||||
let chats = Chatlist::try_load(&t.ctx, 0, Some("b"), None).unwrap();
|
|
||||||
assert_eq!(chats.len(), 1);
|
|
||||||
|
|
||||||
let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap();
|
|
||||||
assert_eq!(chats.len(), 0);
|
|
||||||
|
|
||||||
chat::archive(&t.ctx, chat_id1, true).ok();
|
|
||||||
let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap();
|
|
||||||
assert_eq!(chats.len(), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
//! # Key-value configuration management
|
|
||||||
|
|
||||||
use strum::{EnumProperty, IntoEnumIterator};
|
use strum::{EnumProperty, IntoEnumIterator};
|
||||||
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
|
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
|
||||||
|
|
||||||
use crate::blob::BlobObject;
|
|
||||||
use crate::constants::DC_VERSION_STR;
|
use crate::constants::DC_VERSION_STR;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::dc_tools::*;
|
use crate::dc_tools::*;
|
||||||
|
use crate::error::Error;
|
||||||
use crate::job::*;
|
use crate::job::*;
|
||||||
use crate::stock::StockMessage;
|
use crate::stock::StockMessage;
|
||||||
use rusqlite::NO_PARAMS;
|
|
||||||
|
|
||||||
/// The available configuration keys.
|
/// The available configuration keys.
|
||||||
#[derive(
|
#[derive(
|
||||||
@@ -29,38 +26,27 @@ pub enum Config {
|
|||||||
SendPort,
|
SendPort,
|
||||||
SmtpCertificateChecks,
|
SmtpCertificateChecks,
|
||||||
ServerFlags,
|
ServerFlags,
|
||||||
|
|
||||||
#[strum(props(default = "INBOX"))]
|
#[strum(props(default = "INBOX"))]
|
||||||
ImapFolder,
|
ImapFolder,
|
||||||
|
|
||||||
Displayname,
|
Displayname,
|
||||||
Selfstatus,
|
Selfstatus,
|
||||||
Selfavatar,
|
Selfavatar,
|
||||||
|
#[strum(props(default = "1"))]
|
||||||
#[strum(props(default = "0"))]
|
|
||||||
BccSelf,
|
BccSelf,
|
||||||
|
|
||||||
#[strum(props(default = "1"))]
|
#[strum(props(default = "1"))]
|
||||||
E2eeEnabled,
|
E2eeEnabled,
|
||||||
|
|
||||||
#[strum(props(default = "1"))]
|
#[strum(props(default = "1"))]
|
||||||
MdnsEnabled,
|
MdnsEnabled,
|
||||||
|
|
||||||
#[strum(props(default = "1"))]
|
#[strum(props(default = "1"))]
|
||||||
InboxWatch,
|
InboxWatch,
|
||||||
|
|
||||||
#[strum(props(default = "1"))]
|
#[strum(props(default = "1"))]
|
||||||
SentboxWatch,
|
SentboxWatch,
|
||||||
|
|
||||||
#[strum(props(default = "1"))]
|
#[strum(props(default = "1"))]
|
||||||
MvboxWatch,
|
MvboxWatch,
|
||||||
|
|
||||||
#[strum(props(default = "1"))]
|
#[strum(props(default = "1"))]
|
||||||
MvboxMove,
|
MvboxMove,
|
||||||
|
|
||||||
#[strum(props(default = "0"))] // also change ShowEmails.default() on changes
|
#[strum(props(default = "0"))] // also change ShowEmails.default() on changes
|
||||||
ShowEmails,
|
ShowEmails,
|
||||||
|
|
||||||
SaveMimeHeaders,
|
SaveMimeHeaders,
|
||||||
ConfiguredAddr,
|
ConfiguredAddr,
|
||||||
ConfiguredMailServer,
|
ConfiguredMailServer,
|
||||||
@@ -78,13 +64,11 @@ pub enum Config {
|
|||||||
ConfiguredSendSecurity,
|
ConfiguredSendSecurity,
|
||||||
ConfiguredE2EEEnabled,
|
ConfiguredE2EEEnabled,
|
||||||
Configured,
|
Configured,
|
||||||
|
// Deprecated
|
||||||
#[strum(serialize = "sys.version")]
|
#[strum(serialize = "sys.version")]
|
||||||
SysVersion,
|
SysVersion,
|
||||||
|
|
||||||
#[strum(serialize = "sys.msgsize_max_recommended")]
|
#[strum(serialize = "sys.msgsize_max_recommended")]
|
||||||
SysMsgsizeMaxRecommended,
|
SysMsgsizeMaxRecommended,
|
||||||
|
|
||||||
#[strum(serialize = "sys.config_keys")]
|
#[strum(serialize = "sys.config_keys")]
|
||||||
SysConfigKeys,
|
SysConfigKeys,
|
||||||
}
|
}
|
||||||
@@ -126,25 +110,16 @@ impl Context {
|
|||||||
|
|
||||||
/// Set the given config key.
|
/// 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.
|
/// 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>) -> crate::sql::Result<()> {
|
pub fn set_config(&self, key: Config, value: Option<&str>) -> Result<(), Error> {
|
||||||
match key {
|
match key {
|
||||||
Config::Selfavatar => {
|
Config::Selfavatar if value.is_some() => {
|
||||||
|
let rel_path = std::fs::canonicalize(value.unwrap())?;
|
||||||
self.sql
|
self.sql
|
||||||
.execute("UPDATE contacts SET selfavatar_sent=0;", NO_PARAMS)?;
|
.set_raw_config(self, key, Some(&rel_path.to_string_lossy()))
|
||||||
self.sql
|
|
||||||
.set_raw_config_bool(self, "attach_selfavatar", true)?;
|
|
||||||
match value {
|
|
||||||
Some(value) => {
|
|
||||||
let blob = BlobObject::new_from_path(&self, value)?;
|
|
||||||
blob.recode_to_avatar_size(self)?;
|
|
||||||
self.sql.set_raw_config(self, key, Some(blob.as_name()))
|
|
||||||
}
|
|
||||||
None => self.sql.set_raw_config(self, key, None),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Config::InboxWatch => {
|
Config::InboxWatch => {
|
||||||
let ret = self.sql.set_raw_config(self, key, value);
|
let ret = self.sql.set_raw_config(self, key, value);
|
||||||
interrupt_inbox_idle(self, true);
|
interrupt_imap_idle(self);
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
Config::SentboxWatch => {
|
Config::SentboxWatch => {
|
||||||
@@ -190,10 +165,6 @@ mod tests {
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::string::ToString;
|
use std::string::ToString;
|
||||||
|
|
||||||
use crate::test_utils::*;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_to_string() {
|
fn test_to_string() {
|
||||||
assert_eq!(Config::MailServer.to_string(), "mail_server");
|
assert_eq!(Config::MailServer.to_string(), "mail_server");
|
||||||
@@ -210,34 +181,4 @@ mod tests {
|
|||||||
fn test_default_prop() {
|
fn test_default_prop() {
|
||||||
assert_eq!(Config::ImapFolder.get_str("default"), Some("INBOX"));
|
assert_eq!(Config::ImapFolder.get_str("default"), Some("INBOX"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_selfavatar_outside_blobdir() -> failure::Fallible<()> {
|
|
||||||
let t = dummy_context();
|
|
||||||
let avatar_src = t.dir.path().join("avatar.jpg");
|
|
||||||
let avatar_bytes = include_bytes!("../test-data/image/avatar1000x1000.jpg");
|
|
||||||
File::create(&avatar_src)?.write_all(avatar_bytes)?;
|
|
||||||
let avatar_blob = t.ctx.get_blobdir().join("avatar.jpg");
|
|
||||||
assert!(!avatar_blob.exists());
|
|
||||||
t.ctx
|
|
||||||
.set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap()))?;
|
|
||||||
assert!(avatar_blob.exists());
|
|
||||||
assert!(std::fs::metadata(&avatar_blob).unwrap().len() < avatar_bytes.len() as u64);
|
|
||||||
let avatar_cfg = t.ctx.get_config(Config::Selfavatar);
|
|
||||||
assert_eq!(avatar_cfg, avatar_blob.to_str().map(|s| s.to_string()));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_selfavatar_in_blobdir() -> failure::Fallible<()> {
|
|
||||||
let t = dummy_context();
|
|
||||||
let avatar_src = t.ctx.get_blobdir().join("avatar.jpg");
|
|
||||||
let avatar_bytes = include_bytes!("../test-data/image/avatar1000x1000.jpg");
|
|
||||||
File::create(&avatar_src)?.write_all(avatar_bytes)?;
|
|
||||||
t.ctx
|
|
||||||
.set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap()))?;
|
|
||||||
let avatar_cfg = t.ctx.get_config(Config::Selfavatar);
|
|
||||||
assert_eq!(avatar_cfg, avatar_src.to_str().map(|s| s.to_string()));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,16 @@
|
|||||||
//! # Thunderbird's Autoconfiguration implementation
|
|
||||||
//!
|
|
||||||
//! Documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
|
|
||||||
use quick_xml;
|
use quick_xml;
|
||||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||||
|
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
|
use crate::error::Error;
|
||||||
use crate::login_param::LoginParam;
|
use crate::login_param::LoginParam;
|
||||||
|
|
||||||
use super::read_url::read_url;
|
use super::read_autoconf_file;
|
||||||
|
/* ******************************************************************************
|
||||||
#[derive(Debug, Fail)]
|
* Thunderbird's Autoconfigure
|
||||||
pub enum Error {
|
******************************************************************************/
|
||||||
#[fail(display = "Invalid email address: {:?}", _0)]
|
/* documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
|
||||||
InvalidEmailAddress(String),
|
|
||||||
|
|
||||||
#[fail(display = "XML error at position {}", position)]
|
|
||||||
InvalidXml {
|
|
||||||
position: usize,
|
|
||||||
#[cause]
|
|
||||||
error: quick_xml::Error,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[fail(display = "Bad or incomplete autoconfig")]
|
|
||||||
IncompleteAutoconfig(LoginParam),
|
|
||||||
|
|
||||||
#[fail(display = "Failed to get URL {}", _0)]
|
|
||||||
ReadUrlError(#[cause] super::read_url::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
impl From<super::read_url::Error> for Error {
|
|
||||||
fn from(err: super::read_url::Error) -> Error {
|
|
||||||
Error::ReadUrlError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct MozAutoconfigure<'a> {
|
struct MozAutoconfigure<'a> {
|
||||||
pub in_emailaddr: &'a str,
|
pub in_emailaddr: &'a str,
|
||||||
pub in_emaildomain: &'a str,
|
pub in_emaildomain: &'a str,
|
||||||
@@ -49,14 +22,13 @@ struct MozAutoconfigure<'a> {
|
|||||||
pub tag_config: MozConfigTag,
|
pub tag_config: MozConfigTag,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(PartialEq)]
|
||||||
enum MozServer {
|
enum MozServer {
|
||||||
Undefined,
|
Undefined,
|
||||||
Imap,
|
Imap,
|
||||||
Smtp,
|
Smtp,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum MozConfigTag {
|
enum MozConfigTag {
|
||||||
Undefined,
|
Undefined,
|
||||||
Hostname,
|
Hostname,
|
||||||
@@ -65,14 +37,15 @@ enum MozConfigTag {
|
|||||||
Username,
|
Username,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam> {
|
pub fn moz_parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Error> {
|
||||||
let mut reader = quick_xml::Reader::from_str(xml_raw);
|
let mut reader = quick_xml::Reader::from_str(xml_raw);
|
||||||
reader.trim_text(true);
|
reader.trim_text(true);
|
||||||
|
|
||||||
// Split address into local part and domain part.
|
// Split address into local part and domain part.
|
||||||
let p = in_emailaddr
|
let p = match in_emailaddr.find('@') {
|
||||||
.find('@')
|
Some(i) => i,
|
||||||
.ok_or_else(|| Error::InvalidEmailAddress(in_emailaddr.to_string()))?;
|
None => bail!("Email address {} does not contain @", in_emailaddr),
|
||||||
|
};
|
||||||
let (in_emaillocalpart, in_emaildomain) = in_emailaddr.split_at(p);
|
let (in_emaillocalpart, in_emaildomain) = in_emailaddr.split_at(p);
|
||||||
let in_emaildomain = &in_emaildomain[1..];
|
let in_emaildomain = &in_emaildomain[1..];
|
||||||
|
|
||||||
@@ -89,22 +62,22 @@ fn parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam> {
|
|||||||
|
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
loop {
|
loop {
|
||||||
let event = reader
|
match reader.read_event(&mut buf) {
|
||||||
.read_event(&mut buf)
|
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||||
.map_err(|error| Error::InvalidXml {
|
|
||||||
position: reader.buffer_position(),
|
|
||||||
error,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
match event {
|
|
||||||
quick_xml::events::Event::Start(ref e) => {
|
|
||||||
moz_autoconfigure_starttag_cb(e, &mut moz_ac, &reader)
|
moz_autoconfigure_starttag_cb(e, &mut moz_ac, &reader)
|
||||||
}
|
}
|
||||||
quick_xml::events::Event::End(ref e) => moz_autoconfigure_endtag_cb(e, &mut moz_ac),
|
Ok(quick_xml::events::Event::End(ref e)) => moz_autoconfigure_endtag_cb(e, &mut moz_ac),
|
||||||
quick_xml::events::Event::Text(ref e) => {
|
Ok(quick_xml::events::Event::Text(ref e)) => {
|
||||||
moz_autoconfigure_text_cb(e, &mut moz_ac, &reader)
|
moz_autoconfigure_text_cb(e, &mut moz_ac, &reader)
|
||||||
}
|
}
|
||||||
quick_xml::events::Event::Eof => break,
|
Err(e) => {
|
||||||
|
bail!(
|
||||||
|
"Configure xml: Error at position {}: {:?}",
|
||||||
|
reader.buffer_position(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(quick_xml::events::Event::Eof) => break,
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
buf.clear();
|
buf.clear();
|
||||||
@@ -115,27 +88,27 @@ fn parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam> {
|
|||||||
|| moz_ac.out.send_server.is_empty()
|
|| moz_ac.out.send_server.is_empty()
|
||||||
|| moz_ac.out.send_port == 0
|
|| moz_ac.out.send_port == 0
|
||||||
{
|
{
|
||||||
Err(Error::IncompleteAutoconfig(moz_ac.out))
|
let r = moz_ac.out.to_string();
|
||||||
} else {
|
bail!("Bad or incomplete autoconfig: {}", r,);
|
||||||
Ok(moz_ac.out)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(moz_ac.out)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn moz_autoconfigure(
|
pub fn moz_autoconfigure(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
url: &str,
|
url: &str,
|
||||||
param_in: &LoginParam,
|
param_in: &LoginParam,
|
||||||
) -> Result<LoginParam> {
|
) -> Option<LoginParam> {
|
||||||
let xml_raw = read_url(context, url)?;
|
let xml_raw = read_autoconf_file(context, url)?;
|
||||||
|
|
||||||
let res = parse_xml(¶m_in.addr, &xml_raw);
|
match moz_parse_xml(¶m_in.addr, &xml_raw) {
|
||||||
if let Err(err) = &res {
|
Err(err) => {
|
||||||
warn!(
|
warn!(context, "{}", err);
|
||||||
context,
|
None
|
||||||
"Failed to parse Thunderbird autoconfiguration XML: {}", err
|
}
|
||||||
);
|
Ok(lp) => Some(lp),
|
||||||
}
|
}
|
||||||
res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
||||||
@@ -340,7 +313,7 @@ mod tests {
|
|||||||
</loginPageInfo>
|
</loginPageInfo>
|
||||||
</webMail>
|
</webMail>
|
||||||
</clientConfig>";
|
</clientConfig>";
|
||||||
let res = parse_xml("example@outlook.com", xml_raw).expect("XML parsing failed");
|
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_server, "outlook.office365.com");
|
||||||
assert_eq!(res.mail_port, 993);
|
assert_eq!(res.mail_port, 993);
|
||||||
assert_eq!(res.send_server, "smtp.office365.com");
|
assert_eq!(res.send_server, "smtp.office365.com");
|
||||||
|
|||||||
@@ -1,41 +1,14 @@
|
|||||||
//! Outlook's Autodiscover
|
|
||||||
|
|
||||||
use quick_xml;
|
use quick_xml;
|
||||||
use quick_xml::events::BytesEnd;
|
use quick_xml::events::BytesEnd;
|
||||||
|
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
|
use crate::error::Error;
|
||||||
use crate::login_param::LoginParam;
|
use crate::login_param::LoginParam;
|
||||||
|
|
||||||
use super::read_url::read_url;
|
use super::read_autoconf_file;
|
||||||
|
|
||||||
#[derive(Debug, Fail)]
|
|
||||||
pub enum Error {
|
|
||||||
#[fail(display = "XML error at position {}", position)]
|
|
||||||
InvalidXml {
|
|
||||||
position: usize,
|
|
||||||
#[cause]
|
|
||||||
error: quick_xml::Error,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[fail(display = "Bad or incomplete autoconfig")]
|
|
||||||
IncompleteAutoconfig(LoginParam),
|
|
||||||
|
|
||||||
#[fail(display = "Failed to get URL {}", _0)]
|
|
||||||
ReadUrlError(#[cause] super::read_url::Error),
|
|
||||||
|
|
||||||
#[fail(display = "Number of redirection is exceeded")]
|
|
||||||
RedirectionError,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
impl From<super::read_url::Error> for Error {
|
|
||||||
fn from(err: super::read_url::Error) -> Error {
|
|
||||||
Error::ReadUrlError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// Outlook's Autodiscover
|
||||||
struct OutlookAutodiscover {
|
struct OutlookAutodiscover {
|
||||||
pub out: LoginParam,
|
pub out: LoginParam,
|
||||||
pub out_imap_set: bool,
|
pub out_imap_set: bool,
|
||||||
@@ -52,7 +25,7 @@ enum ParsingResult {
|
|||||||
RedirectUrl(String),
|
RedirectUrl(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_xml(xml_raw: &str) -> Result<ParsingResult> {
|
fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
|
||||||
let mut outlk_ad = OutlookAutodiscover {
|
let mut outlk_ad = OutlookAutodiscover {
|
||||||
out: LoginParam::new(),
|
out: LoginParam::new(),
|
||||||
out_imap_set: false,
|
out_imap_set: false,
|
||||||
@@ -72,15 +45,8 @@ fn parse_xml(xml_raw: &str) -> Result<ParsingResult> {
|
|||||||
let mut current_tag: Option<String> = None;
|
let mut current_tag: Option<String> = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let event = reader
|
match reader.read_event(&mut buf) {
|
||||||
.read_event(&mut buf)
|
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||||
.map_err(|error| Error::InvalidXml {
|
|
||||||
position: reader.buffer_position(),
|
|
||||||
error,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
match event {
|
|
||||||
quick_xml::events::Event::Start(ref e) => {
|
|
||||||
let tag = String::from_utf8_lossy(e.name()).trim().to_lowercase();
|
let tag = String::from_utf8_lossy(e.name()).trim().to_lowercase();
|
||||||
|
|
||||||
if tag == "protocol" {
|
if tag == "protocol" {
|
||||||
@@ -95,11 +61,11 @@ fn parse_xml(xml_raw: &str) -> Result<ParsingResult> {
|
|||||||
current_tag = Some(tag);
|
current_tag = Some(tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
quick_xml::events::Event::End(ref e) => {
|
Ok(quick_xml::events::Event::End(ref e)) => {
|
||||||
outlk_autodiscover_endtag_cb(e, &mut outlk_ad);
|
outlk_autodiscover_endtag_cb(e, &mut outlk_ad);
|
||||||
current_tag = None;
|
current_tag = None;
|
||||||
}
|
}
|
||||||
quick_xml::events::Event::Text(ref e) => {
|
Ok(quick_xml::events::Event::Text(ref e)) => {
|
||||||
let val = e.unescape_and_decode(&reader).unwrap_or_default();
|
let val = e.unescape_and_decode(&reader).unwrap_or_default();
|
||||||
|
|
||||||
if let Some(ref tag) = current_tag {
|
if let Some(ref tag) = current_tag {
|
||||||
@@ -115,14 +81,21 @@ fn parse_xml(xml_raw: &str) -> Result<ParsingResult> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
quick_xml::events::Event::Eof => break,
|
Err(e) => {
|
||||||
|
bail!(
|
||||||
|
"Configure xml: Error at position {}: {:?}",
|
||||||
|
reader.buffer_position(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(quick_xml::events::Event::Eof) => break,
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
buf.clear();
|
buf.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// XML redirect via redirecturl
|
// XML redirect via redirecturl
|
||||||
let res = if outlk_ad.config_redirecturl.is_none()
|
if outlk_ad.config_redirecturl.is_none()
|
||||||
|| outlk_ad.config_redirecturl.as_ref().unwrap().is_empty()
|
|| outlk_ad.config_redirecturl.as_ref().unwrap().is_empty()
|
||||||
{
|
{
|
||||||
if outlk_ad.out.mail_server.is_empty()
|
if outlk_ad.out.mail_server.is_empty()
|
||||||
@@ -130,34 +103,41 @@ fn parse_xml(xml_raw: &str) -> Result<ParsingResult> {
|
|||||||
|| outlk_ad.out.send_server.is_empty()
|
|| outlk_ad.out.send_server.is_empty()
|
||||||
|| outlk_ad.out.send_port == 0
|
|| outlk_ad.out.send_port == 0
|
||||||
{
|
{
|
||||||
return Err(Error::IncompleteAutoconfig(outlk_ad.out));
|
let r = outlk_ad.out.to_string();
|
||||||
|
bail!("Bad or incomplete autoconfig: {}", r,);
|
||||||
}
|
}
|
||||||
ParsingResult::LoginParam(outlk_ad.out)
|
Ok(ParsingResult::LoginParam(outlk_ad.out))
|
||||||
} else {
|
} else {
|
||||||
ParsingResult::RedirectUrl(outlk_ad.config_redirecturl.unwrap())
|
Ok(ParsingResult::RedirectUrl(
|
||||||
};
|
outlk_ad.config_redirecturl.unwrap(),
|
||||||
Ok(res)
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn outlk_autodiscover(
|
pub fn outlk_autodiscover(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
url: &str,
|
url: &str,
|
||||||
_param_in: &LoginParam,
|
_param_in: &LoginParam,
|
||||||
) -> Result<LoginParam> {
|
) -> Option<LoginParam> {
|
||||||
let mut url = url.to_string();
|
let mut url = url.to_string();
|
||||||
/* Follow up to 10 xml-redirects (http-redirects are followed in read_url() */
|
/* Follow up to 10 xml-redirects (http-redirects are followed in read_autoconf_file() */
|
||||||
for _i in 0..10 {
|
for _i in 0..10 {
|
||||||
let xml_raw = read_url(context, &url)?;
|
if let Some(xml_raw) = read_autoconf_file(context, &url) {
|
||||||
let res = parse_xml(&xml_raw);
|
match outlk_parse_xml(&xml_raw) {
|
||||||
if let Err(err) = &res {
|
Err(err) => {
|
||||||
warn!(context, "{}", err);
|
warn!(context, "{}", err);
|
||||||
}
|
return None;
|
||||||
match res? {
|
}
|
||||||
ParsingResult::RedirectUrl(redirect_url) => url = redirect_url,
|
Ok(res) => match res {
|
||||||
ParsingResult::LoginParam(login_param) => return Ok(login_param),
|
ParsingResult::RedirectUrl(redirect_url) => url = redirect_url,
|
||||||
|
ParsingResult::LoginParam(login_param) => return Some(login_param),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(Error::RedirectionError)
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut OutlookAutodiscover) {
|
fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut OutlookAutodiscover) {
|
||||||
@@ -199,7 +179,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_redirect() {
|
fn test_parse_redirect() {
|
||||||
let res = parse_xml("
|
let res = outlk_parse_xml("
|
||||||
<?xml version=\"1.0\" encoding=\"utf-8\"?>
|
<?xml version=\"1.0\" encoding=\"utf-8\"?>
|
||||||
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
|
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
|
||||||
<Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">
|
<Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">
|
||||||
@@ -226,7 +206,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_loginparam() {
|
fn test_parse_loginparam() {
|
||||||
let res = parse_xml(
|
let res = outlk_parse_xml(
|
||||||
"\
|
"\
|
||||||
<?xml version=\"1.0\" encoding=\"utf-8\"?>
|
<?xml version=\"1.0\" encoding=\"utf-8\"?>
|
||||||
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
|
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
|
||||||
|
|||||||
@@ -1,25 +1,20 @@
|
|||||||
//! Email accounts autoconfiguration process module
|
|
||||||
|
|
||||||
mod auto_mozilla;
|
|
||||||
mod auto_outlook;
|
|
||||||
mod read_url;
|
|
||||||
|
|
||||||
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
||||||
|
|
||||||
use async_std::task;
|
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::dc_tools::*;
|
use crate::dc_tools::*;
|
||||||
use crate::e2ee;
|
use crate::e2ee;
|
||||||
|
use crate::imap::*;
|
||||||
use crate::job::*;
|
use crate::job::*;
|
||||||
use crate::login_param::{CertificateChecks, LoginParam};
|
use crate::login_param::LoginParam;
|
||||||
use crate::oauth2::*;
|
use crate::oauth2::*;
|
||||||
use crate::param::Params;
|
use crate::param::Params;
|
||||||
|
|
||||||
use auto_mozilla::moz_autoconfigure;
|
mod auto_outlook;
|
||||||
use auto_outlook::outlk_autodiscover;
|
use auto_outlook::outlk_autodiscover;
|
||||||
|
mod auto_mozilla;
|
||||||
|
use auto_mozilla::moz_autoconfigure;
|
||||||
|
|
||||||
macro_rules! progress {
|
macro_rules! progress {
|
||||||
($context:tt, $progress:expr) => {
|
($context:tt, $progress:expr) => {
|
||||||
@@ -49,8 +44,9 @@ pub fn dc_is_configured(context: &Context) -> bool {
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Configure JOB
|
* Configure JOB
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
// the other dc_job_do_DC_JOB_*() functions are declared static in the c-file
|
||||||
#[allow(non_snake_case, unused_must_use)]
|
#[allow(non_snake_case, unused_must_use)]
|
||||||
pub fn JobConfigureImap(context: &Context) {
|
pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||||
if !context.sql.is_open() {
|
if !context.sql.is_open() {
|
||||||
error!(context, "Cannot configure, database not opened.",);
|
error!(context, "Cannot configure, database not opened.",);
|
||||||
progress!(context, 0);
|
progress!(context, 0);
|
||||||
@@ -66,12 +62,7 @@ pub fn JobConfigureImap(context: &Context) {
|
|||||||
|
|
||||||
let mut param_autoconfig: Option<LoginParam> = None;
|
let mut param_autoconfig: Option<LoginParam> = None;
|
||||||
|
|
||||||
context
|
context.inbox.read().unwrap().disconnect(context);
|
||||||
.inbox_thread
|
|
||||||
.read()
|
|
||||||
.unwrap()
|
|
||||||
.imap
|
|
||||||
.disconnect(context);
|
|
||||||
context
|
context
|
||||||
.sentbox_thread
|
.sentbox_thread
|
||||||
.read()
|
.read()
|
||||||
@@ -94,14 +85,12 @@ pub fn JobConfigureImap(context: &Context) {
|
|||||||
let mut param_domain = "undefined.undefined".to_owned();
|
let mut param_domain = "undefined.undefined".to_owned();
|
||||||
let mut param_addr_urlencoded: String =
|
let mut param_addr_urlencoded: String =
|
||||||
"Internal Error: this value should never be used".to_owned();
|
"Internal Error: this value should never be used".to_owned();
|
||||||
let mut keep_flags = 0;
|
let mut keep_flags = std::i32::MAX;
|
||||||
|
|
||||||
const STEP_12_USE_AUTOCONFIG: u8 = 12;
|
|
||||||
const STEP_13_AFTER_AUTOCONFIG: u8 = 13;
|
|
||||||
|
|
||||||
|
const STEP_3_INDEX: u8 = 13;
|
||||||
let mut step_counter: u8 = 0;
|
let mut step_counter: u8 = 0;
|
||||||
while !context.shall_stop_ongoing() {
|
while !context.shall_stop_ongoing() {
|
||||||
step_counter += 1;
|
step_counter = step_counter + 1;
|
||||||
|
|
||||||
let success = match step_counter {
|
let success = match step_counter {
|
||||||
// Read login parameters from the database
|
// Read login parameters from the database
|
||||||
@@ -114,7 +103,7 @@ pub fn JobConfigureImap(context: &Context) {
|
|||||||
}
|
}
|
||||||
// Step 1: Load the parameters and check email-address and password
|
// Step 1: Load the parameters and check email-address and password
|
||||||
2 => {
|
2 => {
|
||||||
if 0 != param.server_flags & DC_LP_AUTH_OAUTH2 {
|
if 0 != param.server_flags & 0x2 {
|
||||||
// the used oauth2 addr may differ, check this.
|
// the used oauth2 addr may differ, check this.
|
||||||
// if dc_get_oauth2_addr() is not available in the oauth2 implementation,
|
// if dc_get_oauth2_addr() is not available in the oauth2 implementation,
|
||||||
// just use the given one.
|
// just use the given one.
|
||||||
@@ -149,7 +138,6 @@ pub fn JobConfigureImap(context: &Context) {
|
|||||||
// Step 2: Autoconfig
|
// Step 2: Autoconfig
|
||||||
4 => {
|
4 => {
|
||||||
progress!(context, 200);
|
progress!(context, 200);
|
||||||
|
|
||||||
if param.mail_server.is_empty()
|
if param.mail_server.is_empty()
|
||||||
&& param.mail_port == 0
|
&& param.mail_port == 0
|
||||||
/*&¶m.mail_user.is_empty() -- the user can enter a loginname which is used by autoconfig then */
|
/*&¶m.mail_user.is_empty() -- the user can enter a loginname which is used by autoconfig then */
|
||||||
@@ -157,18 +145,12 @@ pub fn JobConfigureImap(context: &Context) {
|
|||||||
&& param.send_port == 0
|
&& param.send_port == 0
|
||||||
&& param.send_user.is_empty()
|
&& param.send_user.is_empty()
|
||||||
/*&¶m.send_pw.is_empty() -- the password cannot be auto-configured and is no criterion for autoconfig or not */
|
/*&¶m.send_pw.is_empty() -- the password cannot be auto-configured and is no criterion for autoconfig or not */
|
||||||
&& (param.server_flags & !DC_LP_AUTH_OAUTH2) == 0
|
&& param.server_flags & !0x2 == 0
|
||||||
{
|
{
|
||||||
// no advanced parameters entered by the user: query provider-database or do Autoconfig
|
keep_flags = param.server_flags & 0x2;
|
||||||
keep_flags = param.server_flags & DC_LP_AUTH_OAUTH2;
|
|
||||||
if let Some(new_param) = get_offline_autoconfig(context, ¶m) {
|
|
||||||
// got parameters from our provider-database, skip Autoconfig, preserve the OAuth2 setting
|
|
||||||
param_autoconfig = Some(new_param);
|
|
||||||
step_counter = STEP_12_USE_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// advanced parameters entered by the user: skip Autoconfig
|
// Autoconfig is not needed so skip it.
|
||||||
step_counter = STEP_13_AFTER_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop
|
step_counter = STEP_3_INDEX - 1;
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -179,7 +161,7 @@ pub fn JobConfigureImap(context: &Context) {
|
|||||||
"https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
|
"https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
|
||||||
param_domain, param_addr_urlencoded
|
param_domain, param_addr_urlencoded
|
||||||
);
|
);
|
||||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok();
|
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -191,7 +173,7 @@ pub fn JobConfigureImap(context: &Context) {
|
|||||||
"https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}",
|
"https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}",
|
||||||
param_domain, param_addr_urlencoded
|
param_domain, param_addr_urlencoded
|
||||||
);
|
);
|
||||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok();
|
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -204,7 +186,7 @@ pub fn JobConfigureImap(context: &Context) {
|
|||||||
"https://{}{}/autodiscover/autodiscover.xml",
|
"https://{}{}/autodiscover/autodiscover.xml",
|
||||||
"", param_domain
|
"", param_domain
|
||||||
);
|
);
|
||||||
param_autoconfig = outlk_autodiscover(context, &url, ¶m).ok();
|
param_autoconfig = outlk_autodiscover(context, &url, ¶m);
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -215,7 +197,7 @@ pub fn JobConfigureImap(context: &Context) {
|
|||||||
"https://{}{}/autodiscover/autodiscover.xml",
|
"https://{}{}/autodiscover/autodiscover.xml",
|
||||||
"autodiscover.", param_domain
|
"autodiscover.", param_domain
|
||||||
);
|
);
|
||||||
param_autoconfig = outlk_autodiscover(context, &url, ¶m).ok();
|
param_autoconfig = outlk_autodiscover(context, &url, ¶m);
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -227,7 +209,7 @@ pub fn JobConfigureImap(context: &Context) {
|
|||||||
"http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
|
"http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
|
||||||
param_domain, param_addr_urlencoded
|
param_domain, param_addr_urlencoded
|
||||||
);
|
);
|
||||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok();
|
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -239,7 +221,7 @@ pub fn JobConfigureImap(context: &Context) {
|
|||||||
"http://{}/.well-known/autoconfig/mail/config-v1.1.xml",
|
"http://{}/.well-known/autoconfig/mail/config-v1.1.xml",
|
||||||
param_domain
|
param_domain
|
||||||
);
|
);
|
||||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok();
|
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -249,14 +231,12 @@ pub fn JobConfigureImap(context: &Context) {
|
|||||||
if param_autoconfig.is_none() {
|
if param_autoconfig.is_none() {
|
||||||
/* always SSL for Thunderbird's database */
|
/* always SSL for Thunderbird's database */
|
||||||
let url = format!("https://autoconfig.thunderbird.net/v1.1/{}", param_domain);
|
let url = format!("https://autoconfig.thunderbird.net/v1.1/{}", param_domain);
|
||||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok();
|
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
/* C. Do we have any autoconfig result?
|
/* C. Do we have any result? */
|
||||||
If you change the match-number here, also update STEP_12_COPY_AUTOCONFIG above
|
12 => {
|
||||||
*/
|
|
||||||
STEP_12_USE_AUTOCONFIG => {
|
|
||||||
progress!(context, 500);
|
progress!(context, 500);
|
||||||
if let Some(ref cfg) = param_autoconfig {
|
if let Some(ref cfg) = param_autoconfig {
|
||||||
info!(context, "Got autoconfig: {}", &cfg);
|
info!(context, "Got autoconfig: {}", &cfg);
|
||||||
@@ -269,15 +249,15 @@ pub fn JobConfigureImap(context: &Context) {
|
|||||||
param.send_port = cfg.send_port;
|
param.send_port = cfg.send_port;
|
||||||
param.send_user = cfg.send_user.clone();
|
param.send_user = cfg.send_user.clone();
|
||||||
param.server_flags = cfg.server_flags;
|
param.server_flags = cfg.server_flags;
|
||||||
/* although param_autoconfig's data are no longer needed from,
|
/* although param_autoconfig's data are no longer needed from, it is important to keep the object as
|
||||||
it is used to later to prevent trying variations of port/server/logins */
|
we may enter "deep guessing" if we could not read a configuration */
|
||||||
}
|
}
|
||||||
param.server_flags |= keep_flags;
|
param.server_flags |= keep_flags;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
// Step 3: Fill missing fields with defaults
|
// Step 3: Fill missing fields with defaults
|
||||||
// If you change the match-number here, also update STEP_13_AFTER_AUTOCONFIG above
|
13 => {
|
||||||
STEP_13_AFTER_AUTOCONFIG => {
|
// if you move this, don't forget to update STEP_3_INDEX, too
|
||||||
if param.mail_server.is_empty() {
|
if param.mail_server.is_empty() {
|
||||||
param.mail_server = format!("imap.{}", param_domain,)
|
param.mail_server = format!("imap.{}", param_domain,)
|
||||||
}
|
}
|
||||||
@@ -368,21 +348,19 @@ pub fn JobConfigureImap(context: &Context) {
|
|||||||
}
|
}
|
||||||
16 => {
|
16 => {
|
||||||
progress!(context, 900);
|
progress!(context, 900);
|
||||||
let create_mvbox = context.get_config_bool(Config::MvboxWatch)
|
let flags: libc::c_int = if context.get_config_bool(Config::MvboxWatch)
|
||||||
|| context.get_config_bool(Config::MvboxMove);
|
|| context.get_config_bool(Config::MvboxMove)
|
||||||
let imap = &context.inbox_thread.read().unwrap().imap;
|
{
|
||||||
if let Err(err) = imap.ensure_configured_folders(context, create_mvbox) {
|
DC_CREATE_MVBOX as i32
|
||||||
warn!(context, "configuring folders failed: {:?}", err);
|
|
||||||
false
|
|
||||||
} else {
|
} else {
|
||||||
let res = imap.select_with_uidvalidity(context, "INBOX");
|
0
|
||||||
if let Err(err) = res {
|
};
|
||||||
error!(context, "could not read INBOX status: {:?}", err);
|
context
|
||||||
false
|
.inbox
|
||||||
} else {
|
.read()
|
||||||
true
|
.unwrap()
|
||||||
}
|
.configure_folders(context, flags);
|
||||||
}
|
true
|
||||||
}
|
}
|
||||||
17 => {
|
17 => {
|
||||||
progress!(context, 910);
|
progress!(context, 910);
|
||||||
@@ -404,10 +382,11 @@ pub fn JobConfigureImap(context: &Context) {
|
|||||||
// (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow.
|
// (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow.
|
||||||
e2ee::ensure_secret_key_exists(context);
|
e2ee::ensure_secret_key_exists(context);
|
||||||
success = true;
|
success = true;
|
||||||
info!(context, "key generation completed");
|
info!(context, "Configure completed.");
|
||||||
progress!(context, 940);
|
progress!(context, 940);
|
||||||
break; // We are done here
|
break; // We are done here
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
error!(context, "Internal error: step counter out of bound",);
|
error!(context, "Internal error: step counter out of bound",);
|
||||||
break;
|
break;
|
||||||
@@ -419,17 +398,30 @@ pub fn JobConfigureImap(context: &Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if imap_connected_here {
|
if imap_connected_here {
|
||||||
context
|
context.inbox.read().unwrap().disconnect(context);
|
||||||
.inbox_thread
|
|
||||||
.read()
|
|
||||||
.unwrap()
|
|
||||||
.imap
|
|
||||||
.disconnect(context);
|
|
||||||
}
|
}
|
||||||
if smtp_connected_here {
|
if smtp_connected_here {
|
||||||
context.smtp.clone().lock().unwrap().disconnect();
|
context.smtp.clone().lock().unwrap().disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
if !success {
|
||||||
|
// disconnect if configure did not succeed
|
||||||
|
if imap_connected_here {
|
||||||
|
// context.inbox.read().unwrap().disconnect(context);
|
||||||
|
}
|
||||||
|
if smtp_connected_here {
|
||||||
|
// context.smtp.clone().lock().unwrap().disconnect();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert!(imap_connected_here && smtp_connected_here);
|
||||||
|
info!(
|
||||||
|
context,
|
||||||
|
0, "Keeping IMAP/SMTP connections open after successful configuration"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// remember the entered parameters on success
|
// remember the entered parameters on success
|
||||||
// and restore to last-entered on failure.
|
// and restore to last-entered on failure.
|
||||||
// this way, the parameters visible to the ui are always in-sync with the current configuration.
|
// this way, the parameters visible to the ui are always in-sync with the current configuration.
|
||||||
@@ -443,42 +435,6 @@ pub fn JobConfigureImap(context: &Context) {
|
|||||||
progress!(context, if success { 1000 } else { 0 });
|
progress!(context, if success { 1000 } else { 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_offline_autoconfig(context: &Context, param: &LoginParam) -> Option<LoginParam> {
|
|
||||||
// XXX we don't have https://github.com/deltachat/provider-db APIs
|
|
||||||
// integrated yet but we'll already add nauta as a first use case, also
|
|
||||||
// showing what we need from provider-db in the future.
|
|
||||||
info!(
|
|
||||||
context,
|
|
||||||
"checking internal provider-info for offline autoconfig"
|
|
||||||
);
|
|
||||||
|
|
||||||
if param.addr.ends_with("@nauta.cu") {
|
|
||||||
let mut p = LoginParam::new();
|
|
||||||
|
|
||||||
p.addr = param.addr.clone();
|
|
||||||
p.mail_server = "imap.nauta.cu".to_string();
|
|
||||||
p.mail_user = param.addr.clone();
|
|
||||||
p.mail_pw = param.mail_pw.clone();
|
|
||||||
p.mail_port = 143;
|
|
||||||
p.imap_certificate_checks = CertificateChecks::AcceptInvalidCertificates;
|
|
||||||
|
|
||||||
p.send_server = "smtp.nauta.cu".to_string();
|
|
||||||
p.send_user = param.addr.clone();
|
|
||||||
p.send_pw = param.mail_pw.clone();
|
|
||||||
p.send_port = 25;
|
|
||||||
p.smtp_certificate_checks = CertificateChecks::AcceptInvalidCertificates;
|
|
||||||
p.server_flags = DC_LP_AUTH_NORMAL as i32
|
|
||||||
| DC_LP_IMAP_SOCKET_STARTTLS as i32
|
|
||||||
| DC_LP_SMTP_SOCKET_STARTTLS as i32;
|
|
||||||
|
|
||||||
info!(context, "found offline autoconfig: {}", p);
|
|
||||||
Some(p)
|
|
||||||
} else {
|
|
||||||
info!(context, "no offline autoconfig found");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_imap_connections(
|
fn try_imap_connections(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
mut param: &mut LoginParam,
|
mut param: &mut LoginParam,
|
||||||
@@ -534,22 +490,11 @@ fn try_imap_connection(
|
|||||||
|
|
||||||
fn try_imap_one_param(context: &Context, param: &LoginParam) -> Option<bool> {
|
fn try_imap_one_param(context: &Context, param: &LoginParam) -> Option<bool> {
|
||||||
let inf = format!(
|
let inf = format!(
|
||||||
"imap: {}@{}:{} flags=0x{:x} certificate_checks={}",
|
"imap: {}@{}:{} flags=0x{:x}",
|
||||||
param.mail_user,
|
param.mail_user, param.mail_server, param.mail_port, param.server_flags
|
||||||
param.mail_server,
|
|
||||||
param.mail_port,
|
|
||||||
param.server_flags,
|
|
||||||
param.imap_certificate_checks
|
|
||||||
);
|
);
|
||||||
info!(context, "Trying: {}", inf);
|
info!(context, "Trying: {}", inf);
|
||||||
if task::block_on(
|
if context.inbox.read().unwrap().connect(context, ¶m) {
|
||||||
context
|
|
||||||
.inbox_thread
|
|
||||||
.read()
|
|
||||||
.unwrap()
|
|
||||||
.imap
|
|
||||||
.connect(context, ¶m),
|
|
||||||
) {
|
|
||||||
info!(context, "success: {}", inf);
|
info!(context, "success: {}", inf);
|
||||||
return Some(true);
|
return Some(true);
|
||||||
}
|
}
|
||||||
@@ -618,12 +563,54 @@ fn try_smtp_one_param(context: &Context, param: &LoginParam) -> Option<bool> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Connect to configured account
|
||||||
|
******************************************************************************/
|
||||||
|
pub fn dc_connect_to_configured_imap(context: &Context, imap: &Imap) -> libc::c_int {
|
||||||
|
let mut ret_connected = 0;
|
||||||
|
|
||||||
|
if async_std::task::block_on(async move { imap.is_connected().await }) {
|
||||||
|
ret_connected = 1
|
||||||
|
} else if !context.sql.get_raw_config_bool(context, "configured") {
|
||||||
|
warn!(context, "Not configured, cannot connect.",);
|
||||||
|
} else {
|
||||||
|
let param = LoginParam::from_database(context, "configured_");
|
||||||
|
// the trailing underscore is correct
|
||||||
|
|
||||||
|
if imap.connect(context, ¶m) {
|
||||||
|
ret_connected = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret_connected
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Configure a Context
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
pub fn read_autoconf_file(context: &Context, url: &str) -> Option<String> {
|
||||||
|
info!(context, "Testing {} ...", url);
|
||||||
|
|
||||||
|
match reqwest::Client::new()
|
||||||
|
.get(url)
|
||||||
|
.send()
|
||||||
|
.and_then(|mut res| res.text())
|
||||||
|
{
|
||||||
|
Ok(res) => Some(res),
|
||||||
|
Err(_err) => {
|
||||||
|
info!(context, "Can\'t read file.",);
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::config::*;
|
use crate::config::*;
|
||||||
use crate::configure::JobConfigureImap;
|
use crate::configure::dc_job_do_DC_JOB_CONFIGURE_IMAP;
|
||||||
use crate::test_utils::*;
|
use crate::test_utils::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -633,21 +620,6 @@ mod tests {
|
|||||||
.set_config(Config::Addr, Some("probably@unexistant.addr"))
|
.set_config(Config::Addr, Some("probably@unexistant.addr"))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
t.ctx.set_config(Config::MailPw, Some("123456")).unwrap();
|
t.ctx.set_config(Config::MailPw, Some("123456")).unwrap();
|
||||||
JobConfigureImap(&t.ctx);
|
dc_job_do_DC_JOB_CONFIGURE_IMAP(&t.ctx);
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get_offline_autoconfig() {
|
|
||||||
let context = dummy_context().ctx;
|
|
||||||
|
|
||||||
let mut params = LoginParam::new();
|
|
||||||
params.addr = "someone123@example.org".to_string();
|
|
||||||
assert!(get_offline_autoconfig(&context, ¶ms).is_none());
|
|
||||||
|
|
||||||
let mut params = LoginParam::new();
|
|
||||||
params.addr = "someone123@nauta.cu".to_string();
|
|
||||||
let found_params = get_offline_autoconfig(&context, ¶ms).unwrap();
|
|
||||||
assert_eq!(found_params.mail_server, "imap.nauta.cu".to_string());
|
|
||||||
assert_eq!(found_params.send_server, "smtp.nauta.cu".to_string());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
use crate::context::Context;
|
|
||||||
|
|
||||||
#[derive(Debug, Fail)]
|
|
||||||
pub enum Error {
|
|
||||||
#[fail(display = "URL request error")]
|
|
||||||
GetError(#[cause] reqwest::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
pub fn read_url(context: &Context, url: &str) -> Result<String> {
|
|
||||||
info!(context, "Requesting URL {}", url);
|
|
||||||
|
|
||||||
match reqwest::Client::new()
|
|
||||||
.get(url)
|
|
||||||
.send()
|
|
||||||
.and_then(|mut res| res.text())
|
|
||||||
{
|
|
||||||
Ok(res) => Ok(res),
|
|
||||||
Err(err) => {
|
|
||||||
info!(context, "Can\'t read URL {}", url);
|
|
||||||
|
|
||||||
Err(Error::GetError(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
//! # Constants
|
//! Constants
|
||||||
#![allow(non_camel_case_types, dead_code)]
|
#![allow(non_camel_case_types, dead_code)]
|
||||||
|
|
||||||
use deltachat_derive::*;
|
use deltachat_derive::*;
|
||||||
@@ -8,6 +8,21 @@ lazy_static! {
|
|||||||
pub static ref DC_VERSION_STR: String = env!("CARGO_PKG_VERSION").to_string();
|
pub static ref DC_VERSION_STR: String = env!("CARGO_PKG_VERSION").to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
|
||||||
|
pub enum MoveState {
|
||||||
|
Undefined = 0,
|
||||||
|
Pending = 1,
|
||||||
|
Stay = 2,
|
||||||
|
Moving = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MoveState {
|
||||||
|
fn default() -> Self {
|
||||||
|
MoveState::Undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// some defaults
|
// some defaults
|
||||||
const DC_E2EE_DEFAULT_ENABLED: i32 = 1;
|
const DC_E2EE_DEFAULT_ENABLED: i32 = 1;
|
||||||
const DC_INBOX_WATCH_DEFAULT: i32 = 1;
|
const DC_INBOX_WATCH_DEFAULT: i32 = 1;
|
||||||
@@ -58,9 +73,6 @@ pub const DC_GCM_ADDDAYMARKER: u32 = 0x01;
|
|||||||
pub const DC_GCL_VERIFIED_ONLY: usize = 0x01;
|
pub const DC_GCL_VERIFIED_ONLY: usize = 0x01;
|
||||||
pub const DC_GCL_ADD_SELF: usize = 0x02;
|
pub const DC_GCL_ADD_SELF: usize = 0x02;
|
||||||
|
|
||||||
// unchanged user avatars are resent to the recipients every some days
|
|
||||||
pub const DC_RESEND_USER_AVATAR_DAYS: i64 = 14;
|
|
||||||
|
|
||||||
// values for DC_PARAM_FORCE_PLAINTEXT
|
// values for DC_PARAM_FORCE_PLAINTEXT
|
||||||
pub(crate) const DC_FP_NO_AUTOCRYPT_HEADER: i32 = 2;
|
pub(crate) const DC_FP_NO_AUTOCRYPT_HEADER: i32 = 2;
|
||||||
pub(crate) const DC_FP_ADD_AUTOCRYPT_HEADER: i32 = 1;
|
pub(crate) const DC_FP_ADD_AUTOCRYPT_HEADER: i32 = 1;
|
||||||
@@ -122,9 +134,7 @@ pub const DC_CONTACT_ID_INFO: u32 = 2;
|
|||||||
pub const DC_CONTACT_ID_DEVICE: u32 = 5;
|
pub const DC_CONTACT_ID_DEVICE: u32 = 5;
|
||||||
pub const DC_CONTACT_ID_LAST_SPECIAL: u32 = 9;
|
pub const DC_CONTACT_ID_LAST_SPECIAL: u32 = 9;
|
||||||
|
|
||||||
// decorative address that is used for DC_CONTACT_ID_DEVICE
|
pub const DC_CREATE_MVBOX: usize = 1;
|
||||||
// when an api that returns an email is called.
|
|
||||||
pub const DC_CONTACT_ID_DEVICE_ADDR: &str = "device@localhost";
|
|
||||||
|
|
||||||
// Flags for empty server job
|
// Flags for empty server job
|
||||||
|
|
||||||
@@ -184,9 +194,6 @@ pub const DC_VC_CONTACT_CONFIRM: i32 = 6;
|
|||||||
pub const DC_BOB_ERROR: i32 = 0;
|
pub const DC_BOB_ERROR: i32 = 0;
|
||||||
pub const DC_BOB_SUCCESS: i32 = 1;
|
pub const DC_BOB_SUCCESS: i32 = 1;
|
||||||
|
|
||||||
// max. width/height of an avatar
|
|
||||||
pub const AVATAR_SIZE: u32 = 192;
|
|
||||||
|
|
||||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
|
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
|
||||||
#[repr(i32)]
|
#[repr(i32)]
|
||||||
pub enum Viewtype {
|
pub enum Viewtype {
|
||||||
|
|||||||
335
src/contact.rs
@@ -1,5 +1,3 @@
|
|||||||
//! Contacts module
|
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use deltachat_derive::*;
|
use deltachat_derive::*;
|
||||||
@@ -12,13 +10,11 @@ use crate::constants::*;
|
|||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::dc_tools::*;
|
use crate::dc_tools::*;
|
||||||
use crate::e2ee;
|
use crate::e2ee;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::Result;
|
||||||
use crate::events::Event;
|
use crate::events::Event;
|
||||||
use crate::key::*;
|
use crate::key::*;
|
||||||
use crate::login_param::LoginParam;
|
use crate::login_param::LoginParam;
|
||||||
use crate::message::{MessageState, MsgId};
|
use crate::message::{MessageState, MsgId};
|
||||||
use crate::mimeparser::AvatarAction;
|
|
||||||
use crate::param::*;
|
|
||||||
use crate::peerstate::*;
|
use crate::peerstate::*;
|
||||||
use crate::sql;
|
use crate::sql;
|
||||||
use crate::stock::StockMessage;
|
use crate::stock::StockMessage;
|
||||||
@@ -27,17 +23,15 @@ use crate::stock::StockMessage;
|
|||||||
const DC_ORIGIN_MIN_CONTACT_LIST: i32 = 0x100;
|
const DC_ORIGIN_MIN_CONTACT_LIST: i32 = 0x100;
|
||||||
|
|
||||||
/// An object representing a single contact in memory.
|
/// An object representing a single contact in memory.
|
||||||
///
|
|
||||||
/// The contact object is not updated.
|
/// The contact object is not updated.
|
||||||
/// If you want an update, you have to recreate the object.
|
/// If you want an update, you have to recreate the object.
|
||||||
///
|
///
|
||||||
/// The library makes sure
|
/// The library makes sure
|
||||||
/// only to use names _authorized_ by the contact in `To:` or `Cc:`.
|
/// only to use names _authorized_ by the contact in `To:` or `Cc:`.
|
||||||
/// *Given-names* as "Daddy" or "Honey" are not used there.
|
/// _Given-names _as "Daddy" or "Honey" are not used there.
|
||||||
/// For this purpose, internally, two names are tracked -
|
/// For this purpose, internally, two names are tracked -
|
||||||
/// authorized name and given name.
|
/// authorized-name and given-name.
|
||||||
/// By default, these names are equal, but functions working with contact names
|
/// By default, these names are equal, but functions working with contact names
|
||||||
/// only affect the given name.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Contact {
|
pub struct Contact {
|
||||||
/// The contact ID.
|
/// The contact ID.
|
||||||
@@ -53,8 +47,8 @@ pub struct Contact {
|
|||||||
/// May be empty, initially set to `authname`.
|
/// May be empty, initially set to `authname`.
|
||||||
name: String,
|
name: String,
|
||||||
/// Name authorized by the contact himself. Only this name may be spread to others,
|
/// Name authorized by the contact himself. Only this name may be spread to others,
|
||||||
/// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_authname`,
|
/// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_name`,
|
||||||
/// to access this field.
|
/// `Contact::get_display_name` or `Contact::get_name_n_addr` to access this field.
|
||||||
authname: String,
|
authname: String,
|
||||||
/// E-Mail-Address of the contact. It is recommended to use `Contact::get_addr`` to access this field.
|
/// E-Mail-Address of the contact. It is recommended to use `Contact::get_addr`` to access this field.
|
||||||
addr: String,
|
addr: String,
|
||||||
@@ -62,8 +56,6 @@ pub struct Contact {
|
|||||||
blocked: bool,
|
blocked: bool,
|
||||||
/// The origin/source of the contact.
|
/// The origin/source of the contact.
|
||||||
pub origin: Origin,
|
pub origin: Origin,
|
||||||
/// Parameters as Param::ProfileImage
|
|
||||||
pub param: Params,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Possible origins of a contact.
|
/// Possible origins of a contact.
|
||||||
@@ -114,6 +106,11 @@ impl Default for Origin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Origin {
|
impl Origin {
|
||||||
|
/// Contacts that start a new "normal" chat, defaults to off.
|
||||||
|
pub fn is_start_new_chat(self) -> bool {
|
||||||
|
self as i32 >= 0x7FFFFFFF
|
||||||
|
}
|
||||||
|
|
||||||
/// Contacts that are verified and known not to be spam.
|
/// Contacts that are verified and known not to be spam.
|
||||||
pub fn is_verified(self) -> bool {
|
pub fn is_verified(self) -> bool {
|
||||||
self as i32 >= 0x100
|
self as i32 >= 0x100
|
||||||
@@ -144,11 +141,33 @@ pub enum VerifiedStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Contact {
|
impl Contact {
|
||||||
pub fn load_from_db(context: &Context, contact_id: u32) -> crate::sql::Result<Self> {
|
pub fn load_from_db(context: &Context, contact_id: u32) -> Result<Self> {
|
||||||
let mut res = context.sql.query_row(
|
if contact_id == DC_CONTACT_ID_SELF {
|
||||||
"SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param
|
let contact = Contact {
|
||||||
FROM contacts c
|
id: contact_id,
|
||||||
WHERE c.id=?;",
|
name: context.stock_str(StockMessage::SelfMsg).into(),
|
||||||
|
authname: "".into(),
|
||||||
|
addr: context
|
||||||
|
.get_config(Config::ConfiguredAddr)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.sql.query_row(
|
||||||
|
"SELECT c.name, c.addr, c.origin, c.blocked, c.authname FROM contacts c WHERE c.id=?;",
|
||||||
params![contact_id as i32],
|
params![contact_id as i32],
|
||||||
|row| {
|
|row| {
|
||||||
let contact = Self {
|
let contact = Self {
|
||||||
@@ -158,21 +177,10 @@ impl Contact {
|
|||||||
addr: row.get::<_, String>(1)?,
|
addr: row.get::<_, String>(1)?,
|
||||||
blocked: row.get::<_, Option<i32>>(3)?.unwrap_or_default() != 0,
|
blocked: row.get::<_, Option<i32>>(3)?.unwrap_or_default() != 0,
|
||||||
origin: row.get(2)?,
|
origin: row.get(2)?,
|
||||||
param: row.get::<_, String>(5)?.parse().unwrap_or_default(),
|
|
||||||
};
|
};
|
||||||
Ok(contact)
|
Ok(contact)
|
||||||
},
|
}
|
||||||
)?;
|
)
|
||||||
if contact_id == DC_CONTACT_ID_SELF {
|
|
||||||
res.name = context.stock_str(StockMessage::SelfMsg).to_string();
|
|
||||||
res.addr = context
|
|
||||||
.get_config(Config::ConfiguredAddr)
|
|
||||||
.unwrap_or_default();
|
|
||||||
} else if contact_id == DC_CONTACT_ID_DEVICE {
|
|
||||||
res.name = context.stock_str(StockMessage::DeviceMessages).to_string();
|
|
||||||
res.addr = DC_CONTACT_ID_DEVICE_ADDR.to_string();
|
|
||||||
}
|
|
||||||
Ok(res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if this contact is blocked.
|
/// Returns `true` if this contact is blocked.
|
||||||
@@ -200,7 +208,7 @@ impl Contact {
|
|||||||
/// Add a single contact as a result of an _explicit_ user action.
|
/// Add a single contact as a result of an _explicit_ user action.
|
||||||
///
|
///
|
||||||
/// We assume, the contact name, if any, is entered by the user and is used "as is" therefore,
|
/// We assume, the contact name, if any, is entered by the user and is used "as is" therefore,
|
||||||
/// normalize() is *not* called for the name. If the contact is blocked, it is unblocked.
|
/// normalize() is _not_ called for the name. If the contact is blocked, it is unblocked.
|
||||||
///
|
///
|
||||||
/// To add a number of contacts, see `dc_add_address_book()` which is much faster for adding
|
/// To add a number of contacts, see `dc_add_address_book()` which is much faster for adding
|
||||||
/// a bunch of addresses.
|
/// a bunch of addresses.
|
||||||
@@ -230,7 +238,7 @@ impl Contact {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Mark all messages sent by the given contact
|
/// Mark all messages sent by the given contact
|
||||||
/// as *noticed*. See also dc_marknoticed_chat() and dc_markseen_msgs()
|
/// as _noticed_. See also dc_marknoticed_chat() and dc_markseen_msgs()
|
||||||
///
|
///
|
||||||
/// Calling this function usually results in the event `#DC_EVENT_MSGS_CHANGED`.
|
/// Calling this function usually results in the event `#DC_EVENT_MSGS_CHANGED`.
|
||||||
pub fn mark_noticed(context: &Context, id: u32) {
|
pub fn mark_noticed(context: &Context, id: u32) {
|
||||||
@@ -264,7 +272,7 @@ impl Contact {
|
|||||||
.get_config(Config::ConfiguredAddr)
|
.get_config(Config::ConfiguredAddr)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if addr_cmp(addr_normalized, addr_self) {
|
if addr_normalized == addr_self {
|
||||||
return DC_CONTACT_ID_SELF;
|
return DC_CONTACT_ID_SELF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,7 +309,7 @@ impl Contact {
|
|||||||
.get_config(Config::ConfiguredAddr)
|
.get_config(Config::ConfiguredAddr)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if addr_cmp(addr, addr_self) {
|
if addr == addr_self {
|
||||||
return Ok((DC_CONTACT_ID_SELF, sth_modified));
|
return Ok((DC_CONTACT_ID_SELF, sth_modified));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,7 +428,7 @@ impl Contact {
|
|||||||
/// the event `DC_EVENT_CONTACTS_CHANGED` is sent.
|
/// the event `DC_EVENT_CONTACTS_CHANGED` is sent.
|
||||||
///
|
///
|
||||||
/// To add a single contact entered by the user, you should prefer `Contact::create`,
|
/// To add a single contact entered by the user, you should prefer `Contact::create`,
|
||||||
/// however, for adding a bunch of addresses, this function is much faster.
|
/// however, for adding a bunch of addresses, this function is _much_ faster.
|
||||||
///
|
///
|
||||||
/// The `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
|
/// The `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
|
||||||
///
|
///
|
||||||
@@ -577,12 +585,7 @@ impl Contact {
|
|||||||
|
|
||||||
let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql);
|
let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql);
|
||||||
|
|
||||||
if peerstate.is_some()
|
if peerstate.is_some() && peerstate.as_ref().and_then(|p| p.peek_key(0)).is_some() {
|
||||||
&& peerstate
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|p| p.peek_key(PeerstateVerifiedStatus::Unverified))
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
let peerstate = peerstate.as_ref().unwrap();
|
let peerstate = peerstate.as_ref().unwrap();
|
||||||
let p =
|
let p =
|
||||||
context.stock_str(if peerstate.prefer_encrypt == EncryptPreference::Mutual {
|
context.stock_str(if peerstate.prefer_encrypt == EncryptPreference::Mutual {
|
||||||
@@ -602,25 +605,25 @@ impl Contact {
|
|||||||
.map(|k| k.formatted_fingerprint())
|
.map(|k| k.formatted_fingerprint())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let fingerprint_other_verified = peerstate
|
let fingerprint_other_verified = peerstate
|
||||||
.peek_key(PeerstateVerifiedStatus::BidirectVerified)
|
.peek_key(2)
|
||||||
.map(|k| k.formatted_fingerprint())
|
.map(|k| k.formatted_fingerprint())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let fingerprint_other_unverified = peerstate
|
let fingerprint_other_unverified = peerstate
|
||||||
.peek_key(PeerstateVerifiedStatus::Unverified)
|
.peek_key(0)
|
||||||
.map(|k| k.formatted_fingerprint())
|
.map(|k| k.formatted_fingerprint())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
if loginparam.addr < peerstate.addr {
|
if peerstate.addr.is_some() && &loginparam.addr < peerstate.addr.as_ref().unwrap() {
|
||||||
cat_fingerprint(&mut ret, &loginparam.addr, &fingerprint_self, "");
|
cat_fingerprint(&mut ret, &loginparam.addr, &fingerprint_self, "");
|
||||||
cat_fingerprint(
|
cat_fingerprint(
|
||||||
&mut ret,
|
&mut ret,
|
||||||
peerstate.addr.clone(),
|
peerstate.addr.as_ref().unwrap(),
|
||||||
&fingerprint_other_verified,
|
&fingerprint_other_verified,
|
||||||
&fingerprint_other_unverified,
|
&fingerprint_other_unverified,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
cat_fingerprint(
|
cat_fingerprint(
|
||||||
&mut ret,
|
&mut ret,
|
||||||
peerstate.addr.clone(),
|
peerstate.addr.as_ref().unwrap(),
|
||||||
&fingerprint_other_verified,
|
&fingerprint_other_verified,
|
||||||
&fingerprint_other_unverified,
|
&fingerprint_other_unverified,
|
||||||
);
|
);
|
||||||
@@ -683,7 +686,7 @@ impl Contact {
|
|||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(context, "delete_contact {} failed ({})", contact_id, err);
|
error!(context, "delete_contact {} failed ({})", contact_id, err);
|
||||||
return Err(err.into());
|
return Err(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -701,17 +704,7 @@ impl Contact {
|
|||||||
/// like "Me" in the selected language and the email address
|
/// like "Me" in the selected language and the email address
|
||||||
/// defined by dc_set_config().
|
/// defined by dc_set_config().
|
||||||
pub fn get_by_id(context: &Context, contact_id: u32) -> Result<Contact> {
|
pub fn get_by_id(context: &Context, contact_id: u32) -> Result<Contact> {
|
||||||
Ok(Contact::load_from_db(context, contact_id)?)
|
Contact::load_from_db(context, contact_id)
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_param(&mut self, context: &Context) -> Result<()> {
|
|
||||||
sql::execute(
|
|
||||||
context,
|
|
||||||
&context.sql,
|
|
||||||
"UPDATE contacts SET param=? WHERE id=?",
|
|
||||||
params![self.param.to_string(), self.id as i32],
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the ID of the contact.
|
/// Get the ID of the contact.
|
||||||
@@ -724,7 +717,6 @@ impl Contact {
|
|||||||
&self.addr
|
&self.addr
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get name authorized by the contact.
|
|
||||||
pub fn get_authname(&self) -> &str {
|
pub fn get_authname(&self) -> &str {
|
||||||
&self.authname
|
&self.authname
|
||||||
}
|
}
|
||||||
@@ -747,9 +739,6 @@ impl Contact {
|
|||||||
if !self.name.is_empty() {
|
if !self.name.is_empty() {
|
||||||
return &self.name;
|
return &self.name;
|
||||||
}
|
}
|
||||||
if !self.authname.is_empty() {
|
|
||||||
return &self.authname;
|
|
||||||
}
|
|
||||||
&self.addr
|
&self.addr
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -785,11 +774,8 @@ impl Contact {
|
|||||||
if let Some(p) = context.get_config(Config::Selfavatar) {
|
if let Some(p) = context.get_config(Config::Selfavatar) {
|
||||||
return Some(PathBuf::from(p));
|
return Some(PathBuf::from(p));
|
||||||
}
|
}
|
||||||
} else if let Some(image_rel) = self.param.get(Param::ProfileImage) {
|
|
||||||
if !image_rel.is_empty() {
|
|
||||||
return Some(dc_get_abs_path(context, image_rel));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// TODO: else get image_abs from contact param
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -872,14 +858,14 @@ impl Contact {
|
|||||||
.unwrap_or_default() as usize
|
.unwrap_or_default() as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_origin_by_id(context: &Context, contact_id: u32, ret_blocked: &mut bool) -> Origin {
|
pub fn get_origin_by_id(context: &Context, contact_id: u32, ret_blocked: &mut i32) -> Origin {
|
||||||
let mut ret = Origin::Unknown;
|
let mut ret = Origin::Unknown;
|
||||||
*ret_blocked = false;
|
*ret_blocked = 0;
|
||||||
|
|
||||||
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
|
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
|
||||||
/* we could optimize this by loading only the needed fields */
|
/* we could optimize this by loading only the needed fields */
|
||||||
if contact.blocked {
|
if contact.blocked {
|
||||||
*ret_blocked = true;
|
*ret_blocked = 1;
|
||||||
} else {
|
} else {
|
||||||
ret = contact.origin;
|
ret = contact.origin;
|
||||||
}
|
}
|
||||||
@@ -889,7 +875,7 @@ impl Contact {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn real_exists_by_id(context: &Context, contact_id: u32) -> bool {
|
pub fn real_exists_by_id(context: &Context, contact_id: u32) -> bool {
|
||||||
if !context.sql.is_open() || contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
|
if !context.sql.is_open() || contact_id <= 9 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -913,8 +899,7 @@ impl Contact {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts first name from full name.
|
fn get_first_name<'a>(full_name: &'a str) -> &'a str {
|
||||||
fn get_first_name(full_name: &str) -> &str {
|
|
||||||
full_name.splitn(2, ' ').next().unwrap_or_default()
|
full_name.splitn(2, ' ').next().unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -924,7 +909,6 @@ pub fn may_be_valid_addr(addr: &str) -> bool {
|
|||||||
res.is_ok()
|
res.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns address with whitespace trimmed and `mailto:` prefix removed.
|
|
||||||
pub fn addr_normalize(addr: &str) -> &str {
|
pub fn addr_normalize(addr: &str) -> &str {
|
||||||
let norm = addr.trim();
|
let norm = addr.trim();
|
||||||
|
|
||||||
@@ -936,26 +920,26 @@ pub fn addr_normalize(addr: &str) -> &str {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
|
fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
|
||||||
if contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
|
if contact_id <= 9 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
|
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
|
||||||
if contact.blocked != new_blocking
|
if contact.blocked != new_blocking {
|
||||||
&& sql::execute(
|
if sql::execute(
|
||||||
context,
|
context,
|
||||||
&context.sql,
|
&context.sql,
|
||||||
"UPDATE contacts SET blocked=? WHERE id=?;",
|
"UPDATE contacts SET blocked=? WHERE id=?;",
|
||||||
params![new_blocking as i32, contact_id as i32],
|
params![new_blocking as i32, contact_id as i32],
|
||||||
)
|
)
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
// also (un)block all chats with _only_ this contact - we do not delete them to allow a
|
// also (un)block all chats with _only_ this contact - we do not delete them to allow a
|
||||||
// non-destructive blocking->unblocking.
|
// non-destructive blocking->unblocking.
|
||||||
// (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
|
// (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
|
||||||
// However, I'm not sure about this point; it may be confusing if the user wants to add other people;
|
// However, I'm not sure about this point; it may be confusing if the user wants to add other people;
|
||||||
// this would result in recreating the same group...)
|
// this would result in recreating the same group...)
|
||||||
if sql::execute(
|
if sql::execute(
|
||||||
context,
|
context,
|
||||||
&context.sql,
|
&context.sql,
|
||||||
"UPDATE chats SET blocked=? WHERE type=? AND id IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
|
"UPDATE chats SET blocked=? WHERE type=? AND id IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
|
||||||
@@ -964,36 +948,11 @@ fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
|
|||||||
Contact::mark_noticed(context, contact_id);
|
Contact::mark_noticed(context, contact_id);
|
||||||
context.call_cb(Event::ContactsChanged(None));
|
context.call_cb(Event::ContactsChanged(None));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_profile_image(
|
|
||||||
context: &Context,
|
|
||||||
contact_id: u32,
|
|
||||||
profile_image: &AvatarAction,
|
|
||||||
) -> Result<()> {
|
|
||||||
// the given profile image is expected to be already in the blob directory
|
|
||||||
// as profile images can be set only by receiving messages, this should be always the case, however.
|
|
||||||
let mut contact = Contact::load_from_db(context, contact_id)?;
|
|
||||||
let changed = match profile_image {
|
|
||||||
AvatarAction::Change(profile_image) => {
|
|
||||||
contact.param.set(Param::ProfileImage, profile_image);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
AvatarAction::Delete => {
|
|
||||||
contact.param.remove(Param::ProfileImage);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
AvatarAction::None => false,
|
|
||||||
};
|
|
||||||
if changed {
|
|
||||||
contact.update_param(context)?;
|
|
||||||
context.call_cb(Event::ContactsChanged(Some(contact_id)));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Normalize a name.
|
/// Normalize a name.
|
||||||
///
|
///
|
||||||
/// - Remove quotes (come from some bad MUA implementations)
|
/// - Remove quotes (come from some bad MUA implementations)
|
||||||
@@ -1058,25 +1017,23 @@ fn cat_fingerprint(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
|
||||||
/// determine whether the specified addr maps to the/a self addr
|
|
||||||
pub fn is_self_addr(&self, addr: &str) -> Result<bool> {
|
|
||||||
let self_addr = match self.get_config(Config::ConfiguredAddr) {
|
|
||||||
Some(s) => s,
|
|
||||||
None => return Err(Error::NotConfigured),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(addr_cmp(self_addr, addr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addr_cmp(addr1: impl AsRef<str>, addr2: impl AsRef<str>) -> bool {
|
pub fn addr_cmp(addr1: impl AsRef<str>, addr2: impl AsRef<str>) -> bool {
|
||||||
let norm1 = addr_normalize(addr1.as_ref()).to_lowercase();
|
let norm1 = addr_normalize(addr1.as_ref());
|
||||||
let norm2 = addr_normalize(addr2.as_ref()).to_lowercase();
|
let norm2 = addr_normalize(addr2.as_ref());
|
||||||
|
|
||||||
norm1 == norm2
|
norm1 == norm2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn addr_equals_self(context: &Context, addr: impl AsRef<str>) -> bool {
|
||||||
|
if !addr.as_ref().is_empty() {
|
||||||
|
let normalized_addr = addr_normalize(addr.as_ref());
|
||||||
|
if let Some(self_addr) = context.get_config(Config::ConfiguredAddr) {
|
||||||
|
return normalized_addr == self_addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn split_address_book(book: &str) -> Vec<(&str, &str)> {
|
fn split_address_book(book: &str) -> Vec<(&str, &str)> {
|
||||||
book.lines()
|
book.lines()
|
||||||
.chunks(2)
|
.chunks(2)
|
||||||
@@ -1096,8 +1053,6 @@ fn split_address_book(book: &str) -> Vec<(&str, &str)> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use crate::test_utils::*;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_may_be_valid_addr() {
|
fn test_may_be_valid_addr() {
|
||||||
assert_eq!(may_be_valid_addr(""), false);
|
assert_eq!(may_be_valid_addr(""), false);
|
||||||
@@ -1123,10 +1078,6 @@ mod tests {
|
|||||||
fn test_normalize_addr() {
|
fn test_normalize_addr() {
|
||||||
assert_eq!(addr_normalize("mailto:john@doe.com"), "john@doe.com");
|
assert_eq!(addr_normalize("mailto:john@doe.com"), "john@doe.com");
|
||||||
assert_eq!(addr_normalize(" hello@world.com "), "hello@world.com");
|
assert_eq!(addr_normalize(" hello@world.com "), "hello@world.com");
|
||||||
|
|
||||||
// normalisation preserves case to allow user-defined spelling.
|
|
||||||
// however, case is ignored on addr_cmp()
|
|
||||||
assert_ne!(addr_normalize("John@Doe.com"), "john@doe.com");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1143,128 +1094,4 @@ mod tests {
|
|||||||
vec![("Name one", "Address one"), ("Name two", "Address two")]
|
vec![("Name one", "Address one"), ("Name two", "Address two")]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get_contacts() {
|
|
||||||
let context = dummy_context();
|
|
||||||
let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap();
|
|
||||||
assert_eq!(contacts.len(), 0);
|
|
||||||
|
|
||||||
let id = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
|
|
||||||
assert_ne!(id, 0);
|
|
||||||
|
|
||||||
let contacts = Contact::get_all(&context.ctx, 0, Some("bob")).unwrap();
|
|
||||||
assert_eq!(contacts.len(), 1);
|
|
||||||
|
|
||||||
let contacts = Contact::get_all(&context.ctx, 0, Some("alice")).unwrap();
|
|
||||||
assert_eq!(contacts.len(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_is_self_addr() -> Result<()> {
|
|
||||||
let t = test_context(None);
|
|
||||||
assert!(t.ctx.is_self_addr("me@me.org").is_err());
|
|
||||||
|
|
||||||
let addr = configure_alice_keypair(&t.ctx);
|
|
||||||
assert_eq!(t.ctx.is_self_addr("me@me.org")?, false);
|
|
||||||
assert_eq!(t.ctx.is_self_addr(&addr)?, true);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_add_or_lookup() {
|
|
||||||
// add some contacts, this also tests add_address_book()
|
|
||||||
let t = dummy_context();
|
|
||||||
let book = concat!(
|
|
||||||
" Name one \n one@eins.org \n",
|
|
||||||
"Name two\ntwo@deux.net\n",
|
|
||||||
"\nthree@drei.sam\n",
|
|
||||||
"Name two\ntwo@deux.net\n" // should not be added again
|
|
||||||
);
|
|
||||||
assert_eq!(Contact::add_address_book(&t.ctx, book).unwrap(), 3);
|
|
||||||
|
|
||||||
// check first added contact, this does not modify because of lower origin
|
|
||||||
let (contact_id, sth_modified) =
|
|
||||||
Contact::add_or_lookup(&t.ctx, "bla foo", "one@eins.org", Origin::IncomingUnknownTo)
|
|
||||||
.unwrap();
|
|
||||||
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
|
|
||||||
assert_eq!(sth_modified, Modifier::None);
|
|
||||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
|
||||||
assert_eq!(contact.get_id(), contact_id);
|
|
||||||
assert_eq!(contact.get_name(), "Name one");
|
|
||||||
assert_eq!(contact.get_display_name(), "Name one");
|
|
||||||
assert_eq!(contact.get_addr(), "one@eins.org");
|
|
||||||
assert_eq!(contact.get_name_n_addr(), "Name one (one@eins.org)");
|
|
||||||
|
|
||||||
// modify first added contact
|
|
||||||
let (contact_id_test, sth_modified) = Contact::add_or_lookup(
|
|
||||||
&t.ctx,
|
|
||||||
"Real one",
|
|
||||||
" one@eins.org ",
|
|
||||||
Origin::ManuallyCreated,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(contact_id, contact_id_test);
|
|
||||||
assert_eq!(sth_modified, Modifier::Modified);
|
|
||||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
|
||||||
assert_eq!(contact.get_name(), "Real one");
|
|
||||||
assert_eq!(contact.get_addr(), "one@eins.org");
|
|
||||||
assert!(!contact.is_blocked());
|
|
||||||
|
|
||||||
// check third added contact (contact without name)
|
|
||||||
let (contact_id, sth_modified) =
|
|
||||||
Contact::add_or_lookup(&t.ctx, "", "three@drei.sam", Origin::IncomingUnknownTo)
|
|
||||||
.unwrap();
|
|
||||||
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
|
|
||||||
assert_eq!(sth_modified, Modifier::None);
|
|
||||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
|
||||||
assert_eq!(contact.get_name(), "");
|
|
||||||
assert_eq!(contact.get_display_name(), "three@drei.sam");
|
|
||||||
assert_eq!(contact.get_addr(), "three@drei.sam");
|
|
||||||
assert_eq!(contact.get_name_n_addr(), "three@drei.sam");
|
|
||||||
|
|
||||||
// add name to third contact from incoming message (this becomes authorized name)
|
|
||||||
let (contact_id_test, sth_modified) = Contact::add_or_lookup(
|
|
||||||
&t.ctx,
|
|
||||||
"m. serious",
|
|
||||||
"three@drei.sam",
|
|
||||||
Origin::IncomingUnknownFrom,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(contact_id, contact_id_test);
|
|
||||||
assert_eq!(sth_modified, Modifier::Modified);
|
|
||||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
|
||||||
assert_eq!(contact.get_name_n_addr(), "m. serious (three@drei.sam)");
|
|
||||||
assert!(!contact.is_blocked());
|
|
||||||
|
|
||||||
// manually edit name of third contact (does not changed authorized name)
|
|
||||||
let (contact_id_test, sth_modified) = Contact::add_or_lookup(
|
|
||||||
&t.ctx,
|
|
||||||
"schnucki",
|
|
||||||
"three@drei.sam",
|
|
||||||
Origin::ManuallyCreated,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(contact_id, contact_id_test);
|
|
||||||
assert_eq!(sth_modified, Modifier::Modified);
|
|
||||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
|
||||||
assert_eq!(contact.get_authname(), "m. serious");
|
|
||||||
assert_eq!(contact.get_name_n_addr(), "schnucki (three@drei.sam)");
|
|
||||||
assert!(!contact.is_blocked());
|
|
||||||
|
|
||||||
// check SELF
|
|
||||||
let contact = Contact::load_from_db(&t.ctx, DC_CONTACT_ID_SELF).unwrap();
|
|
||||||
assert_eq!(DC_CONTACT_ID_SELF, 1);
|
|
||||||
assert_eq!(contact.get_name(), t.ctx.stock_str(StockMessage::SelfMsg));
|
|
||||||
assert_eq!(contact.get_addr(), ""); // we're not configured
|
|
||||||
assert!(!contact.is_blocked());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_addr_cmp() {
|
|
||||||
assert!(addr_cmp("AA@AA.ORG", "aa@aa.ORG"));
|
|
||||||
assert!(addr_cmp(" aa@aa.ORG ", "AA@AA.ORG"));
|
|
||||||
assert!(addr_cmp(" mailto:AA@AA.ORG", "Aa@Aa.orG"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
//! Context module
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -21,7 +19,7 @@ use crate::login_param::LoginParam;
|
|||||||
use crate::lot::Lot;
|
use crate::lot::Lot;
|
||||||
use crate::message::{self, Message, MsgId};
|
use crate::message::{self, Message, MsgId};
|
||||||
use crate::param::Params;
|
use crate::param::Params;
|
||||||
use crate::smtp::Smtp;
|
use crate::smtp::*;
|
||||||
use crate::sql::Sql;
|
use crate::sql::Sql;
|
||||||
|
|
||||||
/// Callback function type for [Context]
|
/// Callback function type for [Context]
|
||||||
@@ -41,14 +39,12 @@ pub type ContextCallback = dyn Fn(&Context, Event) -> uintptr_t + Send + Sync;
|
|||||||
|
|
||||||
#[derive(DebugStub)]
|
#[derive(DebugStub)]
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
/// Database file path
|
|
||||||
dbfile: PathBuf,
|
dbfile: PathBuf,
|
||||||
/// Blob directory path
|
|
||||||
blobdir: PathBuf,
|
blobdir: PathBuf,
|
||||||
pub sql: Sql,
|
pub sql: Sql,
|
||||||
|
pub inbox: Arc<RwLock<Imap>>,
|
||||||
pub perform_inbox_jobs_needed: Arc<RwLock<bool>>,
|
pub perform_inbox_jobs_needed: Arc<RwLock<bool>>,
|
||||||
pub probe_imap_network: Arc<RwLock<bool>>,
|
pub probe_imap_network: Arc<RwLock<bool>>,
|
||||||
pub inbox_thread: Arc<RwLock<JobThread>>,
|
|
||||||
pub sentbox_thread: Arc<RwLock<JobThread>>,
|
pub sentbox_thread: Arc<RwLock<JobThread>>,
|
||||||
pub mvbox_thread: Arc<RwLock<JobThread>>,
|
pub mvbox_thread: Arc<RwLock<JobThread>>,
|
||||||
pub smtp: Arc<Mutex<Smtp>>,
|
pub smtp: Arc<Mutex<Smtp>>,
|
||||||
@@ -59,7 +55,7 @@ pub struct Context {
|
|||||||
pub os_name: Option<String>,
|
pub os_name: Option<String>,
|
||||||
pub cmdline_sel_chat_id: Arc<RwLock<u32>>,
|
pub cmdline_sel_chat_id: Arc<RwLock<u32>>,
|
||||||
pub bob: Arc<RwLock<BobStatus>>,
|
pub bob: Arc<RwLock<BobStatus>>,
|
||||||
pub last_smeared_timestamp: RwLock<i64>,
|
pub last_smeared_timestamp: Arc<RwLock<i64>>,
|
||||||
pub running_state: Arc<RwLock<RunningState>>,
|
pub running_state: Arc<RwLock<RunningState>>,
|
||||||
/// Mutex to avoid generating the key for the user more than once.
|
/// Mutex to avoid generating the key for the user more than once.
|
||||||
pub generating_key_mutex: Mutex<()>,
|
pub generating_key_mutex: Mutex<()>,
|
||||||
@@ -88,7 +84,7 @@ pub fn get_info() -> HashMap<&'static str, String> {
|
|||||||
);
|
);
|
||||||
res.insert(
|
res.insert(
|
||||||
"arch",
|
"arch",
|
||||||
(std::mem::size_of::<*mut libc::c_void>())
|
(::std::mem::size_of::<*mut libc::c_void>())
|
||||||
.wrapping_mul(8)
|
.wrapping_mul(8)
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
@@ -97,7 +93,6 @@ pub fn get_info() -> HashMap<&'static str, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
/// Creates new context.
|
|
||||||
pub fn new(cb: Box<ContextCallback>, os_name: String, dbfile: PathBuf) -> Result<Context> {
|
pub fn new(cb: Box<ContextCallback>, os_name: String, dbfile: PathBuf) -> Result<Context> {
|
||||||
let mut blob_fname = OsString::new();
|
let mut blob_fname = OsString::new();
|
||||||
blob_fname.push(dbfile.file_name().unwrap_or_default());
|
blob_fname.push(dbfile.file_name().unwrap_or_default());
|
||||||
@@ -123,6 +118,7 @@ impl Context {
|
|||||||
let ctx = Context {
|
let ctx = Context {
|
||||||
blobdir,
|
blobdir,
|
||||||
dbfile,
|
dbfile,
|
||||||
|
inbox: Arc::new(RwLock::new(Imap::new())),
|
||||||
cb,
|
cb,
|
||||||
os_name: Some(os_name),
|
os_name: Some(os_name),
|
||||||
running_state: Arc::new(RwLock::new(Default::default())),
|
running_state: Arc::new(RwLock::new(Default::default())),
|
||||||
@@ -131,13 +127,8 @@ impl Context {
|
|||||||
smtp_state: Arc::new((Mutex::new(Default::default()), Condvar::new())),
|
smtp_state: Arc::new((Mutex::new(Default::default()), Condvar::new())),
|
||||||
oauth2_critical: Arc::new(Mutex::new(())),
|
oauth2_critical: Arc::new(Mutex::new(())),
|
||||||
bob: Arc::new(RwLock::new(Default::default())),
|
bob: Arc::new(RwLock::new(Default::default())),
|
||||||
last_smeared_timestamp: RwLock::new(0),
|
last_smeared_timestamp: Arc::new(RwLock::new(0)),
|
||||||
cmdline_sel_chat_id: Arc::new(RwLock::new(0)),
|
cmdline_sel_chat_id: Arc::new(RwLock::new(0)),
|
||||||
inbox_thread: Arc::new(RwLock::new(JobThread::new(
|
|
||||||
"INBOX",
|
|
||||||
"configured_inbox_folder",
|
|
||||||
Imap::new(),
|
|
||||||
))),
|
|
||||||
sentbox_thread: Arc::new(RwLock::new(JobThread::new(
|
sentbox_thread: Arc::new(RwLock::new(JobThread::new(
|
||||||
"SENTBOX",
|
"SENTBOX",
|
||||||
"configured_sentbox_folder",
|
"configured_sentbox_folder",
|
||||||
@@ -162,12 +153,10 @@ impl Context {
|
|||||||
Ok(ctx)
|
Ok(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns database file path.
|
|
||||||
pub fn get_dbfile(&self) -> &Path {
|
pub fn get_dbfile(&self) -> &Path {
|
||||||
self.dbfile.as_path()
|
self.dbfile.as_path()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns blob directory path.
|
|
||||||
pub fn get_blobdir(&self) -> &Path {
|
pub fn get_blobdir(&self) -> &Path {
|
||||||
self.blobdir.as_path()
|
self.blobdir.as_path()
|
||||||
}
|
}
|
||||||
@@ -298,11 +287,6 @@ impl Context {
|
|||||||
res.insert("database_version", dbversion.to_string());
|
res.insert("database_version", dbversion.to_string());
|
||||||
res.insert("blobdir", self.get_blobdir().display().to_string());
|
res.insert("blobdir", self.get_blobdir().display().to_string());
|
||||||
res.insert("display_name", displayname.unwrap_or_else(|| unset.into()));
|
res.insert("display_name", displayname.unwrap_or_else(|| unset.into()));
|
||||||
res.insert(
|
|
||||||
"selfavatar",
|
|
||||||
self.get_config(Config::Selfavatar)
|
|
||||||
.unwrap_or_else(|| "<unset>".to_string()),
|
|
||||||
);
|
|
||||||
res.insert("is_configured", is_configured.to_string());
|
res.insert("is_configured", is_configured.to_string());
|
||||||
res.insert("entered_account_settings", l.to_string());
|
res.insert("entered_account_settings", l.to_string());
|
||||||
res.insert("used_account_settings", l2.to_string());
|
res.insert("used_account_settings", l2.to_string());
|
||||||
@@ -442,9 +426,10 @@ impl Context {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.is_mvbox(folder) {
|
if !self.is_inbox(folder) && !self.is_sentbox(folder) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(msg) = Message::load_from_db(self, msg_id) {
|
if let Ok(msg) = Message::load_from_db(self, msg_id) {
|
||||||
if msg.is_setupmessage() {
|
if msg.is_setupmessage() {
|
||||||
// do not move setup messages;
|
// do not move setup messages;
|
||||||
@@ -452,6 +437,10 @@ impl Context {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.is_mvbox(folder) {
|
||||||
|
message::update_msg_move_state(self, &msg.rfc724_mid, MoveState::Stay);
|
||||||
|
}
|
||||||
|
|
||||||
// 1 = dc message, 2 = reply to dc message
|
// 1 = dc message, 2 = reply to dc message
|
||||||
if 0 != msg.is_dc_message {
|
if 0 != msg.is_dc_message {
|
||||||
job_add(
|
job_add(
|
||||||
@@ -461,6 +450,7 @@ impl Context {
|
|||||||
Params::new(),
|
Params::new(),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
|
message::update_msg_move_state(self, &msg.rfc724_mid, MoveState::Moving);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -468,8 +458,8 @@ impl Context {
|
|||||||
|
|
||||||
impl Drop for Context {
|
impl Drop for Context {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
info!(self, "disconnecting inbox-thread",);
|
info!(self, "disconnecting INBOX-watch",);
|
||||||
self.inbox_thread.read().unwrap().imap.disconnect(self);
|
self.inbox.read().unwrap().disconnect(self);
|
||||||
info!(self, "disconnecting sentbox-thread",);
|
info!(self, "disconnecting sentbox-thread",);
|
||||||
self.sentbox_thread.read().unwrap().imap.disconnect(self);
|
self.sentbox_thread.read().unwrap().imap.disconnect(self);
|
||||||
info!(self, "disconnecting mvbox-thread",);
|
info!(self, "disconnecting mvbox-thread",);
|
||||||
@@ -496,25 +486,12 @@ pub struct BobStatus {
|
|||||||
pub qr_scan: Option<Lot>,
|
pub qr_scan: Option<Lot>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum PerformJobsNeeded {
|
|
||||||
Not,
|
|
||||||
AtOnce,
|
|
||||||
AvoidDos,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PerformJobsNeeded {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Not
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct SmtpState {
|
pub struct SmtpState {
|
||||||
pub idle: bool,
|
pub idle: bool,
|
||||||
pub suspended: bool,
|
pub suspended: bool,
|
||||||
pub doing_jobs: bool,
|
pub doing_jobs: bool,
|
||||||
pub perform_jobs_needed: PerformJobsNeeded,
|
pub perform_jobs_needed: i32,
|
||||||
pub probe_network: bool,
|
pub probe_network: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
//! De-HTML
|
|
||||||
//!
|
|
||||||
//! A module to remove HTML tags from the email text
|
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use quick_xml;
|
use quick_xml;
|
||||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||||
@@ -23,18 +19,22 @@ enum AddText {
|
|||||||
YesPreserveLineEnds,
|
YesPreserveLineEnds,
|
||||||
}
|
}
|
||||||
|
|
||||||
// dehtml() returns way too many newlines; however, an optimisation on this issue is not needed as
|
// dc_dehtml() returns way too many lineends; however, an optimisation on this issue is not needed as
|
||||||
// the newlines are typically removed in further processing by the caller
|
// the lineends are typically remove in further processing by the caller
|
||||||
pub fn dehtml(buf: &str) -> String {
|
pub fn dc_dehtml(buf_terminated: &str) -> String {
|
||||||
let buf = buf.trim();
|
let buf_terminated = buf_terminated.trim();
|
||||||
|
|
||||||
|
if buf_terminated.is_empty() {
|
||||||
|
return "".into();
|
||||||
|
}
|
||||||
|
|
||||||
let mut dehtml = Dehtml {
|
let mut dehtml = Dehtml {
|
||||||
strbuilder: String::with_capacity(buf.len()),
|
strbuilder: String::with_capacity(buf_terminated.len()),
|
||||||
add_text: AddText::YesRemoveLineEnds,
|
add_text: AddText::YesRemoveLineEnds,
|
||||||
last_href: None,
|
last_href: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut reader = quick_xml::Reader::from_str(buf);
|
let mut reader = quick_xml::Reader::from_str(buf_terminated);
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
|
|
||||||
@@ -171,7 +171,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_dehtml() {
|
fn test_dc_dehtml() {
|
||||||
let cases = vec![
|
let cases = vec![
|
||||||
(
|
(
|
||||||
"<a href='https://example.com'> Foo </a>",
|
"<a href='https://example.com'> Foo </a>",
|
||||||
@@ -186,7 +186,7 @@ mod tests {
|
|||||||
("", ""),
|
("", ""),
|
||||||
];
|
];
|
||||||
for (input, output) in cases {
|
for (input, output) in cases {
|
||||||
assert_eq!(dehtml(input), output);
|
assert_eq!(dc_dehtml(input), output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1312
src/dc_mimeparser.rs
Normal file
@@ -1,169 +1,175 @@
|
|||||||
use crate::dehtml::*;
|
use crate::dc_dehtml::*;
|
||||||
|
|
||||||
/// Remove standard (RFC 3676, §4.3) footer if it is found.
|
#[derive(Copy, Clone)]
|
||||||
fn remove_message_footer<'a>(lines: &'a [&str]) -> &'a [&'a str] {
|
pub struct Simplify {
|
||||||
|
pub is_forwarded: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return index of footer line in vector of message lines, or vector length if
|
||||||
|
/// no footer is found.
|
||||||
|
///
|
||||||
|
/// 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, &line) in lines.iter().enumerate() {
|
||||||
// quoted-printable may encode `-- ` to `-- =20` which is converted
|
// quoted-printable may encode `-- ` to `-- =20` which is converted
|
||||||
// back to `-- `
|
// back to `-- `
|
||||||
match line {
|
match line {
|
||||||
"-- " | "-- " => return &lines[..ix],
|
"-- " | "-- " => return (ix, false),
|
||||||
|
"--" | "---" | "----" => return (ix, true),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lines
|
(lines.len(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove nonstandard footer and a boolean indicating whether such
|
impl Simplify {
|
||||||
/// footer was removed.
|
pub fn new() -> Self {
|
||||||
fn remove_nonstandard_footer<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
|
Simplify {
|
||||||
for (ix, &line) in lines.iter().enumerate() {
|
is_forwarded: false,
|
||||||
if line == "--"
|
|
||||||
|| line == "---"
|
|
||||||
|| line == "----"
|
|
||||||
|| line.starts_with("-----")
|
|
||||||
|| line.starts_with("_____")
|
|
||||||
|| line.starts_with("=====")
|
|
||||||
|| line.starts_with("*****")
|
|
||||||
|| line.starts_with("~~~~~")
|
|
||||||
{
|
|
||||||
return (&lines[..ix], true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(lines, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn split_lines(buf: &str) -> Vec<&str> {
|
/// Simplify and normalise text: Remove quotes, signatures, unnecessary
|
||||||
buf.split('\n').collect()
|
/// lineends etc.
|
||||||
}
|
/// The data returned from simplify() must be free()'d when no longer used.
|
||||||
|
pub fn simplify(&mut self, input: &str, is_html: bool, is_msgrmsg: bool) -> String {
|
||||||
|
let mut out = if is_html {
|
||||||
|
dc_dehtml(input)
|
||||||
|
} else {
|
||||||
|
input.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
/// Simplify message text for chat display.
|
out.retain(|c| c != '\r');
|
||||||
/// Remove quotes, signatures, trailing empty lines etc.
|
out = self.simplify_plain_text(&out, is_msgrmsg);
|
||||||
pub fn simplify(input: &str, is_html: bool, is_chat_message: bool) -> (String, bool) {
|
out.retain(|c| c != '\r');
|
||||||
let mut out = if is_html {
|
|
||||||
dehtml(input)
|
|
||||||
} else {
|
|
||||||
input.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
out.retain(|c| c != '\r');
|
out
|
||||||
let lines = split_lines(&out);
|
|
||||||
let (lines, is_forwarded) = skip_forward_header(&lines);
|
|
||||||
|
|
||||||
let lines = remove_message_footer(lines);
|
|
||||||
let (lines, has_nonstandard_footer) = remove_nonstandard_footer(lines);
|
|
||||||
let (lines, has_bottom_quote) = if !is_chat_message {
|
|
||||||
remove_bottom_quote(lines)
|
|
||||||
} else {
|
|
||||||
(lines, false)
|
|
||||||
};
|
|
||||||
let (lines, has_top_quote) = if !is_chat_message {
|
|
||||||
remove_top_quote(lines)
|
|
||||||
} else {
|
|
||||||
(lines, false)
|
|
||||||
};
|
|
||||||
|
|
||||||
// re-create buffer from the remaining lines
|
|
||||||
let text = render_message(
|
|
||||||
lines,
|
|
||||||
has_top_quote,
|
|
||||||
has_nonstandard_footer || has_bottom_quote,
|
|
||||||
);
|
|
||||||
(text, is_forwarded)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Skips "forwarded message" header.
|
|
||||||
/// Returns message body lines and a boolean indicating whether
|
|
||||||
/// a message is forwarded or not.
|
|
||||||
fn skip_forward_header<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
|
|
||||||
if lines.len() >= 3
|
|
||||||
&& lines[0] == "---------- Forwarded message ----------"
|
|
||||||
&& lines[1].starts_with("From: ")
|
|
||||||
&& lines[2].is_empty()
|
|
||||||
{
|
|
||||||
(&lines[3..], true)
|
|
||||||
} else {
|
|
||||||
(lines, false)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_bottom_quote<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
|
/**
|
||||||
let mut last_quoted_line = None;
|
* Simplify Plain Text
|
||||||
for (l, line) in lines.iter().enumerate().rev() {
|
*/
|
||||||
if is_plain_quote(line) {
|
#[allow(non_snake_case)]
|
||||||
last_quoted_line = Some(l)
|
fn simplify_plain_text(&mut self, buf_terminated: &str, is_msgrmsg: bool) -> String {
|
||||||
} else if !is_empty_line(line) {
|
/* This function ...
|
||||||
break;
|
... removes all text after the line `-- ` (footer mark)
|
||||||
}
|
... removes full quotes at the beginning and at the end of the text -
|
||||||
}
|
these are all lines starting with the character `>`
|
||||||
if let Some(mut l_last) = last_quoted_line {
|
... remove a non-empty line before the removed quote (contains sth. like "On 2.9.2016, Bjoern wrote:" in different formats and lanugages) */
|
||||||
if l_last > 1 && is_empty_line(lines[l_last - 1]) {
|
/* split the given buffer into lines */
|
||||||
l_last -= 1
|
let lines: Vec<_> = buf_terminated.split('\n').collect();
|
||||||
}
|
let mut l_first: usize = 0;
|
||||||
if l_last > 1 {
|
let mut is_cut_at_begin = false;
|
||||||
let line = lines[l_last - 1];
|
let (mut l_last, mut is_cut_at_end) = find_message_footer(&lines);
|
||||||
if is_quoted_headline(line) {
|
|
||||||
l_last -= 1
|
if l_last > l_first + 2 {
|
||||||
|
let line0 = lines[l_first];
|
||||||
|
let line1 = lines[l_first + 1];
|
||||||
|
let line2 = lines[l_first + 2];
|
||||||
|
if line0 == "---------- Forwarded message ----------"
|
||||||
|
&& line1.starts_with("From: ")
|
||||||
|
&& line2.is_empty()
|
||||||
|
{
|
||||||
|
self.is_forwarded = true;
|
||||||
|
l_first += 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(&lines[..l_last], true)
|
for l in l_first..l_last {
|
||||||
} else {
|
let line = lines[l];
|
||||||
(lines, false)
|
if line == "-----"
|
||||||
}
|
|| line == "_____"
|
||||||
}
|
|| line == "====="
|
||||||
|
|| line == "*****"
|
||||||
fn remove_top_quote<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
|
|| line == "~~~~~"
|
||||||
let mut last_quoted_line = None;
|
{
|
||||||
let mut has_quoted_headline = false;
|
l_last = l;
|
||||||
for (l, line) in lines.iter().enumerate() {
|
is_cut_at_end = true;
|
||||||
if is_plain_quote(line) {
|
/* done */
|
||||||
last_quoted_line = Some(l)
|
|
||||||
} else if !is_empty_line(line) {
|
|
||||||
if is_quoted_headline(line) && !has_quoted_headline && last_quoted_line.is_none() {
|
|
||||||
has_quoted_headline = true
|
|
||||||
} else {
|
|
||||||
/* non-quoting line found */
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if !is_msgrmsg {
|
||||||
if let Some(last_quoted_line) = last_quoted_line {
|
let mut l_lastQuotedLine = None;
|
||||||
(&lines[last_quoted_line + 1..], true)
|
for l in (l_first..l_last).rev() {
|
||||||
} else {
|
let line = lines[l];
|
||||||
(lines, false)
|
if is_plain_quote(line) {
|
||||||
}
|
l_lastQuotedLine = Some(l)
|
||||||
}
|
} else if !is_empty_line(line) {
|
||||||
|
break;
|
||||||
fn render_message(lines: &[&str], is_cut_at_begin: bool, is_cut_at_end: bool) -> String {
|
}
|
||||||
let mut ret = String::new();
|
}
|
||||||
if is_cut_at_begin {
|
if let Some(last_quoted_line) = l_lastQuotedLine {
|
||||||
ret += "[...]";
|
l_last = last_quoted_line;
|
||||||
}
|
is_cut_at_end = true;
|
||||||
/* we write empty lines only in case and non-empty line follows */
|
if l_last > 1 && is_empty_line(lines[l_last - 1]) {
|
||||||
let mut pending_linebreaks = 0;
|
l_last -= 1
|
||||||
let mut empty_body = true;
|
}
|
||||||
for line in lines {
|
if l_last > 1 {
|
||||||
if is_empty_line(line) {
|
let line = lines[l_last - 1];
|
||||||
pending_linebreaks += 1
|
if is_quoted_headline(line) {
|
||||||
} else {
|
l_last -= 1
|
||||||
if !empty_body {
|
}
|
||||||
if pending_linebreaks > 2 {
|
|
||||||
pending_linebreaks = 2
|
|
||||||
}
|
|
||||||
while 0 != pending_linebreaks {
|
|
||||||
ret += "\n";
|
|
||||||
pending_linebreaks -= 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// the incoming message might contain invalid UTF8
|
|
||||||
ret += line;
|
|
||||||
empty_body = false;
|
|
||||||
pending_linebreaks = 1
|
|
||||||
}
|
}
|
||||||
|
if !is_msgrmsg {
|
||||||
|
let mut l_lastQuotedLine_0 = None;
|
||||||
|
let mut hasQuotedHeadline = 0;
|
||||||
|
for l in l_first..l_last {
|
||||||
|
let line = lines[l];
|
||||||
|
if is_plain_quote(line) {
|
||||||
|
l_lastQuotedLine_0 = Some(l)
|
||||||
|
} else if !is_empty_line(line) {
|
||||||
|
if is_quoted_headline(line)
|
||||||
|
&& 0 == hasQuotedHeadline
|
||||||
|
&& l_lastQuotedLine_0.is_none()
|
||||||
|
{
|
||||||
|
hasQuotedHeadline = 1i32
|
||||||
|
} else {
|
||||||
|
/* non-quoting line found */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(last_quoted_line) = l_lastQuotedLine_0 {
|
||||||
|
l_first = last_quoted_line + 1;
|
||||||
|
is_cut_at_begin = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* re-create buffer from the remaining lines */
|
||||||
|
let mut ret = String::new();
|
||||||
|
if is_cut_at_begin {
|
||||||
|
ret += "[...]";
|
||||||
|
}
|
||||||
|
/* we write empty lines only in case and non-empty line follows */
|
||||||
|
let mut pending_linebreaks = 0;
|
||||||
|
let mut content_lines_added = 0;
|
||||||
|
for l in l_first..l_last {
|
||||||
|
let line = lines[l];
|
||||||
|
if is_empty_line(line) {
|
||||||
|
pending_linebreaks += 1
|
||||||
|
} else {
|
||||||
|
if 0 != content_lines_added {
|
||||||
|
if pending_linebreaks > 2i32 {
|
||||||
|
pending_linebreaks = 2i32
|
||||||
|
}
|
||||||
|
while 0 != pending_linebreaks {
|
||||||
|
ret += "\n";
|
||||||
|
pending_linebreaks -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// the incoming message might contain invalid UTF8
|
||||||
|
ret += line;
|
||||||
|
content_lines_added += 1;
|
||||||
|
pending_linebreaks = 1i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if is_cut_at_end && (!is_cut_at_begin || 0 != content_lines_added) {
|
||||||
|
ret += " [...]";
|
||||||
|
}
|
||||||
|
|
||||||
|
ret
|
||||||
}
|
}
|
||||||
if is_cut_at_end && (!is_cut_at_begin || !empty_body) {
|
|
||||||
ret += " [...]";
|
|
||||||
}
|
|
||||||
ret
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -207,59 +213,50 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
// proptest does not support [[:graphical:][:space:]] regex.
|
// proptest does not support [[:graphical:][:space:]] regex.
|
||||||
fn test_simplify_plain_text_fuzzy(input in "[!-~\t \n]+") {
|
fn test_simplify_plain_text_fuzzy(input in "[!-~\t \n]+") {
|
||||||
let (output, _is_forwarded) = simplify(&input, false, true);
|
let output = Simplify::new().simplify_plain_text(&input, true);
|
||||||
assert!(output.split('\n').all(|s| s != "-- "));
|
assert!(output.split('\n').all(|s| s != "-- "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simplify_trim() {
|
fn test_simplify_trim() {
|
||||||
|
let mut simplify = Simplify::new();
|
||||||
let html = "\r\r\nline1<br>\r\n\r\n\r\rline2\n\r";
|
let html = "\r\r\nline1<br>\r\n\r\n\r\rline2\n\r";
|
||||||
let (plain, is_forwarded) = simplify(html, true, false);
|
let plain = simplify.simplify(html, true, false);
|
||||||
|
|
||||||
assert_eq!(plain, "line1\nline2");
|
assert_eq!(plain, "line1\nline2");
|
||||||
assert!(!is_forwarded);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simplify_parse_href() {
|
fn test_simplify_parse_href() {
|
||||||
|
let mut simplify = Simplify::new();
|
||||||
let html = "<a href=url>text</a";
|
let html = "<a href=url>text</a";
|
||||||
let (plain, is_forwarded) = simplify(html, true, false);
|
let plain = simplify.simplify(html, true, false);
|
||||||
|
|
||||||
assert_eq!(plain, "[text](url)");
|
assert_eq!(plain, "[text](url)");
|
||||||
assert!(!is_forwarded);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simplify_bold_text() {
|
fn test_simplify_bold_text() {
|
||||||
|
let mut simplify = Simplify::new();
|
||||||
let html = "<!DOCTYPE name [<!DOCTYPE ...>]><!-- comment -->text <b><?php echo ... ?>bold</b><![CDATA[<>]]>";
|
let html = "<!DOCTYPE name [<!DOCTYPE ...>]><!-- comment -->text <b><?php echo ... ?>bold</b><![CDATA[<>]]>";
|
||||||
let (plain, is_forwarded) = simplify(html, true, false);
|
let plain = simplify.simplify(html, true, false);
|
||||||
|
|
||||||
assert_eq!(plain, "text *bold*<>");
|
assert_eq!(plain, "text *bold*<>");
|
||||||
assert!(!is_forwarded);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_simplify_forwarded_message() {
|
|
||||||
let text = "---------- Forwarded message ----------\r\nFrom: test@example.com\r\n\r\nForwarded message\r\n-- \r\nSignature goes here";
|
|
||||||
let (plain, is_forwarded) = simplify(text, false, false);
|
|
||||||
|
|
||||||
assert_eq!(plain, "Forwarded message");
|
|
||||||
assert!(is_forwarded);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simplify_html_encoded() {
|
fn test_simplify_html_encoded() {
|
||||||
|
let mut simplify = Simplify::new();
|
||||||
let html =
|
let html =
|
||||||
"<>"'& äÄöÖüÜß fooÆçÇ ♦‎‏‌&noent;‍";
|
"<>"'& äÄöÖüÜß fooÆçÇ ♦‎‏‌&noent;‍";
|
||||||
|
|
||||||
let (plain, is_forwarded) = simplify(html, true, false);
|
let plain = simplify.simplify(html, true, false);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
plain,
|
plain,
|
||||||
"<>\"\'& äÄöÖüÜß fooÆçÇ \u{2666}\u{200e}\u{200f}\u{200c}&noent;\u{200d}"
|
"<>\"\'& äÄöÖüÜß fooÆçÇ \u{2666}\u{200e}\u{200f}\u{200c}&noent;\u{200d}"
|
||||||
);
|
);
|
||||||
assert!(!is_forwarded);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -273,19 +270,4 @@ mod tests {
|
|||||||
assert!(!is_plain_quote("Life is pain"));
|
assert!(!is_plain_quote("Life is pain"));
|
||||||
assert!(!is_plain_quote(""));
|
assert!(!is_plain_quote(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_remove_top_quote() {
|
|
||||||
let (lines, has_top_quote) = remove_top_quote(&["> first", "> second"]);
|
|
||||||
assert!(lines.is_empty());
|
|
||||||
assert!(has_top_quote);
|
|
||||||
|
|
||||||
let (lines, has_top_quote) = remove_top_quote(&["> first", "> second", "not a quote"]);
|
|
||||||
assert_eq!(lines, &["not a quote"]);
|
|
||||||
assert!(has_top_quote);
|
|
||||||
|
|
||||||
let (lines, has_top_quote) = remove_top_quote(&["not a quote", "> first", "> second"]);
|
|
||||||
assert_eq!(lines, &["not a quote", "> first", "> second"]);
|
|
||||||
assert!(!has_top_quote);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
251
src/dc_strencode.rs
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
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::other::*;
|
||||||
|
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
|
||||||
|
|
||||||
|
use crate::dc_tools::*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode non-ascii-strings as `=?UTF-8?Q?Bj=c3=b6rn_Petersen?=`.
|
||||||
|
* Belongs to RFC 2047: https://tools.ietf.org/html/rfc2047
|
||||||
|
*
|
||||||
|
* We do not fold at position 72; this would result in empty words as `=?utf-8?Q??=` which are correct,
|
||||||
|
* but cannot be displayed by some mail programs (eg. Android Stock Mail).
|
||||||
|
* however, this is not needed, as long as _one_ word is not longer than 72 characters.
|
||||||
|
* _if_ it is, the display may get weird. This affects the subject only.
|
||||||
|
* the best solution wor all this would be if libetpan encodes the line as only libetpan knowns when a header line is full.
|
||||||
|
*
|
||||||
|
* @param to_encode Null-terminated UTF-8-string to encode.
|
||||||
|
* @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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
} else {
|
||||||
|
result.push(byte as _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if encoded {
|
||||||
|
result = format!("=?utf-8?Q?{}?=", &result);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ******************************************************************************
|
||||||
|
* Encode/decode header words, RFC 2047
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
pub(crate) fn dc_decode_header_words(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();
|
||||||
|
let mut cur_token = 0;
|
||||||
|
let input_c = CString::yolo(input);
|
||||||
|
unsafe {
|
||||||
|
let r = mailmime_encoded_phrase_parse(
|
||||||
|
FROM_ENCODING.as_ptr().cast(),
|
||||||
|
input_c.as_ptr(),
|
||||||
|
input.len(),
|
||||||
|
&mut cur_token,
|
||||||
|
TO_ENCODING.as_ptr().cast(),
|
||||||
|
&mut out,
|
||||||
|
);
|
||||||
|
if r as u32 != MAILIMF_NO_ERROR || out.is_null() {
|
||||||
|
input.to_string()
|
||||||
|
} else {
|
||||||
|
let res = to_string_lossy(out);
|
||||||
|
free(out.cast());
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dc_needs_ext_header(to_check: impl AsRef<str>) -> bool {
|
||||||
|
let to_check = to_check.as_ref();
|
||||||
|
|
||||||
|
if to_check.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
to_check.chars().any(|c| {
|
||||||
|
!(c.is_ascii_alphanumeric()
|
||||||
|
|| c == '-'
|
||||||
|
|| c == '_'
|
||||||
|
|| c == '_'
|
||||||
|
|| c == '.'
|
||||||
|
|| c == '~'
|
||||||
|
|| c == '%')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const EXT_ASCII_ST: &AsciiSet = &CONTROLS
|
||||||
|
.add(b' ')
|
||||||
|
.add(b'-')
|
||||||
|
.add(b'_')
|
||||||
|
.add(b'.')
|
||||||
|
.add(b'~')
|
||||||
|
.add(b'%');
|
||||||
|
|
||||||
|
/// Encode an UTF-8 string to the extended header format.
|
||||||
|
pub fn dc_encode_ext_header(to_encode: impl AsRef<str>) -> String {
|
||||||
|
let encoded = utf8_percent_encode(to_encode.as_ref(), &EXT_ASCII_ST);
|
||||||
|
format!("utf-8''{}", encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode an extended-header-format strings to UTF-8.
|
||||||
|
pub fn dc_decode_ext_header(to_decode: &[u8]) -> Cow<str> {
|
||||||
|
if let Some(index) = bytes!(b'\'').find(to_decode) {
|
||||||
|
let (charset, rest) = to_decode.split_at(index);
|
||||||
|
if !charset.is_empty() {
|
||||||
|
// skip language
|
||||||
|
if let Some(index2) = bytes!(b'\'').find(&rest[1..]) {
|
||||||
|
let decoded = percent_decode(&rest[index2 + 2..]);
|
||||||
|
|
||||||
|
if charset != b"utf-8" && charset != b"UTF-8" {
|
||||||
|
if let Some(encoding) = Charset::for_label(charset) {
|
||||||
|
let bytes = decoded.collect::<Vec<u8>>();
|
||||||
|
let (res, _, _) = encoding.decode(&bytes);
|
||||||
|
return Cow::Owned(res.into_owned());
|
||||||
|
} else {
|
||||||
|
return decoded.decode_utf8_lossy();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return decoded.decode_utf8_lossy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String::from_utf8_lossy(to_decode)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dc_encode_ext_header() {
|
||||||
|
let buf1 = dc_encode_ext_header("Björn Petersen");
|
||||||
|
assert_eq!(&buf1, "utf-8\'\'Bj%C3%B6rn%20Petersen");
|
||||||
|
let buf2 = dc_decode_ext_header(buf1.as_bytes());
|
||||||
|
assert_eq!(&buf2, "Björn Petersen",);
|
||||||
|
|
||||||
|
let buf1 = dc_decode_ext_header(b"iso-8859-1\'en\'%A3%20rates");
|
||||||
|
assert_eq!(buf1, "£ rates",);
|
||||||
|
|
||||||
|
let buf1 = dc_decode_ext_header(b"wrong\'format");
|
||||||
|
assert_eq!(buf1, "wrong\'format",);
|
||||||
|
|
||||||
|
let buf1 = dc_decode_ext_header(b"\'\'");
|
||||||
|
assert_eq!(buf1, "\'\'");
|
||||||
|
|
||||||
|
let buf1 = dc_decode_ext_header(b"x\'\'");
|
||||||
|
assert_eq!(buf1, "");
|
||||||
|
|
||||||
|
let buf1 = dc_decode_ext_header(b"\'");
|
||||||
|
assert_eq!(buf1, "\'");
|
||||||
|
|
||||||
|
let buf1 = dc_decode_ext_header(b"");
|
||||||
|
assert_eq!(buf1, "");
|
||||||
|
|
||||||
|
// regressions
|
||||||
|
assert_eq!(
|
||||||
|
dc_decode_ext_header(dc_encode_ext_header("%0A").as_bytes()),
|
||||||
|
"%0A"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dc_needs_ext_header() {
|
||||||
|
assert_eq!(dc_needs_ext_header("Björn"), true);
|
||||||
|
assert_eq!(dc_needs_ext_header("Bjoern"), false);
|
||||||
|
assert_eq!(dc_needs_ext_header(""), false);
|
||||||
|
assert_eq!(dc_needs_ext_header(" "), true);
|
||||||
|
assert_eq!(dc_needs_ext_header("a b"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
use proptest::prelude::*;
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn test_ext_header_roundtrip(buf: String) {
|
||||||
|
let encoded = dc_encode_ext_header(&buf);
|
||||||
|
let decoded = dc_decode_ext_header(encoded.as_bytes());
|
||||||
|
assert_eq!(buf, decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ext_header_decode_anything(buf: Vec<u8>) {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
705
src/dc_tools.rs
@@ -1,24 +1,56 @@
|
|||||||
//! Some tools and enhancements to the used libraries, there should be
|
//! Some tools and enhancements to the used libraries, there should be
|
||||||
//! no references to Context and other "larger" entities here.
|
//! no references to Context and other "larger" entities here.
|
||||||
|
|
||||||
use core::cmp::{max, min};
|
use core::cmp::max;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::ffi::{CStr, CString};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use std::{fmt, fs};
|
use std::{fmt, fs, ptr};
|
||||||
|
|
||||||
use chrono::{Local, TimeZone};
|
use chrono::{Local, TimeZone};
|
||||||
|
use libc::{memcpy, strlen};
|
||||||
|
use mmime::clist::*;
|
||||||
|
use mmime::mailimf::types::*;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
|
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::events::Event;
|
use crate::events::Event;
|
||||||
|
|
||||||
pub(crate) fn dc_exactly_one_bit_set(v: i32) -> bool {
|
pub(crate) fn dc_exactly_one_bit_set(v: libc::c_int) -> bool {
|
||||||
0 != v && 0 == v & (v - 1)
|
0 != v && 0 == v & (v - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Duplicates a string
|
||||||
|
///
|
||||||
|
/// returns an empty string if NULL is given, never returns NULL (exits on errors)
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use deltachat::dc_tools::{dc_strdup, to_string_lossy};
|
||||||
|
/// unsafe {
|
||||||
|
/// let str_a = b"foobar\x00" as *const u8 as *const libc::c_char;
|
||||||
|
/// let str_a_copy = dc_strdup(str_a);
|
||||||
|
/// assert_eq!(to_string_lossy(str_a_copy), "foobar");
|
||||||
|
/// assert_ne!(str_a, str_a_copy);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
|
||||||
|
let ret: *mut libc::c_char;
|
||||||
|
if !s.is_null() {
|
||||||
|
ret = strdup(s);
|
||||||
|
assert!(!ret.is_null());
|
||||||
|
} else {
|
||||||
|
ret = libc::calloc(1, 1) as *mut libc::c_char;
|
||||||
|
assert!(!ret.is_null());
|
||||||
|
}
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
/// Shortens a string to a specified length and adds "..." or "[...]" to the end of
|
/// Shortens a string to a specified length and adds "..." or "[...]" to the end of
|
||||||
/// the shortened string.
|
/// the shortened string.
|
||||||
pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Cow<str> {
|
pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Cow<str> {
|
||||||
@@ -42,12 +74,36 @@ pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the colors must fulfill some criterions as:
|
pub(crate) fn dc_str_from_clist(list: *const clist, delimiter: &str) -> String {
|
||||||
/// - contrast to black and to white
|
let mut res = String::new();
|
||||||
/// - work as a text-color
|
|
||||||
/// - being noticeable on a typical map
|
if !list.is_null() {
|
||||||
/// - harmonize together while being different enough
|
for rfc724_mid in unsafe { (*list).into_iter() } {
|
||||||
/// (therefore, we cannot just use random rgb colors :)
|
if !res.is_empty() {
|
||||||
|
res += delimiter;
|
||||||
|
}
|
||||||
|
res += &to_string_lossy(rfc724_mid as *const libc::c_char);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn dc_str_to_clist(str: &str, delimiter: &str) -> *mut clist {
|
||||||
|
unsafe {
|
||||||
|
let list: *mut clist = clist_new();
|
||||||
|
for cur in str.split(&delimiter) {
|
||||||
|
clist_insert_after(list, (*list).last, cur.strdup().cast());
|
||||||
|
}
|
||||||
|
list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the colors must fulfill some criterions as:
|
||||||
|
- contrast to black and to white
|
||||||
|
- work as a text-color
|
||||||
|
- being noticeable on a typical map
|
||||||
|
- harmonize together while being different enough
|
||||||
|
(therefore, we cannot just use random rgb colors :) */
|
||||||
const COLORS: [u32; 16] = [
|
const COLORS: [u32; 16] = [
|
||||||
0xe56555, 0xf28c48, 0x8e85ee, 0x76c84d, 0x5bb6cc, 0x549cdd, 0xd25c99, 0xb37800, 0xf23030,
|
0xe56555, 0xf28c48, 0x8e85ee, 0x76c84d, 0x5bb6cc, 0x549cdd, 0xd25c99, 0xb37800, 0xf23030,
|
||||||
0x39b249, 0xbb243b, 0x964078, 0x66874f, 0x308ab9, 0x127ed0, 0xbe450c,
|
0x39b249, 0xbb243b, 0x964078, 0x66874f, 0x308ab9, 0x127ed0, 0xbe450c,
|
||||||
@@ -66,6 +122,33 @@ pub(crate) fn dc_str_to_color(s: impl AsRef<str>) -> u32 {
|
|||||||
COLORS[color_index]
|
COLORS[color_index]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* date/time tools */
|
||||||
|
/* the result is UTC or DC_INVALID_TIMESTAMP */
|
||||||
|
pub(crate) fn dc_timestamp_from_date(date_time: *mut mailimf_date_time) -> i64 {
|
||||||
|
assert!(!date_time.is_null());
|
||||||
|
let dt = unsafe { *date_time };
|
||||||
|
|
||||||
|
let sec = dt.dt_sec;
|
||||||
|
let min = dt.dt_min;
|
||||||
|
let hour = dt.dt_hour;
|
||||||
|
let day = dt.dt_day;
|
||||||
|
let month = dt.dt_month;
|
||||||
|
let year = dt.dt_year;
|
||||||
|
|
||||||
|
let ts = chrono::NaiveDateTime::new(
|
||||||
|
chrono::NaiveDate::from_ymd(year, month as u32, day as u32),
|
||||||
|
chrono::NaiveTime::from_hms(hour as u32, min as u32, sec as u32),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (zone_hour, zone_min) = if dt.dt_zone >= 0 {
|
||||||
|
(dt.dt_zone / 100, dt.dt_zone % 100)
|
||||||
|
} else {
|
||||||
|
(-(-dt.dt_zone / 100), -(-dt.dt_zone % 100))
|
||||||
|
};
|
||||||
|
|
||||||
|
ts.timestamp() - (zone_hour * 3600 + zone_min * 60) as i64
|
||||||
|
}
|
||||||
|
|
||||||
/* ******************************************************************************
|
/* ******************************************************************************
|
||||||
* date/time tools
|
* date/time tools
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
@@ -82,26 +165,11 @@ pub(crate) fn dc_gm2local_offset() -> i64 {
|
|||||||
lt.offset().local_minus_utc() as i64
|
lt.offset().local_minus_utc() as i64
|
||||||
}
|
}
|
||||||
|
|
||||||
// timesmearing
|
/* timesmearing */
|
||||||
// - as e-mails typically only use a second-based-resolution for timestamps,
|
|
||||||
// the order of two mails sent withing one second is unclear.
|
|
||||||
// this is bad eg. when forwarding some messages from a chat -
|
|
||||||
// these messages will appear at the recipient easily out of order.
|
|
||||||
// - we work around this issue by not sending out two mails with the same timestamp.
|
|
||||||
// - for this purpose, in short, we track the last timestamp used in `last_smeared_timestamp`
|
|
||||||
// when another timestamp is needed in the same second, we use `last_smeared_timestamp+1`
|
|
||||||
// - after some moments without messages sent out,
|
|
||||||
// `last_smeared_timestamp` is again in sync with the normal time.
|
|
||||||
// - however, we do not do all this for the far future,
|
|
||||||
// but at max `MAX_SECONDS_TO_LEND_FROM_FUTURE`
|
|
||||||
const MAX_SECONDS_TO_LEND_FROM_FUTURE: i64 = 5;
|
|
||||||
|
|
||||||
// returns the currently smeared timestamp,
|
|
||||||
// may be used to check if call to dc_create_smeared_timestamp() is needed or not.
|
|
||||||
// the returned timestamp MUST NOT be used to be sent out or saved in the database!
|
|
||||||
pub(crate) fn dc_smeared_time(context: &Context) -> i64 {
|
pub(crate) fn dc_smeared_time(context: &Context) -> i64 {
|
||||||
|
/* function returns a corrected time(NULL) */
|
||||||
let mut now = time();
|
let mut now = time();
|
||||||
let ts = *context.last_smeared_timestamp.read().unwrap();
|
let ts = *context.last_smeared_timestamp.clone().read().unwrap();
|
||||||
if ts >= now {
|
if ts >= now {
|
||||||
now = ts + 1;
|
now = ts + 1;
|
||||||
}
|
}
|
||||||
@@ -109,36 +177,32 @@ pub(crate) fn dc_smeared_time(context: &Context) -> i64 {
|
|||||||
now
|
now
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns a timestamp that is guaranteed to be unique.
|
|
||||||
pub(crate) fn dc_create_smeared_timestamp(context: &Context) -> i64 {
|
pub(crate) fn dc_create_smeared_timestamp(context: &Context) -> i64 {
|
||||||
let now = time();
|
let now = time();
|
||||||
let mut ret = now;
|
let mut ret = now;
|
||||||
|
|
||||||
let mut last_smeared_timestamp = context.last_smeared_timestamp.write().unwrap();
|
let ts = *context.last_smeared_timestamp.clone().write().unwrap();
|
||||||
if ret <= *last_smeared_timestamp {
|
if ret <= ts {
|
||||||
ret = *last_smeared_timestamp + 1;
|
ret = ts + 1;
|
||||||
if ret - now > MAX_SECONDS_TO_LEND_FROM_FUTURE {
|
if ret - now > 5 {
|
||||||
ret = now + MAX_SECONDS_TO_LEND_FROM_FUTURE
|
ret = now + 5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*last_smeared_timestamp = ret;
|
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates `count` timestamps that are guaranteed to be unique.
|
|
||||||
// the frist created timestamps is returned directly,
|
|
||||||
// get the other timestamps just by adding 1..count-1
|
|
||||||
pub(crate) fn dc_create_smeared_timestamps(context: &Context, count: usize) -> i64 {
|
pub(crate) fn dc_create_smeared_timestamps(context: &Context, count: usize) -> i64 {
|
||||||
|
/* get a range to timestamps that can be used uniquely */
|
||||||
let now = time();
|
let now = time();
|
||||||
let count = count as i64;
|
let start = now + (if count < 5 { count } else { 5 }) as i64 - count as i64;
|
||||||
let mut start = now + min(count, MAX_SECONDS_TO_LEND_FROM_FUTURE) - count;
|
|
||||||
|
|
||||||
let mut last_smeared_timestamp = context.last_smeared_timestamp.write().unwrap();
|
let ts = *context.last_smeared_timestamp.clone().write().unwrap();
|
||||||
start = max(*last_smeared_timestamp + 1, start);
|
if ts + 1 > start {
|
||||||
|
ts + 1
|
||||||
*last_smeared_timestamp = start + count - 1;
|
} else {
|
||||||
start
|
start
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Message-ID tools */
|
/* Message-ID tools */
|
||||||
@@ -182,6 +246,22 @@ fn encode_66bits_as_base64(v1: u32, v2: u32, fill: u32) -> String {
|
|||||||
String::from_utf8(wrapped_writer).unwrap()
|
String::from_utf8(wrapped_writer).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn dc_create_incoming_rfc724_mid(
|
||||||
|
message_timestamp: i64,
|
||||||
|
contact_id_from: u32,
|
||||||
|
contact_ids_to: &[u32],
|
||||||
|
) -> Option<String> {
|
||||||
|
/* create a deterministic rfc724_mid from input such that
|
||||||
|
repeatedly calling it with the same input results in the same Message-id */
|
||||||
|
|
||||||
|
let largest_id_to = contact_ids_to.iter().max().copied().unwrap_or_default();
|
||||||
|
let result = format!(
|
||||||
|
"{}-{}-{}@stub",
|
||||||
|
message_timestamp, contact_id_from, largest_id_to
|
||||||
|
);
|
||||||
|
Some(result)
|
||||||
|
}
|
||||||
|
|
||||||
/// Function generates a Message-ID that can be used for a new outgoing message.
|
/// Function generates a Message-ID that can be used for a new outgoing message.
|
||||||
/// - this function is called for all outgoing messages.
|
/// - this function is called for all outgoing messages.
|
||||||
/// - the message ID should be globally unique
|
/// - the message ID should be globally unique
|
||||||
@@ -201,11 +281,8 @@ pub(crate) fn dc_create_outgoing_rfc724_mid(grpid: Option<&str>, from_addr: &str
|
|||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `mid` - A string that holds the message id. Leading/Trailing <>
|
/// * `mid` - A string that holds the message id
|
||||||
/// characters are automatically stripped.
|
|
||||||
pub(crate) fn dc_extract_grpid_from_rfc724_mid(mid: &str) -> Option<&str> {
|
pub(crate) fn dc_extract_grpid_from_rfc724_mid(mid: &str) -> Option<&str> {
|
||||||
let mid = mid.trim_start_matches('<').trim_end_matches('>');
|
|
||||||
|
|
||||||
if mid.len() < 9 || !mid.starts_with("Gr.") {
|
if mid.len() < 9 || !mid.starts_with("Gr.") {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -222,6 +299,29 @@ pub(crate) fn dc_extract_grpid_from_rfc724_mid(mid: &str) -> Option<&str> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn dc_extract_grpid_from_rfc724_mid_list(list: *const clist) -> *mut libc::c_char {
|
||||||
|
if !list.is_null() {
|
||||||
|
unsafe {
|
||||||
|
for cur in (*list).into_iter() {
|
||||||
|
let mid = to_string_lossy(cur as *const libc::c_char);
|
||||||
|
|
||||||
|
if let Some(grpid) = dc_extract_grpid_from_rfc724_mid(&mid) {
|
||||||
|
return grpid.strdup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn dc_ensure_no_slash_safe(path: &str) -> &str {
|
||||||
|
if path.ends_with('/') || path.ends_with('\\') {
|
||||||
|
return &path[..path.len() - 1];
|
||||||
|
}
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
// Function returns a sanitized basename that does not contain
|
// Function returns a sanitized basename that does not contain
|
||||||
// win/linux path separators and also not any non-ascii chars
|
// win/linux path separators and also not any non-ascii chars
|
||||||
fn get_safe_basename(filename: &str) -> String {
|
fn get_safe_basename(filename: &str) -> String {
|
||||||
@@ -262,9 +362,11 @@ pub fn dc_derive_safe_stem_ext(filename: &str) -> (String, String) {
|
|||||||
// the returned suffix is lower-case
|
// the returned suffix is lower-case
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn dc_get_filesuffix_lc(path_filename: impl AsRef<str>) -> Option<String> {
|
pub fn dc_get_filesuffix_lc(path_filename: impl AsRef<str>) -> Option<String> {
|
||||||
Path::new(path_filename.as_ref())
|
if let Some(p) = Path::new(path_filename.as_ref()).extension() {
|
||||||
.extension()
|
Some(p.to_string_lossy().to_lowercase())
|
||||||
.map(|p| p.to_string_lossy().to_lowercase())
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `(width, height)` of the given image buffer.
|
/// Returns the `(width, height)` of the given image buffer.
|
||||||
@@ -381,14 +483,11 @@ pub(crate) fn dc_copy_file(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn dc_create_folder(
|
pub(crate) fn dc_create_folder(context: &Context, path: impl AsRef<std::path::Path>) -> bool {
|
||||||
context: &Context,
|
|
||||||
path: impl AsRef<std::path::Path>,
|
|
||||||
) -> Result<(), std::io::Error> {
|
|
||||||
let path_abs = dc_get_abs_path(context, &path);
|
let path_abs = dc_get_abs_path(context, &path);
|
||||||
if !path_abs.exists() {
|
if !path_abs.exists() {
|
||||||
match fs::create_dir_all(path_abs) {
|
match fs::create_dir_all(path_abs) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => true,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!(
|
warn!(
|
||||||
context,
|
context,
|
||||||
@@ -396,22 +495,18 @@ pub(crate) fn dc_create_folder(
|
|||||||
path.as_ref().display(),
|
path.as_ref().display(),
|
||||||
err
|
err
|
||||||
);
|
);
|
||||||
Err(err)
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a the given content to provied file path.
|
/// Write a the given content to provied file path.
|
||||||
pub(crate) fn dc_write_file(
|
pub(crate) fn dc_write_file(context: &Context, path: impl AsRef<Path>, buf: &[u8]) -> bool {
|
||||||
context: &Context,
|
|
||||||
path: impl AsRef<Path>,
|
|
||||||
buf: &[u8],
|
|
||||||
) -> Result<(), std::io::Error> {
|
|
||||||
let path_abs = dc_get_abs_path(context, &path);
|
let path_abs = dc_get_abs_path(context, &path);
|
||||||
fs::write(&path_abs, buf).map_err(|err| {
|
if let Err(err) = fs::write(&path_abs, buf) {
|
||||||
warn!(
|
warn!(
|
||||||
context,
|
context,
|
||||||
"Cannot write {} bytes to \"{}\": {}",
|
"Cannot write {} bytes to \"{}\": {}",
|
||||||
@@ -419,8 +514,10 @@ pub(crate) fn dc_write_file(
|
|||||||
path.as_ref().display(),
|
path.as_ref().display(),
|
||||||
err
|
err
|
||||||
);
|
);
|
||||||
err
|
false
|
||||||
})
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dc_read_file<P: AsRef<std::path::Path>>(
|
pub fn dc_read_file<P: AsRef<std::path::Path>>(
|
||||||
@@ -483,6 +580,212 @@ pub(crate) fn dc_get_next_backup_path(
|
|||||||
bail!("could not create backup file, disk full?");
|
bail!("could not create backup file, disk full?");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Error type for the [OsStrExt] trait
|
||||||
|
#[derive(Debug, Fail, PartialEq)]
|
||||||
|
pub enum CStringError {
|
||||||
|
/// The string contains an interior null byte
|
||||||
|
#[fail(display = "String contains an interior null byte")]
|
||||||
|
InteriorNullByte,
|
||||||
|
/// The string is not valid Unicode
|
||||||
|
#[fail(display = "String is not valid unicode")]
|
||||||
|
NotUnicode,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extra convenience methods on [std::ffi::OsStr] to work with `*libc::c_char`.
|
||||||
|
///
|
||||||
|
/// The primary function of this trait is to more easily convert
|
||||||
|
/// [OsStr], [OsString] or [Path] into pointers to C strings. This always
|
||||||
|
/// allocates a new string since it is very common for the source
|
||||||
|
/// string not to have the required terminal null byte.
|
||||||
|
///
|
||||||
|
/// It is implemented for `AsRef<std::ffi::OsStr>>` trait, which
|
||||||
|
/// allows any type which implements this trait to transparently use
|
||||||
|
/// this. This is how the conversion for [Path] works.
|
||||||
|
///
|
||||||
|
/// [OsStr]: std::ffi::OsStr
|
||||||
|
/// [OsString]: std::ffi::OsString
|
||||||
|
/// [Path]: std::path::Path
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use deltachat::dc_tools::{dc_strdup, OsStrExt};
|
||||||
|
/// let path = std::path::Path::new("/some/path");
|
||||||
|
/// let path_c = path.to_c_string().unwrap();
|
||||||
|
/// unsafe {
|
||||||
|
/// let mut c_ptr: *mut libc::c_char = dc_strdup(path_c.as_ptr());
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub trait OsStrExt {
|
||||||
|
/// Convert a [std::ffi::OsStr] to an [std::ffi::CString]
|
||||||
|
///
|
||||||
|
/// This is useful to convert e.g. a [std::path::Path] to
|
||||||
|
/// [*libc::c_char] by using
|
||||||
|
/// [Path::as_os_str()](std::path::Path::as_os_str) and
|
||||||
|
/// [CStr::as_ptr()](std::ffi::CStr::as_ptr).
|
||||||
|
///
|
||||||
|
/// This returns [CString] and not [&CStr] because not all [OsStr]
|
||||||
|
/// slices end with a null byte, particularly those coming from
|
||||||
|
/// [Path] do not have a null byte and having to handle this as
|
||||||
|
/// the caller would defeat the point of this function.
|
||||||
|
///
|
||||||
|
/// On Windows this requires that the [OsStr] contains valid
|
||||||
|
/// unicode, which should normally be the case for a [Path].
|
||||||
|
///
|
||||||
|
/// [CString]: std::ffi::CString
|
||||||
|
/// [CStr]: std::ffi::CStr
|
||||||
|
/// [OsStr]: std::ffi::OsStr
|
||||||
|
/// [Path]: std::path::Path
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Since a C `*char` is terminated by a NULL byte this conversion
|
||||||
|
/// will fail, when the [OsStr] has an interior null byte. The
|
||||||
|
/// function will return
|
||||||
|
/// `[Err]([CStringError::InteriorNullByte])`. When converting
|
||||||
|
/// from a [Path] it should be safe to
|
||||||
|
/// [`.unwrap()`](std::result::Result::unwrap) this anyway since a
|
||||||
|
/// [Path] should not contain interior null bytes.
|
||||||
|
///
|
||||||
|
/// On windows when the string contains invalid Unicode
|
||||||
|
/// `[Err]([CStringError::NotUnicode])` is returned.
|
||||||
|
fn to_c_string(&self) -> Result<CString, CStringError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<std::ffi::OsStr>> OsStrExt for T {
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
fn to_c_string(&self) -> Result<CString, CStringError> {
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
CString::new(self.as_ref().as_bytes()).map_err(|err| match err {
|
||||||
|
std::ffi::NulError { .. } => CStringError::InteriorNullByte,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn to_c_string(&self) -> Result<CString, CStringError> {
|
||||||
|
os_str_to_c_string_unicode(&self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementation for os_str_to_c_string on windows.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn os_str_to_c_string_unicode(
|
||||||
|
os_str: &dyn AsRef<std::ffi::OsStr>,
|
||||||
|
) -> Result<CString, CStringError> {
|
||||||
|
match os_str.as_ref().to_str() {
|
||||||
|
Some(val) => CString::new(val.as_bytes()).map_err(|err| match err {
|
||||||
|
std::ffi::NulError { .. } => CStringError::InteriorNullByte,
|
||||||
|
}),
|
||||||
|
None => Err(CStringError::NotUnicode),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience methods/associated functions for working with [CString]
|
||||||
|
///
|
||||||
|
/// This is helps transitioning from unsafe code.
|
||||||
|
pub trait CStringExt {
|
||||||
|
/// Create a new [CString], yolo style
|
||||||
|
///
|
||||||
|
/// This unwrap the result, panicking when there are embedded NULL
|
||||||
|
/// bytes.
|
||||||
|
fn yolo<T: Into<Vec<u8>>>(t: T) -> CString {
|
||||||
|
CString::new(t).expect("String contains null byte, can not be CString")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CStringExt for CString {}
|
||||||
|
|
||||||
|
/// Convenience methods to make transitioning from raw C strings easier.
|
||||||
|
///
|
||||||
|
/// To interact with (legacy) C APIs we often need to convert from
|
||||||
|
/// Rust strings to raw C strings. This can be clumsy to do correctly
|
||||||
|
/// and the compiler sometimes allows it in an unsafe way. These
|
||||||
|
/// methods make it more succinct and help you get it right.
|
||||||
|
pub trait StrExt {
|
||||||
|
/// Allocate a new raw C `*char` version of this string.
|
||||||
|
///
|
||||||
|
/// This allocates a new raw C string which must be freed using
|
||||||
|
/// `free`. It takes care of some common pitfalls with using
|
||||||
|
/// [CString.as_ptr].
|
||||||
|
///
|
||||||
|
/// [CString.as_ptr]: std::ffi::CString.as_ptr
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function will panic when the original string contains an
|
||||||
|
/// interior null byte as this can not be represented in raw C
|
||||||
|
/// strings.
|
||||||
|
unsafe fn strdup(&self) -> *mut libc::c_char;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<str>> StrExt for T {
|
||||||
|
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||||
|
let tmp = CString::yolo(self.as_ref());
|
||||||
|
dc_strdup(tmp.as_ptr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_string_lossy(s: *const libc::c_char) -> String {
|
||||||
|
if s.is_null() {
|
||||||
|
return "".into();
|
||||||
|
}
|
||||||
|
|
||||||
|
let cstr = unsafe { CStr::from_ptr(s) };
|
||||||
|
|
||||||
|
cstr.to_string_lossy().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
|
||||||
|
if s.is_null() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(to_string_lossy(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a C `*char` pointer to a [std::path::Path] slice.
|
||||||
|
///
|
||||||
|
/// This converts a `*libc::c_char` pointer to a [Path] slice. This
|
||||||
|
/// essentially has to convert the pointer to [std::ffi::OsStr] to do
|
||||||
|
/// so and thus is the inverse of [OsStrExt::to_c_string]. Just like
|
||||||
|
/// [OsStrExt::to_c_string] requires valid Unicode on Windows, this
|
||||||
|
/// requires that the pointer contains valid UTF-8 on Windows.
|
||||||
|
///
|
||||||
|
/// Because this returns a reference the [Path] silce can not outlive
|
||||||
|
/// the original pointer.
|
||||||
|
///
|
||||||
|
/// [Path]: std::path::Path
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||||
|
assert!(!s.is_null(), "cannot be used on null pointers");
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
unsafe {
|
||||||
|
let c_str = std::ffi::CStr::from_ptr(s).to_bytes();
|
||||||
|
let os_str = std::ffi::OsStr::from_bytes(c_str);
|
||||||
|
std::path::Path::new(os_str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// as_path() implementation for windows, documented above.
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||||
|
as_path_unicode(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementation for as_path() on Windows.
|
||||||
|
//
|
||||||
|
// Having this as a separate function means it can be tested on unix
|
||||||
|
// too.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn as_path_unicode<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||||
|
assert!(!s.is_null(), "cannot be used on null pointers");
|
||||||
|
|
||||||
|
let cstr = unsafe { CStr::from_ptr(s) };
|
||||||
|
let str = cstr.to_str().unwrap_or_else(|err| panic!("{}", err));
|
||||||
|
|
||||||
|
std::path::Path::new(str)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn time() -> i64 {
|
pub(crate) fn time() -> i64 {
|
||||||
SystemTime::now()
|
SystemTime::now()
|
||||||
.duration_since(SystemTime::UNIX_EPOCH)
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
@@ -560,14 +863,71 @@ pub(crate) fn listflags_has(listflags: u32, bitindex: usize) -> bool {
|
|||||||
(listflags & bitindex) == bitindex
|
(listflags & bitindex) == bitindex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) unsafe fn strdup(s: *const libc::c_char) -> *mut libc::c_char {
|
||||||
|
if s.is_null() {
|
||||||
|
return std::ptr::null_mut();
|
||||||
|
}
|
||||||
|
|
||||||
|
let slen = strlen(s);
|
||||||
|
let result = libc::malloc(slen + 1);
|
||||||
|
if result.is_null() {
|
||||||
|
return std::ptr::null_mut();
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(result, s as *const _, slen + 1);
|
||||||
|
result as *mut _
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) unsafe fn strcasecmp(s1: *const libc::c_char, s2: *const libc::c_char) -> libc::c_int {
|
||||||
|
let s1 = std::ffi::CStr::from_ptr(s1)
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_lowercase();
|
||||||
|
let s2 = std::ffi::CStr::from_ptr(s2)
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_lowercase();
|
||||||
|
if s1 == s2 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
use libc::{free, strcmp};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
use std::ffi::CStr;
|
||||||
|
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::test_utils::*;
|
use crate::test_utils::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dc_strdup() {
|
||||||
|
unsafe {
|
||||||
|
let str_a = b"foobar\x00" as *const u8 as *const libc::c_char;
|
||||||
|
let str_a_copy = dc_strdup(str_a);
|
||||||
|
|
||||||
|
// Value of str_a_copy should equal foobar
|
||||||
|
assert_eq!(
|
||||||
|
CStr::from_ptr(str_a_copy),
|
||||||
|
CString::new("foobar").unwrap().as_c_str()
|
||||||
|
);
|
||||||
|
// Address of str_a should be different from str_a_copy
|
||||||
|
assert_ne!(str_a, str_a_copy);
|
||||||
|
|
||||||
|
let str_a = std::ptr::null() as *const libc::c_char;
|
||||||
|
let str_a_copy = dc_strdup(str_a);
|
||||||
|
// Value of str_a_copy should equal ""
|
||||||
|
assert_eq!(
|
||||||
|
CStr::from_ptr(str_a_copy),
|
||||||
|
CString::new("").unwrap().as_c_str()
|
||||||
|
);
|
||||||
|
assert_ne!(str_a, str_a_copy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rust_ftoa() {
|
fn test_rust_ftoa() {
|
||||||
assert_eq!("1.22", format!("{}", 1.22));
|
assert_eq!("1.22", format!("{}", 1.22));
|
||||||
@@ -628,6 +988,44 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* calls free() for each item content */
|
||||||
|
unsafe fn clist_free_content(haystack: *const clist) {
|
||||||
|
let mut iter = (*haystack).first;
|
||||||
|
|
||||||
|
while !iter.is_null() {
|
||||||
|
free((*iter).data);
|
||||||
|
(*iter).data = ptr::null_mut();
|
||||||
|
iter = if !iter.is_null() {
|
||||||
|
(*iter).next
|
||||||
|
} else {
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dc_str_to_clist_1() {
|
||||||
|
unsafe {
|
||||||
|
let list = dc_str_to_clist("", " ");
|
||||||
|
assert_eq!((*list).count, 1);
|
||||||
|
clist_free_content(list);
|
||||||
|
clist_free(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dc_str_to_clist_4() {
|
||||||
|
unsafe {
|
||||||
|
let list: *mut clist = dc_str_to_clist("foo bar test", " ");
|
||||||
|
assert_eq!((*list).count, 3);
|
||||||
|
let str = dc_str_from_clist(list, " ");
|
||||||
|
assert_eq!(str, "foo bar test");
|
||||||
|
|
||||||
|
clist_free_content(list);
|
||||||
|
clist_free(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_dc_create_id() {
|
fn test_dc_create_id() {
|
||||||
let buf = dc_create_id();
|
let buf = dc_create_id();
|
||||||
@@ -655,6 +1053,113 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
fn test_os_str_to_c_string_cwd() {
|
||||||
|
let some_dir = std::env::current_dir().unwrap();
|
||||||
|
some_dir.as_os_str().to_c_string().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_os_str_to_c_string_unicode() {
|
||||||
|
let some_str = String::from("/some/valid/utf8");
|
||||||
|
let some_dir = std::path::Path::new(&some_str);
|
||||||
|
assert_eq!(
|
||||||
|
some_dir.as_os_str().to_c_string().unwrap(),
|
||||||
|
CString::new("/some/valid/utf8").unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_os_str_to_c_string_nul() {
|
||||||
|
let some_str = std::ffi::OsString::from("foo\x00bar");
|
||||||
|
assert_eq!(
|
||||||
|
some_str.to_c_string().err().unwrap(),
|
||||||
|
CStringError::InteriorNullByte
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_path_to_c_string_cwd() {
|
||||||
|
let some_dir = std::env::current_dir().unwrap();
|
||||||
|
some_dir.to_c_string().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_path_to_c_string_unicode() {
|
||||||
|
let some_str = String::from("/some/valid/utf8");
|
||||||
|
let some_dir = std::path::Path::new(&some_str);
|
||||||
|
assert_eq!(
|
||||||
|
some_dir.as_os_str().to_c_string().unwrap(),
|
||||||
|
CString::new("/some/valid/utf8").unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_os_str_to_c_string_unicode_fn() {
|
||||||
|
let some_str = std::ffi::OsString::from("foo");
|
||||||
|
assert_eq!(
|
||||||
|
os_str_to_c_string_unicode(&some_str).unwrap(),
|
||||||
|
CString::new("foo").unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_path_to_c_string_unicode_fn() {
|
||||||
|
let some_str = String::from("/some/path");
|
||||||
|
let some_path = std::path::Path::new(&some_str);
|
||||||
|
assert_eq!(
|
||||||
|
os_str_to_c_string_unicode(&some_path).unwrap(),
|
||||||
|
CString::new("/some/path").unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_os_str_to_c_string_unicode_fn_nul() {
|
||||||
|
let some_str = std::ffi::OsString::from("fooz\x00bar");
|
||||||
|
assert_eq!(
|
||||||
|
os_str_to_c_string_unicode(&some_str).err().unwrap(),
|
||||||
|
CStringError::InteriorNullByte
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_as_path() {
|
||||||
|
let some_path = CString::new("/some/path").unwrap();
|
||||||
|
let ptr = some_path.as_ptr();
|
||||||
|
assert_eq!(as_path(ptr), std::ffi::OsString::from("/some/path"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_as_path_unicode_fn() {
|
||||||
|
let some_path = CString::new("/some/path").unwrap();
|
||||||
|
let ptr = some_path.as_ptr();
|
||||||
|
assert_eq!(as_path_unicode(ptr), std::ffi::OsString::from("/some/path"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cstring_yolo() {
|
||||||
|
assert_eq!(CString::new("hello").unwrap(), CString::yolo("hello"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_strdup_str() {
|
||||||
|
unsafe {
|
||||||
|
let s = "hello".strdup();
|
||||||
|
let cmp = strcmp(s, b"hello\x00" as *const u8 as *const libc::c_char);
|
||||||
|
free(s as *mut libc::c_void);
|
||||||
|
assert_eq!(cmp, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_strdup_string() {
|
||||||
|
unsafe {
|
||||||
|
let s = String::from("hello").strdup();
|
||||||
|
let cmp = strcmp(s, b"hello\x00" as *const u8 as *const libc::c_char);
|
||||||
|
free(s as *mut libc::c_void);
|
||||||
|
assert_eq!(cmp, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_dc_extract_grpid_from_rfc724_mid() {
|
fn test_dc_extract_grpid_from_rfc724_mid() {
|
||||||
// Should return None if we pass invalid mid
|
// Should return None if we pass invalid mid
|
||||||
@@ -676,16 +1181,6 @@ mod tests {
|
|||||||
let mid = "Gr.1234567890123456.morerandom@domain.de";
|
let mid = "Gr.1234567890123456.morerandom@domain.de";
|
||||||
let grpid = dc_extract_grpid_from_rfc724_mid(mid);
|
let grpid = dc_extract_grpid_from_rfc724_mid(mid);
|
||||||
assert_eq!(grpid, Some("1234567890123456"));
|
assert_eq!(grpid, Some("1234567890123456"));
|
||||||
|
|
||||||
// Should return extracted grpid for grpid with length of 11
|
|
||||||
let mid = "<Gr.12345678901.morerandom@domain.de>";
|
|
||||||
let grpid = dc_extract_grpid_from_rfc724_mid(mid);
|
|
||||||
assert_eq!(grpid, Some("12345678901"));
|
|
||||||
|
|
||||||
// Should return extracted grpid for grpid with length of 11
|
|
||||||
let mid = "<Gr.1234567890123456.morerandom@domain.de>";
|
|
||||||
let grpid = dc_extract_grpid_from_rfc724_mid(mid);
|
|
||||||
assert_eq!(grpid, Some("1234567890123456"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -767,6 +1262,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dc_create_incoming_rfc724_mid() {
|
||||||
|
let res = dc_create_incoming_rfc724_mid(123, 45, &vec![6, 7]);
|
||||||
|
assert_eq!(res, Some("123-45-7@stub".into()));
|
||||||
|
let res = dc_create_incoming_rfc724_mid(123, 45, &vec![]);
|
||||||
|
assert_eq!(res, Some("123-45-0@stub".into()));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_file_get_safe_basename() {
|
fn test_file_get_safe_basename() {
|
||||||
assert_eq!(get_safe_basename("12312/hello"), "hello");
|
assert_eq!(get_safe_basename("12312/hello"), "hello");
|
||||||
@@ -801,7 +1304,7 @@ mod tests {
|
|||||||
dc_delete_file(context, "$BLOBDIR/foobar.dadada");
|
dc_delete_file(context, "$BLOBDIR/foobar.dadada");
|
||||||
dc_delete_file(context, "$BLOBDIR/foobar-folder");
|
dc_delete_file(context, "$BLOBDIR/foobar-folder");
|
||||||
}
|
}
|
||||||
assert!(dc_write_file(context, "$BLOBDIR/foobar", b"content").is_ok());
|
assert!(dc_write_file(context, "$BLOBDIR/foobar", b"content"));
|
||||||
assert!(dc_file_exist(context, "$BLOBDIR/foobar",));
|
assert!(dc_file_exist(context, "$BLOBDIR/foobar",));
|
||||||
assert!(!dc_file_exist(context, "$BLOBDIR/foobarx"));
|
assert!(!dc_file_exist(context, "$BLOBDIR/foobarx"));
|
||||||
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/foobar"), 7);
|
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/foobar"), 7);
|
||||||
@@ -828,12 +1331,12 @@ mod tests {
|
|||||||
|
|
||||||
assert!(dc_delete_file(context, "$BLOBDIR/foobar"));
|
assert!(dc_delete_file(context, "$BLOBDIR/foobar"));
|
||||||
assert!(dc_delete_file(context, "$BLOBDIR/dada"));
|
assert!(dc_delete_file(context, "$BLOBDIR/dada"));
|
||||||
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder").is_ok());
|
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder"));
|
||||||
assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",));
|
assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",));
|
||||||
assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder"));
|
assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder"));
|
||||||
|
|
||||||
let fn0 = "$BLOBDIR/data.data";
|
let fn0 = "$BLOBDIR/data.data";
|
||||||
assert!(dc_write_file(context, &fn0, b"content").is_ok());
|
assert!(dc_write_file(context, &fn0, b"content"));
|
||||||
|
|
||||||
assert!(dc_delete_file(context, &fn0));
|
assert!(dc_delete_file(context, &fn0));
|
||||||
assert!(!dc_file_exist(context, &fn0));
|
assert!(!dc_file_exist(context, &fn0));
|
||||||
@@ -842,44 +1345,14 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_listflags_has() {
|
fn test_listflags_has() {
|
||||||
let listflags: u32 = 0x1101;
|
let listflags: u32 = 0x1101;
|
||||||
assert!(listflags_has(listflags, 0x1));
|
assert!(listflags_has(listflags, 0x1) == true);
|
||||||
assert!(!listflags_has(listflags, 0x10));
|
assert!(listflags_has(listflags, 0x10) == false);
|
||||||
assert!(listflags_has(listflags, 0x100));
|
assert!(listflags_has(listflags, 0x100) == true);
|
||||||
assert!(listflags_has(listflags, 0x1000));
|
assert!(listflags_has(listflags, 0x1000) == true);
|
||||||
let listflags: u32 = (DC_GCL_ADD_SELF | DC_GCL_VERIFIED_ONLY).try_into().unwrap();
|
let listflags: u32 = (DC_GCL_ADD_SELF | DC_GCL_VERIFIED_ONLY).try_into().unwrap();
|
||||||
assert!(listflags_has(listflags, DC_GCL_VERIFIED_ONLY));
|
assert!(listflags_has(listflags, DC_GCL_VERIFIED_ONLY) == true);
|
||||||
assert!(listflags_has(listflags, DC_GCL_ADD_SELF));
|
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == true);
|
||||||
let listflags: u32 = DC_GCL_VERIFIED_ONLY.try_into().unwrap();
|
let listflags: u32 = DC_GCL_VERIFIED_ONLY.try_into().unwrap();
|
||||||
assert!(!listflags_has(listflags, DC_GCL_ADD_SELF));
|
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == false);
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_create_smeared_timestamp() {
|
|
||||||
let t = dummy_context();
|
|
||||||
assert_ne!(
|
|
||||||
dc_create_smeared_timestamp(&t.ctx),
|
|
||||||
dc_create_smeared_timestamp(&t.ctx)
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
dc_create_smeared_timestamp(&t.ctx)
|
|
||||||
>= SystemTime::now()
|
|
||||||
.duration_since(SystemTime::UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_secs() as i64
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_create_smeared_timestamps() {
|
|
||||||
let t = dummy_context();
|
|
||||||
let count = MAX_SECONDS_TO_LEND_FROM_FUTURE - 1;
|
|
||||||
let start = dc_create_smeared_timestamps(&t.ctx, count as usize);
|
|
||||||
let next = dc_smeared_time(&t.ctx);
|
|
||||||
assert!((start + count - 1) < next);
|
|
||||||
|
|
||||||
let count = MAX_SECONDS_TO_LEND_FROM_FUTURE + 30;
|
|
||||||
let start = dc_create_smeared_timestamps(&t.ctx, count as usize);
|
|
||||||
let next = dc_smeared_time(&t.ctx);
|
|
||||||
assert!((start + count - 1) < next);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
605
src/e2ee.rs
@@ -1,19 +1,41 @@
|
|||||||
//! End-to-end encryption support.
|
//! End-to-end encryption support.
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::ptr;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use mailparse::{MailHeaderMap, ParsedMail};
|
use libc::strlen;
|
||||||
|
use mmime::clist::*;
|
||||||
|
use mmime::mailimf::types::*;
|
||||||
|
use mmime::mailimf::types_helper::*;
|
||||||
|
use mmime::mailimf::*;
|
||||||
|
use mmime::mailmime::content::*;
|
||||||
|
use mmime::mailmime::types::*;
|
||||||
|
use mmime::mailmime::types_helper::*;
|
||||||
|
use mmime::mailmime::write_mem::*;
|
||||||
|
use mmime::mailmime::*;
|
||||||
|
use mmime::mailprivacy_prepare_mime;
|
||||||
|
use mmime::mmapstring::*;
|
||||||
|
use mmime::{mailmime_substitute, MAILIMF_NO_ERROR, MAIL_NO_ERROR};
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
|
|
||||||
use crate::aheader::*;
|
use crate::aheader::*;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
|
use crate::dc_tools::*;
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use crate::key::*;
|
use crate::key::*;
|
||||||
use crate::keyring::*;
|
use crate::keyring::*;
|
||||||
|
use crate::mimefactory::MimeFactory;
|
||||||
use crate::peerstate::*;
|
use crate::peerstate::*;
|
||||||
use crate::pgp;
|
use crate::pgp;
|
||||||
use crate::securejoin::handle_degrade_event;
|
use crate::securejoin::handle_degrade_event;
|
||||||
|
use crate::wrapmime;
|
||||||
|
use crate::wrapmime::*;
|
||||||
|
|
||||||
|
// standard mime-version header aka b"Version: 1\r\n\x00"
|
||||||
|
static mut VERSION_CONTENT: [libc::c_char; 13] =
|
||||||
|
[86, 101, 114, 115, 105, 111, 110, 58, 32, 49, 13, 10, 0];
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EncryptHelper {
|
pub struct EncryptHelper {
|
||||||
@@ -49,27 +71,39 @@ impl EncryptHelper {
|
|||||||
Aheader::new(addr, pk, self.prefer_encrypt)
|
Aheader::new(addr, pk, self.prefer_encrypt)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines if we can and should encrypt.
|
pub fn try_encrypt(
|
||||||
pub fn should_encrypt(
|
&mut self,
|
||||||
&self,
|
factory: &mut MimeFactory,
|
||||||
context: &Context,
|
|
||||||
e2ee_guaranteed: bool,
|
e2ee_guaranteed: bool,
|
||||||
peerstates: &[(Option<Peerstate>, &str)],
|
min_verified: libc::c_int,
|
||||||
|
do_gossip: bool,
|
||||||
|
mut in_out_message: *mut Mailmime,
|
||||||
|
imffields_unprotected: *mut mailimf_fields,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
|
// libEtPan's pgp_encrypt_mime() takes the parent as the new root.
|
||||||
|
// We just expect the root as being given to this function.
|
||||||
|
ensure!(
|
||||||
|
!in_out_message.is_null() && unsafe { (*in_out_message).mm_parent.is_null() },
|
||||||
|
"corrupted inputs"
|
||||||
|
);
|
||||||
|
|
||||||
if !(self.prefer_encrypt == EncryptPreference::Mutual || e2ee_guaranteed) {
|
if !(self.prefer_encrypt == EncryptPreference::Mutual || e2ee_guaranteed) {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (peerstate, addr) in peerstates {
|
let context = &factory.context;
|
||||||
match peerstate {
|
let mut keyring = Keyring::default();
|
||||||
Some(peerstate) => {
|
let mut gossip_headers: Vec<String> = Vec::with_capacity(factory.recipients_addr.len());
|
||||||
if peerstate.prefer_encrypt != EncryptPreference::Mutual && !e2ee_guaranteed {
|
|
||||||
info!(context, "peerstate for {:?} is no-encrypt", addr);
|
// determine if we can and should encrypt
|
||||||
return Ok(false);
|
for recipient_addr in factory.recipients_addr.iter() {
|
||||||
}
|
if recipient_addr == &self.addr {
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
|
let peerstate = match Peerstate::from_addr(context, &context.sql, recipient_addr) {
|
||||||
|
Some(peerstate) => peerstate,
|
||||||
None => {
|
None => {
|
||||||
let msg = format!("peerstate for {:?} missing, cannot encrypt", addr);
|
let msg = format!("peerstate for {} missing, cannot encrypt", recipient_addr);
|
||||||
if e2ee_guaranteed {
|
if e2ee_guaranteed {
|
||||||
return Err(format_err!("{}", msg));
|
return Err(format_err!("{}", msg));
|
||||||
} else {
|
} else {
|
||||||
@@ -77,59 +111,169 @@ impl EncryptHelper {
|
|||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if peerstate.prefer_encrypt != EncryptPreference::Mutual && !e2ee_guaranteed {
|
||||||
|
info!(context, "peerstate for {} is no-encrypt", recipient_addr);
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(key) = peerstate.peek_key(min_verified as usize) {
|
||||||
|
keyring.add_owned(key.clone());
|
||||||
|
if do_gossip {
|
||||||
|
if let Some(header) = peerstate.render_gossip_header(min_verified as usize) {
|
||||||
|
gossip_headers.push(header.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bail!(
|
||||||
|
"proper enc-key for {} missing, cannot encrypt",
|
||||||
|
recipient_addr
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(true)
|
let sign_key = {
|
||||||
}
|
keyring.add_ref(&self.public_key);
|
||||||
|
let key = Key::from_self_private(context, self.addr.clone(), &context.sql);
|
||||||
|
ensure!(key.is_some(), "no own private key found");
|
||||||
|
|
||||||
/// Tries to encrypt the passed in `mail`.
|
key
|
||||||
pub fn encrypt(
|
};
|
||||||
&mut self,
|
|
||||||
context: &Context,
|
|
||||||
min_verified: PeerstateVerifiedStatus,
|
|
||||||
mail_to_encrypt: lettre_email::PartBuilder,
|
|
||||||
peerstates: &[(Option<Peerstate>, &str)],
|
|
||||||
) -> Result<String> {
|
|
||||||
let mut keyring = Keyring::default();
|
|
||||||
|
|
||||||
for (peerstate, addr) in peerstates
|
// encrypt message
|
||||||
.iter()
|
unsafe {
|
||||||
.filter_map(|(state, addr)| state.as_ref().map(|s| (s, addr)))
|
mailprivacy_prepare_mime(in_out_message);
|
||||||
{
|
let mut part_to_encrypt = (*in_out_message).mm_data.mm_message.mm_msg_mime;
|
||||||
let key = peerstate.peek_key(min_verified).ok_or_else(|| {
|
(*part_to_encrypt).mm_parent = ptr::null_mut();
|
||||||
format_err!("proper enc-key for {} missing, cannot encrypt", addr)
|
let imffields_encrypted = mailimf_fields_new_empty();
|
||||||
})?;
|
|
||||||
keyring.add_ref(key);
|
// mailmime_new_message_data() calls mailmime_fields_new_with_version()
|
||||||
|
// which would add the unwanted MIME-Version:-header
|
||||||
|
let message_to_encrypt = mailmime_new_simple(
|
||||||
|
MAILMIME_MESSAGE as libc::c_int,
|
||||||
|
mailmime_fields_new_empty(),
|
||||||
|
mailmime_get_content_message(),
|
||||||
|
imffields_encrypted,
|
||||||
|
part_to_encrypt,
|
||||||
|
);
|
||||||
|
|
||||||
|
for header in &gossip_headers {
|
||||||
|
wrapmime::new_custom_field(imffields_encrypted, "Autocrypt-Gossip", &header)
|
||||||
|
}
|
||||||
|
|
||||||
|
// memoryhole headers: move some headers into encrypted part
|
||||||
|
// XXX note we can't use clist's into_iter() because the loop body also removes items
|
||||||
|
let mut cur = (*(*imffields_unprotected).fld_list).first;
|
||||||
|
while !cur.is_null() {
|
||||||
|
let field = (*cur).data as *mut mailimf_field;
|
||||||
|
let mut move_to_encrypted = false;
|
||||||
|
|
||||||
|
if !field.is_null() {
|
||||||
|
if (*field).fld_type == MAILIMF_FIELD_SUBJECT as libc::c_int {
|
||||||
|
move_to_encrypted = true;
|
||||||
|
} else if (*field).fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
|
||||||
|
let opt_field = (*field).fld_data.fld_optional_field;
|
||||||
|
if !opt_field.is_null() && !(*opt_field).fld_name.is_null() {
|
||||||
|
let fld_name = to_string_lossy((*opt_field).fld_name);
|
||||||
|
if fld_name.starts_with("Secure-Join")
|
||||||
|
|| (fld_name.starts_with("Chat-") && fld_name != "Chat-Version")
|
||||||
|
{
|
||||||
|
move_to_encrypted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if move_to_encrypted {
|
||||||
|
mailimf_fields_add(imffields_encrypted, field);
|
||||||
|
cur = clist_delete((*imffields_unprotected).fld_list, cur);
|
||||||
|
} else {
|
||||||
|
cur = (*cur).next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let subject = mailimf_subject_new("...".strdup());
|
||||||
|
mailimf_fields_add(imffields_unprotected, mailimf_field_new_subject(subject));
|
||||||
|
|
||||||
|
wrapmime::append_ct_param(
|
||||||
|
(*part_to_encrypt).mm_content_type,
|
||||||
|
"protected-headers",
|
||||||
|
"v1",
|
||||||
|
)?;
|
||||||
|
let plain = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
|
||||||
|
let mut col = 0;
|
||||||
|
mailmime_write_mem(plain, &mut col, message_to_encrypt);
|
||||||
|
mailmime_free(message_to_encrypt);
|
||||||
|
|
||||||
|
ensure!(
|
||||||
|
!(*plain).str_0.is_null() && (*plain).len > 0,
|
||||||
|
"could not write/allocate"
|
||||||
|
);
|
||||||
|
|
||||||
|
let ctext = pgp::pk_encrypt(
|
||||||
|
std::slice::from_raw_parts((*plain).str_0 as *const u8, (*plain).len),
|
||||||
|
&keyring,
|
||||||
|
sign_key.as_ref(),
|
||||||
|
);
|
||||||
|
mmap_string_free(plain);
|
||||||
|
|
||||||
|
let ctext_v = ctext?;
|
||||||
|
|
||||||
|
// create MIME-structure that will contain the encrypted text
|
||||||
|
let mut encrypted_part = new_data_part(
|
||||||
|
ptr::null_mut(),
|
||||||
|
0 as libc::size_t,
|
||||||
|
"multipart/encrypted",
|
||||||
|
MAILMIME_MECHANISM_BASE64,
|
||||||
|
)?;
|
||||||
|
let content = (*encrypted_part).mm_content_type;
|
||||||
|
wrapmime::append_ct_param(content, "protocol", "application/pgp-encrypted")?;
|
||||||
|
|
||||||
|
let version_mime = new_data_part(
|
||||||
|
VERSION_CONTENT.as_mut_ptr() as *mut libc::c_void,
|
||||||
|
strlen(VERSION_CONTENT.as_mut_ptr()),
|
||||||
|
"application/pgp-encrypted",
|
||||||
|
MAILMIME_MECHANISM_7BIT,
|
||||||
|
)?;
|
||||||
|
mailmime_smart_add_part(encrypted_part, version_mime);
|
||||||
|
|
||||||
|
// we assume that ctext_v is not dropped until the end
|
||||||
|
// of this if-scope
|
||||||
|
let ctext_part = new_data_part(
|
||||||
|
ctext_v.as_ptr() as *mut libc::c_void,
|
||||||
|
ctext_v.len(),
|
||||||
|
"application/octet-stream",
|
||||||
|
MAILMIME_MECHANISM_7BIT,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
mailmime_smart_add_part(encrypted_part, ctext_part);
|
||||||
|
(*in_out_message).mm_data.mm_message.mm_msg_mime = encrypted_part;
|
||||||
|
(*encrypted_part).mm_parent = in_out_message;
|
||||||
|
let gossiped = !&gossip_headers.is_empty();
|
||||||
|
factory.finalize_mime_message(in_out_message, true, gossiped)?;
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
keyring.add_ref(&self.public_key);
|
|
||||||
let sign_key = Key::from_self_private(context, self.addr.clone(), &context.sql)
|
|
||||||
.ok_or_else(|| format_err!("missing own private key"))?;
|
|
||||||
|
|
||||||
let raw_message = mail_to_encrypt.build().as_string().into_bytes();
|
|
||||||
|
|
||||||
let ctext = pgp::pk_encrypt(&raw_message, &keyring, Some(&sign_key))?;
|
|
||||||
|
|
||||||
Ok(ctext)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_decrypt(
|
pub fn try_decrypt(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
mail: &ParsedMail<'_>,
|
in_out_message: *mut Mailmime,
|
||||||
message_time: i64,
|
) -> Result<(bool, HashSet<String>, HashSet<String>)> {
|
||||||
) -> Result<(Option<Vec<u8>>, HashSet<String>)> {
|
// just a pointer into mailmime structure, must not be freed
|
||||||
let from = mail
|
let imffields = mailmime_find_mailimf_fields(in_out_message);
|
||||||
.headers
|
ensure!(
|
||||||
.get_first_value("From")?
|
!in_out_message.is_null() && !imffields.is_null(),
|
||||||
.and_then(|from_addr| mailparse::addrparse(&from_addr).ok())
|
"corrupt invalid mime inputs"
|
||||||
.and_then(|from| from.extract_single_info())
|
);
|
||||||
.map(|from| from.addr)
|
|
||||||
.unwrap_or_default();
|
let from = wrapmime::get_field_from(imffields)?;
|
||||||
|
let message_time = wrapmime::get_field_date(imffields)?;
|
||||||
|
|
||||||
let mut peerstate = None;
|
let mut peerstate = None;
|
||||||
let autocryptheader = Aheader::from_headers(context, &from, &mail.headers);
|
let autocryptheader = Aheader::from_imffields(&from, imffields);
|
||||||
|
|
||||||
if message_time > 0 {
|
if message_time > 0 {
|
||||||
peerstate = Peerstate::from_addr(context, &context.sql, &from);
|
peerstate = Peerstate::from_addr(context, &context.sql, &from);
|
||||||
@@ -138,7 +282,9 @@ pub fn try_decrypt(
|
|||||||
if let Some(ref header) = autocryptheader {
|
if let Some(ref header) = autocryptheader {
|
||||||
peerstate.apply_header(&header, message_time);
|
peerstate.apply_header(&header, message_time);
|
||||||
peerstate.save_to_db(&context.sql, false)?;
|
peerstate.save_to_db(&context.sql, false)?;
|
||||||
} else if message_time > peerstate.last_seen_autocrypt && !contains_report(mail) {
|
} else if message_time > peerstate.last_seen_autocrypt
|
||||||
|
&& !contains_report(in_out_message)
|
||||||
|
{
|
||||||
peerstate.degrade_encryption(message_time);
|
peerstate.degrade_encryption(message_time);
|
||||||
peerstate.save_to_db(&context.sql, false)?;
|
peerstate.save_to_db(&context.sql, false)?;
|
||||||
}
|
}
|
||||||
@@ -148,12 +294,13 @@ pub fn try_decrypt(
|
|||||||
peerstate = Some(p);
|
peerstate = Some(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* possibly perform decryption */
|
/* possibly perform decryption */
|
||||||
let mut private_keyring = Keyring::default();
|
let mut private_keyring = Keyring::default();
|
||||||
let mut public_keyring_for_validate = Keyring::default();
|
let mut public_keyring_for_validate = Keyring::default();
|
||||||
let mut out_mail = None;
|
let mut encrypted = false;
|
||||||
let mut signatures = HashSet::default();
|
let mut signatures = HashSet::default();
|
||||||
|
let mut gossipped_addr = HashSet::default();
|
||||||
|
|
||||||
let self_addr = context.get_config(Config::ConfiguredAddr);
|
let self_addr = context.get_config(Config::ConfiguredAddr);
|
||||||
|
|
||||||
if let Some(self_addr) = self_addr {
|
if let Some(self_addr) = self_addr {
|
||||||
@@ -173,16 +320,60 @@ pub fn try_decrypt(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out_mail = decrypt_if_autocrypt_message(
|
let mut gossip_headers = ptr::null_mut();
|
||||||
|
encrypted = decrypt_if_autocrypt_message(
|
||||||
context,
|
context,
|
||||||
mail,
|
in_out_message,
|
||||||
&private_keyring,
|
&private_keyring,
|
||||||
&public_keyring_for_validate,
|
&public_keyring_for_validate,
|
||||||
&mut signatures,
|
&mut signatures,
|
||||||
|
&mut gossip_headers,
|
||||||
)?;
|
)?;
|
||||||
|
if !gossip_headers.is_null() {
|
||||||
|
gossipped_addr =
|
||||||
|
update_gossip_peerstates(context, message_time, imffields, gossip_headers)?;
|
||||||
|
unsafe { mailimf_fields_free(gossip_headers) };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok((out_mail, signatures))
|
Ok((encrypted, signatures, gossipped_addr))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_data_part(
|
||||||
|
data: *mut libc::c_void,
|
||||||
|
data_bytes: libc::size_t,
|
||||||
|
content_type: &str,
|
||||||
|
default_encoding: u32,
|
||||||
|
) -> Result<*mut Mailmime> {
|
||||||
|
let content = new_content_type(&content_type)?;
|
||||||
|
let mut encoding = ptr::null_mut();
|
||||||
|
if wrapmime::content_type_needs_encoding(content) {
|
||||||
|
encoding = unsafe { mailmime_mechanism_new(default_encoding as i32, ptr::null_mut()) };
|
||||||
|
ensure!(!encoding.is_null(), "failed to create encoding");
|
||||||
|
}
|
||||||
|
let mime_fields = {
|
||||||
|
unsafe {
|
||||||
|
mailmime_fields_new_with_data(
|
||||||
|
encoding,
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ensure!(!mime_fields.is_null(), "internal mime error");
|
||||||
|
|
||||||
|
let mime = unsafe { mailmime_new_empty(content, mime_fields) };
|
||||||
|
ensure!(!mime.is_null(), "internal mime error");
|
||||||
|
|
||||||
|
if unsafe { (*mime).mm_type } == MAILMIME_SINGLE as libc::c_int {
|
||||||
|
if !data.is_null() && data_bytes > 0 {
|
||||||
|
unsafe { mailmime_set_body_text(mime, data as *mut libc::c_char, data_bytes) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(mime)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load public key from database or generate a new one.
|
/// Load public key from database or generate a new one.
|
||||||
@@ -209,7 +400,7 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
|
|||||||
);
|
);
|
||||||
match pgp::create_keypair(&self_addr) {
|
match pgp::create_keypair(&self_addr) {
|
||||||
Some((public_key, private_key)) => {
|
Some((public_key, private_key)) => {
|
||||||
if dc_key_save_self_keypair(
|
match dc_key_save_self_keypair(
|
||||||
context,
|
context,
|
||||||
&public_key,
|
&public_key,
|
||||||
&private_key,
|
&private_key,
|
||||||
@@ -217,105 +408,219 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
|
|||||||
true,
|
true,
|
||||||
&context.sql,
|
&context.sql,
|
||||||
) {
|
) {
|
||||||
info!(
|
true => {
|
||||||
context,
|
info!(
|
||||||
"Keypair generated in {:.3}s.",
|
context,
|
||||||
start.elapsed().as_secs()
|
"Keypair generated in {:.3}s.",
|
||||||
);
|
start.elapsed().as_secs()
|
||||||
Ok(public_key)
|
);
|
||||||
} else {
|
Ok(public_key)
|
||||||
Err(format_err!("Failed to save keypair"))
|
}
|
||||||
|
false => Err(format_err!("Failed to save keypair")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => Err(format_err!("Failed to generate keypair")),
|
None => Err(format_err!("Failed to generate keypair")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the encrypted payload and validates the autocrypt structure.
|
fn update_gossip_peerstates(
|
||||||
fn get_autocrypt_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Result<&'a ParsedMail<'b>> {
|
context: &Context,
|
||||||
ensure!(
|
message_time: i64,
|
||||||
mail.ctype.mimetype == "multipart/encrypted",
|
imffields: *mut mailimf_fields,
|
||||||
"Not a multipart/encrypted message: {}",
|
gossip_headers: *const mailimf_fields,
|
||||||
mail.ctype.mimetype
|
) -> Result<HashSet<String>> {
|
||||||
);
|
// XXX split the parsing from the modification part
|
||||||
ensure!(
|
let mut recipients: Option<HashSet<String>> = None;
|
||||||
mail.subparts.len() == 2,
|
let mut gossipped_addr: HashSet<String> = Default::default();
|
||||||
"Invalid Autocrypt Level 1 Mime Parts"
|
|
||||||
);
|
|
||||||
|
|
||||||
ensure!(
|
for cur_data in unsafe { (*(*gossip_headers).fld_list).into_iter() } {
|
||||||
mail.subparts[0].ctype.mimetype == "application/pgp-encrypted",
|
let field = cur_data as *mut mailimf_field;
|
||||||
"Invalid Autocrypt Level 1 version part: {:?}",
|
if field.is_null() {
|
||||||
mail.subparts[0].ctype,
|
continue;
|
||||||
);
|
}
|
||||||
|
|
||||||
ensure!(
|
let field = unsafe { *field };
|
||||||
mail.subparts[1].ctype.mimetype == "application/octet-stream",
|
|
||||||
"Invalid Autocrypt Level 1 encrypted part: {:?}",
|
|
||||||
mail.subparts[1].ctype
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(&mail.subparts[1])
|
if field.fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
|
||||||
|
let optional_field = unsafe { field.fld_data.fld_optional_field };
|
||||||
|
if optional_field.is_null() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let optional_field = unsafe { *optional_field };
|
||||||
|
if !optional_field.fld_name.is_null()
|
||||||
|
&& to_string_lossy(optional_field.fld_name) == "Autocrypt-Gossip"
|
||||||
|
{
|
||||||
|
let value = to_string_lossy(optional_field.fld_value);
|
||||||
|
let gossip_header = Aheader::from_str(&value);
|
||||||
|
|
||||||
|
if let Ok(ref header) = gossip_header {
|
||||||
|
if recipients.is_none() {
|
||||||
|
recipients = Some(mailimf_get_recipients(imffields));
|
||||||
|
}
|
||||||
|
if recipients.as_ref().unwrap().contains(&header.addr) {
|
||||||
|
let mut peerstate =
|
||||||
|
Peerstate::from_addr(context, &context.sql, &header.addr);
|
||||||
|
if let Some(ref mut peerstate) = peerstate {
|
||||||
|
peerstate.apply_gossip(header, message_time);
|
||||||
|
peerstate.save_to_db(&context.sql, false)?;
|
||||||
|
} else {
|
||||||
|
let p = Peerstate::from_gossip(context, header, message_time);
|
||||||
|
p.save_to_db(&context.sql, true)?;
|
||||||
|
peerstate = Some(p);
|
||||||
|
}
|
||||||
|
if let Some(peerstate) = peerstate {
|
||||||
|
if peerstate.degrade_event.is_some() {
|
||||||
|
handle_degrade_event(context, &peerstate)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gossipped_addr.insert(header.addr.clone());
|
||||||
|
} else {
|
||||||
|
info!(
|
||||||
|
context,
|
||||||
|
"Ignoring gossipped \"{}\" as the address is not in To/Cc list.",
|
||||||
|
&header.addr,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(gossipped_addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrypt_if_autocrypt_message<'a>(
|
fn decrypt_if_autocrypt_message(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
mail: &ParsedMail<'a>,
|
mime_undetermined: *mut Mailmime,
|
||||||
private_keyring: &Keyring,
|
private_keyring: &Keyring,
|
||||||
public_keyring_for_validate: &Keyring,
|
public_keyring_for_validate: &Keyring,
|
||||||
ret_valid_signatures: &mut HashSet<String>,
|
ret_valid_signatures: &mut HashSet<String>,
|
||||||
) -> Result<Option<Vec<u8>>> {
|
ret_gossip_headers: *mut *mut mailimf_fields,
|
||||||
// The returned bool is true if we detected an Autocrypt-encrypted
|
) -> Result<bool> {
|
||||||
// message and successfully decrypted it. Decryption then modifies the
|
/* The returned bool is true if we detected an Autocrypt-encrypted
|
||||||
// passed in mime structure in place. The returned bool is false
|
message and successfully decrypted it. Decryption then modifies the
|
||||||
// if it was not an Autocrypt message.
|
passed in mime structure in place. The returned bool is false
|
||||||
//
|
if it was not an Autocrypt message.
|
||||||
// Errors are returned for failures related to decryption of AC-messages.
|
|
||||||
|
|
||||||
let encrypted_data_part = match get_autocrypt_mime(mail) {
|
Errors are returned for failures related to decryption of AC-messages.
|
||||||
|
*/
|
||||||
|
ensure!(!mime_undetermined.is_null(), "Invalid mime reference");
|
||||||
|
|
||||||
|
let (mime, encrypted_data_part) = match wrapmime::get_autocrypt_mime(mime_undetermined) {
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// not an autocrypt mime message, abort and ignore
|
// not a proper autocrypt message, abort and ignore
|
||||||
return Ok(None);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
};
|
};
|
||||||
info!(context, "Detected Autocrypt-mime message");
|
|
||||||
|
|
||||||
decrypt_part(
|
let decrypted_mime = decrypt_part(
|
||||||
context,
|
context,
|
||||||
encrypted_data_part,
|
encrypted_data_part,
|
||||||
private_keyring,
|
private_keyring,
|
||||||
public_keyring_for_validate,
|
public_keyring_for_validate,
|
||||||
ret_valid_signatures,
|
ret_valid_signatures,
|
||||||
)
|
)?;
|
||||||
|
// decrypted_mime is a dangling pointer which we now put into mailmime's Ownership
|
||||||
|
unsafe {
|
||||||
|
mailmime_substitute(mime, decrypted_mime);
|
||||||
|
mailmime_free(mime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally, let's also return gossip headers
|
||||||
|
// XXX better return parsed headers so that upstream
|
||||||
|
// does not need to dive into mmime-stuff again.
|
||||||
|
unsafe {
|
||||||
|
if (*ret_gossip_headers).is_null() && !ret_valid_signatures.is_empty() {
|
||||||
|
let mut dummy: libc::size_t = 0;
|
||||||
|
let mut test: *mut mailimf_fields = ptr::null_mut();
|
||||||
|
if mailimf_envelope_and_optional_fields_parse(
|
||||||
|
(*decrypted_mime).mm_mime_start,
|
||||||
|
(*decrypted_mime).mm_length,
|
||||||
|
&mut dummy,
|
||||||
|
&mut test,
|
||||||
|
) == MAILIMF_NO_ERROR as libc::c_int
|
||||||
|
&& !test.is_null()
|
||||||
|
{
|
||||||
|
*ret_gossip_headers = test;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns Ok(None) if nothing encrypted was found.
|
|
||||||
fn decrypt_part(
|
fn decrypt_part(
|
||||||
_context: &Context,
|
_context: &Context,
|
||||||
mail: &ParsedMail<'_>,
|
mime: *mut Mailmime,
|
||||||
private_keyring: &Keyring,
|
private_keyring: &Keyring,
|
||||||
public_keyring_for_validate: &Keyring,
|
public_keyring_for_validate: &Keyring,
|
||||||
ret_valid_signatures: &mut HashSet<String>,
|
ret_valid_signatures: &mut HashSet<String>,
|
||||||
) -> Result<Option<Vec<u8>>> {
|
) -> Result<*mut Mailmime> {
|
||||||
let data = mail.get_body_raw()?;
|
let mime_data: *mut mailmime_data;
|
||||||
|
let mut mime_transfer_encoding = MAILMIME_MECHANISM_BINARY as libc::c_int;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
mime_data = (*mime).mm_data.mm_single;
|
||||||
|
}
|
||||||
|
if !wrapmime::has_decryptable_data(mime_data) {
|
||||||
|
return Ok(ptr::null_mut());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(enc) = wrapmime::get_mime_transfer_encoding(mime) {
|
||||||
|
mime_transfer_encoding = enc;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: Vec<u8> = wrapmime::decode_dt_data(mime_data, mime_transfer_encoding)?;
|
||||||
|
|
||||||
|
let mut ret_decrypted_mime = ptr::null_mut();
|
||||||
|
|
||||||
if has_decrypted_pgp_armor(&data) {
|
if has_decrypted_pgp_armor(&data) {
|
||||||
// we should only have one decryption happening
|
// we should only have one decryption happening
|
||||||
ensure!(ret_valid_signatures.is_empty(), "corrupt signatures");
|
ensure!(ret_valid_signatures.is_empty(), "corrupt signatures");
|
||||||
|
|
||||||
let plain = pgp::pk_decrypt(
|
let plain = match pgp::pk_decrypt(
|
||||||
&data,
|
&data,
|
||||||
&private_keyring,
|
&private_keyring,
|
||||||
&public_keyring_for_validate,
|
&public_keyring_for_validate,
|
||||||
Some(ret_valid_signatures),
|
Some(ret_valid_signatures),
|
||||||
)?;
|
) {
|
||||||
|
Ok(plain) => {
|
||||||
|
ensure!(!ret_valid_signatures.is_empty(), "no valid signatures");
|
||||||
|
plain
|
||||||
|
}
|
||||||
|
Err(err) => bail!("could not decrypt: {}", err),
|
||||||
|
};
|
||||||
|
let plain_bytes = plain.len();
|
||||||
|
let plain_buf = plain.as_ptr() as *const libc::c_char;
|
||||||
|
|
||||||
ensure!(!ret_valid_signatures.is_empty(), "no valid signatures");
|
let mut index = 0;
|
||||||
return Ok(Some(plain));
|
let mut decrypted_mime = ptr::null_mut();
|
||||||
|
if unsafe {
|
||||||
|
mailmime_parse(
|
||||||
|
plain_buf as *const _,
|
||||||
|
plain_bytes,
|
||||||
|
&mut index,
|
||||||
|
&mut decrypted_mime,
|
||||||
|
)
|
||||||
|
} != MAIL_NO_ERROR as libc::c_int
|
||||||
|
|| decrypted_mime.is_null()
|
||||||
|
{
|
||||||
|
if !decrypted_mime.is_null() {
|
||||||
|
unsafe { mailmime_free(decrypted_mime) };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// decrypted_mime points into `plain`.
|
||||||
|
// FIXME(@dignifiedquire): this still leaks memory I believe, as mailmime_free
|
||||||
|
// does not free the underlying buffer. But for now we have to live with it
|
||||||
|
std::mem::forget(plain);
|
||||||
|
ret_decrypted_mime = decrypted_mime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(ret_decrypted_mime)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_decrypted_pgp_armor(input: &[u8]) -> bool {
|
fn has_decrypted_pgp_armor(input: &[u8]) -> bool {
|
||||||
@@ -339,8 +644,36 @@ fn has_decrypted_pgp_armor(input: &[u8]) -> bool {
|
|||||||
/// However, Delta Chat itself has no problem with encrypted multipart/report
|
/// However, Delta Chat itself has no problem with encrypted multipart/report
|
||||||
/// parts and MUAs should be encouraged to encrpyt multipart/reports as well so
|
/// parts and MUAs should be encouraged to encrpyt multipart/reports as well so
|
||||||
/// that we could use the normal Autocrypt processing.
|
/// that we could use the normal Autocrypt processing.
|
||||||
fn contains_report(mail: &ParsedMail<'_>) -> bool {
|
fn contains_report(mime: *mut Mailmime) -> bool {
|
||||||
mail.ctype.mimetype == "multipart/report"
|
assert!(!mime.is_null());
|
||||||
|
let mime = unsafe { *mime };
|
||||||
|
|
||||||
|
if mime.mm_type == MAILMIME_MULTIPLE as libc::c_int {
|
||||||
|
let tp_type = unsafe { (*(*mime.mm_content_type).ct_type).tp_type };
|
||||||
|
let ct_type =
|
||||||
|
unsafe { (*(*(*mime.mm_content_type).ct_type).tp_data.tp_composite_type).ct_type };
|
||||||
|
|
||||||
|
if tp_type == MAILMIME_TYPE_COMPOSITE_TYPE as libc::c_int
|
||||||
|
&& ct_type == MAILMIME_COMPOSITE_TYPE_MULTIPART as libc::c_int
|
||||||
|
&& to_string_lossy(unsafe { (*mime.mm_content_type).ct_subtype }) == "report"
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for cur_data in unsafe { (*(*mime.mm_mime_fields).fld_list).into_iter() } {
|
||||||
|
if contains_report(cur_data as *mut Mailmime) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if mime.mm_type == MAILMIME_MESSAGE as libc::c_int {
|
||||||
|
let m = unsafe { mime.mm_data.mm_message.mm_msg_mime };
|
||||||
|
|
||||||
|
if contains_report(m) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ensures a private key exists for the configured user.
|
/// Ensures a private key exists for the configured user.
|
||||||
@@ -366,6 +699,7 @@ pub fn ensure_secret_key_exists(context: &Context) -> Result<String> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use libc::free;
|
||||||
|
|
||||||
use crate::test_utils::*;
|
use crate::test_utils::*;
|
||||||
|
|
||||||
@@ -388,7 +722,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mailmime_parse() {
|
fn test_mailmime_parse() {
|
||||||
let plain = b"Chat-Disposition-Notification-To: hello@world.de
|
let plain = b"Chat-Disposition-Notification-To: holger@deltachat.de
|
||||||
Chat-Group-ID: CovhGgau8M-
|
Chat-Group-ID: CovhGgau8M-
|
||||||
Chat-Group-Name: Delta Chat Dev
|
Chat-Group-Name: Delta Chat Dev
|
||||||
Subject: =?utf-8?Q?Chat=3A?= Delta Chat =?utf-8?Q?Dev=3A?= sidenote for
|
Subject: =?utf-8?Q?Chat=3A?= Delta Chat =?utf-8?Q?Dev=3A?= sidenote for
|
||||||
@@ -396,18 +730,35 @@ Subject: =?utf-8?Q?Chat=3A?= Delta Chat =?utf-8?Q?Dev=3A?= sidenote for
|
|||||||
Content-Type: text/plain; charset=\"utf-8\"; protected-headers=\"v1\"
|
Content-Type: text/plain; charset=\"utf-8\"; protected-headers=\"v1\"
|
||||||
Content-Transfer-Encoding: quoted-printable
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
|
||||||
sidenote for all: things are trick atm recomm=
|
sidenote for all: rust core master is broken currently ... so dont recomm=
|
||||||
end not to try to run with desktop or ios unless you are ready to hunt bugs
|
end to try to run with desktop or ios unless you are ready to hunt bugs
|
||||||
|
|
||||||
-- =20
|
-- =20
|
||||||
Sent with my Delta Chat Messenger: https://delta.chat";
|
Sent with my Delta Chat Messenger: https://delta.chat";
|
||||||
let mail = mailparse::parse_mail(plain).expect("failed to parse valid message");
|
let plain_bytes = plain.len();
|
||||||
|
let plain_buf = plain.as_ptr() as *const libc::c_char;
|
||||||
|
|
||||||
assert_eq!(mail.headers.len(), 6);
|
let mut index = 0;
|
||||||
assert!(
|
let mut decrypted_mime = std::ptr::null_mut();
|
||||||
mail.get_body().unwrap().starts_with(
|
|
||||||
"sidenote for all: things are trick atm recommend not to try to run with desktop or ios unless you are ready to hunt bugs")
|
let res = unsafe {
|
||||||
);
|
mailmime_parse(
|
||||||
|
plain_buf as *const _,
|
||||||
|
plain_bytes,
|
||||||
|
&mut index,
|
||||||
|
&mut decrypted_mime,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
let msg1 = (*decrypted_mime).mm_data.mm_message.mm_msg_mime;
|
||||||
|
let data = mailmime_transfer_decode(msg1).unwrap();
|
||||||
|
println!("{:?}", String::from_utf8_lossy(&data));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(res, 0);
|
||||||
|
assert!(!decrypted_mime.is_null());
|
||||||
|
|
||||||
|
unsafe { free(decrypted_mime as *mut _) };
|
||||||
}
|
}
|
||||||
|
|
||||||
mod load_or_generate_self_public_key {
|
mod load_or_generate_self_public_key {
|
||||||
|
|||||||
64
src/error.rs
@@ -1,13 +1,19 @@
|
|||||||
//! # Error handling
|
use failure::Fail;
|
||||||
|
|
||||||
use lettre_email::mime;
|
|
||||||
|
|
||||||
#[derive(Debug, Fail)]
|
#[derive(Debug, Fail)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
#[fail(display = "Sqlite Error: {:?}", _0)]
|
||||||
|
Sql(rusqlite::Error),
|
||||||
|
#[fail(display = "Sqlite Connection Pool Error: {:?}", _0)]
|
||||||
|
ConnectionPool(r2d2::Error),
|
||||||
#[fail(display = "{:?}", _0)]
|
#[fail(display = "{:?}", _0)]
|
||||||
Failure(failure::Error),
|
Failure(failure::Error),
|
||||||
#[fail(display = "SQL error: {:?}", _0)]
|
#[fail(display = "Sqlite: Connection closed")]
|
||||||
SqlError(#[cause] crate::sql::Error),
|
SqlNoConnection,
|
||||||
|
#[fail(display = "Sqlite: Already open")]
|
||||||
|
SqlAlreadyOpen,
|
||||||
|
#[fail(display = "Sqlite: Failed to open")]
|
||||||
|
SqlFailedToOpen,
|
||||||
#[fail(display = "{:?}", _0)]
|
#[fail(display = "{:?}", _0)]
|
||||||
Io(std::io::Error),
|
Io(std::io::Error),
|
||||||
#[fail(display = "{:?}", _0)]
|
#[fail(display = "{:?}", _0)]
|
||||||
@@ -16,6 +22,8 @@ pub enum Error {
|
|||||||
Image(image_meta::ImageError),
|
Image(image_meta::ImageError),
|
||||||
#[fail(display = "{:?}", _0)]
|
#[fail(display = "{:?}", _0)]
|
||||||
Utf8(std::str::Utf8Error),
|
Utf8(std::str::Utf8Error),
|
||||||
|
#[fail(display = "{:?}", _0)]
|
||||||
|
CStringError(crate::dc_tools::CStringError),
|
||||||
#[fail(display = "PGP: {:?}", _0)]
|
#[fail(display = "PGP: {:?}", _0)]
|
||||||
Pgp(pgp::errors::Error),
|
Pgp(pgp::errors::Error),
|
||||||
#[fail(display = "Base64Decode: {:?}", _0)]
|
#[fail(display = "Base64Decode: {:?}", _0)]
|
||||||
@@ -26,23 +34,13 @@ pub enum Error {
|
|||||||
BlobError(#[cause] crate::blob::BlobError),
|
BlobError(#[cause] crate::blob::BlobError),
|
||||||
#[fail(display = "Invalid Message ID.")]
|
#[fail(display = "Invalid Message ID.")]
|
||||||
InvalidMsgId,
|
InvalidMsgId,
|
||||||
#[fail(display = "Watch folder not found {:?}", _0)]
|
|
||||||
WatchFolderNotFound(String),
|
|
||||||
#[fail(display = "Invalid Email: {:?}", _0)]
|
|
||||||
MailParseError(#[cause] mailparse::MailParseError),
|
|
||||||
#[fail(display = "Building invalid Email: {:?}", _0)]
|
|
||||||
LettreError(#[cause] lettre_email::error::Error),
|
|
||||||
#[fail(display = "FromStr error: {:?}", _0)]
|
|
||||||
FromStr(#[cause] mime::FromStrError),
|
|
||||||
#[fail(display = "Not Configured")]
|
|
||||||
NotConfigured,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
impl From<crate::sql::Error> for Error {
|
impl From<rusqlite::Error> for Error {
|
||||||
fn from(err: crate::sql::Error) -> Error {
|
fn from(err: rusqlite::Error) -> Error {
|
||||||
Error::SqlError(err)
|
Error::Sql(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +56,12 @@ impl From<failure::Error> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<r2d2::Error> for Error {
|
||||||
|
fn from(err: r2d2::Error) -> Error {
|
||||||
|
Error::ConnectionPool(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<std::io::Error> for Error {
|
impl From<std::io::Error> for Error {
|
||||||
fn from(err: std::io::Error) -> Error {
|
fn from(err: std::io::Error) -> Error {
|
||||||
Error::Io(err)
|
Error::Io(err)
|
||||||
@@ -76,6 +80,12 @@ impl From<image_meta::ImageError> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<crate::dc_tools::CStringError> for Error {
|
||||||
|
fn from(err: crate::dc_tools::CStringError) -> Error {
|
||||||
|
Error::CStringError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<pgp::errors::Error> for Error {
|
impl From<pgp::errors::Error> for Error {
|
||||||
fn from(err: pgp::errors::Error) -> Error {
|
fn from(err: pgp::errors::Error) -> Error {
|
||||||
Error::Pgp(err)
|
Error::Pgp(err)
|
||||||
@@ -100,24 +110,6 @@ impl From<crate::message::InvalidMsgId> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<mailparse::MailParseError> for Error {
|
|
||||||
fn from(err: mailparse::MailParseError) -> Error {
|
|
||||||
Error::MailParseError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<lettre_email::error::Error> for Error {
|
|
||||||
fn from(err: lettre_email::error::Error) -> Error {
|
|
||||||
Error::LettreError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<mime::FromStrError> for Error {
|
|
||||||
fn from(err: mime::FromStrError) -> Error {
|
|
||||||
Error::FromStr(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! bail {
|
macro_rules! bail {
|
||||||
($e:expr) => {
|
($e:expr) => {
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
//! # Events specification
|
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use strum::EnumProperty;
|
use strum::EnumProperty;
|
||||||
@@ -92,7 +90,7 @@ pub enum Event {
|
|||||||
/// However, for ongoing processes (eg. configure())
|
/// However, for ongoing processes (eg. configure())
|
||||||
/// or for functions that are expected to fail (eg. dc_continue_key_transfer())
|
/// or for functions that are expected to fail (eg. dc_continue_key_transfer())
|
||||||
/// it might be better to delay showing these events until the function has really
|
/// it might be better to delay showing these events until the function has really
|
||||||
/// failed (returned false). It should be sufficient to report only the *last* error
|
/// failed (returned false). It should be sufficient to report only the _last_ error
|
||||||
/// in a messasge box then.
|
/// in a messasge box then.
|
||||||
///
|
///
|
||||||
/// @return
|
/// @return
|
||||||
@@ -245,11 +243,4 @@ pub enum Event {
|
|||||||
/// @return 0
|
/// @return 0
|
||||||
#[strum(props(id = "2061"))]
|
#[strum(props(id = "2061"))]
|
||||||
SecurejoinJoinerProgress { contact_id: u32, progress: usize },
|
SecurejoinJoinerProgress { contact_id: u32, progress: usize },
|
||||||
|
|
||||||
/// This event is sent out to the inviter when a joiner successfully joined a group.
|
|
||||||
/// @param data1 (int) chat_id
|
|
||||||
/// @param data2 (int) contact_id
|
|
||||||
/// @return 0
|
|
||||||
#[strum(props(id = "2062"))]
|
|
||||||
SecurejoinMemberAdded { chat_id: u32, contact_id: u32 },
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
#[derive(Debug, Display, Clone, PartialEq, Eq, EnumVariantNames)]
|
|
||||||
#[strum(serialize_all = "kebab_case")]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum HeaderDef {
|
|
||||||
MessageId,
|
|
||||||
Subject,
|
|
||||||
Date,
|
|
||||||
From_,
|
|
||||||
To,
|
|
||||||
Cc,
|
|
||||||
Disposition,
|
|
||||||
OriginalMessageId,
|
|
||||||
ListId,
|
|
||||||
References,
|
|
||||||
InReplyTo,
|
|
||||||
Precedence,
|
|
||||||
ChatVersion,
|
|
||||||
ChatGroupId,
|
|
||||||
ChatGroupName,
|
|
||||||
ChatGroupNameChanged,
|
|
||||||
ChatVerified,
|
|
||||||
ChatGroupImage, // deprecated
|
|
||||||
ChatGroupAvatar,
|
|
||||||
ChatUserAvatar,
|
|
||||||
ChatVoiceMessage,
|
|
||||||
ChatGroupMemberRemoved,
|
|
||||||
ChatGroupMemberAdded,
|
|
||||||
ChatContent,
|
|
||||||
ChatDuration,
|
|
||||||
ChatDispositionNotificationTo,
|
|
||||||
AutocryptSetupMessage,
|
|
||||||
SecureJoin,
|
|
||||||
SecureJoinGroup,
|
|
||||||
SecureJoinFingerprint,
|
|
||||||
SecureJoinInvitenumber,
|
|
||||||
SecureJoinAuth,
|
|
||||||
_TestHeader,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HeaderDef {
|
|
||||||
/// Returns the corresponding Event id.
|
|
||||||
pub fn get_headername(&self) -> String {
|
|
||||||
self.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
/// Test that kebab_case serialization works as expected
|
|
||||||
fn kebab_test() {
|
|
||||||
assert_eq!(HeaderDef::From_.to_string(), "from");
|
|
||||||
|
|
||||||
assert_eq!(HeaderDef::_TestHeader.to_string(), "test-header");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1382
src/imap.rs
Normal file
273
src/imap/idle.rs
@@ -1,273 +0,0 @@
|
|||||||
use super::Imap;
|
|
||||||
|
|
||||||
use async_imap::extensions::idle::IdleResponse;
|
|
||||||
use async_std::prelude::*;
|
|
||||||
use async_std::task;
|
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
use std::time::{Duration, SystemTime};
|
|
||||||
|
|
||||||
use crate::context::Context;
|
|
||||||
use crate::imap_client::*;
|
|
||||||
|
|
||||||
use super::select_folder;
|
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
#[derive(Debug, Fail)]
|
|
||||||
pub enum Error {
|
|
||||||
#[fail(display = "IMAP IDLE protocol failed to init/complete")]
|
|
||||||
IdleProtocolFailed(#[cause] async_imap::error::Error),
|
|
||||||
|
|
||||||
#[fail(display = "IMAP IDLE protocol timed out")]
|
|
||||||
IdleTimeout(#[cause] async_std::future::TimeoutError),
|
|
||||||
|
|
||||||
#[fail(display = "IMAP server does not have IDLE capability")]
|
|
||||||
IdleAbilityMissing,
|
|
||||||
|
|
||||||
#[fail(display = "IMAP select folder error")]
|
|
||||||
SelectFolderError(#[cause] select_folder::Error),
|
|
||||||
|
|
||||||
#[fail(display = "IMAP error")]
|
|
||||||
ImapError(#[cause] async_imap::error::Error),
|
|
||||||
|
|
||||||
#[fail(display = "Setup handle error")]
|
|
||||||
SetupHandleError(#[cause] super::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<select_folder::Error> for Error {
|
|
||||||
fn from(err: select_folder::Error) -> Error {
|
|
||||||
Error::SelectFolderError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Imap {
|
|
||||||
pub fn can_idle(&self) -> bool {
|
|
||||||
task::block_on(async move { self.config.read().await.can_idle })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn idle(&self, context: &Context, watch_folder: Option<String>) -> Result<()> {
|
|
||||||
task::block_on(async move {
|
|
||||||
if !self.can_idle() {
|
|
||||||
return Err(Error::IdleAbilityMissing);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.setup_handle_if_needed(context)
|
|
||||||
.await
|
|
||||||
.map_err(Error::SetupHandleError)?;
|
|
||||||
|
|
||||||
self.select_folder(context, watch_folder.clone()).await?;
|
|
||||||
|
|
||||||
let session = self.session.lock().await.take();
|
|
||||||
let timeout = Duration::from_secs(23 * 60);
|
|
||||||
if let Some(session) = session {
|
|
||||||
match session.idle() {
|
|
||||||
// BEWARE: If you change the Secure branch you
|
|
||||||
// typically also need to change the Insecure branch.
|
|
||||||
IdleHandle::Secure(mut handle) => {
|
|
||||||
if let Err(err) = handle.init().await {
|
|
||||||
return Err(Error::IdleProtocolFailed(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
let (idle_wait, interrupt) = handle.wait_with_timeout(timeout);
|
|
||||||
*self.interrupt.lock().await = Some(interrupt);
|
|
||||||
|
|
||||||
if self.skip_next_idle_wait.load(Ordering::SeqCst) {
|
|
||||||
// interrupt_idle has happened before we
|
|
||||||
// provided self.interrupt
|
|
||||||
self.skip_next_idle_wait.store(false, Ordering::SeqCst);
|
|
||||||
std::mem::drop(idle_wait);
|
|
||||||
info!(context, "Idle wait was skipped");
|
|
||||||
} else {
|
|
||||||
info!(context, "Idle entering wait-on-remote state");
|
|
||||||
match idle_wait.await {
|
|
||||||
IdleResponse::NewData(_) => {
|
|
||||||
info!(context, "Idle has NewData");
|
|
||||||
}
|
|
||||||
// TODO: idle_wait does not distinguish manual interrupts
|
|
||||||
// from Timeouts if we would know it's a Timeout we could bail
|
|
||||||
// directly and reconnect .
|
|
||||||
IdleResponse::Timeout => {
|
|
||||||
info!(context, "Idle-wait timeout or interruption");
|
|
||||||
}
|
|
||||||
IdleResponse::ManualInterrupt => {
|
|
||||||
info!(context, "Idle wait was interrupted");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if we can't properly terminate the idle
|
|
||||||
// protocol let's break the connection.
|
|
||||||
let res =
|
|
||||||
async_std::future::timeout(Duration::from_secs(15), handle.done())
|
|
||||||
.await
|
|
||||||
.map_err(|err| {
|
|
||||||
self.trigger_reconnect();
|
|
||||||
Error::IdleTimeout(err)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
match res {
|
|
||||||
Ok(session) => {
|
|
||||||
*self.session.lock().await = Some(Session::Secure(session));
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
// if we cannot terminate IDLE it probably
|
|
||||||
// means that we waited long (with idle_wait)
|
|
||||||
// but the network went away/changed
|
|
||||||
self.trigger_reconnect();
|
|
||||||
return Err(Error::IdleProtocolFailed(err));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IdleHandle::Insecure(mut handle) => {
|
|
||||||
if let Err(err) = handle.init().await {
|
|
||||||
return Err(Error::IdleProtocolFailed(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
let (idle_wait, interrupt) = handle.wait_with_timeout(timeout);
|
|
||||||
*self.interrupt.lock().await = Some(interrupt);
|
|
||||||
|
|
||||||
if self.skip_next_idle_wait.load(Ordering::SeqCst) {
|
|
||||||
// interrupt_idle has happened before we
|
|
||||||
// provided self.interrupt
|
|
||||||
self.skip_next_idle_wait.store(false, Ordering::SeqCst);
|
|
||||||
std::mem::drop(idle_wait);
|
|
||||||
info!(context, "Idle wait was skipped");
|
|
||||||
} else {
|
|
||||||
info!(context, "Idle entering wait-on-remote state");
|
|
||||||
match idle_wait.await {
|
|
||||||
IdleResponse::NewData(_) => {
|
|
||||||
info!(context, "Idle has NewData");
|
|
||||||
}
|
|
||||||
// TODO: idle_wait does not distinguish manual interrupts
|
|
||||||
// from Timeouts if we would know it's a Timeout we could bail
|
|
||||||
// directly and reconnect .
|
|
||||||
IdleResponse::Timeout => {
|
|
||||||
info!(context, "Idle-wait timeout or interruption");
|
|
||||||
}
|
|
||||||
IdleResponse::ManualInterrupt => {
|
|
||||||
info!(context, "Idle wait was interrupted");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if we can't properly terminate the idle
|
|
||||||
// protocol let's break the connection.
|
|
||||||
let res =
|
|
||||||
async_std::future::timeout(Duration::from_secs(15), handle.done())
|
|
||||||
.await
|
|
||||||
.map_err(|err| {
|
|
||||||
self.trigger_reconnect();
|
|
||||||
Error::IdleTimeout(err)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
match res {
|
|
||||||
Ok(session) => {
|
|
||||||
*self.session.lock().await = Some(Session::Insecure(session));
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
// if we cannot terminate IDLE it probably
|
|
||||||
// means that we waited long (with idle_wait)
|
|
||||||
// but the network went away/changed
|
|
||||||
self.trigger_reconnect();
|
|
||||||
return Err(Error::IdleProtocolFailed(err));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn fake_idle(&self, context: &Context, watch_folder: Option<String>) {
|
|
||||||
// Idle using polling. This is also needed if we're not yet configured -
|
|
||||||
// in this case, we're waiting for a configure job (and an interrupt).
|
|
||||||
task::block_on(async move {
|
|
||||||
let fake_idle_start_time = SystemTime::now();
|
|
||||||
|
|
||||||
info!(context, "IMAP-fake-IDLEing...");
|
|
||||||
|
|
||||||
let interrupt = stop_token::StopSource::new();
|
|
||||||
|
|
||||||
// check every minute if there are new messages
|
|
||||||
// TODO: grow sleep durations / make them more flexible
|
|
||||||
let interval = async_std::stream::interval(Duration::from_secs(60));
|
|
||||||
let mut interrupt_interval = interrupt.stop_token().stop_stream(interval);
|
|
||||||
*self.interrupt.lock().await = Some(interrupt);
|
|
||||||
if self.skip_next_idle_wait.load(Ordering::SeqCst) {
|
|
||||||
// interrupt_idle has happened before we
|
|
||||||
// provided self.interrupt
|
|
||||||
self.skip_next_idle_wait.store(false, Ordering::SeqCst);
|
|
||||||
info!(context, "fake-idle wait was skipped");
|
|
||||||
} else {
|
|
||||||
// loop until we are interrupted or if we fetched something
|
|
||||||
while let Some(_) = interrupt_interval.next().await {
|
|
||||||
// try to connect with proper login params
|
|
||||||
// (setup_handle_if_needed might not know about them if we
|
|
||||||
// never successfully connected)
|
|
||||||
if let Err(err) = self.connect_configured(context) {
|
|
||||||
warn!(context, "fake_idle: could not connect: {}", err);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if self.config.read().await.can_idle {
|
|
||||||
// we only fake-idled because network was gone during IDLE, probably
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
info!(context, "fake_idle is connected");
|
|
||||||
// we are connected, let's see if fetching messages results
|
|
||||||
// in anything. If so, we behave as if IDLE had data but
|
|
||||||
// will have already fetched the messages so perform_*_fetch
|
|
||||||
// will not find any new.
|
|
||||||
|
|
||||||
if let Some(ref watch_folder) = watch_folder {
|
|
||||||
match self.fetch_new_messages(context, watch_folder).await {
|
|
||||||
Ok(res) => {
|
|
||||||
info!(context, "fetch_new_messages returned {:?}", res);
|
|
||||||
if res {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
error!(context, "could not fetch from folder: {}", err);
|
|
||||||
self.trigger_reconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.interrupt.lock().await.take();
|
|
||||||
|
|
||||||
info!(
|
|
||||||
context,
|
|
||||||
"IMAP-fake-IDLE done after {:.4}s",
|
|
||||||
SystemTime::now()
|
|
||||||
.duration_since(fake_idle_start_time)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.as_millis() as f64
|
|
||||||
/ 1000.,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn interrupt_idle(&self, context: &Context) {
|
|
||||||
task::block_on(async move {
|
|
||||||
let mut interrupt: Option<stop_token::StopSource> = self.interrupt.lock().await.take();
|
|
||||||
if interrupt.is_none() {
|
|
||||||
// idle wait is not running, signal it needs to skip
|
|
||||||
self.skip_next_idle_wait.store(true, Ordering::SeqCst);
|
|
||||||
|
|
||||||
// meanwhile idle-wait may have produced the StopSource
|
|
||||||
interrupt = self.interrupt.lock().await.take();
|
|
||||||
}
|
|
||||||
// let's manually drop the StopSource
|
|
||||||
if interrupt.is_some() {
|
|
||||||
// the imap thread provided us a stop token but might
|
|
||||||
// not have entered idle_wait yet, give it some time
|
|
||||||
// for that to happen. XXX handle this without extra wait
|
|
||||||
// https://github.com/deltachat/deltachat-core-rust/issues/925
|
|
||||||
std::thread::sleep(Duration::from_millis(200));
|
|
||||||
info!(context, "low-level: dropping stop-source to interrupt idle");
|
|
||||||
std::mem::drop(interrupt)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1273
src/imap/mod.rs
@@ -1,113 +0,0 @@
|
|||||||
use super::Imap;
|
|
||||||
|
|
||||||
use crate::context::Context;
|
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
#[derive(Debug, Fail)]
|
|
||||||
pub enum Error {
|
|
||||||
#[fail(display = "IMAP Could not obtain imap-session object.")]
|
|
||||||
NoSession,
|
|
||||||
|
|
||||||
#[fail(display = "IMAP Connection Lost or no connection established")]
|
|
||||||
ConnectionLost,
|
|
||||||
|
|
||||||
#[fail(display = "IMAP Folder name invalid: {:?}", _0)]
|
|
||||||
BadFolderName(String),
|
|
||||||
|
|
||||||
#[fail(display = "IMAP close/expunge failed: {}", _0)]
|
|
||||||
CloseExpungeFailed(#[cause] async_imap::error::Error),
|
|
||||||
|
|
||||||
#[fail(display = "IMAP other error: {:?}", _0)]
|
|
||||||
Other(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Imap {
|
|
||||||
/// select a folder, possibly update uid_validity and, if needed,
|
|
||||||
/// expunge the folder to remove delete-marked messages.
|
|
||||||
pub(super) async fn select_folder<S: AsRef<str>>(
|
|
||||||
&self,
|
|
||||||
context: &Context,
|
|
||||||
folder: Option<S>,
|
|
||||||
) -> Result<()> {
|
|
||||||
if self.session.lock().await.is_none() {
|
|
||||||
let mut cfg = self.config.write().await;
|
|
||||||
cfg.selected_folder = None;
|
|
||||||
cfg.selected_folder_needs_expunge = false;
|
|
||||||
self.trigger_reconnect();
|
|
||||||
return Err(Error::NoSession);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there is a new folder and the new folder is equal to the selected one, there's nothing to do.
|
|
||||||
// if there is _no_ new folder, we continue as we might want to expunge below.
|
|
||||||
if let Some(ref folder) = folder {
|
|
||||||
if let Some(ref selected_folder) = self.config.read().await.selected_folder {
|
|
||||||
if folder.as_ref() == selected_folder {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// deselect existing folder, if needed (it's also done implicitly by SELECT, however, without EXPUNGE then)
|
|
||||||
let needs_expunge = { self.config.read().await.selected_folder_needs_expunge };
|
|
||||||
if needs_expunge {
|
|
||||||
if let Some(ref folder) = self.config.read().await.selected_folder {
|
|
||||||
info!(context, "Expunge messages in \"{}\".", folder);
|
|
||||||
|
|
||||||
// A CLOSE-SELECT is considerably faster than an EXPUNGE-SELECT, see
|
|
||||||
// https://tools.ietf.org/html/rfc3501#section-6.4.2
|
|
||||||
if let Some(ref mut session) = &mut *self.session.lock().await {
|
|
||||||
match session.close().await {
|
|
||||||
Ok(_) => {
|
|
||||||
info!(context, "close/expunge succeeded");
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
self.trigger_reconnect();
|
|
||||||
return Err(Error::CloseExpungeFailed(err));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(Error::NoSession);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.config.write().await.selected_folder_needs_expunge = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// select new folder
|
|
||||||
if let Some(ref folder) = folder {
|
|
||||||
if let Some(ref mut session) = &mut *self.session.lock().await {
|
|
||||||
let res = session.select(folder).await;
|
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc3501#section-6.3.1
|
|
||||||
// says that if the server reports select failure we are in
|
|
||||||
// authenticated (not-select) state.
|
|
||||||
|
|
||||||
match res {
|
|
||||||
Ok(mailbox) => {
|
|
||||||
let mut config = self.config.write().await;
|
|
||||||
config.selected_folder = Some(folder.as_ref().to_string());
|
|
||||||
config.selected_mailbox = Some(mailbox);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Err(async_imap::error::Error::ConnectionLost) => {
|
|
||||||
self.trigger_reconnect();
|
|
||||||
self.config.write().await.selected_folder = None;
|
|
||||||
Err(Error::ConnectionLost)
|
|
||||||
}
|
|
||||||
Err(async_imap::error::Error::Validate(_)) => {
|
|
||||||
Err(Error::BadFolderName(folder.as_ref().to_string()))
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
self.config.write().await.selected_folder = None;
|
|
||||||
self.trigger_reconnect();
|
|
||||||
Err(Error::Other(err.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Error::NoSession)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||