mirror of
https://github.com/chatmail/core.git
synced 2026-04-06 15:42:10 +03:00
Compare commits
323 Commits
fix/setupc
...
1.0.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a4a4389fa | ||
|
|
131889cdfb | ||
|
|
bed14d5c02 | ||
|
|
d3c831a0a2 | ||
|
|
0007c12dea | ||
|
|
049077f13b | ||
|
|
e17c69f89c | ||
|
|
4b24f32d6c | ||
|
|
f404e31e30 | ||
|
|
7455b26ab2 | ||
|
|
ee3259a74d | ||
|
|
391ba67ad5 | ||
|
|
54f8c68151 | ||
|
|
4a2e1897a6 | ||
|
|
076616bfb9 | ||
|
|
a9dd78f622 | ||
|
|
d16bdafaf0 | ||
|
|
4f126c5292 | ||
|
|
7b958a20fd | ||
|
|
4519071718 | ||
|
|
0108b4724e | ||
|
|
bb08b39c71 | ||
|
|
1908ac428b | ||
|
|
dfc453c1d1 | ||
|
|
9fa6289093 | ||
|
|
6f92ce0fa8 | ||
|
|
cde2c9137f | ||
|
|
120524ae00 | ||
|
|
7bb73f45a5 | ||
|
|
2d0f563dfe | ||
|
|
cfe3c69f00 | ||
|
|
c266d2ca0d | ||
|
|
85fc696975 | ||
|
|
9bf8bed0c3 | ||
|
|
c4d55f6ba4 | ||
|
|
766d7cbd3a | ||
|
|
8e0e1bd58d | ||
|
|
a471ccc95a | ||
|
|
daac8c4824 | ||
|
|
59c22a5626 | ||
|
|
5154f27f72 | ||
|
|
5f7279eb85 | ||
|
|
ce67f593f6 | ||
|
|
556ea57f37 | ||
|
|
a4257b619a | ||
|
|
8479c8afbf | ||
|
|
eba012b965 | ||
|
|
66e53e6804 | ||
|
|
c8aa8b55f6 | ||
|
|
900e3905c0 | ||
|
|
088490721d | ||
|
|
a40b99aae0 | ||
|
|
b9646446f8 | ||
|
|
4e36b35039 | ||
|
|
d412ee6042 | ||
|
|
67848e3333 | ||
|
|
4d79c6e235 | ||
|
|
bc99d9d196 | ||
|
|
e1fc5863c2 | ||
|
|
f0791149e6 | ||
|
|
297b032bdc | ||
|
|
98180c175d | ||
|
|
46e8a436cb | ||
|
|
dc2cf8ecfc | ||
|
|
fd69ebfd1f | ||
|
|
03979fdc51 | ||
|
|
2c98e91276 | ||
|
|
3270120d16 | ||
|
|
77a7efc920 | ||
|
|
de604e744e | ||
|
|
24c0a833bd | ||
|
|
45f011c63c | ||
|
|
bc86201b44 | ||
|
|
b82af9fff3 | ||
|
|
3a1e74a306 | ||
|
|
e4cca92910 | ||
|
|
102220834c | ||
|
|
24d744b94c | ||
|
|
1df6229e99 | ||
|
|
c23e98ff83 | ||
|
|
b7c81f37c0 | ||
|
|
5c3a7e4119 | ||
|
|
a94acef49b | ||
|
|
7f5b362eda | ||
|
|
ba5b3ad675 | ||
|
|
c1e4d1e7a4 | ||
|
|
dd8744b74e | ||
|
|
b775ecca08 | ||
|
|
b8f211a013 | ||
|
|
51534b2fae | ||
|
|
710db2ba0a | ||
|
|
32ef0d4dc3 | ||
|
|
b3cd80ba6d | ||
|
|
3f053f899e | ||
|
|
22b4d1734c | ||
|
|
46a71e81a0 | ||
|
|
b4851187ba | ||
|
|
0252969f7e | ||
|
|
f86cec4844 | ||
|
|
cd2e36da92 | ||
|
|
1b13107181 | ||
|
|
6fbde21995 | ||
|
|
a6608513ac | ||
|
|
d43c225be3 | ||
|
|
1802d7658d | ||
|
|
275f5d713f | ||
|
|
e40cfeec58 | ||
|
|
275b4b8d36 | ||
|
|
77cef632c7 | ||
|
|
db2064de14 | ||
|
|
e251c7b1c8 | ||
|
|
2fe98775f9 | ||
|
|
187179d87b | ||
|
|
dd03f6e8af | ||
|
|
07b32241bd | ||
|
|
abfff96cd4 | ||
|
|
735bdd1c20 | ||
|
|
9cae075b6f | ||
|
|
2317518e5e | ||
|
|
477af413c6 | ||
|
|
93f0f5ccae | ||
|
|
79b92727cc | ||
|
|
8dfd04672f | ||
|
|
603761e4b7 | ||
|
|
467c09f491 | ||
|
|
a953b494cb | ||
|
|
dca9afa10b | ||
|
|
23d2d87c24 | ||
|
|
c6b2d640ae | ||
|
|
b4b8a1d15b | ||
|
|
130d485cac | ||
|
|
d5b92744ed | ||
|
|
a5c4e16405 | ||
|
|
216266d7bf | ||
|
|
bf1652a1be | ||
|
|
f93f3d6012 | ||
|
|
41806f86ba | ||
|
|
59df97944f | ||
|
|
468651534e | ||
|
|
6343ae8161 | ||
|
|
641bd5eb15 | ||
|
|
063d989225 | ||
|
|
b8ca7b1591 | ||
|
|
e222f49c9d | ||
|
|
297bc635e8 | ||
|
|
230c65594c | ||
|
|
509a21ff05 | ||
|
|
96066712bd | ||
|
|
d83aa1e898 | ||
|
|
f0a7bdb6d6 | ||
|
|
9c077c98cd | ||
|
|
6dc45642b7 | ||
|
|
5c1b9c83f7 | ||
|
|
92438737c9 | ||
|
|
489cdd1b24 | ||
|
|
3f7995a7ea | ||
|
|
f7ad93229d | ||
|
|
555b4bc8c7 | ||
|
|
75f41bcb90 | ||
|
|
97e1fbc198 | ||
|
|
ee6d16f1b1 | ||
|
|
22d2097132 | ||
|
|
c376de9b5e | ||
|
|
ab2ef1e1e4 | ||
|
|
18030fa61e | ||
|
|
064337b5d3 | ||
|
|
a6a6fc48c1 | ||
|
|
d72e9bb05b | ||
|
|
7a9fdb4acd | ||
|
|
a6d0464735 | ||
|
|
52f69cc7dc | ||
|
|
0beadde758 | ||
|
|
618abd63cf | ||
|
|
34b3ddf63b | ||
|
|
ca76cac314 | ||
|
|
3a16ad89bd | ||
|
|
fb9369f333 | ||
|
|
66897611d9 | ||
|
|
6888554e9d | ||
|
|
f28a971b96 | ||
|
|
18808d0a61 | ||
|
|
86369148ee | ||
|
|
f45ee2ab4d | ||
|
|
2b73fab913 | ||
|
|
e0d750ac64 | ||
|
|
bb57c6e7b7 | ||
|
|
f346a052c1 | ||
|
|
3933353b5f | ||
|
|
6c9c21c135 | ||
|
|
d02a721eed | ||
|
|
8ffb4ae127 | ||
|
|
96fbeb583b | ||
|
|
33b98a15d3 | ||
|
|
e523ebe3c1 | ||
|
|
e17c671b7c | ||
|
|
e7565e1a2a | ||
|
|
b73d6377fc | ||
|
|
31f5fffc45 | ||
|
|
64c518c2f2 | ||
|
|
1ed543b0e8 | ||
|
|
8b7cd2dd1a | ||
|
|
8520b5211a | ||
|
|
03661e2a71 | ||
|
|
20b82b3638 | ||
|
|
cb499ae502 | ||
|
|
02b73207f9 | ||
|
|
53b5cbc12a | ||
|
|
f4c6decd2d | ||
|
|
69f1497986 | ||
|
|
2b46f01fe3 | ||
|
|
dd4adb57cf | ||
|
|
452bce07e1 | ||
|
|
8d702d0b77 | ||
|
|
e1dc4b69f5 | ||
|
|
6cd3580263 | ||
|
|
d5383aecc9 | ||
|
|
71cbbab2c9 | ||
|
|
8518d8f456 | ||
|
|
adc0db04bc | ||
|
|
c61fc59003 | ||
|
|
40f9072250 | ||
|
|
ea30bb351e | ||
|
|
b93550f6c8 | ||
|
|
60bd053095 | ||
|
|
8165b76001 | ||
|
|
efc563f5ff | ||
|
|
e52acc994c | ||
|
|
646833d3ec | ||
|
|
fd72c27afe | ||
|
|
c13bcc25c6 | ||
|
|
21c9ff6c85 | ||
|
|
4d6b367654 | ||
|
|
e2fd22a78e | ||
|
|
0759bdde01 | ||
|
|
faa03e0e14 | ||
|
|
f70897a6d3 | ||
|
|
ba231d2c5f | ||
|
|
095cb759ed | ||
|
|
5cbcb76039 | ||
|
|
3388b42f20 | ||
|
|
e1d541b02e | ||
|
|
cb784615ee | ||
|
|
321c5e049b | ||
|
|
ed7cf218f8 | ||
|
|
74d8368525 | ||
|
|
dcbfa272f9 | ||
|
|
42dd600e0c | ||
|
|
9689df601f | ||
|
|
f6019583b7 | ||
|
|
2d50a3335d | ||
|
|
202bfa987d | ||
|
|
93f9c7cfbd | ||
|
|
8f6a0bbf09 | ||
|
|
1a47c148e5 | ||
|
|
2435ba1ea0 | ||
|
|
9ba57a923b | ||
|
|
90e2b6f26b | ||
|
|
05f9f454c3 | ||
|
|
b85f59798c | ||
|
|
e80345a05b | ||
|
|
0bdcc3d616 | ||
|
|
987f12740e | ||
|
|
1265016a55 | ||
|
|
48d1de3678 | ||
|
|
43f6db3252 | ||
|
|
5b917e7d10 | ||
|
|
0523868a88 | ||
|
|
26f176eb7e | ||
|
|
ad32b5ca8f | ||
|
|
dbf14179dc | ||
|
|
45b0a4ec27 | ||
|
|
1969ee02a5 | ||
|
|
266b205c75 | ||
|
|
7dd3bad8bd | ||
|
|
4b45be7cda | ||
|
|
497ffd86fa | ||
|
|
0bdcc4269f | ||
|
|
e583c99f94 | ||
|
|
e22e50c3fa | ||
|
|
e9c9a3e1ce | ||
|
|
fa7bb71f3f | ||
|
|
34a3ad82e0 | ||
|
|
2f5d74dbf4 | ||
|
|
4bf5ba594c | ||
|
|
6e2da27f45 | ||
|
|
6ee9465d43 | ||
|
|
391a6bf422 | ||
|
|
5001a0e37d | ||
|
|
fd8d16a7db | ||
|
|
f3ac9306f3 | ||
|
|
59740d0b56 | ||
|
|
fb05a6c26f | ||
|
|
7943b708d2 | ||
|
|
04e37d1eca | ||
|
|
91b98e8c6d | ||
|
|
70234e5b19 | ||
|
|
ceff85d892 | ||
|
|
9f914dd42e | ||
|
|
24f5d68fef | ||
|
|
735fc325b1 | ||
|
|
178b216e48 | ||
|
|
5d0481f7a2 | ||
|
|
711bc69750 | ||
|
|
7263c9490d | ||
|
|
0c88bc6ac7 | ||
|
|
fda8d0a2e2 | ||
|
|
14bdf7fae8 | ||
|
|
d4ff7ecbaa | ||
|
|
030aec7373 | ||
|
|
6a351de4f9 | ||
|
|
bbff1c9c3e | ||
|
|
bbb8144129 | ||
|
|
83f3e23297 | ||
|
|
4f880932ae | ||
|
|
62e8c2497c | ||
|
|
37f854be3e | ||
|
|
e0e82e1877 | ||
|
|
95d8665dbe | ||
|
|
8667de994e | ||
|
|
cee0e22ce7 | ||
|
|
dc8a2f54e5 | ||
|
|
b3bc5b2520 | ||
|
|
00e929afac |
@@ -4,6 +4,9 @@ executors:
|
|||||||
docker:
|
docker:
|
||||||
- image: filecoin/rust:latest
|
- image: filecoin/rust:latest
|
||||||
working_directory: /mnt/crate
|
working_directory: /mnt/crate
|
||||||
|
doxygen:
|
||||||
|
docker:
|
||||||
|
- image: hrektts/doxygen
|
||||||
|
|
||||||
restore-workspace: &restore-workspace
|
restore-workspace: &restore-workspace
|
||||||
attach_workspace:
|
attach_workspace:
|
||||||
@@ -12,7 +15,7 @@ restore-workspace: &restore-workspace
|
|||||||
restore-cache: &restore-cache
|
restore-cache: &restore-cache
|
||||||
restore_cache:
|
restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- cargo-v1-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
- cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||||
- repo-source-{{ .Branch }}-{{ .Revision }}
|
- repo-source-{{ .Branch }}-{{ .Revision }}
|
||||||
|
|
||||||
commands:
|
commands:
|
||||||
@@ -23,20 +26,9 @@ commands:
|
|||||||
steps:
|
steps:
|
||||||
- *restore-workspace
|
- *restore-workspace
|
||||||
- *restore-cache
|
- *restore-cache
|
||||||
- setup_remote_docker:
|
|
||||||
docker_layer_caching: true
|
|
||||||
# TODO: move into image
|
|
||||||
- run:
|
|
||||||
name: Install Docker client
|
|
||||||
command: |
|
|
||||||
set -x
|
|
||||||
VER="18.09.2"
|
|
||||||
curl -L -o /tmp/docker-$VER.tgz https://download.docker.com/linux/static/stable/x86_64/docker-$VER.tgz
|
|
||||||
tar -xz -C /tmp -f /tmp/docker-$VER.tgz
|
|
||||||
mv /tmp/docker/* /usr/bin
|
|
||||||
- run:
|
- run:
|
||||||
name: Test (<< parameters.target >>)
|
name: Test (<< parameters.target >>)
|
||||||
command: TARGET=<< parameters.target >> ci/run.sh
|
command: TARGET=<< parameters.target >> ci_scripts/run-rust-test.sh
|
||||||
no_output_timeout: 15m
|
no_output_timeout: 15m
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -52,7 +44,7 @@ jobs:
|
|||||||
command: cargo generate-lockfile
|
command: cargo generate-lockfile
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- cargo-v1-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
- cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||||
- run: rustup install $(cat rust-toolchain)
|
- run: rustup install $(cat rust-toolchain)
|
||||||
- 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
|
||||||
@@ -61,13 +53,14 @@ jobs:
|
|||||||
- 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
|
||||||
- run: rm -rf .git
|
# make sure this git repo doesn't grow too big
|
||||||
|
- run: git gc
|
||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
root: /mnt
|
root: /mnt
|
||||||
paths:
|
paths:
|
||||||
- crate
|
- crate
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: cargo-v1-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
key: cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||||
paths:
|
paths:
|
||||||
- "~/.cargo"
|
- "~/.cargo"
|
||||||
- "~/.rustup"
|
- "~/.rustup"
|
||||||
@@ -102,7 +95,7 @@ jobs:
|
|||||||
- run: cargo fetch
|
- run: cargo fetch
|
||||||
- run:
|
- run:
|
||||||
name: Test
|
name: Test
|
||||||
command: TARGET=x86_64-apple-darwin ci/run.sh
|
command: TARGET=x86_64-apple-darwin ci_scripts/run-rust-test.sh
|
||||||
|
|
||||||
test_x86_64-unknown-linux-gnu:
|
test_x86_64-unknown-linux-gnu:
|
||||||
executor: default
|
executor: default
|
||||||
@@ -123,19 +116,31 @@ jobs:
|
|||||||
target: "aarch64-linux-android"
|
target: "aarch64-linux-android"
|
||||||
|
|
||||||
|
|
||||||
build_test_docs_wheel:
|
build_doxygen:
|
||||||
machine: True
|
executor: doxygen
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
# - run: docker pull deltachat/doxygen
|
- run: bash ci_scripts/run-doxygen.sh
|
||||||
- run: docker pull deltachat/coredeps
|
- run: mkdir -p workspace/c-docs
|
||||||
- run:
|
- run: cp -av deltachat-ffi/{html,xml} workspace/c-docs/
|
||||||
name: build docs, run tests and build wheels
|
- persist_to_workspace:
|
||||||
command: ci_scripts/ci_run.sh
|
root: workspace
|
||||||
environment:
|
paths:
|
||||||
|
- c-docs
|
||||||
|
|
||||||
|
build_test_docs_wheel:
|
||||||
|
docker:
|
||||||
|
- image: deltachat/coredeps
|
||||||
|
environment:
|
||||||
TESTS: 1
|
TESTS: 1
|
||||||
DOCS: 1
|
DOCS: 1
|
||||||
|
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:
|
- run:
|
||||||
name: copying docs and wheels to workspace
|
name: copying docs and wheels to workspace
|
||||||
command: |
|
command: |
|
||||||
@@ -143,7 +148,6 @@ jobs:
|
|||||||
# cp -av docs workspace/c-docs
|
# cp -av docs workspace/c-docs
|
||||||
cp -av python/.docker-tox/wheelhouse workspace/
|
cp -av python/.docker-tox/wheelhouse workspace/
|
||||||
cp -av python/doc/_build/ workspace/py-docs
|
cp -av python/doc/_build/ workspace/py-docs
|
||||||
|
|
||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
root: workspace
|
root: workspace
|
||||||
paths:
|
paths:
|
||||||
@@ -159,7 +163,7 @@ jobs:
|
|||||||
at: workspace
|
at: workspace
|
||||||
- run: pyenv global 3.5.2
|
- run: pyenv global 3.5.2
|
||||||
- run: ls -laR workspace
|
- run: ls -laR workspace
|
||||||
- run: ci_scripts/ci_upload.sh workspace/py-docs workspace/wheelhouse
|
- run: ci_scripts/ci_upload.sh workspace/py-docs workspace/wheelhouse workspace/c-docs
|
||||||
|
|
||||||
clippy:
|
clippy:
|
||||||
executor: default
|
executor: default
|
||||||
@@ -176,18 +180,22 @@ workflows:
|
|||||||
|
|
||||||
test:
|
test:
|
||||||
jobs:
|
jobs:
|
||||||
- build_test_docs_wheel
|
- cargo_fetch
|
||||||
|
- build_doxygen
|
||||||
|
|
||||||
|
- build_test_docs_wheel:
|
||||||
|
requires:
|
||||||
|
- cargo_fetch
|
||||||
- upload_docs_wheels:
|
- upload_docs_wheels:
|
||||||
requires:
|
requires:
|
||||||
- build_test_docs_wheel
|
- build_test_docs_wheel
|
||||||
- cargo_fetch
|
- build_doxygen
|
||||||
- rustfmt:
|
- rustfmt:
|
||||||
requires:
|
requires:
|
||||||
- cargo_fetch
|
- cargo_fetch
|
||||||
|
|
||||||
- clippy:
|
- clippy:
|
||||||
requires:
|
requires:
|
||||||
- cargo_fetch
|
- cargo_fetch
|
||||||
|
|
||||||
# Linux Desktop 64bit
|
# Linux Desktop 64bit
|
||||||
- test_x86_64-unknown-linux-gnu:
|
- test_x86_64-unknown-linux-gnu:
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -16,6 +16,7 @@ python/.tox
|
|||||||
*.egg-info
|
*.egg-info
|
||||||
__pycache__
|
__pycache__
|
||||||
python/src/deltachat/capi*.so
|
python/src/deltachat/capi*.so
|
||||||
|
python/.venv/
|
||||||
|
|
||||||
python/liveconfig*
|
python/liveconfig*
|
||||||
|
|
||||||
|
|||||||
44
CHANGELOG.md
Normal file
44
CHANGELOG.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 1.0.0-beta2
|
||||||
|
|
||||||
|
- https://c.delta.chat docs are now regenerated again through our CI
|
||||||
|
|
||||||
|
- several rPGP cleanups, security fixes and better multi-platform support
|
||||||
|
|
||||||
|
- reconnect on io errors and broken pipes (imap)
|
||||||
|
|
||||||
|
- probe SMTP with real connection not just setup
|
||||||
|
|
||||||
|
- various imap/smtp related fixes
|
||||||
|
|
||||||
|
- use to_string_lossy in most places instead of relying on valid utf-8
|
||||||
|
encodings
|
||||||
|
|
||||||
|
- rework, rustify and test autoconfig-reading and parsing
|
||||||
|
|
||||||
|
- some rustifications/boolifications of c-ints
|
||||||
|
|
||||||
|
|
||||||
|
## 1.0.0-beta1
|
||||||
|
|
||||||
|
- first beta of the Delta Chat Rust core library. many fixes of crashes
|
||||||
|
and other issues compared to 1.0.0-alpha.5.
|
||||||
|
|
||||||
|
- Most code is now "rustified" and does not do manual memory allocation anymore.
|
||||||
|
|
||||||
|
- The `DC_EVENT_GET_STRING` event is not used anymore, removing the last
|
||||||
|
event where the core requested a return value from the event callback.
|
||||||
|
|
||||||
|
Please now use `dc_set_stock_translation()` API for core messages
|
||||||
|
to be properly localized.
|
||||||
|
|
||||||
|
- Deltachat FFI docs are automatically generated and available here:
|
||||||
|
https://c.delta.chat
|
||||||
|
|
||||||
|
- New events ImapMessageMoved and ImapMessageDeleted
|
||||||
|
|
||||||
|
For a full list of changes, please see our closed Pull Requests:
|
||||||
|
|
||||||
|
https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
|
||||||
|
|
||||||
800
Cargo.lock
generated
800
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
19
Cargo.toml
19
Cargo.toml
@@ -1,26 +1,26 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat"
|
name = "deltachat"
|
||||||
version = "1.0.0-alpha.4"
|
version = "1.0.0-beta.2"
|
||||||
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
|
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.2", default-features = false }
|
pgp = { version = "0.2.3", default-features = false }
|
||||||
hex = "0.3.2"
|
hex = "0.3.2"
|
||||||
sha2 = "0.8.0"
|
sha2 = "0.8.0"
|
||||||
rand = "0.6.5"
|
rand = "0.6.5"
|
||||||
phf = { git = "https://github.com/sfackler/rust-phf", rev = "0d00821", features = ["macros"] }
|
|
||||||
smallvec = "0.6.9"
|
smallvec = "0.6.9"
|
||||||
reqwest = "0.9.15"
|
reqwest = "0.9.15"
|
||||||
num-derive = "0.2.5"
|
num-derive = "0.2.5"
|
||||||
num-traits = "0.2.6"
|
num-traits = "0.2.6"
|
||||||
native-tls = "0.2.3"
|
native-tls = "0.2.3"
|
||||||
lettre = "0.9.0"
|
lettre = { git = "https://github.com/deltachat/lettre", branch = "master" }
|
||||||
imap = { git = "https://github.com/jonhoo/rust-imap", rev = "281d2eb8ab50dc656ceff2ae749ca5045f334e15" }
|
imap = { git = "https://github.com/jonhoo/rust-imap", branch = "master" }
|
||||||
mmime = { git = "https://github.com/dignifiedquire/mmime", rev = "bccd2c2" }
|
|
||||||
base64 = "0.10"
|
base64 = "0.10"
|
||||||
charset = "0.1"
|
charset = "0.1"
|
||||||
percent-encoding = "2.0"
|
percent-encoding = "2.0"
|
||||||
@@ -36,8 +36,8 @@ regex = "1.1.6"
|
|||||||
rusqlite = { version = "0.20", features = ["bundled"] }
|
rusqlite = { version = "0.20", features = ["bundled"] }
|
||||||
r2d2_sqlite = "0.12.0"
|
r2d2_sqlite = "0.12.0"
|
||||||
r2d2 = "0.8.5"
|
r2d2 = "0.8.5"
|
||||||
strum = "0.15.0"
|
strum = "0.16.0"
|
||||||
strum_macros = "0.15.0"
|
strum_macros = "0.16.0"
|
||||||
thread-local-object = "0.1.0"
|
thread-local-object = "0.1.0"
|
||||||
backtrace = "0.3.33"
|
backtrace = "0.3.33"
|
||||||
byteorder = "1.3.1"
|
byteorder = "1.3.1"
|
||||||
@@ -59,6 +59,7 @@ proptest = "0.9.4"
|
|||||||
members = [
|
members = [
|
||||||
"deltachat-ffi",
|
"deltachat-ffi",
|
||||||
"deltachat_derive",
|
"deltachat_derive",
|
||||||
|
"mmime",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
[dependencies.std]
|
|
||||||
features = ["panic-unwind"]
|
|
||||||
|
|
||||||
# if using `cargo test`
|
|
||||||
[dependencies.test]
|
|
||||||
stage = 1
|
|
||||||
@@ -13,7 +13,7 @@ install:
|
|||||||
build: false
|
build: false
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- cargo test --release
|
- cargo test --release --all
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
- target
|
- target
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
# perform CI jobs on PRs and after merges to master.
|
|
||||||
# triggered from .circleci/config.yml
|
|
||||||
|
|
||||||
set -e -x
|
|
||||||
|
|
||||||
export BRANCH=${CIRCLE_BRANCH:-test7}
|
|
||||||
|
|
||||||
# run doxygen on c-source (needed by later doc-generation steps).
|
|
||||||
# XXX modifies the host filesystem docs/xml and docs/html directories
|
|
||||||
# XXX which you can then only remove with sudo as they belong to root
|
|
||||||
|
|
||||||
# XXX we don't do doxygen doc generation with Rust anymore, needs to be
|
|
||||||
# substituted with rust-docs
|
|
||||||
#if [ -n "$DOCS" ] ; then
|
|
||||||
# docker run --rm -it -v $PWD:/mnt -w /mnt/docs deltachat/doxygen doxygen
|
|
||||||
#fi
|
|
||||||
|
|
||||||
# run everything else inside docker (TESTS, DOCS, WHEELS)
|
|
||||||
docker run -e DCC_PY_LIVECONFIG -e BRANCH -e TESTS -e DOCS \
|
|
||||||
--rm -it -v $(pwd):/mnt -w /mnt \
|
|
||||||
deltachat/coredeps ci_scripts/run_all.sh
|
|
||||||
|
|
||||||
@@ -7,9 +7,9 @@ fi
|
|||||||
|
|
||||||
set -xe
|
set -xe
|
||||||
|
|
||||||
#DOXYDOCDIR=${1:?directory where doxygen docs to be found}
|
|
||||||
PYDOCDIR=${1:?directory with python docs}
|
PYDOCDIR=${1:?directory with python docs}
|
||||||
WHEELHOUSEDIR=${2:?directory with pre-built wheels}
|
WHEELHOUSEDIR=${2:?directory with pre-built wheels}
|
||||||
|
DOXYDOCDIR=${3:?directory where doxygen docs to be found}
|
||||||
|
|
||||||
export BRANCH=${CIRCLE_BRANCH:?specify branch for uploading purposes}
|
export BRANCH=${CIRCLE_BRANCH:?specify branch for uploading purposes}
|
||||||
|
|
||||||
@@ -22,10 +22,11 @@ rsync -avz \
|
|||||||
delta@py.delta.chat:build/${BRANCH}
|
delta@py.delta.chat:build/${BRANCH}
|
||||||
|
|
||||||
# C docs to c.delta.chat
|
# C docs to c.delta.chat
|
||||||
#rsync -avz \
|
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@c.delta.chat mkdir -p build-c/${BRANCH}
|
||||||
# -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
|
rsync -avz \
|
||||||
# "$DOXYDOCDIR/html/" \
|
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
|
||||||
# delta@py.delta.chat:build-c/${BRANCH}
|
"$DOXYDOCDIR/html/" \
|
||||||
|
delta@c.delta.chat:build-c/${BRANCH}
|
||||||
|
|
||||||
echo -----------------------
|
echo -----------------------
|
||||||
echo upload wheels
|
echo upload wheels
|
||||||
|
|||||||
7
ci_scripts/run-doxygen.sh
Executable file
7
ci_scripts/run-doxygen.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
cd deltachat-ffi
|
||||||
|
doxygen
|
||||||
|
|
||||||
@@ -36,20 +36,25 @@ if [ -n "$TESTS" ]; then
|
|||||||
rm -rf src/deltachat/__pycache__
|
rm -rf src/deltachat/__pycache__
|
||||||
export PYTHONDONTWRITEBYTECODE=1
|
export PYTHONDONTWRITEBYTECODE=1
|
||||||
|
|
||||||
# run tox
|
# run tox. The circle-ci project env-var-setting DCC_PY_LIVECONFIG
|
||||||
# XXX we don't run liveconfig tests because they hang sometimes
|
# allows running of "liveconfig" tests but for speed reasons
|
||||||
# see https://github.com/deltachat/deltachat-core-rust/issues/331
|
# we run them only for the highest python version we support
|
||||||
# unset DCC_PY_LIVECONFIG
|
|
||||||
|
# we split out qr-tests run to minimize likelyness of flaky tests
|
||||||
tox --workdir "$TOXWORKDIR" -e lint,py35,py36,py37,auditwheels -- -k "not qr"
|
# (some qr tests are pretty heavy in terms of send/received
|
||||||
tox --workdir "$TOXWORKDIR" -e py35,py36,py37 -- -k "qr"
|
# messages and rust's imap code likely has concurrency problems)
|
||||||
|
tox --workdir "$TOXWORKDIR" -e py37 -- -k "not qr"
|
||||||
|
tox --workdir "$TOXWORKDIR" -e py37 -- -k "qr"
|
||||||
|
unset DCC_PY_LIVECONFIG
|
||||||
|
tox --workdir "$TOXWORKDIR" -p4 -e lint,py35,py36,doc
|
||||||
|
tox --workdir "$TOXWORKDIR" -e auditwheels
|
||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
if [ -n "$DOCS" ]; then
|
# if [ -n "$DOCS" ]; then
|
||||||
echo -----------------------
|
# echo -----------------------
|
||||||
echo generating python docs
|
# echo generating python docs
|
||||||
echo -----------------------
|
# echo -----------------------
|
||||||
(cd python && tox --workdir "$TOXWORKDIR" -e doc)
|
# (cd python && tox --workdir "$TOXWORKDIR" -e doc)
|
||||||
fi
|
# fi
|
||||||
@@ -31,7 +31,7 @@ fi
|
|||||||
if [[ $NORUN == "1" ]]; then
|
if [[ $NORUN == "1" ]]; then
|
||||||
export CARGO_SUBCMD="build"
|
export CARGO_SUBCMD="build"
|
||||||
else
|
else
|
||||||
export CARGO_SUBCMD="test"
|
export CARGO_SUBCMD="test --all"
|
||||||
export OPT="${OPT} "
|
export OPT="${OPT} "
|
||||||
export OPT_RELEASE="${OPT_RELEASE} "
|
export OPT_RELEASE="${OPT_RELEASE} "
|
||||||
export OPT_RELEASE_IGNORED="${OPT_RELEASE} -- --ignored"
|
export OPT_RELEASE_IGNORED="${OPT_RELEASE} -- --ignored"
|
||||||
@@ -41,6 +41,3 @@ fi
|
|||||||
$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
|
||||||
|
|
||||||
# Build the ffi lib
|
|
||||||
$CARGO_CMD $CARGO_SUBCMD $OPT_FFI_RELEASE
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat_ffi"
|
name = "deltachat_ffi"
|
||||||
version = "1.0.0-alpha.4"
|
version = "1.0.0-beta.2"
|
||||||
description = "Deltachat FFI"
|
description = "Deltachat FFI"
|
||||||
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
|
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
@@ -16,6 +16,7 @@ crate-type = ["cdylib", "staticlib"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
deltachat = { path = "../", default-features = false }
|
deltachat = { path = "../", default-features = false }
|
||||||
|
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"
|
||||||
|
|||||||
@@ -11,6 +11,16 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct _dc_context dc_context_t;
|
||||||
|
typedef struct _dc_array dc_array_t;
|
||||||
|
typedef struct _dc_chatlist dc_chatlist_t;
|
||||||
|
typedef struct _dc_chat dc_chat_t;
|
||||||
|
typedef struct _dc_msg dc_msg_t;
|
||||||
|
typedef struct _dc_contact dc_contact_t;
|
||||||
|
typedef struct _dc_lot dc_lot_t;
|
||||||
|
typedef struct _dc_provider dc_provider_t;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @mainpage Getting started
|
* @mainpage Getting started
|
||||||
*
|
*
|
||||||
@@ -68,8 +78,7 @@ extern "C" {
|
|||||||
*
|
*
|
||||||
* The example above uses "pthreads",
|
* The example above uses "pthreads",
|
||||||
* however, you can also use anything else for thread handling.
|
* however, you can also use anything else for thread handling.
|
||||||
* NB: The deltachat-core library itself does not create any threads on its own,
|
* All deltachat-core-functions, unless stated otherwise, are thread-safe.
|
||||||
* however, functions, unless stated otherwise, are thread-safe.
|
|
||||||
*
|
*
|
||||||
* After that you can **define and open a database.**
|
* After that you can **define and open a database.**
|
||||||
* The database is a normal sqlite-file and is created as needed:
|
* The database is a normal sqlite-file and is created as needed:
|
||||||
@@ -125,7 +134,7 @@ extern "C" {
|
|||||||
*
|
*
|
||||||
* printf("Message %i: %s\n", i+1, text);
|
* printf("Message %i: %s\n", i+1, text);
|
||||||
*
|
*
|
||||||
* free(text);
|
* dc_str_unref(text);
|
||||||
* dc_msg_unref(msg);
|
* dc_msg_unref(msg);
|
||||||
* }
|
* }
|
||||||
* dc_array_unref(msglist);
|
* dc_array_unref(msglist);
|
||||||
@@ -189,13 +198,6 @@ extern "C" {
|
|||||||
* SQLite database for offline functionality and for account-related
|
* SQLite database for offline functionality and for account-related
|
||||||
* settings.
|
* settings.
|
||||||
*/
|
*/
|
||||||
typedef struct _dc_context dc_context_t;
|
|
||||||
typedef struct _dc_array dc_array_t;
|
|
||||||
typedef struct _dc_chatlist dc_chatlist_t;
|
|
||||||
typedef struct _dc_chat dc_chat_t;
|
|
||||||
typedef struct _dc_msg dc_msg_t;
|
|
||||||
typedef struct _dc_contact dc_contact_t;
|
|
||||||
typedef struct _dc_lot dc_lot_t;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -318,7 +320,7 @@ int dc_is_open (const dc_context_t* context);
|
|||||||
* @memberof dc_context_t
|
* @memberof dc_context_t
|
||||||
* @param context The context object as created by dc_context_new().
|
* @param context The context object as created by dc_context_new().
|
||||||
* @return Blob directory associated with the context object, empty string if unset or on errors. NULL is never returned.
|
* @return Blob directory associated with the context object, empty string if unset or on errors. NULL is never returned.
|
||||||
* The returned string must be free()'d.
|
* The returned string must be released using dc_str_unref().
|
||||||
*/
|
*/
|
||||||
char* dc_get_blobdir (const dc_context_t* context);
|
char* dc_get_blobdir (const dc_context_t* context);
|
||||||
|
|
||||||
@@ -394,20 +396,38 @@ int dc_set_config (dc_context_t* context, const char*
|
|||||||
* @param context The context object as created by dc_context_new(). For querying system values, this can be NULL.
|
* @param context The context object as created by dc_context_new(). For querying system values, this can be NULL.
|
||||||
* @param key The key to query.
|
* @param key The key to query.
|
||||||
* @return Returns current value of "key", if "key" is unset, the default
|
* @return Returns current value of "key", if "key" is unset, the default
|
||||||
* value is returned. The returned value must be free()'d, NULL is never
|
* value is returned. The returned value must be released using dc_str_unref(), NULL is never
|
||||||
* returned. If there is an error an empty string will be returned.
|
* returned. If there is an error an empty string will be returned.
|
||||||
*/
|
*/
|
||||||
char* dc_get_config (dc_context_t* context, const char* key);
|
char* dc_get_config (dc_context_t* context, const char* key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set stock string translation.
|
||||||
|
*
|
||||||
|
* The function will emit warnings if it returns an error state.
|
||||||
|
*
|
||||||
|
* @memberof dc_context_t
|
||||||
|
* @param context The context object
|
||||||
|
* @param stock_id the integer id of the stock message (DC_STR_*)
|
||||||
|
* @param stock_msg the message to be used
|
||||||
|
* @return int (==0 on error, 1 on success)
|
||||||
|
*/
|
||||||
|
int dc_set_stock_translation(dc_context_t* context, uint32_t stock_id, const char* stock_msg);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get information about the context.
|
* Get information about the context.
|
||||||
|
*
|
||||||
* The information is returned by a multi-line string
|
* The information is returned by a multi-line string
|
||||||
* and contains information about the current configuration.
|
* and contains information about the current configuration.
|
||||||
*
|
*
|
||||||
|
* If the context is not open or configured only a subset of the information
|
||||||
|
* will be available. There is no guarantee about which information will be
|
||||||
|
* included when however.
|
||||||
|
*
|
||||||
* @memberof dc_context_t
|
* @memberof dc_context_t
|
||||||
* @param context The context as created by dc_context_new().
|
* @param context The context as created by dc_context_new().
|
||||||
* @return String which must be free()'d after usage. Never returns NULL.
|
* @return String which must be released using dc_str_unref() after usage. Never returns NULL.
|
||||||
*/
|
*/
|
||||||
char* dc_get_info (dc_context_t* context);
|
char* dc_get_info (dc_context_t* context);
|
||||||
|
|
||||||
@@ -438,12 +458,12 @@ char* dc_get_info (dc_context_t* context);
|
|||||||
* `https://localhost:PORT/PATH`, `urn:ietf:wg:oauth:2.0:oob`
|
* `https://localhost:PORT/PATH`, `urn:ietf:wg:oauth:2.0:oob`
|
||||||
* (the latter just displays the code the user can copy+paste then)
|
* (the latter just displays the code the user can copy+paste then)
|
||||||
* @return URL that can be opened in the browser to start OAuth2.
|
* @return URL that can be opened in the browser to start OAuth2.
|
||||||
|
* Returned strings must be released using dc_str_unref().
|
||||||
* If OAuth2 is not possible for the given e-mail-address, NULL is returned.
|
* If OAuth2 is not possible for the given e-mail-address, NULL is returned.
|
||||||
*/
|
*/
|
||||||
char* dc_get_oauth2_url (dc_context_t* context, const char* addr, const char* redirect_uri);
|
char* dc_get_oauth2_url (dc_context_t* context, const char* addr, const char* redirect_uri);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// connect
|
// connect
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -608,12 +628,8 @@ void dc_interrupt_imap_idle (dc_context_t* context);
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch new messages from the MVBOX, if any.
|
* Execute pending mvbox-jobs.
|
||||||
* The MVBOX is a folder on the account where chat messages are moved to.
|
* This function and dc_perform_mvbox_fetch() and dc_perform_mvbox_idle()
|
||||||
* The moving is done to not disturb shared accounts that are used by both,
|
|
||||||
* Delta Chat and a classical MUA.
|
|
||||||
*
|
|
||||||
* This function and dc_perform_mvbox_idle()
|
|
||||||
* must be called from the same thread, typically in a loop.
|
* must be called from the same thread, typically in a loop.
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
@@ -621,6 +637,7 @@ void dc_interrupt_imap_idle (dc_context_t* context);
|
|||||||
* void* mvbox_thread_func(void* context)
|
* void* mvbox_thread_func(void* context)
|
||||||
* {
|
* {
|
||||||
* while (true) {
|
* while (true) {
|
||||||
|
* dc_perform_mvbox_jobs(context);
|
||||||
* dc_perform_mvbox_fetch(context);
|
* dc_perform_mvbox_fetch(context);
|
||||||
* dc_perform_mvbox_idle(context);
|
* dc_perform_mvbox_idle(context);
|
||||||
* }
|
* }
|
||||||
@@ -634,13 +651,26 @@ void dc_interrupt_imap_idle (dc_context_t* context);
|
|||||||
*
|
*
|
||||||
* // network becomes available again -
|
* // network becomes available again -
|
||||||
* // the interrupt causes dc_perform_mvbox_idle() in the thread above
|
* // the interrupt causes dc_perform_mvbox_idle() in the thread above
|
||||||
* // to return so that and messages are fetched.
|
* // to return so that jobs are executed and messages are fetched.
|
||||||
* dc_maybe_network(context);
|
* dc_maybe_network(context);
|
||||||
*
|
*
|
||||||
* @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().
|
||||||
* @return None.
|
* @return None.
|
||||||
*/
|
*/
|
||||||
|
void dc_perform_mvbox_jobs (dc_context_t* context);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch new messages from the MVBOX, if any.
|
||||||
|
* The MVBOX is a folder on the account where chat messages are moved to.
|
||||||
|
* The moving is done to not disturb shared accounts that are used by both,
|
||||||
|
* Delta Chat and a classical MUA.
|
||||||
|
*
|
||||||
|
* @memberof dc_context_t
|
||||||
|
* @param context The context as created by dc_context_new().
|
||||||
|
* @return None.
|
||||||
|
*/
|
||||||
void dc_perform_mvbox_fetch (dc_context_t* context);
|
void dc_perform_mvbox_fetch (dc_context_t* context);
|
||||||
|
|
||||||
|
|
||||||
@@ -678,6 +708,40 @@ void dc_perform_mvbox_idle (dc_context_t* context);
|
|||||||
void dc_interrupt_mvbox_idle (dc_context_t* context);
|
void dc_interrupt_mvbox_idle (dc_context_t* context);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute pending sentbox-jobs.
|
||||||
|
* This function and dc_perform_sentbox_fetch() and dc_perform_sentbox_idle()
|
||||||
|
* must be called from the same thread, typically in a loop.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* void* sentbox_thread_func(void* context)
|
||||||
|
* {
|
||||||
|
* while (true) {
|
||||||
|
* dc_perform_sentbox_jobs(context);
|
||||||
|
* dc_perform_sentbox_fetch(context);
|
||||||
|
* dc_perform_sentbox_idle(context);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // start sentbox-thread that runs forever
|
||||||
|
* pthread_t sentbox_thread;
|
||||||
|
* pthread_create(&sentbox_thread, NULL, sentbox_thread_func, context);
|
||||||
|
*
|
||||||
|
* ... program runs ...
|
||||||
|
*
|
||||||
|
* // network becomes available again -
|
||||||
|
* // the interrupt causes dc_perform_sentbox_idle() in the thread above
|
||||||
|
* // to return so that jobs are executed and messages are fetched.
|
||||||
|
* dc_maybe_network(context);
|
||||||
|
*
|
||||||
|
* @memberof dc_context_t
|
||||||
|
* @param context The context as created by dc_context_new().
|
||||||
|
* @return None.
|
||||||
|
*/
|
||||||
|
void dc_perform_sentbox_jobs (dc_context_t* context);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch new messages from the Sent folder, if any.
|
* Fetch new messages from the Sent folder, if any.
|
||||||
* This function and dc_perform_sentbox_idle()
|
* This function and dc_perform_sentbox_idle()
|
||||||
@@ -1415,7 +1479,7 @@ int dc_set_chat_profile_image (dc_context_t* context, uint32_t ch
|
|||||||
* @memberof dc_context_t
|
* @memberof dc_context_t
|
||||||
* @param context The context object as created by dc_context_new().
|
* @param context The context object as created by dc_context_new().
|
||||||
* @param msg_id The message id for which information should be generated
|
* @param msg_id The message id for which information should be generated
|
||||||
* @return Text string, must be free()'d after usage
|
* @return Text string, must be released using dc_str_unref() after usage
|
||||||
*/
|
*/
|
||||||
char* dc_get_msg_info (dc_context_t* context, uint32_t msg_id);
|
char* dc_get_msg_info (dc_context_t* context, uint32_t msg_id);
|
||||||
|
|
||||||
@@ -1429,7 +1493,7 @@ char* dc_get_msg_info (dc_context_t* context, uint32_t ms
|
|||||||
* @memberof dc_context_t
|
* @memberof dc_context_t
|
||||||
* @param context The context object as created by dc_context_new().
|
* @param context The context object as created by dc_context_new().
|
||||||
* @param msg_id The message id, must be the id of an incoming message.
|
* @param msg_id The message id, must be the id of an incoming message.
|
||||||
* @return Raw headers as a multi-line string, must be free()'d after usage.
|
* @return Raw headers as a multi-line string, must be released using dc_str_unref() after usage.
|
||||||
* Returns NULL if there are no headers saved for the given message,
|
* Returns NULL if there are no headers saved for the given message,
|
||||||
* eg. because of save_mime_headers is not set
|
* eg. because of save_mime_headers is not set
|
||||||
* or the message is not incoming.
|
* or the message is not incoming.
|
||||||
@@ -1670,7 +1734,7 @@ void dc_block_contact (dc_context_t* context, uint32_t co
|
|||||||
* @memberof dc_context_t
|
* @memberof dc_context_t
|
||||||
* @param context The context object as created by dc_context_new().
|
* @param context The context object as created by dc_context_new().
|
||||||
* @param contact_id ID of the contact to get the encryption info for.
|
* @param contact_id ID of the contact to get the encryption info for.
|
||||||
* @return Multi-line text, must be free()'d after usage.
|
* @return Multi-line text, must be released using dc_str_unref() after usage.
|
||||||
*/
|
*/
|
||||||
char* dc_get_contact_encrinfo (dc_context_t* context, uint32_t contact_id);
|
char* dc_get_contact_encrinfo (dc_context_t* context, uint32_t contact_id);
|
||||||
|
|
||||||
@@ -1806,7 +1870,8 @@ void dc_imex (dc_context_t* context, int what, c
|
|||||||
* @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 dir Directory to search backups in.
|
* @param dir Directory to search backups in.
|
||||||
* @return String with the backup file, typically given to dc_imex(), returned strings must be free()'d.
|
* @return String with the backup file, typically given to dc_imex(),
|
||||||
|
* returned strings must be released using dc_str_unref().
|
||||||
* The function returns NULL if no backup was found.
|
* The function returns NULL if no backup was found.
|
||||||
*/
|
*/
|
||||||
char* dc_imex_has_backup (dc_context_t* context, const char* dir);
|
char* dc_imex_has_backup (dc_context_t* context, const char* dir);
|
||||||
@@ -1854,7 +1919,7 @@ char* dc_imex_has_backup (dc_context_t* context, const char*
|
|||||||
*
|
*
|
||||||
* @memberof dc_context_t
|
* @memberof dc_context_t
|
||||||
* @param context The context object.
|
* @param context The context object.
|
||||||
* @return The setup code. Must be free()'d after usage.
|
* @return The setup code. Must be released using dc_str_unref() after usage.
|
||||||
* On errors, eg. if the message could not be sent, NULL is returned.
|
* On errors, eg. if the message could not be sent, NULL is returned.
|
||||||
*/
|
*/
|
||||||
char* dc_initiate_key_transfer (dc_context_t* context);
|
char* dc_initiate_key_transfer (dc_context_t* context);
|
||||||
@@ -1959,7 +2024,7 @@ dc_lot_t* dc_check_qr (dc_context_t* context, const char*
|
|||||||
* If set to 0, the setup-Verified-contact-protocol is offered in the QR code.
|
* If set to 0, the setup-Verified-contact-protocol is offered in the QR code.
|
||||||
* @return Text that should go to the QR code,
|
* @return Text that should go to the QR code,
|
||||||
* On errors, an empty QR code is returned, NULL is never returned.
|
* On errors, an empty QR code is returned, NULL is never returned.
|
||||||
* The returned string must be free()'d after usage.
|
* The returned string must be released using dc_str_unref() after usage.
|
||||||
*/
|
*/
|
||||||
char* dc_get_securejoin_qr (dc_context_t* context, uint32_t chat_id);
|
char* dc_get_securejoin_qr (dc_context_t* context, uint32_t chat_id);
|
||||||
|
|
||||||
@@ -2129,6 +2194,21 @@ dc_array_t* dc_get_locations (dc_context_t* context, uint32_t cha
|
|||||||
void dc_delete_all_locations (dc_context_t* context);
|
void dc_delete_all_locations (dc_context_t* context);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release a string returned by another deltachat-core function.
|
||||||
|
* - Strings returned by any deltachat-core-function
|
||||||
|
* MUST NOT be released by the standard free() function;
|
||||||
|
* always use dc_str_unref() for this purpose.
|
||||||
|
* - dc_str_unref() MUST NOT be called for strings not returned by deltachat-core.
|
||||||
|
* - dc_str_unref() MUST NOT be called for other objectes returned by deltachat-core.
|
||||||
|
*
|
||||||
|
* @memberof dc_context_t
|
||||||
|
* @param str The string to release.
|
||||||
|
* @return None.
|
||||||
|
*/
|
||||||
|
void dc_str_unref (char* str);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class dc_array_t
|
* @class dc_array_t
|
||||||
*
|
*
|
||||||
@@ -2269,7 +2349,7 @@ uint32_t dc_array_get_msg_id (const dc_array_t* array, size_t in
|
|||||||
* @param index Index of the item. Must be between 0 and dc_array_get_cnt()-1.
|
* @param index Index of the item. Must be between 0 and dc_array_get_cnt()-1.
|
||||||
* @return Marker-character of the item at the given index.
|
* @return Marker-character of the item at the given index.
|
||||||
* NULL if there is no marker-character bound to the given item.
|
* NULL if there is no marker-character bound to the given item.
|
||||||
* The returned value must be free()'d after usage.
|
* The returned value must be released using dc_str_unref() after usage.
|
||||||
*/
|
*/
|
||||||
char* dc_array_get_marker (const dc_array_t* array, size_t index);
|
char* dc_array_get_marker (const dc_array_t* array, size_t index);
|
||||||
|
|
||||||
@@ -2524,7 +2604,7 @@ int dc_chat_get_type (const dc_chat_t* chat);
|
|||||||
*
|
*
|
||||||
* @memberof dc_chat_t
|
* @memberof dc_chat_t
|
||||||
* @param chat The chat object.
|
* @param chat The chat object.
|
||||||
* @return Chat name as a string. Must be free()'d after usage. Never NULL.
|
* @return Chat name as a string. Must be released using dc_str_unref() after usage. Never NULL.
|
||||||
*/
|
*/
|
||||||
char* dc_chat_get_name (const dc_chat_t* chat);
|
char* dc_chat_get_name (const dc_chat_t* chat);
|
||||||
|
|
||||||
@@ -2537,7 +2617,7 @@ char* dc_chat_get_name (const dc_chat_t* chat);
|
|||||||
*
|
*
|
||||||
* @memberof dc_chat_t
|
* @memberof dc_chat_t
|
||||||
* @param chat The chat object to calulate the subtitle for.
|
* @param chat The chat object to calulate the subtitle for.
|
||||||
* @return Subtitle as a string. Must be free()'d after usage. Never NULL.
|
* @return Subtitle as a string. Must be released using dc_str_unref() after usage. Never NULL.
|
||||||
*/
|
*/
|
||||||
char* dc_chat_get_subtitle (const dc_chat_t* chat);
|
char* dc_chat_get_subtitle (const dc_chat_t* chat);
|
||||||
|
|
||||||
@@ -2553,7 +2633,7 @@ char* dc_chat_get_subtitle (const dc_chat_t* chat);
|
|||||||
* @param chat The chat object.
|
* @param chat The chat object.
|
||||||
* @return Path and file if the profile image, if any.
|
* @return Path and file if the profile image, if any.
|
||||||
* NULL otherwise.
|
* NULL otherwise.
|
||||||
* Must be free()'d after usage.
|
* Must be released using dc_str_unref() after usage.
|
||||||
*/
|
*/
|
||||||
char* dc_chat_get_profile_image (const dc_chat_t* chat);
|
char* dc_chat_get_profile_image (const dc_chat_t* chat);
|
||||||
|
|
||||||
@@ -2857,7 +2937,7 @@ int64_t dc_msg_get_sort_timestamp (const dc_msg_t* msg);
|
|||||||
*
|
*
|
||||||
* @memberof dc_msg_t
|
* @memberof dc_msg_t
|
||||||
* @param msg The message object.
|
* @param msg The message object.
|
||||||
* @return Message text. The result must be free()'d. Never returns NULL.
|
* @return Message text. The result must be released using dc_str_unref(). Never returns NULL.
|
||||||
*/
|
*/
|
||||||
char* dc_msg_get_text (const dc_msg_t* msg);
|
char* dc_msg_get_text (const dc_msg_t* msg);
|
||||||
|
|
||||||
@@ -2871,9 +2951,9 @@ char* dc_msg_get_text (const dc_msg_t* msg);
|
|||||||
*
|
*
|
||||||
* @memberof dc_msg_t
|
* @memberof dc_msg_t
|
||||||
* @param msg The message object.
|
* @param msg The message object.
|
||||||
* @return Full path, file name and extension of the file associated with the
|
* @return Full path, file name and extension of the file associated with the message.
|
||||||
* message. If there is no file associated with the message, an emtpy
|
* If there is no file associated with the message, an emtpy string is returned.
|
||||||
* string is returned. NULL is never returned and the returned value must be free()'d.
|
* NULL is never returned and the returned value must be released using dc_str_unref().
|
||||||
*/
|
*/
|
||||||
char* dc_msg_get_file (const dc_msg_t* msg);
|
char* dc_msg_get_file (const dc_msg_t* msg);
|
||||||
|
|
||||||
@@ -2886,7 +2966,7 @@ char* dc_msg_get_file (const dc_msg_t* msg);
|
|||||||
* @param msg The message object.
|
* @param msg The message object.
|
||||||
* @return Base file name plus extension without part. If there is no file
|
* @return Base file name plus extension without part. If there is no file
|
||||||
* associated with the message, an empty string is returned. The returned
|
* associated with the message, an empty string is returned. The returned
|
||||||
* value must be free()'d.
|
* value must be released using dc_str_unref().
|
||||||
*/
|
*/
|
||||||
char* dc_msg_get_filename (const dc_msg_t* msg);
|
char* dc_msg_get_filename (const dc_msg_t* msg);
|
||||||
|
|
||||||
@@ -2898,7 +2978,8 @@ char* dc_msg_get_filename (const dc_msg_t* msg);
|
|||||||
*
|
*
|
||||||
* @memberof dc_msg_t
|
* @memberof dc_msg_t
|
||||||
* @param msg The message object.
|
* @param msg The message object.
|
||||||
* @return String containing the mime type. Must be free()'d after usage. NULL is never returned.
|
* @return String containing the mime type.
|
||||||
|
* Must be released using dc_str_unref() after usage. NULL is never returned.
|
||||||
*/
|
*/
|
||||||
char* dc_msg_get_filemime (const dc_msg_t* msg);
|
char* dc_msg_get_filemime (const dc_msg_t* msg);
|
||||||
|
|
||||||
@@ -3006,7 +3087,8 @@ dc_lot_t* dc_msg_get_summary (const dc_msg_t* msg, const dc_cha
|
|||||||
* @memberof dc_msg_t
|
* @memberof dc_msg_t
|
||||||
* @param msg The message object.
|
* @param msg The message object.
|
||||||
* @param approx_characters Rough length of the expected string.
|
* @param approx_characters Rough length of the expected string.
|
||||||
* @return A summary for the given messages. The returned string must be free()'d.
|
* @return A summary for the given messages.
|
||||||
|
* The returned string must be released using dc_str_unref().
|
||||||
* Returns an empty string on errors, never returns NULL.
|
* Returns an empty string on errors, never returns NULL.
|
||||||
*/
|
*/
|
||||||
char* dc_msg_get_summarytext (const dc_msg_t* msg, int approx_characters);
|
char* dc_msg_get_summarytext (const dc_msg_t* msg, int approx_characters);
|
||||||
@@ -3151,7 +3233,7 @@ int dc_msg_is_setupmessage (const dc_msg_t* msg);
|
|||||||
* @memberof dc_msg_t
|
* @memberof dc_msg_t
|
||||||
* @param msg The message object.
|
* @param msg The message object.
|
||||||
* @return Typically, the first two digits of the setup code or an empty string if unknown.
|
* @return Typically, the first two digits of the setup code or an empty string if unknown.
|
||||||
* NULL is never returned. Must be free()'d when done.
|
* NULL is never returned. Must be released using dc_str_unref() when done.
|
||||||
*/
|
*/
|
||||||
char* dc_msg_get_setupcodebegin (const dc_msg_t* msg);
|
char* dc_msg_get_setupcodebegin (const dc_msg_t* msg);
|
||||||
|
|
||||||
@@ -3308,7 +3390,8 @@ uint32_t dc_contact_get_id (const dc_contact_t* contact);
|
|||||||
*
|
*
|
||||||
* @memberof dc_contact_t
|
* @memberof dc_contact_t
|
||||||
* @param contact The contact object.
|
* @param contact The contact object.
|
||||||
* @return String with the email address, must be free()'d. Never returns NULL.
|
* @return String with the email address,
|
||||||
|
* must be released using dc_str_unref(). Never returns NULL.
|
||||||
*/
|
*/
|
||||||
char* dc_contact_get_addr (const dc_contact_t* contact);
|
char* dc_contact_get_addr (const dc_contact_t* contact);
|
||||||
|
|
||||||
@@ -3322,7 +3405,8 @@ char* dc_contact_get_addr (const dc_contact_t* contact);
|
|||||||
*
|
*
|
||||||
* @memberof dc_contact_t
|
* @memberof dc_contact_t
|
||||||
* @param contact The contact object.
|
* @param contact The contact object.
|
||||||
* @return String with the name to display, must be free()'d. Empty string if unset, never returns NULL.
|
* @return String with the name to display, must be released using dc_str_unref().
|
||||||
|
* Empty string if unset, never returns NULL.
|
||||||
*/
|
*/
|
||||||
char* dc_contact_get_name (const dc_contact_t* contact);
|
char* dc_contact_get_name (const dc_contact_t* contact);
|
||||||
|
|
||||||
@@ -3336,7 +3420,8 @@ char* dc_contact_get_name (const dc_contact_t* contact);
|
|||||||
*
|
*
|
||||||
* @memberof dc_contact_t
|
* @memberof dc_contact_t
|
||||||
* @param contact The contact object.
|
* @param contact The contact object.
|
||||||
* @return String with the name to display, must be free()'d. Never returns NULL.
|
* @return String with the name to display, must be released using dc_str_unref().
|
||||||
|
* Never returns NULL.
|
||||||
*/
|
*/
|
||||||
char* dc_contact_get_display_name (const dc_contact_t* contact);
|
char* dc_contact_get_display_name (const dc_contact_t* contact);
|
||||||
|
|
||||||
@@ -3352,7 +3437,8 @@ char* dc_contact_get_display_name (const dc_contact_t* contact);
|
|||||||
*
|
*
|
||||||
* @memberof dc_contact_t
|
* @memberof dc_contact_t
|
||||||
* @param contact The contact object.
|
* @param contact The contact object.
|
||||||
* @return Summary string, must be free()'d. Never returns NULL.
|
* @return Summary string, must be released using dc_str_unref().
|
||||||
|
* Never returns NULL.
|
||||||
*/
|
*/
|
||||||
char* dc_contact_get_name_n_addr (const dc_contact_t* contact);
|
char* dc_contact_get_name_n_addr (const dc_contact_t* contact);
|
||||||
|
|
||||||
@@ -3364,7 +3450,8 @@ char* dc_contact_get_name_n_addr (const dc_contact_t* contact);
|
|||||||
*
|
*
|
||||||
* @memberof dc_contact_t
|
* @memberof dc_contact_t
|
||||||
* @param contact The contact object.
|
* @param contact The contact object.
|
||||||
* @return String with the name to display, must be free()'d. Never returns NULL.
|
* @return String with the name to display, must be released using dc_str_unref().
|
||||||
|
* Never returns NULL.
|
||||||
*/
|
*/
|
||||||
char* dc_contact_get_first_name (const dc_contact_t* contact);
|
char* dc_contact_get_first_name (const dc_contact_t* contact);
|
||||||
|
|
||||||
@@ -3378,7 +3465,7 @@ char* dc_contact_get_first_name (const dc_contact_t* contact);
|
|||||||
* @param contact The contact object.
|
* @param contact The contact object.
|
||||||
* @return Path and file if the profile image, if any.
|
* @return Path and file if the profile image, if any.
|
||||||
* NULL otherwise.
|
* NULL otherwise.
|
||||||
* Must be free()'d after usage.
|
* Must be released using dc_str_unref() after usage.
|
||||||
*/
|
*/
|
||||||
char* dc_contact_get_profile_image (const dc_contact_t* contact);
|
char* dc_contact_get_profile_image (const dc_contact_t* contact);
|
||||||
|
|
||||||
@@ -3419,6 +3506,110 @@ int dc_contact_is_blocked (const dc_contact_t* contact);
|
|||||||
int dc_contact_is_verified (dc_contact_t* contact);
|
int dc_contact_is_verified (dc_contact_t* contact);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class dc_provider_t
|
||||||
|
*
|
||||||
|
* Opaque object containing information about one single email provider.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a provider struct for the given domain.
|
||||||
|
*
|
||||||
|
* @memberof dc_provider_t
|
||||||
|
* @param domain The domain to get provider info for.
|
||||||
|
* @return a dc_provider_t struct which can be used with the dc_provider_get_*
|
||||||
|
* accessor functions. If no provider info is found, NULL will be
|
||||||
|
* returned.
|
||||||
|
*/
|
||||||
|
dc_provider_t* dc_provider_new_from_domain (const char* domain);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a provider struct for the given email address.
|
||||||
|
*
|
||||||
|
* The provider is extracted from the email address and it's information is returned.
|
||||||
|
*
|
||||||
|
* @memberof dc_provider_t
|
||||||
|
* @param email The user's email address to extract the provider info form.
|
||||||
|
* @return a dc_provider_t struct which can be used with the dc_provider_get_*
|
||||||
|
* accessor functions. If no provider info is found, NULL will be
|
||||||
|
* returned.
|
||||||
|
*/
|
||||||
|
dc_provider_t* dc_provider_new_from_email (const char* email);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL of the overview page.
|
||||||
|
*
|
||||||
|
* This URL allows linking to the providers page on providers.delta.chat.
|
||||||
|
*
|
||||||
|
* @memberof dc_provider_t
|
||||||
|
* @param provider The dc_provider_t struct.
|
||||||
|
* @return A string which must be released using dc_str_unref().
|
||||||
|
*/
|
||||||
|
char* dc_provider_get_overview_page (const dc_provider_t* provider);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The provider's name.
|
||||||
|
*
|
||||||
|
* The name of the provider, e.g. "POSTEO".
|
||||||
|
*
|
||||||
|
* @memberof dc_provider_t
|
||||||
|
* @param provider The dc_provider_t struct.
|
||||||
|
* @return A string which must be released using dc_str_unref().
|
||||||
|
*/
|
||||||
|
char* dc_provider_get_name (const dc_provider_t* provider);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The markdown content of the providers page.
|
||||||
|
*
|
||||||
|
* This contains the preparation steps or additional information if the status
|
||||||
|
* is @ref DC_PROVIDER_STATUS_BROKEN.
|
||||||
|
*
|
||||||
|
* @memberof dc_provider_t
|
||||||
|
* @param provider The dc_provider_t struct.
|
||||||
|
* @return A string which must be released using dc_str_unref().
|
||||||
|
*/
|
||||||
|
char* dc_provider_get_markdown (const dc_provider_t* provider);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date of when the state was last checked/updated.
|
||||||
|
*
|
||||||
|
* This is returned as a string.
|
||||||
|
*
|
||||||
|
* @memberof dc_provider_t
|
||||||
|
* @param provider The dc_provider_t struct.
|
||||||
|
* @return A string which must be released using dc_str_unref().
|
||||||
|
*/
|
||||||
|
char* dc_provider_get_status_date (const dc_provider_t* provider);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether DC works with this provider.
|
||||||
|
*
|
||||||
|
* Can be one of @ref DC_PROVIDER_STATUS_OK, @ref
|
||||||
|
* DC_PROVIDER_STATUS_PREPARATION and @ref DC_PROVIDER_STATUS_BROKEN.
|
||||||
|
*
|
||||||
|
* @memberof dc_provider_t
|
||||||
|
* @param provider The dc_provider_t struct.
|
||||||
|
* @return The status as a constant number.
|
||||||
|
*/
|
||||||
|
int dc_provider_get_status (const dc_provider_t* provider);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free the provider info struct.
|
||||||
|
*
|
||||||
|
* @memberof dc_provider_t
|
||||||
|
* @param provider The dc_provider_t struct.
|
||||||
|
*/
|
||||||
|
void dc_provider_unref (const dc_provider_t* provider);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class dc_lot_t
|
* @class dc_lot_t
|
||||||
*
|
*
|
||||||
@@ -3454,7 +3645,9 @@ void dc_lot_unref (dc_lot_t* lot);
|
|||||||
*
|
*
|
||||||
* @memberof dc_lot_t
|
* @memberof dc_lot_t
|
||||||
* @param lot The lot object.
|
* @param lot The lot object.
|
||||||
* @return A string, the string may be empty and the returned value must be free()'d. NULL if there is no such string.
|
* @return A string, the string may be empty
|
||||||
|
* and the returned value must be released using dc_str_unref().
|
||||||
|
* NULL if there is no such string.
|
||||||
*/
|
*/
|
||||||
char* dc_lot_get_text1 (const dc_lot_t* lot);
|
char* dc_lot_get_text1 (const dc_lot_t* lot);
|
||||||
|
|
||||||
@@ -3463,10 +3656,10 @@ char* dc_lot_get_text1 (const dc_lot_t* lot);
|
|||||||
* Get second string. The meaning of the string is defined by the creator of the object.
|
* Get second string. The meaning of the string is defined by the creator of the object.
|
||||||
*
|
*
|
||||||
* @memberof dc_lot_t
|
* @memberof dc_lot_t
|
||||||
*
|
|
||||||
* @param lot The lot object.
|
* @param lot The lot object.
|
||||||
*
|
* @return A string, the string may be empty
|
||||||
* @return A string, the string may be empty and the returned value must be free()'d . NULL if there is no such string.
|
* and the returned value must be released using dc_str_unref().
|
||||||
|
* NULL if there is no such string.
|
||||||
*/
|
*/
|
||||||
char* dc_lot_get_text2 (const dc_lot_t* lot);
|
char* dc_lot_get_text2 (const dc_lot_t* lot);
|
||||||
|
|
||||||
@@ -3487,9 +3680,7 @@ int dc_lot_get_text1_meaning (const dc_lot_t* lot);
|
|||||||
* Get the associated state. The meaning of the state is defined by the creator of the object.
|
* Get the associated state. The meaning of the state is defined by the creator of the object.
|
||||||
*
|
*
|
||||||
* @memberof dc_lot_t
|
* @memberof dc_lot_t
|
||||||
*
|
|
||||||
* @param lot The lot object.
|
* @param lot The lot object.
|
||||||
*
|
|
||||||
* @return The state as defined by the creator of the object. 0 if there is not state or on errors.
|
* @return The state as defined by the creator of the object. 0 if there is not state or on errors.
|
||||||
*/
|
*/
|
||||||
int dc_lot_get_state (const dc_lot_t* lot);
|
int dc_lot_get_state (const dc_lot_t* lot);
|
||||||
@@ -3511,9 +3702,7 @@ uint32_t dc_lot_get_id (const dc_lot_t* lot);
|
|||||||
* The meaning of the timestamp is defined by the creator of the object.
|
* The meaning of the timestamp is defined by the creator of the object.
|
||||||
*
|
*
|
||||||
* @memberof dc_lot_t
|
* @memberof dc_lot_t
|
||||||
*
|
|
||||||
* @param lot The lot object.
|
* @param lot The lot object.
|
||||||
*
|
|
||||||
* @return The timestamp as defined by the creator of the object. 0 if there is not timestamp or on errors.
|
* @return The timestamp as defined by the creator of the object. 0 if there is not timestamp or on errors.
|
||||||
*/
|
*/
|
||||||
int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||||
@@ -3565,6 +3754,14 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
#define DC_MSG_GIF 21
|
#define DC_MSG_GIF 21
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message containing a sticker, similar to image.
|
||||||
|
* If possible, the ui should display the image without borders in a transparent way.
|
||||||
|
* A click on a sticker will offer to install the sticker set in some future.
|
||||||
|
*/
|
||||||
|
#define DC_MSG_STICKER 23
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Message containing an Audio file.
|
* Message containing an Audio file.
|
||||||
* File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
|
* File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
|
||||||
@@ -3705,7 +3902,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
*
|
*
|
||||||
* @param data1 0
|
* @param data1 0
|
||||||
* @param data2 (const char*) Info string in english language.
|
* @param data2 (const char*) Info string in english language.
|
||||||
* Must not be free()'d or modified and is valid only until the callback returns.
|
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||||
* @return 0
|
* @return 0
|
||||||
*/
|
*/
|
||||||
#define DC_EVENT_INFO 100
|
#define DC_EVENT_INFO 100
|
||||||
@@ -3716,7 +3913,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
*
|
*
|
||||||
* @param data1 0
|
* @param data1 0
|
||||||
* @param data2 (const char*) Info string in english language.
|
* @param data2 (const char*) Info string in english language.
|
||||||
* Must not be free()'d or modified and is valid only until the callback returns.
|
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||||
* @return 0
|
* @return 0
|
||||||
*/
|
*/
|
||||||
#define DC_EVENT_SMTP_CONNECTED 101
|
#define DC_EVENT_SMTP_CONNECTED 101
|
||||||
@@ -3727,7 +3924,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
*
|
*
|
||||||
* @param data1 0
|
* @param data1 0
|
||||||
* @param data2 (const char*) Info string in english language.
|
* @param data2 (const char*) Info string in english language.
|
||||||
* Must not be free()'d or modified and is valid only until the callback returns.
|
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||||
* @return 0
|
* @return 0
|
||||||
*/
|
*/
|
||||||
#define DC_EVENT_IMAP_CONNECTED 102
|
#define DC_EVENT_IMAP_CONNECTED 102
|
||||||
@@ -3737,11 +3934,50 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
*
|
*
|
||||||
* @param data1 0
|
* @param data1 0
|
||||||
* @param data2 (const char*) Info string in english language.
|
* @param data2 (const char*) Info string in english language.
|
||||||
* Must not be free()'d or modified and is valid only until the callback returns.
|
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||||
* @return 0
|
* @return 0
|
||||||
*/
|
*/
|
||||||
#define DC_EVENT_SMTP_MESSAGE_SENT 103
|
#define DC_EVENT_SMTP_MESSAGE_SENT 103
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when a message was successfully marked as deleted on the IMAP server.
|
||||||
|
*
|
||||||
|
* @param data1 0
|
||||||
|
* @param data2 (const char*) Info string in english language.
|
||||||
|
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||||
|
* @return 0
|
||||||
|
*/
|
||||||
|
#define DC_EVENT_IMAP_MESSAGE_DELETED 104
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when a message was successfully moved on IMAP.
|
||||||
|
*
|
||||||
|
* @param data1 0
|
||||||
|
* @param data2 (const char*) Info string in english language.
|
||||||
|
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||||
|
* @return 0
|
||||||
|
*/
|
||||||
|
#define DC_EVENT_IMAP_MESSAGE_MOVED 105
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when a new blob file was successfully written
|
||||||
|
*
|
||||||
|
* @param data1 0
|
||||||
|
* @param data2 (const char*) path name
|
||||||
|
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||||
|
* @return 0
|
||||||
|
*/
|
||||||
|
#define DC_EVENT_NEW_BLOB_FILE 150
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when a blob file was successfully deleted
|
||||||
|
*
|
||||||
|
* @param data1 0
|
||||||
|
* @param data2 (const char*) path name
|
||||||
|
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||||
|
* @return 0
|
||||||
|
*/
|
||||||
|
#define DC_EVENT_DELETED_BLOB_FILE 151
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The library-user should write a warning string to the log.
|
* The library-user should write a warning string to the log.
|
||||||
@@ -3751,7 +3987,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
*
|
*
|
||||||
* @param data1 0
|
* @param data1 0
|
||||||
* @param data2 (const char*) Warning string in english language.
|
* @param data2 (const char*) Warning string in english language.
|
||||||
* Must not be free()'d or modified and is valid only until the callback returns.
|
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||||
* @return 0
|
* @return 0
|
||||||
*/
|
*/
|
||||||
#define DC_EVENT_WARNING 300
|
#define DC_EVENT_WARNING 300
|
||||||
@@ -3771,9 +4007,10 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
* in a messasge box then.
|
* in a messasge box then.
|
||||||
*
|
*
|
||||||
* @param data1 0
|
* @param data1 0
|
||||||
* @param data2 (const char*) Error string, always set, never NULL. Frequent error strings are
|
* @param data2 (const char*) Error string, always set, never NULL.
|
||||||
* localized using #DC_EVENT_GET_STRING, however, most error strings will be in english language.
|
* Some error strings are taken from dc_set_stock_translation(),
|
||||||
* Must not be free()'d or modified and is valid only until the callback returns.
|
* however, most error strings will be in english language.
|
||||||
|
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||||
* @return 0
|
* @return 0
|
||||||
*/
|
*/
|
||||||
#define DC_EVENT_ERROR 400
|
#define DC_EVENT_ERROR 400
|
||||||
@@ -3797,7 +4034,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
* @param data1 (int) 1=first/new network error, should be reported the user;
|
* @param data1 (int) 1=first/new network error, should be reported the user;
|
||||||
* 0=subsequent network error, should be logged only
|
* 0=subsequent network error, should be logged only
|
||||||
* @param data2 (const char*) Error string, always set, never NULL.
|
* @param data2 (const char*) Error string, always set, never NULL.
|
||||||
* Must not be free()'d or modified and is valid only until the callback returns.
|
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||||
* @return 0
|
* @return 0
|
||||||
*/
|
*/
|
||||||
#define DC_EVENT_ERROR_NETWORK 401
|
#define DC_EVENT_ERROR_NETWORK 401
|
||||||
@@ -3812,7 +4049,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
*
|
*
|
||||||
* @param data1 0
|
* @param data1 0
|
||||||
* @param data2 (const char*) Info string in english language.
|
* @param data2 (const char*) Info string in english language.
|
||||||
* Must not be free()'d or modified
|
* Must not be unref'd or modified
|
||||||
* and is valid only until the callback returns.
|
* and is valid only until the callback returns.
|
||||||
* @return 0
|
* @return 0
|
||||||
*/
|
*/
|
||||||
@@ -3943,7 +4180,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
* services.
|
* services.
|
||||||
*
|
*
|
||||||
* @param data1 (const char*) Path and file name.
|
* @param data1 (const char*) Path and file name.
|
||||||
* Must not be free()'d or modified and is valid only until the callback returns.
|
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||||
* @param data2 0
|
* @param data2 0
|
||||||
* @return 0
|
* @return 0
|
||||||
*/
|
*/
|
||||||
@@ -3984,36 +4221,21 @@ 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
|
||||||
|
|
||||||
|
|
||||||
// the following events are functions that should be provided by the frontends
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Requeste a localized string from the frontend.
|
|
||||||
*
|
|
||||||
* @param data1 (int) ID of the string to request, one of the DC_STR_* constants.
|
|
||||||
* @param data2 (int) The count. If the requested string contains a placeholder for a numeric value,
|
|
||||||
* the ui may use this value to return different strings on different plural forms.
|
|
||||||
* @return (const char*) Null-terminated UTF-8 string.
|
|
||||||
* The string will be free()'d by the core,
|
|
||||||
* so it must be allocated using malloc() or a compatible function.
|
|
||||||
* Return 0 if the ui cannot provide the requested string
|
|
||||||
* the core will use a default string in english language then.
|
|
||||||
*/
|
|
||||||
#define DC_EVENT_GET_STRING 2091
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @}
|
* @}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define DC_EVENT_FILE_COPIED 2055 // deprecated
|
|
||||||
#define DC_EVENT_IS_OFFLINE 2081 // deprecated
|
#define DC_EVENT_FILE_COPIED 2055 // not used anymore
|
||||||
#define DC_ERROR_SEE_STRING 0 // deprecated
|
#define DC_EVENT_IS_OFFLINE 2081 // not used anymore
|
||||||
#define DC_ERROR_SELF_NOT_IN_GROUP 1 // deprecated
|
#define DC_EVENT_GET_STRING 2091 // not used anymore, use dc_set_stock_translation()
|
||||||
#define DC_STR_SELFNOTINGRP 21 // deprecated
|
#define DC_ERROR_SEE_STRING 0 // not used anymore
|
||||||
|
#define DC_ERROR_SELF_NOT_IN_GROUP 1 // not used anymore
|
||||||
|
#define DC_STR_SELFNOTINGRP 21 // not used anymore
|
||||||
#define DC_EVENT_DATA1_IS_STRING(e) ((e)==DC_EVENT_IMEX_FILE_WRITTEN || (e)==DC_EVENT_FILE_COPIED)
|
#define DC_EVENT_DATA1_IS_STRING(e) ((e)==DC_EVENT_IMEX_FILE_WRITTEN || (e)==DC_EVENT_FILE_COPIED)
|
||||||
#define DC_EVENT_DATA2_IS_STRING(e) ((e)>=100 && (e)<=499)
|
#define DC_EVENT_DATA2_IS_STRING(e) ((e)>=100 && (e)<=499)
|
||||||
#define DC_EVENT_RETURNS_INT(e) ((e)==DC_EVENT_IS_OFFLINE)
|
#define DC_EVENT_RETURNS_INT(e) ((e)==DC_EVENT_IS_OFFLINE) // not used anymore
|
||||||
#define DC_EVENT_RETURNS_STRING(e) ((e)==DC_EVENT_GET_STRING)
|
#define DC_EVENT_RETURNS_STRING(e) ((e)==DC_EVENT_GET_STRING) // not used anymore
|
||||||
char* dc_get_version_str (void); // deprecated
|
char* dc_get_version_str (void); // deprecated
|
||||||
void dc_array_add_id (dc_array_t*, uint32_t); // deprecated
|
void dc_array_add_id (dc_array_t*, uint32_t); // deprecated
|
||||||
|
|
||||||
@@ -4026,6 +4248,41 @@ void dc_array_add_id (dc_array_t*, uint32_t); // depreca
|
|||||||
#define DC_SHOW_EMAILS_ALL 2
|
#define DC_SHOW_EMAILS_ALL 2
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @defgroup DC_PROVIDER_STATUS DC_PROVIDER_STATUS
|
||||||
|
*
|
||||||
|
* These constants are used as return values for dc_provider_get_status().
|
||||||
|
*
|
||||||
|
* @addtogroup DC_PROVIDER_STATUS
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider status returned by dc_provider_get_status().
|
||||||
|
*
|
||||||
|
* Works right out of the box without any preperation steps needed
|
||||||
|
*/
|
||||||
|
#define DC_PROVIDER_STATUS_OK 1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider status returned by dc_provider_get_status().
|
||||||
|
*
|
||||||
|
* Works, but preparation steps are needed
|
||||||
|
*/
|
||||||
|
#define DC_PROVIDER_STATUS_PREPARATION 2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider status returned by dc_provider_get_status().
|
||||||
|
*
|
||||||
|
* Doesn't work (too unstable to use falls also in this category)
|
||||||
|
*/
|
||||||
|
#define DC_PROVIDER_STATUS_BROKEN 3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO: Strings need some doumentation about used placeholders.
|
* TODO: Strings need some doumentation about used placeholders.
|
||||||
*
|
*
|
||||||
@@ -4080,10 +4337,8 @@ void dc_array_add_id (dc_array_t*, uint32_t); // depreca
|
|||||||
#define DC_STR_MSGLOCATIONENABLED 64
|
#define DC_STR_MSGLOCATIONENABLED 64
|
||||||
#define DC_STR_MSGLOCATIONDISABLED 65
|
#define DC_STR_MSGLOCATIONDISABLED 65
|
||||||
#define DC_STR_LOCATION 66
|
#define DC_STR_LOCATION 66
|
||||||
#define DC_STR_COUNT 66
|
#define DC_STR_STICKER 67
|
||||||
|
#define DC_STR_COUNT 67
|
||||||
void dc_str_unref (char*);
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @}
|
* @}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
92
deltachat-ffi/src/providers.rs
Normal file
92
deltachat-ffi/src/providers.rs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
extern crate deltachat_provider_database;
|
||||||
|
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
use deltachat::dc_tools::{as_str, StrExt};
|
||||||
|
use deltachat_provider_database::StatusState;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub type dc_provider_t = deltachat_provider_database::Provider;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_provider_new_from_domain(
|
||||||
|
domain: *const libc::c_char,
|
||||||
|
) -> *const dc_provider_t {
|
||||||
|
match deltachat_provider_database::get_provider_info(as_str(domain)) {
|
||||||
|
Some(provider) => provider,
|
||||||
|
None => ptr::null(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_provider_new_from_email(
|
||||||
|
email: *const libc::c_char,
|
||||||
|
) -> *const dc_provider_t {
|
||||||
|
let domain = deltachat_provider_database::get_domain_from_email(as_str(email));
|
||||||
|
match deltachat_provider_database::get_provider_info(domain) {
|
||||||
|
Some(provider) => provider,
|
||||||
|
None => ptr::null(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! null_guard {
|
||||||
|
($context:tt) => {
|
||||||
|
if $context.is_null() {
|
||||||
|
return ptr::null_mut() as *mut libc::c_char;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_provider_get_overview_page(
|
||||||
|
provider: *const dc_provider_t,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
null_guard!(provider);
|
||||||
|
format!(
|
||||||
|
"{}/{}",
|
||||||
|
deltachat_provider_database::PROVIDER_OVERVIEW_URL,
|
||||||
|
(*provider).overview_page
|
||||||
|
)
|
||||||
|
.strdup()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_provider_get_name(provider: *const dc_provider_t) -> *mut libc::c_char {
|
||||||
|
null_guard!(provider);
|
||||||
|
(*provider).name.strdup()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_provider_get_markdown(
|
||||||
|
provider: *const dc_provider_t,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
null_guard!(provider);
|
||||||
|
(*provider).markdown.strdup()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_provider_get_status_date(
|
||||||
|
provider: *const dc_provider_t,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
null_guard!(provider);
|
||||||
|
(*provider).status.date.strdup()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_provider_get_status(provider: *const dc_provider_t) -> u32 {
|
||||||
|
if provider.is_null() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
match (*provider).status.state {
|
||||||
|
StatusState::OK => 1,
|
||||||
|
StatusState::PREPARATION => 2,
|
||||||
|
StatusState::BROKEN => 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_provider_unref(_provider: *const dc_provider_t) {
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO expose general provider overview url?
|
||||||
@@ -1,27 +1,25 @@
|
|||||||
use std::ffi::CString;
|
use std::path::Path;
|
||||||
use std::ptr;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use deltachat::chat::{self, Chat};
|
use deltachat::chat::{self, Chat};
|
||||||
use deltachat::chatlist::*;
|
use deltachat::chatlist::*;
|
||||||
use deltachat::config;
|
use deltachat::config;
|
||||||
use deltachat::configure::*;
|
|
||||||
use deltachat::constants::*;
|
use deltachat::constants::*;
|
||||||
use deltachat::contact::*;
|
use deltachat::contact::*;
|
||||||
use deltachat::context::*;
|
use deltachat::context::*;
|
||||||
use deltachat::dc_imex::*;
|
|
||||||
use deltachat::dc_receive_imf::*;
|
use deltachat::dc_receive_imf::*;
|
||||||
use deltachat::dc_tools::*;
|
use deltachat::dc_tools::*;
|
||||||
use deltachat::error::Error;
|
use deltachat::error::Error;
|
||||||
|
use deltachat::imex::*;
|
||||||
use deltachat::job::*;
|
use deltachat::job::*;
|
||||||
use deltachat::location;
|
use deltachat::location;
|
||||||
use deltachat::lot::LotState;
|
use deltachat::lot::LotState;
|
||||||
use deltachat::message::*;
|
use deltachat::message::{self, Message, MessageState};
|
||||||
use deltachat::peerstate::*;
|
use deltachat::peerstate::*;
|
||||||
use deltachat::qr::*;
|
use deltachat::qr::*;
|
||||||
use deltachat::sql;
|
use deltachat::sql;
|
||||||
use deltachat::x::*;
|
|
||||||
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.
|
||||||
@@ -94,170 +92,133 @@ pub unsafe fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
|
|||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn dc_poke_eml_file(context: &Context, filename: *const libc::c_char) -> libc::c_int {
|
fn dc_poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<(), Error> {
|
||||||
/* mainly for testing, may be called by dc_import_spec() */
|
let data = dc_read_file(context, filename)?;
|
||||||
let mut success: libc::c_int = 0i32;
|
|
||||||
let mut data: *mut libc::c_char = ptr::null_mut();
|
|
||||||
let mut data_bytes = 0;
|
|
||||||
if !(dc_read_file(
|
|
||||||
context,
|
|
||||||
filename,
|
|
||||||
&mut data as *mut *mut libc::c_char as *mut *mut libc::c_void,
|
|
||||||
&mut data_bytes,
|
|
||||||
) == 0i32)
|
|
||||||
{
|
|
||||||
dc_receive_imf(context, data, data_bytes, "import", 0, 0);
|
|
||||||
success = 1;
|
|
||||||
}
|
|
||||||
free(data as *mut libc::c_void);
|
|
||||||
|
|
||||||
success
|
unsafe { dc_receive_imf(context, &data, "import", 0, 0) };
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Import a file to the database.
|
/// Import a file to the database.
|
||||||
/// For testing, import a folder with eml-files, a single eml-file, e-mail plus public key and so on.
|
/// For testing, import a folder with eml-files, a single eml-file, e-mail plus public key and so on.
|
||||||
/// For normal importing, use dc_imex().
|
/// For normal importing, use imex().
|
||||||
///
|
///
|
||||||
/// @private @memberof Context
|
/// @private @memberof Context
|
||||||
/// @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.
|
||||||
unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> 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 ok_to_continue;
|
let real_spec: String;
|
||||||
let mut success: libc::c_int = 0;
|
let mut read_cnt = 0;
|
||||||
let real_spec: *mut libc::c_char;
|
|
||||||
let mut suffix: *mut libc::c_char = ptr::null_mut();
|
|
||||||
let mut read_cnt: libc::c_int = 0;
|
|
||||||
|
|
||||||
/* if `spec` is given, remember it for later usage; if it is not given, try to use the last one */
|
/* if `spec` is given, remember it for later usage; if it is not given, try to use the last one */
|
||||||
if !spec.is_null() {
|
if !spec.is_null() {
|
||||||
real_spec = dc_strdup(spec);
|
real_spec = to_string_lossy(spec);
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.set_config(context, "import_spec", Some(as_str(real_spec)))
|
.set_raw_config(context, "import_spec", Some(&real_spec))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
ok_to_continue = true;
|
|
||||||
} else {
|
} else {
|
||||||
let rs = context.sql.get_config(context, "import_spec");
|
let rs = context.sql.get_raw_config(context, "import_spec");
|
||||||
if rs.is_none() {
|
if rs.is_none() {
|
||||||
error!(context, "Import: No file or folder given.");
|
error!(context, "Import: No file or folder given.");
|
||||||
ok_to_continue = false;
|
return 0;
|
||||||
} else {
|
|
||||||
ok_to_continue = true;
|
|
||||||
}
|
}
|
||||||
real_spec = rs.unwrap_or_default().strdup();
|
real_spec = rs.unwrap();
|
||||||
}
|
}
|
||||||
if ok_to_continue {
|
if let Some(suffix) = dc_get_filesuffix_lc(&real_spec) {
|
||||||
let ok_to_continue2;
|
if suffix == "eml" {
|
||||||
suffix = dc_get_filesuffix_lc(as_str(real_spec));
|
if dc_poke_eml_file(context, &real_spec).is_ok() {
|
||||||
if !suffix.is_null() && strcmp(suffix, b"eml\x00" as *const u8 as *const libc::c_char) == 0
|
|
||||||
{
|
|
||||||
if 0 != dc_poke_eml_file(context, real_spec) {
|
|
||||||
read_cnt += 1
|
read_cnt += 1
|
||||||
}
|
}
|
||||||
ok_to_continue2 = true;
|
}
|
||||||
|
} else {
|
||||||
|
/* import a directory */
|
||||||
|
let dir_name = std::path::Path::new(&real_spec);
|
||||||
|
let dir = std::fs::read_dir(dir_name);
|
||||||
|
if dir.is_err() {
|
||||||
|
error!(context, "Import: Cannot open directory \"{}\".", &real_spec,);
|
||||||
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
/* import a directory */
|
let dir = dir.unwrap();
|
||||||
let dir_name = std::path::Path::new(as_str(real_spec));
|
for entry in dir {
|
||||||
let dir = std::fs::read_dir(dir_name);
|
if entry.is_err() {
|
||||||
if dir.is_err() {
|
break;
|
||||||
error!(
|
}
|
||||||
context,
|
let entry = entry.unwrap();
|
||||||
"Import: Cannot open directory \"{}\".",
|
let name_f = entry.file_name();
|
||||||
as_str(real_spec),
|
let name = name_f.to_string_lossy();
|
||||||
);
|
if name.ends_with(".eml") {
|
||||||
ok_to_continue2 = false;
|
let path_plus_name = format!("{}/{}", &real_spec, name);
|
||||||
} else {
|
info!(context, "Import: {}", path_plus_name);
|
||||||
let dir = dir.unwrap();
|
if dc_poke_eml_file(context, path_plus_name).is_ok() {
|
||||||
for entry in dir {
|
read_cnt += 1
|
||||||
if entry.is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let entry = entry.unwrap();
|
|
||||||
let name_f = entry.file_name();
|
|
||||||
let name = name_f.to_string_lossy();
|
|
||||||
if name.ends_with(".eml") {
|
|
||||||
let path_plus_name = format!("{}/{}", as_str(real_spec), name);
|
|
||||||
info!(context, "Import: {}", path_plus_name);
|
|
||||||
let path_plus_name_c = CString::yolo(path_plus_name);
|
|
||||||
if 0 != dc_poke_eml_file(context, path_plus_name_c.as_ptr()) {
|
|
||||||
read_cnt += 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ok_to_continue2 = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ok_to_continue2 {
|
|
||||||
info!(
|
|
||||||
context,
|
|
||||||
"Import: {} items read from \"{}\".",
|
|
||||||
read_cnt,
|
|
||||||
as_str(real_spec)
|
|
||||||
);
|
|
||||||
if read_cnt > 0 {
|
|
||||||
context.call_cb(Event::MsgsChanged {
|
|
||||||
chat_id: 0,
|
|
||||||
msg_id: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
success = 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
info!(
|
||||||
free(real_spec as *mut libc::c_void);
|
context,
|
||||||
free(suffix as *mut libc::c_void);
|
"Import: {} items read from \"{}\".", read_cnt, &real_spec
|
||||||
success
|
);
|
||||||
|
if read_cnt > 0 {
|
||||||
|
context.call_cb(Event::MsgsChanged {
|
||||||
|
chat_id: 0,
|
||||||
|
msg_id: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe 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, dc_msg_get_from_id(msg)).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();
|
||||||
|
|
||||||
let statestr = match dc_msg_get_state(msg) {
|
let statestr = match msg.get_state() {
|
||||||
MessageState::OutPending => " o",
|
MessageState::OutPending => " o",
|
||||||
MessageState::OutDelivered => " √",
|
MessageState::OutDelivered => " √",
|
||||||
MessageState::OutMdnRcvd => " √√",
|
MessageState::OutMdnRcvd => " √√",
|
||||||
MessageState::OutFailed => " !!",
|
MessageState::OutFailed => " !!",
|
||||||
_ => "",
|
_ => "",
|
||||||
};
|
};
|
||||||
let temp2 = dc_timestamp_to_str(dc_msg_get_timestamp(msg));
|
let temp2 = dc_timestamp_to_str(msg.get_timestamp());
|
||||||
let msgtext = dc_msg_get_text(msg);
|
let msgtext = msg.get_text();
|
||||||
info!(
|
info!(
|
||||||
context,
|
context,
|
||||||
"{}#{}{}{}: {} (Contact#{}): {} {}{}{}{} [{}]",
|
"{}#{}{}{}: {} (Contact#{}): {} {}{}{}{}{} [{}]",
|
||||||
prefix.as_ref(),
|
prefix.as_ref(),
|
||||||
dc_msg_get_id(msg) as libc::c_int,
|
msg.get_id() as libc::c_int,
|
||||||
if dc_msg_get_showpadlock(msg) {
|
if msg.get_showpadlock() { "🔒" } else { "" },
|
||||||
"🔒"
|
if msg.has_location() { "📍" } else { "" },
|
||||||
} else {
|
|
||||||
""
|
|
||||||
},
|
|
||||||
if dc_msg_has_location(msg) { "📍" } else { "" },
|
|
||||||
&contact_name,
|
&contact_name,
|
||||||
contact_id,
|
contact_id,
|
||||||
as_str(msgtext),
|
msgtext.unwrap_or_default(),
|
||||||
if dc_msg_is_starred(msg) { "★" } else { "" },
|
if msg.is_starred() { "★" } else { "" },
|
||||||
if dc_msg_get_from_id(msg) == 1 as libc::c_uint {
|
if msg.get_from_id() == 1 as libc::c_uint {
|
||||||
""
|
""
|
||||||
} else if dc_msg_get_state(msg) == MessageState::InSeen {
|
} else if msg.get_state() == MessageState::InSeen {
|
||||||
"[SEEN]"
|
"[SEEN]"
|
||||||
} else if dc_msg_get_state(msg) == MessageState::InNoticed {
|
} else if msg.get_state() == MessageState::InNoticed {
|
||||||
"[NOTICED]"
|
"[NOTICED]"
|
||||||
} else {
|
} else {
|
||||||
"[FRESH]"
|
"[FRESH]"
|
||||||
},
|
},
|
||||||
if dc_msg_is_info(msg) { "[INFO]" } else { "" },
|
if msg.is_info() { "[INFO]" } else { "" },
|
||||||
|
if msg.is_forwarded() {
|
||||||
|
"[FORWARDED]"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
statestr,
|
statestr,
|
||||||
&temp2,
|
&temp2,
|
||||||
);
|
);
|
||||||
free(msgtext as *mut libc::c_void);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn log_msglist(context: &Context, msglist: &Vec<u32>) -> Result<(), Error> {
|
unsafe fn log_msglist(context: &Context, msglist: &Vec<u32>) -> Result<(), Error> {
|
||||||
@@ -278,7 +239,7 @@ unsafe fn log_msglist(context: &Context, msglist: &Vec<u32>) -> Result<(), Error
|
|||||||
);
|
);
|
||||||
lines_out += 1
|
lines_out += 1
|
||||||
}
|
}
|
||||||
let msg = dc_get_msg(context, msg_id)?;
|
let msg = Message::load_from_db(context, msg_id)?;
|
||||||
log_msg(context, "Msg", &msg);
|
log_msg(context, "Msg", &msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -360,12 +321,8 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
|||||||
arg1.strdup() as *const _
|
arg1.strdup() as *const _
|
||||||
};
|
};
|
||||||
let arg2 = args.next().unwrap_or_default();
|
let arg2 = args.next().unwrap_or_default();
|
||||||
let arg2_c = if arg2.is_empty() {
|
|
||||||
std::ptr::null()
|
|
||||||
} else {
|
|
||||||
arg2.strdup() as *const _
|
|
||||||
};
|
|
||||||
|
|
||||||
|
let blobdir = context.get_blobdir();
|
||||||
match arg0 {
|
match arg0 {
|
||||||
"help" | "?" => match arg1 {
|
"help" | "?" => match arg1 {
|
||||||
// TODO: reuse commands definition in main.rs.
|
// TODO: reuse commands definition in main.rs.
|
||||||
@@ -452,30 +409,24 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
|||||||
============================================="
|
============================================="
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
"initiate-key-transfer" => {
|
"initiate-key-transfer" => match initiate_key_transfer(context) {
|
||||||
let setup_code = dc_initiate_key_transfer(context);
|
Ok(setup_code) => println!(
|
||||||
if !setup_code.is_null() {
|
"Setup code for the transferred setup message: {}",
|
||||||
println!(
|
setup_code,
|
||||||
"Setup code for the transferred setup message: {}",
|
),
|
||||||
as_str(setup_code),
|
Err(err) => bail!("Failed to generate setup code: {}", err),
|
||||||
);
|
},
|
||||||
free(setup_code as *mut libc::c_void);
|
|
||||||
} else {
|
|
||||||
bail!("Failed to generate setup code");
|
|
||||||
};
|
|
||||||
}
|
|
||||||
"get-setupcodebegin" => {
|
"get-setupcodebegin" => {
|
||||||
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
||||||
let msg_id: u32 = arg1.parse()?;
|
let msg_id: u32 = arg1.parse()?;
|
||||||
let msg = dc_get_msg(context, msg_id)?;
|
let msg = Message::load_from_db(context, msg_id)?;
|
||||||
if dc_msg_is_setupmessage(&msg) {
|
if msg.is_setupmessage() {
|
||||||
let setupcodebegin = dc_msg_get_setupcodebegin(context, &msg);
|
let setupcodebegin = msg.get_setupcodebegin(context);
|
||||||
println!(
|
println!(
|
||||||
"The setup code for setup message Msg#{} starts with: {}",
|
"The setup code for setup message Msg#{} starts with: {}",
|
||||||
msg_id,
|
msg_id,
|
||||||
as_str(setupcodebegin),
|
setupcodebegin.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
free(setupcodebegin as *mut libc::c_void);
|
|
||||||
} else {
|
} else {
|
||||||
bail!("Msg#{} is no setup message.", msg_id,);
|
bail!("Msg#{} is no setup message.", msg_id,);
|
||||||
}
|
}
|
||||||
@@ -485,33 +436,28 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
|||||||
!arg1.is_empty() && !arg2.is_empty(),
|
!arg1.is_empty() && !arg2.is_empty(),
|
||||||
"Arguments <msg-id> <setup-code> expected"
|
"Arguments <msg-id> <setup-code> expected"
|
||||||
);
|
);
|
||||||
if !dc_continue_key_transfer(context, arg1.parse()?, arg2_c) {
|
continue_key_transfer(context, arg1.parse()?, &arg2)?;
|
||||||
bail!("Continue key transfer failed");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"has-backup" => {
|
"has-backup" => {
|
||||||
let ret = dc_imex_has_backup(context, context.get_blobdir());
|
has_backup(context, blobdir)?;
|
||||||
if ret.is_null() {
|
|
||||||
println!("No backup found.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"export-backup" => {
|
"export-backup" => {
|
||||||
dc_imex(context, 11, Some(context.get_blobdir()), ptr::null());
|
imex(context, ImexMode::ExportBackup, Some(blobdir));
|
||||||
}
|
}
|
||||||
"import-backup" => {
|
"import-backup" => {
|
||||||
ensure!(!arg1.is_empty(), "Argument <backup-file> missing.");
|
ensure!(!arg1.is_empty(), "Argument <backup-file> missing.");
|
||||||
dc_imex(context, 12, Some(arg1), ptr::null());
|
imex(context, ImexMode::ImportBackup, Some(arg1));
|
||||||
}
|
}
|
||||||
"export-keys" => {
|
"export-keys" => {
|
||||||
dc_imex(context, 1, Some(context.get_blobdir()), ptr::null());
|
imex(context, ImexMode::ExportSelfKeys, Some(blobdir));
|
||||||
}
|
}
|
||||||
"import-keys" => {
|
"import-keys" => {
|
||||||
dc_imex(context, 2, Some(context.get_blobdir()), ptr::null());
|
imex(context, ImexMode::ImportSelfKeys, Some(blobdir));
|
||||||
}
|
}
|
||||||
"export-setup" => {
|
"export-setup" => {
|
||||||
let setup_code = dc_create_setup_code(context);
|
let setup_code = create_setup_code(context);
|
||||||
let file_name = context.get_blobdir().join("autocrypt-setup-message.html");
|
let file_name = blobdir.join("autocrypt-setup-message.html");
|
||||||
let file_content = dc_render_setup_file(context, &setup_code)?;
|
let file_content = render_setup_file(context, &setup_code)?;
|
||||||
std::fs::write(&file_name, file_content)?;
|
std::fs::write(&file_name, file_content)?;
|
||||||
println!(
|
println!(
|
||||||
"Setup message written to: {}\nSetup code: {}",
|
"Setup message written to: {}\nSetup code: {}",
|
||||||
@@ -529,7 +475,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
|||||||
ensure!(0 != dc_reset_tables(context, bits), "Reset failed");
|
ensure!(0 != dc_reset_tables(context, bits), "Reset failed");
|
||||||
}
|
}
|
||||||
"stop" => {
|
"stop" => {
|
||||||
dc_stop_ongoing_process(context);
|
context.stop_ongoing();
|
||||||
}
|
}
|
||||||
"set" => {
|
"set" => {
|
||||||
ensure!(!arg1.is_empty(), "Argument <key> missing.");
|
ensure!(!arg1.is_empty(), "Argument <key> missing.");
|
||||||
@@ -800,7 +746,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
|||||||
let longitude = arg2.parse()?;
|
let longitude = arg2.parse()?;
|
||||||
|
|
||||||
let continue_streaming = location::set(context, latitude, longitude, 0.);
|
let continue_streaming = location::set(context, latitude, longitude, 0.);
|
||||||
if 0 != continue_streaming {
|
if continue_streaming {
|
||||||
println!("Success, streaming should be continued.");
|
println!("Success, streaming should be continued.");
|
||||||
} else {
|
} else {
|
||||||
println!("Success, streaming can be stoppped.");
|
println!("Success, streaming can be stoppped.");
|
||||||
@@ -825,14 +771,14 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
|||||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||||
ensure!(!arg1.is_empty(), "No file given.");
|
ensure!(!arg1.is_empty(), "No file given.");
|
||||||
|
|
||||||
let mut msg = dc_msg_new(if arg0 == "sendimage" {
|
let mut msg = Message::new(if arg0 == "sendimage" {
|
||||||
Viewtype::Image
|
Viewtype::Image
|
||||||
} else {
|
} else {
|
||||||
Viewtype::File
|
Viewtype::File
|
||||||
});
|
});
|
||||||
dc_msg_set_file(&mut msg, arg1_c, ptr::null());
|
msg.set_file(arg1, None);
|
||||||
if !arg2.is_empty() {
|
if !arg2.is_empty() {
|
||||||
dc_msg_set_text(&mut msg, arg2_c);
|
msg.set_text(Some(arg2.to_string()));
|
||||||
}
|
}
|
||||||
chat::send_msg(context, sel_chat.as_ref().unwrap().get_id(), &mut msg)?;
|
chat::send_msg(context, sel_chat.as_ref().unwrap().get_id(), &mut msg)?;
|
||||||
}
|
}
|
||||||
@@ -854,8 +800,8 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
|||||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||||
|
|
||||||
if !arg1.is_empty() {
|
if !arg1.is_empty() {
|
||||||
let mut draft = dc_msg_new(Viewtype::Text);
|
let mut draft = Message::new(Viewtype::Text);
|
||||||
dc_msg_set_text(&mut draft, arg1_c);
|
draft.set_text(Some(arg1.to_string()));
|
||||||
chat::set_draft(
|
chat::set_draft(
|
||||||
context,
|
context,
|
||||||
sel_chat.as_ref().unwrap().get_id(),
|
sel_chat.as_ref().unwrap().get_id(),
|
||||||
@@ -904,8 +850,8 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
|||||||
"msginfo" => {
|
"msginfo" => {
|
||||||
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
||||||
let id = arg1.parse()?;
|
let id = arg1.parse()?;
|
||||||
let res = dc_get_msg_info(context, id);
|
let res = message::get_msg_info(context, id);
|
||||||
println!("{}", as_str(res));
|
println!("{}", res);
|
||||||
}
|
}
|
||||||
"listfresh" => {
|
"listfresh" => {
|
||||||
let msglist = context.get_fresh_msgs();
|
let msglist = context.get_fresh_msgs();
|
||||||
@@ -922,30 +868,25 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
|||||||
let mut msg_ids = [0; 1];
|
let mut msg_ids = [0; 1];
|
||||||
let chat_id = arg2.parse()?;
|
let chat_id = arg2.parse()?;
|
||||||
msg_ids[0] = arg1.parse()?;
|
msg_ids[0] = arg1.parse()?;
|
||||||
chat::forward_msgs(context, msg_ids.as_mut_ptr(), 1, chat_id);
|
chat::forward_msgs(context, &msg_ids, chat_id)?;
|
||||||
}
|
}
|
||||||
"markseen" => {
|
"markseen" => {
|
||||||
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
||||||
let mut msg_ids = [0; 1];
|
let mut msg_ids = [0; 1];
|
||||||
msg_ids[0] = arg1.parse()?;
|
msg_ids[0] = arg1.parse()?;
|
||||||
dc_markseen_msgs(context, msg_ids.as_mut_ptr(), 1);
|
message::markseen_msgs(context, &msg_ids);
|
||||||
}
|
}
|
||||||
"star" | "unstar" => {
|
"star" | "unstar" => {
|
||||||
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
||||||
let mut msg_ids = [0; 1];
|
let mut msg_ids = [0; 1];
|
||||||
msg_ids[0] = arg1.parse()?;
|
msg_ids[0] = arg1.parse()?;
|
||||||
dc_star_msgs(
|
message::star_msgs(context, &msg_ids, arg0 == "star");
|
||||||
context,
|
|
||||||
msg_ids.as_mut_ptr(),
|
|
||||||
1,
|
|
||||||
if arg0 == "star" { 1 } else { 0 },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
"delmsg" => {
|
"delmsg" => {
|
||||||
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
||||||
let mut ids = [0; 1];
|
let mut ids = [0; 1];
|
||||||
ids[0] = arg1.parse()?;
|
ids[0] = arg1.parse()?;
|
||||||
dc_delete_msgs(context, ids.as_mut_ptr(), 1);
|
message::delete_msgs(context, &ids);
|
||||||
}
|
}
|
||||||
"listcontacts" | "contacts" | "listverified" => {
|
"listcontacts" | "contacts" | "listverified" => {
|
||||||
let contacts = Contact::get_all(
|
let contacts = Contact::get_all(
|
||||||
@@ -1028,7 +969,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
|||||||
"fileinfo" => {
|
"fileinfo" => {
|
||||||
ensure!(!arg1.is_empty(), "Argument <file> missing.");
|
ensure!(!arg1.is_empty(), "Argument <file> missing.");
|
||||||
|
|
||||||
if let Some(buf) = dc_read_file_safe(context, &arg1) {
|
if let Ok(buf) = dc_read_file(context, &arg1) {
|
||||||
let (width, height) = dc_get_filemeta(&buf)?;
|
let (width, height) = dc_get_filemeta(&buf)?;
|
||||||
println!("width={}, height={}", width, height);
|
println!("width={}, height={}", width, height);
|
||||||
} else {
|
} else {
|
||||||
@@ -1040,7 +981,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
|||||||
}
|
}
|
||||||
|
|
||||||
free(arg1_c as *mut _);
|
free(arg1_c as *mut _);
|
||||||
free(arg2_c as *mut _);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,11 +23,9 @@ use std::sync::{Arc, Mutex, RwLock};
|
|||||||
use deltachat::config;
|
use deltachat::config;
|
||||||
use deltachat::configure::*;
|
use deltachat::configure::*;
|
||||||
use deltachat::context::*;
|
use deltachat::context::*;
|
||||||
use deltachat::dc_tools::*;
|
|
||||||
use deltachat::job::*;
|
use deltachat::job::*;
|
||||||
use deltachat::oauth2::*;
|
use deltachat::oauth2::*;
|
||||||
use deltachat::securejoin::*;
|
use deltachat::securejoin::*;
|
||||||
use deltachat::x::*;
|
|
||||||
use deltachat::Event;
|
use deltachat::Event;
|
||||||
use rustyline::completion::{Completer, FilenameCompleter, Pair};
|
use rustyline::completion::{Completer, FilenameCompleter, Pair};
|
||||||
use rustyline::config::OutputStreamType;
|
use rustyline::config::OutputStreamType;
|
||||||
@@ -45,7 +43,6 @@ use self::cmdline::*;
|
|||||||
|
|
||||||
fn receive_event(_context: &Context, event: Event) -> libc::uintptr_t {
|
fn receive_event(_context: &Context, event: Event) -> libc::uintptr_t {
|
||||||
match event {
|
match event {
|
||||||
Event::GetString { .. } => {}
|
|
||||||
Event::Info(msg) => {
|
Event::Info(msg) => {
|
||||||
/* do not show the event as this would fill the screen */
|
/* do not show the event as this would fill the screen */
|
||||||
println!("{}", msg);
|
println!("{}", msg);
|
||||||
@@ -441,11 +438,6 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
|
|||||||
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();
|
||||||
let arg1_c = if arg1.is_empty() {
|
|
||||||
std::ptr::null()
|
|
||||||
} else {
|
|
||||||
arg1.strdup()
|
|
||||||
};
|
|
||||||
|
|
||||||
match arg0 {
|
match arg0 {
|
||||||
"connect" => {
|
"connect" => {
|
||||||
@@ -521,8 +513,6 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
|
|||||||
_ => dc_cmdline(&ctx.read().unwrap(), line)?,
|
_ => dc_cmdline(&ctx.read().unwrap(), line)?,
|
||||||
}
|
}
|
||||||
|
|
||||||
free(arg1_c as *mut _);
|
|
||||||
|
|
||||||
Ok(ExitResult::Continue)
|
Ok(ExitResult::Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,93 +35,90 @@ fn cb(_ctx: &Context, event: Event) -> usize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
unsafe {
|
let dir = tempdir().unwrap();
|
||||||
let dir = tempdir().unwrap();
|
let dbfile = dir.path().join("db.sqlite");
|
||||||
let dbfile = dir.path().join("db.sqlite");
|
println!("creating database {:?}", dbfile);
|
||||||
println!("creating database {:?}", dbfile);
|
let ctx =
|
||||||
let ctx =
|
Context::new(Box::new(cb), "FakeOs".into(), dbfile).expect("Failed to create context");
|
||||||
Context::new(Box::new(cb), "FakeOs".into(), dbfile).expect("Failed to create context");
|
let running = Arc::new(RwLock::new(true));
|
||||||
let running = Arc::new(RwLock::new(true));
|
let info = ctx.get_info();
|
||||||
let info = ctx.get_info();
|
let duration = time::Duration::from_millis(4000);
|
||||||
let duration = time::Duration::from_millis(4000);
|
println!("info: {:#?}", info);
|
||||||
println!("info: {:#?}", info);
|
|
||||||
|
let ctx = Arc::new(ctx);
|
||||||
|
let ctx1 = ctx.clone();
|
||||||
|
let r1 = running.clone();
|
||||||
|
let t1 = thread::spawn(move || {
|
||||||
|
while *r1.read().unwrap() {
|
||||||
|
perform_imap_jobs(&ctx1);
|
||||||
|
if *r1.read().unwrap() {
|
||||||
|
perform_imap_fetch(&ctx1);
|
||||||
|
|
||||||
let ctx = Arc::new(ctx);
|
|
||||||
let ctx1 = ctx.clone();
|
|
||||||
let r1 = running.clone();
|
|
||||||
let t1 = thread::spawn(move || {
|
|
||||||
while *r1.read().unwrap() {
|
|
||||||
perform_imap_jobs(&ctx1);
|
|
||||||
if *r1.read().unwrap() {
|
if *r1.read().unwrap() {
|
||||||
perform_imap_fetch(&ctx1);
|
perform_imap_idle(&ctx1);
|
||||||
|
|
||||||
if *r1.read().unwrap() {
|
|
||||||
perform_imap_idle(&ctx1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
let ctx1 = ctx.clone();
|
|
||||||
let r1 = running.clone();
|
|
||||||
let t2 = thread::spawn(move || {
|
|
||||||
while *r1.read().unwrap() {
|
|
||||||
perform_smtp_jobs(&ctx1);
|
|
||||||
if *r1.read().unwrap() {
|
|
||||||
perform_smtp_idle(&ctx1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
println!("configuring");
|
|
||||||
let args = std::env::args().collect::<Vec<String>>();
|
|
||||||
assert_eq!(args.len(), 2, "missing password");
|
|
||||||
let pw = args[1].clone();
|
|
||||||
ctx.set_config(config::Config::Addr, Some("d@testrun.org"))
|
|
||||||
.unwrap();
|
|
||||||
ctx.set_config(config::Config::MailPw, Some(&pw)).unwrap();
|
|
||||||
configure(&ctx);
|
|
||||||
|
|
||||||
thread::sleep(duration);
|
|
||||||
|
|
||||||
println!("sending a message");
|
|
||||||
let contact_id =
|
|
||||||
Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com").unwrap();
|
|
||||||
let chat_id = chat::create_by_contact_id(&ctx, contact_id).unwrap();
|
|
||||||
chat::send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into()).unwrap();
|
|
||||||
|
|
||||||
println!("fetching chats..");
|
|
||||||
let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap();
|
|
||||||
|
|
||||||
for i in 0..chats.len() {
|
|
||||||
let summary = chats.get_summary(&ctx, 0, None);
|
|
||||||
let text1 = summary.get_text1();
|
|
||||||
let text2 = summary.get_text2();
|
|
||||||
println!("chat: {} - {:?} - {:?}", i, text1, text2,);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
thread::sleep(duration);
|
let ctx1 = ctx.clone();
|
||||||
|
let r1 = running.clone();
|
||||||
|
let t2 = thread::spawn(move || {
|
||||||
|
while *r1.read().unwrap() {
|
||||||
|
perform_smtp_jobs(&ctx1);
|
||||||
|
if *r1.read().unwrap() {
|
||||||
|
perform_smtp_idle(&ctx1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// let msglist = dc_get_chat_msgs(&ctx, chat_id, 0, 0);
|
println!("configuring");
|
||||||
// for i in 0..dc_array_get_cnt(msglist) {
|
let args = std::env::args().collect::<Vec<String>>();
|
||||||
// let msg_id = dc_array_get_id(msglist, i);
|
assert_eq!(args.len(), 2, "missing password");
|
||||||
// let msg = dc_get_msg(context, msg_id);
|
let pw = args[1].clone();
|
||||||
// let text = CStr::from_ptr(dc_msg_get_text(msg)).unwrap();
|
ctx.set_config(config::Config::Addr, Some("d@testrun.org"))
|
||||||
// println!("Message {}: {}\n", i + 1, text.to_str().unwrap());
|
.unwrap();
|
||||||
// dc_msg_unref(msg);
|
ctx.set_config(config::Config::MailPw, Some(&pw)).unwrap();
|
||||||
// }
|
configure(&ctx);
|
||||||
// dc_array_unref(msglist);
|
|
||||||
|
|
||||||
println!("stopping threads");
|
thread::sleep(duration);
|
||||||
|
|
||||||
*running.clone().write().unwrap() = false;
|
println!("sending a message");
|
||||||
deltachat::job::interrupt_imap_idle(&ctx);
|
let contact_id = Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com").unwrap();
|
||||||
deltachat::job::interrupt_smtp_idle(&ctx);
|
let chat_id = chat::create_by_contact_id(&ctx, contact_id).unwrap();
|
||||||
|
chat::send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into()).unwrap();
|
||||||
|
|
||||||
println!("joining");
|
println!("fetching chats..");
|
||||||
t1.join().unwrap();
|
let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap();
|
||||||
t2.join().unwrap();
|
|
||||||
|
|
||||||
println!("closing");
|
for i in 0..chats.len() {
|
||||||
|
let summary = chats.get_summary(&ctx, 0, None);
|
||||||
|
let text1 = summary.get_text1();
|
||||||
|
let text2 = summary.get_text2();
|
||||||
|
println!("chat: {} - {:?} - {:?}", i, text1, text2,);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
thread::sleep(duration);
|
||||||
|
|
||||||
|
// let msglist = dc_get_chat_msgs(&ctx, chat_id, 0, 0);
|
||||||
|
// for i in 0..dc_array_get_cnt(msglist) {
|
||||||
|
// let msg_id = dc_array_get_id(msglist, i);
|
||||||
|
// let msg = dc_get_msg(context, msg_id);
|
||||||
|
// let text = CStr::from_ptr(dc_msg_get_text(msg)).unwrap();
|
||||||
|
// println!("Message {}: {}\n", i + 1, text.to_str().unwrap());
|
||||||
|
// dc_msg_unref(msg);
|
||||||
|
// }
|
||||||
|
// dc_array_unref(msglist);
|
||||||
|
|
||||||
|
println!("stopping threads");
|
||||||
|
|
||||||
|
*running.clone().write().unwrap() = false;
|
||||||
|
deltachat::job::interrupt_imap_idle(&ctx);
|
||||||
|
deltachat::job::interrupt_smtp_idle(&ctx);
|
||||||
|
|
||||||
|
println!("joining");
|
||||||
|
t1.join().unwrap();
|
||||||
|
t2.join().unwrap();
|
||||||
|
|
||||||
|
println!("closing");
|
||||||
}
|
}
|
||||||
|
|||||||
42
mmime/.circleci/config.yml
Normal file
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
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
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
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
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
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
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
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
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
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
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
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
5921
mmime/src/mailimf/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
1196
mmime/src/mailimf/types.rs
Normal file
1196
mmime/src/mailimf/types.rs
Normal file
File diff suppressed because it is too large
Load Diff
89
mmime/src/mailimf/types_helper.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
1985
mmime/src/mailimf/write_generic.rs
Normal file
File diff suppressed because it is too large
Load Diff
2357
mmime/src/mailmime/content.rs
Normal file
2357
mmime/src/mailmime/content.rs
Normal file
File diff suppressed because it is too large
Load Diff
860
mmime/src/mailmime/decode.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
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
1143
mmime/src/mailmime/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
891
mmime/src/mailmime/types.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
1445
mmime/src/mailmime/types_helper.rs
Normal file
File diff suppressed because it is too large
Load Diff
1979
mmime/src/mailmime/write_generic.rs
Normal file
1979
mmime/src/mailmime/write_generic.rs
Normal file
File diff suppressed because it is too large
Load Diff
82
mmime/src/mailmime/write_mem.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
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
1728
mmime/src/other.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,3 +5,4 @@
|
|||||||
# It is recommended to check this file in to source control so that
|
# It is recommended to check this file in to source control so that
|
||||||
# everyone who runs the test benefits from these saved cases.
|
# everyone who runs the test benefits from these saved cases.
|
||||||
cc 679506fe9ac59df773f8cfa800fdab5f0a32fe49d2ab370394000a1aa5bc2a72 # shrinks to buf = "%0A"
|
cc 679506fe9ac59df773f8cfa800fdab5f0a32fe49d2ab370394000a1aa5bc2a72 # shrinks to buf = "%0A"
|
||||||
|
cc e34960438edb2426904b44fb4215154e7e2880f2fd1c3183b98bfcc76fec4882 # shrinks to input = " 0"
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ If you want to run "liveconfig" functional tests you can set
|
|||||||
chat devs.
|
chat devs.
|
||||||
|
|
||||||
- or the path of a file that contains two lines, each describing
|
- or the path of a file that contains two lines, each describing
|
||||||
via "addr=... mail_pwd=..." a test account login that will
|
via "addr=... mail_pw=..." a test account login that will
|
||||||
be used for the live tests.
|
be used for the live tests.
|
||||||
|
|
||||||
With ``DCC_PY_LIVECONFIG`` set pytest invocations will use real
|
With ``DCC_PY_LIVECONFIG`` set pytest invocations will use real
|
||||||
|
|||||||
1
python/doc/_templates/globaltoc.html
vendored
1
python/doc/_templates/globaltoc.html
vendored
@@ -6,7 +6,6 @@
|
|||||||
<li><a href="{{ pathto('install') }}">install</a></li>
|
<li><a href="{{ pathto('install') }}">install</a></li>
|
||||||
<li><a href="{{ pathto('api') }}">high level API</a></li>
|
<li><a href="{{ pathto('api') }}">high level API</a></li>
|
||||||
<li><a href="{{ pathto('lapi') }}">low level API</a></li>
|
<li><a href="{{ pathto('lapi') }}">low level API</a></li>
|
||||||
<li><a href="{{ pathto('capi') }}">C deltachat.h</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<b>external links:</b>
|
<b>external links:</b>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
@@ -2,7 +2,13 @@
|
|||||||
low level API reference
|
low level API reference
|
||||||
===================================
|
===================================
|
||||||
|
|
||||||
for full C-docs, defines and function checkout :doc:`capi`
|
for full doxygen-generated C-docs, defines and functions please checkout
|
||||||
|
|
||||||
|
https://c.delta.chat
|
||||||
|
|
||||||
|
|
||||||
|
Python low-level capi calls
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
|
||||||
.. automodule:: deltachat.capi.lib
|
.. automodule:: deltachat.capi.lib
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import os
|
import sys
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
os.environ["DCC_RS_TARGET"] = target = "release"
|
os.environ["DCC_RS_TARGET"] = target = "release"
|
||||||
@@ -21,5 +21,5 @@ if __name__ == "__main__":
|
|||||||
subprocess.check_call("rm -rf build/ src/deltachat/*.so" , shell=True)
|
subprocess.check_call("rm -rf build/ src/deltachat/*.so" , shell=True)
|
||||||
|
|
||||||
subprocess.check_call([
|
subprocess.check_call([
|
||||||
"pip", "install", "-e", "."
|
sys.executable, "-m", "pip", "install", "-e", "."
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
""" Account class implementation. """
|
""" Account class implementation. """
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
import atexit
|
||||||
import threading
|
import threading
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
from array import array
|
from array import array
|
||||||
@@ -23,7 +23,7 @@ class Account(object):
|
|||||||
by the underlying deltachat c-library. All public Account methods are
|
by the underlying deltachat c-library. All public Account methods are
|
||||||
meant to be memory-safe and return memory-safe objects.
|
meant to be memory-safe and return memory-safe objects.
|
||||||
"""
|
"""
|
||||||
def __init__(self, db_path, logid=None, eventlogging=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
|
||||||
@@ -31,13 +31,14 @@ 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 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, ffi.NULL),
|
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
||||||
_destroy_dc_context,
|
_destroy_dc_context,
|
||||||
)
|
)
|
||||||
if eventlogging:
|
if eventlogging:
|
||||||
self._evlogger = EventLogger(self._dc_context, logid)
|
self._evlogger = EventLogger(self._dc_context, logid, debug)
|
||||||
deltachat.set_context_callback(self._dc_context, self._process_event)
|
deltachat.set_context_callback(self._dc_context, self._process_event)
|
||||||
self._threads = IOThreads(self._dc_context, self._evlogger._log_event)
|
self._threads = IOThreads(self._dc_context, self._evlogger._log_event)
|
||||||
else:
|
else:
|
||||||
@@ -48,10 +49,11 @@ class Account(object):
|
|||||||
if not lib.dc_open(self._dc_context, db_path, ffi.NULL):
|
if not lib.dc_open(self._dc_context, db_path, ffi.NULL):
|
||||||
raise ValueError("Could not dc_open: {}".format(db_path))
|
raise ValueError("Could not dc_open: {}".format(db_path))
|
||||||
self._configkeys = self.get_config("sys.config_keys").split()
|
self._configkeys = self.get_config("sys.config_keys").split()
|
||||||
self._imex_completed = threading.Event()
|
self._imex_events = Queue()
|
||||||
|
atexit.register(self.shutdown)
|
||||||
|
|
||||||
def __del__(self):
|
# def __del__(self):
|
||||||
self.shutdown()
|
# self.shutdown()
|
||||||
|
|
||||||
def _check_config_key(self, name):
|
def _check_config_key(self, name):
|
||||||
if name not in self._configkeys:
|
if name not in self._configkeys:
|
||||||
@@ -69,6 +71,18 @@ class Account(object):
|
|||||||
d[key.lower()] = value
|
d[key.lower()] = value
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
def set_stock_translation(self, id, string):
|
||||||
|
""" set stock translation string.
|
||||||
|
|
||||||
|
:param id: id of stock string (const.DC_STR_*)
|
||||||
|
:param value: string to set as new transalation
|
||||||
|
:returns: None
|
||||||
|
"""
|
||||||
|
string = string.encode("utf8")
|
||||||
|
res = lib.dc_set_stock_translation(self._dc_context, id, string)
|
||||||
|
if res == 0:
|
||||||
|
raise ValueError("could not set translation string")
|
||||||
|
|
||||||
def set_config(self, name, value):
|
def set_config(self, name, value):
|
||||||
""" set configuration values.
|
""" set configuration values.
|
||||||
|
|
||||||
@@ -231,7 +245,7 @@ class Account(object):
|
|||||||
:returns: a :class:`deltachat.chatting.Chat` object.
|
:returns: a :class:`deltachat.chatting.Chat` object.
|
||||||
"""
|
"""
|
||||||
bytes_name = name.encode("utf8")
|
bytes_name = name.encode("utf8")
|
||||||
chat_id = lib.dc_create_group_chat(self._dc_context, verified, bytes_name)
|
chat_id = lib.dc_create_group_chat(self._dc_context, int(verified), bytes_name)
|
||||||
return Chat(self, chat_id)
|
return Chat(self, chat_id)
|
||||||
|
|
||||||
def get_chats(self):
|
def get_chats(self):
|
||||||
@@ -289,31 +303,64 @@ class Account(object):
|
|||||||
msg_ids = [msg.id for msg in messages]
|
msg_ids = [msg.id for msg in messages]
|
||||||
lib.dc_delete_msgs(self._dc_context, msg_ids, len(msg_ids))
|
lib.dc_delete_msgs(self._dc_context, msg_ids, len(msg_ids))
|
||||||
|
|
||||||
def export_to_dir(self, backupdir):
|
def export_self_keys(self, path):
|
||||||
"""return after all delta chat state is exported to a new file in
|
""" export public and private keys to the specified directory. """
|
||||||
the specified directory.
|
return self._export(path, imex_cmd=1)
|
||||||
|
|
||||||
|
def export_all(self, path):
|
||||||
|
"""return new file containing a backup of all database state
|
||||||
|
(chats, contacts, keys, media, ...). The file is created in the
|
||||||
|
the `path` directory.
|
||||||
"""
|
"""
|
||||||
snap_files = os.listdir(backupdir)
|
export_files = self._export(path, 11)
|
||||||
self._imex_completed.clear()
|
if len(export_files) != 1:
|
||||||
lib.dc_imex(self._dc_context, 11, as_dc_charpointer(backupdir), ffi.NULL)
|
raise RuntimeError("found more than one new file")
|
||||||
|
return export_files[0]
|
||||||
|
|
||||||
|
def _imex_events_clear(self):
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
self._imex_events.get_nowait()
|
||||||
|
except Empty:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _export(self, path, imex_cmd):
|
||||||
|
self._imex_events_clear()
|
||||||
|
lib.dc_imex(self._dc_context, imex_cmd, as_dc_charpointer(path), ffi.NULL)
|
||||||
if not self._threads.is_started():
|
if not self._threads.is_started():
|
||||||
lib.dc_perform_imap_jobs(self._dc_context)
|
lib.dc_perform_imap_jobs(self._dc_context)
|
||||||
self._imex_completed.wait()
|
files_written = []
|
||||||
for x in os.listdir(backupdir):
|
while True:
|
||||||
if x not in snap_files:
|
ev = self._imex_events.get()
|
||||||
return os.path.join(backupdir, x)
|
if isinstance(ev, str):
|
||||||
|
files_written.append(ev)
|
||||||
|
elif isinstance(ev, bool):
|
||||||
|
if not ev:
|
||||||
|
raise ValueError("export failed, exp-files: {}".format(files_written))
|
||||||
|
return files_written
|
||||||
|
|
||||||
def import_from_file(self, path):
|
def import_self_keys(self, path):
|
||||||
"""import delta chat state from the specified backup file.
|
""" Import private keys found in the `path` directory.
|
||||||
|
The last imported key is made the default keys unless its name
|
||||||
|
contains the string legacy. Public keys are not imported.
|
||||||
|
"""
|
||||||
|
self._import(path, imex_cmd=2)
|
||||||
|
|
||||||
|
def import_all(self, path):
|
||||||
|
"""import delta chat state from the specified backup `path` (a file).
|
||||||
|
|
||||||
The account must be in unconfigured state for import to attempted.
|
The account must be in unconfigured state for import to attempted.
|
||||||
"""
|
"""
|
||||||
assert not self.is_configured(), "cannot import into configured account"
|
assert not self.is_configured(), "cannot import into configured account"
|
||||||
self._imex_completed.clear()
|
self._import(path, imex_cmd=12)
|
||||||
lib.dc_imex(self._dc_context, 12, as_dc_charpointer(path), ffi.NULL)
|
|
||||||
|
def _import(self, path, imex_cmd):
|
||||||
|
self._imex_events_clear()
|
||||||
|
lib.dc_imex(self._dc_context, imex_cmd, as_dc_charpointer(path), ffi.NULL)
|
||||||
if not self._threads.is_started():
|
if not self._threads.is_started():
|
||||||
lib.dc_perform_imap_jobs(self._dc_context)
|
lib.dc_perform_imap_jobs(self._dc_context)
|
||||||
self._imex_completed.wait()
|
if not self._imex_events.get():
|
||||||
|
raise ValueError("import from path '{}' failed".format(path))
|
||||||
|
|
||||||
def initiate_key_transfer(self):
|
def initiate_key_transfer(self):
|
||||||
"""return setup code after a Autocrypt setup message
|
"""return setup code after a Autocrypt setup message
|
||||||
@@ -378,7 +425,19 @@ class Account(object):
|
|||||||
raise ValueError("could not join group")
|
raise ValueError("could not join group")
|
||||||
return Chat(self, chat_id)
|
return Chat(self, chat_id)
|
||||||
|
|
||||||
def start_threads(self):
|
def stop_ongoing(self):
|
||||||
|
lib.dc_stop_ongoing_process(self._dc_context)
|
||||||
|
|
||||||
|
#
|
||||||
|
# meta API for start/stop and event based processing
|
||||||
|
#
|
||||||
|
|
||||||
|
def wait_next_incoming_message(self):
|
||||||
|
""" wait for and return next incoming message. """
|
||||||
|
ev = self._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
|
||||||
|
return self.get_message_by_id(ev[2])
|
||||||
|
|
||||||
|
def start_threads(self, mvbox=False, sentbox=False):
|
||||||
""" start IMAP/SMTP threads (and configure account if it hasn't happened).
|
""" start IMAP/SMTP threads (and configure account if it hasn't happened).
|
||||||
|
|
||||||
:raises: ValueError if 'addr' or 'mail_pw' are not configured.
|
:raises: ValueError if 'addr' or 'mail_pw' are not configured.
|
||||||
@@ -386,11 +445,11 @@ class Account(object):
|
|||||||
"""
|
"""
|
||||||
if not self.is_configured():
|
if not self.is_configured():
|
||||||
self.configure()
|
self.configure()
|
||||||
self._threads.start()
|
self._threads.start(mvbox=mvbox, sentbox=sentbox)
|
||||||
|
|
||||||
def stop_threads(self, wait=True):
|
def stop_threads(self, wait=True):
|
||||||
""" stop IMAP/SMTP threads. """
|
""" stop IMAP/SMTP threads. """
|
||||||
lib.dc_stop_ongoing_process(self._dc_context)
|
self.stop_ongoing()
|
||||||
self._threads.stop(wait=wait)
|
self._threads.stop(wait=wait)
|
||||||
|
|
||||||
def shutdown(self, wait=True):
|
def shutdown(self, wait=True):
|
||||||
@@ -402,6 +461,7 @@ class Account(object):
|
|||||||
self.stop_threads(wait=wait) # to wait for threads
|
self.stop_threads(wait=wait) # to wait for threads
|
||||||
deltachat.clear_context_callback(self._dc_context)
|
deltachat.clear_context_callback(self._dc_context)
|
||||||
del self._dc_context
|
del self._dc_context
|
||||||
|
atexit.unregister(self.shutdown)
|
||||||
|
|
||||||
def _process_event(self, ctx, evt_name, data1, data2):
|
def _process_event(self, ctx, evt_name, data1, data2):
|
||||||
assert ctx == self._dc_context
|
assert ctx == self._dc_context
|
||||||
@@ -414,7 +474,12 @@ class Account(object):
|
|||||||
|
|
||||||
def on_dc_event_imex_progress(self, data1, data2):
|
def on_dc_event_imex_progress(self, data1, data2):
|
||||||
if data1 == 1000:
|
if data1 == 1000:
|
||||||
self._imex_completed.set()
|
self._imex_events.put(True)
|
||||||
|
elif data1 == 0:
|
||||||
|
self._imex_events.put(False)
|
||||||
|
|
||||||
|
def on_dc_event_imex_file_written(self, data1, data2):
|
||||||
|
self._imex_events.put(data1)
|
||||||
|
|
||||||
|
|
||||||
class IOThreads:
|
class IOThreads:
|
||||||
@@ -427,10 +492,14 @@ class IOThreads:
|
|||||||
def is_started(self):
|
def is_started(self):
|
||||||
return len(self._name2thread) > 0
|
return len(self._name2thread) > 0
|
||||||
|
|
||||||
def start(self, imap=True, smtp=True):
|
def start(self, imap=True, smtp=True, mvbox=False, sentbox=False):
|
||||||
assert not self.is_started()
|
assert not self.is_started()
|
||||||
if imap:
|
if imap:
|
||||||
self._start_one_thread("imap", self.imap_thread_run)
|
self._start_one_thread("inbox", self.imap_thread_run)
|
||||||
|
if mvbox:
|
||||||
|
self._start_one_thread("mvbox", self.mvbox_thread_run)
|
||||||
|
if sentbox:
|
||||||
|
self._start_one_thread("sentbox", self.sentbox_thread_run)
|
||||||
if smtp:
|
if smtp:
|
||||||
self._start_one_thread("smtp", self.smtp_thread_run)
|
self._start_one_thread("smtp", self.smtp_thread_run)
|
||||||
|
|
||||||
@@ -443,17 +512,35 @@ class IOThreads:
|
|||||||
self._thread_quitflag = True
|
self._thread_quitflag = True
|
||||||
lib.dc_interrupt_imap_idle(self._dc_context)
|
lib.dc_interrupt_imap_idle(self._dc_context)
|
||||||
lib.dc_interrupt_smtp_idle(self._dc_context)
|
lib.dc_interrupt_smtp_idle(self._dc_context)
|
||||||
|
lib.dc_interrupt_mvbox_idle(self._dc_context)
|
||||||
|
lib.dc_interrupt_sentbox_idle(self._dc_context)
|
||||||
if wait:
|
if wait:
|
||||||
for name, thread in self._name2thread.items():
|
for name, thread in self._name2thread.items():
|
||||||
thread.join()
|
thread.join()
|
||||||
|
|
||||||
def imap_thread_run(self):
|
def imap_thread_run(self):
|
||||||
self._log_event("py-bindings-info", 0, "IMAP 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)
|
||||||
lib.dc_perform_imap_fetch(self._dc_context)
|
lib.dc_perform_imap_fetch(self._dc_context)
|
||||||
lib.dc_perform_imap_idle(self._dc_context)
|
lib.dc_perform_imap_idle(self._dc_context)
|
||||||
self._log_event("py-bindings-info", 0, "IMAP THREAD FINISHED")
|
self._log_event("py-bindings-info", 0, "INBOX THREAD FINISHED")
|
||||||
|
|
||||||
|
def mvbox_thread_run(self):
|
||||||
|
self._log_event("py-bindings-info", 0, "MVBOX THREAD START")
|
||||||
|
while not self._thread_quitflag:
|
||||||
|
lib.dc_perform_mvbox_jobs(self._dc_context)
|
||||||
|
lib.dc_perform_mvbox_fetch(self._dc_context)
|
||||||
|
lib.dc_perform_mvbox_idle(self._dc_context)
|
||||||
|
self._log_event("py-bindings-info", 0, "MVBOX THREAD FINISHED")
|
||||||
|
|
||||||
|
def sentbox_thread_run(self):
|
||||||
|
self._log_event("py-bindings-info", 0, "SENTBOX THREAD START")
|
||||||
|
while not self._thread_quitflag:
|
||||||
|
lib.dc_perform_sentbox_jobs(self._dc_context)
|
||||||
|
lib.dc_perform_sentbox_fetch(self._dc_context)
|
||||||
|
lib.dc_perform_sentbox_idle(self._dc_context)
|
||||||
|
self._log_event("py-bindings-info", 0, "SENTBOX THREAD FINISHED")
|
||||||
|
|
||||||
def smtp_thread_run(self):
|
def smtp_thread_run(self):
|
||||||
self._log_event("py-bindings-info", 0, "SMTP THREAD START")
|
self._log_event("py-bindings-info", 0, "SMTP THREAD START")
|
||||||
@@ -505,11 +592,11 @@ class EventLogger:
|
|||||||
else:
|
else:
|
||||||
assert not rex.match(ev[0]), "event found {}".format(ev)
|
assert not rex.match(ev[0]), "event found {}".format(ev)
|
||||||
|
|
||||||
def get_matching(self, event_name_regex, check_error=True):
|
def get_matching(self, event_name_regex, check_error=True, timeout=None):
|
||||||
self._log("-- waiting for event with regex: {} --".format(event_name_regex))
|
self._log("-- waiting for event with regex: {} --".format(event_name_regex))
|
||||||
rex = re.compile("(?:{}).*".format(event_name_regex))
|
rex = re.compile("(?:{}).*".format(event_name_regex))
|
||||||
while 1:
|
while 1:
|
||||||
ev = self.get()
|
ev = self.get(timeout=timeout, check_error=check_error)
|
||||||
if rex.match(ev[0]):
|
if rex.match(ev[0]):
|
||||||
return ev
|
return ev
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,13 @@ class Chat(object):
|
|||||||
"""
|
"""
|
||||||
return not lib.dc_chat_is_unpromoted(self._dc_chat)
|
return not lib.dc_chat_is_unpromoted(self._dc_chat)
|
||||||
|
|
||||||
|
def is_verified(self):
|
||||||
|
""" return True if this chat is a verified group.
|
||||||
|
|
||||||
|
:returns: True if chat is verified, False otherwise.
|
||||||
|
"""
|
||||||
|
return lib.dc_chat_is_verified(self._dc_chat)
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
""" return name of this chat.
|
""" return name of this chat.
|
||||||
|
|
||||||
|
|||||||
@@ -52,14 +52,27 @@ DC_CONTACT_ID_LAST_SPECIAL = 9
|
|||||||
DC_MSG_TEXT = 10
|
DC_MSG_TEXT = 10
|
||||||
DC_MSG_IMAGE = 20
|
DC_MSG_IMAGE = 20
|
||||||
DC_MSG_GIF = 21
|
DC_MSG_GIF = 21
|
||||||
|
DC_MSG_STICKER = 23
|
||||||
DC_MSG_AUDIO = 40
|
DC_MSG_AUDIO = 40
|
||||||
DC_MSG_VOICE = 41
|
DC_MSG_VOICE = 41
|
||||||
DC_MSG_VIDEO = 50
|
DC_MSG_VIDEO = 50
|
||||||
DC_MSG_FILE = 60
|
DC_MSG_FILE = 60
|
||||||
|
DC_LP_AUTH_OAUTH2 = 0x2
|
||||||
|
DC_LP_AUTH_NORMAL = 0x4
|
||||||
|
DC_LP_IMAP_SOCKET_STARTTLS = 0x100
|
||||||
|
DC_LP_IMAP_SOCKET_SSL = 0x200
|
||||||
|
DC_LP_IMAP_SOCKET_PLAIN = 0x400
|
||||||
|
DC_LP_SMTP_SOCKET_STARTTLS = 0x10000
|
||||||
|
DC_LP_SMTP_SOCKET_SSL = 0x20000
|
||||||
|
DC_LP_SMTP_SOCKET_PLAIN = 0x40000
|
||||||
DC_EVENT_INFO = 100
|
DC_EVENT_INFO = 100
|
||||||
DC_EVENT_SMTP_CONNECTED = 101
|
DC_EVENT_SMTP_CONNECTED = 101
|
||||||
DC_EVENT_IMAP_CONNECTED = 102
|
DC_EVENT_IMAP_CONNECTED = 102
|
||||||
DC_EVENT_SMTP_MESSAGE_SENT = 103
|
DC_EVENT_SMTP_MESSAGE_SENT = 103
|
||||||
|
DC_EVENT_IMAP_MESSAGE_DELETED = 104
|
||||||
|
DC_EVENT_IMAP_MESSAGE_MOVED = 105
|
||||||
|
DC_EVENT_NEW_BLOB_FILE = 150
|
||||||
|
DC_EVENT_DELETED_BLOB_FILE = 151
|
||||||
DC_EVENT_WARNING = 300
|
DC_EVENT_WARNING = 300
|
||||||
DC_EVENT_ERROR = 400
|
DC_EVENT_ERROR = 400
|
||||||
DC_EVENT_ERROR_NETWORK = 401
|
DC_EVENT_ERROR_NETWORK = 401
|
||||||
@@ -77,14 +90,65 @@ 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_GET_STRING = 2091
|
|
||||||
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_STR_SELFNOTINGRP = 21
|
||||||
|
DC_PROVIDER_STATUS_OK = 1
|
||||||
|
DC_PROVIDER_STATUS_PREPARATION = 2
|
||||||
|
DC_PROVIDER_STATUS_BROKEN = 3
|
||||||
|
DC_STR_NOMESSAGES = 1
|
||||||
|
DC_STR_SELF = 2
|
||||||
|
DC_STR_DRAFT = 3
|
||||||
|
DC_STR_MEMBER = 4
|
||||||
|
DC_STR_CONTACT = 6
|
||||||
|
DC_STR_VOICEMESSAGE = 7
|
||||||
|
DC_STR_DEADDROP = 8
|
||||||
|
DC_STR_IMAGE = 9
|
||||||
|
DC_STR_VIDEO = 10
|
||||||
|
DC_STR_AUDIO = 11
|
||||||
|
DC_STR_FILE = 12
|
||||||
|
DC_STR_STATUSLINE = 13
|
||||||
|
DC_STR_NEWGROUPDRAFT = 14
|
||||||
|
DC_STR_MSGGRPNAME = 15
|
||||||
|
DC_STR_MSGGRPIMGCHANGED = 16
|
||||||
|
DC_STR_MSGADDMEMBER = 17
|
||||||
|
DC_STR_MSGDELMEMBER = 18
|
||||||
|
DC_STR_MSGGROUPLEFT = 19
|
||||||
|
DC_STR_GIF = 23
|
||||||
|
DC_STR_ENCRYPTEDMSG = 24
|
||||||
|
DC_STR_E2E_AVAILABLE = 25
|
||||||
|
DC_STR_ENCR_TRANSP = 27
|
||||||
|
DC_STR_ENCR_NONE = 28
|
||||||
|
DC_STR_CANTDECRYPT_MSG_BODY = 29
|
||||||
|
DC_STR_FINGERPRINTS = 30
|
||||||
|
DC_STR_READRCPT = 31
|
||||||
|
DC_STR_READRCPT_MAILBODY = 32
|
||||||
|
DC_STR_MSGGRPIMGDELETED = 33
|
||||||
|
DC_STR_E2E_PREFERRED = 34
|
||||||
|
DC_STR_CONTACT_VERIFIED = 35
|
||||||
|
DC_STR_CONTACT_NOT_VERIFIED = 36
|
||||||
|
DC_STR_CONTACT_SETUP_CHANGED = 37
|
||||||
|
DC_STR_ARCHIVEDCHATS = 40
|
||||||
|
DC_STR_STARREDMSGS = 41
|
||||||
|
DC_STR_AC_SETUP_MSG_SUBJECT = 42
|
||||||
|
DC_STR_AC_SETUP_MSG_BODY = 43
|
||||||
|
DC_STR_SELFTALK_SUBTITLE = 50
|
||||||
|
DC_STR_CANNOT_LOGIN = 60
|
||||||
|
DC_STR_SERVER_RESPONSE = 61
|
||||||
|
DC_STR_MSGACTIONBYUSER = 62
|
||||||
|
DC_STR_MSGACTIONBYME = 63
|
||||||
|
DC_STR_MSGLOCATIONENABLED = 64
|
||||||
|
DC_STR_MSGLOCATIONDISABLED = 65
|
||||||
|
DC_STR_LOCATION = 66
|
||||||
|
DC_STR_STICKER = 67
|
||||||
|
DC_STR_COUNT = 67
|
||||||
# end const generated
|
# end const generated
|
||||||
|
|
||||||
|
|
||||||
def read_event_defines(f):
|
def read_event_defines(f):
|
||||||
rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_QR|DC_MSG|DC_STATE_|DC_CONTACT_ID_|DC_GCL|DC_CHAT)\S+)\s+([x\d]+).*')
|
rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_QR|DC_MSG|DC_LP|DC_STATE_|DC_STR|'
|
||||||
|
r'DC_CONTACT_ID_|DC_GCL|DC_CHAT|DC_PROVIDER)\S+)\s+([x\d]+).*')
|
||||||
for line in f:
|
for line in f:
|
||||||
m = rex.match(line)
|
m = rex.match(line)
|
||||||
if m:
|
if m:
|
||||||
|
|||||||
@@ -101,6 +101,18 @@ class Message(object):
|
|||||||
""" return True if this message is a setup message. """
|
""" return True if this message is a setup message. """
|
||||||
return lib.dc_msg_is_setupmessage(self._dc_msg)
|
return lib.dc_msg_is_setupmessage(self._dc_msg)
|
||||||
|
|
||||||
|
def get_setupcodebegin(self):
|
||||||
|
""" return the first characters of a setup code in a setup message. """
|
||||||
|
return from_dc_charpointer(lib.dc_msg_get_setupcodebegin(self._dc_msg))
|
||||||
|
|
||||||
|
def is_encrypted(self):
|
||||||
|
""" return True if this message was encrypted. """
|
||||||
|
return bool(lib.dc_msg_get_showpadlock(self._dc_msg))
|
||||||
|
|
||||||
|
def is_forwarded(self):
|
||||||
|
""" return True if this message was forwarded. """
|
||||||
|
return bool(lib.dc_msg_is_forwarded(self._dc_msg))
|
||||||
|
|
||||||
def get_message_info(self):
|
def get_message_info(self):
|
||||||
""" Return informational text for a single message.
|
""" Return informational text for a single message.
|
||||||
|
|
||||||
|
|||||||
67
python/src/deltachat/provider.py
Normal file
67
python/src/deltachat/provider.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
"""Provider info class."""
|
||||||
|
|
||||||
|
from .capi import ffi, lib
|
||||||
|
from .cutil import as_dc_charpointer, from_dc_charpointer
|
||||||
|
|
||||||
|
|
||||||
|
class ProviderNotFoundError(Exception):
|
||||||
|
"""The provider information was not found."""
|
||||||
|
|
||||||
|
|
||||||
|
class Provider(object):
|
||||||
|
"""Provider information.
|
||||||
|
|
||||||
|
:param domain: The domain to get the provider info for, this is
|
||||||
|
normally the part following the `@` of the domain.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, domain):
|
||||||
|
provider = ffi.gc(
|
||||||
|
lib.dc_provider_new_from_domain(as_dc_charpointer(domain)),
|
||||||
|
lib.dc_provider_unref,
|
||||||
|
)
|
||||||
|
if provider == ffi.NULL:
|
||||||
|
raise ProviderNotFoundError("Provider not found")
|
||||||
|
self._provider = provider
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_email(cls, email):
|
||||||
|
"""Create provider info from an email address.
|
||||||
|
|
||||||
|
:param email: Email address to get provider info for.
|
||||||
|
"""
|
||||||
|
return cls(email.split('@')[-1])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def overview_page(self):
|
||||||
|
"""URL to the overview page of the provider on providers.delta.chat."""
|
||||||
|
return from_dc_charpointer(
|
||||||
|
lib.dc_provider_get_overview_page(self._provider))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""The name of the provider."""
|
||||||
|
return from_dc_charpointer(lib.dc_provider_get_name(self._provider))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def markdown(self):
|
||||||
|
"""Content of the information page, formatted as markdown."""
|
||||||
|
return from_dc_charpointer(
|
||||||
|
lib.dc_provider_get_markdown(self._provider))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status_date(self):
|
||||||
|
"""The date the provider info was last updated, as a string."""
|
||||||
|
return from_dc_charpointer(
|
||||||
|
lib.dc_provider_get_status_date(self._provider))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self):
|
||||||
|
"""The status of the provider information.
|
||||||
|
|
||||||
|
This is one of the
|
||||||
|
:attr:`deltachat.const.DC_PROVIDER_STATUS_OK`,
|
||||||
|
:attr:`deltachat.const.DC_PROVIDER_STATUS_PREPARATION` or
|
||||||
|
:attr:`deltachat.const.DC_PROVIDER_STATUS_BROKEN` constants.
|
||||||
|
"""
|
||||||
|
return lib.dc_provider_get_status(self._provider)
|
||||||
@@ -150,19 +150,33 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
|
|||||||
lib.dc_set_config(ac._dc_context, b"configured", b"1")
|
lib.dc_set_config(ac._dc_context, b"configured", b"1")
|
||||||
return ac
|
return ac
|
||||||
|
|
||||||
def get_online_configuring_account(self):
|
def peek_online_config(self):
|
||||||
|
if not session_liveconfig:
|
||||||
|
pytest.skip("specify DCC_PY_LIVECONFIG or --liveconfig")
|
||||||
|
return session_liveconfig.get(self.live_count)
|
||||||
|
|
||||||
|
def get_online_config(self):
|
||||||
if not session_liveconfig:
|
if not session_liveconfig:
|
||||||
pytest.skip("specify DCC_PY_LIVECONFIG or --liveconfig")
|
pytest.skip("specify DCC_PY_LIVECONFIG or --liveconfig")
|
||||||
configdict = session_liveconfig.get(self.live_count)
|
configdict = session_liveconfig.get(self.live_count)
|
||||||
self.live_count += 1
|
self.live_count += 1
|
||||||
if "e2ee_enabled" not in configdict:
|
if "e2ee_enabled" not in configdict:
|
||||||
configdict["e2ee_enabled"] = "1"
|
configdict["e2ee_enabled"] = "1"
|
||||||
|
|
||||||
|
# Enable strict certificate checks for online accounts
|
||||||
|
configdict["imap_certificate_checks"] = "1"
|
||||||
|
configdict["smtp_certificate_checks"] = "1"
|
||||||
|
|
||||||
tmpdb = tmpdir.join("livedb%d" % self.live_count)
|
tmpdb = tmpdir.join("livedb%d" % self.live_count)
|
||||||
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.live_count))
|
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.live_count))
|
||||||
ac._evlogger.init_time = self.init_time
|
ac._evlogger.init_time = self.init_time
|
||||||
ac._evlogger.set_timeout(30)
|
ac._evlogger.set_timeout(30)
|
||||||
|
return ac, dict(configdict)
|
||||||
|
|
||||||
|
def get_online_configuring_account(self, mvbox=False, sentbox=False):
|
||||||
|
ac, configdict = self.get_online_config()
|
||||||
ac.configure(**configdict)
|
ac.configure(**configdict)
|
||||||
ac.start_threads()
|
ac.start_threads(mvbox=mvbox, sentbox=sentbox)
|
||||||
return ac
|
return ac
|
||||||
|
|
||||||
def get_two_online_accounts(self):
|
def get_two_online_accounts(self):
|
||||||
@@ -206,12 +220,13 @@ def lp():
|
|||||||
return Printer()
|
return Printer()
|
||||||
|
|
||||||
|
|
||||||
def wait_configuration_progress(account, target):
|
def wait_configuration_progress(account, min_target, max_target=1001):
|
||||||
|
min_target = min(min_target, max_target)
|
||||||
while 1:
|
while 1:
|
||||||
evt_name, data1, data2 = \
|
evt_name, data1, data2 = \
|
||||||
account._evlogger.get_matching("DC_EVENT_CONFIGURE_PROGRESS")
|
account._evlogger.get_matching("DC_EVENT_CONFIGURE_PROGRESS")
|
||||||
if data1 >= target:
|
if data1 >= min_target and data1 <= max_target:
|
||||||
print("** CONFIG PROGRESS {}".format(target), account)
|
print("** CONFIG PROGRESS {}".format(min_target), account)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import pytest
|
import pytest
|
||||||
import os
|
import os
|
||||||
|
import queue
|
||||||
from deltachat import const, Account
|
from deltachat import const, Account
|
||||||
from deltachat.message import Message
|
from deltachat.message import Message
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
@@ -19,6 +20,7 @@ class TestOfflineAccountBasic:
|
|||||||
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"] == "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()
|
||||||
@@ -37,6 +39,11 @@ class TestOfflineAccountBasic:
|
|||||||
ac1 = acfactory.get_unconfigured_account()
|
ac1 = acfactory.get_unconfigured_account()
|
||||||
assert "save_mime_headers" in ac1.get_config("sys.config_keys").split()
|
assert "save_mime_headers" in ac1.get_config("sys.config_keys").split()
|
||||||
|
|
||||||
|
def test_has_bccself(self, acfactory):
|
||||||
|
ac1 = acfactory.get_unconfigured_account()
|
||||||
|
assert "bcc_self" in ac1.get_config("sys.config_keys").split()
|
||||||
|
assert ac1.get_config("bcc_self") == "1"
|
||||||
|
|
||||||
def test_selfcontact_if_unconfigured(self, acfactory):
|
def test_selfcontact_if_unconfigured(self, acfactory):
|
||||||
ac1 = acfactory.get_unconfigured_account()
|
ac1 = acfactory.get_unconfigured_account()
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
@@ -93,8 +100,9 @@ class TestOfflineContact:
|
|||||||
ac1 = acfactory.get_configured_offline_account()
|
ac1 = acfactory.get_configured_offline_account()
|
||||||
contact1 = ac1.create_contact(email="some1@example.com", name="some1")
|
contact1 = ac1.create_contact(email="some1@example.com", name="some1")
|
||||||
chat = ac1.create_chat_by_contact(contact1)
|
chat = ac1.create_chat_by_contact(contact1)
|
||||||
chat.send_text("one messae")
|
msg = chat.send_text("one message")
|
||||||
assert not ac1.delete_contact(contact1)
|
assert not ac1.delete_contact(contact1)
|
||||||
|
assert not msg.filemime
|
||||||
|
|
||||||
|
|
||||||
class TestOfflineChat:
|
class TestOfflineChat:
|
||||||
@@ -106,7 +114,7 @@ class TestOfflineChat:
|
|||||||
def chat1(self, ac1):
|
def chat1(self, ac1):
|
||||||
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||||
chat = ac1.create_chat_by_contact(contact1)
|
chat = ac1.create_chat_by_contact(contact1)
|
||||||
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL, chat.id
|
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL, chat.id
|
||||||
return chat
|
return chat
|
||||||
|
|
||||||
def test_display(self, chat1):
|
def test_display(self, chat1):
|
||||||
@@ -140,6 +148,27 @@ class TestOfflineChat:
|
|||||||
chat.set_name("title2")
|
chat.set_name("title2")
|
||||||
assert chat.get_name() == "title2"
|
assert chat.get_name() == "title2"
|
||||||
|
|
||||||
|
def test_group_chat_creation_with_translation(self, ac1):
|
||||||
|
ac1.set_stock_translation(const.DC_STR_NEWGROUPDRAFT, "xyz %1$s")
|
||||||
|
ac1._evlogger.consume_events()
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
ac1.set_stock_translation(const.DC_STR_NEWGROUPDRAFT, "xyz %2$s")
|
||||||
|
ac1._evlogger.get_matching("DC_EVENT_WARNING")
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
ac1.set_stock_translation(500, "xyz %1$s")
|
||||||
|
ac1._evlogger.get_matching("DC_EVENT_WARNING")
|
||||||
|
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||||
|
contact2 = ac1.create_contact("some2@hello.com", name="some2")
|
||||||
|
chat = ac1.create_group_chat(name="title1")
|
||||||
|
chat.add_contact(contact1)
|
||||||
|
chat.add_contact(contact2)
|
||||||
|
assert chat.get_name() == "title1"
|
||||||
|
assert contact1 in chat.get_contacts()
|
||||||
|
assert contact2 in chat.get_contacts()
|
||||||
|
assert not chat.is_promoted()
|
||||||
|
msg = chat.get_draft()
|
||||||
|
assert msg.text == "xyz title1"
|
||||||
|
|
||||||
@pytest.mark.parametrize("verified", [True, False])
|
@pytest.mark.parametrize("verified", [True, False])
|
||||||
def test_group_chat_qr(self, acfactory, ac1, verified):
|
def test_group_chat_qr(self, acfactory, ac1, verified):
|
||||||
ac2 = acfactory.get_configured_offline_account()
|
ac2 = acfactory.get_configured_offline_account()
|
||||||
@@ -221,7 +250,9 @@ class TestOfflineChat:
|
|||||||
chat1.send_image(path="notexists")
|
chat1.send_image(path="notexists")
|
||||||
fn = data.get_path("d.png")
|
fn = data.get_path("d.png")
|
||||||
lp.sec("sending image")
|
lp.sec("sending image")
|
||||||
|
chat1.account._evlogger.consume_events()
|
||||||
msg = chat1.send_image(fn)
|
msg = chat1.send_image(fn)
|
||||||
|
chat1.account._evlogger.get_matching("DC_EVENT_NEW_BLOB_FILE")
|
||||||
assert msg.is_image()
|
assert msg.is_image()
|
||||||
assert msg
|
assert msg
|
||||||
assert msg.id > 0
|
assert msg.id > 0
|
||||||
@@ -293,10 +324,10 @@ class TestOfflineChat:
|
|||||||
assert contact == ac1.get_self_contact()
|
assert contact == ac1.get_self_contact()
|
||||||
assert not backupdir.listdir()
|
assert not backupdir.listdir()
|
||||||
|
|
||||||
path = ac1.export_to_dir(backupdir.strpath)
|
path = ac1.export_all(backupdir.strpath)
|
||||||
assert os.path.exists(path)
|
assert os.path.exists(path)
|
||||||
ac2 = acfactory.get_unconfigured_account()
|
ac2 = acfactory.get_unconfigured_account()
|
||||||
ac2.import_from_file(path)
|
ac2.import_all(path)
|
||||||
contacts = ac2.get_contacts(query="some1")
|
contacts = ac2.get_contacts(query="some1")
|
||||||
assert len(contacts) == 1
|
assert len(contacts) == 1
|
||||||
contact2 = contacts[0]
|
contact2 = contacts[0]
|
||||||
@@ -334,36 +365,81 @@ class TestOfflineChat:
|
|||||||
|
|
||||||
|
|
||||||
class TestOnlineAccount:
|
class TestOnlineAccount:
|
||||||
def get_chat(self, ac1, ac2):
|
def get_chat(self, ac1, ac2, both_created=False):
|
||||||
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||||
chat = ac1.create_chat_by_contact(c2)
|
chat = ac1.create_chat_by_contact(c2)
|
||||||
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
|
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
|
if both_created:
|
||||||
|
ac2.create_chat_by_contact(ac2.create_contact(email=ac1.get_config("addr")))
|
||||||
return chat
|
return chat
|
||||||
|
|
||||||
def test_one_account_send(self, acfactory):
|
def test_configure_canceled(self, acfactory):
|
||||||
ac1 = acfactory.get_online_configuring_account()
|
ac1 = acfactory.get_online_configuring_account()
|
||||||
c2 = ac1.create_contact(email=ac1.get_config("addr"))
|
wait_configuration_progress(ac1, 200)
|
||||||
|
ac1.stop_ongoing()
|
||||||
|
wait_configuration_progress(ac1, 0, 0)
|
||||||
|
|
||||||
|
def test_export_import_self_keys(self, acfactory, tmpdir):
|
||||||
|
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||||
|
dir = tmpdir.mkdir("exportdir")
|
||||||
|
export_files = ac1.export_self_keys(dir.strpath)
|
||||||
|
assert len(export_files) == 2
|
||||||
|
for x in export_files:
|
||||||
|
assert x.startswith(dir.strpath)
|
||||||
|
ac1._evlogger.consume_events()
|
||||||
|
ac1.import_self_keys(dir.strpath)
|
||||||
|
|
||||||
|
def test_one_account_send_bcc_setting(self, acfactory, lp):
|
||||||
|
ac1 = acfactory.get_online_configuring_account()
|
||||||
|
ac2_config = acfactory.peek_online_config()
|
||||||
|
c2 = ac1.create_contact(email=ac2_config["addr"])
|
||||||
chat = ac1.create_chat_by_contact(c2)
|
chat = ac1.create_chat_by_contact(c2)
|
||||||
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
|
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
wait_successful_IMAP_SMTP_connection(ac1)
|
wait_successful_IMAP_SMTP_connection(ac1)
|
||||||
wait_configuration_progress(ac1, 1000)
|
wait_configuration_progress(ac1, 1000)
|
||||||
|
|
||||||
|
lp.sec("send out message with bcc to ourselves")
|
||||||
msg_out = chat.send_text("message2")
|
msg_out = chat.send_text("message2")
|
||||||
# wait for own account to receive
|
ev = ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||||
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
|
||||||
assert ev[1] == msg_out.id
|
|
||||||
|
|
||||||
def test_two_accounts_send_receive(self, acfactory):
|
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
|
||||||
chat = self.get_chat(ac1, ac2)
|
|
||||||
|
|
||||||
msg_out = chat.send_text("message1")
|
|
||||||
|
|
||||||
# wait for other account to receive
|
|
||||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
|
||||||
assert ev[2] == msg_out.id
|
assert ev[2] == msg_out.id
|
||||||
msg_in = ac2.get_message_by_id(msg_out.id)
|
# wait for send out (BCC)
|
||||||
assert msg_in.text == "message1"
|
assert ac1.get_config("bcc_self") == "1"
|
||||||
|
self_addr = ac1.get_config("addr")
|
||||||
|
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
|
||||||
|
assert self_addr in ev[2]
|
||||||
|
ev = ac1._evlogger.get_matching("DC_EVENT_DELETED_BLOB_FILE")
|
||||||
|
|
||||||
|
ac1._evlogger.consume_events()
|
||||||
|
lp.sec("send out message without bcc")
|
||||||
|
ac1.set_config("bcc_self", "0")
|
||||||
|
msg_out = chat.send_text("message3")
|
||||||
|
assert not msg_out.is_forwarded()
|
||||||
|
ev = ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||||
|
assert ev[2] == msg_out.id
|
||||||
|
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
|
||||||
|
assert self_addr not in ev[2]
|
||||||
|
ev = ac1._evlogger.get_matching("DC_EVENT_DELETED_BLOB_FILE")
|
||||||
|
|
||||||
|
def test_mvbox_sentbox_threads(self, acfactory):
|
||||||
|
ac1 = acfactory.get_online_configuring_account(mvbox=True, sentbox=True)
|
||||||
|
ac2 = acfactory.get_online_configuring_account()
|
||||||
|
wait_configuration_progress(ac2, 1000)
|
||||||
|
wait_configuration_progress(ac1, 1000)
|
||||||
|
chat = self.get_chat(ac1, ac2)
|
||||||
|
chat.send_text("message1")
|
||||||
|
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||||
|
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
|
|
||||||
|
def test_move_works(self, acfactory):
|
||||||
|
ac1 = acfactory.get_online_configuring_account()
|
||||||
|
ac2 = acfactory.get_online_configuring_account(mvbox=True)
|
||||||
|
wait_configuration_progress(ac2, 1000)
|
||||||
|
wait_configuration_progress(ac1, 1000)
|
||||||
|
chat = self.get_chat(ac1, ac2)
|
||||||
|
chat.send_text("message1")
|
||||||
|
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||||
|
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
|
ev = ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||||
|
|
||||||
def test_forward_messages(self, acfactory):
|
def test_forward_messages(self, acfactory):
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||||
@@ -380,6 +456,7 @@ class TestOnlineAccount:
|
|||||||
# check the message arrived in contact-requests/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 chat2.is_deaddrop()
|
assert chat2.is_deaddrop()
|
||||||
assert chat2 == ac2.get_deaddrop_chat()
|
assert chat2 == ac2.get_deaddrop_chat()
|
||||||
chat3 = ac2.create_group_chat("newgroup")
|
chat3 = ac2.create_group_chat("newgroup")
|
||||||
@@ -387,10 +464,36 @@ class TestOnlineAccount:
|
|||||||
ac2.forward_messages([msg_in], chat3)
|
ac2.forward_messages([msg_in], chat3)
|
||||||
assert chat3.is_promoted()
|
assert chat3.is_promoted()
|
||||||
messages = chat3.get_messages()
|
messages = chat3.get_messages()
|
||||||
|
msg = messages[-1]
|
||||||
|
assert msg.is_forwarded()
|
||||||
ac2.delete_messages(messages)
|
ac2.delete_messages(messages)
|
||||||
assert not chat3.get_messages()
|
assert not chat3.get_messages()
|
||||||
|
|
||||||
def test_send_and_receive_message(self, acfactory, lp):
|
def test_forward_own_message(self, acfactory, lp):
|
||||||
|
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||||
|
chat = self.get_chat(ac1, ac2, both_created=True)
|
||||||
|
|
||||||
|
lp.sec("sending message")
|
||||||
|
msg_out = chat.send_text("message2")
|
||||||
|
|
||||||
|
lp.sec("receiving message")
|
||||||
|
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
|
||||||
|
msg_in = ac2.get_message_by_id(ev[2])
|
||||||
|
assert msg_in.text == "message2"
|
||||||
|
assert not msg_in.is_forwarded()
|
||||||
|
|
||||||
|
lp.sec("ac1: creating group chat, and forward own message")
|
||||||
|
group = ac1.create_group_chat("newgroup2")
|
||||||
|
group.add_contact(ac1.create_contact(ac2.get_config("addr")))
|
||||||
|
ac1.forward_messages([msg_out], group)
|
||||||
|
|
||||||
|
# wait for other account to receive
|
||||||
|
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
|
||||||
|
msg_in = ac2.get_message_by_id(ev[2])
|
||||||
|
assert msg_in.text == "message2"
|
||||||
|
assert msg_in.is_forwarded()
|
||||||
|
|
||||||
|
def test_send_and_receive_message_markseen(self, acfactory, lp):
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||||
|
|
||||||
lp.sec("ac1: create chat with ac2")
|
lp.sec("ac1: create chat with ac2")
|
||||||
@@ -409,6 +512,7 @@ class TestOnlineAccount:
|
|||||||
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 == "message1"
|
assert msg_in.text == "message1"
|
||||||
|
assert not msg_in.is_forwarded()
|
||||||
|
|
||||||
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
|
||||||
@@ -430,11 +534,19 @@ class TestOnlineAccount:
|
|||||||
ac2.mark_seen_messages([msg_in])
|
ac2.mark_seen_messages([msg_in])
|
||||||
lp.step("1")
|
lp.step("1")
|
||||||
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_READ")
|
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_READ")
|
||||||
assert ev[1] >= const.DC_CHAT_ID_LAST_SPECIAL
|
assert ev[1] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
assert ev[2] >= const.DC_MSG_ID_LAST_SPECIAL
|
assert ev[2] > const.DC_MSG_ID_LAST_SPECIAL
|
||||||
lp.step("2")
|
lp.step("2")
|
||||||
assert msg_out.is_out_mdn_received()
|
assert msg_out.is_out_mdn_received()
|
||||||
|
|
||||||
|
lp.sec("check that a second call to mark_seen does not create change or smtp job")
|
||||||
|
ac2._evlogger.consume_events()
|
||||||
|
ac2.mark_seen_messages([msg_in])
|
||||||
|
try:
|
||||||
|
ac2._evlogger.get_matching("DC_EVENT_MSG_READ", timeout=0.01)
|
||||||
|
except queue.Empty:
|
||||||
|
pass # mark_seen_messages() has generated events before it returns
|
||||||
|
|
||||||
def test_send_and_receive_will_encrypt_decrypt(self, acfactory, lp):
|
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()
|
||||||
|
|
||||||
@@ -443,6 +555,7 @@ class TestOnlineAccount:
|
|||||||
|
|
||||||
lp.sec("sending text message from ac1 to ac2")
|
lp.sec("sending text message from ac1 to ac2")
|
||||||
msg_out = chat.send_text("message1")
|
msg_out = chat.send_text("message1")
|
||||||
|
assert not msg_out.is_encrypted()
|
||||||
|
|
||||||
lp.sec("wait for ac2 to receive message")
|
lp.sec("wait for ac2 to receive message")
|
||||||
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||||
@@ -460,6 +573,15 @@ class TestOnlineAccount:
|
|||||||
assert ev[2] > msg_out.id
|
assert ev[2] > msg_out.id
|
||||||
msg_back = ac1.get_message_by_id(ev[2])
|
msg_back = ac1.get_message_by_id(ev[2])
|
||||||
assert msg_back.text == "message-back"
|
assert msg_back.text == "message-back"
|
||||||
|
assert msg_back.is_encrypted()
|
||||||
|
|
||||||
|
lp.sec("create group chat with two members, one of which has no encrypt state")
|
||||||
|
chat = ac1.create_group_chat("encryption test")
|
||||||
|
chat.add_contact(ac1.create_contact(ac2.get_config("addr")))
|
||||||
|
chat.add_contact(ac1.create_contact("notexisting@testrun.org"))
|
||||||
|
msg = chat.send_text("test not encrypt")
|
||||||
|
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
|
||||||
|
assert not msg.is_encrypted()
|
||||||
|
|
||||||
def test_saved_mime_on_received_message(self, acfactory, lp):
|
def test_saved_mime_on_received_message(self, acfactory, lp):
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||||
@@ -501,7 +623,7 @@ class TestOnlineAccount:
|
|||||||
assert os.path.exists(msg_in.filename)
|
assert os.path.exists(msg_in.filename)
|
||||||
assert os.stat(msg_in.filename).st_size == os.stat(path).st_size
|
assert os.stat(msg_in.filename).st_size == os.stat(path).st_size
|
||||||
|
|
||||||
def test_import_export_online(self, acfactory, tmpdir):
|
def test_import_export_online_all(self, acfactory, tmpdir):
|
||||||
ac1 = acfactory.get_online_configuring_account()
|
ac1 = acfactory.get_online_configuring_account()
|
||||||
wait_configuration_progress(ac1, 1000)
|
wait_configuration_progress(ac1, 1000)
|
||||||
|
|
||||||
@@ -509,11 +631,11 @@ class TestOnlineAccount:
|
|||||||
chat = ac1.create_chat_by_contact(contact1)
|
chat = ac1.create_chat_by_contact(contact1)
|
||||||
chat.send_text("msg1")
|
chat.send_text("msg1")
|
||||||
backupdir = tmpdir.mkdir("backup")
|
backupdir = tmpdir.mkdir("backup")
|
||||||
path = ac1.export_to_dir(backupdir.strpath)
|
path = ac1.export_all(backupdir.strpath)
|
||||||
assert os.path.exists(path)
|
assert os.path.exists(path)
|
||||||
|
|
||||||
ac2 = acfactory.get_unconfigured_account()
|
ac2 = acfactory.get_unconfigured_account()
|
||||||
ac2.import_from_file(path)
|
ac2.import_all(path)
|
||||||
contacts = ac2.get_contacts(query="some1")
|
contacts = ac2.get_contacts(query="some1")
|
||||||
assert len(contacts) == 1
|
assert len(contacts) == 1
|
||||||
contact2 = contacts[0]
|
contact2 = contacts[0]
|
||||||
@@ -523,7 +645,7 @@ class TestOnlineAccount:
|
|||||||
assert len(messages) == 1
|
assert len(messages) == 1
|
||||||
assert messages[0].text == "msg1"
|
assert messages[0].text == "msg1"
|
||||||
|
|
||||||
def test_ac_setup_message(self, acfactory):
|
def test_ac_setup_message(self, acfactory, lp):
|
||||||
# note that the receiving account needs to be configured and running
|
# note that the receiving account needs to be configured and running
|
||||||
# before ther setup message is send. DC does not read old messages
|
# before ther setup message is send. DC does not read old messages
|
||||||
# as of Jul2019
|
# as of Jul2019
|
||||||
@@ -531,15 +653,18 @@ class TestOnlineAccount:
|
|||||||
ac2 = acfactory.clone_online_account(ac1)
|
ac2 = acfactory.clone_online_account(ac1)
|
||||||
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 and return setupcode")
|
||||||
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
|
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
|
||||||
setup_code = ac1.initiate_key_transfer()
|
setup_code = ac1.initiate_key_transfer()
|
||||||
ac2._evlogger.set_timeout(30)
|
ac2._evlogger.set_timeout(30)
|
||||||
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")
|
||||||
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()
|
||||||
# first try a bad setup code
|
assert msg.get_setupcodebegin() == setup_code[:2]
|
||||||
|
lp.sec("try a bad setup code")
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
msg.continue_key_transfer(str(reversed(setup_code)))
|
msg.continue_key_transfer(str(reversed(setup_code)))
|
||||||
|
lp.sec("try a good setup code")
|
||||||
print("*************** Incoming ASM File at: ", msg.filename)
|
print("*************** Incoming ASM File at: ", msg.filename)
|
||||||
print("*************** Setup Code: ", setup_code)
|
print("*************** Setup Code: ", setup_code)
|
||||||
msg.continue_key_transfer(setup_code)
|
msg.continue_key_transfer(setup_code)
|
||||||
@@ -562,8 +687,43 @@ class TestOnlineAccount:
|
|||||||
lp.sec("ac2: start QR-code based join-group protocol")
|
lp.sec("ac2: start QR-code based join-group protocol")
|
||||||
ch = ac2.qr_join_chat(qr)
|
ch = ac2.qr_join_chat(qr)
|
||||||
assert ch.id >= 10
|
assert ch.id >= 10
|
||||||
|
# check that at least some of the handshake messages are deleted
|
||||||
|
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
||||||
|
ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
||||||
wait_securejoin_inviter_progress(ac1, 1000)
|
wait_securejoin_inviter_progress(ac1, 1000)
|
||||||
|
|
||||||
|
def test_qr_verified_group_and_chatting(self, acfactory, lp):
|
||||||
|
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||||
|
lp.sec("ac1: create verified-group QR, ac2 scans and joins")
|
||||||
|
chat1 = ac1.create_group_chat("hello", verified=True)
|
||||||
|
assert chat1.is_verified()
|
||||||
|
qr = chat1.get_join_qr()
|
||||||
|
lp.sec("ac2: start QR-code based join-group protocol")
|
||||||
|
chat2 = ac2.qr_join_chat(qr)
|
||||||
|
assert chat2.id >= 10
|
||||||
|
wait_securejoin_inviter_progress(ac1, 1000)
|
||||||
|
|
||||||
|
lp.sec("ac2: read member added message")
|
||||||
|
msg = ac2.wait_next_incoming_message()
|
||||||
|
assert msg.is_encrypted()
|
||||||
|
assert "added" in msg.text.lower()
|
||||||
|
|
||||||
|
lp.sec("ac1: send message")
|
||||||
|
msg_out = chat1.send_text("hello")
|
||||||
|
assert msg_out.is_encrypted()
|
||||||
|
|
||||||
|
lp.sec("ac2: read message and check it's verified chat")
|
||||||
|
msg = ac2.wait_next_incoming_message()
|
||||||
|
assert msg.text == "hello"
|
||||||
|
assert msg.chat.is_verified()
|
||||||
|
assert msg.is_encrypted()
|
||||||
|
|
||||||
|
lp.sec("ac2: send message and let ac1 read it")
|
||||||
|
chat2.send_text("world")
|
||||||
|
msg = ac1.wait_next_incoming_message()
|
||||||
|
assert msg.text == "world"
|
||||||
|
assert msg.is_encrypted()
|
||||||
|
|
||||||
def test_set_get_profile_image(self, acfactory, data, lp):
|
def test_set_get_profile_image(self, acfactory, data, lp):
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||||
|
|
||||||
@@ -616,3 +776,32 @@ class TestOnlineAccount:
|
|||||||
chat1b = ac1.create_chat_by_message(ev[2])
|
chat1b = ac1.create_chat_by_message(ev[2])
|
||||||
assert chat1b.get_profile_image() is None
|
assert chat1b.get_profile_image() is None
|
||||||
assert chat.get_profile_image() is None
|
assert chat.get_profile_image() is None
|
||||||
|
|
||||||
|
|
||||||
|
class TestOnlineConfigureFails:
|
||||||
|
def test_invalid_password(self, acfactory):
|
||||||
|
ac1, configdict = acfactory.get_online_config()
|
||||||
|
ac1.configure(addr=configdict["addr"], mail_pw="123")
|
||||||
|
ac1.start_threads()
|
||||||
|
wait_configuration_progress(ac1, 500)
|
||||||
|
ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK")
|
||||||
|
assert "authentication failed" in ev1[2].lower()
|
||||||
|
wait_configuration_progress(ac1, 0, 0)
|
||||||
|
|
||||||
|
def test_invalid_user(self, acfactory):
|
||||||
|
ac1, configdict = acfactory.get_online_config()
|
||||||
|
ac1.configure(addr="x" + configdict["addr"], mail_pw=configdict["mail_pw"])
|
||||||
|
ac1.start_threads()
|
||||||
|
wait_configuration_progress(ac1, 500)
|
||||||
|
ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK")
|
||||||
|
assert "authentication failed" in ev1[2].lower()
|
||||||
|
wait_configuration_progress(ac1, 0, 0)
|
||||||
|
|
||||||
|
def test_invalid_domain(self, acfactory):
|
||||||
|
ac1, configdict = acfactory.get_online_config()
|
||||||
|
ac1.configure(addr=configdict["addr"] + "x", mail_pw=configdict["mail_pw"])
|
||||||
|
ac1.start_threads()
|
||||||
|
wait_configuration_progress(ac1, 500)
|
||||||
|
ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK")
|
||||||
|
assert "could not connect" in ev1[2].lower()
|
||||||
|
wait_configuration_progress(ac1, 0, 0)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from deltachat import const
|
|||||||
from conftest import wait_configuration_progress, wait_msgs_changed
|
from conftest import wait_configuration_progress, wait_msgs_changed
|
||||||
|
|
||||||
|
|
||||||
class TestInCreation:
|
class TestOnlineInCreation:
|
||||||
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()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from deltachat import capi, const, set_context_callback, clear_context_callback
|
from deltachat import capi, cutil, const, set_context_callback, clear_context_callback
|
||||||
from deltachat.capi import ffi
|
from deltachat.capi import ffi
|
||||||
from deltachat.capi import lib
|
from deltachat.capi import lib
|
||||||
from deltachat.account import EventLogger
|
from deltachat.account import EventLogger
|
||||||
@@ -59,6 +59,16 @@ def test_wrong_db(tmpdir):
|
|||||||
assert not lib.dc_open(dc_context, p.strpath.encode("ascii"), ffi.NULL)
|
assert not lib.dc_open(dc_context, p.strpath.encode("ascii"), ffi.NULL)
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_blobdir(tmpdir):
|
||||||
|
# Apparently some client code expects this to be the same as passing NULL.
|
||||||
|
ctx = ffi.gc(
|
||||||
|
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
||||||
|
lib.dc_context_unref,
|
||||||
|
)
|
||||||
|
db_fname = tmpdir.join("hello.db")
|
||||||
|
assert lib.dc_open(ctx, db_fname.strpath.encode("ascii"), b"")
|
||||||
|
|
||||||
|
|
||||||
def test_event_defines():
|
def test_event_defines():
|
||||||
assert const.DC_EVENT_INFO == 100
|
assert const.DC_EVENT_INFO == 100
|
||||||
assert const.DC_CONTACT_ID_SELF
|
assert const.DC_CONTACT_ID_SELF
|
||||||
@@ -83,3 +93,58 @@ def test_markseen_invalid_message_ids(acfactory):
|
|||||||
msg_ids = [9]
|
msg_ids = [9]
|
||||||
lib.dc_markseen_msgs(ac1._dc_context, msg_ids, len(msg_ids))
|
lib.dc_markseen_msgs(ac1._dc_context, msg_ids, len(msg_ids))
|
||||||
ac1._evlogger.ensure_event_not_queued("DC_EVENT_WARNING|DC_EVENT_ERROR")
|
ac1._evlogger.ensure_event_not_queued("DC_EVENT_WARNING|DC_EVENT_ERROR")
|
||||||
|
|
||||||
|
|
||||||
|
def test_provider_info():
|
||||||
|
provider = lib.dc_provider_new_from_email(cutil.as_dc_charpointer("ex@example.com"))
|
||||||
|
assert cutil.from_dc_charpointer(
|
||||||
|
lib.dc_provider_get_overview_page(provider)
|
||||||
|
) == "https://providers.delta.chat/example.com"
|
||||||
|
assert cutil.from_dc_charpointer(lib.dc_provider_get_name(provider)) == "Example"
|
||||||
|
assert cutil.from_dc_charpointer(lib.dc_provider_get_markdown(provider)) == "\n..."
|
||||||
|
assert cutil.from_dc_charpointer(lib.dc_provider_get_status_date(provider)) == "2018-09"
|
||||||
|
assert lib.dc_provider_get_status(provider) == const.DC_PROVIDER_STATUS_PREPARATION
|
||||||
|
|
||||||
|
|
||||||
|
def test_provider_info_none():
|
||||||
|
assert lib.dc_provider_new_from_email(cutil.as_dc_charpointer("email@unexistent.no")) == ffi.NULL
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_info_closed():
|
||||||
|
ctx = ffi.gc(
|
||||||
|
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
||||||
|
lib.dc_context_unref,
|
||||||
|
)
|
||||||
|
info = cutil.from_dc_charpointer(lib.dc_get_info(ctx))
|
||||||
|
assert 'deltachat_core_version' in info
|
||||||
|
assert 'database_dir' not in info
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_info_open(tmpdir):
|
||||||
|
ctx = ffi.gc(
|
||||||
|
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
||||||
|
lib.dc_context_unref,
|
||||||
|
)
|
||||||
|
db_fname = tmpdir.join("test.db")
|
||||||
|
lib.dc_open(ctx, db_fname.strpath.encode("ascii"), ffi.NULL)
|
||||||
|
info = cutil.from_dc_charpointer(lib.dc_get_info(ctx))
|
||||||
|
assert 'deltachat_core_version' in info
|
||||||
|
assert 'database_dir' in info
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_open_closed():
|
||||||
|
ctx = ffi.gc(
|
||||||
|
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
||||||
|
lib.dc_context_unref,
|
||||||
|
)
|
||||||
|
assert lib.dc_is_open(ctx) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_open_actually_open(tmpdir):
|
||||||
|
ctx = ffi.gc(
|
||||||
|
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
||||||
|
lib.dc_context_unref,
|
||||||
|
)
|
||||||
|
db_fname = tmpdir.join("test.db")
|
||||||
|
lib.dc_open(ctx, db_fname.strpath.encode("ascii"), ffi.NULL)
|
||||||
|
assert lib.dc_is_open(ctx) == 1
|
||||||
|
|||||||
27
python/tests/test_provider_info.py
Normal file
27
python/tests/test_provider_info.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from deltachat import const
|
||||||
|
from deltachat import provider
|
||||||
|
|
||||||
|
|
||||||
|
def test_provider_info_from_email():
|
||||||
|
example = provider.Provider.from_email("email@example.com")
|
||||||
|
assert example.overview_page == "https://providers.delta.chat/example.com"
|
||||||
|
assert example.name == "Example"
|
||||||
|
assert example.markdown == "\n..."
|
||||||
|
assert example.status_date == "2018-09"
|
||||||
|
assert example.status == const.DC_PROVIDER_STATUS_PREPARATION
|
||||||
|
|
||||||
|
|
||||||
|
def test_provider_info_from_domain():
|
||||||
|
example = provider.Provider("example.com")
|
||||||
|
assert example.overview_page == "https://providers.delta.chat/example.com"
|
||||||
|
assert example.name == "Example"
|
||||||
|
assert example.markdown == "\n..."
|
||||||
|
assert example.status_date == "2018-09"
|
||||||
|
assert example.status == const.DC_PROVIDER_STATUS_PREPARATION
|
||||||
|
|
||||||
|
|
||||||
|
def test_provider_info_none():
|
||||||
|
with pytest.raises(provider.ProviderNotFoundError):
|
||||||
|
provider.Provider.from_email("email@unexistent.no")
|
||||||
@@ -19,12 +19,12 @@ deps =
|
|||||||
pytest-rerunfailures
|
pytest-rerunfailures
|
||||||
pytest-timeout
|
pytest-timeout
|
||||||
pytest-xdist
|
pytest-xdist
|
||||||
auditwheel
|
|
||||||
pdbpp
|
pdbpp
|
||||||
requests
|
requests
|
||||||
|
|
||||||
[testenv:auditwheels]
|
[testenv:auditwheels]
|
||||||
skipsdist = True
|
skipsdist = True
|
||||||
|
deps = auditwheel
|
||||||
commands =
|
commands =
|
||||||
python tests/auditwheels.py {toxworkdir}/wheelhouse
|
python tests/auditwheels.py {toxworkdir}/wheelhouse
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ commands =
|
|||||||
[testenv:doc]
|
[testenv:doc]
|
||||||
basepython = python3.5
|
basepython = python3.5
|
||||||
deps =
|
deps =
|
||||||
sphinx==2.0.1
|
sphinx==2.2.0
|
||||||
breathe
|
breathe
|
||||||
|
|
||||||
changedir = doc
|
changedir = doc
|
||||||
|
|||||||
201
spec.md
201
spec.md
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
Version 0.19.0
|
Version 0.19.0
|
||||||
|
|
||||||
This document describes how emails can be used to implement typical messenger functions while staying compatible to existing MUAs.
|
This document describes how emails can be used
|
||||||
|
to implement typical messenger functions
|
||||||
|
while staying compatible to existing MUAs.
|
||||||
|
|
||||||
- [Encryption](#encryption)
|
- [Encryption](#encryption)
|
||||||
- [Outgoing messages](#outgoing-messages)
|
- [Outgoing messages](#outgoing-messages)
|
||||||
@@ -20,10 +22,14 @@ This document describes how emails can be used to implement typical messenger fu
|
|||||||
|
|
||||||
# Encryption
|
# Encryption
|
||||||
|
|
||||||
Messages SHOULD be encrypted by the [Autocrypt](https://autocrypt.org/level1.html) standard; `prefer-encrypt=mutual` MAY be set by default.
|
Messages SHOULD be encrypted by the
|
||||||
|
[Autocrypt](https://autocrypt.org/level1.html) standard;
|
||||||
|
`prefer-encrypt=mutual` MAY be set by default.
|
||||||
|
|
||||||
Meta data (at least the subject and all chat-headers) SHOULD be encrypted by the [Memoryhole](https://github.com/autocrypt/memoryhole) standard.
|
Meta data (at least the subject and all chat-headers) SHOULD be encrypted
|
||||||
If Memoryhole is not used, the subject of encrypted messages SHOULD be replaced by the string
|
by the [Memoryhole](https://github.com/autocrypt/memoryhole) standard.
|
||||||
|
If Memoryhole is not used,
|
||||||
|
the subject of encrypted messages SHOULD be replaced by the string
|
||||||
`Chat: Encrypted message` where the part after the colon MAY be localized.
|
`Chat: Encrypted message` where the part after the colon MAY be localized.
|
||||||
|
|
||||||
|
|
||||||
@@ -31,7 +37,8 @@ If Memoryhole is not used, the subject of encrypted messages SHOULD be replaced
|
|||||||
|
|
||||||
Messengers MUST add a `Chat-Version: 1.0` header to outgoing messages.
|
Messengers MUST add a `Chat-Version: 1.0` header to outgoing messages.
|
||||||
For filtering and smart appearance of the messages in normal MUAs,
|
For filtering and smart appearance of the messages in normal MUAs,
|
||||||
the `Subject` header SHOULD start with the characters `Chat:` and SHOULD be an excerpt of the message.
|
the `Subject` header SHOULD start with the characters `Chat:`
|
||||||
|
and SHOULD be an excerpt of the message.
|
||||||
Replies to messages MAY follow the typical `Re:`-format.
|
Replies to messages MAY follow the typical `Re:`-format.
|
||||||
|
|
||||||
The body MAY contain text which MUST have the content type `text/plain`
|
The body MAY contain text which MUST have the content type `text/plain`
|
||||||
@@ -41,8 +48,8 @@ The text MAY be divided into a user-text-part and a footer-part using the
|
|||||||
line `-- ` (minus, minus, space, lineend).
|
line `-- ` (minus, minus, space, lineend).
|
||||||
|
|
||||||
The user-text-part MUST contain only user generated content.
|
The user-text-part MUST contain only user generated content.
|
||||||
User generated content are eg. texts a user has actually typed or pasted or
|
User generated content are eg. texts a user has actually typed
|
||||||
forwarded from another user.
|
or pasted or forwarded from another user.
|
||||||
Full quotes, footers or sth. like that MUST NOT go to the user-text-part.
|
Full quotes, footers or sth. like that MUST NOT go to the user-text-part.
|
||||||
|
|
||||||
From: sender@domain
|
From: sender@domain
|
||||||
@@ -56,14 +63,19 @@ Full quotes, footers or sth. like that MUST NOT go to the user-text-part.
|
|||||||
|
|
||||||
# Incoming messages
|
# Incoming messages
|
||||||
|
|
||||||
The `Chat-Version` header MAY be used to detect if a messages comes from a compatible messenger.
|
The `Chat-Version` header MAY be used
|
||||||
|
to detect if a messages comes from a compatible messenger.
|
||||||
|
|
||||||
The `Subject` header MUST NOT be used to detect compatible messengers, groups or whatever.
|
The `Subject` header MUST NOT be used
|
||||||
|
to detect compatible messengers, groups or whatever.
|
||||||
|
|
||||||
Messenger SHOULD show the `Subject` if the message comes from a normal MUA together with the email-body.
|
Messenger SHOULD show the `Subject`
|
||||||
The email-body SHOULD be converted to plain text, full-quotes and similar regions SHOULD be cut.
|
if the message comes from a normal MUA together with the email-body.
|
||||||
|
The email-body SHOULD be converted
|
||||||
|
to plain text, full-quotes and similar regions SHOULD be cut.
|
||||||
|
|
||||||
Attachments SHOULD be shown where possible. If an attachment cannot be shown, a non-distracting warning SHOULD be printed.
|
Attachments SHOULD be shown where possible.
|
||||||
|
If an attachment cannot be shown, a non-distracting warning SHOULD be printed.
|
||||||
|
|
||||||
|
|
||||||
# Forwarded messages
|
# Forwarded messages
|
||||||
@@ -90,21 +102,26 @@ which SHOULD be anonymized or just a placeholder.
|
|||||||
Hello world!
|
Hello world!
|
||||||
|
|
||||||
Incoming forwarded messages are detected by the header.
|
Incoming forwarded messages are detected by the header.
|
||||||
The messenger SHOULD mark these messages in a way that it becomes obvious
|
The messenger SHOULD mark these messages in a way that
|
||||||
that the message is not created by the sender.
|
it becomes obvious that the message is not created by the sender.
|
||||||
Note that most messengers do not show the original sender with forwarded messages
|
Note that most messengers do not show the original sender of forwarded messages
|
||||||
but MUAs typically expose the sender in the UI.
|
but MUAs typically expose the sender in the UI.
|
||||||
|
|
||||||
|
|
||||||
# Groups
|
# Groups
|
||||||
|
|
||||||
Groups are chats with usually more than one recipient, each defined by an email-address.
|
Groups are chats with usually more than one recipient,
|
||||||
|
each defined by an email-address.
|
||||||
The sender plus the recipients are the group members.
|
The sender plus the recipients are the group members.
|
||||||
|
|
||||||
To allow different groups with the same members, groups are identified by a group-id.
|
To allow different groups with the same members,
|
||||||
The group-id MUST be created only from the characters `0`-`9`, `A`-`Z`, `a`-`z` `_` and `-`.
|
groups are identified by a group-id.
|
||||||
|
The group-id MUST be created only from the characters
|
||||||
|
`0`-`9`, `A`-`Z`, `a`-`z` `_` and `-`
|
||||||
|
and MUST have a length of at least 11 characters.
|
||||||
|
|
||||||
Groups MUST have a group-name. The group-name is any non-zero-length UTF-8 string.
|
Groups MUST have a group-name.
|
||||||
|
The group-name is any non-zero-length UTF-8 string.
|
||||||
|
|
||||||
Groups MAY have a group-image.
|
Groups MAY have a group-image.
|
||||||
|
|
||||||
@@ -113,57 +130,77 @@ Groups MAY have a group-image.
|
|||||||
|
|
||||||
All group members MUST be added to the `From`/`To` headers.
|
All group members MUST be added to the `From`/`To` headers.
|
||||||
The group-id MUST be written to the `Chat-Group-ID` header.
|
The group-id MUST be written to the `Chat-Group-ID` header.
|
||||||
The group-name MUST be written to `Chat-Group-Name` header (the forced presence of this header makes it easier to join a group chat on a second device any time).
|
The group-name MUST be written to `Chat-Group-Name` header
|
||||||
|
(the forced presence of this header makes it easier
|
||||||
|
to join a group chat on a second device any time).
|
||||||
|
|
||||||
The `Subject` header of outgoing group messages SHOULD start with the characters `Chat:` followed by the group-name and a colon followed by an excerpt of the message.
|
The `Subject` header of outgoing group messages
|
||||||
|
SHOULD start with the characters `Chat:`
|
||||||
|
followed by the group-name and a colon followed by an excerpt of the message.
|
||||||
|
|
||||||
To identify the group-id on replies from normal MUAs, the group-id MUST also be added to
|
To identify the group-id on replies from normal MUAs,
|
||||||
the message-id of outgoing messages. The message-id MUST have the
|
the group-id MUST also be added to the message-id of outgoing messages.
|
||||||
format `Gr.<group-id>.<unique data>`.
|
The message-id MUST have the format `Gr.<group-id>.<unique data>`.
|
||||||
|
|
||||||
From: member1@domain
|
From: member1@domain
|
||||||
To: member2@domain, member3@domain
|
To: member2@domain, member3@domain
|
||||||
Chat-Version: 1.0
|
Chat-Version: 1.0
|
||||||
Chat-Group-ID: 1234xyZ
|
Chat-Group-ID: 12345uvwxyZ
|
||||||
Chat-Group-Name: My Group
|
Chat-Group-Name: My Group
|
||||||
Message-ID: Gr.1234xyZ.0001@domain
|
Message-ID: Gr.12345uvwxyZ.0001@domain
|
||||||
Subject: Chat: My Group: Hello group ...
|
Subject: Chat: My Group: Hello group ...
|
||||||
|
|
||||||
Hello group - this group contains three members
|
Hello group - this group contains three members
|
||||||
|
|
||||||
Messengers adding the member list in the form `Name <email-address>` MUST take care only to spread the names authorized by the contacts themselves.
|
Messengers adding the member list in the form `Name <email-address>`
|
||||||
Otherwise, names as _Daddy_ or _Honey_ may be spread (this issue is also true for normal MUAs, however, for more contact- and chat-centralized apps
|
MUST take care only to spread the names authorized by the contacts themselves.
|
||||||
|
Otherwise, names as _Daddy_ or _Honey_ may be spread
|
||||||
|
(this issue is also true for normal MUAs, however,
|
||||||
|
for more contact- and chat-centralized apps
|
||||||
such situations happen more frequently).
|
such situations happen more frequently).
|
||||||
|
|
||||||
|
|
||||||
## Incoming group messages
|
## Incoming group messages
|
||||||
|
|
||||||
The messenger MUST search incoming messages for the group-id in the following headers: `Chat-Group-ID`,
|
The messenger MUST search incoming messages for the group-id
|
||||||
|
in the following headers: `Chat-Group-ID`,
|
||||||
`Message-ID`, `In-Reply-To` and `References` (in this order).
|
`Message-ID`, `In-Reply-To` and `References` (in this order).
|
||||||
|
|
||||||
If the messenger finds a valid and existent group-id, the message SHOULD be assigned to the given group.
|
If the messenger finds a valid and existent group-id,
|
||||||
If the messenger finds a valid but not existent group-id, the messenger MAY create a new group.
|
the message SHOULD be assigned to the given group.
|
||||||
If no group-id is found, the message MAY be assigned to a normal single-user chat with the email-address given in `From`.
|
If the messenger finds a valid but not existent group-id,
|
||||||
|
the messenger MAY create a new group.
|
||||||
|
If no group-id is found,
|
||||||
|
the message MAY be assigned
|
||||||
|
to a normal single-user chat with the email-address given in `From`.
|
||||||
|
|
||||||
|
|
||||||
## Add and remove members
|
## Add and remove members
|
||||||
|
|
||||||
Messenger clients MUST construct the member list from the `From`/`To` headers only on the first group message or if they see a `Chat-Group-Member-Added` or `Chat-Group-Member-Removed` action header.
|
Messenger clients MUST construct the member list
|
||||||
Both headers MUST have the email-address of the added or removed member as the value.
|
from the `From`/`To` headers only on the first group message
|
||||||
Messenger clients MUST NOT construct the member list on other group messages (this is to avoid accidentally altered To-lists in normal MUAs; the user
|
or if they see a `Chat-Group-Member-Added`
|
||||||
does not expect adding a user to a _message_ will also add him to the _group_ "forever").
|
or `Chat-Group-Member-Removed` action header.
|
||||||
|
Both headers MUST have the email-address
|
||||||
|
of the added or removed member as the value.
|
||||||
|
Messenger clients MUST NOT construct the member list
|
||||||
|
on other group messages
|
||||||
|
(this is to avoid accidentally altered To-lists in normal MUAs;
|
||||||
|
the user does not expect adding a user to a _message_
|
||||||
|
will also add him to the _group_ "forever").
|
||||||
|
|
||||||
The messenger SHOULD send an explicit mail for each added or removed member.
|
The messenger SHOULD send an explicit mail for each added or removed member.
|
||||||
The body of the message SHOULD contain a localized description about what happened
|
The body of the message SHOULD contain
|
||||||
|
a localized description about what happened
|
||||||
and the message SHOULD appear as a message or action from the sender.
|
and the message SHOULD appear as a message or action from the sender.
|
||||||
|
|
||||||
From: member1@domain
|
From: member1@domain
|
||||||
To: member2@domain, member3@domain, member4@domain
|
To: member2@domain, member3@domain, member4@domain
|
||||||
Chat-Version: 1.0
|
Chat-Version: 1.0
|
||||||
Chat-Group-ID: 1234xyZ
|
Chat-Group-ID: 12345uvwxyZ
|
||||||
Chat-Group-Name: My Group
|
Chat-Group-Name: My Group
|
||||||
Chat-Group-Member-Added: member4@domain
|
Chat-Group-Member-Added: member4@domain
|
||||||
Message-ID: Gr.1234xyZ.0002@domain
|
Message-ID: Gr.12345uvwxyZ.0002@domain
|
||||||
Subject: Chat: My Group: Hello, ...
|
Subject: Chat: My Group: Hello, ...
|
||||||
|
|
||||||
Hello, I've added member4@domain to our group. Now we have 4 members.
|
Hello, I've added member4@domain to our group. Now we have 4 members.
|
||||||
@@ -173,10 +210,10 @@ To remove a member:
|
|||||||
From: member1@domain
|
From: member1@domain
|
||||||
To: member2@domain, member3@domain
|
To: member2@domain, member3@domain
|
||||||
Chat-Version: 1.0
|
Chat-Version: 1.0
|
||||||
Chat-Group-ID: 1234xyZ
|
Chat-Group-ID: 12345uvwxyZ
|
||||||
Chat-Group-Name: My Group
|
Chat-Group-Name: My Group
|
||||||
Chat-Group-Member-Removed: member4@domain
|
Chat-Group-Member-Removed: member4@domain
|
||||||
Message-ID: Gr.1234xyZ.0003@domain
|
Message-ID: Gr.12345uvwxyZ.0003@domain
|
||||||
Subject: Chat: My Group: Hello, ...
|
Subject: Chat: My Group: Hello, ...
|
||||||
|
|
||||||
Hello, I've removed member4@domain from our group. Now we have 3 members.
|
Hello, I've removed member4@domain from our group. Now we have 3 members.
|
||||||
@@ -190,16 +227,17 @@ with the value set to the old group name to all group members.
|
|||||||
The new group name goes to the header `Chat-Group-Name`.
|
The new group name goes to the header `Chat-Group-Name`.
|
||||||
|
|
||||||
The messenger SHOULD send an explicit mail for each name change.
|
The messenger SHOULD send an explicit mail for each name change.
|
||||||
The body of the message SHOULD contain a localized description about what happened
|
The body of the message SHOULD contain
|
||||||
|
a localized description about what happened
|
||||||
and the message SHOULD appear as a message or action from the sender.
|
and the message SHOULD appear as a message or action from the sender.
|
||||||
|
|
||||||
From: member1@domain
|
From: member1@domain
|
||||||
To: member2@domain, member3@domain
|
To: member2@domain, member3@domain
|
||||||
Chat-Version: 1.0
|
Chat-Version: 1.0
|
||||||
Chat-Group-ID: 1234xyZ
|
Chat-Group-ID: 12345uvwxyZ
|
||||||
Chat-Group-Name: Our Group
|
Chat-Group-Name: Our Group
|
||||||
Chat-Group-Name-Changed: My Group
|
Chat-Group-Name-Changed: My Group
|
||||||
Message-ID: Gr.1234xyZ.0004@domain
|
Message-ID: Gr.12345uvwxyZ.0004@domain
|
||||||
Subject: Chat: Our Group: Hello, ...
|
Subject: Chat: Our Group: Hello, ...
|
||||||
|
|
||||||
Hello, I've changed the group name from "My Group" to "Our Group".
|
Hello, I've changed the group name from "My Group" to "Our Group".
|
||||||
@@ -208,23 +246,27 @@ and the message SHOULD appear as a message or action from the sender.
|
|||||||
## Set group image
|
## Set group image
|
||||||
|
|
||||||
A group MAY have a group-image.
|
A group MAY have a group-image.
|
||||||
To change or set the group-image, the messenger MUST attach an image file to a message and MUST add the header `Chat-Group-Image` with the
|
To change or set the group-image,
|
||||||
value set to the image name.
|
the messenger MUST attach an image file to a message
|
||||||
|
and MUST add the header `Chat-Group-Image`
|
||||||
|
with the value set to the image name.
|
||||||
|
|
||||||
To remove the group-image, the messenger MUST add the header `Chat-Group-Image: 0`.
|
To remove the group-image,
|
||||||
|
the messenger MUST add the header `Chat-Group-Image: 0`.
|
||||||
|
|
||||||
The messenger SHOULD send an explicit mail for each group image change.
|
The messenger SHOULD send an explicit mail for each group image change.
|
||||||
The body of the message SHOULD contain a localized description about what happened
|
The body of the message SHOULD contain
|
||||||
|
a localized description about what happened
|
||||||
and the message SHOULD appear as a message or action from the sender.
|
and the message SHOULD appear as a message or action from the sender.
|
||||||
|
|
||||||
|
|
||||||
From: member1@domain
|
From: member1@domain
|
||||||
To: member2@domain, member3@domain
|
To: member2@domain, member3@domain
|
||||||
Chat-Version: 1.0
|
Chat-Version: 1.0
|
||||||
Chat-Group-ID: 1234xyZ
|
Chat-Group-ID: 12345uvwxyZ
|
||||||
Chat-Group-Name: Our Group
|
Chat-Group-Name: Our Group
|
||||||
Chat-Group-Image: image.jpg
|
Chat-Group-Image: image.jpg
|
||||||
Message-ID: Gr.1234xyZ.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=="
|
||||||
|
|
||||||
@@ -239,20 +281,29 @@ and the message SHOULD appear as a message or action from the sender.
|
|||||||
/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBw ...
|
/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBw ...
|
||||||
--==break==--
|
--==break==--
|
||||||
|
|
||||||
The image format SHOULD be image/jpeg or image/png. To save data, it is RECOMMENDED to add a `Chat-Group-Image` only on image changes.
|
The image format SHOULD be image/jpeg or image/png.
|
||||||
|
To save data, it is RECOMMENDED
|
||||||
|
to add a `Chat-Group-Image` only on image changes.
|
||||||
|
|
||||||
|
|
||||||
# Set profile image
|
# Set profile image
|
||||||
|
|
||||||
A user MAY have a profile-image that MAY be spread to his contacts.
|
A user MAY have a profile-image that MAY be spread to his contacts.
|
||||||
To change or set the profile-image, the messenger MUST attach an image file to a message and MUST add the header `Chat-Profile-Image` with the
|
To change or set the profile-image,
|
||||||
value set to the image name.
|
the messenger MUST attach an image file to a message
|
||||||
|
and MUST add the header `Chat-Profile-Image`
|
||||||
|
with the value set to the image name.
|
||||||
|
|
||||||
To remove the profile-image, the messenger MUST add the header `Chat-Profile-Image: 0`.
|
To remove the profile-image,
|
||||||
|
the messenger MUST add the header `Chat-Profile-Image: 0`.
|
||||||
|
|
||||||
To spread the image, the messenger MAY send the profile image together with the next mail to a given contact
|
To spread the image,
|
||||||
(to do this only once, the messenger has to keep a `profile_image_update_state` somewhere).
|
the messenger MAY send the profile image
|
||||||
Alternatively, the messenger MAY send an explicit mail for each profile-image change to all contacts using a compatible messenger.
|
together with the next mail to a given contact
|
||||||
|
(to do this only once,
|
||||||
|
the messenger has to keep a `profile_image_update_state` somewhere).
|
||||||
|
Alternatively, the messenger MAY send an explicit mail
|
||||||
|
for each profile-image change to all contacts using a compatible messenger.
|
||||||
The messenger SHOULD NOT send an explicit mail to normal MUAs.
|
The messenger SHOULD NOT send an explicit mail to normal MUAs.
|
||||||
|
|
||||||
From: sender@domain
|
From: sender@domain
|
||||||
@@ -273,18 +324,25 @@ The messenger SHOULD NOT send an explicit mail to normal MUAs.
|
|||||||
AKCgkJi3j4l5kjoldfUAKCgkJi3j4lldfHjgWICwgIEBQYFBA ...
|
AKCgkJi3j4l5kjoldfUAKCgkJi3j4lldfHjgWICwgIEBQYFBA ...
|
||||||
--==break==--
|
--==break==--
|
||||||
|
|
||||||
The image format SHOULD be image/jpeg or image/png. Note that `Chat-Profile-Image` may appear together with all other headers, eg. there may be a
|
The image format SHOULD be image/jpeg or image/png.
|
||||||
`Chat-Profile-Image` and a `Chat-Group-Image` header in the same message. To save data, it is RECOMMENDED to add a `Chat-Profile-Image` header only on image changes.
|
Note that `Chat-Profile-Image` may appear together with all other headers,
|
||||||
|
eg. there may be a `Chat-Profile-Image` and a `Chat-Group-Image` header
|
||||||
|
in the same message.
|
||||||
|
To save data, it is RECOMMENDED to add a `Chat-Profile-Image` header
|
||||||
|
only on image changes.
|
||||||
|
|
||||||
|
|
||||||
# Miscellaneous
|
# Miscellaneous
|
||||||
|
|
||||||
Messengers SHOULD use the header `Chat-Predecessor` instead of `In-Reply-To` as
|
Messengers SHOULD use the header `Chat-Predecessor`
|
||||||
the latter one results in infinite threads on typical MUAs.
|
instead of `In-Reply-To` as the latter one results
|
||||||
|
in infinite threads on typical MUAs.
|
||||||
|
|
||||||
Messengers SHOULD add a `Chat-Voice-message: 1` header if an attached audio file is a voice message.
|
Messengers SHOULD add a `Chat-Voice-message: 1` header
|
||||||
|
if an attached audio file is a voice message.
|
||||||
|
|
||||||
Messengers MAY add a `Chat-Duration` header to specify the duration of attached audio or video files.
|
Messengers MAY add a `Chat-Duration` header
|
||||||
|
to specify the duration of attached audio or video files.
|
||||||
The value MUST be the duration in milliseconds.
|
The value MUST be the duration in milliseconds.
|
||||||
This allows the receiver to show the time without knowing the file format.
|
This allows the receiver to show the time without knowing the file format.
|
||||||
|
|
||||||
@@ -292,16 +350,23 @@ This allows the receiver to show the time without knowing the file format.
|
|||||||
Chat-Voice-Message: 1
|
Chat-Voice-Message: 1
|
||||||
Chat-Duration: 10000
|
Chat-Duration: 10000
|
||||||
|
|
||||||
Messengers MAY send and receive Message Disposition Notifications (MDNs, [RFC 8098](https://tools.ietf.org/html/rfc8098), [RFC 3503](https://tools.ietf.org/html/rfc3503))
|
Messengers MAY send and receive Message Disposition Notifications
|
||||||
using the `Chat-Disposition-Notification-To` header instead of the `Disposition-Notification-To` (which unfortunately forces many other MUAs to send weird mails not following any
|
(MDNs, [RFC 8098](https://tools.ietf.org/html/rfc8098),
|
||||||
standard).
|
[RFC 3503](https://tools.ietf.org/html/rfc3503))
|
||||||
|
using the `Chat-Disposition-Notification-To` header
|
||||||
|
instead of the `Disposition-Notification-To`
|
||||||
|
(which unfortunately forces many other MUAs
|
||||||
|
to send weird mails not following any standard).
|
||||||
|
|
||||||
|
|
||||||
## Sync messages
|
## Sync messages
|
||||||
|
|
||||||
If some action is required by a message header, the action should only be performed if the _effective date_ is newer than the date the last action was performed.
|
If some action is required by a message header,
|
||||||
|
the action should only be performed if the _effective date_ is newer
|
||||||
|
than the date the last action was performed.
|
||||||
|
|
||||||
We define the effective date of a message as the sending time of the message as indicated by its Date header,
|
We define the effective date of a message
|
||||||
|
as the sending time of the message as indicated by its Date header,
|
||||||
or the time of first receipt if that date is in the future or unavailable.
|
or the time of first receipt if that date is in the future or unavailable.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::ffi::CStr;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{fmt, str};
|
use std::{fmt, str};
|
||||||
|
|
||||||
use mmime::mailimf_types::*;
|
use mmime::mailimf::types::*;
|
||||||
|
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::contact::*;
|
use crate::contact::*;
|
||||||
@@ -79,27 +79,21 @@ impl Aheader {
|
|||||||
let optional_field = unsafe { (*field).fld_data.fld_optional_field };
|
let optional_field = unsafe { (*field).fld_data.fld_optional_field };
|
||||||
if !optional_field.is_null()
|
if !optional_field.is_null()
|
||||||
&& unsafe { !(*optional_field).fld_name.is_null() }
|
&& unsafe { !(*optional_field).fld_name.is_null() }
|
||||||
&& unsafe { CStr::from_ptr((*optional_field).fld_name).to_str().unwrap() }
|
&& unsafe { CStr::from_ptr((*optional_field).fld_name).to_string_lossy() }
|
||||||
== "Autocrypt"
|
== "Autocrypt"
|
||||||
{
|
{
|
||||||
let value = unsafe {
|
let value =
|
||||||
CStr::from_ptr((*optional_field).fld_value)
|
unsafe { CStr::from_ptr((*optional_field).fld_value).to_string_lossy() };
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
match Self::from_str(value) {
|
if let Ok(test) = Self::from_str(&value) {
|
||||||
Ok(test) => {
|
if addr_cmp(&test.addr, wanted_from) {
|
||||||
if addr_cmp(&test.addr, wanted_from) {
|
if fine_header.is_none() {
|
||||||
if fine_header.is_none() {
|
fine_header = Some(test);
|
||||||
fine_header = Some(test);
|
} else {
|
||||||
} else {
|
// TODO: figure out what kind of error case this is
|
||||||
// TODO: figure out what kind of error case this is
|
return None;
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,9 +125,9 @@ impl str::FromStr for Aheader {
|
|||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let mut attributes: BTreeMap<String, String> = s
|
let mut attributes: BTreeMap<String, String> = s
|
||||||
.split(";")
|
.split(';')
|
||||||
.filter_map(|a| {
|
.filter_map(|a| {
|
||||||
let attribute: Vec<&str> = a.trim().splitn(2, "=").collect();
|
let attribute: Vec<&str> = a.trim().splitn(2, '=').collect();
|
||||||
if attribute.len() < 2 {
|
if attribute.len() < 2 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -178,7 +172,7 @@ impl str::FromStr for Aheader {
|
|||||||
|
|
||||||
// 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
|
||||||
if attributes.keys().find(|k| !k.starts_with("_")).is_some() {
|
if attributes.keys().any(|k| !k.starts_with('_')) {
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
621
src/chat.rs
621
src/chat.rs
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ 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::*;
|
use crate::message::Message;
|
||||||
use crate::stock::StockMessage;
|
use crate::stock::StockMessage;
|
||||||
|
|
||||||
/// An object representing a single chatlist in memory.
|
/// An object representing a single chatlist in memory.
|
||||||
@@ -182,7 +182,7 @@ impl Chatlist {
|
|||||||
if 0 == listflags & DC_GCL_NO_SPECIALS {
|
if 0 == listflags & DC_GCL_NO_SPECIALS {
|
||||||
let last_deaddrop_fresh_msg_id = get_last_deaddrop_fresh_msg(context);
|
let last_deaddrop_fresh_msg_id = get_last_deaddrop_fresh_msg(context);
|
||||||
if last_deaddrop_fresh_msg_id > 0 {
|
if last_deaddrop_fresh_msg_id > 0 {
|
||||||
ids.push((1, last_deaddrop_fresh_msg_id));
|
ids.insert(0, (DC_CHAT_ID_DEADDROP, last_deaddrop_fresh_msg_id));
|
||||||
}
|
}
|
||||||
add_archived_link_item = 1;
|
add_archived_link_item = 1;
|
||||||
}
|
}
|
||||||
@@ -258,21 +258,19 @@ impl Chatlist {
|
|||||||
let chat_loaded: Chat;
|
let chat_loaded: Chat;
|
||||||
let chat = if let Some(chat) = chat {
|
let chat = if let Some(chat) = chat {
|
||||||
chat
|
chat
|
||||||
|
} else if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0) {
|
||||||
|
chat_loaded = chat;
|
||||||
|
&chat_loaded
|
||||||
} else {
|
} else {
|
||||||
if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0) {
|
return ret;
|
||||||
chat_loaded = chat;
|
|
||||||
&chat_loaded
|
|
||||||
} else {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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 0 != lastmsg_id {
|
let lastmsg = if 0 != lastmsg_id {
|
||||||
if let Ok(lastmsg) = dc_msg_load_from_db(context, lastmsg_id) {
|
if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
|
||||||
if lastmsg.from_id != 1 as libc::c_uint
|
if lastmsg.from_id != 1
|
||||||
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
|
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
|
||||||
{
|
{
|
||||||
lastcontact = Contact::load_from_db(context, lastmsg.from_id).ok();
|
lastcontact = Contact::load_from_db(context, lastmsg.from_id).ok();
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ pub enum Config {
|
|||||||
MailUser,
|
MailUser,
|
||||||
MailPw,
|
MailPw,
|
||||||
MailPort,
|
MailPort,
|
||||||
|
ImapCertificateChecks,
|
||||||
SendServer,
|
SendServer,
|
||||||
SendUser,
|
SendUser,
|
||||||
SendPw,
|
SendPw,
|
||||||
SendPort,
|
SendPort,
|
||||||
|
SmtpCertificateChecks,
|
||||||
ServerFlags,
|
ServerFlags,
|
||||||
#[strum(props(default = "INBOX"))]
|
#[strum(props(default = "INBOX"))]
|
||||||
ImapFolder,
|
ImapFolder,
|
||||||
@@ -30,6 +32,8 @@ pub enum Config {
|
|||||||
Selfstatus,
|
Selfstatus,
|
||||||
Selfavatar,
|
Selfavatar,
|
||||||
#[strum(props(default = "1"))]
|
#[strum(props(default = "1"))]
|
||||||
|
BccSelf,
|
||||||
|
#[strum(props(default = "1"))]
|
||||||
E2eeEnabled,
|
E2eeEnabled,
|
||||||
#[strum(props(default = "1"))]
|
#[strum(props(default = "1"))]
|
||||||
MdnsEnabled,
|
MdnsEnabled,
|
||||||
@@ -50,10 +54,12 @@ pub enum Config {
|
|||||||
ConfiguredMailPw,
|
ConfiguredMailPw,
|
||||||
ConfiguredMailPort,
|
ConfiguredMailPort,
|
||||||
ConfiguredMailSecurity,
|
ConfiguredMailSecurity,
|
||||||
|
ConfiguredImapCertificateChecks,
|
||||||
ConfiguredSendServer,
|
ConfiguredSendServer,
|
||||||
ConfiguredSendUser,
|
ConfiguredSendUser,
|
||||||
ConfiguredSendPw,
|
ConfiguredSendPw,
|
||||||
ConfiguredSendPort,
|
ConfiguredSendPort,
|
||||||
|
ConfiguredSmtpCertificateChecks,
|
||||||
ConfiguredServerFlags,
|
ConfiguredServerFlags,
|
||||||
ConfiguredSendSecurity,
|
ConfiguredSendSecurity,
|
||||||
ConfiguredE2EEEnabled,
|
ConfiguredE2EEEnabled,
|
||||||
@@ -72,13 +78,13 @@ impl Context {
|
|||||||
pub fn get_config(&self, key: Config) -> Option<String> {
|
pub fn get_config(&self, key: Config) -> Option<String> {
|
||||||
let value = match key {
|
let value = match key {
|
||||||
Config::Selfavatar => {
|
Config::Selfavatar => {
|
||||||
let rel_path = self.sql.get_config(self, key);
|
let rel_path = self.sql.get_raw_config(self, key);
|
||||||
rel_path.map(|p| dc_get_abs_path(self, &p).to_str().unwrap().to_string())
|
rel_path.map(|p| dc_get_abs_path(self, &p).to_string_lossy().into_owned())
|
||||||
}
|
}
|
||||||
Config::SysVersion => Some((&*DC_VERSION_STR).clone()),
|
Config::SysVersion => Some((&*DC_VERSION_STR).clone()),
|
||||||
Config::SysMsgsizeMaxRecommended => Some(format!("{}", 24 * 1024 * 1024 / 4 * 3)),
|
Config::SysMsgsizeMaxRecommended => Some(format!("{}", 24 * 1024 * 1024 / 4 * 3)),
|
||||||
Config::SysConfigKeys => Some(get_config_keys_string()),
|
Config::SysConfigKeys => Some(get_config_keys_string()),
|
||||||
_ => self.sql.get_config(self, key),
|
_ => self.sql.get_raw_config(self, key),
|
||||||
};
|
};
|
||||||
|
|
||||||
if value.is_some() {
|
if value.is_some() {
|
||||||
@@ -92,6 +98,16 @@ impl Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_config_int(&self, key: Config) -> i32 {
|
||||||
|
self.get_config(key)
|
||||||
|
.and_then(|s| s.parse().ok())
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_config_bool(&self, key: Config) -> bool {
|
||||||
|
self.get_config_int(key) != 0
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the given config key.
|
/// 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>) -> Result<(), Error> {
|
pub fn set_config(&self, key: Config, value: Option<&str>) -> Result<(), Error> {
|
||||||
@@ -99,20 +115,20 @@ impl Context {
|
|||||||
Config::Selfavatar if value.is_some() => {
|
Config::Selfavatar if value.is_some() => {
|
||||||
let rel_path = std::fs::canonicalize(value.unwrap())?;
|
let rel_path = std::fs::canonicalize(value.unwrap())?;
|
||||||
self.sql
|
self.sql
|
||||||
.set_config(self, key, Some(&rel_path.to_string_lossy()))
|
.set_raw_config(self, key, Some(&rel_path.to_string_lossy()))
|
||||||
}
|
}
|
||||||
Config::InboxWatch => {
|
Config::InboxWatch => {
|
||||||
let ret = self.sql.set_config(self, key, value);
|
let ret = self.sql.set_raw_config(self, key, value);
|
||||||
interrupt_imap_idle(self);
|
interrupt_imap_idle(self);
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
Config::SentboxWatch => {
|
Config::SentboxWatch => {
|
||||||
let ret = self.sql.set_config(self, key, value);
|
let ret = self.sql.set_raw_config(self, key, value);
|
||||||
interrupt_sentbox_idle(self);
|
interrupt_sentbox_idle(self);
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
Config::MvboxWatch => {
|
Config::MvboxWatch => {
|
||||||
let ret = self.sql.set_config(self, key, value);
|
let ret = self.sql.set_raw_config(self, key, value);
|
||||||
interrupt_mvbox_idle(self);
|
interrupt_mvbox_idle(self);
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
@@ -124,9 +140,9 @@ impl Context {
|
|||||||
value
|
value
|
||||||
};
|
};
|
||||||
|
|
||||||
self.sql.set_config(self, key, val)
|
self.sql.set_raw_config(self, key, val)
|
||||||
}
|
}
|
||||||
_ => self.sql.set_config(self, key, value),
|
_ => self.sql.set_raw_config(self, key, value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,61 +3,64 @@ use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
|||||||
|
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::dc_tools::*;
|
use crate::error::Error;
|
||||||
use crate::login_param::LoginParam;
|
use crate::login_param::LoginParam;
|
||||||
use crate::x::*;
|
|
||||||
|
|
||||||
use super::read_autoconf_file;
|
use super::read_autoconf_file;
|
||||||
/* ******************************************************************************
|
/* ******************************************************************************
|
||||||
* Thunderbird's Autoconfigure
|
* Thunderbird's Autoconfigure
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
/* documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
|
/* documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
|
||||||
#[repr(C)]
|
struct MozAutoconfigure<'a> {
|
||||||
struct moz_autoconfigure_t<'a> {
|
pub in_emailaddr: &'a str,
|
||||||
pub in_0: &'a LoginParam,
|
|
||||||
pub in_emaildomain: &'a str,
|
pub in_emaildomain: &'a str,
|
||||||
pub in_emaillocalpart: &'a str,
|
pub in_emaillocalpart: &'a str,
|
||||||
pub out: LoginParam,
|
pub out: LoginParam,
|
||||||
pub out_imap_set: libc::c_int,
|
pub out_imap_set: bool,
|
||||||
pub out_smtp_set: libc::c_int,
|
pub out_smtp_set: bool,
|
||||||
pub tag_server: libc::c_int,
|
pub tag_server: MozServer,
|
||||||
pub tag_config: libc::c_int,
|
pub tag_config: MozConfigTag,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn moz_autoconfigure(
|
#[derive(PartialEq)]
|
||||||
context: &Context,
|
enum MozServer {
|
||||||
url: &str,
|
Undefined,
|
||||||
param_in: &LoginParam,
|
Imap,
|
||||||
) -> Option<LoginParam> {
|
Smtp,
|
||||||
let xml_raw = read_autoconf_file(context, url);
|
}
|
||||||
if xml_raw.is_null() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split address into local part and domain part.
|
enum MozConfigTag {
|
||||||
let p = param_in.addr.find("@");
|
Undefined,
|
||||||
if p.is_none() {
|
Hostname,
|
||||||
free(xml_raw as *mut libc::c_void);
|
Port,
|
||||||
return None;
|
Sockettype,
|
||||||
}
|
Username,
|
||||||
let (in_emaillocalpart, in_emaildomain) = param_in.addr.split_at(p.unwrap());
|
}
|
||||||
let in_emaildomain = &in_emaildomain[1..];
|
|
||||||
|
|
||||||
let mut reader = quick_xml::Reader::from_str(as_str(xml_raw));
|
pub fn moz_parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Error> {
|
||||||
|
let mut reader = quick_xml::Reader::from_str(xml_raw);
|
||||||
reader.trim_text(true);
|
reader.trim_text(true);
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
// Split address into local part and domain part.
|
||||||
|
let p = match in_emailaddr.find('@') {
|
||||||
|
Some(i) => i,
|
||||||
|
None => bail!("Email address {} does not contain @", in_emailaddr),
|
||||||
|
};
|
||||||
|
let (in_emaillocalpart, in_emaildomain) = in_emailaddr.split_at(p);
|
||||||
|
let in_emaildomain = &in_emaildomain[1..];
|
||||||
|
|
||||||
let mut moz_ac = moz_autoconfigure_t {
|
let mut moz_ac = MozAutoconfigure {
|
||||||
in_0: param_in,
|
in_emailaddr,
|
||||||
in_emaildomain,
|
in_emaildomain,
|
||||||
in_emaillocalpart,
|
in_emaillocalpart,
|
||||||
out: LoginParam::new(),
|
out: LoginParam::new(),
|
||||||
out_imap_set: 0,
|
out_imap_set: false,
|
||||||
out_smtp_set: 0,
|
out_smtp_set: false,
|
||||||
tag_server: 0,
|
tag_server: MozServer::Undefined,
|
||||||
tag_config: 0,
|
tag_config: MozConfigTag::Undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut buf = Vec::new();
|
||||||
loop {
|
loop {
|
||||||
match reader.read_event(&mut buf) {
|
match reader.read_event(&mut buf) {
|
||||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||||
@@ -68,8 +71,7 @@ pub unsafe fn moz_autoconfigure(
|
|||||||
moz_autoconfigure_text_cb(e, &mut moz_ac, &reader)
|
moz_autoconfigure_text_cb(e, &mut moz_ac, &reader)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(
|
bail!(
|
||||||
context,
|
|
||||||
"Configure xml: Error at position {}: {:?}",
|
"Configure xml: Error at position {}: {:?}",
|
||||||
reader.buffer_position(),
|
reader.buffer_position(),
|
||||||
e
|
e
|
||||||
@@ -87,23 +89,36 @@ pub unsafe fn moz_autoconfigure(
|
|||||||
|| moz_ac.out.send_port == 0
|
|| moz_ac.out.send_port == 0
|
||||||
{
|
{
|
||||||
let r = moz_ac.out.to_string();
|
let r = moz_ac.out.to_string();
|
||||||
warn!(context, "Bad or incomplete autoconfig: {}", r,);
|
bail!("Bad or incomplete autoconfig: {}", r,);
|
||||||
free(xml_raw as *mut libc::c_void);
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
free(xml_raw as *mut libc::c_void);
|
Ok(moz_ac.out)
|
||||||
Some(moz_ac.out)
|
}
|
||||||
|
|
||||||
|
pub fn moz_autoconfigure(
|
||||||
|
context: &Context,
|
||||||
|
url: &str,
|
||||||
|
param_in: &LoginParam,
|
||||||
|
) -> Option<LoginParam> {
|
||||||
|
let xml_raw = read_autoconf_file(context, url)?;
|
||||||
|
|
||||||
|
match moz_parse_xml(¶m_in.addr, &xml_raw) {
|
||||||
|
Err(err) => {
|
||||||
|
warn!(context, "{}", err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Ok(lp) => Some(lp),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
||||||
event: &BytesText,
|
event: &BytesText,
|
||||||
moz_ac: &mut moz_autoconfigure_t,
|
moz_ac: &mut MozAutoconfigure,
|
||||||
reader: &quick_xml::Reader<B>,
|
reader: &quick_xml::Reader<B>,
|
||||||
) {
|
) {
|
||||||
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
||||||
|
|
||||||
let addr = &moz_ac.in_0.addr;
|
let addr = moz_ac.in_emailaddr;
|
||||||
let email_local = moz_ac.in_emaillocalpart;
|
let email_local = moz_ac.in_emaillocalpart;
|
||||||
let email_domain = moz_ac.in_emaildomain;
|
let email_domain = moz_ac.in_emaildomain;
|
||||||
|
|
||||||
@@ -113,12 +128,12 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
|||||||
.replace("%EMAILLOCALPART%", email_local)
|
.replace("%EMAILLOCALPART%", email_local)
|
||||||
.replace("%EMAILDOMAIN%", email_domain);
|
.replace("%EMAILDOMAIN%", email_domain);
|
||||||
|
|
||||||
if moz_ac.tag_server == 1 {
|
match moz_ac.tag_server {
|
||||||
match moz_ac.tag_config {
|
MozServer::Imap => match moz_ac.tag_config {
|
||||||
10 => moz_ac.out.mail_server = val,
|
MozConfigTag::Hostname => moz_ac.out.mail_server = val,
|
||||||
11 => moz_ac.out.mail_port = val.parse().unwrap_or_default(),
|
MozConfigTag::Port => moz_ac.out.mail_port = val.parse().unwrap_or_default(),
|
||||||
12 => moz_ac.out.mail_user = val,
|
MozConfigTag::Username => moz_ac.out.mail_user = val,
|
||||||
13 => {
|
MozConfigTag::Sockettype => {
|
||||||
let val_lower = val.to_lowercase();
|
let val_lower = val.to_lowercase();
|
||||||
if val_lower == "ssl" {
|
if val_lower == "ssl" {
|
||||||
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
|
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
|
||||||
@@ -131,13 +146,12 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
},
|
||||||
} else if moz_ac.tag_server == 2 {
|
MozServer::Smtp => match moz_ac.tag_config {
|
||||||
match moz_ac.tag_config {
|
MozConfigTag::Hostname => moz_ac.out.send_server = val,
|
||||||
10 => moz_ac.out.send_server = val,
|
MozConfigTag::Port => moz_ac.out.send_port = val.parse().unwrap_or_default(),
|
||||||
11 => moz_ac.out.send_port = val.parse().unwrap_or_default(),
|
MozConfigTag::Username => moz_ac.out.send_user = val,
|
||||||
12 => moz_ac.out.send_user = val,
|
MozConfigTag::Sockettype => {
|
||||||
13 => {
|
|
||||||
let val_lower = val.to_lowercase();
|
let val_lower = val.to_lowercase();
|
||||||
if val_lower == "ssl" {
|
if val_lower == "ssl" {
|
||||||
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
|
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
|
||||||
@@ -150,29 +164,34 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
},
|
||||||
|
MozServer::Undefined => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn moz_autoconfigure_endtag_cb(event: &BytesEnd, moz_ac: &mut moz_autoconfigure_t) {
|
fn moz_autoconfigure_endtag_cb(event: &BytesEnd, moz_ac: &mut MozAutoconfigure) {
|
||||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||||
|
|
||||||
if tag == "incomingserver" {
|
if tag == "incomingserver" {
|
||||||
moz_ac.tag_server = 0;
|
if moz_ac.tag_server == MozServer::Imap {
|
||||||
moz_ac.tag_config = 0;
|
moz_ac.out_imap_set = true;
|
||||||
moz_ac.out_imap_set = 1;
|
}
|
||||||
|
moz_ac.tag_server = MozServer::Undefined;
|
||||||
|
moz_ac.tag_config = MozConfigTag::Undefined;
|
||||||
} else if tag == "outgoingserver" {
|
} else if tag == "outgoingserver" {
|
||||||
moz_ac.tag_server = 0;
|
if moz_ac.tag_server == MozServer::Smtp {
|
||||||
moz_ac.tag_config = 0;
|
moz_ac.out_smtp_set = true;
|
||||||
moz_ac.out_smtp_set = 1;
|
}
|
||||||
|
moz_ac.tag_server = MozServer::Undefined;
|
||||||
|
moz_ac.tag_config = MozConfigTag::Undefined;
|
||||||
} else {
|
} else {
|
||||||
moz_ac.tag_config = 0;
|
moz_ac.tag_config = MozConfigTag::Undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn moz_autoconfigure_starttag_cb<B: std::io::BufRead>(
|
fn moz_autoconfigure_starttag_cb<B: std::io::BufRead>(
|
||||||
event: &BytesStart,
|
event: &BytesStart,
|
||||||
moz_ac: &mut moz_autoconfigure_t,
|
moz_ac: &mut MozAutoconfigure,
|
||||||
reader: &quick_xml::Reader<B>,
|
reader: &quick_xml::Reader<B>,
|
||||||
) {
|
) {
|
||||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||||
@@ -189,25 +208,115 @@ fn moz_autoconfigure_starttag_cb<B: std::io::BufRead>(
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_lowercase();
|
.to_lowercase();
|
||||||
|
|
||||||
if typ == "imap" && moz_ac.out_imap_set == 0 {
|
if typ == "imap" && !moz_ac.out_imap_set {
|
||||||
1
|
MozServer::Imap
|
||||||
} else {
|
} else {
|
||||||
0
|
MozServer::Undefined
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
0
|
MozServer::Undefined
|
||||||
};
|
};
|
||||||
moz_ac.tag_config = 0;
|
moz_ac.tag_config = MozConfigTag::Undefined;
|
||||||
} else if tag == "outgoingserver" {
|
} else if tag == "outgoingserver" {
|
||||||
moz_ac.tag_server = if moz_ac.out_smtp_set == 0 { 2 } else { 0 };
|
moz_ac.tag_server = if !moz_ac.out_smtp_set {
|
||||||
moz_ac.tag_config = 0;
|
MozServer::Smtp
|
||||||
|
} else {
|
||||||
|
MozServer::Undefined
|
||||||
|
};
|
||||||
|
moz_ac.tag_config = MozConfigTag::Undefined;
|
||||||
} else if tag == "hostname" {
|
} else if tag == "hostname" {
|
||||||
moz_ac.tag_config = 10;
|
moz_ac.tag_config = MozConfigTag::Hostname;
|
||||||
} else if tag == "port" {
|
} else if tag == "port" {
|
||||||
moz_ac.tag_config = 11;
|
moz_ac.tag_config = MozConfigTag::Port;
|
||||||
} else if tag == "sockettype" {
|
} else if tag == "sockettype" {
|
||||||
moz_ac.tag_config = 13;
|
moz_ac.tag_config = MozConfigTag::Sockettype;
|
||||||
} else if tag == "username" {
|
} else if tag == "username" {
|
||||||
moz_ac.tag_config = 12;
|
moz_ac.tag_config = MozConfigTag::Username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_outlook_autoconfig() {
|
||||||
|
// Copied from https://autoconfig.thunderbird.net/v1.1/outlook.com on 2019-10-11
|
||||||
|
let xml_raw =
|
||||||
|
"<clientConfig version=\"1.1\">
|
||||||
|
<emailProvider id=\"outlook.com\">
|
||||||
|
<domain>hotmail.com</domain>
|
||||||
|
<domain>hotmail.co.uk</domain>
|
||||||
|
<domain>hotmail.co.jp</domain>
|
||||||
|
<domain>hotmail.com.br</domain>
|
||||||
|
<domain>hotmail.de</domain>
|
||||||
|
<domain>hotmail.fr</domain>
|
||||||
|
<domain>hotmail.it</domain>
|
||||||
|
<domain>hotmail.es</domain>
|
||||||
|
<domain>live.com</domain>
|
||||||
|
<domain>live.co.uk</domain>
|
||||||
|
<domain>live.co.jp</domain>
|
||||||
|
<domain>live.de</domain>
|
||||||
|
<domain>live.fr</domain>
|
||||||
|
<domain>live.it</domain>
|
||||||
|
<domain>live.jp</domain>
|
||||||
|
<domain>msn.com</domain>
|
||||||
|
<domain>outlook.com</domain>
|
||||||
|
<displayName>Outlook.com (Microsoft)</displayName>
|
||||||
|
<displayShortName>Outlook</displayShortName>
|
||||||
|
<incomingServer type=\"exchange\">
|
||||||
|
<hostname>outlook.office365.com</hostname>
|
||||||
|
<port>443</port>
|
||||||
|
<username>%EMAILADDRESS%</username>
|
||||||
|
<socketType>SSL</socketType>
|
||||||
|
<authentication>OAuth2</authentication>
|
||||||
|
<owaURL>https://outlook.office365.com/owa/</owaURL>
|
||||||
|
<ewsURL>https://outlook.office365.com/ews/exchange.asmx</ewsURL>
|
||||||
|
<useGlobalPreferredServer>true</useGlobalPreferredServer>
|
||||||
|
</incomingServer>
|
||||||
|
<incomingServer type=\"imap\">
|
||||||
|
<hostname>outlook.office365.com</hostname>
|
||||||
|
<port>993</port>
|
||||||
|
<socketType>SSL</socketType>
|
||||||
|
<authentication>password-cleartext</authentication>
|
||||||
|
<username>%EMAILADDRESS%</username>
|
||||||
|
</incomingServer>
|
||||||
|
<incomingServer type=\"pop3\">
|
||||||
|
<hostname>outlook.office365.com</hostname>
|
||||||
|
<port>995</port>
|
||||||
|
<socketType>SSL</socketType>
|
||||||
|
<authentication>password-cleartext</authentication>
|
||||||
|
<username>%EMAILADDRESS%</username>
|
||||||
|
<pop3>
|
||||||
|
<leaveMessagesOnServer>true</leaveMessagesOnServer>
|
||||||
|
<!-- Outlook.com docs specifically mention that POP3 deletes have effect on the main inbox on webmail and IMAP -->
|
||||||
|
</pop3>
|
||||||
|
</incomingServer>
|
||||||
|
<outgoingServer type=\"smtp\">
|
||||||
|
<hostname>smtp.office365.com</hostname>
|
||||||
|
<port>587</port>
|
||||||
|
<socketType>STARTTLS</socketType>
|
||||||
|
<authentication>password-cleartext</authentication>
|
||||||
|
<username>%EMAILADDRESS%</username>
|
||||||
|
</outgoingServer>
|
||||||
|
<documentation url=\"http://windows.microsoft.com/en-US/windows/outlook/send-receive-from-app\">
|
||||||
|
<descr lang=\"en\">Set up an email app with Outlook.com</descr>
|
||||||
|
</documentation>
|
||||||
|
</emailProvider>
|
||||||
|
<webMail>
|
||||||
|
<loginPage url=\"https://www.outlook.com/\"/>
|
||||||
|
<loginPageInfo url=\"https://www.outlook.com/\">
|
||||||
|
<username>%EMAILADDRESS%</username>
|
||||||
|
<usernameField id=\"i0116\" name=\"login\"/>
|
||||||
|
<passwordField id=\"i0118\" name=\"passwd\"/>
|
||||||
|
<loginButton id=\"idSIButton9\" name=\"SI\"/>
|
||||||
|
</loginPageInfo>
|
||||||
|
</webMail>
|
||||||
|
</clientConfig>";
|
||||||
|
let res = moz_parse_xml("example@outlook.com", xml_raw).expect("XML parsing failed");
|
||||||
|
assert_eq!(res.mail_server, "outlook.office365.com");
|
||||||
|
assert_eq!(res.mail_port, 993);
|
||||||
|
assert_eq!(res.send_server, "smtp.office365.com");
|
||||||
|
assert_eq!(res.send_port, 587);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,213 +1,249 @@
|
|||||||
use quick_xml;
|
use quick_xml;
|
||||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
use quick_xml::events::BytesEnd;
|
||||||
|
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::dc_tools::*;
|
use crate::error::Error;
|
||||||
use crate::login_param::LoginParam;
|
use crate::login_param::LoginParam;
|
||||||
use crate::x::*;
|
|
||||||
use std::ptr;
|
|
||||||
|
|
||||||
use super::read_autoconf_file;
|
use super::read_autoconf_file;
|
||||||
/* ******************************************************************************
|
|
||||||
* Outlook's Autodiscover
|
/// Outlook's Autodiscover
|
||||||
******************************************************************************/
|
struct OutlookAutodiscover {
|
||||||
#[repr(C)]
|
|
||||||
struct outlk_autodiscover_t<'a> {
|
|
||||||
pub in_0: &'a LoginParam,
|
|
||||||
pub out: LoginParam,
|
pub out: LoginParam,
|
||||||
pub out_imap_set: libc::c_int,
|
pub out_imap_set: bool,
|
||||||
pub out_smtp_set: libc::c_int,
|
pub out_smtp_set: bool,
|
||||||
pub tag_config: libc::c_int,
|
pub config_type: Option<String>,
|
||||||
pub config: [*mut libc::c_char; 6],
|
pub config_server: String,
|
||||||
pub redirect: *mut libc::c_char,
|
pub config_port: i32,
|
||||||
|
pub config_ssl: String,
|
||||||
|
pub config_redirecturl: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn outlk_autodiscover(
|
enum ParsingResult {
|
||||||
context: &Context,
|
LoginParam(LoginParam),
|
||||||
url__: &str,
|
RedirectUrl(String),
|
||||||
param_in: &LoginParam,
|
}
|
||||||
) -> Option<LoginParam> {
|
|
||||||
let mut xml_raw: *mut libc::c_char = ptr::null_mut();
|
fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
|
||||||
let mut url = url__.strdup();
|
let mut outlk_ad = OutlookAutodiscover {
|
||||||
let mut outlk_ad = outlk_autodiscover_t {
|
|
||||||
in_0: param_in,
|
|
||||||
out: LoginParam::new(),
|
out: LoginParam::new(),
|
||||||
out_imap_set: 0,
|
out_imap_set: false,
|
||||||
out_smtp_set: 0,
|
out_smtp_set: false,
|
||||||
tag_config: 0,
|
config_type: None,
|
||||||
config: [ptr::null_mut(); 6],
|
config_server: String::new(),
|
||||||
redirect: ptr::null_mut(),
|
config_port: 0,
|
||||||
|
config_ssl: String::new(),
|
||||||
|
config_redirecturl: None,
|
||||||
};
|
};
|
||||||
let ok_to_continue;
|
|
||||||
let mut i = 0;
|
let mut reader = quick_xml::Reader::from_str(&xml_raw);
|
||||||
|
reader.trim_text(true);
|
||||||
|
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
|
||||||
|
let mut current_tag: Option<String> = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if !(i < 10) {
|
match reader.read_event(&mut buf) {
|
||||||
ok_to_continue = true;
|
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||||
break;
|
let tag = String::from_utf8_lossy(e.name()).trim().to_lowercase();
|
||||||
}
|
|
||||||
memset(
|
|
||||||
&mut outlk_ad as *mut outlk_autodiscover_t as *mut libc::c_void,
|
|
||||||
0,
|
|
||||||
::std::mem::size_of::<outlk_autodiscover_t>(),
|
|
||||||
);
|
|
||||||
xml_raw = read_autoconf_file(context, as_str(url));
|
|
||||||
if xml_raw.is_null() {
|
|
||||||
ok_to_continue = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut reader = quick_xml::Reader::from_str(as_str(xml_raw));
|
if tag == "protocol" {
|
||||||
reader.trim_text(true);
|
outlk_ad.config_type = None;
|
||||||
|
outlk_ad.config_server = String::new();
|
||||||
|
outlk_ad.config_port = 0;
|
||||||
|
outlk_ad.config_ssl = String::new();
|
||||||
|
outlk_ad.config_redirecturl = None;
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
current_tag = None;
|
||||||
|
} else {
|
||||||
loop {
|
current_tag = Some(tag);
|
||||||
match reader.read_event(&mut buf) {
|
|
||||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
|
||||||
outlk_autodiscover_starttag_cb(e, &mut outlk_ad)
|
|
||||||
}
|
}
|
||||||
Ok(quick_xml::events::Event::End(ref e)) => {
|
|
||||||
outlk_autodiscover_endtag_cb(e, &mut outlk_ad)
|
|
||||||
}
|
|
||||||
Ok(quick_xml::events::Event::Text(ref e)) => {
|
|
||||||
outlk_autodiscover_text_cb(e, &mut outlk_ad, &reader)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!(
|
|
||||||
context,
|
|
||||||
"Configure xml: Error at position {}: {:?}",
|
|
||||||
reader.buffer_position(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(quick_xml::events::Event::Eof) => break,
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
buf.clear();
|
Ok(quick_xml::events::Event::End(ref e)) => {
|
||||||
}
|
outlk_autodiscover_endtag_cb(e, &mut outlk_ad);
|
||||||
|
current_tag = None;
|
||||||
|
}
|
||||||
|
Ok(quick_xml::events::Event::Text(ref e)) => {
|
||||||
|
let val = e.unescape_and_decode(&reader).unwrap_or_default();
|
||||||
|
|
||||||
if !(!outlk_ad.config[5].is_null()
|
if let Some(ref tag) = current_tag {
|
||||||
&& 0 != *outlk_ad.config[5usize].offset(0isize) as libc::c_int)
|
match tag.as_str() {
|
||||||
{
|
"type" => {
|
||||||
ok_to_continue = true;
|
outlk_ad.config_type = Some(val.trim().to_lowercase().to_string())
|
||||||
break;
|
}
|
||||||
|
"server" => outlk_ad.config_server = val.trim().to_string(),
|
||||||
|
"port" => outlk_ad.config_port = val.trim().parse().unwrap_or_default(),
|
||||||
|
"ssl" => outlk_ad.config_ssl = val.trim().to_string(),
|
||||||
|
"redirecturl" => outlk_ad.config_redirecturl = Some(val.trim().to_string()),
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
bail!(
|
||||||
|
"Configure xml: Error at position {}: {:?}",
|
||||||
|
reader.buffer_position(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(quick_xml::events::Event::Eof) => break,
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
free(url as *mut libc::c_void);
|
buf.clear();
|
||||||
url = dc_strdup(outlk_ad.config[5usize]);
|
|
||||||
|
|
||||||
outlk_clean_config(&mut outlk_ad);
|
|
||||||
free(xml_raw as *mut libc::c_void);
|
|
||||||
xml_raw = ptr::null_mut();
|
|
||||||
i += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok_to_continue {
|
// XML redirect via redirecturl
|
||||||
|
if outlk_ad.config_redirecturl.is_none()
|
||||||
|
|| outlk_ad.config_redirecturl.as_ref().unwrap().is_empty()
|
||||||
|
{
|
||||||
if outlk_ad.out.mail_server.is_empty()
|
if outlk_ad.out.mail_server.is_empty()
|
||||||
|| outlk_ad.out.mail_port == 0
|
|| outlk_ad.out.mail_port == 0
|
||||||
|| 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
|
||||||
{
|
{
|
||||||
let r = outlk_ad.out.to_string();
|
let r = outlk_ad.out.to_string();
|
||||||
warn!(context, "Bad or incomplete autoconfig: {}", r,);
|
bail!("Bad or incomplete autoconfig: {}", r,);
|
||||||
free(url as *mut libc::c_void);
|
}
|
||||||
free(xml_raw as *mut libc::c_void);
|
Ok(ParsingResult::LoginParam(outlk_ad.out))
|
||||||
outlk_clean_config(&mut outlk_ad);
|
} else {
|
||||||
|
Ok(ParsingResult::RedirectUrl(
|
||||||
|
outlk_ad.config_redirecturl.unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn outlk_autodiscover(
|
||||||
|
context: &Context,
|
||||||
|
url: &str,
|
||||||
|
_param_in: &LoginParam,
|
||||||
|
) -> Option<LoginParam> {
|
||||||
|
let mut url = url.to_string();
|
||||||
|
/* Follow up to 10 xml-redirects (http-redirects are followed in read_autoconf_file() */
|
||||||
|
for _i in 0..10 {
|
||||||
|
if let Some(xml_raw) = read_autoconf_file(context, &url) {
|
||||||
|
match outlk_parse_xml(&xml_raw) {
|
||||||
|
Err(err) => {
|
||||||
|
warn!(context, "{}", err);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Ok(res) => match res {
|
||||||
|
ParsingResult::RedirectUrl(redirect_url) => url = redirect_url,
|
||||||
|
ParsingResult::LoginParam(login_param) => return Some(login_param),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
free(url as *mut libc::c_void);
|
None
|
||||||
free(xml_raw as *mut libc::c_void);
|
|
||||||
outlk_clean_config(&mut outlk_ad);
|
|
||||||
Some(outlk_ad.out)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn outlk_clean_config(mut outlk_ad: *mut outlk_autodiscover_t) {
|
fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut OutlookAutodiscover) {
|
||||||
for i in 0..6 {
|
|
||||||
free((*outlk_ad).config[i] as *mut libc::c_void);
|
|
||||||
(*outlk_ad).config[i] = ptr::null_mut();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn outlk_autodiscover_text_cb<B: std::io::BufRead>(
|
|
||||||
event: &BytesText,
|
|
||||||
outlk_ad: &mut outlk_autodiscover_t,
|
|
||||||
reader: &quick_xml::Reader<B>,
|
|
||||||
) {
|
|
||||||
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
free(outlk_ad.config[outlk_ad.tag_config as usize].cast());
|
|
||||||
outlk_ad.config[outlk_ad.tag_config as usize] = val.trim().strdup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut outlk_autodiscover_t) {
|
|
||||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||||
|
|
||||||
if tag == "protocol" {
|
if tag == "protocol" {
|
||||||
if !outlk_ad.config[1].is_null() {
|
if let Some(type_) = &outlk_ad.config_type {
|
||||||
let port = dc_atoi_null_is_0(outlk_ad.config[3]);
|
let port = outlk_ad.config_port;
|
||||||
let ssl_on = (!outlk_ad.config[4].is_null()
|
let ssl_on = outlk_ad.config_ssl == "on";
|
||||||
&& strcasecmp(
|
let ssl_off = outlk_ad.config_ssl == "off";
|
||||||
outlk_ad.config[4],
|
if type_ == "imap" && !outlk_ad.out_imap_set {
|
||||||
b"on\x00" as *const u8 as *const libc::c_char,
|
outlk_ad.out.mail_server =
|
||||||
) == 0) as libc::c_int;
|
std::mem::replace(&mut outlk_ad.config_server, String::new());
|
||||||
let ssl_off = (!outlk_ad.config[4].is_null()
|
|
||||||
&& strcasecmp(
|
|
||||||
outlk_ad.config[4],
|
|
||||||
b"off\x00" as *const u8 as *const libc::c_char,
|
|
||||||
) == 0) as libc::c_int;
|
|
||||||
if strcasecmp(
|
|
||||||
outlk_ad.config[1],
|
|
||||||
b"imap\x00" as *const u8 as *const libc::c_char,
|
|
||||||
) == 0
|
|
||||||
&& outlk_ad.out_imap_set == 0
|
|
||||||
{
|
|
||||||
outlk_ad.out.mail_server = to_string(outlk_ad.config[2]);
|
|
||||||
outlk_ad.out.mail_port = port;
|
outlk_ad.out.mail_port = port;
|
||||||
if 0 != ssl_on {
|
if ssl_on {
|
||||||
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
|
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
|
||||||
} else if 0 != ssl_off {
|
} else if ssl_off {
|
||||||
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
|
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
|
||||||
}
|
}
|
||||||
outlk_ad.out_imap_set = 1
|
outlk_ad.out_imap_set = true
|
||||||
} else if strcasecmp(
|
} else if type_ == "smtp" && !outlk_ad.out_smtp_set {
|
||||||
outlk_ad.config[1usize],
|
outlk_ad.out.send_server =
|
||||||
b"smtp\x00" as *const u8 as *const libc::c_char,
|
std::mem::replace(&mut outlk_ad.config_server, String::new());
|
||||||
) == 0
|
outlk_ad.out.send_port = outlk_ad.config_port;
|
||||||
&& outlk_ad.out_smtp_set == 0
|
if ssl_on {
|
||||||
{
|
|
||||||
outlk_ad.out.send_server = to_string(outlk_ad.config[2]);
|
|
||||||
outlk_ad.out.send_port = port;
|
|
||||||
if 0 != ssl_on {
|
|
||||||
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
|
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
|
||||||
} else if 0 != ssl_off {
|
} else if ssl_off {
|
||||||
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
|
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
|
||||||
}
|
}
|
||||||
outlk_ad.out_smtp_set = 1
|
outlk_ad.out_smtp_set = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
outlk_clean_config(outlk_ad);
|
|
||||||
}
|
}
|
||||||
outlk_ad.tag_config = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn outlk_autodiscover_starttag_cb(event: &BytesStart, outlk_ad: &mut outlk_autodiscover_t) {
|
#[cfg(test)]
|
||||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
if tag == "protocol" {
|
#[test]
|
||||||
unsafe { outlk_clean_config(outlk_ad) };
|
fn test_parse_redirect() {
|
||||||
} else if tag == "type" {
|
let res = outlk_parse_xml("
|
||||||
outlk_ad.tag_config = 1
|
<?xml version=\"1.0\" encoding=\"utf-8\"?>
|
||||||
} else if tag == "server" {
|
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
|
||||||
outlk_ad.tag_config = 2
|
<Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">
|
||||||
} else if tag == "port" {
|
<Account>
|
||||||
outlk_ad.tag_config = 3
|
<AccountType>email</AccountType>
|
||||||
} else if tag == "ssl" {
|
<Action>redirectUrl</Action>
|
||||||
outlk_ad.tag_config = 4
|
<RedirectUrl>https://mail.example.com/autodiscover/autodiscover.xml</RedirectUrl>
|
||||||
} else if tag == "redirecturl" {
|
</Account>
|
||||||
outlk_ad.tag_config = 5
|
</Response>
|
||||||
};
|
</Autodiscover>
|
||||||
|
").expect("XML is not parsed successfully");
|
||||||
|
match res {
|
||||||
|
ParsingResult::LoginParam(_lp) => {
|
||||||
|
panic!("redirecturl is not found");
|
||||||
|
}
|
||||||
|
ParsingResult::RedirectUrl(url) => {
|
||||||
|
assert_eq!(
|
||||||
|
url,
|
||||||
|
"https://mail.example.com/autodiscover/autodiscover.xml"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_loginparam() {
|
||||||
|
let res = outlk_parse_xml(
|
||||||
|
"\
|
||||||
|
<?xml version=\"1.0\" encoding=\"utf-8\"?>
|
||||||
|
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
|
||||||
|
<Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">
|
||||||
|
<Account>
|
||||||
|
<AccountType>email</AccountType>
|
||||||
|
<Action>settings</Action>
|
||||||
|
<Protocol>
|
||||||
|
<Type>IMAP</Type>
|
||||||
|
<Server>example.com</Server>
|
||||||
|
<Port>993</Port>
|
||||||
|
<SSL>on</SSL>
|
||||||
|
<AuthRequired>on</AuthRequired>
|
||||||
|
</Protocol>
|
||||||
|
<Protocol>
|
||||||
|
<Type>SMTP</Type>
|
||||||
|
<Server>smtp.example.com</Server>
|
||||||
|
<Port>25</Port>
|
||||||
|
<SSL>off</SSL>
|
||||||
|
<AuthRequired>on</AuthRequired>
|
||||||
|
</Protocol>
|
||||||
|
</Account>
|
||||||
|
</Response>
|
||||||
|
</Autodiscover>",
|
||||||
|
)
|
||||||
|
.expect("XML is not parsed successfully");
|
||||||
|
|
||||||
|
match res {
|
||||||
|
ParsingResult::LoginParam(lp) => {
|
||||||
|
assert_eq!(lp.mail_server, "example.com");
|
||||||
|
assert_eq!(lp.mail_port, 993);
|
||||||
|
assert_eq!(lp.send_server, "smtp.example.com");
|
||||||
|
assert_eq!(lp.send_port, 25);
|
||||||
|
}
|
||||||
|
ParsingResult::RedirectUrl(_) => {
|
||||||
|
panic!("RedirectUrl is not expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -25,7 +25,6 @@ impl Default for MoveState {
|
|||||||
|
|
||||||
// some defaults
|
// some defaults
|
||||||
const DC_E2EE_DEFAULT_ENABLED: i32 = 1;
|
const DC_E2EE_DEFAULT_ENABLED: i32 = 1;
|
||||||
pub const DC_MDNS_DEFAULT_ENABLED: i32 = 1;
|
|
||||||
const DC_INBOX_WATCH_DEFAULT: i32 = 1;
|
const DC_INBOX_WATCH_DEFAULT: i32 = 1;
|
||||||
const DC_SENTBOX_WATCH_DEFAULT: i32 = 1;
|
const DC_SENTBOX_WATCH_DEFAULT: i32 = 1;
|
||||||
const DC_MVBOX_WATCH_DEFAULT: i32 = 1;
|
const DC_MVBOX_WATCH_DEFAULT: i32 = 1;
|
||||||
@@ -60,14 +59,9 @@ const DC_GCM_ADDDAYMARKER: usize = 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;
|
||||||
|
|
||||||
/// param1 is a directory where the keys are written to
|
// values for DC_PARAM_FORCE_PLAINTEXT
|
||||||
const DC_IMEX_EXPORT_SELF_KEYS: usize = 1;
|
pub(crate) const DC_FP_NO_AUTOCRYPT_HEADER: i32 = 2;
|
||||||
/// param1 is a directory where the keys are searched in and read from
|
pub(crate) const DC_FP_ADD_AUTOCRYPT_HEADER: i32 = 1;
|
||||||
const DC_IMEX_IMPORT_SELF_KEYS: usize = 2;
|
|
||||||
/// param1 is a directory where the backup is written to
|
|
||||||
const DC_IMEX_EXPORT_BACKUP: usize = 11;
|
|
||||||
/// param1 is the file with the backup to import
|
|
||||||
const DC_IMEX_IMPORT_BACKUP: usize = 12;
|
|
||||||
|
|
||||||
/// virtual chat showing all messages belonging to chats flagged with chats.blocked=2
|
/// virtual chat showing all messages belonging to chats flagged with chats.blocked=2
|
||||||
pub(crate) const DC_CHAT_ID_DEADDROP: u32 = 1;
|
pub(crate) const DC_CHAT_ID_DEADDROP: u32 = 1;
|
||||||
@@ -122,7 +116,7 @@ const DC_MAX_GET_INFO_LEN: usize = 100000;
|
|||||||
|
|
||||||
pub const DC_CONTACT_ID_UNDEFINED: u32 = 0;
|
pub const DC_CONTACT_ID_UNDEFINED: u32 = 0;
|
||||||
pub const DC_CONTACT_ID_SELF: u32 = 1;
|
pub const DC_CONTACT_ID_SELF: u32 = 1;
|
||||||
const DC_CONTACT_ID_DEVICE: u32 = 2;
|
pub const DC_CONTACT_ID_DEVICE: u32 = 2;
|
||||||
pub const DC_CONTACT_ID_LAST_SPECIAL: u32 = 9;
|
pub const DC_CONTACT_ID_LAST_SPECIAL: u32 = 9;
|
||||||
|
|
||||||
pub const DC_CREATE_MVBOX: usize = 1;
|
pub const DC_CREATE_MVBOX: usize = 1;
|
||||||
@@ -135,23 +129,23 @@ pub const DC_CREATE_MVBOX: usize = 1;
|
|||||||
/// Force OAuth2 authorization. This flag does not skip automatic configuration.
|
/// Force OAuth2 authorization. This flag does not skip automatic configuration.
|
||||||
/// Before calling configure() with DC_LP_AUTH_OAUTH2 set,
|
/// Before calling configure() with DC_LP_AUTH_OAUTH2 set,
|
||||||
/// the user has to confirm access at the URL returned by dc_get_oauth2_url().
|
/// the user has to confirm access at the URL returned by dc_get_oauth2_url().
|
||||||
pub const DC_LP_AUTH_OAUTH2: usize = 0x2;
|
pub const DC_LP_AUTH_OAUTH2: i32 = 0x2;
|
||||||
|
|
||||||
/// Force NORMAL authorization, this is the default.
|
/// Force NORMAL authorization, this is the default.
|
||||||
/// If this flag is set, automatic configuration is skipped.
|
/// If this flag is set, automatic configuration is skipped.
|
||||||
pub const DC_LP_AUTH_NORMAL: usize = 0x4;
|
pub const DC_LP_AUTH_NORMAL: i32 = 0x4;
|
||||||
|
|
||||||
/// Connect to IMAP via STARTTLS.
|
/// Connect to IMAP via STARTTLS.
|
||||||
/// If this flag is set, automatic configuration is skipped.
|
/// If this flag is set, automatic configuration is skipped.
|
||||||
pub const DC_LP_IMAP_SOCKET_STARTTLS: usize = 0x100;
|
pub const DC_LP_IMAP_SOCKET_STARTTLS: i32 = 0x100;
|
||||||
|
|
||||||
/// Connect to IMAP via SSL.
|
/// Connect to IMAP via SSL.
|
||||||
/// If this flag is set, automatic configuration is skipped.
|
/// If this flag is set, automatic configuration is skipped.
|
||||||
pub const DC_LP_IMAP_SOCKET_SSL: usize = 0x200;
|
pub const DC_LP_IMAP_SOCKET_SSL: i32 = 0x200;
|
||||||
|
|
||||||
/// Connect to IMAP unencrypted, this should not be used.
|
/// Connect to IMAP unencrypted, this should not be used.
|
||||||
/// If this flag is set, automatic configuration is skipped.
|
/// If this flag is set, automatic configuration is skipped.
|
||||||
pub const DC_LP_IMAP_SOCKET_PLAIN: usize = 0x400;
|
pub const DC_LP_IMAP_SOCKET_PLAIN: i32 = 0x400;
|
||||||
|
|
||||||
/// Connect to SMTP via STARTTLS.
|
/// Connect to SMTP via STARTTLS.
|
||||||
/// If this flag is set, automatic configuration is skipped.
|
/// If this flag is set, automatic configuration is skipped.
|
||||||
@@ -166,9 +160,9 @@ pub const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
|
|||||||
pub const DC_LP_SMTP_SOCKET_PLAIN: usize = 0x40000;
|
pub const DC_LP_SMTP_SOCKET_PLAIN: usize = 0x40000;
|
||||||
|
|
||||||
/// if none of these flags are set, the default is chosen
|
/// if none of these flags are set, the default is chosen
|
||||||
pub const DC_LP_AUTH_FLAGS: usize = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
|
pub const DC_LP_AUTH_FLAGS: i32 = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
|
||||||
/// if none of these flags are set, the default is chosen
|
/// if none of these flags are set, the default is chosen
|
||||||
pub const DC_LP_IMAP_SOCKET_FLAGS: usize =
|
pub const DC_LP_IMAP_SOCKET_FLAGS: i32 =
|
||||||
(DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_SSL | DC_LP_IMAP_SOCKET_PLAIN);
|
(DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_SSL | DC_LP_IMAP_SOCKET_PLAIN);
|
||||||
/// if none of these flags are set, the default is chosen
|
/// if none of these flags are set, the default is chosen
|
||||||
pub const DC_LP_SMTP_SOCKET_FLAGS: usize =
|
pub const DC_LP_SMTP_SOCKET_FLAGS: usize =
|
||||||
@@ -200,6 +194,11 @@ pub enum Viewtype {
|
|||||||
/// and retrieved via dc_msg_get_file(), dc_msg_get_width(), dc_msg_get_height().
|
/// and retrieved via dc_msg_get_file(), dc_msg_get_width(), dc_msg_get_height().
|
||||||
Gif = 21,
|
Gif = 21,
|
||||||
|
|
||||||
|
/// Message containing a sticker, similar to image.
|
||||||
|
/// If possible, the ui should display the image without borders in a transparent way.
|
||||||
|
/// A click on a sticker will offer to install the sticker set in some future.
|
||||||
|
Sticker = 23,
|
||||||
|
|
||||||
/// Message containing an Audio file.
|
/// Message containing an Audio file.
|
||||||
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
|
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
|
||||||
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration().
|
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration().
|
||||||
@@ -258,7 +257,7 @@ const DC_SHOW_EMAILS_ACCEPTED_CONTACTS: usize = 1;
|
|||||||
const DC_SHOW_EMAILS_ALL: usize = 2;
|
const DC_SHOW_EMAILS_ALL: usize = 2;
|
||||||
|
|
||||||
// TODO: Strings need some doumentation about used placeholders.
|
// TODO: Strings need some doumentation about used placeholders.
|
||||||
// These constants are used to request strings using #DC_EVENT_GET_STRING.
|
// These constants are used to set stock translation strings
|
||||||
|
|
||||||
const DC_STR_NOMESSAGES: usize = 1;
|
const DC_STR_NOMESSAGES: usize = 1;
|
||||||
const DC_STR_SELF: usize = 2;
|
const DC_STR_SELF: usize = 2;
|
||||||
@@ -304,7 +303,8 @@ const DC_STR_MSGACTIONBYME: usize = 63;
|
|||||||
const DC_STR_MSGLOCATIONENABLED: usize = 64;
|
const DC_STR_MSGLOCATIONENABLED: usize = 64;
|
||||||
const DC_STR_MSGLOCATIONDISABLED: usize = 65;
|
const DC_STR_MSGLOCATIONDISABLED: usize = 65;
|
||||||
const DC_STR_LOCATION: usize = 66;
|
const DC_STR_LOCATION: usize = 66;
|
||||||
const DC_STR_COUNT: usize = 66;
|
const DC_STR_STICKER: usize = 67;
|
||||||
|
const DC_STR_COUNT: usize = 67;
|
||||||
|
|
||||||
pub const DC_JOB_DELETE_MSG_ON_IMAP: i32 = 110;
|
pub const DC_JOB_DELETE_MSG_ON_IMAP: i32 = 110;
|
||||||
|
|
||||||
|
|||||||
@@ -390,20 +390,18 @@ impl Contact {
|
|||||||
}
|
}
|
||||||
sth_modified = Modifier::Modified;
|
sth_modified = Modifier::Modified;
|
||||||
}
|
}
|
||||||
|
} else if sql::execute(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
"INSERT INTO contacts (name, addr, origin) VALUES(?, ?, ?);",
|
||||||
|
params![name.as_ref(), addr, origin,],
|
||||||
|
)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
|
||||||
|
sth_modified = Modifier::Created;
|
||||||
} else {
|
} else {
|
||||||
if sql::execute(
|
error!(context, "Cannot add contact.");
|
||||||
context,
|
|
||||||
&context.sql,
|
|
||||||
"INSERT INTO contacts (name, addr, origin) VALUES(?, ?, ?);",
|
|
||||||
params![name.as_ref(), addr, origin,],
|
|
||||||
)
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
|
|
||||||
sth_modified = Modifier::Created;
|
|
||||||
} else {
|
|
||||||
error!(context, "Cannot add contact.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((row_id, sth_modified))
|
Ok((row_id, sth_modified))
|
||||||
@@ -804,14 +802,14 @@ impl Contact {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(peerstate) = peerstate {
|
if let Some(peerstate) = peerstate {
|
||||||
if peerstate.verified_key().is_some() {
|
if peerstate.verified_key.is_some() {
|
||||||
return VerifiedStatus::BidirectVerified;
|
return VerifiedStatus::BidirectVerified;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let peerstate = Peerstate::from_addr(context, &context.sql, &self.addr);
|
let peerstate = Peerstate::from_addr(context, &context.sql, &self.addr);
|
||||||
if let Some(ps) = peerstate {
|
if let Some(ps) = peerstate {
|
||||||
if ps.verified_key().is_some() {
|
if ps.verified_key.is_some() {
|
||||||
return VerifiedStatus::BidirectVerified;
|
return VerifiedStatus::BidirectVerified;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -827,7 +825,7 @@ impl Contact {
|
|||||||
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
|
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
|
||||||
if !contact.addr.is_empty() {
|
if !contact.addr.is_empty() {
|
||||||
let normalized_addr = addr_normalize(addr.as_ref());
|
let normalized_addr = addr_normalize(addr.as_ref());
|
||||||
if &contact.addr == &normalized_addr {
|
if contact.addr == normalized_addr {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -963,9 +961,9 @@ pub fn normalize_name(full_name: impl AsRef<str>) -> String {
|
|||||||
if len > 0 {
|
if len > 0 {
|
||||||
let firstchar = full_name.as_bytes()[0];
|
let firstchar = full_name.as_bytes()[0];
|
||||||
let lastchar = full_name.as_bytes()[len - 1];
|
let lastchar = full_name.as_bytes()[len - 1];
|
||||||
if firstchar == '\'' as u8 && lastchar == '\'' as u8
|
if firstchar == b'\'' && lastchar == b'\''
|
||||||
|| firstchar == '\"' as u8 && lastchar == '\"' as u8
|
|| firstchar == b'\"' && lastchar == b'\"'
|
||||||
|| firstchar == '<' as u8 && lastchar == '>' as u8
|
|| firstchar == b'<' && lastchar == b'>'
|
||||||
{
|
{
|
||||||
full_name = &full_name[1..len - 1];
|
full_name = &full_name[1..len - 1];
|
||||||
}
|
}
|
||||||
|
|||||||
302
src/context.rs
302
src/context.rs
@@ -1,13 +1,17 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
use std::fs;
|
||||||
|
use std::io::Write;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Arc, Condvar, Mutex, RwLock};
|
use std::sync::{Arc, Condvar, Mutex, RwLock};
|
||||||
|
|
||||||
use libc::uintptr_t;
|
use libc::uintptr_t;
|
||||||
|
|
||||||
use crate::chat::*;
|
use crate::chat::*;
|
||||||
|
use crate::config::Config;
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::contact::*;
|
use crate::contact::*;
|
||||||
|
use crate::dc_tools::{dc_copy_file, dc_derive_safe_stem_ext};
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use crate::events::Event;
|
use crate::events::Event;
|
||||||
use crate::imap::*;
|
use crate::imap::*;
|
||||||
@@ -16,10 +20,11 @@ use crate::job_thread::JobThread;
|
|||||||
use crate::key::*;
|
use crate::key::*;
|
||||||
use crate::login_param::LoginParam;
|
use crate::login_param::LoginParam;
|
||||||
use crate::lot::Lot;
|
use crate::lot::Lot;
|
||||||
use crate::message::*;
|
use crate::message::{self, Message};
|
||||||
use crate::param::Params;
|
use crate::param::Params;
|
||||||
use crate::smtp::*;
|
use crate::smtp::*;
|
||||||
use crate::sql::Sql;
|
use crate::sql::Sql;
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
|
||||||
/// Callback function type for [Context]
|
/// Callback function type for [Context]
|
||||||
///
|
///
|
||||||
@@ -58,12 +63,37 @@ pub struct Context {
|
|||||||
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<()>,
|
||||||
|
pub translated_stockstrings: RwLock<HashMap<usize, String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct RunningState {
|
pub struct RunningState {
|
||||||
pub ongoing_running: bool,
|
pub ongoing_running: bool,
|
||||||
pub shall_stop_ongoing: bool,
|
shall_stop_ongoing: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return some info about deltachat-core
|
||||||
|
///
|
||||||
|
/// This contains information mostly about the library itself, the
|
||||||
|
/// actual keys and their values which will be present are not
|
||||||
|
/// guaranteed. Calling [Context::get_info] also includes information
|
||||||
|
/// about the context on top of the information here.
|
||||||
|
pub fn get_info() -> HashMap<&'static str, String> {
|
||||||
|
let mut res = HashMap::new();
|
||||||
|
res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR));
|
||||||
|
res.insert("sqlite_version", rusqlite::version().to_string());
|
||||||
|
res.insert(
|
||||||
|
"sqlite_thread_safe",
|
||||||
|
unsafe { rusqlite::ffi::sqlite3_threadsafe() }.to_string(),
|
||||||
|
);
|
||||||
|
res.insert(
|
||||||
|
"arch",
|
||||||
|
(::std::mem::size_of::<*mut libc::c_void>())
|
||||||
|
.wrapping_mul(8)
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
res.insert("level", "awesome".into());
|
||||||
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
@@ -116,10 +146,11 @@ impl Context {
|
|||||||
probe_imap_network: Arc::new(RwLock::new(false)),
|
probe_imap_network: Arc::new(RwLock::new(false)),
|
||||||
perform_inbox_jobs_needed: Arc::new(RwLock::new(false)),
|
perform_inbox_jobs_needed: Arc::new(RwLock::new(false)),
|
||||||
generating_key_mutex: Mutex::new(()),
|
generating_key_mutex: Mutex::new(()),
|
||||||
|
translated_stockstrings: RwLock::new(HashMap::new()),
|
||||||
};
|
};
|
||||||
|
|
||||||
ensure!(
|
ensure!(
|
||||||
ctx.sql.open(&ctx, &ctx.dbfile, 0),
|
ctx.sql.open(&ctx, &ctx.dbfile, false),
|
||||||
"Failed opening sqlite database"
|
"Failed opening sqlite database"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -134,35 +165,141 @@ impl Context {
|
|||||||
self.blobdir.as_path()
|
self.blobdir.as_path()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn copy_to_blobdir(&self, orig_filename: impl AsRef<str>) -> Result<String> {
|
||||||
|
// return a $BLOBDIR/<filename> with the content of orig_filename
|
||||||
|
// copied into it. The <filename> will be safely derived from
|
||||||
|
// orig_filename, and will not clash with existing filenames.
|
||||||
|
let dest = self.new_blob_file(&orig_filename, b"")?;
|
||||||
|
if dc_copy_file(
|
||||||
|
&self,
|
||||||
|
PathBuf::from(orig_filename.as_ref()),
|
||||||
|
PathBuf::from(&dest),
|
||||||
|
) {
|
||||||
|
Ok(dest)
|
||||||
|
} else {
|
||||||
|
bail!("could not copy {} to {}", orig_filename.as_ref(), dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_blob_file(&self, orig_filename: impl AsRef<str>, data: &[u8]) -> Result<String> {
|
||||||
|
// return a $BLOBDIR/<FILENAME> string which corresponds to the
|
||||||
|
// respective file in the blobdir, and which contains the data.
|
||||||
|
// FILENAME is computed by looking and possibly mangling the
|
||||||
|
// basename of orig_filename. The resulting filenames are meant
|
||||||
|
// to be human-readable.
|
||||||
|
let (stem, ext) = dc_derive_safe_stem_ext(orig_filename.as_ref());
|
||||||
|
|
||||||
|
// ext starts with "." or is empty string, so we can always resconstruct
|
||||||
|
|
||||||
|
for i in 0..3 {
|
||||||
|
let candidate_basename = match i {
|
||||||
|
// first a try to just use the (possibly mangled) original basename
|
||||||
|
0 => format!("{}{}", stem, ext),
|
||||||
|
|
||||||
|
// otherwise extend stem with random numbers
|
||||||
|
_ => {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let random_id: u32 = rng.gen();
|
||||||
|
format!("{}-{}{}", stem, random_id, ext)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let path = self.get_blobdir().join(&candidate_basename);
|
||||||
|
if let Ok(mut file) = fs::OpenOptions::new()
|
||||||
|
.create_new(true)
|
||||||
|
.write(true)
|
||||||
|
.open(&path)
|
||||||
|
{
|
||||||
|
file.write_all(data)?;
|
||||||
|
let db_entry = format!("$BLOBDIR/{}", candidate_basename);
|
||||||
|
self.call_cb(Event::NewBlobFile(db_entry.clone()));
|
||||||
|
return Ok(db_entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bail!("out of luck to create new blob file");
|
||||||
|
}
|
||||||
|
|
||||||
pub fn call_cb(&self, event: Event) -> uintptr_t {
|
pub fn call_cb(&self, event: Event) -> uintptr_t {
|
||||||
(*self.cb)(self, event)
|
(*self.cb)(self, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Ongoing process allocation/free/check
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
pub fn alloc_ongoing(&self) -> bool {
|
||||||
|
if self.has_ongoing() {
|
||||||
|
warn!(self, "There is already another ongoing process running.",);
|
||||||
|
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
let s_a = self.running_state.clone();
|
||||||
|
let mut s = s_a.write().unwrap();
|
||||||
|
|
||||||
|
s.ongoing_running = true;
|
||||||
|
s.shall_stop_ongoing = false;
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn free_ongoing(&self) {
|
||||||
|
let s_a = self.running_state.clone();
|
||||||
|
let mut s = s_a.write().unwrap();
|
||||||
|
|
||||||
|
s.ongoing_running = false;
|
||||||
|
s.shall_stop_ongoing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_ongoing(&self) -> bool {
|
||||||
|
let s_a = self.running_state.clone();
|
||||||
|
let s = s_a.read().unwrap();
|
||||||
|
|
||||||
|
s.ongoing_running || !s.shall_stop_ongoing
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signal an ongoing process to stop.
|
||||||
|
pub fn stop_ongoing(&self) {
|
||||||
|
let s_a = self.running_state.clone();
|
||||||
|
let mut s = s_a.write().unwrap();
|
||||||
|
|
||||||
|
if s.ongoing_running && !s.shall_stop_ongoing {
|
||||||
|
info!(self, "Signaling the ongoing process to stop ASAP.",);
|
||||||
|
s.shall_stop_ongoing = true;
|
||||||
|
} else {
|
||||||
|
info!(self, "No ongoing process to stop.",);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shall_stop_ongoing(&self) -> bool {
|
||||||
|
self.running_state
|
||||||
|
.clone()
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.shall_stop_ongoing
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* UI chat/message related API
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
pub fn get_info(&self) -> HashMap<&'static str, String> {
|
pub fn get_info(&self) -> HashMap<&'static str, String> {
|
||||||
let unset = "0";
|
let unset = "0";
|
||||||
let l = LoginParam::from_database(self, "");
|
let l = LoginParam::from_database(self, "");
|
||||||
let l2 = LoginParam::from_database(self, "configured_");
|
let l2 = LoginParam::from_database(self, "configured_");
|
||||||
let displayname = self.sql.get_config(self, "displayname");
|
let displayname = self.get_config(Config::Displayname);
|
||||||
let chats = get_chat_cnt(self) as usize;
|
let chats = get_chat_cnt(self) as usize;
|
||||||
let real_msgs = dc_get_real_msg_cnt(self) as usize;
|
let real_msgs = message::get_real_msg_cnt(self) as usize;
|
||||||
let deaddrop_msgs = dc_get_deaddrop_msg_cnt(self) as usize;
|
let deaddrop_msgs = message::get_deaddrop_msg_cnt(self) as usize;
|
||||||
let contacts = Contact::get_real_cnt(self) as usize;
|
let contacts = Contact::get_real_cnt(self) as usize;
|
||||||
let is_configured = self
|
let is_configured = self.get_config_int(Config::Configured);
|
||||||
.sql
|
|
||||||
.get_config_int(self, "configured")
|
|
||||||
.unwrap_or_default();
|
|
||||||
let dbversion = self
|
let dbversion = self
|
||||||
.sql
|
.sql
|
||||||
.get_config_int(self, "dbversion")
|
.get_raw_config_int(self, "dbversion")
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let e2ee_enabled = self
|
|
||||||
.sql
|
let e2ee_enabled = self.get_config_int(Config::E2eeEnabled);
|
||||||
.get_config_int(self, "e2ee_enabled")
|
let mdns_enabled = self.get_config_int(Config::MdnsEnabled);
|
||||||
.unwrap_or_else(|| 1);
|
let bcc_self = self.get_config_int(Config::BccSelf);
|
||||||
let mdns_enabled = self
|
|
||||||
.sql
|
|
||||||
.get_config_int(self, "mdns_enabled")
|
|
||||||
.unwrap_or_else(|| 1);
|
|
||||||
|
|
||||||
let prv_key_cnt: Option<isize> =
|
let prv_key_cnt: Option<isize> =
|
||||||
self.sql
|
self.sql
|
||||||
@@ -180,48 +317,25 @@ impl Context {
|
|||||||
"<Not yet calculated>".into()
|
"<Not yet calculated>".into()
|
||||||
};
|
};
|
||||||
|
|
||||||
let inbox_watch = self
|
let inbox_watch = self.get_config_int(Config::InboxWatch);
|
||||||
.sql
|
let sentbox_watch = self.get_config_int(Config::SentboxWatch);
|
||||||
.get_config_int(self, "inbox_watch")
|
let mvbox_watch = self.get_config_int(Config::MvboxWatch);
|
||||||
.unwrap_or_else(|| 1);
|
let mvbox_move = self.get_config_int(Config::MvboxMove);
|
||||||
let sentbox_watch = self
|
|
||||||
.sql
|
|
||||||
.get_config_int(self, "sentbox_watch")
|
|
||||||
.unwrap_or_else(|| 1);
|
|
||||||
let mvbox_watch = self
|
|
||||||
.sql
|
|
||||||
.get_config_int(self, "mvbox_watch")
|
|
||||||
.unwrap_or_else(|| 1);
|
|
||||||
let mvbox_move = self
|
|
||||||
.sql
|
|
||||||
.get_config_int(self, "mvbox_move")
|
|
||||||
.unwrap_or_else(|| 1);
|
|
||||||
let folders_configured = self
|
let folders_configured = self
|
||||||
.sql
|
.sql
|
||||||
.get_config_int(self, "folders_configured")
|
.get_raw_config_int(self, "folders_configured")
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let configured_sentbox_folder = self
|
let configured_sentbox_folder = self
|
||||||
.sql
|
.sql
|
||||||
.get_config(self, "configured_sentbox_folder")
|
.get_raw_config(self, "configured_sentbox_folder")
|
||||||
.unwrap_or_else(|| "<unset>".to_string());
|
.unwrap_or_else(|| "<unset>".to_string());
|
||||||
let configured_mvbox_folder = self
|
let configured_mvbox_folder = self
|
||||||
.sql
|
.sql
|
||||||
.get_config(self, "configured_mvbox_folder")
|
.get_raw_config(self, "configured_mvbox_folder")
|
||||||
.unwrap_or_else(|| "<unset>".to_string());
|
.unwrap_or_else(|| "<unset>".to_string());
|
||||||
|
|
||||||
let mut res = HashMap::new();
|
let mut res = get_info();
|
||||||
res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR));
|
|
||||||
res.insert("sqlite_version", rusqlite::version().to_string());
|
|
||||||
res.insert(
|
|
||||||
"sqlite_thread_safe",
|
|
||||||
unsafe { rusqlite::ffi::sqlite3_threadsafe() }.to_string(),
|
|
||||||
);
|
|
||||||
res.insert(
|
|
||||||
"arch",
|
|
||||||
(::std::mem::size_of::<*mut libc::c_void>())
|
|
||||||
.wrapping_mul(8)
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
res.insert("number_of_chats", chats.to_string());
|
res.insert("number_of_chats", chats.to_string());
|
||||||
res.insert("number_of_chat_messages", real_msgs.to_string());
|
res.insert("number_of_chat_messages", real_msgs.to_string());
|
||||||
res.insert("messages_in_contact_requests", deaddrop_msgs.to_string());
|
res.insert("messages_in_contact_requests", deaddrop_msgs.to_string());
|
||||||
@@ -242,6 +356,7 @@ impl Context {
|
|||||||
res.insert("configured_mvbox_folder", configured_mvbox_folder);
|
res.insert("configured_mvbox_folder", configured_mvbox_folder);
|
||||||
res.insert("mdns_enabled", mdns_enabled.to_string());
|
res.insert("mdns_enabled", mdns_enabled.to_string());
|
||||||
res.insert("e2ee_enabled", e2ee_enabled.to_string());
|
res.insert("e2ee_enabled", e2ee_enabled.to_string());
|
||||||
|
res.insert("bcc_self", bcc_self.to_string());
|
||||||
res.insert(
|
res.insert(
|
||||||
"private_key_count",
|
"private_key_count",
|
||||||
prv_key_cnt.unwrap_or_default().to_string(),
|
prv_key_cnt.unwrap_or_default().to_string(),
|
||||||
@@ -251,7 +366,6 @@ impl Context {
|
|||||||
pub_key_cnt.unwrap_or_default().to_string(),
|
pub_key_cnt.unwrap_or_default().to_string(),
|
||||||
);
|
);
|
||||||
res.insert("fingerprint", fingerprint_str);
|
res.insert("fingerprint", fingerprint_str);
|
||||||
res.insert("level", "awesome".into());
|
|
||||||
|
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
@@ -278,7 +392,7 @@ impl Context {
|
|||||||
Ok(ret)
|
Ok(ret)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
@@ -322,7 +436,7 @@ impl Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_sentbox(&self, folder_name: impl AsRef<str>) -> bool {
|
pub fn is_sentbox(&self, folder_name: impl AsRef<str>) -> bool {
|
||||||
let sentbox_name = self.sql.get_config(self, "configured_sentbox_folder");
|
let sentbox_name = self.sql.get_raw_config(self, "configured_sentbox_folder");
|
||||||
if let Some(name) = sentbox_name {
|
if let Some(name) = sentbox_name {
|
||||||
name == folder_name.as_ref()
|
name == folder_name.as_ref()
|
||||||
} else {
|
} else {
|
||||||
@@ -331,7 +445,7 @@ impl Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_mvbox(&self, folder_name: impl AsRef<str>) -> bool {
|
pub fn is_mvbox(&self, folder_name: impl AsRef<str>) -> bool {
|
||||||
let mvbox_name = self.sql.get_config(self, "configured_mvbox_folder");
|
let mvbox_name = self.sql.get_raw_config(self, "configured_mvbox_folder");
|
||||||
|
|
||||||
if let Some(name) = mvbox_name {
|
if let Some(name) = mvbox_name {
|
||||||
name == folder_name.as_ref()
|
name == folder_name.as_ref()
|
||||||
@@ -341,12 +455,7 @@ impl Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn do_heuristics_moves(&self, folder: &str, msg_id: u32) {
|
pub fn do_heuristics_moves(&self, folder: &str, msg_id: u32) {
|
||||||
if self
|
if !self.get_config_bool(Config::MvboxMove) {
|
||||||
.sql
|
|
||||||
.get_config_int(self, "mvbox_move")
|
|
||||||
.unwrap_or_else(|| 1)
|
|
||||||
== 0
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,15 +463,15 @@ impl Context {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(msg) = dc_msg_new_load(self, msg_id) {
|
if let Ok(msg) = Message::load_from_db(self, msg_id) {
|
||||||
if dc_msg_is_setupmessage(&msg) {
|
if msg.is_setupmessage() {
|
||||||
// do not move setup messages;
|
// do not move setup messages;
|
||||||
// there may be a non-delta device that wants to handle it
|
// there may be a non-delta device that wants to handle it
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.is_mvbox(folder) {
|
if self.is_mvbox(folder) {
|
||||||
dc_update_msg_move_state(self, msg.rfc724_mid, MoveState::Stay);
|
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
|
||||||
@@ -374,7 +483,7 @@ impl Context {
|
|||||||
Params::new(),
|
Params::new(),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
dc_update_msg_move_state(self, msg.rfc724_mid, MoveState::Moving);
|
message::update_msg_move_state(self, &msg.rfc724_mid, MoveState::Moving);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -427,6 +536,7 @@ pub fn get_version_str() -> &'static str {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
use crate::dc_tools::*;
|
||||||
use crate::test_utils::*;
|
use crate::test_utils::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -464,6 +574,51 @@ mod tests {
|
|||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_new_blob_file() {
|
||||||
|
let t = dummy_context();
|
||||||
|
let context = t.ctx;
|
||||||
|
let x = &context.new_blob_file("hello", b"data").unwrap();
|
||||||
|
assert!(dc_file_exist(&context, x));
|
||||||
|
assert!(x.starts_with("$BLOBDIR"));
|
||||||
|
assert!(dc_read_file(&context, x).unwrap() == b"data");
|
||||||
|
|
||||||
|
let y = &context.new_blob_file("hello", b"data").unwrap();
|
||||||
|
assert!(dc_file_exist(&context, y));
|
||||||
|
assert!(y.starts_with("$BLOBDIR/hello-"));
|
||||||
|
|
||||||
|
let x = &context.new_blob_file("xyz/hello.png", b"data").unwrap();
|
||||||
|
assert!(dc_file_exist(&context, x));
|
||||||
|
assert_eq!(x, "$BLOBDIR/hello.png");
|
||||||
|
|
||||||
|
let y = &context.new_blob_file("hello\\world.png", b"data").unwrap();
|
||||||
|
assert!(dc_file_exist(&context, y));
|
||||||
|
assert_eq!(y, "$BLOBDIR/world.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_new_blob_file_long_names() {
|
||||||
|
let t = dummy_context();
|
||||||
|
let context = t.ctx;
|
||||||
|
let s = "12312312039182039182039812039810293810293810293810293801293801293123123";
|
||||||
|
let x = &context.new_blob_file(s, b"data").unwrap();
|
||||||
|
println!("blobfilename '{}'", x);
|
||||||
|
println!("xxxxfilename '{}'", s);
|
||||||
|
assert!(x.len() < s.len());
|
||||||
|
assert!(dc_file_exist(&context, x));
|
||||||
|
assert!(x.starts_with("$BLOBDIR"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_new_blob_file_unicode() {
|
||||||
|
let t = dummy_context();
|
||||||
|
let context = t.ctx;
|
||||||
|
let s = "helloäworld.qwe";
|
||||||
|
let x = &context.new_blob_file(s, b"data").unwrap();
|
||||||
|
assert_eq!(x, "$BLOBDIR/hello-world.qwe");
|
||||||
|
assert_eq!(dc_read_file(&context, x).unwrap(), b"data");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sqlite_parent_not_exists() {
|
fn test_sqlite_parent_not_exists() {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
@@ -475,6 +630,15 @@ mod tests {
|
|||||||
assert!(dbfile2.is_file());
|
assert!(dbfile2.is_file());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_with_empty_blobdir() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
let dbfile = tmp.path().join("db.sqlite");
|
||||||
|
let blobdir = PathBuf::new();
|
||||||
|
let res = Context::with_blobdir(Box::new(|_, _| 0), "FakeOS".into(), dbfile, blobdir);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_with_blobdir_not_exists() {
|
fn test_with_blobdir_not_exists() {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
@@ -495,6 +659,14 @@ mod tests {
|
|||||||
let t = dummy_context();
|
let t = dummy_context();
|
||||||
|
|
||||||
let info = t.ctx.get_info();
|
let info = t.ctx.get_info();
|
||||||
|
assert!(info.get("database_dir").is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_info_no_context() {
|
||||||
|
let info = get_info();
|
||||||
|
assert!(info.get("deltachat_core_version").is_some());
|
||||||
|
assert!(info.get("database_dir").is_none());
|
||||||
assert_eq!(info.get("level").unwrap(), "awesome");
|
assert_eq!(info.get("level").unwrap(), "awesome");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1089
src/dc_imex.rs
1089
src/dc_imex.rs
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1496
src/dc_mimeparser.rs
1496
src/dc_mimeparser.rs
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -10,18 +10,16 @@ pub struct Simplify {
|
|||||||
///
|
///
|
||||||
/// Also return whether not-standard (rfc3676, §4.3) footer is found.
|
/// Also return whether not-standard (rfc3676, §4.3) footer is found.
|
||||||
fn find_message_footer(lines: &[&str]) -> (usize, bool) {
|
fn find_message_footer(lines: &[&str]) -> (usize, bool) {
|
||||||
for ix in 0..lines.len() {
|
for (ix, &line) in lines.iter().enumerate() {
|
||||||
let line = lines[ix];
|
|
||||||
|
|
||||||
// quoted-printable may encode `-- ` to `-- =20` which is converted
|
// quoted-printable may encode `-- ` to `-- =20` which is converted
|
||||||
// back to `-- `
|
// back to `-- `
|
||||||
match line.as_ref() {
|
match line {
|
||||||
"-- " | "-- " => return (ix, false),
|
"-- " | "-- " => return (ix, false),
|
||||||
"--" | "---" | "----" => return (ix, true),
|
"--" | "---" | "----" => return (ix, true),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (lines.len(), false);
|
(lines.len(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Simplify {
|
impl Simplify {
|
||||||
@@ -103,10 +101,8 @@ impl Simplify {
|
|||||||
if let Some(last_quoted_line) = l_lastQuotedLine {
|
if let Some(last_quoted_line) = l_lastQuotedLine {
|
||||||
l_last = last_quoted_line;
|
l_last = last_quoted_line;
|
||||||
is_cut_at_end = true;
|
is_cut_at_end = true;
|
||||||
if l_last > 1 {
|
if l_last > 1 && is_empty_line(lines[l_last - 1]) {
|
||||||
if is_empty_line(lines[l_last - 1]) {
|
l_last -= 1
|
||||||
l_last -= 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if l_last > 1 {
|
if l_last > 1 {
|
||||||
let line = lines[l_last - 1];
|
let line = lines[l_last - 1];
|
||||||
@@ -146,8 +142,8 @@ impl Simplify {
|
|||||||
ret += "[...]";
|
ret += "[...]";
|
||||||
}
|
}
|
||||||
/* we write empty lines only in case and non-empty line follows */
|
/* we write empty lines only in case and non-empty line follows */
|
||||||
let mut pending_linebreaks: libc::c_int = 0i32;
|
let mut pending_linebreaks = 0;
|
||||||
let mut content_lines_added: libc::c_int = 0i32;
|
let mut content_lines_added = 0;
|
||||||
for l in l_first..l_last {
|
for l in l_first..l_last {
|
||||||
let line = lines[l];
|
let line = lines[l];
|
||||||
if is_empty_line(line) {
|
if is_empty_line(line) {
|
||||||
@@ -205,7 +201,7 @@ fn is_quoted_headline(buf: &str) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_plain_quote(buf: &str) -> bool {
|
fn is_plain_quote(buf: &str) -> bool {
|
||||||
buf.starts_with(">")
|
buf.starts_with('>')
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
|
use itertools::Itertools;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
use charset::Charset;
|
use charset::Charset;
|
||||||
use mmime::mailmime_decode::*;
|
use libc::free;
|
||||||
use mmime::mmapstring::*;
|
use mmime::mailmime::decode::mailmime_encoded_phrase_parse;
|
||||||
use mmime::other::*;
|
use mmime::other::*;
|
||||||
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
|
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
|
||||||
|
|
||||||
use crate::dc_tools::*;
|
use crate::dc_tools::*;
|
||||||
use crate::x::*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode non-ascii-strings as `=?UTF-8?Q?Bj=c3=b6rn_Petersen?=`.
|
* Encode non-ascii-strings as `=?UTF-8?Q?Bj=c3=b6rn_Petersen?=`.
|
||||||
@@ -25,222 +25,50 @@ use crate::x::*;
|
|||||||
* @return Returns the encoded string which must be free()'d when no longed needed.
|
* @return Returns the encoded string which must be free()'d when no longed needed.
|
||||||
* On errors, NULL is returned.
|
* On errors, NULL is returned.
|
||||||
*/
|
*/
|
||||||
pub unsafe fn dc_encode_header_words(to_encode_r: impl AsRef<str>) -> *mut libc::c_char {
|
pub fn dc_encode_header_words(input: impl AsRef<str>) -> String {
|
||||||
let to_encode =
|
let mut result = String::default();
|
||||||
CString::new(to_encode_r.as_ref().as_bytes()).expect("invalid cstring to_encode");
|
for (_, group) in &input.as_ref().chars().group_by(|c| c.is_whitespace()) {
|
||||||
|
let word: String = group.collect();
|
||||||
let mut ok_to_continue = true;
|
result.push_str("e_word(&word.as_bytes()));
|
||||||
let mut ret_str: *mut libc::c_char = ptr::null_mut();
|
|
||||||
let mut cur: *const libc::c_char = to_encode.as_ptr();
|
|
||||||
let mmapstr: *mut MMAPString = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
|
|
||||||
if mmapstr.is_null() {
|
|
||||||
ok_to_continue = false;
|
|
||||||
}
|
|
||||||
loop {
|
|
||||||
if !ok_to_continue {
|
|
||||||
if !mmapstr.is_null() {
|
|
||||||
mmap_string_free(mmapstr);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
if *cur as libc::c_int != '\u{0}' as i32 {
|
|
||||||
let begin: *const libc::c_char;
|
|
||||||
let mut end: *const libc::c_char;
|
|
||||||
let mut do_quote: bool;
|
|
||||||
let mut quote_words: libc::c_int;
|
|
||||||
begin = cur;
|
|
||||||
end = begin;
|
|
||||||
quote_words = 0i32;
|
|
||||||
do_quote = true;
|
|
||||||
while *cur as libc::c_int != '\u{0}' as i32 {
|
|
||||||
get_word(cur, &mut cur, &mut do_quote);
|
|
||||||
if !do_quote {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
quote_words = 1i32;
|
|
||||||
end = cur;
|
|
||||||
if *cur as libc::c_int != '\u{0}' as i32 {
|
|
||||||
cur = cur.offset(1isize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if 0 != quote_words {
|
|
||||||
if !quote_word(
|
|
||||||
b"utf-8\x00" as *const u8 as *const libc::c_char,
|
|
||||||
mmapstr,
|
|
||||||
begin,
|
|
||||||
end.wrapping_offset_from(begin) as libc::size_t,
|
|
||||||
) {
|
|
||||||
ok_to_continue = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if *end as libc::c_int == ' ' as i32 || *end as libc::c_int == '\t' as i32 {
|
|
||||||
if mmap_string_append_c(mmapstr, *end).is_null() {
|
|
||||||
ok_to_continue = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
end = end.offset(1isize)
|
|
||||||
}
|
|
||||||
if *end as libc::c_int != '\u{0}' as i32 {
|
|
||||||
if mmap_string_append_len(
|
|
||||||
mmapstr,
|
|
||||||
end,
|
|
||||||
cur.wrapping_offset_from(end) as libc::size_t,
|
|
||||||
)
|
|
||||||
.is_null()
|
|
||||||
{
|
|
||||||
ok_to_continue = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if mmap_string_append_len(
|
|
||||||
mmapstr,
|
|
||||||
begin,
|
|
||||||
cur.wrapping_offset_from(begin) as libc::size_t,
|
|
||||||
)
|
|
||||||
.is_null()
|
|
||||||
{
|
|
||||||
ok_to_continue = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if !(*cur as libc::c_int == ' ' as i32 || *cur as libc::c_int == '\t' as i32) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if mmap_string_append_c(mmapstr, *cur).is_null() {
|
|
||||||
ok_to_continue = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
cur = cur.offset(1isize);
|
|
||||||
} else {
|
|
||||||
ret_str = strdup((*mmapstr).str_0);
|
|
||||||
ok_to_continue = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ret_str
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn quote_word(
|
fn must_encode(byte: u8) -> bool {
|
||||||
display_charset: *const libc::c_char,
|
static SPECIALS: &[u8] = b",:!\"#$@[\\]^`{|}~=?_";
|
||||||
mmapstr: *mut MMAPString,
|
|
||||||
word: *const libc::c_char,
|
|
||||||
size: libc::size_t,
|
|
||||||
) -> bool {
|
|
||||||
let mut cur: *const libc::c_char;
|
|
||||||
let mut i = 0;
|
|
||||||
let mut hex: [libc::c_char; 4] = [0; 4];
|
|
||||||
// let mut col: libc::c_int = 0i32;
|
|
||||||
if mmap_string_append(mmapstr, b"=?\x00" as *const u8 as *const libc::c_char).is_null() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if mmap_string_append(mmapstr, display_charset).is_null() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if mmap_string_append(mmapstr, b"?Q?\x00" as *const u8 as *const libc::c_char).is_null() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// col = (*mmapstr).len as libc::c_int;
|
|
||||||
cur = word;
|
|
||||||
while i < size {
|
|
||||||
let mut do_quote_char = false;
|
|
||||||
match *cur as u8 as char {
|
|
||||||
',' | ':' | '!' | '"' | '#' | '$' | '@' | '[' | '\\' | ']' | '^' | '`' | '{' | '|'
|
|
||||||
| '}' | '~' | '=' | '?' | '_' => do_quote_char = true,
|
|
||||||
_ => {
|
|
||||||
if *cur as u8 >= 128 {
|
|
||||||
do_quote_char = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if do_quote_char {
|
|
||||||
print_hex(hex.as_mut_ptr(), cur);
|
|
||||||
if mmap_string_append(mmapstr, hex.as_mut_ptr()).is_null() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// col += 3i32
|
|
||||||
} else {
|
|
||||||
if *cur as libc::c_int == ' ' as i32 {
|
|
||||||
if mmap_string_append_c(mmapstr, '_' as i32 as libc::c_char).is_null() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if mmap_string_append_c(mmapstr, *cur).is_null() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// col += 3i32
|
|
||||||
}
|
|
||||||
cur = cur.offset(1isize);
|
|
||||||
i = i.wrapping_add(1)
|
|
||||||
}
|
|
||||||
if mmap_string_append(mmapstr, b"?=\x00" as *const u8 as *const libc::c_char).is_null() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
SPECIALS.into_iter().any(|b| *b == byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn get_word(
|
fn quote_word(word: &[u8]) -> String {
|
||||||
begin: *const libc::c_char,
|
let mut result = String::default();
|
||||||
pend: *mut *const libc::c_char,
|
let mut encoded = false;
|
||||||
pto_be_quoted: *mut bool,
|
|
||||||
) {
|
for byte in word {
|
||||||
let mut cur: *const libc::c_char = begin;
|
let byte = *byte;
|
||||||
while *cur as libc::c_int != ' ' as i32
|
if byte >= 128 || must_encode(byte) {
|
||||||
&& *cur as libc::c_int != '\t' as i32
|
result.push_str(&format!("={:2X}", byte));
|
||||||
&& *cur as libc::c_int != '\u{0}' as i32
|
encoded = true;
|
||||||
{
|
} else if byte == b' ' {
|
||||||
cur = cur.offset(1isize)
|
result.push('_');
|
||||||
|
encoded = true;
|
||||||
|
} else {
|
||||||
|
result.push(byte as _);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
*pto_be_quoted = to_be_quoted(begin, cur.wrapping_offset_from(begin) as libc::size_t);
|
|
||||||
*pend = cur;
|
if encoded {
|
||||||
|
result = format!("=?utf-8?Q?{}?=", &result);
|
||||||
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ******************************************************************************
|
/* ******************************************************************************
|
||||||
* Encode/decode header words, RFC 2047
|
* Encode/decode header words, RFC 2047
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
/* see comment below */
|
pub(crate) fn dc_decode_header_words(input: &str) -> String {
|
||||||
unsafe fn to_be_quoted(word: *const libc::c_char, size: libc::size_t) -> bool {
|
|
||||||
let mut cur: *const libc::c_char = word;
|
|
||||||
let mut i = 0;
|
|
||||||
while i < size {
|
|
||||||
match *cur as libc::c_int {
|
|
||||||
44 | 58 | 33 | 34 | 35 | 36 | 64 | 91 | 92 | 93 | 94 | 96 | 123 | 124 | 125 | 126
|
|
||||||
| 61 | 63 | 95 => return true,
|
|
||||||
_ => {
|
|
||||||
if *cur as libc::c_uchar as libc::c_int >= 128i32 {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cur = cur.offset(1isize);
|
|
||||||
i = i.wrapping_add(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_char {
|
|
||||||
if in_0.is_null() {
|
|
||||||
return ptr::null_mut();
|
|
||||||
}
|
|
||||||
let mut out: *mut libc::c_char = ptr::null_mut();
|
|
||||||
let mut cur_token = 0;
|
|
||||||
let r: libc::c_int = mailmime_encoded_phrase_parse(
|
|
||||||
b"iso-8859-1\x00" as *const u8 as *const libc::c_char,
|
|
||||||
in_0,
|
|
||||||
strlen(in_0),
|
|
||||||
&mut cur_token,
|
|
||||||
b"utf-8\x00" as *const u8 as *const libc::c_char,
|
|
||||||
&mut out,
|
|
||||||
);
|
|
||||||
if r != MAILIMF_NO_ERROR as libc::c_int || out.is_null() {
|
|
||||||
out = dc_strdup(in_0)
|
|
||||||
}
|
|
||||||
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dc_decode_header_words_safe(input: &str) -> String {
|
|
||||||
static FROM_ENCODING: &[u8] = b"iso-8859-1\x00";
|
static FROM_ENCODING: &[u8] = b"iso-8859-1\x00";
|
||||||
static TO_ENCODING: &[u8] = b"utf-8\x00";
|
static TO_ENCODING: &[u8] = b"utf-8\x00";
|
||||||
let mut out = ptr::null_mut();
|
let mut out = ptr::null_mut();
|
||||||
@@ -258,7 +86,7 @@ pub fn dc_decode_header_words_safe(input: &str) -> String {
|
|||||||
if r as u32 != MAILIMF_NO_ERROR || out.is_null() {
|
if r as u32 != MAILIMF_NO_ERROR || out.is_null() {
|
||||||
input.to_string()
|
input.to_string()
|
||||||
} else {
|
} else {
|
||||||
let res = to_string(out);
|
let res = to_string_lossy(out);
|
||||||
free(out.cast());
|
free(out.cast());
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
@@ -324,77 +152,35 @@ pub fn dc_decode_ext_header(to_decode: &[u8]) -> Cow<str> {
|
|||||||
String::from_utf8_lossy(to_decode)
|
String::from_utf8_lossy(to_decode)
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) {
|
|
||||||
assert!(!target.is_null());
|
|
||||||
assert!(!cur.is_null());
|
|
||||||
|
|
||||||
let bytes = std::slice::from_raw_parts(cur as *const _, strlen(cur));
|
|
||||||
let raw = CString::yolo(format!("={}", &hex::encode_upper(bytes)[..2]));
|
|
||||||
libc::memcpy(target as *mut _, raw.as_ptr() as *const _, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::ffi::CStr;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_dc_decode_header_words() {
|
fn test_dc_decode_header_words() {
|
||||||
unsafe {
|
assert_eq!(
|
||||||
let mut buf1: *mut libc::c_char = dc_decode_header_words(
|
dc_decode_header_words("=?utf-8?B?dGVzdMOkw7bDvC50eHQ=?="),
|
||||||
b"=?utf-8?B?dGVzdMOkw7bDvC50eHQ=?=\x00" as *const u8 as *const libc::c_char,
|
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
|
||||||
strcmp(
|
|
||||||
buf1,
|
|
||||||
b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\x00" as *const u8 as *const libc::c_char
|
|
||||||
),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
free(buf1 as *mut libc::c_void);
|
|
||||||
|
|
||||||
buf1 =
|
assert_eq!(dc_decode_header_words("just ascii test"), "just ascii test");
|
||||||
dc_decode_header_words(b"just ascii test\x00" as *const u8 as *const libc::c_char);
|
|
||||||
assert_eq!(CStr::from_ptr(buf1).to_str().unwrap(), "just ascii test");
|
|
||||||
free(buf1 as *mut libc::c_void);
|
|
||||||
|
|
||||||
buf1 = dc_encode_header_words("abcdef");
|
assert_eq!(dc_encode_header_words("abcdef"), "abcdef");
|
||||||
assert_eq!(CStr::from_ptr(buf1).to_str().unwrap(), "abcdef");
|
|
||||||
free(buf1 as *mut libc::c_void);
|
|
||||||
|
|
||||||
buf1 = dc_encode_header_words(
|
let r = dc_encode_header_words(
|
||||||
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec())
|
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
|
||||||
.unwrap(),
|
);
|
||||||
);
|
assert!(r.starts_with("=?utf-8"));
|
||||||
assert_eq!(
|
|
||||||
strncmp(buf1, b"=?utf-8\x00" as *const u8 as *const libc::c_char, 7),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
let buf2: *mut libc::c_char = dc_decode_header_words(buf1);
|
assert_eq!(
|
||||||
assert_eq!(
|
dc_decode_header_words(&r),
|
||||||
strcmp(
|
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
|
||||||
buf2,
|
);
|
||||||
b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\x00" as *const u8 as *const libc::c_char
|
|
||||||
),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
free(buf1 as *mut libc::c_void);
|
|
||||||
free(buf2 as *mut libc::c_void);
|
|
||||||
|
|
||||||
buf1 = dc_decode_header_words(
|
assert_eq!(
|
||||||
b"=?ISO-8859-1?Q?attachment=3B=0D=0A_filename=3D?= =?ISO-8859-1?Q?=22test=E4=F6=FC=2Etxt=22=3B=0D=0A_size=3D39?=\x00" as *const u8 as *const libc::c_char
|
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(),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
|
||||||
strcmp(
|
|
||||||
buf1,
|
|
||||||
b"attachment;\r\n filename=\"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\";\r\n size=39\x00" as *const u8 as *const libc::c_char,
|
|
||||||
|
|
||||||
),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
free(buf1 as *mut libc::c_void);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -438,18 +224,6 @@ mod tests {
|
|||||||
assert_eq!(dc_needs_ext_header("a b"), true);
|
assert_eq!(dc_needs_ext_header("a b"), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_print_hex() {
|
|
||||||
let mut hex: [libc::c_char; 4] = [0; 4];
|
|
||||||
let cur = b"helloworld" as *const u8 as *const libc::c_char;
|
|
||||||
unsafe { print_hex(hex.as_mut_ptr(), cur) };
|
|
||||||
assert_eq!(to_string(hex.as_ptr() as *const _), "=68");
|
|
||||||
|
|
||||||
let cur = b":" as *const u8 as *const libc::c_char;
|
|
||||||
unsafe { print_hex(hex.as_mut_ptr(), cur) };
|
|
||||||
assert_eq!(to_string(hex.as_ptr() as *const _), "=3A");
|
|
||||||
}
|
|
||||||
|
|
||||||
use proptest::prelude::*;
|
use proptest::prelude::*;
|
||||||
|
|
||||||
proptest! {
|
proptest! {
|
||||||
@@ -465,5 +239,13 @@ mod tests {
|
|||||||
// make sure this never panics
|
// make sure this never panics
|
||||||
let _decoded = dc_decode_ext_header(&buf);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1106
src/dc_tools.rs
1106
src/dc_tools.rs
File diff suppressed because it is too large
Load Diff
1357
src/e2ee.rs
1357
src/e2ee.rs
File diff suppressed because it is too large
Load Diff
24
src/error.rs
24
src/error.rs
@@ -24,6 +24,12 @@ pub enum Error {
|
|||||||
Utf8(std::str::Utf8Error),
|
Utf8(std::str::Utf8Error),
|
||||||
#[fail(display = "{:?}", _0)]
|
#[fail(display = "{:?}", _0)]
|
||||||
CStringError(crate::dc_tools::CStringError),
|
CStringError(crate::dc_tools::CStringError),
|
||||||
|
#[fail(display = "PGP: {:?}", _0)]
|
||||||
|
Pgp(pgp::errors::Error),
|
||||||
|
#[fail(display = "Base64Decode: {:?}", _0)]
|
||||||
|
Base64Decode(base64::DecodeError),
|
||||||
|
#[fail(display = "{:?}", _0)]
|
||||||
|
FromUtf8(std::string::FromUtf8Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
@@ -34,6 +40,12 @@ impl From<rusqlite::Error> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<base64::DecodeError> for Error {
|
||||||
|
fn from(err: base64::DecodeError) -> Error {
|
||||||
|
Error::Base64Decode(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<failure::Error> for Error {
|
impl From<failure::Error> for Error {
|
||||||
fn from(err: failure::Error) -> Error {
|
fn from(err: failure::Error) -> Error {
|
||||||
Error::Failure(err)
|
Error::Failure(err)
|
||||||
@@ -70,6 +82,18 @@ impl From<crate::dc_tools::CStringError> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<pgp::errors::Error> for Error {
|
||||||
|
fn from(err: pgp::errors::Error) -> Error {
|
||||||
|
Error::Pgp(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::string::FromUtf8Error> for Error {
|
||||||
|
fn from(err: std::string::FromUtf8Error) -> Error {
|
||||||
|
Error::FromUtf8(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! bail {
|
macro_rules! bail {
|
||||||
($e:expr) => {
|
($e:expr) => {
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use strum::EnumProperty;
|
use strum::EnumProperty;
|
||||||
|
|
||||||
use crate::stock::StockMessage;
|
|
||||||
|
|
||||||
impl Event {
|
impl Event {
|
||||||
/// Returns the corresponding Event id.
|
/// Returns the corresponding Event id.
|
||||||
pub fn as_id(&self) -> i32 {
|
pub fn as_id(&self) -> i32 {
|
||||||
@@ -42,6 +40,30 @@ pub enum Event {
|
|||||||
#[strum(props(id = "103"))]
|
#[strum(props(id = "103"))]
|
||||||
SmtpMessageSent(String),
|
SmtpMessageSent(String),
|
||||||
|
|
||||||
|
/// Emitted when an IMAP message has been marked as deleted
|
||||||
|
///
|
||||||
|
/// @return 0
|
||||||
|
#[strum(props(id = "104"))]
|
||||||
|
ImapMessageDeleted(String),
|
||||||
|
|
||||||
|
/// Emitted when an IMAP message has been moved
|
||||||
|
///
|
||||||
|
/// @return 0
|
||||||
|
#[strum(props(id = "105"))]
|
||||||
|
ImapMessageMoved(String),
|
||||||
|
|
||||||
|
/// Emitted when an new file in the $BLOBDIR was created
|
||||||
|
///
|
||||||
|
/// @return 0
|
||||||
|
#[strum(props(id = "150"))]
|
||||||
|
NewBlobFile(String),
|
||||||
|
|
||||||
|
/// Emitted when an new file in the $BLOBDIR was created
|
||||||
|
///
|
||||||
|
/// @return 0
|
||||||
|
#[strum(props(id = "151"))]
|
||||||
|
DeletedBlobFile(String),
|
||||||
|
|
||||||
/// The library-user should write a warning string to the log.
|
/// The library-user should write a warning string to the log.
|
||||||
/// Passed to the callback given to dc_context_new().
|
/// Passed to the callback given to dc_context_new().
|
||||||
///
|
///
|
||||||
@@ -167,7 +189,7 @@ pub enum Event {
|
|||||||
#[strum(props(id = "2041"))]
|
#[strum(props(id = "2041"))]
|
||||||
ConfigureProgress(usize),
|
ConfigureProgress(usize),
|
||||||
|
|
||||||
/// Inform about the import/export progress started by dc_imex().
|
/// Inform about the import/export progress started by imex().
|
||||||
///
|
///
|
||||||
/// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
|
/// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
|
||||||
/// @param data2 0
|
/// @param data2 0
|
||||||
@@ -175,8 +197,8 @@ pub enum Event {
|
|||||||
#[strum(props(id = "2051"))]
|
#[strum(props(id = "2051"))]
|
||||||
ImexProgress(usize),
|
ImexProgress(usize),
|
||||||
|
|
||||||
/// A file has been exported. A file has been written by dc_imex().
|
/// A file has been exported. A file has been written by imex().
|
||||||
/// This event may be sent multiple times by a single call to dc_imex().
|
/// This event may be sent multiple times by a single call to imex().
|
||||||
///
|
///
|
||||||
/// A typical purpose for a handler of this event may be to make the file public to some system
|
/// A typical purpose for a handler of this event may be to make the file public to some system
|
||||||
/// services.
|
/// services.
|
||||||
@@ -213,17 +235,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 },
|
||||||
|
|
||||||
// the following events are functions that should be provided by the frontends
|
|
||||||
/// Requeste a localized string from the frontend.
|
|
||||||
/// @param data1 (int) ID of the string to request, one of the DC_STR_/// constants.
|
|
||||||
/// @param data2 (int) The count. If the requested string contains a placeholder for a numeric value,
|
|
||||||
/// the ui may use this value to return different strings on different plural forms.
|
|
||||||
/// @return (const char*) Null-terminated UTF-8 string.
|
|
||||||
/// The string will be free()'d by the core,
|
|
||||||
/// so it must be allocated using malloc() or a compatible function.
|
|
||||||
/// Return 0 if the ui cannot provide the requested string
|
|
||||||
/// the core will use a default string in english language then.
|
|
||||||
#[strum(props(id = "2091"))]
|
|
||||||
GetString { id: StockMessage, count: usize },
|
|
||||||
}
|
}
|
||||||
|
|||||||
704
src/imap.rs
704
src/imap.rs
@@ -1,6 +1,4 @@
|
|||||||
use std::ffi::CString;
|
|
||||||
use std::net;
|
use std::net;
|
||||||
use std::ptr;
|
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
Arc, Condvar, Mutex, RwLock,
|
Arc, Condvar, Mutex, RwLock,
|
||||||
@@ -10,26 +8,27 @@ use std::time::{Duration, SystemTime};
|
|||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::dc_receive_imf::dc_receive_imf;
|
use crate::dc_receive_imf::dc_receive_imf;
|
||||||
use crate::dc_tools::CStringExt;
|
use crate::error::Error;
|
||||||
use crate::dc_tools::*;
|
|
||||||
use crate::events::Event;
|
use crate::events::Event;
|
||||||
use crate::job::{job_add, Action};
|
use crate::job::{connect_to_inbox, job_add, Action};
|
||||||
use crate::login_param::LoginParam;
|
use crate::login_param::{dc_build_tls, CertificateChecks, LoginParam};
|
||||||
use crate::message::{dc_rfc724_mid_exists, dc_update_msg_move_state, dc_update_server_uid};
|
use crate::message::{self, update_msg_move_state, update_server_uid};
|
||||||
use crate::oauth2::dc_get_oauth2_access_token;
|
use crate::oauth2::dc_get_oauth2_access_token;
|
||||||
use crate::param::Params;
|
use crate::param::Params;
|
||||||
|
use crate::wrapmime;
|
||||||
|
|
||||||
const DC_IMAP_SEEN: usize = 0x0001;
|
const DC_IMAP_SEEN: usize = 0x0001;
|
||||||
const DC_REGENERATE: usize = 0x01;
|
|
||||||
|
|
||||||
const DC_SUCCESS: usize = 3;
|
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq)]
|
||||||
const DC_ALREADY_DONE: usize = 2;
|
pub enum ImapResult {
|
||||||
const DC_RETRY_LATER: usize = 1;
|
Failed,
|
||||||
const DC_FAILED: usize = 0;
|
RetryLater,
|
||||||
|
AlreadyDone,
|
||||||
|
Success,
|
||||||
|
}
|
||||||
|
|
||||||
const PREFETCH_FLAGS: &str = "(UID ENVELOPE)";
|
const PREFETCH_FLAGS: &str = "(UID ENVELOPE)";
|
||||||
const BODY_FLAGS: &str = "(FLAGS BODY.PEEK[])";
|
const BODY_FLAGS: &str = "(FLAGS BODY.PEEK[])";
|
||||||
const FETCH_FLAGS: &str = "(FLAGS)";
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Imap {
|
pub struct Imap {
|
||||||
@@ -109,18 +108,16 @@ impl Client {
|
|||||||
pub fn connect_secure<A: net::ToSocketAddrs, S: AsRef<str>>(
|
pub fn connect_secure<A: net::ToSocketAddrs, S: AsRef<str>>(
|
||||||
addr: A,
|
addr: A,
|
||||||
domain: S,
|
domain: S,
|
||||||
|
certificate_checks: CertificateChecks,
|
||||||
) -> imap::error::Result<Self> {
|
) -> imap::error::Result<Self> {
|
||||||
let stream = net::TcpStream::connect(addr)?;
|
let stream = net::TcpStream::connect(addr)?;
|
||||||
let tls = native_tls::TlsConnector::builder()
|
let tls = dc_build_tls(certificate_checks).unwrap();
|
||||||
.danger_accept_invalid_hostnames(true)
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let s = stream.try_clone().expect("cloning the stream failed");
|
let s = stream.try_clone().expect("cloning the stream failed");
|
||||||
let tls_stream = native_tls::TlsConnector::connect(&tls, domain.as_ref(), s)?;
|
let tls_stream = native_tls::TlsConnector::connect(&tls, domain.as_ref(), s)?;
|
||||||
|
|
||||||
let client = imap::Client::new(tls_stream);
|
let mut client = imap::Client::new(tls_stream);
|
||||||
// TODO: Read greeting
|
client.read_greeting()?;
|
||||||
|
|
||||||
Ok(Client::Secure(client, stream))
|
Ok(Client::Secure(client, stream))
|
||||||
}
|
}
|
||||||
@@ -128,19 +125,20 @@ impl Client {
|
|||||||
pub fn connect_insecure<A: net::ToSocketAddrs>(addr: A) -> imap::error::Result<Self> {
|
pub fn connect_insecure<A: net::ToSocketAddrs>(addr: A) -> imap::error::Result<Self> {
|
||||||
let stream = net::TcpStream::connect(addr)?;
|
let stream = net::TcpStream::connect(addr)?;
|
||||||
|
|
||||||
let client = imap::Client::new(stream.try_clone().unwrap());
|
let mut client = imap::Client::new(stream.try_clone().unwrap());
|
||||||
// TODO: Read greeting
|
client.read_greeting()?;
|
||||||
|
|
||||||
Ok(Client::Insecure(client, stream))
|
Ok(Client::Insecure(client, stream))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn secure<S: AsRef<str>>(self, domain: S) -> imap::error::Result<Client> {
|
pub fn secure<S: AsRef<str>>(
|
||||||
|
self,
|
||||||
|
domain: S,
|
||||||
|
certificate_checks: CertificateChecks,
|
||||||
|
) -> imap::error::Result<Client> {
|
||||||
match self {
|
match self {
|
||||||
Client::Insecure(client, stream) => {
|
Client::Insecure(client, stream) => {
|
||||||
let tls = native_tls::TlsConnector::builder()
|
let tls = dc_build_tls(certificate_checks).unwrap();
|
||||||
.danger_accept_invalid_hostnames(true)
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let client_sec = client.secure(domain, &tls)?;
|
let client_sec = client.secure(domain, &tls)?;
|
||||||
|
|
||||||
@@ -320,6 +318,7 @@ struct ImapConfig {
|
|||||||
pub imap_port: u16,
|
pub imap_port: u16,
|
||||||
pub imap_user: String,
|
pub imap_user: String,
|
||||||
pub imap_pw: String,
|
pub imap_pw: String,
|
||||||
|
pub certificate_checks: CertificateChecks,
|
||||||
pub server_flags: usize,
|
pub server_flags: usize,
|
||||||
pub selected_folder: Option<String>,
|
pub selected_folder: Option<String>,
|
||||||
pub selected_mailbox: Option<imap::types::Mailbox>,
|
pub selected_mailbox: Option<imap::types::Mailbox>,
|
||||||
@@ -332,12 +331,13 @@ struct ImapConfig {
|
|||||||
|
|
||||||
impl Default for ImapConfig {
|
impl Default for ImapConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let cfg = ImapConfig {
|
ImapConfig {
|
||||||
addr: "".into(),
|
addr: "".into(),
|
||||||
imap_server: "".into(),
|
imap_server: "".into(),
|
||||||
imap_port: 0,
|
imap_port: 0,
|
||||||
imap_user: "".into(),
|
imap_user: "".into(),
|
||||||
imap_pw: "".into(),
|
imap_pw: "".into(),
|
||||||
|
certificate_checks: Default::default(),
|
||||||
server_flags: 0,
|
server_flags: 0,
|
||||||
selected_folder: None,
|
selected_folder: None,
|
||||||
selected_mailbox: None,
|
selected_mailbox: None,
|
||||||
@@ -346,9 +346,7 @@ impl Default for ImapConfig {
|
|||||||
has_xlist: false,
|
has_xlist: false,
|
||||||
imap_delimiter: '.',
|
imap_delimiter: '.',
|
||||||
watch_folder: None,
|
watch_folder: None,
|
||||||
};
|
}
|
||||||
|
|
||||||
cfg
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,7 +384,7 @@ impl Imap {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let server_flags = self.config.read().unwrap().server_flags;
|
let server_flags = self.config.read().unwrap().server_flags as i32;
|
||||||
|
|
||||||
let connection_res: imap::error::Result<Client> =
|
let connection_res: imap::error::Result<Client> =
|
||||||
if (server_flags & (DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_PLAIN)) != 0 {
|
if (server_flags & (DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_PLAIN)) != 0 {
|
||||||
@@ -396,7 +394,7 @@ impl Imap {
|
|||||||
|
|
||||||
Client::connect_insecure((imap_server, imap_port)).and_then(|client| {
|
Client::connect_insecure((imap_server, imap_port)).and_then(|client| {
|
||||||
if (server_flags & DC_LP_IMAP_SOCKET_STARTTLS) != 0 {
|
if (server_flags & DC_LP_IMAP_SOCKET_STARTTLS) != 0 {
|
||||||
client.secure(imap_server)
|
client.secure(imap_server, config.certificate_checks)
|
||||||
} else {
|
} else {
|
||||||
Ok(client)
|
Ok(client)
|
||||||
}
|
}
|
||||||
@@ -406,7 +404,11 @@ impl Imap {
|
|||||||
let imap_server: &str = config.imap_server.as_ref();
|
let imap_server: &str = config.imap_server.as_ref();
|
||||||
let imap_port = config.imap_port;
|
let imap_port = config.imap_port;
|
||||||
|
|
||||||
Client::connect_secure((imap_server, imap_port), imap_server)
|
Client::connect_secure(
|
||||||
|
(imap_server, imap_port),
|
||||||
|
imap_server,
|
||||||
|
config.certificate_checks,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let login_res = match connection_res {
|
let login_res = match connection_res {
|
||||||
@@ -418,9 +420,7 @@ impl Imap {
|
|||||||
if (server_flags & DC_LP_AUTH_OAUTH2) != 0 {
|
if (server_flags & DC_LP_AUTH_OAUTH2) != 0 {
|
||||||
let addr: &str = config.addr.as_ref();
|
let addr: &str = config.addr.as_ref();
|
||||||
|
|
||||||
if let Some(token) =
|
if let Some(token) = dc_get_oauth2_access_token(context, addr, imap_pw, true) {
|
||||||
dc_get_oauth2_access_token(context, addr, imap_pw, DC_REGENERATE as usize)
|
|
||||||
{
|
|
||||||
let auth = OAuth2 {
|
let auth = OAuth2 {
|
||||||
user: imap_user.into(),
|
user: imap_user.into(),
|
||||||
access_token: token,
|
access_token: token,
|
||||||
@@ -535,6 +535,7 @@ impl Imap {
|
|||||||
config.imap_port = imap_port;
|
config.imap_port = imap_port;
|
||||||
config.imap_user = imap_user.to_string();
|
config.imap_user = imap_user.to_string();
|
||||||
config.imap_pw = imap_pw.to_string();
|
config.imap_pw = imap_pw.to_string();
|
||||||
|
config.certificate_checks = lp.imap_certificate_checks;
|
||||||
config.server_flags = server_flags;
|
config.server_flags = server_flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,9 +598,9 @@ impl Imap {
|
|||||||
self.config.write().unwrap().watch_folder = Some(watch_folder);
|
self.config.write().unwrap().watch_folder = Some(watch_folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch(&self, context: &Context) -> libc::c_int {
|
pub fn fetch(&self, context: &Context) -> bool {
|
||||||
if !self.is_connected() || !context.sql.is_open() {
|
if !self.is_connected() || !context.sql.is_open() {
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.setup_handle_if_needed(context);
|
self.setup_handle_if_needed(context);
|
||||||
@@ -615,9 +616,9 @@ impl Imap {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1
|
true
|
||||||
} else {
|
} else {
|
||||||
0
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,7 +684,7 @@ impl Imap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
unreachable!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,12 +693,20 @@ impl Imap {
|
|||||||
|
|
||||||
fn get_config_last_seen_uid<S: AsRef<str>>(&self, context: &Context, folder: S) -> (u32, u32) {
|
fn get_config_last_seen_uid<S: AsRef<str>>(&self, context: &Context, folder: S) -> (u32, u32) {
|
||||||
let key = format!("imap.mailbox.{}", folder.as_ref());
|
let key = format!("imap.mailbox.{}", folder.as_ref());
|
||||||
if let Some(entry) = context.sql.get_config(context, &key) {
|
if let Some(entry) = context.sql.get_raw_config(context, &key) {
|
||||||
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
|
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
|
||||||
let mut parts = entry.split(':');
|
let mut parts = entry.split(':');
|
||||||
(
|
(
|
||||||
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
|
parts
|
||||||
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
|
.next()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.parse()
|
||||||
|
.unwrap_or_else(|_| 0),
|
||||||
|
parts
|
||||||
|
.next()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.parse()
|
||||||
|
.unwrap_or_else(|_| 0),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(0, 0)
|
(0, 0)
|
||||||
@@ -741,7 +750,7 @@ impl Imap {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if mailbox.uid_validity.unwrap() != uid_validity {
|
if mailbox.uid_validity.unwrap_or_default() != uid_validity {
|
||||||
// first time this folder is selected or UIDVALIDITY has changed, init lastseenuid and save it to config
|
// first time this folder is selected or UIDVALIDITY has changed, init lastseenuid and save it to config
|
||||||
|
|
||||||
if mailbox.exists == 0 {
|
if mailbox.exists == 0 {
|
||||||
@@ -751,7 +760,12 @@ impl Imap {
|
|||||||
// id we do not do this here, we'll miss the first message
|
// id we do not do this here, we'll miss the first message
|
||||||
// as we will get in here again and fetch from lastseenuid+1 then
|
// as we will get in here again and fetch from lastseenuid+1 then
|
||||||
|
|
||||||
self.set_config_last_seen_uid(context, &folder, mailbox.uid_validity.unwrap(), 0);
|
self.set_config_last_seen_uid(
|
||||||
|
context,
|
||||||
|
&folder,
|
||||||
|
mailbox.uid_validity.unwrap_or_default(),
|
||||||
|
0,
|
||||||
|
);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -782,7 +796,7 @@ impl Imap {
|
|||||||
last_seen_uid -= 1;
|
last_seen_uid -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
uid_validity = mailbox.uid_validity.unwrap();
|
uid_validity = mailbox.uid_validity.unwrap_or_default();
|
||||||
self.set_config_last_seen_uid(context, &folder, uid_validity, last_seen_uid);
|
self.set_config_last_seen_uid(context, &folder, uid_validity, last_seen_uid);
|
||||||
info!(
|
info!(
|
||||||
context,
|
context,
|
||||||
@@ -818,16 +832,9 @@ impl Imap {
|
|||||||
if cur_uid > last_seen_uid {
|
if cur_uid > last_seen_uid {
|
||||||
read_cnt += 1;
|
read_cnt += 1;
|
||||||
|
|
||||||
let message_id = msg
|
let message_id = prefetch_get_message_id(msg).unwrap_or_default();
|
||||||
.envelope()
|
|
||||||
.expect("missing envelope")
|
|
||||||
.message_id
|
|
||||||
.expect("missing message id");
|
|
||||||
|
|
||||||
if 0 == unsafe {
|
if !precheck_imf(context, &message_id, folder.as_ref(), cur_uid) {
|
||||||
let message_id_c = CString::yolo(message_id);
|
|
||||||
precheck_imf(context, message_id_c.as_ptr(), folder.as_ref(), cur_uid)
|
|
||||||
} {
|
|
||||||
// check passed, go fetch the rest
|
// check passed, go fetch the rest
|
||||||
if self.fetch_single_msg(context, &folder, cur_uid) == 0 {
|
if self.fetch_single_msg(context, &folder, cur_uid) == 0 {
|
||||||
info!(
|
info!(
|
||||||
@@ -890,7 +897,7 @@ impl Imap {
|
|||||||
let key = format!("imap.mailbox.{}", folder.as_ref());
|
let key = format!("imap.mailbox.{}", folder.as_ref());
|
||||||
let val = format!("{}:{}", uidvalidity, lastseenuid);
|
let val = format!("{}:{}", uidvalidity, lastseenuid);
|
||||||
|
|
||||||
context.sql.set_config(context, &key, Some(&val)).ok();
|
context.sql.set_raw_config(context, &key, Some(&val)).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_single_msg<S: AsRef<str>>(
|
fn fetch_single_msg<S: AsRef<str>>(
|
||||||
@@ -938,36 +945,22 @@ impl Imap {
|
|||||||
} else {
|
} else {
|
||||||
let msg = &msgs[0];
|
let msg = &msgs[0];
|
||||||
|
|
||||||
let is_deleted = msg
|
// XXX put flags into a set and pass them to dc_receive_imf
|
||||||
.flags()
|
let is_deleted = msg.flags().iter().any(|flag| match flag {
|
||||||
.iter()
|
imap::types::Flag::Deleted => true,
|
||||||
.find(|flag| match flag {
|
_ => false,
|
||||||
imap::types::Flag::Deleted => true,
|
});
|
||||||
_ => false,
|
let is_seen = msg.flags().iter().any(|flag| match flag {
|
||||||
})
|
imap::types::Flag::Seen => true,
|
||||||
.is_some();
|
_ => false,
|
||||||
let is_seen = msg
|
});
|
||||||
.flags()
|
|
||||||
.iter()
|
|
||||||
.find(|flag| match flag {
|
|
||||||
imap::types::Flag::Seen => true,
|
|
||||||
_ => false,
|
|
||||||
})
|
|
||||||
.is_some();
|
|
||||||
|
|
||||||
let flags = if is_seen { DC_IMAP_SEEN } else { 0 };
|
let flags = if is_seen { DC_IMAP_SEEN } else { 0 };
|
||||||
|
|
||||||
if !is_deleted && msg.body().is_some() {
|
if !is_deleted && msg.body().is_some() {
|
||||||
let body = msg.body().unwrap();
|
let body = msg.body().unwrap_or_default();
|
||||||
unsafe {
|
unsafe {
|
||||||
dc_receive_imf(
|
dc_receive_imf(context, &body, folder.as_ref(), server_uid, flags as u32);
|
||||||
context,
|
|
||||||
body.as_ptr() as *const libc::c_char,
|
|
||||||
body.len(),
|
|
||||||
folder.as_ref(),
|
|
||||||
server_uid,
|
|
||||||
flags as u32,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1032,7 +1025,7 @@ impl Imap {
|
|||||||
info!(context, "IMAP-IDLE has data.");
|
info!(context, "IMAP-IDLE has data.");
|
||||||
}
|
}
|
||||||
Err(err) => match err {
|
Err(err) => match err {
|
||||||
imap::error::Error::ConnectionLost => {
|
imap::error::Error::Io(_) | imap::error::Error::ConnectionLost => {
|
||||||
info!(context, "IMAP-IDLE wait cancelled, we will reconnect soon.");
|
info!(context, "IMAP-IDLE wait cancelled, we will reconnect soon.");
|
||||||
self.should_reconnect.store(true, Ordering::Relaxed);
|
self.should_reconnect.store(true, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
@@ -1076,13 +1069,14 @@ impl Imap {
|
|||||||
let mut do_fake_idle = true;
|
let mut do_fake_idle = true;
|
||||||
while do_fake_idle {
|
while do_fake_idle {
|
||||||
// wait a moment: every 5 seconds in the first 3 minutes after a new message, after that every 60 seconds.
|
// wait a moment: every 5 seconds in the first 3 minutes after a new message, after that every 60 seconds.
|
||||||
let seconds_to_wait =
|
let seconds_to_wait = if fake_idle_start_time.elapsed().unwrap_or_default()
|
||||||
if fake_idle_start_time.elapsed().unwrap() < Duration::new(3 * 60, 0) && !wait_long
|
< Duration::new(3 * 60, 0)
|
||||||
{
|
&& !wait_long
|
||||||
Duration::new(5, 0)
|
{
|
||||||
} else {
|
Duration::new(5, 0)
|
||||||
Duration::new(60, 0)
|
} else {
|
||||||
};
|
Duration::new(60, 0)
|
||||||
|
};
|
||||||
|
|
||||||
let &(ref lock, ref cvar) = &*self.watch.clone();
|
let &(ref lock, ref cvar) = &*self.watch.clone();
|
||||||
let mut watch = lock.lock().unwrap();
|
let mut watch = lock.lock().unwrap();
|
||||||
@@ -1131,117 +1125,94 @@ impl Imap {
|
|||||||
cvar.notify_one();
|
cvar.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mv<S1: AsRef<str>, S2: AsRef<str>>(
|
pub fn mv(
|
||||||
&self,
|
&self,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
folder: S1,
|
folder: &str,
|
||||||
uid: u32,
|
uid: u32,
|
||||||
dest_folder: S2,
|
dest_folder: &str,
|
||||||
dest_uid: &mut u32,
|
dest_uid: &mut u32,
|
||||||
) -> usize {
|
) -> ImapResult {
|
||||||
let mut res = DC_RETRY_LATER;
|
if folder == dest_folder {
|
||||||
|
info!(
|
||||||
|
context,
|
||||||
|
"Skip moving message; message {}/{} is already in {}...", folder, uid, dest_folder,
|
||||||
|
);
|
||||||
|
return ImapResult::AlreadyDone;
|
||||||
|
}
|
||||||
|
if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, uid) {
|
||||||
|
return imapresult;
|
||||||
|
}
|
||||||
|
// we are connected, and the folder is selected
|
||||||
|
|
||||||
|
// XXX Rust-Imap provides no target uid on mv, so just set it to 0
|
||||||
|
*dest_uid = 0;
|
||||||
|
|
||||||
let set = format!("{}", uid);
|
let set = format!("{}", uid);
|
||||||
|
let display_folder_id = format!("{}/{}", folder, uid);
|
||||||
if uid == 0 {
|
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||||
res = DC_FAILED;
|
match session.uid_mv(&set, &dest_folder) {
|
||||||
} else if folder.as_ref() == dest_folder.as_ref() {
|
Ok(_) => {
|
||||||
info!(
|
emit_event!(
|
||||||
context,
|
context,
|
||||||
"Skip moving message; message {}/{} is already in {}...",
|
Event::ImapMessageMoved(format!(
|
||||||
folder.as_ref(),
|
"IMAP Message {} moved to {}",
|
||||||
uid,
|
display_folder_id, dest_folder
|
||||||
dest_folder.as_ref()
|
))
|
||||||
);
|
);
|
||||||
|
return ImapResult::Success;
|
||||||
res = DC_ALREADY_DONE;
|
}
|
||||||
} else {
|
Err(err) => {
|
||||||
info!(
|
warn!(
|
||||||
context,
|
context,
|
||||||
"Moving message {}/{} to {}...",
|
"Cannot move message, fallback to COPY/DELETE {}/{} to {}: {}",
|
||||||
folder.as_ref(),
|
folder,
|
||||||
uid,
|
uid,
|
||||||
dest_folder.as_ref()
|
dest_folder,
|
||||||
);
|
err
|
||||||
|
);
|
||||||
if self.select_folder(context, Some(folder.as_ref())) == 0 {
|
|
||||||
warn!(
|
|
||||||
context,
|
|
||||||
"Cannot select folder {} for moving message.",
|
|
||||||
folder.as_ref()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let moved = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
|
||||||
match session.uid_mv(&set, &dest_folder) {
|
|
||||||
Ok(_) => {
|
|
||||||
res = DC_SUCCESS;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
info!(
|
|
||||||
context,
|
|
||||||
"Cannot move message, fallback to COPY/DELETE {}/{} to {}: {}",
|
|
||||||
folder.as_ref(),
|
|
||||||
uid,
|
|
||||||
dest_folder.as_ref(),
|
|
||||||
err
|
|
||||||
);
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unreachable!();
|
|
||||||
};
|
|
||||||
|
|
||||||
if !moved {
|
|
||||||
let copied = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
|
||||||
match session.uid_copy(&set, &dest_folder) {
|
|
||||||
Ok(_) => true,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("error copy: {:?}", err);
|
|
||||||
info!(context, "Cannot copy message.",);
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unreachable!();
|
|
||||||
};
|
|
||||||
|
|
||||||
if copied {
|
|
||||||
if self.add_flag(context, uid, "\\Deleted") == 0 {
|
|
||||||
warn!(context, "Cannot mark message as \"Deleted\".",);
|
|
||||||
}
|
|
||||||
self.config.write().unwrap().selected_folder_needs_expunge = true;
|
|
||||||
res = DC_SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
|
||||||
if res == DC_SUCCESS {
|
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||||
// TODO: is this correct?
|
match session.uid_copy(&set, &dest_folder) {
|
||||||
*dest_uid = uid;
|
Ok(_) => {
|
||||||
}
|
if !self.add_flag_finalized(context, uid, "\\Deleted") {
|
||||||
|
warn!(context, "Cannot mark {} as \"Deleted\" after copy.", uid);
|
||||||
if res == DC_RETRY_LATER {
|
ImapResult::Failed
|
||||||
if self.should_reconnect() {
|
} else {
|
||||||
DC_RETRY_LATER
|
self.config.write().unwrap().selected_folder_needs_expunge = true;
|
||||||
} else {
|
ImapResult::Success
|
||||||
DC_FAILED
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
warn!(context, "Could not copy message: {}", err);
|
||||||
|
ImapResult::Failed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
res
|
unreachable!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_flag<S: AsRef<str>>(&self, context: &Context, server_uid: u32, flag: S) -> usize {
|
fn add_flag_finalized(&self, context: &Context, server_uid: u32, flag: &str) -> bool {
|
||||||
|
// return true if we successfully set the flag or we otherwise
|
||||||
|
// think add_flag should not be retried: Disconnection during setting
|
||||||
|
// the flag, or other imap-errors, returns true as well.
|
||||||
|
//
|
||||||
|
// returning false means that the operation can be retried.
|
||||||
if server_uid == 0 {
|
if server_uid == 0 {
|
||||||
return 0;
|
return true; // might be moved but we don't want to have a stuck job
|
||||||
|
}
|
||||||
|
if self.should_reconnect() {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||||
let set = format!("{}", server_uid);
|
let set = format!("{}", server_uid);
|
||||||
let query = format!("+FLAGS ({})", flag.as_ref());
|
let query = format!("+FLAGS ({})", flag);
|
||||||
match session.uid_store(&set, &query) {
|
match session.uid_store(&set, &query) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -1251,243 +1222,126 @@ impl Imap {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
true // we tried once, that's probably enough for setting flag
|
||||||
|
|
||||||
// All non-connection states are treated as success - the mail may
|
|
||||||
// already be deleted or moved away on the server.
|
|
||||||
if self.should_reconnect() {
|
|
||||||
0
|
|
||||||
} else {
|
} else {
|
||||||
1
|
unreachable!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_seen<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> usize {
|
pub fn prepare_imap_operation_on_msg(
|
||||||
let mut res = DC_RETRY_LATER;
|
&self,
|
||||||
|
context: &Context,
|
||||||
|
folder: &str,
|
||||||
|
uid: u32,
|
||||||
|
) -> Option<ImapResult> {
|
||||||
if uid == 0 {
|
if uid == 0 {
|
||||||
res = DC_FAILED
|
return Some(ImapResult::Failed);
|
||||||
} else if self.is_connected() {
|
} else if !self.is_connected() {
|
||||||
info!(
|
connect_to_inbox(context, &self);
|
||||||
context,
|
if !self.is_connected() {
|
||||||
"Marking message {}/{} as seen...",
|
return Some(ImapResult::RetryLater);
|
||||||
folder.as_ref(),
|
|
||||||
uid,
|
|
||||||
);
|
|
||||||
|
|
||||||
if self.select_folder(context, Some(folder.as_ref())) == 0 {
|
|
||||||
warn!(
|
|
||||||
context,
|
|
||||||
"Cannot select folder {} for setting SEEN flag.",
|
|
||||||
folder.as_ref(),
|
|
||||||
);
|
|
||||||
} else if self.add_flag(context, uid, "\\Seen") == 0 {
|
|
||||||
warn!(context, "Cannot mark message as seen.",);
|
|
||||||
} else {
|
|
||||||
res = DC_SUCCESS
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if self.select_folder(context, Some(&folder)) == 0 {
|
||||||
if res == DC_RETRY_LATER {
|
warn!(
|
||||||
if self.should_reconnect() {
|
context,
|
||||||
DC_RETRY_LATER
|
"Cannot select folder {} for preparing IMAP operation", folder
|
||||||
} else {
|
);
|
||||||
DC_FAILED
|
Some(ImapResult::RetryLater)
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
res
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_mdnsent<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> usize {
|
pub fn set_seen(&self, context: &Context, folder: &str, uid: u32) -> ImapResult {
|
||||||
// returns 0=job should be retried later, 1=job done, 2=job done and flag just set
|
if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, uid) {
|
||||||
let mut res = DC_RETRY_LATER;
|
return imapresult;
|
||||||
let set = format!("{}", uid);
|
|
||||||
|
|
||||||
if uid == 0 {
|
|
||||||
res = DC_FAILED;
|
|
||||||
} else if self.is_connected() {
|
|
||||||
info!(
|
|
||||||
context,
|
|
||||||
"Marking message {}/{} as $MDNSent...",
|
|
||||||
folder.as_ref(),
|
|
||||||
uid,
|
|
||||||
);
|
|
||||||
|
|
||||||
if self.select_folder(context, Some(folder.as_ref())) == 0 {
|
|
||||||
warn!(
|
|
||||||
context,
|
|
||||||
"Cannot select folder {} for setting $MDNSent flag.",
|
|
||||||
folder.as_ref()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Check if the folder can handle the `$MDNSent` flag (see RFC 3503). If so, and not
|
|
||||||
// set: set the flags and return this information.
|
|
||||||
// If the folder cannot handle the `$MDNSent` flag, we risk duplicated MDNs; it's up
|
|
||||||
// to the receiving MUA to handle this then (eg. Delta Chat has no problem with this).
|
|
||||||
|
|
||||||
let can_create_flag = self
|
|
||||||
.config
|
|
||||||
.read()
|
|
||||||
.unwrap()
|
|
||||||
.selected_mailbox
|
|
||||||
.as_ref()
|
|
||||||
.map(|mbox| {
|
|
||||||
// empty means, everything can be stored
|
|
||||||
mbox.permanent_flags.is_empty()
|
|
||||||
|| mbox
|
|
||||||
.permanent_flags
|
|
||||||
.iter()
|
|
||||||
.find(|flag| match flag {
|
|
||||||
imap::types::Flag::Custom(s) => s == "$MDNSent",
|
|
||||||
_ => false,
|
|
||||||
})
|
|
||||||
.is_some()
|
|
||||||
})
|
|
||||||
.expect("just selected folder");
|
|
||||||
|
|
||||||
if can_create_flag {
|
|
||||||
let fetched_msgs =
|
|
||||||
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
|
||||||
match session.uid_fetch(set, FETCH_FLAGS) {
|
|
||||||
Ok(res) => Some(res),
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("fetch error: {:?}", err);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unreachable!();
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(msgs) = fetched_msgs {
|
|
||||||
let flag_set = msgs
|
|
||||||
.first()
|
|
||||||
.map(|msg| {
|
|
||||||
msg.flags()
|
|
||||||
.iter()
|
|
||||||
.find(|flag| match flag {
|
|
||||||
imap::types::Flag::Custom(s) => s == "$MDNSent",
|
|
||||||
_ => false,
|
|
||||||
})
|
|
||||||
.is_some()
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| false);
|
|
||||||
|
|
||||||
res = if flag_set {
|
|
||||||
DC_ALREADY_DONE
|
|
||||||
} else if self.add_flag(context, uid, "$MDNSent") != 0 {
|
|
||||||
DC_SUCCESS
|
|
||||||
} else {
|
|
||||||
res
|
|
||||||
};
|
|
||||||
|
|
||||||
if res == DC_SUCCESS {
|
|
||||||
info!(context, "$MDNSent just set and MDN will be sent.");
|
|
||||||
} else {
|
|
||||||
info!(context, "$MDNSent already set and MDN already sent.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res = DC_SUCCESS;
|
|
||||||
info!(
|
|
||||||
context,
|
|
||||||
"Cannot store $MDNSent flags, risk sending duplicate MDN.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// we are connected, and the folder is selected
|
||||||
|
info!(context, "Marking message {}/{} as seen...", folder, uid,);
|
||||||
|
|
||||||
if res == DC_RETRY_LATER {
|
if self.add_flag_finalized(context, uid, "\\Seen") {
|
||||||
if self.should_reconnect() {
|
ImapResult::Success
|
||||||
DC_RETRY_LATER
|
|
||||||
} else {
|
|
||||||
DC_FAILED
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
res
|
warn!(
|
||||||
|
context,
|
||||||
|
"Cannot mark message {} in folder {} as seen, ignoring.", uid, folder
|
||||||
|
);
|
||||||
|
ImapResult::Failed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// only returns 0 on connection problems; we should try later again in this case *
|
// only returns 0 on connection problems; we should try later again in this case *
|
||||||
pub fn delete_msg<S1: AsRef<str>, S2: AsRef<str>>(
|
pub fn delete_msg(
|
||||||
&self,
|
&self,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
message_id: S1,
|
message_id: &str,
|
||||||
folder: S2,
|
folder: &str,
|
||||||
server_uid: &mut u32,
|
uid: &mut u32,
|
||||||
) -> usize {
|
) -> ImapResult {
|
||||||
let mut success = false;
|
if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, *uid) {
|
||||||
if *server_uid == 0 {
|
return imapresult;
|
||||||
success = true
|
}
|
||||||
} else {
|
// we are connected, and the folder is selected
|
||||||
info!(
|
|
||||||
context,
|
|
||||||
"Marking message \"{}\", {}/{} for deletion...",
|
|
||||||
message_id.as_ref(),
|
|
||||||
folder.as_ref(),
|
|
||||||
server_uid,
|
|
||||||
);
|
|
||||||
|
|
||||||
if self.select_folder(context, Some(&folder)) == 0 {
|
let set = format!("{}", uid);
|
||||||
warn!(
|
let display_imap_id = format!("{}/{}", folder, uid);
|
||||||
context,
|
|
||||||
"Cannot select folder {} for deleting message.",
|
|
||||||
folder.as_ref()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let set = format!("{}", server_uid);
|
|
||||||
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
|
||||||
match session.uid_fetch(set, PREFETCH_FLAGS) {
|
|
||||||
Ok(msgs) => {
|
|
||||||
if msgs.is_empty()
|
|
||||||
|| msgs
|
|
||||||
.first()
|
|
||||||
.unwrap()
|
|
||||||
.envelope()
|
|
||||||
.expect("missing envelope")
|
|
||||||
.message_id
|
|
||||||
.expect("missing message id")
|
|
||||||
!= message_id.as_ref()
|
|
||||||
{
|
|
||||||
warn!(
|
|
||||||
context,
|
|
||||||
"Cannot delete on IMAP, {}/{} does not match {}.",
|
|
||||||
folder.as_ref(),
|
|
||||||
server_uid,
|
|
||||||
message_id.as_ref(),
|
|
||||||
);
|
|
||||||
*server_uid = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("fetch error: {:?}", err);
|
|
||||||
|
|
||||||
warn!(
|
// double-check that we are deleting the correct message-id
|
||||||
context,
|
// this comes at the expense of another imap query
|
||||||
"Cannot delete on IMAP, {}/{} not found.",
|
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||||
folder.as_ref(),
|
match session.uid_fetch(set, PREFETCH_FLAGS) {
|
||||||
server_uid,
|
Ok(msgs) => {
|
||||||
);
|
if msgs.is_empty() {
|
||||||
*server_uid = 0;
|
warn!(
|
||||||
}
|
context,
|
||||||
|
"Cannot delete on IMAP, {}: imap entry gone '{}'",
|
||||||
|
display_imap_id,
|
||||||
|
message_id,
|
||||||
|
);
|
||||||
|
return ImapResult::Failed;
|
||||||
}
|
}
|
||||||
}
|
let remote_message_id =
|
||||||
|
prefetch_get_message_id(msgs.first().unwrap()).unwrap_or_default();
|
||||||
|
|
||||||
// mark the message for deletion
|
if remote_message_id != message_id {
|
||||||
if self.add_flag(context, *server_uid, "\\Deleted") == 0 {
|
warn!(
|
||||||
warn!(context, "Cannot mark message as \"Deleted\".");
|
context,
|
||||||
} else {
|
"Cannot delete on IMAP, {}: remote message-id '{}' != '{}'",
|
||||||
self.config.write().unwrap().selected_folder_needs_expunge = true;
|
display_imap_id,
|
||||||
success = true
|
remote_message_id,
|
||||||
|
message_id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
*uid = 0;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
warn!(
|
||||||
|
context,
|
||||||
|
"Cannot delete {} on IMAP: {}", display_imap_id, err
|
||||||
|
);
|
||||||
|
*uid = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if success {
|
// mark the message for deletion
|
||||||
1
|
if !self.add_flag_finalized(context, *uid, "\\Deleted") {
|
||||||
|
warn!(
|
||||||
|
context,
|
||||||
|
"Cannot mark message {} as \"Deleted\".", display_imap_id
|
||||||
|
);
|
||||||
|
ImapResult::Failed
|
||||||
} else {
|
} else {
|
||||||
self.is_connected() as usize
|
emit_event!(
|
||||||
|
context,
|
||||||
|
Event::ImapMessageDeleted(format!(
|
||||||
|
"IMAP Message {} marked as deleted [{}]",
|
||||||
|
display_imap_id, message_id
|
||||||
|
))
|
||||||
|
);
|
||||||
|
self.config.write().unwrap().selected_folder_needs_expunge = true;
|
||||||
|
ImapResult::Success
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1556,18 +1410,18 @@ impl Imap {
|
|||||||
|
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.set_config_int(context, "folders_configured", 3)
|
.set_raw_config_int(context, "folders_configured", 3)
|
||||||
.ok();
|
.ok();
|
||||||
if let Some(ref mvbox_folder) = mvbox_folder {
|
if let Some(ref mvbox_folder) = mvbox_folder {
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.set_config(context, "configured_mvbox_folder", Some(mvbox_folder))
|
.set_raw_config(context, "configured_mvbox_folder", Some(mvbox_folder))
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
if let Some(ref sentbox_folder) = sentbox_folder {
|
if let Some(ref sentbox_folder) = sentbox_folder {
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.set_config(
|
.set_raw_config(
|
||||||
context,
|
context,
|
||||||
"configured_sentbox_folder",
|
"configured_sentbox_folder",
|
||||||
Some(sentbox_folder.name()),
|
Some(sentbox_folder.name()),
|
||||||
@@ -1646,43 +1500,12 @@ fn get_folder_meaning(folder_name: &imap::types::Name) -> FolderMeaning {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn precheck_imf(
|
fn precheck_imf(context: &Context, rfc724_mid: &str, server_folder: &str, server_uid: u32) -> bool {
|
||||||
context: &Context,
|
if let Ok((old_server_folder, old_server_uid, msg_id)) =
|
||||||
rfc724_mid: *const libc::c_char,
|
message::rfc724_mid_exists(context, &rfc724_mid)
|
||||||
server_folder: &str,
|
{
|
||||||
server_uid: u32,
|
if old_server_folder.is_empty() && old_server_uid == 0 {
|
||||||
) -> libc::c_int {
|
info!(context, "[move] detected bbc-self {}", rfc724_mid,);
|
||||||
let mut rfc724_mid_exists: libc::c_int = 0i32;
|
|
||||||
let msg_id: u32;
|
|
||||||
let mut old_server_folder: *mut libc::c_char = ptr::null_mut();
|
|
||||||
let mut old_server_uid: u32 = 0i32 as u32;
|
|
||||||
let mut mark_seen: libc::c_int = 0i32;
|
|
||||||
msg_id = dc_rfc724_mid_exists(
|
|
||||||
context,
|
|
||||||
rfc724_mid,
|
|
||||||
&mut old_server_folder,
|
|
||||||
&mut old_server_uid,
|
|
||||||
);
|
|
||||||
if msg_id != 0i32 as libc::c_uint {
|
|
||||||
rfc724_mid_exists = 1i32;
|
|
||||||
if *old_server_folder.offset(0isize) as libc::c_int == 0i32
|
|
||||||
&& old_server_uid == 0i32 as libc::c_uint
|
|
||||||
{
|
|
||||||
info!(context, "[move] detected bbc-self {}", as_str(rfc724_mid),);
|
|
||||||
mark_seen = 1i32
|
|
||||||
} else if as_str(old_server_folder) != server_folder {
|
|
||||||
info!(
|
|
||||||
context,
|
|
||||||
"[move] detected moved message {}",
|
|
||||||
as_str(rfc724_mid),
|
|
||||||
);
|
|
||||||
dc_update_msg_move_state(context, rfc724_mid, MoveState::Stay);
|
|
||||||
}
|
|
||||||
if as_str(old_server_folder) != server_folder || old_server_uid != server_uid {
|
|
||||||
dc_update_server_uid(context, rfc724_mid, server_folder, server_uid);
|
|
||||||
}
|
|
||||||
context.do_heuristics_moves(server_folder, msg_id);
|
|
||||||
if 0 != mark_seen {
|
|
||||||
job_add(
|
job_add(
|
||||||
context,
|
context,
|
||||||
Action::MarkseenMsgOnImap,
|
Action::MarkseenMsgOnImap,
|
||||||
@@ -1690,8 +1513,21 @@ unsafe fn precheck_imf(
|
|||||||
Params::new(),
|
Params::new(),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
|
} else if old_server_folder != server_folder {
|
||||||
|
info!(context, "[move] detected moved message {}", rfc724_mid,);
|
||||||
|
update_msg_move_state(context, &rfc724_mid, MoveState::Stay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if old_server_folder != server_folder || old_server_uid != server_uid {
|
||||||
|
update_server_uid(context, &rfc724_mid, server_folder, server_uid);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
libc::free(old_server_folder as *mut libc::c_void);
|
}
|
||||||
rfc724_mid_exists
|
|
||||||
|
fn prefetch_get_message_id(prefetch_msg: &imap::types::Fetch) -> Result<String, Error> {
|
||||||
|
let message_id = prefetch_msg.envelope().unwrap().message_id.unwrap();
|
||||||
|
wrapmime::parse_message_id(&message_id)
|
||||||
}
|
}
|
||||||
|
|||||||
823
src/imex.rs
Normal file
823
src/imex.rs
Normal file
@@ -0,0 +1,823 @@
|
|||||||
|
use core::cmp::{max, min};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use num_traits::FromPrimitive;
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
|
||||||
|
use crate::chat;
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::configure::*;
|
||||||
|
use crate::constants::*;
|
||||||
|
use crate::context::Context;
|
||||||
|
use crate::dc_tools::*;
|
||||||
|
use crate::e2ee;
|
||||||
|
use crate::error::*;
|
||||||
|
use crate::events::Event;
|
||||||
|
use crate::job::*;
|
||||||
|
use crate::key::*;
|
||||||
|
use crate::message::Message;
|
||||||
|
use crate::param::*;
|
||||||
|
use crate::pgp;
|
||||||
|
use crate::sql::{self, Sql};
|
||||||
|
use crate::stock::StockMessage;
|
||||||
|
|
||||||
|
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum ImexMode {
|
||||||
|
/// Export all private keys and all public keys of the user to the
|
||||||
|
/// directory given as `param1`. The default key is written to the files `public-key-default.asc`
|
||||||
|
/// and `private-key-default.asc`, if there are more keys, they are written to files as
|
||||||
|
/// `public-key-<id>.asc` and `private-key-<id>.asc`
|
||||||
|
ExportSelfKeys = 1,
|
||||||
|
/// Import private keys found in the directory given as `param1`.
|
||||||
|
/// The last imported key is made the default keys unless its name contains the string `legacy`.
|
||||||
|
/// Public keys are not imported.
|
||||||
|
ImportSelfKeys = 2,
|
||||||
|
/// Export a backup to the directory given as `param1`.
|
||||||
|
/// The backup contains all contacts, chats, images and other data and device independent settings.
|
||||||
|
/// The backup does not contain device dependent settings as ringtones or LED notification settings.
|
||||||
|
/// The name of the backup is typically `delta-chat.<day>.bak`, if more than one backup is create on a day,
|
||||||
|
/// the format is `delta-chat.<day>-<number>.bak`
|
||||||
|
ExportBackup = 11,
|
||||||
|
/// `param1` is the file (not: directory) to import. The file is normally
|
||||||
|
/// created by DC_IMEX_EXPORT_BACKUP and detected by dc_imex_has_backup(). Importing a backup
|
||||||
|
/// is only possible as long as the context is not configured or used in another way.
|
||||||
|
ImportBackup = 12,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Import/export things.
|
||||||
|
/// For this purpose, the function creates a job that is executed in the IMAP-thread then;
|
||||||
|
/// this requires to call dc_perform_imap_jobs() regularly.
|
||||||
|
///
|
||||||
|
/// What to do is defined by the _what_ parameter.
|
||||||
|
///
|
||||||
|
/// While dc_imex() returns immediately, the started job may take a while,
|
||||||
|
/// you can stop it using dc_stop_ongoing_process(). During execution of the job,
|
||||||
|
/// some events are sent out:
|
||||||
|
///
|
||||||
|
/// - A number of #DC_EVENT_IMEX_PROGRESS events are sent and may be used to create
|
||||||
|
/// a progress bar or stuff like that. Moreover, you'll be informed when the imex-job is done.
|
||||||
|
///
|
||||||
|
/// - For each file written on export, the function sends #DC_EVENT_IMEX_FILE_WRITTEN
|
||||||
|
///
|
||||||
|
/// Only one import-/export-progress can run at the same time.
|
||||||
|
/// To cancel an import-/export-progress, use dc_stop_ongoing_process().
|
||||||
|
pub fn imex(context: &Context, what: ImexMode, param1: Option<impl AsRef<Path>>) {
|
||||||
|
let mut param = Params::new();
|
||||||
|
param.set_int(Param::Cmd, what as i32);
|
||||||
|
if let Some(param1) = param1 {
|
||||||
|
param.set(Param::Arg, param1.as_ref().to_string_lossy());
|
||||||
|
}
|
||||||
|
|
||||||
|
job_kill_action(context, Action::ImexImap);
|
||||||
|
job_add(context, Action::ImexImap, 0, param, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the filename of the backup if found, nullptr otherwise.
|
||||||
|
pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<String> {
|
||||||
|
let dir_name = dir_name.as_ref();
|
||||||
|
let dir_iter = std::fs::read_dir(dir_name)?;
|
||||||
|
let mut newest_backup_time = 0;
|
||||||
|
let mut newest_backup_path: Option<std::path::PathBuf> = None;
|
||||||
|
for dirent in dir_iter {
|
||||||
|
match dirent {
|
||||||
|
Ok(dirent) => {
|
||||||
|
let path = dirent.path();
|
||||||
|
let name = dirent.file_name();
|
||||||
|
let name = name.to_string_lossy();
|
||||||
|
if name.starts_with("delta-chat") && name.ends_with(".bak") {
|
||||||
|
let sql = Sql::new();
|
||||||
|
if sql.open(context, &path, true) {
|
||||||
|
let curr_backup_time =
|
||||||
|
sql.get_raw_config_int(context, "backup_time")
|
||||||
|
.unwrap_or_default() as u64;
|
||||||
|
if curr_backup_time > newest_backup_time {
|
||||||
|
newest_backup_path = Some(path);
|
||||||
|
newest_backup_time = curr_backup_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match newest_backup_path {
|
||||||
|
Some(path) => Ok(path.to_string_lossy().into_owned()),
|
||||||
|
None => bail!("no backup found"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initiate_key_transfer(context: &Context) -> Result<String> {
|
||||||
|
ensure!(context.alloc_ongoing(), "could not allocate ongoing");
|
||||||
|
let res = do_initiate_key_transfer(context);
|
||||||
|
context.free_ongoing();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_initiate_key_transfer(context: &Context) -> Result<String> {
|
||||||
|
let mut msg: Message;
|
||||||
|
let setup_code = create_setup_code(context);
|
||||||
|
/* this may require a keypair to be created. this may take a second ... */
|
||||||
|
ensure!(!context.shall_stop_ongoing(), "canceled");
|
||||||
|
let setup_file_content = render_setup_file(context, &setup_code)?;
|
||||||
|
/* encrypting may also take a while ... */
|
||||||
|
ensure!(!context.shall_stop_ongoing(), "canceled");
|
||||||
|
let setup_file_name = context.new_blob_file(
|
||||||
|
"autocrypt-setup-message.html",
|
||||||
|
setup_file_content.as_bytes(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let chat_id = chat::create_by_contact_id(context, 1)?;
|
||||||
|
msg = Message::default();
|
||||||
|
msg.type_0 = Viewtype::File;
|
||||||
|
msg.param.set(Param::File, setup_file_name);
|
||||||
|
|
||||||
|
msg.param
|
||||||
|
.set(Param::MimeType, "application/autocrypt-setup");
|
||||||
|
msg.param.set_int(Param::Cmd, 6);
|
||||||
|
msg.param
|
||||||
|
.set_int(Param::ForcePlaintext, DC_FP_NO_AUTOCRYPT_HEADER);
|
||||||
|
|
||||||
|
ensure!(!context.shall_stop_ongoing(), "canceled");
|
||||||
|
let msg_id = chat::send_msg(context, chat_id, &mut msg)?;
|
||||||
|
info!(context, "Wait for setup message being sent ...",);
|
||||||
|
while !context.shall_stop_ongoing() {
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||||
|
if let Ok(msg) = Message::load_from_db(context, msg_id) {
|
||||||
|
if msg.is_sent() {
|
||||||
|
info!(context, "... setup message sent.",);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(setup_code)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders HTML body of a setup file message.
|
||||||
|
///
|
||||||
|
/// The `passphrase` must be at least 2 characters long.
|
||||||
|
pub fn render_setup_file(context: &Context, passphrase: &str) -> Result<String> {
|
||||||
|
ensure!(
|
||||||
|
passphrase.len() >= 2,
|
||||||
|
"Passphrase must be at least 2 chars long."
|
||||||
|
);
|
||||||
|
let self_addr = e2ee::ensure_secret_key_exists(context)?;
|
||||||
|
let private_key = Key::from_self_private(context, self_addr, &context.sql)
|
||||||
|
.ok_or(format_err!("Failed to get private key."))?;
|
||||||
|
let ac_headers = match context.get_config_bool(Config::E2eeEnabled) {
|
||||||
|
false => None,
|
||||||
|
true => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
|
||||||
|
};
|
||||||
|
let private_key_asc = private_key.to_asc(ac_headers);
|
||||||
|
let encr = pgp::symm_encrypt(&passphrase, private_key_asc.as_bytes())?;
|
||||||
|
|
||||||
|
let replacement = format!(
|
||||||
|
concat!(
|
||||||
|
"-----BEGIN PGP MESSAGE-----\r\n",
|
||||||
|
"Passphrase-Format: numeric9x4\r\n",
|
||||||
|
"Passphrase-Begin: {}"
|
||||||
|
),
|
||||||
|
&passphrase[..2]
|
||||||
|
);
|
||||||
|
let pgp_msg = encr.replace("-----BEGIN PGP MESSAGE-----", &replacement);
|
||||||
|
|
||||||
|
let msg_subj = context.stock_str(StockMessage::AcSetupMsgSubject);
|
||||||
|
let msg_body = context.stock_str(StockMessage::AcSetupMsgBody);
|
||||||
|
let msg_body_html = msg_body.replace("\r", "").replace("\n", "<br>");
|
||||||
|
Ok(format!(
|
||||||
|
concat!(
|
||||||
|
"<!DOCTYPE html>\r\n",
|
||||||
|
"<html>\r\n",
|
||||||
|
" <head>\r\n",
|
||||||
|
" <title>{}</title>\r\n",
|
||||||
|
" </head>\r\n",
|
||||||
|
" <body>\r\n",
|
||||||
|
" <h1>{}</h1>\r\n",
|
||||||
|
" <p>{}</p>\r\n",
|
||||||
|
" <pre>\r\n{}\r\n</pre>\r\n",
|
||||||
|
" </body>\r\n",
|
||||||
|
"</html>\r\n"
|
||||||
|
),
|
||||||
|
msg_subj, msg_subj, msg_body_html, pgp_msg
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_setup_code(_context: &Context) -> String {
|
||||||
|
let mut random_val: u16;
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let mut ret = String::new();
|
||||||
|
|
||||||
|
for i in 0..9 {
|
||||||
|
loop {
|
||||||
|
random_val = rng.gen();
|
||||||
|
if !(random_val as usize > 60000) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
random_val = (random_val as usize % 10000) as u16;
|
||||||
|
ret += &format!(
|
||||||
|
"{}{:04}",
|
||||||
|
if 0 != i { "-" } else { "" },
|
||||||
|
random_val as usize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn continue_key_transfer(context: &Context, msg_id: u32, setup_code: &str) -> Result<()> {
|
||||||
|
ensure!(msg_id > DC_MSG_ID_LAST_SPECIAL, "wrong id");
|
||||||
|
|
||||||
|
let msg = Message::load_from_db(context, msg_id);
|
||||||
|
if msg.is_err() {
|
||||||
|
bail!("Message is no Autocrypt Setup Message.");
|
||||||
|
}
|
||||||
|
let msg = msg.unwrap_or_default();
|
||||||
|
ensure!(
|
||||||
|
msg.is_setupmessage(),
|
||||||
|
"Message is no Autocrypt Setup Message."
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(filename) = msg.get_file(context) {
|
||||||
|
let file = dc_open_file(context, filename)?;
|
||||||
|
let sc = normalize_setup_code(setup_code);
|
||||||
|
let armored_key = decrypt_setup_file(context, &sc, file)?;
|
||||||
|
set_self_key(context, &armored_key, true, true)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
bail!("Message is no Autocrypt Setup Message.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_self_key(
|
||||||
|
context: &Context,
|
||||||
|
armored: &str,
|
||||||
|
set_default: bool,
|
||||||
|
prefer_encrypt_required: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
// try hard to only modify key-state
|
||||||
|
let keys = Key::from_armored_string(armored, KeyType::Private)
|
||||||
|
.and_then(|(k, h)| if k.verify() { Some((k, h)) } else { None })
|
||||||
|
.and_then(|(k, h)| k.split_key().map(|pub_key| (k, pub_key, h)));
|
||||||
|
|
||||||
|
ensure!(keys.is_some(), "Not a valid private key");
|
||||||
|
|
||||||
|
let (private_key, public_key, header) = keys.unwrap();
|
||||||
|
let preferencrypt = header.get("Autocrypt-Prefer-Encrypt");
|
||||||
|
match preferencrypt.map(|s| s.as_str()) {
|
||||||
|
Some(headerval) => {
|
||||||
|
let e2ee_enabled = match headerval {
|
||||||
|
"nopreference" => 0,
|
||||||
|
"mutual" => 1,
|
||||||
|
_ => {
|
||||||
|
bail!("invalid Autocrypt-Prefer-Encrypt header: {:?}", header);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
context
|
||||||
|
.sql
|
||||||
|
.set_raw_config_int(context, "e2ee_enabled", e2ee_enabled)?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
if prefer_encrypt_required {
|
||||||
|
bail!("missing Autocrypt-Prefer-Encrypt header");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let self_addr = context.get_config(Config::ConfiguredAddr);
|
||||||
|
ensure!(self_addr.is_some(), "Missing self addr");
|
||||||
|
|
||||||
|
// XXX maybe better make dc_key_save_self_keypair delete things
|
||||||
|
sql::execute(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
"DELETE FROM keypairs WHERE public_key=? OR private_key=?;",
|
||||||
|
params![public_key.to_bytes(), private_key.to_bytes()],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if set_default {
|
||||||
|
sql::execute(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
"UPDATE keypairs SET is_default=0;",
|
||||||
|
params![],
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dc_key_save_self_keypair(
|
||||||
|
context,
|
||||||
|
&public_key,
|
||||||
|
&private_key,
|
||||||
|
self_addr.unwrap_or_default(),
|
||||||
|
set_default,
|
||||||
|
&context.sql,
|
||||||
|
) {
|
||||||
|
bail!("Cannot save keypair, internal key-state possibly corrupted now!");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt_setup_file<T: std::io::Read + std::io::Seek>(
|
||||||
|
_context: &Context,
|
||||||
|
passphrase: &str,
|
||||||
|
file: T,
|
||||||
|
) -> Result<String> {
|
||||||
|
let plain_bytes = pgp::symm_decrypt(passphrase, file)?;
|
||||||
|
let plain_text = std::string::String::from_utf8(plain_bytes)?;
|
||||||
|
|
||||||
|
Ok(plain_text)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn normalize_setup_code(s: &str) -> String {
|
||||||
|
let mut out = String::new();
|
||||||
|
for c in s.chars() {
|
||||||
|
if c >= '0' && c <= '9' {
|
||||||
|
out.push(c);
|
||||||
|
if let 4 | 9 | 14 | 19 | 24 | 29 | 34 | 39 = out.len() {
|
||||||
|
out += "-"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) -> Result<()> {
|
||||||
|
ensure!(context.alloc_ongoing(), "could not allocate ongoing");
|
||||||
|
let what: Option<ImexMode> = job.param.get_int(Param::Cmd).and_then(ImexMode::from_i32);
|
||||||
|
let param = job.param.get(Param::Arg).unwrap_or_default();
|
||||||
|
|
||||||
|
ensure!(!param.is_empty(), "No Import/export dir/file given.");
|
||||||
|
info!(context, "Import/export process started.");
|
||||||
|
context.call_cb(Event::ImexProgress(10));
|
||||||
|
|
||||||
|
ensure!(context.sql.is_open(), "Database not opened.");
|
||||||
|
if what == Some(ImexMode::ExportBackup) || what == Some(ImexMode::ExportSelfKeys) {
|
||||||
|
// before we export anything, make sure the private key exists
|
||||||
|
if e2ee::ensure_secret_key_exists(context).is_err() {
|
||||||
|
context.free_ongoing();
|
||||||
|
bail!("Cannot create private key or private key not available.");
|
||||||
|
} else {
|
||||||
|
dc_create_folder(context, ¶m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let path = Path::new(param);
|
||||||
|
let success = match what {
|
||||||
|
Some(ImexMode::ExportSelfKeys) => export_self_keys(context, path),
|
||||||
|
Some(ImexMode::ImportSelfKeys) => import_self_keys(context, path),
|
||||||
|
Some(ImexMode::ExportBackup) => export_backup(context, path),
|
||||||
|
Some(ImexMode::ImportBackup) => import_backup(context, path),
|
||||||
|
None => {
|
||||||
|
bail!("unknown IMEX type");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
context.free_ongoing();
|
||||||
|
match success {
|
||||||
|
Ok(()) => {
|
||||||
|
info!(context, "IMEX successfully completed");
|
||||||
|
context.call_cb(Event::ImexProgress(1000));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
context.call_cb(Event::ImexProgress(0));
|
||||||
|
bail!("IMEX FAILED to complete: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Import Backup
|
||||||
|
fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Result<()> {
|
||||||
|
info!(
|
||||||
|
context,
|
||||||
|
"Import \"{}\" to \"{}\".",
|
||||||
|
backup_to_import.as_ref().display(),
|
||||||
|
context.get_dbfile().display()
|
||||||
|
);
|
||||||
|
|
||||||
|
ensure!(
|
||||||
|
!dc_is_configured(context),
|
||||||
|
"Cannot import backups to accounts in use."
|
||||||
|
);
|
||||||
|
context.sql.close(&context);
|
||||||
|
dc_delete_file(context, context.get_dbfile());
|
||||||
|
ensure!(
|
||||||
|
!dc_file_exist(context, context.get_dbfile()),
|
||||||
|
"Cannot delete old database."
|
||||||
|
);
|
||||||
|
|
||||||
|
ensure!(
|
||||||
|
dc_copy_file(context, backup_to_import.as_ref(), context.get_dbfile()),
|
||||||
|
"could not copy file"
|
||||||
|
);
|
||||||
|
/* error already logged */
|
||||||
|
/* re-open copied database file */
|
||||||
|
ensure!(
|
||||||
|
context.sql.open(&context, &context.get_dbfile(), false),
|
||||||
|
"could not re-open db"
|
||||||
|
);
|
||||||
|
|
||||||
|
let total_files_cnt = context
|
||||||
|
.sql
|
||||||
|
.query_get_value::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![])
|
||||||
|
.unwrap_or_default() as usize;
|
||||||
|
info!(
|
||||||
|
context,
|
||||||
|
"***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt,
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = context.sql.query_map(
|
||||||
|
"SELECT file_name, file_content FROM backup_blobs ORDER BY id;",
|
||||||
|
params![],
|
||||||
|
|row| {
|
||||||
|
let name: String = row.get(0)?;
|
||||||
|
let blob: Vec<u8> = row.get(1)?;
|
||||||
|
|
||||||
|
Ok((name, blob))
|
||||||
|
},
|
||||||
|
|files| {
|
||||||
|
for (processed_files_cnt, file) in files.enumerate() {
|
||||||
|
let (file_name, file_blob) = file?;
|
||||||
|
ensure!(!context.shall_stop_ongoing(), "received stop signal");
|
||||||
|
let mut permille = processed_files_cnt * 1000 / total_files_cnt;
|
||||||
|
if permille < 10 {
|
||||||
|
permille = 10
|
||||||
|
}
|
||||||
|
if permille > 990 {
|
||||||
|
permille = 990
|
||||||
|
}
|
||||||
|
context.call_cb(Event::ImexProgress(permille));
|
||||||
|
if file_blob.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let path_filename = context.get_blobdir().join(file_name);
|
||||||
|
if dc_write_file(context, &path_filename, &file_blob) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bail!(
|
||||||
|
"Storage full? Cannot write file {} with {} bytes.",
|
||||||
|
path_filename.display(),
|
||||||
|
file_blob.len(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
res.and_then(|_| {
|
||||||
|
// only delete backup_blobs if all files were successfully extracted
|
||||||
|
sql::execute(context, &context.sql, "DROP TABLE backup_blobs;", params![])?;
|
||||||
|
sql::try_execute(context, &context.sql, "VACUUM;").ok();
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Export backup
|
||||||
|
******************************************************************************/
|
||||||
|
/* the FILE_PROGRESS macro calls the callback with the permille of files processed.
|
||||||
|
The macro avoids weird values of 0% or 100% while still working. */
|
||||||
|
fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||||
|
// get a fine backup file name (the name includes the date so that multiple backup instances are possible)
|
||||||
|
// FIXME: we should write to a temporary file first and rename it on success. this would guarantee the backup is complete.
|
||||||
|
// let dest_path_filename = dc_get_next_backup_file(context, dir, res);
|
||||||
|
let now = time();
|
||||||
|
let dest_path_filename = dc_get_next_backup_path(dir, now)?;
|
||||||
|
|
||||||
|
sql::housekeeping(context);
|
||||||
|
|
||||||
|
sql::try_execute(context, &context.sql, "VACUUM;").ok();
|
||||||
|
context.sql.close(context);
|
||||||
|
info!(
|
||||||
|
context,
|
||||||
|
"Backup \"{}\" to \"{}\".",
|
||||||
|
context.get_dbfile().display(),
|
||||||
|
dest_path_filename.display(),
|
||||||
|
);
|
||||||
|
let copied = dc_copy_file(context, context.get_dbfile(), &dest_path_filename);
|
||||||
|
context.sql.open(&context, &context.get_dbfile(), false);
|
||||||
|
if !copied {
|
||||||
|
let s = dest_path_filename.to_string_lossy().to_string();
|
||||||
|
bail!(
|
||||||
|
"could not copy file from {:?} to {:?}",
|
||||||
|
context.get_dbfile(),
|
||||||
|
s
|
||||||
|
);
|
||||||
|
}
|
||||||
|
match add_files_to_export(context, &dest_path_filename) {
|
||||||
|
Err(err) => {
|
||||||
|
dc_delete_file(context, &dest_path_filename);
|
||||||
|
error!(context, "backup failed: {}", err);
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
Ok(()) => {
|
||||||
|
context
|
||||||
|
.sql
|
||||||
|
.set_raw_config_int(context, "backup_time", now as i32)?;
|
||||||
|
context.call_cb(Event::ImexFileWritten(dest_path_filename.clone()));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_files_to_export(context: &Context, dest_path_filename: &PathBuf) -> Result<()> {
|
||||||
|
// add all files as blobs to the database copy (this does not require
|
||||||
|
// the source to be locked, neigher the destination as it is used only here)
|
||||||
|
let sql = Sql::new();
|
||||||
|
ensure!(
|
||||||
|
sql.open(context, &dest_path_filename, false),
|
||||||
|
"could not open db"
|
||||||
|
);
|
||||||
|
if !sql.table_exists("backup_blobs") {
|
||||||
|
sql::execute(
|
||||||
|
context,
|
||||||
|
&sql,
|
||||||
|
"CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);",
|
||||||
|
params![],
|
||||||
|
)?
|
||||||
|
}
|
||||||
|
// copy all files from BLOBDIR into backup-db
|
||||||
|
let mut total_files_cnt = 0;
|
||||||
|
let dir = context.get_blobdir();
|
||||||
|
let dir_handle = std::fs::read_dir(&dir)?;
|
||||||
|
total_files_cnt += dir_handle.filter(|r| r.is_ok()).count();
|
||||||
|
|
||||||
|
info!(context, "EXPORT: total_files_cnt={}", total_files_cnt);
|
||||||
|
// scan directory, pass 2: copy files
|
||||||
|
let dir_handle = std::fs::read_dir(&dir)?;
|
||||||
|
sql.prepare(
|
||||||
|
"INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);",
|
||||||
|
|mut stmt, _| {
|
||||||
|
let mut processed_files_cnt = 0;
|
||||||
|
for entry in dir_handle {
|
||||||
|
let entry = entry?;
|
||||||
|
ensure!(
|
||||||
|
!context.shall_stop_ongoing(),
|
||||||
|
"canceled during export-files"
|
||||||
|
);
|
||||||
|
processed_files_cnt += 1;
|
||||||
|
let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10);
|
||||||
|
context.call_cb(Event::ImexProgress(permille));
|
||||||
|
|
||||||
|
let name_f = entry.file_name();
|
||||||
|
let name = name_f.to_string_lossy();
|
||||||
|
if name.starts_with("delta-chat") && name.ends_with(".bak") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
info!(context, "EXPORT: copying filename={}", name);
|
||||||
|
let curr_path_filename = context.get_blobdir().join(entry.file_name());
|
||||||
|
if let Ok(buf) = dc_read_file(context, &curr_path_filename) {
|
||||||
|
if buf.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// bail out if we can't insert
|
||||||
|
stmt.execute(params![name, buf])?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Classic key import
|
||||||
|
******************************************************************************/
|
||||||
|
fn import_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||||
|
/* hint: even if we switch to import Autocrypt Setup Files, we should leave the possibility to import
|
||||||
|
plain ASC keys, at least keys without a password, if we do not want to implement a password entry function.
|
||||||
|
Importing ASC keys is useful to use keys in Delta Chat used by any other non-Autocrypt-PGP implementation.
|
||||||
|
|
||||||
|
Maybe we should make the "default" key handlong also a little bit smarter
|
||||||
|
(currently, the last imported key is the standard key unless it contains the string "legacy" in its name) */
|
||||||
|
let mut set_default: bool;
|
||||||
|
let mut imported_cnt = 0;
|
||||||
|
|
||||||
|
let dir_name = dir.as_ref().to_string_lossy();
|
||||||
|
let dir_handle = std::fs::read_dir(&dir)?;
|
||||||
|
for entry in dir_handle {
|
||||||
|
let entry_fn = entry?.file_name();
|
||||||
|
let name_f = entry_fn.to_string_lossy();
|
||||||
|
let path_plus_name = dir.as_ref().join(&entry_fn);
|
||||||
|
match dc_get_filesuffix_lc(&name_f) {
|
||||||
|
Some(suffix) => {
|
||||||
|
if suffix != "asc" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
set_default = if name_f.contains("legacy") {
|
||||||
|
info!(context, "found legacy key '{}'", path_plus_name.display());
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match dc_read_file(context, &path_plus_name) {
|
||||||
|
Ok(buf) => {
|
||||||
|
let armored = std::string::String::from_utf8_lossy(&buf);
|
||||||
|
if let Err(err) = set_self_key(context, &armored, set_default, false) {
|
||||||
|
error!(context, "set_self_key: {}", err);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => continue,
|
||||||
|
}
|
||||||
|
imported_cnt += 1;
|
||||||
|
}
|
||||||
|
ensure!(
|
||||||
|
imported_cnt > 0,
|
||||||
|
"No private keys found in \"{}\".",
|
||||||
|
dir_name
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||||
|
let mut export_errors = 0;
|
||||||
|
|
||||||
|
context.sql.query_map(
|
||||||
|
"SELECT id, public_key, private_key, is_default FROM keypairs;",
|
||||||
|
params![],
|
||||||
|
|row| {
|
||||||
|
let id = row.get(0)?;
|
||||||
|
let public_key_blob: Vec<u8> = row.get(1)?;
|
||||||
|
let public_key = Key::from_slice(&public_key_blob, KeyType::Public);
|
||||||
|
let private_key_blob: Vec<u8> = row.get(2)?;
|
||||||
|
let private_key = Key::from_slice(&private_key_blob, KeyType::Private);
|
||||||
|
let is_default: i32 = row.get(3)?;
|
||||||
|
|
||||||
|
Ok((id, public_key, private_key, is_default))
|
||||||
|
},
|
||||||
|
|keys| {
|
||||||
|
for key_pair in keys {
|
||||||
|
let (id, public_key, private_key, is_default) = key_pair?;
|
||||||
|
let id = Some(id).filter(|_| is_default != 0);
|
||||||
|
if let Some(key) = public_key {
|
||||||
|
if !export_key_to_asc_file(context, &dir, id, &key) {
|
||||||
|
export_errors += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
export_errors += 1;
|
||||||
|
}
|
||||||
|
if let Some(key) = private_key {
|
||||||
|
if !export_key_to_asc_file(context, &dir, id, &key) {
|
||||||
|
export_errors += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
export_errors += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
ensure!(export_errors == 0, "errors while exporting keys");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Classic key export
|
||||||
|
******************************************************************************/
|
||||||
|
fn export_key_to_asc_file(
|
||||||
|
context: &Context,
|
||||||
|
dir: impl AsRef<Path>,
|
||||||
|
id: Option<i64>,
|
||||||
|
key: &Key,
|
||||||
|
) -> bool {
|
||||||
|
let mut success = false;
|
||||||
|
let file_name = {
|
||||||
|
let kind = if key.is_public() { "public" } else { "private" };
|
||||||
|
let id = id.map_or("default".into(), |i| i.to_string());
|
||||||
|
|
||||||
|
dir.as_ref().join(format!("{}-key-{}.asc", kind, &id))
|
||||||
|
};
|
||||||
|
info!(context, "Exporting key {}", file_name.display());
|
||||||
|
dc_delete_file(context, &file_name);
|
||||||
|
|
||||||
|
if !key.write_asc_to_file(&file_name, context) {
|
||||||
|
error!(context, "Cannot write key to {}", file_name.display());
|
||||||
|
} else {
|
||||||
|
context.call_cb(Event::ImexFileWritten(file_name));
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
success
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use crate::pgp::{split_armored_data, HEADER_AUTOCRYPT, HEADER_SETUPCODE};
|
||||||
|
use crate::test_utils::*;
|
||||||
|
use ::pgp::armor::BlockType;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_render_setup_file() {
|
||||||
|
let t = test_context(Some(Box::new(logging_cb)));
|
||||||
|
|
||||||
|
configure_alice_keypair(&t.ctx);
|
||||||
|
let msg = render_setup_file(&t.ctx, "hello").unwrap();
|
||||||
|
println!("{}", &msg);
|
||||||
|
// Check some substrings, indicating things got substituted.
|
||||||
|
// In particular note the mixing of `\r\n` and `\n` depending
|
||||||
|
// on who generated the stings.
|
||||||
|
assert!(msg.contains("<title>Autocrypt Setup Message</title"));
|
||||||
|
assert!(msg.contains("<h1>Autocrypt Setup Message</h1>"));
|
||||||
|
assert!(msg.contains("<p>This is the Autocrypt Setup Message used to"));
|
||||||
|
assert!(msg.contains("-----BEGIN PGP MESSAGE-----\r\n"));
|
||||||
|
assert!(msg.contains("Passphrase-Format: numeric9x4\r\n"));
|
||||||
|
assert!(msg.contains("Passphrase-Begin: he\n"));
|
||||||
|
assert!(msg.contains("==\n"));
|
||||||
|
assert!(msg.contains("-----END PGP MESSAGE-----\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_render_setup_file_newline_replace() {
|
||||||
|
let t = dummy_context();
|
||||||
|
t.ctx
|
||||||
|
.set_stock_translation(StockMessage::AcSetupMsgBody, "hello\r\nthere".to_string())
|
||||||
|
.unwrap();
|
||||||
|
configure_alice_keypair(&t.ctx);
|
||||||
|
let msg = render_setup_file(&t.ctx, "pw").unwrap();
|
||||||
|
println!("{}", &msg);
|
||||||
|
assert!(msg.contains("<p>hello<br>there</p>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_setup_code() {
|
||||||
|
let t = dummy_context();
|
||||||
|
let setupcode = create_setup_code(&t.ctx);
|
||||||
|
assert_eq!(setupcode.len(), 44);
|
||||||
|
assert_eq!(setupcode.chars().nth(4).unwrap(), '-');
|
||||||
|
assert_eq!(setupcode.chars().nth(9).unwrap(), '-');
|
||||||
|
assert_eq!(setupcode.chars().nth(14).unwrap(), '-');
|
||||||
|
assert_eq!(setupcode.chars().nth(19).unwrap(), '-');
|
||||||
|
assert_eq!(setupcode.chars().nth(24).unwrap(), '-');
|
||||||
|
assert_eq!(setupcode.chars().nth(29).unwrap(), '-');
|
||||||
|
assert_eq!(setupcode.chars().nth(34).unwrap(), '-');
|
||||||
|
assert_eq!(setupcode.chars().nth(39).unwrap(), '-');
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_export_key_to_asc_file() {
|
||||||
|
let context = dummy_context();
|
||||||
|
let base64 = include_str!("../test-data/key/public.asc");
|
||||||
|
let key = Key::from_base64(base64, KeyType::Public).unwrap();
|
||||||
|
let blobdir = "$BLOBDIR";
|
||||||
|
assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key));
|
||||||
|
let blobdir = context.ctx.get_blobdir().to_str().unwrap();
|
||||||
|
let filename = format!("{}/public-key-default.asc", blobdir);
|
||||||
|
let bytes = std::fs::read(&filename).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(bytes, key.to_asc(None).into_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_normalize_setup_code() {
|
||||||
|
let norm = normalize_setup_code("123422343234423452346234723482349234");
|
||||||
|
assert_eq!(norm, "1234-2234-3234-4234-5234-6234-7234-8234-9234");
|
||||||
|
|
||||||
|
let norm =
|
||||||
|
normalize_setup_code("\t1 2 3422343234- foo bar-- 423-45 2 34 6234723482349234 ");
|
||||||
|
assert_eq!(norm, "1234-2234-3234-4234-5234-6234-7234-8234-9234");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* S_EM_SETUPFILE is a AES-256 symm. encrypted setup message created by Enigmail
|
||||||
|
with an "encrypted session key", see RFC 4880. The code is in S_EM_SETUPCODE */
|
||||||
|
const S_EM_SETUPCODE: &str = "1742-0185-6197-1303-7016-8412-3581-4441-0597";
|
||||||
|
const S_EM_SETUPFILE: &str = include_str!("../test-data/message/stress.txt");
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_and_decrypt() {
|
||||||
|
let ctx = dummy_context();
|
||||||
|
let context = &ctx.ctx;
|
||||||
|
|
||||||
|
let buf_1 = S_EM_SETUPFILE.as_bytes().to_vec();
|
||||||
|
let (typ, headers, base64) = split_armored_data(&buf_1).unwrap();
|
||||||
|
assert_eq!(typ, BlockType::Message);
|
||||||
|
assert!(S_EM_SETUPCODE.starts_with(headers.get(HEADER_SETUPCODE).unwrap()));
|
||||||
|
assert!(headers.get(HEADER_AUTOCRYPT).is_none());
|
||||||
|
|
||||||
|
assert!(!base64.is_empty());
|
||||||
|
|
||||||
|
let setup_file = S_EM_SETUPFILE.to_string();
|
||||||
|
let decrypted = decrypt_setup_file(
|
||||||
|
context,
|
||||||
|
S_EM_SETUPCODE,
|
||||||
|
std::io::Cursor::new(setup_file.as_bytes()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (typ, headers, _base64) = split_armored_data(decrypted.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(typ, BlockType::PrivateKey);
|
||||||
|
assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string()));
|
||||||
|
assert!(headers.get(HEADER_SETUPCODE).is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
644
src/job.rs
644
src/job.rs
@@ -1,26 +1,24 @@
|
|||||||
use std::ffi::CStr;
|
|
||||||
use std::ptr;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use deltachat_derive::{FromSql, ToSql};
|
use deltachat_derive::{FromSql, ToSql};
|
||||||
use mmime::clist::*;
|
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
|
|
||||||
use crate::chat;
|
use crate::chat;
|
||||||
|
use crate::config::Config;
|
||||||
use crate::configure::*;
|
use crate::configure::*;
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::dc_imex::*;
|
|
||||||
use crate::dc_mimefactory::*;
|
|
||||||
use crate::dc_tools::*;
|
use crate::dc_tools::*;
|
||||||
|
use crate::error::Error;
|
||||||
use crate::events::Event;
|
use crate::events::Event;
|
||||||
use crate::imap::*;
|
use crate::imap::*;
|
||||||
|
use crate::imex::*;
|
||||||
use crate::location;
|
use crate::location;
|
||||||
use crate::login_param::LoginParam;
|
use crate::login_param::LoginParam;
|
||||||
use crate::message::*;
|
use crate::message::{self, Message, MessageState};
|
||||||
|
use crate::mimefactory::{vec_contains_lowercase, Loaded, MimeFactory};
|
||||||
use crate::param::*;
|
use crate::param::*;
|
||||||
use crate::sql;
|
use crate::sql;
|
||||||
use crate::x::*;
|
|
||||||
|
|
||||||
/// Thread IDs
|
/// Thread IDs
|
||||||
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
|
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
|
||||||
@@ -135,20 +133,20 @@ impl Job {
|
|||||||
let connected = context.smtp.lock().unwrap().connect(context, &loginparam);
|
let connected = context.smtp.lock().unwrap().connect(context, &loginparam);
|
||||||
|
|
||||||
if !connected {
|
if !connected {
|
||||||
self.try_again_later(3i32, None);
|
self.try_again_later(3, None);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(filename) = self.param.get(Param::File) {
|
if let Some(filename) = self.param.get(Param::File) {
|
||||||
if let Some(body) = dc_read_file_safe(context, filename) {
|
if let Ok(body) = dc_read_file(context, filename) {
|
||||||
if let Some(recipients) = self.param.get(Param::Recipients) {
|
if let Some(recipients) = self.param.get(Param::Recipients) {
|
||||||
let recipients_list = recipients
|
let recipients_list = recipients
|
||||||
.split("\x1e")
|
.split('\x1e')
|
||||||
.filter_map(|addr| match lettre::EmailAddress::new(addr.to_string()) {
|
.filter_map(|addr| match lettre::EmailAddress::new(addr.to_string()) {
|
||||||
Ok(addr) => Some(addr),
|
Ok(addr) => Some(addr),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("WARNING: invalid recipient: {} {:?}", addr, err);
|
warn!(context, "invalid recipient: {} {:?}", addr, err);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -157,42 +155,48 @@ impl Job {
|
|||||||
/* if there is a msg-id and it does not exist in the db, cancel sending.
|
/* if there is a msg-id and it does not exist in the db, cancel sending.
|
||||||
this happends if dc_delete_msgs() was called
|
this happends if dc_delete_msgs() was called
|
||||||
before the generated mime was sent out */
|
before the generated mime was sent out */
|
||||||
if 0 != self.foreign_id && !dc_msg_exists(context, self.foreign_id) {
|
if 0 != self.foreign_id && !message::exists(context, self.foreign_id) {
|
||||||
warn!(
|
warn!(
|
||||||
context,
|
context,
|
||||||
"Message {} for job {} does not exist", self.foreign_id, self.job_id,
|
"Not sending Message {} as it was deleted", self.foreign_id
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// hold the smtp lock during sending of a job and
|
// hold the smtp lock during sending of a job and
|
||||||
// its ok/error response processing. Note that if a message
|
// its ok/error response processing. Note that if a message
|
||||||
// was sent we need to mark it in the database as we
|
// was sent we need to mark it in the database ASAP as we
|
||||||
// otherwise might send it twice.
|
// otherwise might send it twice.
|
||||||
let mut sock = context.smtp.lock().unwrap();
|
let mut sock = context.smtp.lock().unwrap();
|
||||||
if 0 == sock.send(context, recipients_list, body) {
|
match sock.send(context, recipients_list, body) {
|
||||||
sock.disconnect();
|
Err(err) => {
|
||||||
self.try_again_later(-1i32, sock.error.clone());
|
sock.disconnect();
|
||||||
} else {
|
warn!(context, "smtp failed: {}", err);
|
||||||
dc_delete_file(context, filename);
|
self.try_again_later(-1, Some(err.to_string()));
|
||||||
if 0 != self.foreign_id {
|
}
|
||||||
dc_update_msg_state(
|
Ok(()) => {
|
||||||
context,
|
// smtp success, update db ASAP, then delete smtp file
|
||||||
self.foreign_id,
|
if 0 != self.foreign_id {
|
||||||
MessageState::OutDelivered,
|
message::update_msg_state(
|
||||||
);
|
|
||||||
let chat_id: i32 = context
|
|
||||||
.sql
|
|
||||||
.query_get_value(
|
|
||||||
context,
|
context,
|
||||||
"SELECT chat_id FROM msgs WHERE id=?",
|
self.foreign_id,
|
||||||
params![self.foreign_id as i32],
|
MessageState::OutDelivered,
|
||||||
)
|
);
|
||||||
.unwrap_or_default();
|
let chat_id: i32 = context
|
||||||
context.call_cb(Event::MsgDelivered {
|
.sql
|
||||||
chat_id: chat_id as u32,
|
.query_get_value(
|
||||||
msg_id: self.foreign_id,
|
context,
|
||||||
});
|
"SELECT chat_id FROM msgs WHERE id=?",
|
||||||
|
params![self.foreign_id as i32],
|
||||||
|
)
|
||||||
|
.unwrap_or_default();
|
||||||
|
context.call_cb(Event::MsgDelivered {
|
||||||
|
chat_id: chat_id as u32,
|
||||||
|
msg_id: self.foreign_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// now also delete the generated file
|
||||||
|
dc_delete_file(context, filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -203,60 +207,51 @@ impl Job {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// this value does not increase the number of tries
|
// this value does not increase the number of tries
|
||||||
fn try_again_later(&mut self, try_again: libc::c_int, pending_error: Option<String>) {
|
fn try_again_later(&mut self, try_again: i32, pending_error: Option<String>) {
|
||||||
self.try_again = try_again;
|
self.try_again = try_again;
|
||||||
self.pending_error = pending_error;
|
self.pending_error = pending_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn do_DC_JOB_MOVE_MSG(&mut self, context: &Context) {
|
fn do_DC_JOB_MOVE_MSG(&mut self, context: &Context) {
|
||||||
let ok_to_continue;
|
|
||||||
let mut dest_uid = 0;
|
|
||||||
|
|
||||||
let inbox = context.inbox.read().unwrap();
|
let inbox = context.inbox.read().unwrap();
|
||||||
|
|
||||||
if !inbox.is_connected() {
|
if let Ok(msg) = Message::load_from_db(context, self.foreign_id) {
|
||||||
connect_to_inbox(context, &inbox);
|
if context
|
||||||
if !inbox.is_connected() {
|
.sql
|
||||||
self.try_again_later(3, None);
|
.get_raw_config_int(context, "folders_configured")
|
||||||
ok_to_continue = false;
|
.unwrap_or_default()
|
||||||
} else {
|
< 3
|
||||||
ok_to_continue = true;
|
{
|
||||||
|
inbox.configure_folders(context, 0x1i32);
|
||||||
}
|
}
|
||||||
} else {
|
let dest_folder = context
|
||||||
ok_to_continue = true;
|
.sql
|
||||||
}
|
.get_raw_config(context, "configured_mvbox_folder");
|
||||||
if ok_to_continue {
|
|
||||||
if let Ok(msg) = dc_msg_load_from_db(context, self.foreign_id) {
|
|
||||||
if context
|
|
||||||
.sql
|
|
||||||
.get_config_int(context, "folders_configured")
|
|
||||||
.unwrap_or_default()
|
|
||||||
< 3
|
|
||||||
{
|
|
||||||
inbox.configure_folders(context, 0x1i32);
|
|
||||||
}
|
|
||||||
let dest_folder = context.sql.get_config(context, "configured_mvbox_folder");
|
|
||||||
|
|
||||||
if let Some(dest_folder) = dest_folder {
|
if let Some(dest_folder) = dest_folder {
|
||||||
let server_folder = msg.server_folder.as_ref().unwrap();
|
let server_folder = msg.server_folder.as_ref().unwrap();
|
||||||
|
let mut dest_uid = 0;
|
||||||
|
|
||||||
match inbox.mv(
|
match inbox.mv(
|
||||||
context,
|
context,
|
||||||
server_folder,
|
server_folder,
|
||||||
msg.server_uid,
|
msg.server_uid,
|
||||||
&dest_folder,
|
&dest_folder,
|
||||||
&mut dest_uid,
|
&mut dest_uid,
|
||||||
) as libc::c_uint
|
) {
|
||||||
{
|
ImapResult::RetryLater => {
|
||||||
1 => {
|
self.try_again_later(3i32, None);
|
||||||
self.try_again_later(3i32, None);
|
|
||||||
}
|
|
||||||
3 => {
|
|
||||||
dc_update_server_uid(context, msg.rfc724_mid, &dest_folder, dest_uid);
|
|
||||||
}
|
|
||||||
0 | 2 | _ => {}
|
|
||||||
}
|
}
|
||||||
|
ImapResult::Success => {
|
||||||
|
message::update_server_uid(
|
||||||
|
context,
|
||||||
|
&msg.rfc724_mid,
|
||||||
|
&dest_folder,
|
||||||
|
dest_uid,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ImapResult::Failed | ImapResult::AlreadyDone => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,101 +259,53 @@ impl Job {
|
|||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn do_DC_JOB_DELETE_MSG_ON_IMAP(&mut self, context: &Context) {
|
fn do_DC_JOB_DELETE_MSG_ON_IMAP(&mut self, context: &Context) {
|
||||||
let mut delete_from_server = 1;
|
|
||||||
let inbox = context.inbox.read().unwrap();
|
let inbox = context.inbox.read().unwrap();
|
||||||
|
|
||||||
if let Ok(mut msg) = dc_msg_load_from_db(context, self.foreign_id) {
|
if let Ok(mut msg) = Message::load_from_db(context, self.foreign_id) {
|
||||||
if !(msg.rfc724_mid.is_null()
|
if !msg.rfc724_mid.is_empty() {
|
||||||
|| unsafe { *msg.rfc724_mid.offset(0isize) as libc::c_int == 0 })
|
|
||||||
{
|
|
||||||
let ok_to_continue1;
|
|
||||||
/* eg. device messages have no Message-ID */
|
/* eg. device messages have no Message-ID */
|
||||||
if dc_rfc724_mid_cnt(context, msg.rfc724_mid) != 1 {
|
if message::rfc724_mid_cnt(context, &msg.rfc724_mid) > 1 {
|
||||||
info!(
|
info!(
|
||||||
context,
|
context,
|
||||||
"The message is deleted from the server when all parts are deleted.",
|
"The message is deleted from the server when all parts are deleted.",
|
||||||
);
|
);
|
||||||
delete_from_server = 0i32
|
|
||||||
}
|
|
||||||
/* if this is the last existing part of the message, we delete the message from the server */
|
|
||||||
if 0 != delete_from_server {
|
|
||||||
let ok_to_continue;
|
|
||||||
if !inbox.is_connected() {
|
|
||||||
connect_to_inbox(context, &inbox);
|
|
||||||
if !inbox.is_connected() {
|
|
||||||
self.try_again_later(3i32, None);
|
|
||||||
ok_to_continue = false;
|
|
||||||
} else {
|
|
||||||
ok_to_continue = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ok_to_continue = true;
|
|
||||||
}
|
|
||||||
if ok_to_continue {
|
|
||||||
let mid = unsafe { CStr::from_ptr(msg.rfc724_mid).to_str().unwrap() };
|
|
||||||
let server_folder = msg.server_folder.as_ref().unwrap();
|
|
||||||
if 0 == inbox.delete_msg(context, mid, server_folder, &mut msg.server_uid) {
|
|
||||||
self.try_again_later(-1i32, None);
|
|
||||||
ok_to_continue1 = false;
|
|
||||||
} else {
|
|
||||||
ok_to_continue1 = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ok_to_continue1 = false;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ok_to_continue1 = true;
|
/* if this is the last existing part of the message,
|
||||||
}
|
we delete the message from the server */
|
||||||
if ok_to_continue1 {
|
let mid = msg.rfc724_mid;
|
||||||
dc_delete_msg_from_db(context, msg.id);
|
let server_folder = msg.server_folder.as_ref().unwrap();
|
||||||
|
let res = inbox.delete_msg(context, &mid, server_folder, &mut msg.server_uid);
|
||||||
|
if res == ImapResult::RetryLater {
|
||||||
|
self.try_again_later(-1i32, None);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Message::delete_from_db(context, msg.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn do_DC_JOB_MARKSEEN_MSG_ON_IMAP(&mut self, context: &Context) {
|
fn do_DC_JOB_MARKSEEN_MSG_ON_IMAP(&mut self, context: &Context) {
|
||||||
let ok_to_continue;
|
|
||||||
let inbox = context.inbox.read().unwrap();
|
let inbox = context.inbox.read().unwrap();
|
||||||
|
|
||||||
if !inbox.is_connected() {
|
if let Ok(msg) = Message::load_from_db(context, self.foreign_id) {
|
||||||
connect_to_inbox(context, &inbox);
|
let folder = msg.server_folder.as_ref().unwrap();
|
||||||
if !inbox.is_connected() {
|
match inbox.set_seen(context, folder, msg.server_uid) {
|
||||||
self.try_again_later(3i32, None);
|
ImapResult::RetryLater => {
|
||||||
ok_to_continue = false;
|
self.try_again_later(3i32, None);
|
||||||
} else {
|
}
|
||||||
ok_to_continue = true;
|
ImapResult::AlreadyDone => {}
|
||||||
}
|
ImapResult::Success | ImapResult::Failed => {
|
||||||
} else {
|
// XXX the message might just have been moved
|
||||||
ok_to_continue = true;
|
// we want to send out an MDN anyway
|
||||||
}
|
// The job will not be retried so locally
|
||||||
if ok_to_continue {
|
// there is no risk of double-sending MDNs.
|
||||||
if let Ok(msg) = dc_msg_load_from_db(context, self.foreign_id) {
|
if 0 != msg.param.get_int(Param::WantsMdn).unwrap_or_default()
|
||||||
let server_folder = msg.server_folder.as_ref().unwrap();
|
&& context.get_config_bool(Config::MdnsEnabled)
|
||||||
match inbox.set_seen(context, server_folder, msg.server_uid) as libc::c_uint {
|
{
|
||||||
0 => {}
|
if let Err(err) = send_mdn(context, msg.id) {
|
||||||
1 => {
|
warn!(context, "could not send out mdn for {}: {}", msg.id, err);
|
||||||
self.try_again_later(3i32, None);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
if 0 != msg.param.get_int(Param::WantsMdn).unwrap_or_default()
|
|
||||||
&& 0 != context
|
|
||||||
.sql
|
|
||||||
.get_config_int(context, "mdns_enabled")
|
|
||||||
.unwrap_or_else(|| 1)
|
|
||||||
{
|
|
||||||
let folder = msg.server_folder.as_ref().unwrap();
|
|
||||||
|
|
||||||
match inbox.set_mdnsent(context, folder, msg.server_uid) as libc::c_uint
|
|
||||||
{
|
|
||||||
1 => {
|
|
||||||
self.try_again_later(3i32, None);
|
|
||||||
}
|
|
||||||
3 => {
|
|
||||||
send_mdn(context, msg.id);
|
|
||||||
}
|
|
||||||
0 | 2 | _ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -368,47 +315,35 @@ impl Job {
|
|||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn do_DC_JOB_MARKSEEN_MDN_ON_IMAP(&mut self, context: &Context) {
|
fn do_DC_JOB_MARKSEEN_MDN_ON_IMAP(&mut self, context: &Context) {
|
||||||
let ok_to_continue;
|
|
||||||
let folder = self
|
let folder = self
|
||||||
.param
|
.param
|
||||||
.get(Param::ServerFolder)
|
.get(Param::ServerFolder)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_string();
|
.to_string();
|
||||||
let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32;
|
let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32;
|
||||||
let mut dest_uid = 0;
|
|
||||||
let inbox = context.inbox.read().unwrap();
|
let inbox = context.inbox.read().unwrap();
|
||||||
|
if inbox.set_seen(context, &folder, uid) == ImapResult::RetryLater {
|
||||||
if !inbox.is_connected() {
|
self.try_again_later(3i32, None);
|
||||||
connect_to_inbox(context, &inbox);
|
return;
|
||||||
if !inbox.is_connected() {
|
|
||||||
self.try_again_later(3, None);
|
|
||||||
ok_to_continue = false;
|
|
||||||
} else {
|
|
||||||
ok_to_continue = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ok_to_continue = true;
|
|
||||||
}
|
}
|
||||||
if ok_to_continue {
|
if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() {
|
||||||
if inbox.set_seen(context, &folder, uid) == 0 {
|
if context
|
||||||
self.try_again_later(3i32, None);
|
.sql
|
||||||
|
.get_raw_config_int(context, "folders_configured")
|
||||||
|
.unwrap_or_default()
|
||||||
|
< 3
|
||||||
|
{
|
||||||
|
inbox.configure_folders(context, 0x1i32);
|
||||||
}
|
}
|
||||||
if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() {
|
let dest_folder = context
|
||||||
if context
|
.sql
|
||||||
.sql
|
.get_raw_config(context, "configured_mvbox_folder");
|
||||||
.get_config_int(context, "folders_configured")
|
if let Some(dest_folder) = dest_folder {
|
||||||
.unwrap_or_default()
|
let mut dest_uid = 0;
|
||||||
< 3
|
if ImapResult::RetryLater
|
||||||
|
== inbox.mv(context, &folder, uid, &dest_folder, &mut dest_uid)
|
||||||
{
|
{
|
||||||
inbox.configure_folders(context, 0x1i32);
|
self.try_again_later(3, None);
|
||||||
}
|
|
||||||
let dest_folder = context.sql.get_config(context, "configured_mvbox_folder");
|
|
||||||
if let Some(dest_folder) = dest_folder {
|
|
||||||
if 1 == inbox.mv(context, folder, uid, dest_folder, &mut dest_uid)
|
|
||||||
as libc::c_uint
|
|
||||||
{
|
|
||||||
self.try_again_later(3, None);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -433,12 +368,7 @@ pub fn perform_imap_fetch(context: &Context) {
|
|||||||
if 0 == connect_to_inbox(context, &inbox) {
|
if 0 == connect_to_inbox(context, &inbox) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if context
|
if !context.get_config_bool(Config::InboxWatch) {
|
||||||
.sql
|
|
||||||
.get_config_int(context, "inbox_watch")
|
|
||||||
.unwrap_or_else(|| 1)
|
|
||||||
== 0
|
|
||||||
{
|
|
||||||
info!(context, "INBOX-watch disabled.",);
|
info!(context, "INBOX-watch disabled.",);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -473,29 +403,23 @@ pub fn perform_imap_idle(context: &Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn perform_mvbox_fetch(context: &Context) {
|
pub fn perform_mvbox_fetch(context: &Context) {
|
||||||
let use_network = context
|
let use_network = context.get_config_bool(Config::MvboxWatch);
|
||||||
.sql
|
|
||||||
.get_config_int(context, "mvbox_watch")
|
|
||||||
.unwrap_or_else(|| 1);
|
|
||||||
|
|
||||||
context
|
context
|
||||||
.mvbox_thread
|
.mvbox_thread
|
||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.fetch(context, use_network == 1);
|
.fetch(context, use_network);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn perform_mvbox_idle(context: &Context) {
|
pub fn perform_mvbox_idle(context: &Context) {
|
||||||
let use_network = context
|
let use_network = context.get_config_bool(Config::MvboxWatch);
|
||||||
.sql
|
|
||||||
.get_config_int(context, "mvbox_watch")
|
|
||||||
.unwrap_or_else(|| 1);
|
|
||||||
|
|
||||||
context
|
context
|
||||||
.mvbox_thread
|
.mvbox_thread
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.idle(context, use_network == 1);
|
.idle(context, use_network);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn interrupt_mvbox_idle(context: &Context) {
|
pub fn interrupt_mvbox_idle(context: &Context) {
|
||||||
@@ -503,29 +427,23 @@ pub fn interrupt_mvbox_idle(context: &Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn perform_sentbox_fetch(context: &Context) {
|
pub fn perform_sentbox_fetch(context: &Context) {
|
||||||
let use_network = context
|
let use_network = context.get_config_bool(Config::SentboxWatch);
|
||||||
.sql
|
|
||||||
.get_config_int(context, "sentbox_watch")
|
|
||||||
.unwrap_or_else(|| 1);
|
|
||||||
|
|
||||||
context
|
context
|
||||||
.sentbox_thread
|
.sentbox_thread
|
||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.fetch(context, use_network == 1);
|
.fetch(context, use_network);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn perform_sentbox_idle(context: &Context) {
|
pub fn perform_sentbox_idle(context: &Context) {
|
||||||
let use_network = context
|
let use_network = context.get_config_bool(Config::SentboxWatch);
|
||||||
.sql
|
|
||||||
.get_config_int(context, "sentbox_watch")
|
|
||||||
.unwrap_or_else(|| 1);
|
|
||||||
|
|
||||||
context
|
context
|
||||||
.sentbox_thread
|
.sentbox_thread
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.idle(context, use_network == 1);
|
.idle(context, use_network);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn interrupt_sentbox_idle(context: &Context) {
|
pub fn interrupt_sentbox_idle(context: &Context) {
|
||||||
@@ -642,114 +560,108 @@ pub fn job_action_exists(context: &Context, action: Action) -> bool {
|
|||||||
|
|
||||||
/* special case for DC_JOB_SEND_MSG_TO_SMTP */
|
/* special case for DC_JOB_SEND_MSG_TO_SMTP */
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
|
pub fn job_send_msg(context: &Context, msg_id: u32) -> Result<(), Error> {
|
||||||
let mut success = 0;
|
let mut mimefactory = MimeFactory::load_msg(context, msg_id)?;
|
||||||
|
|
||||||
/* load message data */
|
if chat::msgtype_has_file(mimefactory.msg.type_0) {
|
||||||
let mimefactory = dc_mimefactory_load_msg(context, msg_id);
|
let file_param = mimefactory
|
||||||
if mimefactory.is_err() || mimefactory.as_ref().unwrap().from_addr.is_null() {
|
.msg
|
||||||
warn!(
|
.param
|
||||||
context,
|
.get(Param::File)
|
||||||
"Cannot load data to send, maybe the message is deleted in between.",
|
.map(|s| s.to_string());
|
||||||
);
|
if let Some(pathNfilename) = file_param {
|
||||||
} else {
|
if (mimefactory.msg.type_0 == Viewtype::Image
|
||||||
let mut mimefactory = mimefactory.unwrap();
|
|| mimefactory.msg.type_0 == Viewtype::Gif)
|
||||||
// no redo, no IMAP. moreover, as the data does not exist, there is no need in calling dc_set_msg_failed()
|
&& !mimefactory.msg.param.exists(Param::Width)
|
||||||
if chat::msgtype_has_file(mimefactory.msg.type_0) {
|
|
||||||
let file_param = mimefactory
|
|
||||||
.msg
|
|
||||||
.param
|
|
||||||
.get(Param::File)
|
|
||||||
.map(|s| s.to_string());
|
|
||||||
if let Some(pathNfilename) = file_param {
|
|
||||||
if (mimefactory.msg.type_0 == Viewtype::Image
|
|
||||||
|| mimefactory.msg.type_0 == Viewtype::Gif)
|
|
||||||
&& !mimefactory.msg.param.exists(Param::Width)
|
|
||||||
{
|
|
||||||
mimefactory.msg.param.set_int(Param::Width, 0);
|
|
||||||
mimefactory.msg.param.set_int(Param::Height, 0);
|
|
||||||
|
|
||||||
if let Some(buf) = dc_read_file_safe(context, pathNfilename) {
|
|
||||||
if let Ok((width, height)) = dc_get_filemeta(&buf) {
|
|
||||||
mimefactory.msg.param.set_int(Param::Width, width as i32);
|
|
||||||
mimefactory.msg.param.set_int(Param::Height, height as i32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dc_msg_save_param_to_disk(context, &mut mimefactory.msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* create message */
|
|
||||||
if !dc_mimefactory_render(context, &mut mimefactory) {
|
|
||||||
dc_set_msg_failed(context, msg_id, as_opt_str(mimefactory.error));
|
|
||||||
} else if 0
|
|
||||||
!= mimefactory
|
|
||||||
.msg
|
|
||||||
.param
|
|
||||||
.get_int(Param::GuranteeE2ee)
|
|
||||||
.unwrap_or_default()
|
|
||||||
&& 0 == mimefactory.out_encrypted
|
|
||||||
{
|
|
||||||
warn!(
|
|
||||||
context,
|
|
||||||
"e2e encryption unavailable {} - {:?}",
|
|
||||||
msg_id,
|
|
||||||
mimefactory.msg.param.get_int(Param::GuranteeE2ee),
|
|
||||||
);
|
|
||||||
dc_set_msg_failed(
|
|
||||||
context,
|
|
||||||
msg_id,
|
|
||||||
Some("End-to-end-encryption unavailable unexpectedly."),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
/* unrecoverable */
|
|
||||||
if !clist_search_string_nocase(mimefactory.recipients_addr, mimefactory.from_addr) {
|
|
||||||
clist_insert_after(
|
|
||||||
mimefactory.recipients_names,
|
|
||||||
(*mimefactory.recipients_names).last,
|
|
||||||
ptr::null_mut(),
|
|
||||||
);
|
|
||||||
clist_insert_after(
|
|
||||||
mimefactory.recipients_addr,
|
|
||||||
(*mimefactory.recipients_addr).last,
|
|
||||||
dc_strdup(mimefactory.from_addr) as *mut libc::c_void,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if 0 != mimefactory.out_gossiped {
|
|
||||||
chat::set_gossiped_timestamp(context, mimefactory.msg.chat_id, time());
|
|
||||||
}
|
|
||||||
if 0 != mimefactory.out_last_added_location_id {
|
|
||||||
if let Err(err) =
|
|
||||||
location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
|
|
||||||
{
|
|
||||||
error!(context, "Failed to set kml sent_timestamp: {:?}", err);
|
|
||||||
}
|
|
||||||
if !mimefactory.msg.hidden {
|
|
||||||
if let Err(err) = location::set_msg_location_id(
|
|
||||||
context,
|
|
||||||
mimefactory.msg.id,
|
|
||||||
mimefactory.out_last_added_location_id,
|
|
||||||
) {
|
|
||||||
error!(context, "Failed to set msg_location_id: {:?}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if 0 != mimefactory.out_encrypted
|
|
||||||
&& mimefactory
|
|
||||||
.msg
|
|
||||||
.param
|
|
||||||
.get_int(Param::GuranteeE2ee)
|
|
||||||
.unwrap_or_default()
|
|
||||||
== 0
|
|
||||||
{
|
{
|
||||||
mimefactory.msg.param.set_int(Param::GuranteeE2ee, 1);
|
mimefactory.msg.param.set_int(Param::Width, 0);
|
||||||
dc_msg_save_param_to_disk(context, &mut mimefactory.msg);
|
mimefactory.msg.param.set_int(Param::Height, 0);
|
||||||
|
|
||||||
|
if let Ok(buf) = dc_read_file(context, pathNfilename) {
|
||||||
|
if let Ok((width, height)) = dc_get_filemeta(&buf) {
|
||||||
|
mimefactory.msg.param.set_int(Param::Width, width as i32);
|
||||||
|
mimefactory.msg.param.set_int(Param::Height, height as i32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mimefactory.msg.save_param_to_disk(context);
|
||||||
}
|
}
|
||||||
success = add_smtp_job(context, Action::SendMsgToSmtp, &mut mimefactory);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
success
|
/* create message */
|
||||||
|
if let Err(msg) = unsafe { mimefactory.render() } {
|
||||||
|
let e = msg.to_string();
|
||||||
|
message::set_msg_failed(context, msg_id, Some(e));
|
||||||
|
return Err(msg);
|
||||||
|
}
|
||||||
|
if 0 != mimefactory
|
||||||
|
.msg
|
||||||
|
.param
|
||||||
|
.get_int(Param::GuranteeE2ee)
|
||||||
|
.unwrap_or_default()
|
||||||
|
&& !mimefactory.out_encrypted
|
||||||
|
{
|
||||||
|
/* unrecoverable */
|
||||||
|
message::set_msg_failed(
|
||||||
|
context,
|
||||||
|
msg_id,
|
||||||
|
Some("End-to-end-encryption unavailable unexpectedly."),
|
||||||
|
);
|
||||||
|
bail!(
|
||||||
|
"e2e encryption unavailable {} - {:?}",
|
||||||
|
msg_id,
|
||||||
|
mimefactory.msg.param.get_int(Param::GuranteeE2ee),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if context.get_config_bool(Config::BccSelf)
|
||||||
|
&& !vec_contains_lowercase(&mimefactory.recipients_addr, &mimefactory.from_addr)
|
||||||
|
{
|
||||||
|
mimefactory.recipients_names.push("".to_string());
|
||||||
|
mimefactory
|
||||||
|
.recipients_addr
|
||||||
|
.push(mimefactory.from_addr.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if mimefactory.recipients_addr.is_empty() {
|
||||||
|
warn!(
|
||||||
|
context,
|
||||||
|
"message {} has no recipient, skipping smtp-send", msg_id
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if mimefactory.out_gossiped {
|
||||||
|
chat::set_gossiped_timestamp(context, mimefactory.msg.chat_id, time());
|
||||||
|
}
|
||||||
|
if 0 != mimefactory.out_last_added_location_id {
|
||||||
|
if let Err(err) = location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
|
||||||
|
{
|
||||||
|
error!(context, "Failed to set kml sent_timestamp: {:?}", err);
|
||||||
|
}
|
||||||
|
if !mimefactory.msg.hidden {
|
||||||
|
if let Err(err) = location::set_msg_location_id(
|
||||||
|
context,
|
||||||
|
mimefactory.msg.id,
|
||||||
|
mimefactory.out_last_added_location_id,
|
||||||
|
) {
|
||||||
|
error!(context, "Failed to set msg_location_id: {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mimefactory.out_encrypted
|
||||||
|
&& mimefactory
|
||||||
|
.msg
|
||||||
|
.param
|
||||||
|
.get_int(Param::GuranteeE2ee)
|
||||||
|
.unwrap_or_default()
|
||||||
|
== 0
|
||||||
|
{
|
||||||
|
mimefactory.msg.param.set_int(Param::GuranteeE2ee, 1);
|
||||||
|
mimefactory.msg.save_param_to_disk(context);
|
||||||
|
}
|
||||||
|
add_smtp_job(context, Action::SendMsgToSmtp, &mut mimefactory)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn perform_imap_jobs(context: &Context) {
|
pub fn perform_imap_jobs(context: &Context) {
|
||||||
@@ -763,6 +675,14 @@ pub fn perform_imap_jobs(context: &Context) {
|
|||||||
info!(context, "dc_perform_imap_jobs ended.",);
|
info!(context, "dc_perform_imap_jobs ended.",);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn perform_mvbox_jobs(context: &Context) {
|
||||||
|
info!(context, "dc_perform_mbox_jobs EMPTY (for now).",);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn perform_sentbox_jobs(context: &Context) {
|
||||||
|
info!(context, "dc_perform_sentbox_jobs EMPTY (for now).",);
|
||||||
|
}
|
||||||
|
|
||||||
fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||||
let query = if !probe_network {
|
let query = if !probe_network {
|
||||||
// processing for first-try and after backoff-timeouts:
|
// processing for first-try and after backoff-timeouts:
|
||||||
@@ -831,13 +751,13 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
|||||||
// - they can be re-executed one time AT_ONCE, but they are not save in the database for later execution
|
// - they can be re-executed one time AT_ONCE, but they are not save in the database for later execution
|
||||||
if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
|
if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
|
||||||
job_kill_action(context, job.action);
|
job_kill_action(context, job.action);
|
||||||
&context
|
context
|
||||||
.sentbox_thread
|
.sentbox_thread
|
||||||
.clone()
|
.clone()
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.suspend(context);
|
.suspend(context);
|
||||||
&context
|
context
|
||||||
.mvbox_thread
|
.mvbox_thread
|
||||||
.clone()
|
.clone()
|
||||||
.read()
|
.read()
|
||||||
@@ -861,8 +781,13 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
|||||||
Action::MarkseenMdnOnImap => job.do_DC_JOB_MARKSEEN_MDN_ON_IMAP(context),
|
Action::MarkseenMdnOnImap => job.do_DC_JOB_MARKSEEN_MDN_ON_IMAP(context),
|
||||||
Action::MoveMsg => job.do_DC_JOB_MOVE_MSG(context),
|
Action::MoveMsg => job.do_DC_JOB_MOVE_MSG(context),
|
||||||
Action::SendMdn => job.do_DC_JOB_SEND(context),
|
Action::SendMdn => job.do_DC_JOB_SEND(context),
|
||||||
Action::ConfigureImap => unsafe { dc_job_do_DC_JOB_CONFIGURE_IMAP(context, &job) },
|
Action::ConfigureImap => dc_job_do_DC_JOB_CONFIGURE_IMAP(context),
|
||||||
Action::ImexImap => unsafe { dc_job_do_DC_JOB_IMEX_IMAP(context, &job) },
|
Action::ImexImap => match job_do_DC_JOB_IMEX_IMAP(context, &job) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(err) => {
|
||||||
|
error!(context, "{}", err);
|
||||||
|
}
|
||||||
|
},
|
||||||
Action::MaybeSendLocations => {
|
Action::MaybeSendLocations => {
|
||||||
location::job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context, &job)
|
location::job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context, &job)
|
||||||
}
|
}
|
||||||
@@ -936,7 +861,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if job.action == Action::SendMsgToSmtp {
|
if job.action == Action::SendMsgToSmtp {
|
||||||
dc_set_msg_failed(context, job.foreign_id, job.pending_error.as_ref());
|
message::set_msg_failed(context, job.foreign_id, job.pending_error.as_ref());
|
||||||
}
|
}
|
||||||
job.delete(context);
|
job.delete(context);
|
||||||
}
|
}
|
||||||
@@ -957,8 +882,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn get_backoff_time_offset(c_tries: libc::c_int) -> i64 {
|
fn get_backoff_time_offset(c_tries: libc::c_int) -> i64 {
|
||||||
// results in ~3 weeks for the last backoff timespan
|
// results in ~3 weeks for the last backoff timespan
|
||||||
let mut N = 2_i32.pow((c_tries - 1) as u32);
|
let N = 2_i32.pow((c_tries - 1) as u32) * 60;
|
||||||
N = N * 60;
|
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
let n: i32 = rng.gen();
|
let n: i32 = rng.gen();
|
||||||
let mut seconds = n % (N + 1);
|
let mut seconds = n % (N + 1);
|
||||||
@@ -980,7 +904,7 @@ fn suspend_smtp_thread(context: &Context, suspend: bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
|
pub fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
|
||||||
let ret_connected = dc_connect_to_configured_imap(context, inbox);
|
let ret_connected = dc_connect_to_configured_imap(context, inbox);
|
||||||
if 0 != ret_connected {
|
if 0 != ret_connected {
|
||||||
inbox.set_watch_folder("INBOX".into());
|
inbox.set_watch_folder("INBOX".into());
|
||||||
@@ -988,62 +912,44 @@ fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
|
|||||||
ret_connected
|
ret_connected
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_mdn(context: &Context, msg_id: u32) {
|
fn send_mdn(context: &Context, msg_id: u32) -> Result<(), Error> {
|
||||||
if let Ok(mut mimefactory) = unsafe { dc_mimefactory_load_mdn(context, msg_id) } {
|
let mut mimefactory = MimeFactory::load_mdn(context, msg_id)?;
|
||||||
if unsafe { dc_mimefactory_render(context, &mut mimefactory) } {
|
unsafe { mimefactory.render()? };
|
||||||
add_smtp_job(context, Action::SendMdn, &mut mimefactory);
|
add_smtp_job(context, Action::SendMdn, &mut mimefactory)?;
|
||||||
}
|
|
||||||
}
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn add_smtp_job(context: &Context, action: Action, mimefactory: &dc_mimefactory_t) -> libc::c_int {
|
fn add_smtp_job(context: &Context, action: Action, mimefactory: &MimeFactory) -> Result<(), Error> {
|
||||||
let mut success: libc::c_int = 0i32;
|
ensure!(
|
||||||
let mut recipients: *mut libc::c_char = ptr::null_mut();
|
!mimefactory.recipients_addr.is_empty(),
|
||||||
|
"no recipients for smtp job set"
|
||||||
|
);
|
||||||
let mut param = Params::new();
|
let mut param = Params::new();
|
||||||
let path_filename =
|
|
||||||
dc_get_fine_path_filename(context, "$BLOBDIR", as_str(mimefactory.rfc724_mid));
|
|
||||||
let bytes = unsafe {
|
let bytes = unsafe {
|
||||||
std::slice::from_raw_parts(
|
std::slice::from_raw_parts(
|
||||||
(*mimefactory.out).str_0 as *const u8,
|
(*mimefactory.out).str_0 as *const u8,
|
||||||
(*mimefactory.out).len,
|
(*mimefactory.out).len,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
if !dc_write_file(context, &path_filename, bytes) {
|
let bpath = context.new_blob_file(&mimefactory.rfc724_mid, bytes)?;
|
||||||
error!(
|
let recipients = mimefactory.recipients_addr.join("\x1e");
|
||||||
context,
|
param.set(Param::File, &bpath);
|
||||||
"Could not write message <{}> to \"{}\".",
|
param.set(Param::Recipients, &recipients);
|
||||||
to_string(mimefactory.rfc724_mid),
|
job_add(
|
||||||
path_filename.display(),
|
context,
|
||||||
);
|
action,
|
||||||
} else {
|
(if mimefactory.loaded == Loaded::Message {
|
||||||
recipients = unsafe {
|
mimefactory.msg.id
|
||||||
dc_str_from_clist(
|
} else {
|
||||||
mimefactory.recipients_addr,
|
0
|
||||||
b"\x1e\x00" as *const u8 as *const libc::c_char,
|
}) as libc::c_int,
|
||||||
)
|
param,
|
||||||
};
|
0,
|
||||||
param.set(Param::File, path_filename.to_string_lossy());
|
);
|
||||||
param.set(Param::Recipients, as_str(recipients));
|
|
||||||
job_add(
|
Ok(())
|
||||||
context,
|
|
||||||
action,
|
|
||||||
(if mimefactory.loaded as libc::c_uint
|
|
||||||
== DC_MF_MSG_LOADED as libc::c_int as libc::c_uint
|
|
||||||
{
|
|
||||||
mimefactory.msg.id
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}) as libc::c_int,
|
|
||||||
param,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
success = 1;
|
|
||||||
}
|
|
||||||
unsafe {
|
|
||||||
free(recipients.cast());
|
|
||||||
}
|
|
||||||
success
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn job_add(
|
pub fn job_add(
|
||||||
|
|||||||
@@ -116,14 +116,14 @@ impl JobThread {
|
|||||||
if ret_connected {
|
if ret_connected {
|
||||||
if context
|
if context
|
||||||
.sql
|
.sql
|
||||||
.get_config_int(context, "folders_configured")
|
.get_raw_config_int(context, "folders_configured")
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
< 3
|
< 3
|
||||||
{
|
{
|
||||||
self.imap.configure_folders(context, 0x1);
|
self.imap.configure_folders(context, 0x1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(mvbox_name) = context.sql.get_config(context, self.folder_config_name) {
|
if let Some(mvbox_name) = context.sql.get_raw_config(context, self.folder_config_name) {
|
||||||
self.imap.set_watch_folder(mvbox_name);
|
self.imap.set_watch_folder(mvbox_name);
|
||||||
} else {
|
} else {
|
||||||
self.imap.disconnect(context);
|
self.imap.disconnect(context);
|
||||||
|
|||||||
17
src/key.rs
17
src/key.rs
@@ -2,7 +2,6 @@ use std::collections::BTreeMap;
|
|||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use libc;
|
|
||||||
use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey};
|
use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey};
|
||||||
use pgp::ser::Serialize;
|
use pgp::ser::Serialize;
|
||||||
use pgp::types::{KeyTrait, SecretKeyTrait};
|
use pgp::types::{KeyTrait, SecretKeyTrait};
|
||||||
@@ -164,8 +163,8 @@ impl Key {
|
|||||||
|
|
||||||
pub fn to_bytes(&self) -> Vec<u8> {
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
match self {
|
match self {
|
||||||
Key::Public(k) => k.to_bytes().unwrap(),
|
Key::Public(k) => k.to_bytes().unwrap_or_default(),
|
||||||
Key::Secret(k) => k.to_bytes().unwrap(),
|
Key::Secret(k) => k.to_bytes().unwrap_or_default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,10 +218,10 @@ impl Key {
|
|||||||
let file_content = self.to_asc(None).into_bytes();
|
let file_content = self.to_asc(None).into_bytes();
|
||||||
|
|
||||||
if dc_write_file(context, &file, &file_content) {
|
if dc_write_file(context, &file, &file_content) {
|
||||||
return true;
|
true
|
||||||
} else {
|
} else {
|
||||||
error!(context, "Cannot write key to {}", file.as_ref().display());
|
error!(context, "Cannot write key to {}", file.as_ref().display());
|
||||||
return false;
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,14 +253,14 @@ pub fn dc_key_save_self_keypair(
|
|||||||
public_key: &Key,
|
public_key: &Key,
|
||||||
private_key: &Key,
|
private_key: &Key,
|
||||||
addr: impl AsRef<str>,
|
addr: impl AsRef<str>,
|
||||||
is_default: libc::c_int,
|
is_default: bool,
|
||||||
sql: &Sql,
|
sql: &Sql,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
sql::execute(
|
sql::execute(
|
||||||
context,
|
context,
|
||||||
sql,
|
sql,
|
||||||
"INSERT INTO keypairs (addr, is_default, public_key, private_key, created) VALUES (?,?,?,?,?);",
|
"INSERT INTO keypairs (addr, is_default, public_key, private_key, created) VALUES (?,?,?,?,?);",
|
||||||
params![addr.as_ref(), is_default, public_key.to_bytes(), private_key.to_bytes(), time()],
|
params![addr.as_ref(), is_default as i32, public_key.to_bytes(), private_key.to_bytes(), time()],
|
||||||
).is_ok()
|
).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,7 +381,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
|||||||
#[test]
|
#[test]
|
||||||
#[ignore] // is too expensive
|
#[ignore] // is too expensive
|
||||||
fn test_from_slice_roundtrip() {
|
fn test_from_slice_roundtrip() {
|
||||||
let (public_key, private_key) = crate::pgp::dc_pgp_create_keypair("hello").unwrap();
|
let (public_key, private_key) = crate::pgp::create_keypair("hello").unwrap();
|
||||||
|
|
||||||
let binary = public_key.to_bytes();
|
let binary = public_key.to_bytes();
|
||||||
let public_key2 = Key::from_slice(&binary, KeyType::Public).expect("invalid public key");
|
let public_key2 = Key::from_slice(&binary, KeyType::Public).expect("invalid public key");
|
||||||
@@ -417,7 +416,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
|||||||
#[test]
|
#[test]
|
||||||
#[ignore] // is too expensive
|
#[ignore] // is too expensive
|
||||||
fn test_ascii_roundtrip() {
|
fn test_ascii_roundtrip() {
|
||||||
let (public_key, private_key) = crate::pgp::dc_pgp_create_keypair("hello").unwrap();
|
let (public_key, private_key) = crate::pgp::create_keypair("hello").unwrap();
|
||||||
|
|
||||||
let s = public_key.to_armored_string(None).unwrap();
|
let s = public_key.to_armored_string(None).unwrap();
|
||||||
let (public_key2, _) =
|
let (public_key2, _) =
|
||||||
|
|||||||
15
src/lib.rs
15
src/lib.rs
@@ -39,35 +39,36 @@ pub mod contact;
|
|||||||
pub mod context;
|
pub mod context;
|
||||||
mod e2ee;
|
mod e2ee;
|
||||||
mod imap;
|
mod imap;
|
||||||
|
pub mod imex;
|
||||||
pub mod job;
|
pub mod job;
|
||||||
mod job_thread;
|
mod job_thread;
|
||||||
pub mod key;
|
pub mod key;
|
||||||
pub mod keyring;
|
pub mod keyring;
|
||||||
pub mod location;
|
pub mod location;
|
||||||
|
mod login_param;
|
||||||
pub mod lot;
|
pub mod lot;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
|
mod mimefactory;
|
||||||
pub mod oauth2;
|
pub mod oauth2;
|
||||||
mod param;
|
mod param;
|
||||||
pub mod peerstate;
|
pub mod peerstate;
|
||||||
pub mod pgp;
|
pub mod pgp;
|
||||||
pub mod qr;
|
pub mod qr;
|
||||||
|
pub mod securejoin;
|
||||||
mod smtp;
|
mod smtp;
|
||||||
pub mod sql;
|
pub mod sql;
|
||||||
mod stock;
|
pub mod stock;
|
||||||
pub mod x;
|
mod token;
|
||||||
|
#[macro_use]
|
||||||
|
mod wrapmime;
|
||||||
|
|
||||||
pub mod dc_array;
|
pub mod dc_array;
|
||||||
mod dc_dehtml;
|
mod dc_dehtml;
|
||||||
pub mod dc_imex;
|
|
||||||
mod dc_mimefactory;
|
|
||||||
pub mod dc_mimeparser;
|
pub mod dc_mimeparser;
|
||||||
pub mod dc_receive_imf;
|
pub mod dc_receive_imf;
|
||||||
mod dc_simplify;
|
mod dc_simplify;
|
||||||
mod dc_strencode;
|
mod dc_strencode;
|
||||||
pub mod dc_tools;
|
pub mod dc_tools;
|
||||||
mod login_param;
|
|
||||||
pub mod securejoin;
|
|
||||||
mod token;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_utils;
|
mod test_utils;
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ use quick_xml;
|
|||||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||||
|
|
||||||
use crate::chat;
|
use crate::chat;
|
||||||
|
use crate::config::Config;
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::context::*;
|
use crate::context::*;
|
||||||
use crate::dc_tools::*;
|
use crate::dc_tools::*;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::events::Event;
|
use crate::events::Event;
|
||||||
use crate::job::*;
|
use crate::job::*;
|
||||||
use crate::message::*;
|
use crate::message::Message;
|
||||||
use crate::param::*;
|
use crate::param::*;
|
||||||
use crate::sql;
|
use crate::sql;
|
||||||
use crate::stock::StockMessage;
|
use crate::stock::StockMessage;
|
||||||
@@ -62,7 +63,7 @@ impl Kml {
|
|||||||
|
|
||||||
pub fn parse(context: &Context, content: impl AsRef<str>) -> Result<Self, Error> {
|
pub fn parse(context: &Context, content: impl AsRef<str>) -> Result<Self, Error> {
|
||||||
ensure!(
|
ensure!(
|
||||||
content.as_ref().len() <= (1 * 1024 * 1024),
|
content.as_ref().len() <= (1024 * 1024),
|
||||||
"A kml-files with {} bytes is larger than reasonably expected.",
|
"A kml-files with {} bytes is larger than reasonably expected.",
|
||||||
content.as_ref().len()
|
content.as_ref().len()
|
||||||
);
|
);
|
||||||
@@ -196,7 +197,7 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
|
|||||||
let now = time();
|
let now = time();
|
||||||
let mut msg: Message;
|
let mut msg: Message;
|
||||||
let is_sending_locations_before: bool;
|
let is_sending_locations_before: bool;
|
||||||
if !(seconds < 0 || chat_id <= 9i32 as libc::c_uint) {
|
if !(seconds < 0 || chat_id <= DC_CHAT_ID_LAST_SPECIAL) {
|
||||||
is_sending_locations_before = is_sending_locations_to_chat(context, chat_id);
|
is_sending_locations_before = is_sending_locations_to_chat(context, chat_id);
|
||||||
if sql::execute(
|
if sql::execute(
|
||||||
context,
|
context,
|
||||||
@@ -214,11 +215,11 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
|
|||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
if 0 != seconds && !is_sending_locations_before {
|
if 0 != seconds && !is_sending_locations_before {
|
||||||
msg = dc_msg_new(Viewtype::Text);
|
msg = Message::new(Viewtype::Text);
|
||||||
msg.text =
|
msg.text =
|
||||||
Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0));
|
Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0));
|
||||||
msg.param.set_int(Param::Cmd, 8);
|
msg.param.set_int(Param::Cmd, 8);
|
||||||
chat::send_msg(context, chat_id, &mut msg).unwrap();
|
chat::send_msg(context, chat_id, &mut msg).unwrap_or_default();
|
||||||
} else if 0 == seconds && is_sending_locations_before {
|
} else if 0 == seconds && is_sending_locations_before {
|
||||||
let stock_str =
|
let stock_str =
|
||||||
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
||||||
@@ -226,7 +227,7 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
|
|||||||
}
|
}
|
||||||
context.call_cb(Event::ChatModified(chat_id));
|
context.call_cb(Event::ChatModified(chat_id));
|
||||||
if 0 != seconds {
|
if 0 != seconds {
|
||||||
schedule_MAYBE_SEND_LOCATIONS(context, 0i32);
|
schedule_MAYBE_SEND_LOCATIONS(context, false);
|
||||||
job_add(
|
job_add(
|
||||||
context,
|
context,
|
||||||
Action::MaybeSendLocationsEnded,
|
Action::MaybeSendLocationsEnded,
|
||||||
@@ -240,8 +241,8 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn schedule_MAYBE_SEND_LOCATIONS(context: &Context, flags: i32) {
|
fn schedule_MAYBE_SEND_LOCATIONS(context: &Context, force_schedule: bool) {
|
||||||
if 0 != flags & 0x1 || !job_action_exists(context, Action::MaybeSendLocations) {
|
if force_schedule || !job_action_exists(context, Action::MaybeSendLocations) {
|
||||||
job_add(context, Action::MaybeSendLocations, 0, Params::new(), 60);
|
job_add(context, Action::MaybeSendLocations, 0, Params::new(), 60);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -256,9 +257,9 @@ pub fn is_sending_locations_to_chat(context: &Context, chat_id: u32) -> bool {
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> libc::c_int {
|
pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> bool {
|
||||||
if latitude == 0.0 && longitude == 0.0 {
|
if latitude == 0.0 && longitude == 0.0 {
|
||||||
return 1;
|
return true;
|
||||||
}
|
}
|
||||||
let mut continue_streaming = false;
|
let mut continue_streaming = false;
|
||||||
|
|
||||||
@@ -289,10 +290,10 @@ pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> l
|
|||||||
if continue_streaming {
|
if continue_streaming {
|
||||||
context.call_cb(Event::LocationChanged(Some(1)));
|
context.call_cb(Event::LocationChanged(Some(1)));
|
||||||
};
|
};
|
||||||
schedule_MAYBE_SEND_LOCATIONS(context, 0);
|
schedule_MAYBE_SEND_LOCATIONS(context, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue_streaming as libc::c_int
|
continue_streaming
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_range(
|
pub fn get_range(
|
||||||
@@ -359,7 +360,7 @@ pub fn get_range(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_marker(txt: &str) -> bool {
|
fn is_marker(txt: &str) -> bool {
|
||||||
txt.len() == 1 && txt.chars().next().unwrap() != ' '
|
txt.len() == 1 && !txt.starts_with(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_all(context: &Context) -> Result<(), Error> {
|
pub fn delete_all(context: &Context) -> Result<(), Error> {
|
||||||
@@ -375,8 +376,7 @@ pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error>
|
|||||||
let mut last_added_location_id = 0;
|
let mut last_added_location_id = 0;
|
||||||
|
|
||||||
let self_addr = context
|
let self_addr = context
|
||||||
.sql
|
.get_config(Config::ConfiguredAddr)
|
||||||
.get_config(context, "configured_addr")
|
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row(
|
let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row(
|
||||||
@@ -540,7 +540,7 @@ pub fn save(
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
|
pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
|
||||||
let now = time();
|
let now = time();
|
||||||
let mut continue_streaming: libc::c_int = 1;
|
let mut continue_streaming = false;
|
||||||
info!(
|
info!(
|
||||||
context,
|
context,
|
||||||
" ----------------- MAYBE_SEND_LOCATIONS -------------- ",
|
" ----------------- MAYBE_SEND_LOCATIONS -------------- ",
|
||||||
@@ -555,7 +555,7 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
|
|||||||
let chat_id: i32 = row.get(0)?;
|
let chat_id: i32 = row.get(0)?;
|
||||||
let locations_send_begin: i64 = row.get(1)?;
|
let locations_send_begin: i64 = row.get(1)?;
|
||||||
let locations_last_sent: i64 = row.get(2)?;
|
let locations_last_sent: i64 = row.get(2)?;
|
||||||
continue_streaming = 1;
|
continue_streaming = true;
|
||||||
|
|
||||||
// be a bit tolerant as the timer may not align exactly with time(NULL)
|
// be a bit tolerant as the timer may not align exactly with time(NULL)
|
||||||
if now - locations_last_sent < (60 - 3) {
|
if now - locations_last_sent < (60 - 3) {
|
||||||
@@ -601,7 +601,7 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
|
|||||||
// the easiest way to determine this, is to check for an empty message queue.
|
// the easiest way to determine this, is to check for an empty message queue.
|
||||||
// (might not be 100%, however, as positions are sent combined later
|
// (might not be 100%, however, as positions are sent combined later
|
||||||
// and dc_set_location() is typically called periodically, this is ok)
|
// and dc_set_location() is typically called periodically, this is ok)
|
||||||
let mut msg = dc_msg_new(Viewtype::Text);
|
let mut msg = Message::new(Viewtype::Text);
|
||||||
msg.hidden = true;
|
msg.hidden = true;
|
||||||
msg.param.set_int(Param::Cmd, 9);
|
msg.param.set_int(Param::Cmd, 9);
|
||||||
Some((chat_id, msg))
|
Some((chat_id, msg))
|
||||||
@@ -615,11 +615,11 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
|
|||||||
|
|
||||||
for (chat_id, mut msg) in msgs.into_iter() {
|
for (chat_id, mut msg) in msgs.into_iter() {
|
||||||
// TODO: better error handling
|
// TODO: better error handling
|
||||||
chat::send_msg(context, chat_id as u32, &mut msg).unwrap();
|
chat::send_msg(context, chat_id as u32, &mut msg).unwrap_or_default();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if 0 != continue_streaming {
|
if continue_streaming {
|
||||||
schedule_MAYBE_SEND_LOCATIONS(context, 0x1);
|
schedule_MAYBE_SEND_LOCATIONS(context, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,22 @@ use std::fmt;
|
|||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Display, FromPrimitive)]
|
||||||
|
#[repr(i32)]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
|
pub enum CertificateChecks {
|
||||||
|
Automatic = 0,
|
||||||
|
Strict = 1,
|
||||||
|
AcceptInvalidHostnames = 2,
|
||||||
|
AcceptInvalidCertificates = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CertificateChecks {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Automatic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct LoginParam {
|
pub struct LoginParam {
|
||||||
pub addr: String,
|
pub addr: String,
|
||||||
@@ -11,10 +27,14 @@ pub struct LoginParam {
|
|||||||
pub mail_user: String,
|
pub mail_user: String,
|
||||||
pub mail_pw: String,
|
pub mail_pw: String,
|
||||||
pub mail_port: i32,
|
pub mail_port: i32,
|
||||||
|
/// IMAP TLS options: whether to allow invalid certificates and/or invalid hostnames
|
||||||
|
pub imap_certificate_checks: CertificateChecks,
|
||||||
pub send_server: String,
|
pub send_server: String,
|
||||||
pub send_user: String,
|
pub send_user: String,
|
||||||
pub send_pw: String,
|
pub send_pw: String,
|
||||||
pub send_port: i32,
|
pub send_port: i32,
|
||||||
|
/// SMTP TLS options: whether to allow invalid certificates and/or invalid hostnames
|
||||||
|
pub smtp_certificate_checks: CertificateChecks,
|
||||||
pub server_flags: i32,
|
pub server_flags: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,37 +51,53 @@ impl LoginParam {
|
|||||||
|
|
||||||
let key = format!("{}addr", prefix);
|
let key = format!("{}addr", prefix);
|
||||||
let addr = sql
|
let addr = sql
|
||||||
.get_config(context, key)
|
.get_raw_config(context, key)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.trim()
|
.trim()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let key = format!("{}mail_server", prefix);
|
let key = format!("{}mail_server", prefix);
|
||||||
let mail_server = sql.get_config(context, key).unwrap_or_default();
|
let mail_server = sql.get_raw_config(context, key).unwrap_or_default();
|
||||||
|
|
||||||
let key = format!("{}mail_port", prefix);
|
let key = format!("{}mail_port", prefix);
|
||||||
let mail_port = sql.get_config_int(context, key).unwrap_or_default();
|
let mail_port = sql.get_raw_config_int(context, key).unwrap_or_default();
|
||||||
|
|
||||||
let key = format!("{}mail_user", prefix);
|
let key = format!("{}mail_user", prefix);
|
||||||
let mail_user = sql.get_config(context, key).unwrap_or_default();
|
let mail_user = sql.get_raw_config(context, key).unwrap_or_default();
|
||||||
|
|
||||||
let key = format!("{}mail_pw", prefix);
|
let key = format!("{}mail_pw", prefix);
|
||||||
let mail_pw = sql.get_config(context, key).unwrap_or_default();
|
let mail_pw = sql.get_raw_config(context, key).unwrap_or_default();
|
||||||
|
|
||||||
|
let key = format!("{}imap_certificate_checks", prefix);
|
||||||
|
let imap_certificate_checks =
|
||||||
|
if let Some(certificate_checks) = sql.get_raw_config_int(context, key) {
|
||||||
|
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
|
||||||
|
} else {
|
||||||
|
Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
let key = format!("{}send_server", prefix);
|
let key = format!("{}send_server", prefix);
|
||||||
let send_server = sql.get_config(context, key).unwrap_or_default();
|
let send_server = sql.get_raw_config(context, key).unwrap_or_default();
|
||||||
|
|
||||||
let key = format!("{}send_port", prefix);
|
let key = format!("{}send_port", prefix);
|
||||||
let send_port = sql.get_config_int(context, key).unwrap_or_default();
|
let send_port = sql.get_raw_config_int(context, key).unwrap_or_default();
|
||||||
|
|
||||||
let key = format!("{}send_user", prefix);
|
let key = format!("{}send_user", prefix);
|
||||||
let send_user = sql.get_config(context, key).unwrap_or_default();
|
let send_user = sql.get_raw_config(context, key).unwrap_or_default();
|
||||||
|
|
||||||
let key = format!("{}send_pw", prefix);
|
let key = format!("{}send_pw", prefix);
|
||||||
let send_pw = sql.get_config(context, key).unwrap_or_default();
|
let send_pw = sql.get_raw_config(context, key).unwrap_or_default();
|
||||||
|
|
||||||
|
let key = format!("{}smtp_certificate_checks", prefix);
|
||||||
|
let smtp_certificate_checks =
|
||||||
|
if let Some(certificate_checks) = sql.get_raw_config_int(context, key) {
|
||||||
|
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
|
||||||
|
} else {
|
||||||
|
Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
let key = format!("{}server_flags", prefix);
|
let key = format!("{}server_flags", prefix);
|
||||||
let server_flags = sql.get_config_int(context, key).unwrap_or_default();
|
let server_flags = sql.get_raw_config_int(context, key).unwrap_or_default();
|
||||||
|
|
||||||
LoginParam {
|
LoginParam {
|
||||||
addr: addr.to_string(),
|
addr: addr.to_string(),
|
||||||
@@ -69,10 +105,12 @@ impl LoginParam {
|
|||||||
mail_user,
|
mail_user,
|
||||||
mail_pw,
|
mail_pw,
|
||||||
mail_port,
|
mail_port,
|
||||||
|
imap_certificate_checks,
|
||||||
send_server,
|
send_server,
|
||||||
send_user,
|
send_user,
|
||||||
send_pw,
|
send_pw,
|
||||||
send_port,
|
send_port,
|
||||||
|
smtp_certificate_checks,
|
||||||
server_flags,
|
server_flags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,34 +129,40 @@ impl LoginParam {
|
|||||||
let sql = &context.sql;
|
let sql = &context.sql;
|
||||||
|
|
||||||
let key = format!("{}addr", prefix);
|
let key = format!("{}addr", prefix);
|
||||||
sql.set_config(context, key, Some(&self.addr))?;
|
sql.set_raw_config(context, key, Some(&self.addr))?;
|
||||||
|
|
||||||
let key = format!("{}mail_server", prefix);
|
let key = format!("{}mail_server", prefix);
|
||||||
sql.set_config(context, key, Some(&self.mail_server))?;
|
sql.set_raw_config(context, key, Some(&self.mail_server))?;
|
||||||
|
|
||||||
let key = format!("{}mail_port", prefix);
|
let key = format!("{}mail_port", prefix);
|
||||||
sql.set_config_int(context, key, self.mail_port)?;
|
sql.set_raw_config_int(context, key, self.mail_port)?;
|
||||||
|
|
||||||
let key = format!("{}mail_user", prefix);
|
let key = format!("{}mail_user", prefix);
|
||||||
sql.set_config(context, key, Some(&self.mail_user))?;
|
sql.set_raw_config(context, key, Some(&self.mail_user))?;
|
||||||
|
|
||||||
let key = format!("{}mail_pw", prefix);
|
let key = format!("{}mail_pw", prefix);
|
||||||
sql.set_config(context, key, Some(&self.mail_pw))?;
|
sql.set_raw_config(context, key, Some(&self.mail_pw))?;
|
||||||
|
|
||||||
|
let key = format!("{}imap_certificate_checks", prefix);
|
||||||
|
sql.set_raw_config_int(context, key, self.imap_certificate_checks as i32)?;
|
||||||
|
|
||||||
let key = format!("{}send_server", prefix);
|
let key = format!("{}send_server", prefix);
|
||||||
sql.set_config(context, key, Some(&self.send_server))?;
|
sql.set_raw_config(context, key, Some(&self.send_server))?;
|
||||||
|
|
||||||
let key = format!("{}send_port", prefix);
|
let key = format!("{}send_port", prefix);
|
||||||
sql.set_config_int(context, key, self.send_port)?;
|
sql.set_raw_config_int(context, key, self.send_port)?;
|
||||||
|
|
||||||
let key = format!("{}send_user", prefix);
|
let key = format!("{}send_user", prefix);
|
||||||
sql.set_config(context, key, Some(&self.send_user))?;
|
sql.set_raw_config(context, key, Some(&self.send_user))?;
|
||||||
|
|
||||||
let key = format!("{}send_pw", prefix);
|
let key = format!("{}send_pw", prefix);
|
||||||
sql.set_config(context, key, Some(&self.send_pw))?;
|
sql.set_raw_config(context, key, Some(&self.send_pw))?;
|
||||||
|
|
||||||
|
let key = format!("{}smtp_certificate_checks", prefix);
|
||||||
|
sql.set_raw_config_int(context, key, self.smtp_certificate_checks as i32)?;
|
||||||
|
|
||||||
let key = format!("{}server_flags", prefix);
|
let key = format!("{}server_flags", prefix);
|
||||||
sql.set_config_int(context, key, self.server_flags)?;
|
sql.set_raw_config_int(context, key, self.server_flags)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -133,16 +177,18 @@ impl fmt::Display for LoginParam {
|
|||||||
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{} {}:{}:{}:{} {}:{}:{}:{} {}",
|
"{} imap:{}:{}:{}:{}:cert_{} smtp:{}:{}:{}:{}:cert_{} {}",
|
||||||
unset_empty(&self.addr),
|
unset_empty(&self.addr),
|
||||||
unset_empty(&self.mail_user),
|
unset_empty(&self.mail_user),
|
||||||
if !self.mail_pw.is_empty() { pw } else { unset },
|
if !self.mail_pw.is_empty() { pw } else { unset },
|
||||||
unset_empty(&self.mail_server),
|
unset_empty(&self.mail_server),
|
||||||
self.mail_port,
|
self.mail_port,
|
||||||
|
self.imap_certificate_checks,
|
||||||
unset_empty(&self.send_user),
|
unset_empty(&self.send_user),
|
||||||
if !self.send_pw.is_empty() { pw } else { unset },
|
if !self.send_pw.is_empty() { pw } else { unset },
|
||||||
unset_empty(&self.send_server),
|
unset_empty(&self.send_server),
|
||||||
self.send_port,
|
self.send_port,
|
||||||
|
self.smtp_certificate_checks,
|
||||||
flags_readable,
|
flags_readable,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -204,3 +250,41 @@ fn get_readable_flags(flags: i32) -> String {
|
|||||||
|
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn dc_build_tls(
|
||||||
|
certificate_checks: CertificateChecks,
|
||||||
|
) -> Result<native_tls::TlsConnector, native_tls::Error> {
|
||||||
|
let mut tls_builder = native_tls::TlsConnector::builder();
|
||||||
|
match certificate_checks {
|
||||||
|
CertificateChecks::Automatic => {
|
||||||
|
// Same as AcceptInvalidCertificates for now.
|
||||||
|
// TODO: use provider database when it becomes available
|
||||||
|
tls_builder
|
||||||
|
.danger_accept_invalid_hostnames(true)
|
||||||
|
.danger_accept_invalid_certs(true)
|
||||||
|
}
|
||||||
|
CertificateChecks::Strict => &mut tls_builder,
|
||||||
|
CertificateChecks::AcceptInvalidHostnames => {
|
||||||
|
tls_builder.danger_accept_invalid_hostnames(true)
|
||||||
|
}
|
||||||
|
CertificateChecks::AcceptInvalidCertificates => tls_builder
|
||||||
|
.danger_accept_invalid_hostnames(true)
|
||||||
|
.danger_accept_invalid_certs(true),
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_certificate_checks_display() {
|
||||||
|
use std::string::ToString;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
"accept_invalid_hostnames".to_string(),
|
||||||
|
CertificateChecks::AcceptInvalidHostnames.to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
1160
src/message.rs
1160
src/message.rs
File diff suppressed because it is too large
Load Diff
924
src/mimefactory.rs
Normal file
924
src/mimefactory.rs
Normal file
@@ -0,0 +1,924 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
use chrono::TimeZone;
|
||||||
|
use mmime::clist::*;
|
||||||
|
use mmime::mailimf::types::*;
|
||||||
|
use mmime::mailimf::types_helper::*;
|
||||||
|
use mmime::mailmime::disposition::*;
|
||||||
|
use mmime::mailmime::types::*;
|
||||||
|
use mmime::mailmime::types_helper::*;
|
||||||
|
use mmime::mailmime::write_mem::*;
|
||||||
|
use mmime::mmapstring::*;
|
||||||
|
use mmime::other::*;
|
||||||
|
|
||||||
|
use crate::chat::{self, Chat};
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::constants::*;
|
||||||
|
use crate::contact::*;
|
||||||
|
use crate::context::{get_version_str, Context};
|
||||||
|
use crate::dc_mimeparser::SystemMessage;
|
||||||
|
use crate::dc_strencode::*;
|
||||||
|
use crate::dc_tools::*;
|
||||||
|
use crate::e2ee::*;
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::location;
|
||||||
|
use crate::message::{self, Message};
|
||||||
|
use crate::param::*;
|
||||||
|
use crate::stock::StockMessage;
|
||||||
|
use crate::wrapmime;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||||
|
pub enum Loaded {
|
||||||
|
Nothing,
|
||||||
|
Message,
|
||||||
|
MDN, // TODO: invent more descriptive name
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct MimeFactory<'a> {
|
||||||
|
pub from_addr: String,
|
||||||
|
pub from_displayname: String,
|
||||||
|
pub selfstatus: String,
|
||||||
|
pub recipients_names: Vec<String>,
|
||||||
|
pub recipients_addr: Vec<String>,
|
||||||
|
pub timestamp: i64,
|
||||||
|
pub rfc724_mid: String,
|
||||||
|
pub loaded: Loaded,
|
||||||
|
pub msg: Message,
|
||||||
|
pub chat: Option<Chat>,
|
||||||
|
pub increation: bool,
|
||||||
|
pub in_reply_to: String,
|
||||||
|
pub references: String,
|
||||||
|
pub req_mdn: bool,
|
||||||
|
pub out: *mut MMAPString,
|
||||||
|
pub out_encrypted: bool,
|
||||||
|
pub out_gossiped: bool,
|
||||||
|
pub out_last_added_location_id: u32,
|
||||||
|
pub context: &'a Context,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MimeFactory<'a> {
|
||||||
|
fn new(context: &'a Context, msg: Message) -> Self {
|
||||||
|
MimeFactory {
|
||||||
|
from_addr: context
|
||||||
|
.get_config(Config::ConfiguredAddr)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
from_displayname: context.get_config(Config::Displayname).unwrap_or_default(),
|
||||||
|
selfstatus: context
|
||||||
|
.get_config(Config::Selfstatus)
|
||||||
|
.unwrap_or_else(|| context.stock_str(StockMessage::StatusLine).to_string()),
|
||||||
|
recipients_names: Vec::with_capacity(5),
|
||||||
|
recipients_addr: Vec::with_capacity(5),
|
||||||
|
timestamp: 0,
|
||||||
|
rfc724_mid: String::default(),
|
||||||
|
loaded: Loaded::Nothing,
|
||||||
|
msg,
|
||||||
|
chat: None,
|
||||||
|
increation: false,
|
||||||
|
in_reply_to: String::default(),
|
||||||
|
references: String::default(),
|
||||||
|
req_mdn: false,
|
||||||
|
out: ptr::null_mut(),
|
||||||
|
out_encrypted: false,
|
||||||
|
out_gossiped: false,
|
||||||
|
out_last_added_location_id: 0,
|
||||||
|
context,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finalize_mime_message(
|
||||||
|
&mut self,
|
||||||
|
message: *mut Mailmime,
|
||||||
|
encrypted: bool,
|
||||||
|
gossiped: bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
unsafe {
|
||||||
|
assert!(self.out.is_null()); // guard against double-calls
|
||||||
|
self.out = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
|
||||||
|
let mut col: libc::c_int = 0;
|
||||||
|
ensure_eq!(
|
||||||
|
mailmime_write_mem(self.out, &mut col, message),
|
||||||
|
0,
|
||||||
|
"mem-error"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.out_encrypted = encrypted;
|
||||||
|
self.out_gossiped = encrypted && gossiped;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_mdn(context: &'a Context, msg_id: u32) -> Result<MimeFactory, Error> {
|
||||||
|
if !context.get_config_bool(Config::MdnsEnabled) {
|
||||||
|
// MDNs not enabled - check this is late, in the job. the
|
||||||
|
// user may have changed its choice while offline ...
|
||||||
|
bail!("MDNs meanwhile disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg = Message::load_from_db(context, msg_id)?;
|
||||||
|
let mut factory = MimeFactory::new(context, msg);
|
||||||
|
let contact = Contact::load_from_db(factory.context, factory.msg.from_id)?;
|
||||||
|
|
||||||
|
// Do not send MDNs trash etc.; chats.blocked is already checked by the caller
|
||||||
|
// in dc_markseen_msgs()
|
||||||
|
ensure!(!contact.is_blocked(), "Contact blocked");
|
||||||
|
ensure!(
|
||||||
|
factory.msg.chat_id > DC_CHAT_ID_LAST_SPECIAL,
|
||||||
|
"Invalid chat id"
|
||||||
|
);
|
||||||
|
|
||||||
|
factory
|
||||||
|
.recipients_names
|
||||||
|
.push(contact.get_authname().to_string());
|
||||||
|
factory.recipients_addr.push(contact.get_addr().to_string());
|
||||||
|
factory.timestamp = dc_create_smeared_timestamp(factory.context);
|
||||||
|
factory.rfc724_mid = dc_create_outgoing_rfc724_mid(None, &factory.from_addr);
|
||||||
|
factory.loaded = Loaded::MDN;
|
||||||
|
|
||||||
|
Ok(factory)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Render a basic email
|
||||||
|
******************************************************************************/
|
||||||
|
// XXX restrict unsafe to parts, introduce wrapmime helpers where appropriate
|
||||||
|
pub unsafe fn render(&mut self) -> Result<(), Error> {
|
||||||
|
if self.loaded == Loaded::Nothing || !self.out.is_null() {
|
||||||
|
bail!("Invalid use of mimefactory-object.");
|
||||||
|
}
|
||||||
|
let context = &self.context;
|
||||||
|
let from = wrapmime::new_mailbox_list(&self.from_displayname, &self.from_addr);
|
||||||
|
|
||||||
|
let to = mailimf_address_list_new_empty();
|
||||||
|
let name_iter = self.recipients_names.iter();
|
||||||
|
let addr_iter = self.recipients_addr.iter();
|
||||||
|
for (name, addr) in name_iter.zip(addr_iter) {
|
||||||
|
mailimf_address_list_add(
|
||||||
|
to,
|
||||||
|
mailimf_address_new(
|
||||||
|
MAILIMF_ADDRESS_MAILBOX as libc::c_int,
|
||||||
|
mailimf_mailbox_new(
|
||||||
|
if !name.is_empty() {
|
||||||
|
dc_encode_header_words(&name).strdup()
|
||||||
|
} else {
|
||||||
|
ptr::null_mut()
|
||||||
|
},
|
||||||
|
addr.strdup(),
|
||||||
|
),
|
||||||
|
ptr::null_mut(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let references_list = if !self.references.is_empty() {
|
||||||
|
dc_str_to_clist(&self.references, " ")
|
||||||
|
} else {
|
||||||
|
ptr::null_mut()
|
||||||
|
};
|
||||||
|
let in_reply_to_list = if !self.in_reply_to.is_empty() {
|
||||||
|
dc_str_to_clist(&self.in_reply_to, " ")
|
||||||
|
} else {
|
||||||
|
ptr::null_mut()
|
||||||
|
};
|
||||||
|
|
||||||
|
let imf_fields = mailimf_fields_new_with_data_all(
|
||||||
|
mailimf_get_date(self.timestamp as i64),
|
||||||
|
from,
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
to,
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
self.rfc724_mid.strdup(),
|
||||||
|
in_reply_to_list,
|
||||||
|
references_list,
|
||||||
|
ptr::null_mut(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let os_name = &self.context.os_name;
|
||||||
|
let os_part = os_name
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| format!("/{}", s))
|
||||||
|
.unwrap_or_default();
|
||||||
|
let version = get_version_str();
|
||||||
|
let headerval = format!("Delta Chat Core {}{}", version, os_part);
|
||||||
|
|
||||||
|
/* Add a X-Mailer header.
|
||||||
|
This is only informational for debugging and may be removed in the release.
|
||||||
|
We do not rely on this header as it may be removed by MTAs. */
|
||||||
|
wrapmime::new_custom_field(imf_fields, "X-Mailer", &headerval);
|
||||||
|
wrapmime::new_custom_field(imf_fields, "Chat-Version", "1.0");
|
||||||
|
if self.req_mdn {
|
||||||
|
/* we use "Chat-Disposition-Notification-To"
|
||||||
|
because replies to "Disposition-Notification-To" are weird in many cases
|
||||||
|
eg. are just freetext and/or do not follow any standard. */
|
||||||
|
wrapmime::new_custom_field(
|
||||||
|
imf_fields,
|
||||||
|
"Chat-Disposition-Notification-To",
|
||||||
|
&self.from_addr,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cleanup = |message: *mut Mailmime| {
|
||||||
|
if !message.is_null() {
|
||||||
|
mailmime_free(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let message = mailmime_new_message_data(0 as *mut Mailmime);
|
||||||
|
ensure!(!message.is_null(), "could not create mime message data");
|
||||||
|
|
||||||
|
mailmime_set_imf_fields(message, imf_fields);
|
||||||
|
|
||||||
|
// 1=add Autocrypt-header (needed eg. for handshaking), 2=no Autocrypte-header (used for MDN)
|
||||||
|
let mut e2ee_guaranteed = false;
|
||||||
|
let mut min_verified: libc::c_int = 0;
|
||||||
|
let mut do_gossip = false;
|
||||||
|
let mut grpimage = None;
|
||||||
|
let force_plaintext: libc::c_int;
|
||||||
|
let subject_str = match self.loaded {
|
||||||
|
Loaded::Message => {
|
||||||
|
/* Render a normal message
|
||||||
|
*********************************************************************/
|
||||||
|
let chat = self.chat.as_ref().unwrap();
|
||||||
|
let mut meta_part: *mut Mailmime = ptr::null_mut();
|
||||||
|
let mut placeholdertext = None;
|
||||||
|
|
||||||
|
if chat.typ == Chattype::VerifiedGroup {
|
||||||
|
wrapmime::new_custom_field(imf_fields, "Chat-Verified", "1");
|
||||||
|
force_plaintext = 0;
|
||||||
|
e2ee_guaranteed = true;
|
||||||
|
min_verified = 2
|
||||||
|
} else {
|
||||||
|
force_plaintext = self
|
||||||
|
.msg
|
||||||
|
.param
|
||||||
|
.get_int(Param::ForcePlaintext)
|
||||||
|
.unwrap_or_default();
|
||||||
|
if force_plaintext == 0 {
|
||||||
|
e2ee_guaranteed = self
|
||||||
|
.msg
|
||||||
|
.param
|
||||||
|
.get_int(Param::GuranteeE2ee)
|
||||||
|
.unwrap_or_default()
|
||||||
|
!= 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* beside key- and member-changes, force re-gossip every 48 hours */
|
||||||
|
if chat.gossiped_timestamp == 0
|
||||||
|
|| (chat.gossiped_timestamp + (2 * 24 * 60 * 60)) < time()
|
||||||
|
{
|
||||||
|
do_gossip = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/* build header etc. */
|
||||||
|
let command = self.msg.param.get_cmd();
|
||||||
|
if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
|
||||||
|
wrapmime::new_custom_field(imf_fields, "Chat-Group-ID", &chat.grpid);
|
||||||
|
|
||||||
|
let encoded = dc_encode_header_words(&chat.name);
|
||||||
|
wrapmime::new_custom_field(imf_fields, "Chat-Group-Name", &encoded);
|
||||||
|
|
||||||
|
match command {
|
||||||
|
SystemMessage::MemberRemovedFromGroup => {
|
||||||
|
let email_to_remove =
|
||||||
|
self.msg.param.get(Param::Arg).unwrap_or_default();
|
||||||
|
if !email_to_remove.is_empty() {
|
||||||
|
wrapmime::new_custom_field(
|
||||||
|
imf_fields,
|
||||||
|
"Chat-Group-Member-Removed",
|
||||||
|
&email_to_remove,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SystemMessage::MemberAddedToGroup => {
|
||||||
|
let msg = &self.msg;
|
||||||
|
do_gossip = true;
|
||||||
|
let email_to_add = msg.param.get(Param::Arg).unwrap_or_default();
|
||||||
|
if !email_to_add.is_empty() {
|
||||||
|
wrapmime::new_custom_field(
|
||||||
|
imf_fields,
|
||||||
|
"Chat-Group-Member-Added",
|
||||||
|
&email_to_add,
|
||||||
|
);
|
||||||
|
grpimage = chat.param.get(Param::ProfileImage);
|
||||||
|
}
|
||||||
|
if 0 != msg.param.get_int(Param::Arg2).unwrap_or_default() & 0x1 {
|
||||||
|
info!(
|
||||||
|
context,
|
||||||
|
"sending secure-join message \'{}\' >>>>>>>>>>>>>>>>>>>>>>>>>",
|
||||||
|
"vg-member-added",
|
||||||
|
);
|
||||||
|
wrapmime::new_custom_field(
|
||||||
|
imf_fields,
|
||||||
|
"Secure-Join",
|
||||||
|
"vg-member-added",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SystemMessage::GroupNameChanged => {
|
||||||
|
let msg = &self.msg;
|
||||||
|
let value_to_add = msg.param.get(Param::Arg).unwrap_or_default();
|
||||||
|
|
||||||
|
wrapmime::new_custom_field(
|
||||||
|
imf_fields,
|
||||||
|
"Chat-Group-Name-Changed",
|
||||||
|
&value_to_add,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
SystemMessage::GroupImageChanged => {
|
||||||
|
let msg = &self.msg;
|
||||||
|
grpimage = msg.param.get(Param::Arg);
|
||||||
|
if grpimage.is_none() {
|
||||||
|
wrapmime::new_custom_field(imf_fields, "Chat-Group-Image", "0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match command {
|
||||||
|
SystemMessage::LocationStreamingEnabled => {
|
||||||
|
wrapmime::new_custom_field(
|
||||||
|
imf_fields,
|
||||||
|
"Chat-Content",
|
||||||
|
"location-streaming-enabled",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
SystemMessage::AutocryptSetupMessage => {
|
||||||
|
wrapmime::new_custom_field(imf_fields, "Autocrypt-Setup-Message", "v1");
|
||||||
|
placeholdertext = Some(
|
||||||
|
self.context
|
||||||
|
.stock_str(StockMessage::AcSetupMsgBody)
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
SystemMessage::SecurejoinMessage => {
|
||||||
|
let msg = &self.msg;
|
||||||
|
let step = msg.param.get(Param::Arg).unwrap_or_default();
|
||||||
|
if !step.is_empty() {
|
||||||
|
info!(
|
||||||
|
context,
|
||||||
|
"sending secure-join message \'{}\' >>>>>>>>>>>>>>>>>>>>>>>>>",
|
||||||
|
step,
|
||||||
|
);
|
||||||
|
wrapmime::new_custom_field(imf_fields, "Secure-Join", &step);
|
||||||
|
let param2 = msg.param.get(Param::Arg2).unwrap_or_default();
|
||||||
|
if !param2.is_empty() {
|
||||||
|
wrapmime::new_custom_field(
|
||||||
|
imf_fields,
|
||||||
|
if step == "vg-request-with-auth"
|
||||||
|
|| step == "vc-request-with-auth"
|
||||||
|
{
|
||||||
|
"Secure-Join-Auth"
|
||||||
|
} else {
|
||||||
|
"Secure-Join-Invitenumber"
|
||||||
|
},
|
||||||
|
param2,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let fingerprint = msg.param.get(Param::Arg3).unwrap_or_default();
|
||||||
|
if !fingerprint.is_empty() {
|
||||||
|
wrapmime::new_custom_field(
|
||||||
|
imf_fields,
|
||||||
|
"Secure-Join-Fingerprint",
|
||||||
|
&fingerprint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some(id) = msg.param.get(Param::Arg4) {
|
||||||
|
wrapmime::new_custom_field(imf_fields, "Secure-Join-Group", &id);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(grpimage) = grpimage {
|
||||||
|
info!(self.context, "setting group image '{}'", grpimage);
|
||||||
|
let mut meta = Message::default();
|
||||||
|
meta.type_0 = Viewtype::Image;
|
||||||
|
meta.param.set(Param::File, grpimage);
|
||||||
|
|
||||||
|
let res = build_body_file(context, &meta, "group-image")?;
|
||||||
|
meta_part = res.0;
|
||||||
|
let filename_as_sent = res.1;
|
||||||
|
if !meta_part.is_null() {
|
||||||
|
wrapmime::new_custom_field(
|
||||||
|
imf_fields,
|
||||||
|
"Chat-Group-Image",
|
||||||
|
&filename_as_sent,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.msg.type_0 == Viewtype::Sticker {
|
||||||
|
wrapmime::new_custom_field(imf_fields, "Chat-Content", "sticker");
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.msg.type_0 == Viewtype::Voice
|
||||||
|
|| self.msg.type_0 == Viewtype::Audio
|
||||||
|
|| self.msg.type_0 == Viewtype::Video
|
||||||
|
{
|
||||||
|
if self.msg.type_0 == Viewtype::Voice {
|
||||||
|
wrapmime::new_custom_field(imf_fields, "Chat-Voice-Message", "1");
|
||||||
|
}
|
||||||
|
let duration_ms = self.msg.param.get_int(Param::Duration).unwrap_or_default();
|
||||||
|
if duration_ms > 0 {
|
||||||
|
let dur = duration_ms.to_string();
|
||||||
|
wrapmime::new_custom_field(imf_fields, "Chat-Duration", &dur);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* add text part - we even add empty text and force a MIME-multipart-message as:
|
||||||
|
- some Apps have problems with Non-text in the main part (eg. "Mail" from stock Android)
|
||||||
|
- we can add "forward hints" this way
|
||||||
|
- it looks better */
|
||||||
|
let afwd_email = self.msg.param.exists(Param::Forwarded);
|
||||||
|
let fwdhint = if afwd_email {
|
||||||
|
Some(
|
||||||
|
"---------- Forwarded message ----------\r\nFrom: Delta Chat\r\n\r\n"
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let final_text = {
|
||||||
|
if let Some(ref text) = placeholdertext {
|
||||||
|
text
|
||||||
|
} else if let Some(ref text) = self.msg.text {
|
||||||
|
text
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let footer = &self.selfstatus;
|
||||||
|
let message_text = format!(
|
||||||
|
"{}{}{}{}{}",
|
||||||
|
fwdhint.unwrap_or_default(),
|
||||||
|
&final_text,
|
||||||
|
if !final_text.is_empty() && !footer.is_empty() {
|
||||||
|
"\r\n\r\n"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
|
if !footer.is_empty() { "-- \r\n" } else { "" },
|
||||||
|
footer
|
||||||
|
);
|
||||||
|
let text_part = wrapmime::build_body_text(&message_text)?;
|
||||||
|
mailmime_smart_add_part(message, text_part);
|
||||||
|
|
||||||
|
/* add attachment part */
|
||||||
|
if chat::msgtype_has_file(self.msg.type_0) {
|
||||||
|
if !is_file_size_okay(context, &self.msg) {
|
||||||
|
cleanup(message);
|
||||||
|
bail!(
|
||||||
|
"Message exceeds the recommended {} MB.",
|
||||||
|
24 * 1024 * 1024 / 4 * 3 / 1000 / 1000,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let (file_part, _) = build_body_file(context, &self.msg, "")?;
|
||||||
|
mailmime_smart_add_part(message, file_part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !meta_part.is_null() {
|
||||||
|
mailmime_smart_add_part(message, meta_part);
|
||||||
|
}
|
||||||
|
if self.msg.param.exists(Param::SetLatitude) {
|
||||||
|
let param = &self.msg.param;
|
||||||
|
let kml_file = location::get_message_kml(
|
||||||
|
self.msg.timestamp_sort,
|
||||||
|
param.get_float(Param::SetLatitude).unwrap_or_default(),
|
||||||
|
param.get_float(Param::SetLongitude).unwrap_or_default(),
|
||||||
|
);
|
||||||
|
wrapmime::add_filename_part(
|
||||||
|
message,
|
||||||
|
"message.kml",
|
||||||
|
"application/vnd.google-earth.kml+xml",
|
||||||
|
&kml_file,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if location::is_sending_locations_to_chat(context, self.msg.chat_id) {
|
||||||
|
if let Ok((kml_file, last_added_location_id)) =
|
||||||
|
location::get_kml(context, self.msg.chat_id)
|
||||||
|
{
|
||||||
|
wrapmime::add_filename_part(
|
||||||
|
message,
|
||||||
|
"location.kml",
|
||||||
|
"application/vnd.google-earth.kml+xml",
|
||||||
|
&kml_file,
|
||||||
|
)?;
|
||||||
|
if !self.msg.param.exists(Param::SetLatitude) {
|
||||||
|
// otherwise, the independent location is already filed
|
||||||
|
self.out_last_added_location_id = last_added_location_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get_subject(context, self.chat.as_ref(), &mut self.msg, afwd_email)
|
||||||
|
}
|
||||||
|
Loaded::MDN => {
|
||||||
|
/* Render a MDN
|
||||||
|
*********************************************************************/
|
||||||
|
/* RFC 6522, this also requires the `report-type` parameter which is equal
|
||||||
|
to the MIME subtype of the second body part of the multipart/report */
|
||||||
|
let multipart = mailmime_multiple_new(
|
||||||
|
b"multipart/report\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
wrapmime::append_ct_param(
|
||||||
|
(*multipart).mm_content_type,
|
||||||
|
"report-type",
|
||||||
|
"disposition-notification",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
mailmime_add_part(message, multipart);
|
||||||
|
|
||||||
|
/* first body part: always human-readable, always REQUIRED by RFC 6522 */
|
||||||
|
let p1 = if 0
|
||||||
|
!= self
|
||||||
|
.msg
|
||||||
|
.param
|
||||||
|
.get_int(Param::GuranteeE2ee)
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
self.context
|
||||||
|
.stock_str(StockMessage::EncryptedMsg)
|
||||||
|
.into_owned()
|
||||||
|
} else {
|
||||||
|
self.msg.get_summarytext(context, 32)
|
||||||
|
};
|
||||||
|
let p2 = self
|
||||||
|
.context
|
||||||
|
.stock_string_repl_str(StockMessage::ReadRcptMailBody, p1);
|
||||||
|
let message_text = format!("{}\r\n", p2);
|
||||||
|
let human_mime_part = wrapmime::build_body_text(&message_text)?;
|
||||||
|
mailmime_add_part(multipart, human_mime_part);
|
||||||
|
|
||||||
|
/* second body part: machine-readable, always REQUIRED by RFC 6522 */
|
||||||
|
let version = get_version_str();
|
||||||
|
let message_text2 = format!(
|
||||||
|
"Reporting-UA: Delta Chat {}\r\nOriginal-Recipient: rfc822;{}\r\nFinal-Recipient: rfc822;{}\r\nOriginal-Message-ID: <{}>\r\nDisposition: manual-action/MDN-sent-automatically; displayed\r\n",
|
||||||
|
version,
|
||||||
|
self.from_addr,
|
||||||
|
self.from_addr,
|
||||||
|
self.msg.rfc724_mid
|
||||||
|
);
|
||||||
|
|
||||||
|
let content_type_0 =
|
||||||
|
wrapmime::new_content_type("message/disposition-notification")?;
|
||||||
|
let mime_fields_0: *mut mailmime_fields =
|
||||||
|
mailmime_fields_new_encoding(MAILMIME_MECHANISM_8BIT as libc::c_int);
|
||||||
|
let mach_mime_part: *mut Mailmime =
|
||||||
|
mailmime_new_empty(content_type_0, mime_fields_0);
|
||||||
|
wrapmime::set_body_text(mach_mime_part, &message_text2)?;
|
||||||
|
mailmime_add_part(multipart, mach_mime_part);
|
||||||
|
force_plaintext = DC_FP_NO_AUTOCRYPT_HEADER;
|
||||||
|
/* currently, we do not send MDNs encrypted:
|
||||||
|
- in a multi-device-setup that is not set up properly, MDNs would disturb the communication as they
|
||||||
|
are send automatically which may lead to spreading outdated Autocrypt headers.
|
||||||
|
- they do not carry any information but the Message-ID
|
||||||
|
- this save some KB
|
||||||
|
- in older versions, we did not encrypt messages to ourself when they to to SMTP - however, if these messages
|
||||||
|
are forwarded for any reasons (eg. gmail always forwards to IMAP), we have no chance to decrypt them;
|
||||||
|
this issue is fixed with 0.9.4 */
|
||||||
|
let e = self.context.stock_str(StockMessage::ReadRcpt);
|
||||||
|
format!("Chat: {}", e).to_string()
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
cleanup(message);
|
||||||
|
bail!("No message loaded.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Create the mime message
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
mailimf_fields_add(
|
||||||
|
imf_fields,
|
||||||
|
mailimf_field_new(
|
||||||
|
MAILIMF_FIELD_SUBJECT as libc::c_int,
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
mailimf_subject_new(dc_encode_header_words(subject_str).strdup()),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
/*just a pointer into mailmime structure, must not be freed*/
|
||||||
|
let imffields_unprotected = wrapmime::mailmime_find_mailimf_fields(message);
|
||||||
|
ensure!(
|
||||||
|
!imffields_unprotected.is_null(),
|
||||||
|
"could not find mime fields"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut encrypt_helper = EncryptHelper::new(&context)?;
|
||||||
|
if force_plaintext != DC_FP_NO_AUTOCRYPT_HEADER {
|
||||||
|
// unless determined otherwise we add Autocrypt header
|
||||||
|
let aheader = encrypt_helper.get_aheader().to_string();
|
||||||
|
wrapmime::new_custom_field(imffields_unprotected, "Autocrypt", &aheader);
|
||||||
|
}
|
||||||
|
let finalized = if force_plaintext == 0 {
|
||||||
|
encrypt_helper.try_encrypt(
|
||||||
|
self,
|
||||||
|
e2ee_guaranteed,
|
||||||
|
min_verified,
|
||||||
|
do_gossip,
|
||||||
|
message,
|
||||||
|
imffields_unprotected,
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
if !finalized {
|
||||||
|
self.finalize_mime_message(message, false, false)?;
|
||||||
|
}
|
||||||
|
cleanup(message);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_msg(context: &Context, msg_id: u32) -> Result<MimeFactory, Error> {
|
||||||
|
ensure!(msg_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat id");
|
||||||
|
|
||||||
|
let msg = Message::load_from_db(context, msg_id)?;
|
||||||
|
let chat = Chat::load_from_db(context, msg.chat_id)?;
|
||||||
|
let mut factory = MimeFactory::new(context, msg);
|
||||||
|
factory.chat = Some(chat);
|
||||||
|
|
||||||
|
// just set the chat above
|
||||||
|
let chat = factory.chat.as_ref().unwrap();
|
||||||
|
|
||||||
|
if chat.is_self_talk() {
|
||||||
|
factory
|
||||||
|
.recipients_names
|
||||||
|
.push(factory.from_displayname.to_string());
|
||||||
|
factory.recipients_addr.push(factory.from_addr.to_string());
|
||||||
|
} else {
|
||||||
|
context.sql.query_map(
|
||||||
|
"SELECT c.authname, c.addr \
|
||||||
|
FROM chats_contacts cc \
|
||||||
|
LEFT JOIN contacts c ON cc.contact_id=c.id \
|
||||||
|
WHERE cc.chat_id=? AND cc.contact_id>9;",
|
||||||
|
params![factory.msg.chat_id as i32],
|
||||||
|
|row| {
|
||||||
|
let authname: String = row.get(0)?;
|
||||||
|
let addr: String = row.get(1)?;
|
||||||
|
Ok((authname, addr))
|
||||||
|
},
|
||||||
|
|rows| {
|
||||||
|
for row in rows {
|
||||||
|
let (authname, addr) = row?;
|
||||||
|
if !vec_contains_lowercase(&factory.recipients_addr, &addr) {
|
||||||
|
factory.recipients_addr.push(addr);
|
||||||
|
factory.recipients_names.push(authname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let command = factory.msg.param.get_cmd();
|
||||||
|
let msg = &factory.msg;
|
||||||
|
|
||||||
|
/* for added members, the list is just fine */
|
||||||
|
if command == SystemMessage::MemberRemovedFromGroup {
|
||||||
|
let email_to_remove = msg.param.get(Param::Arg).unwrap_or_default();
|
||||||
|
|
||||||
|
let self_addr = context
|
||||||
|
.get_config(Config::ConfiguredAddr)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if !email_to_remove.is_empty() && email_to_remove != self_addr {
|
||||||
|
if !vec_contains_lowercase(&factory.recipients_addr, &email_to_remove) {
|
||||||
|
factory.recipients_names.push("".to_string());
|
||||||
|
factory.recipients_addr.push(email_to_remove.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if command != SystemMessage::AutocryptSetupMessage
|
||||||
|
&& command != SystemMessage::SecurejoinMessage
|
||||||
|
&& context.get_config_bool(Config::MdnsEnabled)
|
||||||
|
{
|
||||||
|
factory.req_mdn = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let row = context.sql.query_row(
|
||||||
|
"SELECT mime_in_reply_to, mime_references FROM msgs WHERE id=?",
|
||||||
|
params![factory.msg.id as i32],
|
||||||
|
|row| {
|
||||||
|
let in_reply_to: String = row.get(0)?;
|
||||||
|
let references: String = row.get(1)?;
|
||||||
|
|
||||||
|
Ok((in_reply_to, references))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
match row {
|
||||||
|
Ok((in_reply_to, references)) => {
|
||||||
|
factory.in_reply_to = in_reply_to;
|
||||||
|
factory.references = references;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!(
|
||||||
|
context,
|
||||||
|
"mimefactory: failed to load mime_in_reply_to: {:?}", err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
factory.loaded = Loaded::Message;
|
||||||
|
factory.timestamp = factory.msg.timestamp_sort;
|
||||||
|
factory.rfc724_mid = factory.msg.rfc724_mid.clone();
|
||||||
|
factory.increation = factory.msg.is_increation();
|
||||||
|
|
||||||
|
Ok(factory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Drop for MimeFactory<'a> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
if !self.out.is_null() {
|
||||||
|
mmap_string_free(self.out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_subject(
|
||||||
|
context: &Context,
|
||||||
|
chat: Option<&Chat>,
|
||||||
|
msg: &mut Message,
|
||||||
|
afwd_email: bool,
|
||||||
|
) -> String {
|
||||||
|
if chat.is_none() {
|
||||||
|
return String::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
let chat = chat.unwrap();
|
||||||
|
let raw_subject =
|
||||||
|
message::get_summarytext_by_raw(msg.type_0, msg.text.as_ref(), &mut msg.param, 32, context);
|
||||||
|
let fwd = if afwd_email { "Fwd: " } else { "" };
|
||||||
|
|
||||||
|
if msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage {
|
||||||
|
/* do not add the "Chat:" prefix for setup messages */
|
||||||
|
context
|
||||||
|
.stock_str(StockMessage::AcSetupMsgSubject)
|
||||||
|
.into_owned()
|
||||||
|
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
|
||||||
|
format!("Chat: {}: {}{}", chat.name, fwd, raw_subject,)
|
||||||
|
} else {
|
||||||
|
format!("Chat: {}{}", fwd, raw_subject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn build_body_file(
|
||||||
|
context: &Context,
|
||||||
|
msg: &Message,
|
||||||
|
base_name: &str,
|
||||||
|
) -> Result<(*mut Mailmime, String), Error> {
|
||||||
|
let path_filename = match msg.param.get(Param::File) {
|
||||||
|
None => {
|
||||||
|
bail!("msg has no filename");
|
||||||
|
}
|
||||||
|
Some(path) => path,
|
||||||
|
};
|
||||||
|
let suffix = dc_get_filesuffix_lc(path_filename).unwrap_or_else(|| "dat".into());
|
||||||
|
|
||||||
|
/* get file name to use for sending
|
||||||
|
(for privacy purposes, we do not transfer the original filenames eg. for images;
|
||||||
|
these names are normally not needed and contain timestamps, running numbers etc.) */
|
||||||
|
let filename_to_send = match msg.type_0 {
|
||||||
|
Viewtype::Voice => chrono::Utc
|
||||||
|
.timestamp(msg.timestamp_sort as i64, 0)
|
||||||
|
.format(&format!("voice-message_%Y-%m-%d_%H-%M-%S.{}", suffix))
|
||||||
|
.to_string(),
|
||||||
|
Viewtype::Audio => Path::new(path_filename)
|
||||||
|
.file_name()
|
||||||
|
.map(|c| c.to_string_lossy().to_string())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
Viewtype::Image | Viewtype::Gif => format!(
|
||||||
|
"{}.{}",
|
||||||
|
if base_name.is_empty() {
|
||||||
|
"image"
|
||||||
|
} else {
|
||||||
|
base_name
|
||||||
|
},
|
||||||
|
&suffix,
|
||||||
|
),
|
||||||
|
Viewtype::Video => format!("video.{}", &suffix),
|
||||||
|
_ => Path::new(path_filename)
|
||||||
|
.file_name()
|
||||||
|
.map(|c| c.to_string_lossy().to_string())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
/* check mimetype */
|
||||||
|
let mimetype = match msg.param.get(Param::MimeType) {
|
||||||
|
Some(mtype) => mtype,
|
||||||
|
None => {
|
||||||
|
let path = Path::new(path_filename);
|
||||||
|
if let Some(res) = message::guess_msgtype_from_suffix(&path) {
|
||||||
|
res.1
|
||||||
|
} else {
|
||||||
|
"application/octet-stream"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let needs_ext = dc_needs_ext_header(&filename_to_send);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
/* create mime part, for Content-Disposition, see RFC 2183.
|
||||||
|
`Content-Disposition: attachment` seems not to make a difference to `Content-Disposition: inline` at least on tested Thunderbird and Gma'l in 2017.
|
||||||
|
But I've heard about problems with inline and outl'k, so we just use the attachment-type until we run into other problems ... */
|
||||||
|
let mime_fields = mailmime_fields_new_filename(
|
||||||
|
MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int,
|
||||||
|
if needs_ext {
|
||||||
|
ptr::null_mut()
|
||||||
|
} else {
|
||||||
|
filename_to_send.strdup()
|
||||||
|
},
|
||||||
|
MAILMIME_MECHANISM_BASE64 as libc::c_int,
|
||||||
|
);
|
||||||
|
if needs_ext {
|
||||||
|
for cur_data in (*(*mime_fields).fld_list).into_iter() {
|
||||||
|
let field: *mut mailmime_field = cur_data as *mut _;
|
||||||
|
if (*field).fld_type == MAILMIME_FIELD_DISPOSITION as libc::c_int
|
||||||
|
&& !(*field).fld_data.fld_disposition.is_null()
|
||||||
|
{
|
||||||
|
let file_disposition = (*field).fld_data.fld_disposition;
|
||||||
|
if !file_disposition.is_null() {
|
||||||
|
let parm = mailmime_disposition_parm_new(
|
||||||
|
MAILMIME_DISPOSITION_PARM_PARAMETER as libc::c_int,
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
0 as libc::size_t,
|
||||||
|
mailmime_parameter_new(
|
||||||
|
strdup(b"filename*\x00" as *const u8 as *const libc::c_char),
|
||||||
|
dc_encode_ext_header(&filename_to_send).strdup(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if !parm.is_null() {
|
||||||
|
clist_insert_after(
|
||||||
|
(*file_disposition).dsp_parms,
|
||||||
|
(*(*file_disposition).dsp_parms).last,
|
||||||
|
parm as *mut libc::c_void,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let content = wrapmime::new_content_type(&mimetype)?;
|
||||||
|
let filename_encoded = dc_encode_header_words(&filename_to_send);
|
||||||
|
wrapmime::append_ct_param(content, "name", &filename_encoded)?;
|
||||||
|
|
||||||
|
let mime_sub = mailmime_new_empty(content, mime_fields);
|
||||||
|
let abs_path = dc_get_abs_path(context, path_filename).to_c_string()?;
|
||||||
|
mailmime_set_body_file(mime_sub, dc_strdup(abs_path.as_ptr()));
|
||||||
|
Ok((mime_sub, filename_to_send))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn vec_contains_lowercase(vec: &[String], part: &str) -> bool {
|
||||||
|
let partlc = part.to_lowercase();
|
||||||
|
for cur in vec.iter() {
|
||||||
|
if cur.to_lowercase() == partlc {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_file_size_okay(context: &Context, msg: &Message) -> bool {
|
||||||
|
let mut file_size_okay = true;
|
||||||
|
let path = msg.param.get(Param::File).unwrap_or_default();
|
||||||
|
let bytes = dc_get_filebytes(context, &path);
|
||||||
|
|
||||||
|
if bytes > (49 * 1024 * 1024 / 4 * 3) {
|
||||||
|
file_size_okay = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
file_size_okay
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ use crate::context::Context;
|
|||||||
use crate::dc_tools::*;
|
use crate::dc_tools::*;
|
||||||
|
|
||||||
const OAUTH2_GMAIL: Oauth2 = Oauth2 {
|
const OAUTH2_GMAIL: Oauth2 = Oauth2 {
|
||||||
|
// see https://developers.google.com/identity/protocols/OAuth2InstalledApp
|
||||||
client_id: "959970109878-4mvtgf6feshskf7695nfln6002mom908.apps.googleusercontent.com",
|
client_id: "959970109878-4mvtgf6feshskf7695nfln6002mom908.apps.googleusercontent.com",
|
||||||
get_code: "https://accounts.google.com/o/oauth2/auth?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&response_type=code&scope=https%3A%2F%2Fmail.google.com%2F%20email&access_type=offline",
|
get_code: "https://accounts.google.com/o/oauth2/auth?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&response_type=code&scope=https%3A%2F%2Fmail.google.com%2F%20email&access_type=offline",
|
||||||
init_token: "https://accounts.google.com/o/oauth2/token?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&code=$CODE&grant_type=authorization_code",
|
init_token: "https://accounts.google.com/o/oauth2/token?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&code=$CODE&grant_type=authorization_code",
|
||||||
@@ -15,6 +16,7 @@ const OAUTH2_GMAIL: Oauth2 = Oauth2 {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const OAUTH2_YANDEX: Oauth2 = Oauth2 {
|
const OAUTH2_YANDEX: Oauth2 = Oauth2 {
|
||||||
|
// see https://tech.yandex.com/oauth/doc/dg/reference/auto-code-client-docpage/
|
||||||
client_id: "c4d0b6735fc8420a816d7e1303469341",
|
client_id: "c4d0b6735fc8420a816d7e1303469341",
|
||||||
get_code: "https://oauth.yandex.com/authorize?client_id=$CLIENT_ID&response_type=code&scope=mail%3Aimap_full%20mail%3Asmtp&force_confirm=true",
|
get_code: "https://oauth.yandex.com/authorize?client_id=$CLIENT_ID&response_type=code&scope=mail%3Aimap_full%20mail%3Asmtp&force_confirm=true",
|
||||||
init_token: "https://oauth.yandex.com/token?grant_type=authorization_code&code=$CODE&client_id=$CLIENT_ID&client_secret=58b8c6e94cf44fbe952da8511955dacf",
|
init_token: "https://oauth.yandex.com/token?grant_type=authorization_code&code=$CODE&client_id=$CLIENT_ID&client_secret=58b8c6e94cf44fbe952da8511955dacf",
|
||||||
@@ -50,7 +52,7 @@ pub fn dc_get_oauth2_url(
|
|||||||
if let Some(oauth2) = Oauth2::from_address(addr) {
|
if let Some(oauth2) = Oauth2::from_address(addr) {
|
||||||
if context
|
if context
|
||||||
.sql
|
.sql
|
||||||
.set_config(
|
.set_raw_config(
|
||||||
context,
|
context,
|
||||||
"oauth2_pending_redirect_uri",
|
"oauth2_pending_redirect_uri",
|
||||||
Some(redirect_uri.as_ref()),
|
Some(redirect_uri.as_ref()),
|
||||||
@@ -74,25 +76,26 @@ pub fn dc_get_oauth2_access_token(
|
|||||||
context: &Context,
|
context: &Context,
|
||||||
addr: impl AsRef<str>,
|
addr: impl AsRef<str>,
|
||||||
code: impl AsRef<str>,
|
code: impl AsRef<str>,
|
||||||
flags: usize,
|
regenerate: bool,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
if let Some(oauth2) = Oauth2::from_address(addr) {
|
if let Some(oauth2) = Oauth2::from_address(addr) {
|
||||||
let lock = context.oauth2_critical.clone();
|
let lock = context.oauth2_critical.clone();
|
||||||
let _l = lock.lock().unwrap();
|
let _l = lock.lock().unwrap();
|
||||||
|
|
||||||
// read generated token
|
// read generated token
|
||||||
if 0 == flags & 0x1 && !is_expired(context) {
|
if !regenerate && !is_expired(context) {
|
||||||
let access_token = context.sql.get_config(context, "oauth2_access_token");
|
let access_token = context.sql.get_raw_config(context, "oauth2_access_token");
|
||||||
if access_token.is_some() {
|
if access_token.is_some() {
|
||||||
// success
|
// success
|
||||||
return access_token;
|
return access_token;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let refresh_token = context.sql.get_config(context, "oauth2_refresh_token");
|
// generate new token: build & call auth url
|
||||||
|
let refresh_token = context.sql.get_raw_config(context, "oauth2_refresh_token");
|
||||||
let refresh_token_for = context
|
let refresh_token_for = context
|
||||||
.sql
|
.sql
|
||||||
.get_config(context, "oauth2_refresh_token_for")
|
.get_raw_config(context, "oauth2_refresh_token_for")
|
||||||
.unwrap_or_else(|| "unset".into());
|
.unwrap_or_else(|| "unset".into());
|
||||||
|
|
||||||
let (redirect_uri, token_url, update_redirect_uri_on_success) =
|
let (redirect_uri, token_url, update_redirect_uri_on_success) =
|
||||||
@@ -101,7 +104,7 @@ pub fn dc_get_oauth2_access_token(
|
|||||||
(
|
(
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.get_config(context, "oauth2_pending_redirect_uri")
|
.get_raw_config(context, "oauth2_pending_redirect_uri")
|
||||||
.unwrap_or_else(|| "unset".into()),
|
.unwrap_or_else(|| "unset".into()),
|
||||||
oauth2.init_token,
|
oauth2.init_token,
|
||||||
true,
|
true,
|
||||||
@@ -114,20 +117,43 @@ pub fn dc_get_oauth2_access_token(
|
|||||||
(
|
(
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.get_config(context, "oauth2_redirect_uri")
|
.get_raw_config(context, "oauth2_redirect_uri")
|
||||||
.unwrap_or_else(|| "unset".into()),
|
.unwrap_or_else(|| "unset".into()),
|
||||||
oauth2.refresh_token,
|
oauth2.refresh_token,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let mut token_url = replace_in_uri(&token_url, "$CLIENT_ID", oauth2.client_id);
|
|
||||||
token_url = replace_in_uri(&token_url, "$REDIRECT_URI", &redirect_uri);
|
// to allow easier specification of different configurations,
|
||||||
token_url = replace_in_uri(&token_url, "$CODE", code.as_ref());
|
// token_url is in GET-method-format, sth. as https://domain?param1=val1¶m2=val2 -
|
||||||
if let Some(ref token) = refresh_token {
|
// convert this to POST-format ...
|
||||||
token_url = replace_in_uri(&token_url, "$REFRESH_TOKEN", token);
|
let mut parts = token_url.splitn(2, '?');
|
||||||
|
let post_url = parts.next().unwrap_or_default();
|
||||||
|
let post_args = parts.next().unwrap_or_default();
|
||||||
|
let mut post_param = HashMap::new();
|
||||||
|
for key_value_pair in post_args.split('&') {
|
||||||
|
let mut parts = key_value_pair.splitn(2, '=');
|
||||||
|
let key = parts.next().unwrap_or_default();
|
||||||
|
let mut value = parts.next().unwrap_or_default();
|
||||||
|
|
||||||
|
if value == "$CLIENT_ID" {
|
||||||
|
value = oauth2.client_id;
|
||||||
|
} else if value == "$REDIRECT_URI" {
|
||||||
|
value = &redirect_uri;
|
||||||
|
} else if value == "$CODE" {
|
||||||
|
value = code.as_ref();
|
||||||
|
} else if value == "$REFRESH_TOKEN" && refresh_token.is_some() {
|
||||||
|
value = refresh_token.as_ref().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
post_param.insert(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = reqwest::Client::new().post(&token_url).send();
|
// ... and POST
|
||||||
|
let response = reqwest::Client::new()
|
||||||
|
.post(post_url)
|
||||||
|
.form(&post_param)
|
||||||
|
.send();
|
||||||
if response.is_err() {
|
if response.is_err() {
|
||||||
warn!(
|
warn!(
|
||||||
context,
|
context,
|
||||||
@@ -139,13 +165,14 @@ pub fn dc_get_oauth2_access_token(
|
|||||||
if !response.status().is_success() {
|
if !response.status().is_success() {
|
||||||
warn!(
|
warn!(
|
||||||
context,
|
context,
|
||||||
"Error calling OAuth2 at {}: {:?}",
|
"Unsuccessful response when calling OAuth2 at {}: {:?}",
|
||||||
token_url,
|
token_url,
|
||||||
response.status()
|
response.status()
|
||||||
);
|
);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generate new token: parse returned json
|
||||||
let parsed: reqwest::Result<Response> = response.json();
|
let parsed: reqwest::Result<Response> = response.json();
|
||||||
if parsed.is_err() {
|
if parsed.is_err() {
|
||||||
warn!(
|
warn!(
|
||||||
@@ -155,15 +182,17 @@ pub fn dc_get_oauth2_access_token(
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
println!("response: {:?}", &parsed);
|
println!("response: {:?}", &parsed);
|
||||||
|
|
||||||
|
// update refresh_token if given, typically on the first round, but we update it later as well.
|
||||||
let response = parsed.unwrap();
|
let response = parsed.unwrap();
|
||||||
if let Some(ref token) = response.refresh_token {
|
if let Some(ref token) = response.refresh_token {
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.set_config(context, "oauth2_refresh_token", Some(token))
|
.set_raw_config(context, "oauth2_refresh_token", Some(token))
|
||||||
.ok();
|
.ok();
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.set_config(context, "oauth2_refresh_token_for", Some(code.as_ref()))
|
.set_raw_config(context, "oauth2_refresh_token_for", Some(code.as_ref()))
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +201,7 @@ pub fn dc_get_oauth2_access_token(
|
|||||||
if let Some(ref token) = response.access_token {
|
if let Some(ref token) = response.access_token {
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.set_config(context, "oauth2_access_token", Some(token))
|
.set_raw_config(context, "oauth2_access_token", Some(token))
|
||||||
.ok();
|
.ok();
|
||||||
let expires_in = response
|
let expires_in = response
|
||||||
.expires_in
|
.expires_in
|
||||||
@@ -181,13 +210,13 @@ pub fn dc_get_oauth2_access_token(
|
|||||||
.unwrap_or_else(|| 0);
|
.unwrap_or_else(|| 0);
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.set_config_int64(context, "oauth2_timestamp_expires", expires_in)
|
.set_raw_config_int64(context, "oauth2_timestamp_expires", expires_in)
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
if update_redirect_uri_on_success {
|
if update_redirect_uri_on_success {
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.set_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref()))
|
.set_raw_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref()))
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -207,21 +236,16 @@ pub fn dc_get_oauth2_addr(
|
|||||||
addr: impl AsRef<str>,
|
addr: impl AsRef<str>,
|
||||||
code: impl AsRef<str>,
|
code: impl AsRef<str>,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
let oauth2 = Oauth2::from_address(addr.as_ref());
|
let oauth2 = Oauth2::from_address(addr.as_ref())?;
|
||||||
if oauth2.is_none() {
|
oauth2.get_userinfo?;
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let oauth2 = oauth2.unwrap();
|
|
||||||
if oauth2.get_userinfo.is_none() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(access_token) = dc_get_oauth2_access_token(context, addr.as_ref(), code.as_ref(), 0)
|
if let Some(access_token) =
|
||||||
|
dc_get_oauth2_access_token(context, addr.as_ref(), code.as_ref(), false)
|
||||||
{
|
{
|
||||||
let addr_out = oauth2.get_addr(context, access_token);
|
let addr_out = oauth2.get_addr(context, access_token);
|
||||||
if addr_out.is_none() {
|
if addr_out.is_none() {
|
||||||
// regenerate
|
// regenerate
|
||||||
if let Some(access_token) = dc_get_oauth2_access_token(context, addr, code, 0x1) {
|
if let Some(access_token) = dc_get_oauth2_access_token(context, addr, code, true) {
|
||||||
oauth2.get_addr(context, access_token)
|
oauth2.get_addr(context, access_token)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -273,7 +297,7 @@ impl Oauth2 {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let parsed: reqwest::Result<HashMap<String, String>> = response.json();
|
let parsed: reqwest::Result<HashMap<String, serde_json::Value>> = response.json();
|
||||||
if parsed.is_err() {
|
if parsed.is_err() {
|
||||||
warn!(
|
warn!(
|
||||||
context,
|
context,
|
||||||
@@ -282,11 +306,13 @@ impl Oauth2 {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if let Ok(response) = parsed {
|
if let Ok(response) = parsed {
|
||||||
|
// serde_json::Value.as_str() removes the quotes of json-strings
|
||||||
let addr = response.get("email");
|
let addr = response.get("email");
|
||||||
if addr.is_none() {
|
if addr.is_none() {
|
||||||
warn!(context, "E-mail missing in userinfo.");
|
warn!(context, "E-mail missing in userinfo.");
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
|
let addr = addr.unwrap().as_str();
|
||||||
addr.map(|addr| addr.to_string())
|
addr.map(|addr| addr.to_string())
|
||||||
} else {
|
} else {
|
||||||
warn!(context, "Failed to parse userinfo.");
|
warn!(context, "Failed to parse userinfo.");
|
||||||
@@ -298,7 +324,7 @@ impl Oauth2 {
|
|||||||
fn is_expired(context: &Context) -> bool {
|
fn is_expired(context: &Context) -> bool {
|
||||||
let expire_timestamp = context
|
let expire_timestamp = context
|
||||||
.sql
|
.sql
|
||||||
.get_config_int64(context, "oauth2_timestamp_expires")
|
.get_raw_config_int64(context, "oauth2_timestamp_expires")
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if expire_timestamp <= 0 {
|
if expire_timestamp <= 0 {
|
||||||
|
|||||||
62
src/param.rs
62
src/param.rs
@@ -12,65 +12,65 @@ use crate::error;
|
|||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum Param {
|
pub enum Param {
|
||||||
/// For messages and jobs
|
/// For messages and jobs
|
||||||
File = 'f' as u8,
|
File = b'f',
|
||||||
/// For Messages
|
/// For Messages
|
||||||
Width = 'w' as u8,
|
Width = b'w',
|
||||||
/// For Messages
|
/// For Messages
|
||||||
Height = 'h' as u8,
|
Height = b'h',
|
||||||
/// For Messages
|
/// For Messages
|
||||||
Duration = 'd' as u8,
|
Duration = b'd',
|
||||||
/// For Messages
|
/// For Messages
|
||||||
MimeType = 'm' as u8,
|
MimeType = b'm',
|
||||||
/// For Messages: message is encryoted, outgoing: guarantee E2EE or the message is not send
|
/// For Messages: message is encryoted, outgoing: guarantee E2EE or the message is not send
|
||||||
GuranteeE2ee = 'c' as u8,
|
GuranteeE2ee = b'c',
|
||||||
/// For Messages: decrypted with validation errors or without mutual set, if neither
|
/// For Messages: decrypted with validation errors or without mutual set, if neither
|
||||||
/// 'c' nor 'e' are preset, the messages is only transport encrypted.
|
/// 'c' nor 'e' are preset, the messages is only transport encrypted.
|
||||||
ErroneousE2ee = 'e' as u8,
|
ErroneousE2ee = b'e',
|
||||||
/// For Messages: force unencrypted message, either `ForcePlaintext::AddAutocryptHeader` (1),
|
/// For Messages: force unencrypted message, either `ForcePlaintext::AddAutocryptHeader` (1),
|
||||||
/// `ForcePlaintext::NoAutocryptHeader` (2) or 0.
|
/// `ForcePlaintext::NoAutocryptHeader` (2) or 0.
|
||||||
ForcePlaintext = 'u' as u8,
|
ForcePlaintext = b'u',
|
||||||
/// For Messages
|
/// For Messages
|
||||||
WantsMdn = 'r' as u8,
|
WantsMdn = b'r',
|
||||||
/// For Messages
|
/// For Messages
|
||||||
Forwarded = 'a' as u8,
|
Forwarded = b'a',
|
||||||
/// For Messages
|
/// For Messages
|
||||||
Cmd = 'S' as u8,
|
Cmd = b'S',
|
||||||
/// For Messages
|
/// For Messages
|
||||||
Arg = 'E' as u8,
|
Arg = b'E',
|
||||||
/// For Messages
|
/// For Messages
|
||||||
Arg2 = 'F' as u8,
|
Arg2 = b'F',
|
||||||
/// For Messages
|
/// For Messages
|
||||||
Arg3 = 'G' as u8,
|
Arg3 = b'G',
|
||||||
/// For Messages
|
/// For Messages
|
||||||
Arg4 = 'H' as u8,
|
Arg4 = b'H',
|
||||||
/// For Messages
|
/// For Messages
|
||||||
Error = 'L' as u8,
|
Error = b'L',
|
||||||
/// For Messages: space-separated list of messaged IDs of forwarded copies.
|
/// For Messages: space-separated list of messaged IDs of forwarded copies.
|
||||||
PrepForwards = 'P' as u8,
|
PrepForwards = b'P',
|
||||||
/// For Jobs
|
/// For Jobs
|
||||||
SetLatitude = 'l' as u8,
|
SetLatitude = b'l',
|
||||||
/// For Jobs
|
/// For Jobs
|
||||||
SetLongitude = 'n' as u8,
|
SetLongitude = b'n',
|
||||||
/// For Jobs
|
/// For Jobs
|
||||||
ServerFolder = 'Z' as u8,
|
ServerFolder = b'Z',
|
||||||
/// For Jobs
|
/// For Jobs
|
||||||
ServerUid = 'z' as u8,
|
ServerUid = b'z',
|
||||||
/// For Jobs
|
/// For Jobs
|
||||||
AlsoMove = 'M' as u8,
|
AlsoMove = b'M',
|
||||||
/// For Jobs: space-separated list of message recipients
|
/// For Jobs: space-separated list of message recipients
|
||||||
Recipients = 'R' as u8,
|
Recipients = b'R',
|
||||||
// For Groups
|
// For Groups
|
||||||
Unpromoted = 'U' as u8,
|
Unpromoted = b'U',
|
||||||
// For Groups and Contacts
|
// For Groups and Contacts
|
||||||
ProfileImage = 'i' as u8,
|
ProfileImage = b'i',
|
||||||
// For Chats
|
// For Chats
|
||||||
Selftalk = 'K' as u8,
|
Selftalk = b'K',
|
||||||
// For QR
|
// For QR
|
||||||
Auth = 's' as u8,
|
Auth = b's',
|
||||||
// For QR
|
// For QR
|
||||||
GroupId = 'x' as u8,
|
GroupId = b'x',
|
||||||
// For QR
|
// For QR
|
||||||
GroupName = 'g' as u8,
|
GroupName = b'g',
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Possible values for `Param::ForcePlaintext`.
|
/// Possible values for `Param::ForcePlaintext`.
|
||||||
@@ -122,8 +122,8 @@ impl str::FromStr for Params {
|
|||||||
ensure!(key.is_some(), "Missing key");
|
ensure!(key.is_some(), "Missing key");
|
||||||
ensure!(value.is_some(), "Missing value");
|
ensure!(value.is_some(), "Missing value");
|
||||||
|
|
||||||
let key = key.unwrap().trim();
|
let key = key.unwrap_or_default().trim();
|
||||||
let value = value.unwrap().trim();
|
let value = value.unwrap_or_default().trim();
|
||||||
|
|
||||||
if let Some(key) = Param::from_u8(key.as_bytes()[0]) {
|
if let Some(key) = Param::from_u8(key.as_bytes()[0]) {
|
||||||
inner.insert(key, value.to_string());
|
inner.insert(key, value.to_string());
|
||||||
|
|||||||
147
src/peerstate.rs
147
src/peerstate.rs
@@ -7,6 +7,7 @@ use crate::aheader::*;
|
|||||||
use crate::chat::*;
|
use crate::chat::*;
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
|
use crate::error::*;
|
||||||
use crate::key::*;
|
use crate::key::*;
|
||||||
use crate::sql::{self, Sql};
|
use crate::sql::{self, Sql};
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ pub struct Peerstate<'a> {
|
|||||||
pub gossip_key: Option<Key>,
|
pub gossip_key: Option<Key>,
|
||||||
pub gossip_timestamp: i64,
|
pub gossip_timestamp: i64,
|
||||||
pub gossip_key_fingerprint: Option<String>,
|
pub gossip_key_fingerprint: Option<String>,
|
||||||
verified_key: VerifiedKey,
|
pub verified_key: Option<Key>,
|
||||||
pub verified_key_fingerprint: Option<String>,
|
pub verified_key_fingerprint: Option<String>,
|
||||||
pub to_save: Option<ToSave>,
|
pub to_save: Option<ToSave>,
|
||||||
pub degrade_event: Option<DegradeEvent>,
|
pub degrade_event: Option<DegradeEvent>,
|
||||||
@@ -84,32 +85,6 @@ pub enum DegradeEvent {
|
|||||||
FingerprintChanged = 0x02,
|
FingerprintChanged = 0x02,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
enum VerifiedKey {
|
|
||||||
Gossip,
|
|
||||||
Public,
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for VerifiedKey {
|
|
||||||
fn default() -> Self {
|
|
||||||
VerifiedKey::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VerifiedKey {
|
|
||||||
pub fn is_none(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
VerifiedKey::None => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_some(&self) -> bool {
|
|
||||||
!self.is_none()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Peerstate<'a> {
|
impl<'a> Peerstate<'a> {
|
||||||
pub fn new(context: &'a Context) -> Self {
|
pub fn new(context: &'a Context) -> Self {
|
||||||
Peerstate {
|
Peerstate {
|
||||||
@@ -123,21 +98,13 @@ impl<'a> Peerstate<'a> {
|
|||||||
gossip_key: None,
|
gossip_key: None,
|
||||||
gossip_key_fingerprint: None,
|
gossip_key_fingerprint: None,
|
||||||
gossip_timestamp: 0,
|
gossip_timestamp: 0,
|
||||||
verified_key: Default::default(),
|
verified_key: None,
|
||||||
verified_key_fingerprint: None,
|
verified_key_fingerprint: None,
|
||||||
to_save: None,
|
to_save: None,
|
||||||
degrade_event: None,
|
degrade_event: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verified_key(&self) -> Option<&Key> {
|
|
||||||
match self.verified_key {
|
|
||||||
VerifiedKey::Public => self.public_key.as_ref(),
|
|
||||||
VerifiedKey::Gossip => self.gossip_key.as_ref(),
|
|
||||||
VerifiedKey::None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_header(context: &'a Context, header: &Aheader, message_time: i64) -> Self {
|
pub fn from_header(context: &'a Context, header: &Aheader, message_time: i64) -> Self {
|
||||||
let mut res = Self::new(context);
|
let mut res = Self::new(context);
|
||||||
|
|
||||||
@@ -241,19 +208,11 @@ impl<'a> Peerstate<'a> {
|
|||||||
.get(6)
|
.get(6)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public));
|
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public));
|
||||||
let vk = row
|
res.verified_key = row
|
||||||
.get(9)
|
.get(9)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public));
|
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public));
|
||||||
|
|
||||||
res.verified_key = if vk == res.gossip_key && res.gossip_key.is_some() {
|
|
||||||
VerifiedKey::Gossip
|
|
||||||
} else if vk == res.public_key {
|
|
||||||
VerifiedKey::Public
|
|
||||||
} else {
|
|
||||||
VerifiedKey::None
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
})
|
})
|
||||||
.ok()
|
.ok()
|
||||||
@@ -373,7 +332,7 @@ impl<'a> Peerstate<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if 0 != min_verified {
|
if 0 != min_verified {
|
||||||
return self.verified_key();
|
return self.verified_key.as_ref();
|
||||||
}
|
}
|
||||||
if self.public_key.is_some() {
|
if self.public_key.is_some() {
|
||||||
return self.public_key.as_ref();
|
return self.public_key.as_ref();
|
||||||
@@ -390,7 +349,7 @@ impl<'a> Peerstate<'a> {
|
|||||||
&& self.public_key_fingerprint.as_ref().unwrap() == fingerprint
|
&& self.public_key_fingerprint.as_ref().unwrap() == fingerprint
|
||||||
{
|
{
|
||||||
self.to_save = Some(ToSave::All);
|
self.to_save = Some(ToSave::All);
|
||||||
self.verified_key = VerifiedKey::Public;
|
self.verified_key = self.public_key.clone();
|
||||||
self.verified_key_fingerprint = self.public_key_fingerprint.clone();
|
self.verified_key_fingerprint = self.public_key_fingerprint.clone();
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
@@ -399,7 +358,7 @@ impl<'a> Peerstate<'a> {
|
|||||||
&& self.gossip_key_fingerprint.as_ref().unwrap() == fingerprint
|
&& self.gossip_key_fingerprint.as_ref().unwrap() == fingerprint
|
||||||
{
|
{
|
||||||
self.to_save = Some(ToSave::All);
|
self.to_save = Some(ToSave::All);
|
||||||
self.verified_key = VerifiedKey::Gossip;
|
self.verified_key = self.gossip_key.clone();
|
||||||
self.verified_key_fingerprint = self.gossip_key_fingerprint.clone();
|
self.verified_key_fingerprint = self.gossip_key_fingerprint.clone();
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
@@ -408,28 +367,19 @@ impl<'a> Peerstate<'a> {
|
|||||||
success
|
success
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_to_db(&self, sql: &Sql, create: bool) -> bool {
|
pub fn save_to_db(&self, sql: &Sql, create: bool) -> Result<()> {
|
||||||
let mut success = false;
|
ensure!(!self.addr.is_none(), "self.addr is not configured");
|
||||||
|
|
||||||
if self.addr.is_none() {
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
if create {
|
if create {
|
||||||
if sql::execute(
|
sql::execute(
|
||||||
self.context,
|
self.context,
|
||||||
sql,
|
sql,
|
||||||
"INSERT INTO acpeerstates (addr) VALUES(?);",
|
"INSERT INTO acpeerstates (addr) VALUES(?);",
|
||||||
params![self.addr.as_ref().unwrap()],
|
params![self.addr.as_ref().unwrap()],
|
||||||
)
|
)?;
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.to_save == Some(ToSave::All) || create {
|
if self.to_save == Some(ToSave::All) || create {
|
||||||
success = sql::execute(
|
sql::execute(
|
||||||
self.context,
|
self.context,
|
||||||
sql,
|
sql,
|
||||||
"UPDATE acpeerstates \
|
"UPDATE acpeerstates \
|
||||||
@@ -446,14 +396,14 @@ impl<'a> Peerstate<'a> {
|
|||||||
self.gossip_key.as_ref().map(|k| k.to_bytes()),
|
self.gossip_key.as_ref().map(|k| k.to_bytes()),
|
||||||
&self.public_key_fingerprint,
|
&self.public_key_fingerprint,
|
||||||
&self.gossip_key_fingerprint,
|
&self.gossip_key_fingerprint,
|
||||||
self.verified_key().map(|k| k.to_bytes()),
|
self.verified_key.as_ref().map(|k| k.to_bytes()),
|
||||||
&self.verified_key_fingerprint,
|
&self.verified_key_fingerprint,
|
||||||
&self.addr,
|
&self.addr,
|
||||||
],
|
],
|
||||||
).is_ok();
|
)?;
|
||||||
assert_eq!(success, true);
|
reset_gossiped_timestamp(self.context, 0);
|
||||||
} else if self.to_save == Some(ToSave::Timestamps) {
|
} else if self.to_save == Some(ToSave::Timestamps) {
|
||||||
success = sql::execute(
|
sql::execute(
|
||||||
self.context,
|
self.context,
|
||||||
sql,
|
sql,
|
||||||
"UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? \
|
"UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? \
|
||||||
@@ -464,15 +414,10 @@ impl<'a> Peerstate<'a> {
|
|||||||
self.gossip_timestamp,
|
self.gossip_timestamp,
|
||||||
&self.addr
|
&self.addr
|
||||||
],
|
],
|
||||||
)
|
)?;
|
||||||
.is_ok();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.to_save == Some(ToSave::All) || create {
|
Ok(())
|
||||||
reset_gossiped_timestamp(self.context, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
success
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_verified_key(&self, fingerprints: &HashSet<String>) -> bool {
|
pub fn has_verified_key(&self, fingerprints: &HashSet<String>) -> bool {
|
||||||
@@ -516,15 +461,18 @@ mod tests {
|
|||||||
gossip_key: Some(pub_key.clone()),
|
gossip_key: Some(pub_key.clone()),
|
||||||
gossip_timestamp: 12,
|
gossip_timestamp: 12,
|
||||||
gossip_key_fingerprint: Some(pub_key.fingerprint()),
|
gossip_key_fingerprint: Some(pub_key.fingerprint()),
|
||||||
verified_key: VerifiedKey::Gossip,
|
verified_key: Some(pub_key.clone()),
|
||||||
verified_key_fingerprint: Some(pub_key.fingerprint()),
|
verified_key_fingerprint: Some(pub_key.fingerprint()),
|
||||||
to_save: Some(ToSave::All),
|
to_save: Some(ToSave::All),
|
||||||
degrade_event: None,
|
degrade_event: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(peerstate.save_to_db(&ctx.ctx.sql, true), "failed to save");
|
assert!(
|
||||||
|
peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(),
|
||||||
|
"failed to save to db"
|
||||||
|
);
|
||||||
|
|
||||||
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr.into())
|
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr)
|
||||||
.expect("failed to load peerstate from db");
|
.expect("failed to load peerstate from db");
|
||||||
|
|
||||||
// clear to_save, as that is not persissted
|
// clear to_save, as that is not persissted
|
||||||
@@ -536,6 +484,44 @@ mod tests {
|
|||||||
assert_eq!(peerstate, peerstate_new2);
|
assert_eq!(peerstate, peerstate_new2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_peerstate_double_create() {
|
||||||
|
let ctx = crate::test_utils::dummy_context();
|
||||||
|
let addr = "hello@mail.com";
|
||||||
|
|
||||||
|
let pub_key = crate::key::Key::from_base64(
|
||||||
|
include_str!("../test-data/key/public.asc"),
|
||||||
|
KeyType::Public,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let peerstate = Peerstate {
|
||||||
|
context: &ctx.ctx,
|
||||||
|
addr: Some(addr.into()),
|
||||||
|
last_seen: 10,
|
||||||
|
last_seen_autocrypt: 11,
|
||||||
|
prefer_encrypt: EncryptPreference::Mutual,
|
||||||
|
public_key: Some(pub_key.clone()),
|
||||||
|
public_key_fingerprint: Some(pub_key.fingerprint()),
|
||||||
|
gossip_key: None,
|
||||||
|
gossip_timestamp: 12,
|
||||||
|
gossip_key_fingerprint: None,
|
||||||
|
verified_key: None,
|
||||||
|
verified_key_fingerprint: None,
|
||||||
|
to_save: Some(ToSave::All),
|
||||||
|
degrade_event: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(),
|
||||||
|
"failed to save"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(),
|
||||||
|
"double-call with create failed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_peerstate_with_empty_gossip_key_save_to_db() {
|
fn test_peerstate_with_empty_gossip_key_save_to_db() {
|
||||||
let ctx = crate::test_utils::dummy_context();
|
let ctx = crate::test_utils::dummy_context();
|
||||||
@@ -558,15 +544,18 @@ mod tests {
|
|||||||
gossip_key: None,
|
gossip_key: None,
|
||||||
gossip_timestamp: 12,
|
gossip_timestamp: 12,
|
||||||
gossip_key_fingerprint: None,
|
gossip_key_fingerprint: None,
|
||||||
verified_key: VerifiedKey::None,
|
verified_key: None,
|
||||||
verified_key_fingerprint: None,
|
verified_key_fingerprint: None,
|
||||||
to_save: Some(ToSave::All),
|
to_save: Some(ToSave::All),
|
||||||
degrade_event: None,
|
degrade_event: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(peerstate.save_to_db(&ctx.ctx.sql, true), "failed to save");
|
assert!(
|
||||||
|
peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(),
|
||||||
|
"failed to save"
|
||||||
|
);
|
||||||
|
|
||||||
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr.into())
|
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr)
|
||||||
.expect("failed to load peerstate from db");
|
.expect("failed to load peerstate from db");
|
||||||
|
|
||||||
// clear to_save, as that is not persissted
|
// clear to_save, as that is not persissted
|
||||||
|
|||||||
315
src/pgp.rs
315
src/pgp.rs
@@ -1,8 +1,8 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::{BTreeMap, HashSet};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use std::ptr;
|
|
||||||
|
|
||||||
|
use pgp::armor::BlockType;
|
||||||
use pgp::composed::{
|
use pgp::composed::{
|
||||||
Deserializable, KeyType as PgpKeyType, Message, SecretKeyParamsBuilder, SignedPublicKey,
|
Deserializable, KeyType as PgpKeyType, Message, SecretKeyParamsBuilder, SignedPublicKey,
|
||||||
SignedSecretKey, SubkeyParamsBuilder,
|
SignedSecretKey, SubkeyParamsBuilder,
|
||||||
@@ -11,131 +11,43 @@ use pgp::crypto::{HashAlgorithm, SymmetricKeyAlgorithm};
|
|||||||
use pgp::types::{CompressionAlgorithm, KeyTrait, SecretKeyTrait, StringToKey};
|
use pgp::types::{CompressionAlgorithm, KeyTrait, SecretKeyTrait, StringToKey};
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
|
|
||||||
use crate::dc_tools::*;
|
use crate::error::Error;
|
||||||
use crate::key::*;
|
use crate::key::*;
|
||||||
use crate::keyring::*;
|
use crate::keyring::*;
|
||||||
use crate::x::*;
|
|
||||||
|
|
||||||
pub unsafe fn dc_split_armored_data(
|
pub const HEADER_AUTOCRYPT: &str = "autocrypt-prefer-encrypt";
|
||||||
buf: *mut libc::c_char,
|
pub const HEADER_SETUPCODE: &str = "passphrase-begin";
|
||||||
ret_headerline: *mut *const libc::c_char,
|
|
||||||
ret_setupcodebegin: *mut *const libc::c_char,
|
|
||||||
ret_preferencrypt: *mut *const libc::c_char,
|
|
||||||
ret_base64: *mut *const libc::c_char,
|
|
||||||
) -> bool {
|
|
||||||
let mut success = false;
|
|
||||||
let mut line_chars: libc::size_t = 0;
|
|
||||||
let mut line: *mut libc::c_char = buf;
|
|
||||||
let mut p1: *mut libc::c_char = buf;
|
|
||||||
let mut p2: *mut libc::c_char;
|
|
||||||
let mut headerline: *mut libc::c_char = ptr::null_mut();
|
|
||||||
let mut base64: *mut libc::c_char = ptr::null_mut();
|
|
||||||
if !ret_headerline.is_null() {
|
|
||||||
*ret_headerline = ptr::null()
|
|
||||||
}
|
|
||||||
if !ret_setupcodebegin.is_null() {
|
|
||||||
*ret_setupcodebegin = ptr::null_mut();
|
|
||||||
}
|
|
||||||
if !ret_preferencrypt.is_null() {
|
|
||||||
*ret_preferencrypt = ptr::null();
|
|
||||||
}
|
|
||||||
if !ret_base64.is_null() {
|
|
||||||
*ret_base64 = ptr::null();
|
|
||||||
}
|
|
||||||
if !(buf.is_null() || ret_headerline.is_null()) {
|
|
||||||
dc_remove_cr_chars(buf);
|
|
||||||
while 0 != *p1 {
|
|
||||||
if *p1 as libc::c_int == '\n' as i32 {
|
|
||||||
*line.offset(line_chars as isize) = 0i32 as libc::c_char;
|
|
||||||
if headerline.is_null() {
|
|
||||||
dc_trim(line);
|
|
||||||
if strncmp(
|
|
||||||
line,
|
|
||||||
b"-----BEGIN \x00" as *const u8 as *const libc::c_char,
|
|
||||||
1,
|
|
||||||
) == 0i32
|
|
||||||
&& strncmp(
|
|
||||||
&mut *line.offset(strlen(line).wrapping_sub(5) as isize),
|
|
||||||
b"-----\x00" as *const u8 as *const libc::c_char,
|
|
||||||
5,
|
|
||||||
) == 0i32
|
|
||||||
{
|
|
||||||
headerline = line;
|
|
||||||
if !ret_headerline.is_null() {
|
|
||||||
*ret_headerline = headerline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if strspn(line, b"\t\r\n \x00" as *const u8 as *const libc::c_char)
|
|
||||||
== strlen(line)
|
|
||||||
{
|
|
||||||
base64 = p1.offset(1isize);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
p2 = strchr(line, ':' as i32);
|
|
||||||
if p2.is_null() {
|
|
||||||
*line.offset(line_chars as isize) = '\n' as i32 as libc::c_char;
|
|
||||||
base64 = line;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
*p2 = 0i32 as libc::c_char;
|
|
||||||
dc_trim(line);
|
|
||||||
if strcasecmp(
|
|
||||||
line,
|
|
||||||
b"Passphrase-Begin\x00" as *const u8 as *const libc::c_char,
|
|
||||||
) == 0i32
|
|
||||||
{
|
|
||||||
p2 = p2.offset(1isize);
|
|
||||||
dc_trim(p2);
|
|
||||||
if !ret_setupcodebegin.is_null() {
|
|
||||||
*ret_setupcodebegin = p2
|
|
||||||
}
|
|
||||||
} else if strcasecmp(
|
|
||||||
line,
|
|
||||||
b"Autocrypt-Prefer-Encrypt\x00" as *const u8 as *const libc::c_char,
|
|
||||||
) == 0i32
|
|
||||||
{
|
|
||||||
p2 = p2.offset(1isize);
|
|
||||||
dc_trim(p2);
|
|
||||||
if !ret_preferencrypt.is_null() {
|
|
||||||
*ret_preferencrypt = p2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p1 = p1.offset(1isize);
|
|
||||||
line = p1;
|
|
||||||
line_chars = 0;
|
|
||||||
} else {
|
|
||||||
p1 = p1.offset(1isize);
|
|
||||||
line_chars = line_chars.wrapping_add(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !(headerline.is_null() || base64.is_null()) {
|
|
||||||
/* now, line points to beginning of base64 data, search end */
|
|
||||||
/*the trailing space makes sure, this is not a normal base64 sequence*/
|
|
||||||
p1 = strstr(base64, b"-----END \x00" as *const u8 as *const libc::c_char);
|
|
||||||
if !(p1.is_null()
|
|
||||||
|| strncmp(
|
|
||||||
p1.offset(9isize),
|
|
||||||
headerline.offset(11isize),
|
|
||||||
strlen(headerline.offset(11isize)),
|
|
||||||
) != 0i32)
|
|
||||||
{
|
|
||||||
*p1 = 0i32 as libc::c_char;
|
|
||||||
dc_trim(base64);
|
|
||||||
if !ret_base64.is_null() {
|
|
||||||
*ret_base64 = base64
|
|
||||||
}
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
success
|
/// Split data from PGP Armored Data as defined in https://tools.ietf.org/html/rfc4880#section-6.2.
|
||||||
|
///
|
||||||
|
/// Returns (type, headers, base64 encoded body).
|
||||||
|
pub fn split_armored_data(
|
||||||
|
buf: &[u8],
|
||||||
|
) -> Result<(BlockType, BTreeMap<String, String>, Vec<u8>), Error> {
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
let cursor = Cursor::new(buf);
|
||||||
|
let mut dearmor = pgp::armor::Dearmor::new(cursor);
|
||||||
|
|
||||||
|
let mut bytes = Vec::with_capacity(buf.len());
|
||||||
|
|
||||||
|
dearmor.read_to_end(&mut bytes)?;
|
||||||
|
ensure!(dearmor.typ.is_some(), "Failed to parse type");
|
||||||
|
|
||||||
|
let typ = dearmor.typ.unwrap();
|
||||||
|
|
||||||
|
// normalize headers
|
||||||
|
let headers = dearmor
|
||||||
|
.headers
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, value)| (key.trim().to_lowercase(), value.trim().to_string()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok((typ, headers, bytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new key pair.
|
/// Create a new key pair.
|
||||||
pub fn dc_pgp_create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
|
pub fn create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
|
||||||
let user_id = format!("<{}>", addr.as_ref());
|
let user_id = format!("<{}>", addr.as_ref());
|
||||||
|
|
||||||
let key_params = SecretKeyParamsBuilder::default()
|
let key_params = SecretKeyParamsBuilder::default()
|
||||||
@@ -185,15 +97,15 @@ pub fn dc_pgp_create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
|
|||||||
Some((Key::Public(public_key), Key::Secret(private_key)))
|
Some((Key::Public(public_key), Key::Secret(private_key)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dc_pgp_pk_encrypt(
|
pub fn pk_encrypt(
|
||||||
plain: &[u8],
|
plain: &[u8],
|
||||||
public_keys_for_encryption: &Keyring,
|
public_keys_for_encryption: &Keyring,
|
||||||
private_key_for_signing: Option<&Key>,
|
private_key_for_signing: Option<&Key>,
|
||||||
) -> Option<String> {
|
) -> Result<String, Error> {
|
||||||
let lit_msg = Message::new_literal_bytes("", plain);
|
let lit_msg = Message::new_literal_bytes("", plain);
|
||||||
let pkeys: Vec<&SignedPublicKey> = public_keys_for_encryption
|
let pkeys: Vec<&SignedPublicKey> = public_keys_for_encryption
|
||||||
.keys()
|
.keys()
|
||||||
.into_iter()
|
.iter()
|
||||||
.filter_map(|key| {
|
.filter_map(|key| {
|
||||||
let k: &Key = &key;
|
let k: &Key = &key;
|
||||||
k.try_into().ok()
|
k.try_into().ok()
|
||||||
@@ -203,9 +115,10 @@ pub fn dc_pgp_pk_encrypt(
|
|||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
// TODO: measure time
|
// TODO: measure time
|
||||||
// TODO: better error handling
|
|
||||||
let encrypted_msg = if let Some(private_key) = private_key_for_signing {
|
let encrypted_msg = if let Some(private_key) = private_key_for_signing {
|
||||||
let skey: &SignedSecretKey = private_key.try_into().unwrap();
|
let skey: &SignedSecretKey = private_key
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| format_err!("Invalid private key"))?;
|
||||||
|
|
||||||
lit_msg
|
lit_msg
|
||||||
.sign(skey, || "".into(), Default::default())
|
.sign(skey, || "".into(), Default::default())
|
||||||
@@ -215,87 +128,119 @@ pub fn dc_pgp_pk_encrypt(
|
|||||||
lit_msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys)
|
lit_msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys)
|
||||||
};
|
};
|
||||||
|
|
||||||
encrypted_msg
|
let msg = encrypted_msg?;
|
||||||
.and_then(|msg| msg.to_armored_string(None))
|
let encoded_msg = msg.to_armored_string(None)?;
|
||||||
.ok()
|
|
||||||
|
Ok(encoded_msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dc_pgp_pk_decrypt(
|
pub fn pk_decrypt(
|
||||||
ctext: &[u8],
|
ctext: &[u8],
|
||||||
private_keys_for_decryption: &Keyring,
|
private_keys_for_decryption: &Keyring,
|
||||||
public_keys_for_validation: &Keyring,
|
public_keys_for_validation: &Keyring,
|
||||||
ret_signature_fingerprints: Option<&mut HashSet<String>>,
|
ret_signature_fingerprints: Option<&mut HashSet<String>>,
|
||||||
) -> Option<Vec<u8>> {
|
) -> Result<Vec<u8>, Error> {
|
||||||
// TODO: proper error handling
|
let (msg, _) = Message::from_armor_single(Cursor::new(ctext))?;
|
||||||
if let Ok((msg, _)) = Message::from_armor_single(Cursor::new(ctext)) {
|
let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption
|
||||||
let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption
|
.keys()
|
||||||
.keys()
|
.iter()
|
||||||
.iter()
|
.filter_map(|key| {
|
||||||
.filter_map(|key| {
|
let k: &Key = &key;
|
||||||
let k: &Key = &key;
|
k.try_into().ok()
|
||||||
k.try_into().ok()
|
})
|
||||||
})
|
.collect();
|
||||||
.collect();
|
|
||||||
|
|
||||||
msg.decrypt(|| "".into(), || "".into(), &skeys[..])
|
let (decryptor, _) = msg.decrypt(|| "".into(), || "".into(), &skeys[..])?;
|
||||||
.and_then(|(mut decryptor, _)| {
|
let msgs = decryptor.collect::<Result<Vec<_>, _>>()?;
|
||||||
// TODO: how to handle the case when we detect multiple messages?
|
ensure!(!msgs.is_empty(), "No valid messages found");
|
||||||
decryptor.next().expect("no message")
|
|
||||||
})
|
|
||||||
.and_then(|dec_msg| {
|
|
||||||
if let Some(ret_signature_fingerprints) = ret_signature_fingerprints {
|
|
||||||
if !public_keys_for_validation.keys().is_empty() {
|
|
||||||
let pkeys: Vec<&SignedPublicKey> = public_keys_for_validation
|
|
||||||
.keys()
|
|
||||||
.iter()
|
|
||||||
.filter_map(|key| {
|
|
||||||
let k: &Key = &key;
|
|
||||||
k.try_into().ok()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for pkey in &pkeys {
|
let dec_msg = &msgs[0];
|
||||||
if dec_msg.verify(&pkey.primary_key).is_ok() {
|
|
||||||
let fp = hex::encode_upper(pkey.fingerprint());
|
if let Some(ret_signature_fingerprints) = ret_signature_fingerprints {
|
||||||
ret_signature_fingerprints.insert(fp);
|
if !public_keys_for_validation.keys().is_empty() {
|
||||||
}
|
let pkeys: Vec<&SignedPublicKey> = public_keys_for_validation
|
||||||
}
|
.keys()
|
||||||
}
|
.iter()
|
||||||
|
.filter_map(|key| {
|
||||||
|
let k: &Key = &key;
|
||||||
|
k.try_into().ok()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for pkey in &pkeys {
|
||||||
|
if dec_msg.verify(&pkey.primary_key).is_ok() {
|
||||||
|
let fp = hex::encode_upper(pkey.fingerprint());
|
||||||
|
ret_signature_fingerprints.insert(fp);
|
||||||
}
|
}
|
||||||
dec_msg.get_content()
|
}
|
||||||
})
|
}
|
||||||
.ok()
|
}
|
||||||
.and_then(|content| content)
|
|
||||||
} else {
|
match dec_msg.get_content()? {
|
||||||
None
|
Some(content) => Ok(content),
|
||||||
|
None => bail!("Decrypted message is empty"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Symmetric encryption.
|
/// Symmetric encryption.
|
||||||
pub fn dc_pgp_symm_encrypt(passphrase: &str, plain: &[u8]) -> Option<String> {
|
pub fn symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String, Error> {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
let lit_msg = Message::new_literal_bytes("", plain);
|
let lit_msg = Message::new_literal_bytes("", plain);
|
||||||
|
|
||||||
let s2k = StringToKey::new_default(&mut rng);
|
let s2k = StringToKey::new_default(&mut rng);
|
||||||
let msg =
|
let msg =
|
||||||
lit_msg.encrypt_with_password(&mut rng, s2k, Default::default(), || passphrase.into());
|
lit_msg.encrypt_with_password(&mut rng, s2k, Default::default(), || passphrase.into())?;
|
||||||
|
|
||||||
msg.and_then(|msg| msg.to_armored_string(None)).ok()
|
let encoded_msg = msg.to_armored_string(None)?;
|
||||||
|
|
||||||
|
Ok(encoded_msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Symmetric decryption.
|
/// Symmetric decryption.
|
||||||
pub fn dc_pgp_symm_decrypt(passphrase: &str, ctext: &[u8]) -> Option<Vec<u8>> {
|
pub fn symm_decrypt<T: std::io::Read + std::io::Seek>(
|
||||||
let enc_msg = Message::from_bytes(Cursor::new(ctext));
|
passphrase: &str,
|
||||||
|
ctext: T,
|
||||||
|
) -> Result<Vec<u8>, Error> {
|
||||||
|
let (enc_msg, _) = Message::from_armor_single(ctext)?;
|
||||||
|
let decryptor = enc_msg.decrypt_with_password(|| passphrase.into())?;
|
||||||
|
|
||||||
enc_msg
|
let msgs = decryptor.collect::<Result<Vec<_>, _>>()?;
|
||||||
.and_then(|msg| {
|
ensure!(!msgs.is_empty(), "No valid messages found");
|
||||||
let mut decryptor = msg.decrypt_with_password(|| passphrase.into())?;
|
|
||||||
match decryptor.next() {
|
match msgs[0].get_content()? {
|
||||||
Some(x) => x,
|
Some(content) => Ok(content),
|
||||||
None => Err(pgp::errors::Error::InvalidInput),
|
None => bail!("Decrypted message is empty"),
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.and_then(|msg| msg.get_content())
|
|
||||||
.ok()
|
#[cfg(test)]
|
||||||
.and_then(|content| content)
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_armored_data_1() {
|
||||||
|
let (typ, _headers, base64) = split_armored_data(
|
||||||
|
b"-----BEGIN PGP MESSAGE-----\nNoVal:\n\naGVsbG8gd29ybGQ=\n-----END PGP MESSAGE----",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(typ, BlockType::Message);
|
||||||
|
assert!(!base64.is_empty());
|
||||||
|
assert_eq!(
|
||||||
|
std::string::String::from_utf8(base64).unwrap(),
|
||||||
|
"hello world"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_armored_data_2() {
|
||||||
|
let (typ, headers, base64) = split_armored_data(
|
||||||
|
b"-----BEGIN PGP PRIVATE KEY BLOCK-----\nAutocrypt-Prefer-Encrypt: mutual \n\naGVsbG8gd29ybGQ=\n-----END PGP PRIVATE KEY BLOCK-----"
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(typ, BlockType::PrivateKey);
|
||||||
|
assert!(!base64.is_empty());
|
||||||
|
assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
|
|||||||
use crate::aheader::EncryptPreference;
|
use crate::aheader::EncryptPreference;
|
||||||
use crate::chat::{self, Chat};
|
use crate::chat::{self, Chat};
|
||||||
use crate::config::*;
|
use crate::config::*;
|
||||||
use crate::configure::*;
|
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::contact::*;
|
use crate::contact::*;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
@@ -13,7 +12,7 @@ use crate::error::Error;
|
|||||||
use crate::events::Event;
|
use crate::events::Event;
|
||||||
use crate::key::*;
|
use crate::key::*;
|
||||||
use crate::lot::LotState;
|
use crate::lot::LotState;
|
||||||
use crate::message::*;
|
use crate::message::Message;
|
||||||
use crate::param::*;
|
use crate::param::*;
|
||||||
use crate::peerstate::*;
|
use crate::peerstate::*;
|
||||||
use crate::qr::check_qr;
|
use crate::qr::check_qr;
|
||||||
@@ -82,10 +81,7 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<Str
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let self_name = context
|
let self_name = context.get_config(Config::Displayname).unwrap_or_default();
|
||||||
.sql
|
|
||||||
.get_config(context, "displayname")
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
fingerprint = match get_self_fingerprint(context) {
|
fingerprint = match get_self_fingerprint(context) {
|
||||||
Some(fp) => fp,
|
Some(fp) => fp,
|
||||||
@@ -160,7 +156,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
|
|||||||
bob.qr_scan = None;
|
bob.qr_scan = None;
|
||||||
|
|
||||||
if ongoing_allocated {
|
if ongoing_allocated {
|
||||||
dc_free_ongoing(context);
|
context.free_ongoing();
|
||||||
}
|
}
|
||||||
ret_chat_id as u32
|
ret_chat_id as u32
|
||||||
};
|
};
|
||||||
@@ -173,7 +169,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
|
|||||||
|
|
||||||
info!(context, "Requesting secure-join ...",);
|
info!(context, "Requesting secure-join ...",);
|
||||||
ensure_secret_key_exists(context).ok();
|
ensure_secret_key_exists(context).ok();
|
||||||
if !dc_alloc_ongoing(context) {
|
if !context.alloc_ongoing() {
|
||||||
return cleanup(&context, contact_chat_id, false, join_vg);
|
return cleanup(&context, contact_chat_id, false, join_vg);
|
||||||
}
|
}
|
||||||
let qr_scan = check_qr(context, &qr);
|
let qr_scan = check_qr(context, &qr);
|
||||||
@@ -187,7 +183,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
|
|||||||
error!(context, "Unknown contact.",);
|
error!(context, "Unknown contact.",);
|
||||||
return cleanup(&context, contact_chat_id, true, join_vg);
|
return cleanup(&context, contact_chat_id, true, join_vg);
|
||||||
}
|
}
|
||||||
if check_exit(context) {
|
if context.shall_stop_ongoing() {
|
||||||
return cleanup(&context, contact_chat_id, true, join_vg);
|
return cleanup(&context, contact_chat_id, true, join_vg);
|
||||||
}
|
}
|
||||||
join_vg = qr_scan.get_state() == LotState::QrAskVerifyGroup;
|
join_vg = qr_scan.get_state() == LotState::QrAskVerifyGroup;
|
||||||
@@ -213,7 +209,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
|
|||||||
info!(context, "Taking protocol shortcut.");
|
info!(context, "Taking protocol shortcut.");
|
||||||
context.bob.write().unwrap().expects = DC_VC_CONTACT_CONFIRM;
|
context.bob.write().unwrap().expects = DC_VC_CONTACT_CONFIRM;
|
||||||
joiner_progress!(context, chat_id_2_contact_id(context, contact_chat_id), 400);
|
joiner_progress!(context, chat_id_2_contact_id(context, contact_chat_id), 400);
|
||||||
let own_fingerprint = get_self_fingerprint(context).unwrap();
|
let own_fingerprint = get_self_fingerprint(context).unwrap_or_default();
|
||||||
send_handshake_msg(
|
send_handshake_msg(
|
||||||
context,
|
context,
|
||||||
contact_chat_id,
|
contact_chat_id,
|
||||||
@@ -243,21 +239,12 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bob -> Alice
|
// Bob -> Alice
|
||||||
while !check_exit(&context) {
|
while !context.shall_stop_ongoing() {
|
||||||
std::thread::sleep(std::time::Duration::new(0, 3_000_000));
|
std::thread::sleep(std::time::Duration::new(0, 3_000_000));
|
||||||
}
|
}
|
||||||
cleanup(&context, contact_chat_id, true, join_vg)
|
cleanup(&context, contact_chat_id, true, join_vg)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_exit(context: &Context) -> bool {
|
|
||||||
context
|
|
||||||
.running_state
|
|
||||||
.clone()
|
|
||||||
.read()
|
|
||||||
.unwrap()
|
|
||||||
.shall_stop_ongoing
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_handshake_msg(
|
fn send_handshake_msg(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
contact_chat_id: u32,
|
contact_chat_id: u32,
|
||||||
@@ -266,7 +253,7 @@ fn send_handshake_msg(
|
|||||||
fingerprint: Option<String>,
|
fingerprint: Option<String>,
|
||||||
grpid: impl AsRef<str>,
|
grpid: impl AsRef<str>,
|
||||||
) {
|
) {
|
||||||
let mut msg = dc_msg_new_untyped();
|
let mut msg = Message::default();
|
||||||
msg.type_0 = Viewtype::Text;
|
msg.type_0 = Viewtype::Text;
|
||||||
msg.text = Some(format!("Secure-Join: {}", step));
|
msg.text = Some(format!("Secure-Join: {}", step));
|
||||||
msg.hidden = true;
|
msg.hidden = true;
|
||||||
@@ -294,7 +281,7 @@ fn send_handshake_msg(
|
|||||||
msg.param.set_int(Param::GuranteeE2ee, 1);
|
msg.param.set_int(Param::GuranteeE2ee, 1);
|
||||||
}
|
}
|
||||||
// TODO. handle cleanup on error
|
// TODO. handle cleanup on error
|
||||||
chat::send_msg(context, contact_chat_id, &mut msg).unwrap();
|
chat::send_msg(context, contact_chat_id, &mut msg).unwrap_or_default();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn chat_id_2_contact_id(context: &Context, contact_chat_id: u32) -> u32 {
|
fn chat_id_2_contact_id(context: &Context, contact_chat_id: u32) -> u32 {
|
||||||
@@ -413,7 +400,7 @@ pub fn handle_securejoin_handshake(
|
|||||||
could_not_establish_secure_connection(
|
could_not_establish_secure_connection(
|
||||||
context,
|
context,
|
||||||
contact_chat_id,
|
contact_chat_id,
|
||||||
if mimeparser.e2ee_helper.encrypted {
|
if mimeparser.encrypted {
|
||||||
"No valid signature."
|
"No valid signature."
|
||||||
} else {
|
} else {
|
||||||
"Not encrypted."
|
"Not encrypted."
|
||||||
@@ -522,7 +509,11 @@ pub fn handle_securejoin_handshake(
|
|||||||
error!(context, "Chat {} not found.", &field_grpid);
|
error!(context, "Chat {} not found.", &field_grpid);
|
||||||
return ret;
|
return ret;
|
||||||
} else {
|
} else {
|
||||||
chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, 0x1i32);
|
if let Err(err) =
|
||||||
|
chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, true)
|
||||||
|
{
|
||||||
|
error!(context, "failed to add contact: {}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
send_handshake_msg(context, contact_chat_id, "vc-contact-confirm", "", None, "");
|
send_handshake_msg(context, contact_chat_id, "vc-contact-confirm", "", None, "");
|
||||||
@@ -635,7 +626,7 @@ pub fn handle_securejoin_handshake(
|
|||||||
|
|
||||||
fn end_bobs_joining(context: &Context, status: libc::c_int) {
|
fn end_bobs_joining(context: &Context, status: libc::c_int) {
|
||||||
context.bob.write().unwrap().status = status;
|
context.bob.write().unwrap().status = status;
|
||||||
dc_stop_ongoing_process(context);
|
context.stop_ongoing();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn secure_connection_established(context: &Context, contact_chat_id: u32) {
|
fn secure_connection_established(context: &Context, contact_chat_id: u32) {
|
||||||
@@ -674,7 +665,9 @@ fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Res
|
|||||||
if peerstate.set_verified(1, fingerprint.as_ref(), 2) {
|
if peerstate.set_verified(1, fingerprint.as_ref(), 2) {
|
||||||
peerstate.prefer_encrypt = EncryptPreference::Mutual;
|
peerstate.prefer_encrypt = EncryptPreference::Mutual;
|
||||||
peerstate.to_save = Some(ToSave::All);
|
peerstate.to_save = Some(ToSave::All);
|
||||||
peerstate.save_to_db(&context.sql, false);
|
peerstate
|
||||||
|
.save_to_db(&context.sql, false)
|
||||||
|
.unwrap_or_default();
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -689,17 +682,16 @@ fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Res
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
fn encrypted_and_signed(mimeparser: &MimeParser, expected_fingerprint: impl AsRef<str>) -> bool {
|
fn encrypted_and_signed(mimeparser: &MimeParser, expected_fingerprint: impl AsRef<str>) -> bool {
|
||||||
if !mimeparser.e2ee_helper.encrypted {
|
if !mimeparser.encrypted {
|
||||||
warn!(mimeparser.context, "Message not encrypted.",);
|
warn!(mimeparser.context, "Message not encrypted.",);
|
||||||
false
|
false
|
||||||
} else if mimeparser.e2ee_helper.signatures.len() <= 0 {
|
} else if mimeparser.signatures.is_empty() {
|
||||||
warn!(mimeparser.context, "Message not signed.",);
|
warn!(mimeparser.context, "Message not signed.",);
|
||||||
false
|
false
|
||||||
} else if expected_fingerprint.as_ref().is_empty() {
|
} else if expected_fingerprint.as_ref().is_empty() {
|
||||||
warn!(mimeparser.context, "Fingerprint for comparison missing.",);
|
warn!(mimeparser.context, "Fingerprint for comparison missing.",);
|
||||||
false
|
false
|
||||||
} else if !mimeparser
|
} else if !mimeparser
|
||||||
.e2ee_helper
|
|
||||||
.signatures
|
.signatures
|
||||||
.contains(expected_fingerprint.as_ref())
|
.contains(expected_fingerprint.as_ref())
|
||||||
{
|
{
|
||||||
@@ -714,21 +706,24 @@ fn encrypted_and_signed(mimeparser: &MimeParser, expected_fingerprint: impl AsRe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) {
|
pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) -> Result<(), Error> {
|
||||||
// - we do not issue an warning for DC_DE_ENCRYPTION_PAUSED as this is quite normal
|
// - we do not issue an warning for DC_DE_ENCRYPTION_PAUSED as this is quite normal
|
||||||
// - currently, we do not issue an extra warning for DC_DE_VERIFICATION_LOST - this always comes
|
// - currently, we do not issue an extra warning for DC_DE_VERIFICATION_LOST - this always comes
|
||||||
// together with DC_DE_FINGERPRINT_CHANGED which is logged, the idea is not to bother
|
// together with DC_DE_FINGERPRINT_CHANGED which is logged, the idea is not to bother
|
||||||
// with things they cannot fix, so the user is just kicked from the verified group
|
// with things they cannot fix, so the user is just kicked from the verified group
|
||||||
// (and he will know this and can fix this)
|
// (and he will know this and can fix this)
|
||||||
if Some(DegradeEvent::FingerprintChanged) == peerstate.degrade_event {
|
if Some(DegradeEvent::FingerprintChanged) == peerstate.degrade_event {
|
||||||
let contact_id: i32 = context
|
let contact_id: i32 = match context.sql.query_get_value(
|
||||||
.sql
|
context,
|
||||||
.query_get_value(
|
"SELECT id FROM contacts WHERE addr=?;",
|
||||||
context,
|
params![&peerstate.addr],
|
||||||
"SELECT id FROM contacts WHERE addr=?;",
|
) {
|
||||||
params![&peerstate.addr],
|
None => bail!(
|
||||||
)
|
"contact with peerstate.addr {:?} not found",
|
||||||
.unwrap_or_default();
|
&peerstate.addr
|
||||||
|
),
|
||||||
|
Some(contact_id) => contact_id,
|
||||||
|
};
|
||||||
if contact_id > 0 {
|
if contact_id > 0 {
|
||||||
let (contact_chat_id, _) =
|
let (contact_chat_id, _) =
|
||||||
chat::create_or_lookup_by_contact_id(context, contact_id as u32, Blocked::Deaddrop)
|
chat::create_or_lookup_by_contact_id(context, contact_id as u32, Blocked::Deaddrop)
|
||||||
@@ -744,4 +739,5 @@ pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) {
|
|||||||
emit_event!(context, Event::ChatModified(contact_chat_id));
|
emit_event!(context, Event::ChatModified(contact_chat_id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
98
src/smtp.rs
98
src/smtp.rs
@@ -3,8 +3,9 @@ use lettre::*;
|
|||||||
|
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
|
use crate::error::Error;
|
||||||
use crate::events::Event;
|
use crate::events::Event;
|
||||||
use crate::login_param::LoginParam;
|
use crate::login_param::{dc_build_tls, LoginParam};
|
||||||
use crate::oauth2::*;
|
use crate::oauth2::*;
|
||||||
|
|
||||||
#[derive(DebugStub)]
|
#[derive(DebugStub)]
|
||||||
@@ -14,7 +15,6 @@ pub struct Smtp {
|
|||||||
transport_connected: bool,
|
transport_connected: bool,
|
||||||
/// Email address we are sending from.
|
/// Email address we are sending from.
|
||||||
from: Option<EmailAddress>,
|
from: Option<EmailAddress>,
|
||||||
pub error: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Smtp {
|
impl Smtp {
|
||||||
@@ -24,7 +24,6 @@ impl Smtp {
|
|||||||
transport: None,
|
transport: None,
|
||||||
transport_connected: false,
|
transport_connected: false,
|
||||||
from: None,
|
from: None,
|
||||||
error: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,31 +68,37 @@ impl Smtp {
|
|||||||
let domain = &lp.send_server;
|
let domain = &lp.send_server;
|
||||||
let port = lp.send_port as u16;
|
let port = lp.send_port as u16;
|
||||||
|
|
||||||
let tls = native_tls::TlsConnector::builder()
|
let tls = dc_build_tls(lp.smtp_certificate_checks).unwrap();
|
||||||
// FIXME: unfortunately this is needed to make things work on macos + testrun.org
|
|
||||||
.danger_accept_invalid_hostnames(true)
|
|
||||||
.min_protocol_version(Some(DEFAULT_TLS_PROTOCOLS[0]))
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let tls_parameters = ClientTlsParameters::new(domain.to_string(), tls);
|
let tls_parameters = ClientTlsParameters::new(domain.to_string(), tls);
|
||||||
|
|
||||||
let creds = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
|
let (creds, mechanism) = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
|
||||||
// oauth2
|
// oauth2
|
||||||
let addr = &lp.addr;
|
let addr = &lp.addr;
|
||||||
let send_pw = &lp.send_pw;
|
let send_pw = &lp.send_pw;
|
||||||
let access_token = dc_get_oauth2_access_token(context, addr, send_pw, 0);
|
let access_token = dc_get_oauth2_access_token(context, addr, send_pw, false);
|
||||||
if access_token.is_none() {
|
if access_token.is_none() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let user = &lp.send_user;
|
let user = &lp.send_user;
|
||||||
|
|
||||||
lettre::smtp::authentication::Credentials::new(user.to_string(), access_token.unwrap())
|
(
|
||||||
|
lettre::smtp::authentication::Credentials::new(
|
||||||
|
user.to_string(),
|
||||||
|
access_token.unwrap_or_default(),
|
||||||
|
),
|
||||||
|
vec![lettre::smtp::authentication::Mechanism::Xoauth2],
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
// plain
|
// plain
|
||||||
let user = lp.send_user.clone();
|
let user = lp.send_user.clone();
|
||||||
let pw = lp.send_pw.clone();
|
let pw = lp.send_pw.clone();
|
||||||
lettre::smtp::authentication::Credentials::new(user, pw)
|
(
|
||||||
|
lettre::smtp::authentication::Credentials::new(user, pw),
|
||||||
|
vec![
|
||||||
|
lettre::smtp::authentication::Mechanism::Plain,
|
||||||
|
lettre::smtp::authentication::Mechanism::Login,
|
||||||
|
],
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let security = if 0
|
let security = if 0
|
||||||
@@ -109,52 +114,75 @@ impl Smtp {
|
|||||||
let client = client
|
let client = client
|
||||||
.smtp_utf8(true)
|
.smtp_utf8(true)
|
||||||
.credentials(creds)
|
.credentials(creds)
|
||||||
|
.authentication_mechanism(mechanism)
|
||||||
.connection_reuse(lettre::smtp::ConnectionReuseParameters::ReuseUnlimited);
|
.connection_reuse(lettre::smtp::ConnectionReuseParameters::ReuseUnlimited);
|
||||||
self.transport = Some(client.transport());
|
let mut trans = client.transport();
|
||||||
context.call_cb(Event::SmtpConnected(format!(
|
match trans.connect() {
|
||||||
"SMTP-LOGIN as {} ok",
|
Ok(()) => {
|
||||||
lp.send_user,
|
self.transport = Some(trans);
|
||||||
)));
|
self.transport_connected = true;
|
||||||
true
|
context.call_cb(Event::SmtpConnected(format!(
|
||||||
|
"SMTP-LOGIN as {} ok",
|
||||||
|
lp.send_user,
|
||||||
|
)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
warn!(context, "SMTP: failed to connect {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!(context, "SMTP: failed to establish connection {:?}", err);
|
warn!(context, "SMTP: failed to setup connection {:?}", err);
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// SMTP-Send a prepared mail to recipients.
|
||||||
|
/// returns boolean whether send was successful.
|
||||||
pub fn send<'a>(
|
pub fn send<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
recipients: Vec<EmailAddress>,
|
recipients: Vec<EmailAddress>,
|
||||||
body: Vec<u8>,
|
message: Vec<u8>,
|
||||||
) -> usize {
|
) -> Result<(), Error> {
|
||||||
|
let message_len = message.len();
|
||||||
|
|
||||||
|
let recipients_display = recipients
|
||||||
|
.iter()
|
||||||
|
.map(|x| format!("{}", x))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(",");
|
||||||
|
|
||||||
if let Some(ref mut transport) = self.transport {
|
if let Some(ref mut transport) = self.transport {
|
||||||
let envelope = Envelope::new(self.from.clone(), recipients).expect("invalid envelope");
|
let envelope = Envelope::new(self.from.clone(), recipients);
|
||||||
|
ensure!(envelope.is_ok(), "internal smtp-message construction fail");
|
||||||
|
let envelope = envelope.unwrap();
|
||||||
let mail = SendableEmail::new(
|
let mail = SendableEmail::new(
|
||||||
envelope,
|
envelope,
|
||||||
"mail-id".into(), // TODO: random id
|
"mail-id".into(), // TODO: random id
|
||||||
body,
|
message,
|
||||||
);
|
);
|
||||||
|
|
||||||
match transport.send(mail) {
|
match transport.send(mail) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
context.call_cb(Event::SmtpMessageSent(
|
context.call_cb(Event::SmtpMessageSent(format!(
|
||||||
"Message was sent to SMTP server".into(),
|
"Message len={} was smtp-sent to {}",
|
||||||
));
|
message_len, recipients_display
|
||||||
|
)));
|
||||||
self.transport_connected = true;
|
self.transport_connected = true;
|
||||||
1
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!(context, "SMTP failed to send message: {}", err);
|
bail!("SMTP failed len={}: error: {}", message_len, err);
|
||||||
self.error = Some(format!("{}", err));
|
|
||||||
0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: log error
|
bail!(
|
||||||
0
|
"uh? SMTP has no transport, failed to send to {:?}",
|
||||||
|
recipients_display
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
150
src/sql.rs
150
src/sql.rs
@@ -1,5 +1,6 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use rusqlite::{Connection, OpenFlags, Statement, NO_PARAMS};
|
use rusqlite::{Connection, OpenFlags, Statement, NO_PARAMS};
|
||||||
use thread_local_object::ThreadLocal;
|
use thread_local_object::ThreadLocal;
|
||||||
@@ -10,8 +11,6 @@ use crate::error::{Error, Result};
|
|||||||
use crate::param::*;
|
use crate::param::*;
|
||||||
use crate::peerstate::*;
|
use crate::peerstate::*;
|
||||||
|
|
||||||
const DC_OPEN_READONLY: usize = 0x01;
|
|
||||||
|
|
||||||
/// A wrapper around the underlying Sqlite3 object.
|
/// A wrapper around the underlying Sqlite3 object.
|
||||||
#[derive(DebugStub)]
|
#[derive(DebugStub)]
|
||||||
pub struct Sql {
|
pub struct Sql {
|
||||||
@@ -41,8 +40,8 @@ impl Sql {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// return true on success, false on failure
|
// return true on success, false on failure
|
||||||
pub fn open(&self, context: &Context, dbfile: &std::path::Path, flags: libc::c_int) -> bool {
|
pub fn open(&self, context: &Context, dbfile: &std::path::Path, readonly: bool) -> bool {
|
||||||
match open(context, self, dbfile, flags) {
|
match open(context, self, dbfile, readonly) {
|
||||||
Ok(_) => true,
|
Ok(_) => true,
|
||||||
Err(Error::SqlAlreadyOpen) => false,
|
Err(Error::SqlAlreadyOpen) => false,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -68,6 +67,16 @@ impl Sql {
|
|||||||
let res = match &*self.pool.read().unwrap() {
|
let res = match &*self.pool.read().unwrap() {
|
||||||
Some(pool) => {
|
Some(pool) => {
|
||||||
let conn = pool.get()?;
|
let conn = pool.get()?;
|
||||||
|
|
||||||
|
// Only one process can make changes to the database at one time.
|
||||||
|
// busy_timeout defines, that if a seconds process wants write access,
|
||||||
|
// this second process will wait some milliseconds
|
||||||
|
// and try over until it gets write access or the given timeout is elapsed.
|
||||||
|
// If the second process does not get write access within the given timeout,
|
||||||
|
// sqlite3_step() will return the error SQLITE_BUSY.
|
||||||
|
// (without a busy_timeout, sqlite3_step() would return SQLITE_BUSY _at once_)
|
||||||
|
conn.busy_timeout(Duration::from_secs(10))?;
|
||||||
|
|
||||||
g(&conn)
|
g(&conn)
|
||||||
}
|
}
|
||||||
None => Err(Error::SqlNoConnection),
|
None => Err(Error::SqlNoConnection),
|
||||||
@@ -182,14 +191,14 @@ impl Sql {
|
|||||||
///
|
///
|
||||||
/// Setting `None` deletes the value. On failure an error message
|
/// Setting `None` deletes the value. On failure an error message
|
||||||
/// will already have been logged.
|
/// will already have been logged.
|
||||||
pub fn set_config(
|
pub fn set_raw_config(
|
||||||
&self,
|
&self,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
key: impl AsRef<str>,
|
key: impl AsRef<str>,
|
||||||
value: Option<&str>,
|
value: Option<&str>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if !self.is_open() {
|
if !self.is_open() {
|
||||||
error!(context, "set_config(): Database not ready.");
|
error!(context, "set_raw_config(): Database not ready.");
|
||||||
return Err(Error::SqlNoConnection);
|
return Err(Error::SqlNoConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,14 +232,14 @@ impl Sql {
|
|||||||
match res {
|
match res {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(context, "set_config(): Cannot change value. {:?}", &err);
|
error!(context, "set_raw_config(): Cannot change value. {:?}", &err);
|
||||||
Err(err.into())
|
Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get configuration options from the database.
|
/// Get configuration options from the database.
|
||||||
pub fn get_config(&self, context: &Context, key: impl AsRef<str>) -> Option<String> {
|
pub fn get_raw_config(&self, context: &Context, key: impl AsRef<str>) -> Option<String> {
|
||||||
if !self.is_open() || key.as_ref().is_empty() {
|
if !self.is_open() || key.as_ref().is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -241,44 +250,46 @@ impl Sql {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_config_int(
|
pub fn set_raw_config_int(
|
||||||
&self,
|
&self,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
key: impl AsRef<str>,
|
key: impl AsRef<str>,
|
||||||
value: i32,
|
value: i32,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.set_config(context, key, Some(&format!("{}", value)))
|
self.set_raw_config(context, key, Some(&format!("{}", value)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_config_int(&self, context: &Context, key: impl AsRef<str>) -> Option<i32> {
|
pub fn get_raw_config_int(&self, context: &Context, key: impl AsRef<str>) -> Option<i32> {
|
||||||
self.get_config(context, key).and_then(|s| s.parse().ok())
|
self.get_raw_config(context, key)
|
||||||
|
.and_then(|s| s.parse().ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_config_bool(&self, context: &Context, key: impl AsRef<str>) -> bool {
|
pub fn get_raw_config_bool(&self, context: &Context, key: impl AsRef<str>) -> bool {
|
||||||
// Not the most obvious way to encode bool as string, but it is matter
|
// Not the most obvious way to encode bool as string, but it is matter
|
||||||
// of backward compatibility.
|
// of backward compatibility.
|
||||||
self.get_config_int(context, key).unwrap_or_default() > 0
|
self.get_raw_config_int(context, key).unwrap_or_default() > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_config_bool<T>(&self, context: &Context, key: T, value: bool) -> Result<()>
|
pub fn set_raw_config_bool<T>(&self, context: &Context, key: T, value: bool) -> Result<()>
|
||||||
where
|
where
|
||||||
T: AsRef<str>,
|
T: AsRef<str>,
|
||||||
{
|
{
|
||||||
let value = if value { Some("1") } else { None };
|
let value = if value { Some("1") } else { None };
|
||||||
self.set_config(context, key, value)
|
self.set_raw_config(context, key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_config_int64(
|
pub fn set_raw_config_int64(
|
||||||
&self,
|
&self,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
key: impl AsRef<str>,
|
key: impl AsRef<str>,
|
||||||
value: i64,
|
value: i64,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.set_config(context, key, Some(&format!("{}", value)))
|
self.set_raw_config(context, key, Some(&format!("{}", value)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_config_int64(&self, context: &Context, key: impl AsRef<str>) -> Option<i64> {
|
pub fn get_raw_config_int64(&self, context: &Context, key: impl AsRef<str>) -> Option<i64> {
|
||||||
self.get_config(context, key).and_then(|r| r.parse().ok())
|
self.get_raw_config(context, key)
|
||||||
|
.and_then(|r| r.parse().ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_stmt(&self, stmt: impl AsRef<str>) {
|
fn start_stmt(&self, stmt: impl AsRef<str>) {
|
||||||
@@ -307,7 +318,7 @@ fn open(
|
|||||||
context: &Context,
|
context: &Context,
|
||||||
sql: &Sql,
|
sql: &Sql,
|
||||||
dbfile: impl AsRef<std::path::Path>,
|
dbfile: impl AsRef<std::path::Path>,
|
||||||
flags: libc::c_int,
|
readonly: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if sql.is_open() {
|
if sql.is_open() {
|
||||||
error!(
|
error!(
|
||||||
@@ -319,7 +330,7 @@ fn open(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut open_flags = OpenFlags::SQLITE_OPEN_NO_MUTEX;
|
let mut open_flags = OpenFlags::SQLITE_OPEN_NO_MUTEX;
|
||||||
if 0 != (flags & DC_OPEN_READONLY as i32) {
|
if readonly {
|
||||||
open_flags.insert(OpenFlags::SQLITE_OPEN_READ_ONLY);
|
open_flags.insert(OpenFlags::SQLITE_OPEN_READ_ONLY);
|
||||||
} else {
|
} else {
|
||||||
open_flags.insert(OpenFlags::SQLITE_OPEN_READ_WRITE);
|
open_flags.insert(OpenFlags::SQLITE_OPEN_READ_WRITE);
|
||||||
@@ -330,7 +341,7 @@ fn open(
|
|||||||
.with_init(|c| c.execute_batch("PRAGMA secure_delete=on;"));
|
.with_init(|c| c.execute_batch("PRAGMA secure_delete=on;"));
|
||||||
let pool = r2d2::Pool::builder()
|
let pool = r2d2::Pool::builder()
|
||||||
.min_idle(Some(2))
|
.min_idle(Some(2))
|
||||||
.max_size(4)
|
.max_size(10)
|
||||||
.connection_timeout(std::time::Duration::new(60, 0))
|
.connection_timeout(std::time::Duration::new(60, 0))
|
||||||
.build(mgr)?;
|
.build(mgr)?;
|
||||||
|
|
||||||
@@ -338,7 +349,7 @@ fn open(
|
|||||||
*sql.pool.write().unwrap() = Some(pool);
|
*sql.pool.write().unwrap() = Some(pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
if 0 == flags & DC_OPEN_READONLY as i32 {
|
if !readonly {
|
||||||
let mut exists_before_update = 0;
|
let mut exists_before_update = 0;
|
||||||
let mut dbversion_before_update = 0;
|
let mut dbversion_before_update = 0;
|
||||||
/* Init tables to dbversion=0 */
|
/* Init tables to dbversion=0 */
|
||||||
@@ -466,11 +477,13 @@ fn open(
|
|||||||
// cannot create the tables - maybe we cannot write?
|
// cannot create the tables - maybe we cannot write?
|
||||||
return Err(Error::SqlFailedToOpen);
|
return Err(Error::SqlFailedToOpen);
|
||||||
} else {
|
} else {
|
||||||
sql.set_config_int(context, "dbversion", 0)?;
|
sql.set_raw_config_int(context, "dbversion", 0)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
exists_before_update = 1;
|
exists_before_update = 1;
|
||||||
dbversion_before_update = sql.get_config_int(context, "dbversion").unwrap_or_default();
|
dbversion_before_update = sql
|
||||||
|
.get_raw_config_int(context, "dbversion")
|
||||||
|
.unwrap_or_default();
|
||||||
}
|
}
|
||||||
|
|
||||||
// (1) update low-level database structure.
|
// (1) update low-level database structure.
|
||||||
@@ -482,6 +495,7 @@ fn open(
|
|||||||
let mut update_file_paths = 0;
|
let mut update_file_paths = 0;
|
||||||
|
|
||||||
if dbversion < 1 {
|
if dbversion < 1 {
|
||||||
|
info!(context, "[migration] v1");
|
||||||
sql.execute(
|
sql.execute(
|
||||||
"CREATE TABLE leftgrps ( id INTEGER PRIMARY KEY, grpid TEXT DEFAULT '');",
|
"CREATE TABLE leftgrps ( id INTEGER PRIMARY KEY, grpid TEXT DEFAULT '');",
|
||||||
params![],
|
params![],
|
||||||
@@ -491,17 +505,19 @@ fn open(
|
|||||||
params![],
|
params![],
|
||||||
)?;
|
)?;
|
||||||
dbversion = 1;
|
dbversion = 1;
|
||||||
sql.set_config_int(context, "dbversion", 1)?;
|
sql.set_raw_config_int(context, "dbversion", 1)?;
|
||||||
}
|
}
|
||||||
if dbversion < 2 {
|
if dbversion < 2 {
|
||||||
|
info!(context, "[migration] v2");
|
||||||
sql.execute(
|
sql.execute(
|
||||||
"ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';",
|
"ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';",
|
||||||
params![],
|
params![],
|
||||||
)?;
|
)?;
|
||||||
dbversion = 2;
|
dbversion = 2;
|
||||||
sql.set_config_int(context, "dbversion", 2)?;
|
sql.set_raw_config_int(context, "dbversion", 2)?;
|
||||||
}
|
}
|
||||||
if dbversion < 7 {
|
if dbversion < 7 {
|
||||||
|
info!(context, "[migration] v7");
|
||||||
sql.execute(
|
sql.execute(
|
||||||
"CREATE TABLE keypairs (\
|
"CREATE TABLE keypairs (\
|
||||||
id INTEGER PRIMARY KEY, \
|
id INTEGER PRIMARY KEY, \
|
||||||
@@ -513,9 +529,10 @@ fn open(
|
|||||||
params![],
|
params![],
|
||||||
)?;
|
)?;
|
||||||
dbversion = 7;
|
dbversion = 7;
|
||||||
sql.set_config_int(context, "dbversion", 7)?;
|
sql.set_raw_config_int(context, "dbversion", 7)?;
|
||||||
}
|
}
|
||||||
if dbversion < 10 {
|
if dbversion < 10 {
|
||||||
|
info!(context, "[migration] v10");
|
||||||
sql.execute(
|
sql.execute(
|
||||||
"CREATE TABLE acpeerstates (\
|
"CREATE TABLE acpeerstates (\
|
||||||
id INTEGER PRIMARY KEY, \
|
id INTEGER PRIMARY KEY, \
|
||||||
@@ -531,9 +548,10 @@ fn open(
|
|||||||
params![],
|
params![],
|
||||||
)?;
|
)?;
|
||||||
dbversion = 10;
|
dbversion = 10;
|
||||||
sql.set_config_int(context, "dbversion", 10)?;
|
sql.set_raw_config_int(context, "dbversion", 10)?;
|
||||||
}
|
}
|
||||||
if dbversion < 12 {
|
if dbversion < 12 {
|
||||||
|
info!(context, "[migration] v12");
|
||||||
sql.execute(
|
sql.execute(
|
||||||
"CREATE TABLE msgs_mdns ( msg_id INTEGER, contact_id INTEGER);",
|
"CREATE TABLE msgs_mdns ( msg_id INTEGER, contact_id INTEGER);",
|
||||||
params![],
|
params![],
|
||||||
@@ -543,9 +561,10 @@ fn open(
|
|||||||
params![],
|
params![],
|
||||||
)?;
|
)?;
|
||||||
dbversion = 12;
|
dbversion = 12;
|
||||||
sql.set_config_int(context, "dbversion", 12)?;
|
sql.set_raw_config_int(context, "dbversion", 12)?;
|
||||||
}
|
}
|
||||||
if dbversion < 17 {
|
if dbversion < 17 {
|
||||||
|
info!(context, "[migration] v17");
|
||||||
sql.execute(
|
sql.execute(
|
||||||
"ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;",
|
"ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;",
|
||||||
params![],
|
params![],
|
||||||
@@ -557,18 +576,20 @@ fn open(
|
|||||||
)?;
|
)?;
|
||||||
sql.execute("CREATE INDEX msgs_index5 ON msgs (starred);", params![])?;
|
sql.execute("CREATE INDEX msgs_index5 ON msgs (starred);", params![])?;
|
||||||
dbversion = 17;
|
dbversion = 17;
|
||||||
sql.set_config_int(context, "dbversion", 17)?;
|
sql.set_raw_config_int(context, "dbversion", 17)?;
|
||||||
}
|
}
|
||||||
if dbversion < 18 {
|
if dbversion < 18 {
|
||||||
|
info!(context, "[migration] v18");
|
||||||
sql.execute(
|
sql.execute(
|
||||||
"ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;",
|
"ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;",
|
||||||
params![],
|
params![],
|
||||||
)?;
|
)?;
|
||||||
sql.execute("ALTER TABLE acpeerstates ADD COLUMN gossip_key;", params![])?;
|
sql.execute("ALTER TABLE acpeerstates ADD COLUMN gossip_key;", params![])?;
|
||||||
dbversion = 18;
|
dbversion = 18;
|
||||||
sql.set_config_int(context, "dbversion", 18)?;
|
sql.set_raw_config_int(context, "dbversion", 18)?;
|
||||||
}
|
}
|
||||||
if dbversion < 27 {
|
if dbversion < 27 {
|
||||||
|
info!(context, "[migration] v27");
|
||||||
sql.execute("DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;", params![])?;
|
sql.execute("DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;", params![])?;
|
||||||
sql.execute(
|
sql.execute(
|
||||||
"CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);",
|
"CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);",
|
||||||
@@ -583,9 +604,10 @@ fn open(
|
|||||||
params![],
|
params![],
|
||||||
)?;
|
)?;
|
||||||
dbversion = 27;
|
dbversion = 27;
|
||||||
sql.set_config_int(context, "dbversion", 27)?;
|
sql.set_raw_config_int(context, "dbversion", 27)?;
|
||||||
}
|
}
|
||||||
if dbversion < 34 {
|
if dbversion < 34 {
|
||||||
|
info!(context, "[migration] v34");
|
||||||
sql.execute(
|
sql.execute(
|
||||||
"ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;",
|
"ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;",
|
||||||
params![],
|
params![],
|
||||||
@@ -612,9 +634,10 @@ fn open(
|
|||||||
)?;
|
)?;
|
||||||
recalc_fingerprints = 1;
|
recalc_fingerprints = 1;
|
||||||
dbversion = 34;
|
dbversion = 34;
|
||||||
sql.set_config_int(context, "dbversion", 34)?;
|
sql.set_raw_config_int(context, "dbversion", 34)?;
|
||||||
}
|
}
|
||||||
if dbversion < 39 {
|
if dbversion < 39 {
|
||||||
|
info!(context, "[migration] v39");
|
||||||
sql.execute(
|
sql.execute(
|
||||||
"CREATE TABLE tokens ( id INTEGER PRIMARY KEY, namespc INTEGER DEFAULT 0, foreign_id INTEGER DEFAULT 0, token TEXT DEFAULT '', timestamp INTEGER DEFAULT 0);",
|
"CREATE TABLE tokens ( id INTEGER PRIMARY KEY, namespc INTEGER DEFAULT 0, foreign_id INTEGER DEFAULT 0, token TEXT DEFAULT '', timestamp INTEGER DEFAULT 0);",
|
||||||
params![]
|
params![]
|
||||||
@@ -642,32 +665,37 @@ fn open(
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
dbversion = 39;
|
dbversion = 39;
|
||||||
sql.set_config_int(context, "dbversion", 39)?;
|
sql.set_raw_config_int(context, "dbversion", 39)?;
|
||||||
}
|
}
|
||||||
if dbversion < 40 {
|
if dbversion < 40 {
|
||||||
|
info!(context, "[migration] v40");
|
||||||
sql.execute(
|
sql.execute(
|
||||||
"ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;",
|
"ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;",
|
||||||
params![],
|
params![],
|
||||||
)?;
|
)?;
|
||||||
dbversion = 40;
|
dbversion = 40;
|
||||||
sql.set_config_int(context, "dbversion", 40)?;
|
sql.set_raw_config_int(context, "dbversion", 40)?;
|
||||||
}
|
}
|
||||||
if dbversion < 41 {
|
if dbversion < 41 {
|
||||||
|
info!(context, "[migration] v41");
|
||||||
update_file_paths = 1;
|
update_file_paths = 1;
|
||||||
dbversion = 41;
|
dbversion = 41;
|
||||||
sql.set_config_int(context, "dbversion", 41)?;
|
sql.set_raw_config_int(context, "dbversion", 41)?;
|
||||||
}
|
}
|
||||||
if dbversion < 42 {
|
if dbversion < 42 {
|
||||||
|
info!(context, "[migration] v42");
|
||||||
sql.execute("UPDATE msgs SET txt='' WHERE type!=10", params![])?;
|
sql.execute("UPDATE msgs SET txt='' WHERE type!=10", params![])?;
|
||||||
dbversion = 42;
|
dbversion = 42;
|
||||||
sql.set_config_int(context, "dbversion", 42)?;
|
sql.set_raw_config_int(context, "dbversion", 42)?;
|
||||||
}
|
}
|
||||||
if dbversion < 44 {
|
if dbversion < 44 {
|
||||||
|
info!(context, "[migration] v44");
|
||||||
sql.execute("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", params![])?;
|
sql.execute("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", params![])?;
|
||||||
dbversion = 44;
|
dbversion = 44;
|
||||||
sql.set_config_int(context, "dbversion", 44)?;
|
sql.set_raw_config_int(context, "dbversion", 44)?;
|
||||||
}
|
}
|
||||||
if dbversion < 46 {
|
if dbversion < 46 {
|
||||||
|
info!(context, "[migration] v46");
|
||||||
sql.execute(
|
sql.execute(
|
||||||
"ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;",
|
"ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;",
|
||||||
params![],
|
params![],
|
||||||
@@ -677,7 +705,7 @@ fn open(
|
|||||||
params![],
|
params![],
|
||||||
)?;
|
)?;
|
||||||
dbversion = 46;
|
dbversion = 46;
|
||||||
sql.set_config_int(context, "dbversion", 46)?;
|
sql.set_raw_config_int(context, "dbversion", 46)?;
|
||||||
}
|
}
|
||||||
if dbversion < 47 {
|
if dbversion < 47 {
|
||||||
info!(context, "[migration] v47");
|
info!(context, "[migration] v47");
|
||||||
@@ -686,7 +714,7 @@ fn open(
|
|||||||
params![],
|
params![],
|
||||||
)?;
|
)?;
|
||||||
dbversion = 47;
|
dbversion = 47;
|
||||||
sql.set_config_int(context, "dbversion", 47)?;
|
sql.set_raw_config_int(context, "dbversion", 47)?;
|
||||||
}
|
}
|
||||||
if dbversion < 48 {
|
if dbversion < 48 {
|
||||||
info!(context, "[migration] v48");
|
info!(context, "[migration] v48");
|
||||||
@@ -696,7 +724,7 @@ fn open(
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
dbversion = 48;
|
dbversion = 48;
|
||||||
sql.set_config_int(context, "dbversion", 48)?;
|
sql.set_raw_config_int(context, "dbversion", 48)?;
|
||||||
}
|
}
|
||||||
if dbversion < 49 {
|
if dbversion < 49 {
|
||||||
info!(context, "[migration] v49");
|
info!(context, "[migration] v49");
|
||||||
@@ -705,15 +733,15 @@ fn open(
|
|||||||
params![],
|
params![],
|
||||||
)?;
|
)?;
|
||||||
dbversion = 49;
|
dbversion = 49;
|
||||||
sql.set_config_int(context, "dbversion", 49)?;
|
sql.set_raw_config_int(context, "dbversion", 49)?;
|
||||||
}
|
}
|
||||||
if dbversion < 50 {
|
if dbversion < 50 {
|
||||||
info!(context, "[migration] v50");
|
info!(context, "[migration] v50");
|
||||||
if 0 != exists_before_update {
|
if 0 != exists_before_update {
|
||||||
sql.set_config_int(context, "show_emails", 2)?;
|
sql.set_raw_config_int(context, "show_emails", 2)?;
|
||||||
}
|
}
|
||||||
dbversion = 50;
|
dbversion = 50;
|
||||||
sql.set_config_int(context, "dbversion", 50)?;
|
sql.set_raw_config_int(context, "dbversion", 50)?;
|
||||||
}
|
}
|
||||||
if dbversion < 53 {
|
if dbversion < 53 {
|
||||||
info!(context, "[migration] v53");
|
info!(context, "[migration] v53");
|
||||||
@@ -746,7 +774,7 @@ fn open(
|
|||||||
params![],
|
params![],
|
||||||
)?;
|
)?;
|
||||||
dbversion = 53;
|
dbversion = 53;
|
||||||
sql.set_config_int(context, "dbversion", 53)?;
|
sql.set_raw_config_int(context, "dbversion", 53)?;
|
||||||
}
|
}
|
||||||
if dbversion < 54 {
|
if dbversion < 54 {
|
||||||
info!(context, "[migration] v54");
|
info!(context, "[migration] v54");
|
||||||
@@ -756,18 +784,20 @@ fn open(
|
|||||||
)?;
|
)?;
|
||||||
sql.execute("CREATE INDEX msgs_index6 ON msgs (location_id);", params![])?;
|
sql.execute("CREATE INDEX msgs_index6 ON msgs (location_id);", params![])?;
|
||||||
dbversion = 54;
|
dbversion = 54;
|
||||||
sql.set_config_int(context, "dbversion", 54)?;
|
sql.set_raw_config_int(context, "dbversion", 54)?;
|
||||||
}
|
}
|
||||||
if dbversion < 55 {
|
if dbversion < 55 {
|
||||||
|
info!(context, "[migration] v55");
|
||||||
sql.execute(
|
sql.execute(
|
||||||
"ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;",
|
"ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;",
|
||||||
params![],
|
params![],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
sql.set_config_int(context, "dbversion", 55)?;
|
sql.set_raw_config_int(context, "dbversion", 55)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if 0 != recalc_fingerprints {
|
if 0 != recalc_fingerprints {
|
||||||
|
info!(context, "[migration] recalc fingerprints");
|
||||||
sql.query_map(
|
sql.query_map(
|
||||||
"SELECT addr FROM acpeerstates;",
|
"SELECT addr FROM acpeerstates;",
|
||||||
params![],
|
params![],
|
||||||
@@ -777,7 +807,7 @@ fn open(
|
|||||||
if let Some(ref mut peerstate) = Peerstate::from_addr(context, sql, &addr?)
|
if let Some(ref mut peerstate) = Peerstate::from_addr(context, sql, &addr?)
|
||||||
{
|
{
|
||||||
peerstate.recalc_fingerprint();
|
peerstate.recalc_fingerprint();
|
||||||
peerstate.save_to_db(sql, false);
|
peerstate.save_to_db(sql, false)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -788,11 +818,9 @@ fn open(
|
|||||||
// versions before 2018-08 save the absolute paths in the database files at "param.f=";
|
// versions before 2018-08 save the absolute paths in the database files at "param.f=";
|
||||||
// for newer versions, we copy files always to the blob directory and store relative paths.
|
// for newer versions, we copy files always to the blob directory and store relative paths.
|
||||||
// this snippet converts older databases and can be removed after some time.
|
// this snippet converts older databases and can be removed after some time.
|
||||||
|
info!(context, "[migration] update file paths");
|
||||||
info!(context, "[open] update file paths");
|
|
||||||
|
|
||||||
let repl_from = sql
|
let repl_from = sql
|
||||||
.get_config(context, "backup_for")
|
.get_raw_config(context, "backup_for")
|
||||||
.unwrap_or_else(|| context.get_blobdir().to_string_lossy().into());
|
.unwrap_or_else(|| context.get_blobdir().to_string_lossy().into());
|
||||||
|
|
||||||
let repl_from = dc_ensure_no_slash_safe(&repl_from);
|
let repl_from = dc_ensure_no_slash_safe(&repl_from);
|
||||||
@@ -812,7 +840,7 @@ fn open(
|
|||||||
NO_PARAMS,
|
NO_PARAMS,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
sql.set_config(context, "backup_for", None)?;
|
sql.set_raw_config(context, "backup_for", None)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1133,12 +1161,8 @@ mod test {
|
|||||||
maybe_add_file(&mut files, "$BLOBDIR/world.txt");
|
maybe_add_file(&mut files, "$BLOBDIR/world.txt");
|
||||||
maybe_add_file(&mut files, "world2.txt");
|
maybe_add_file(&mut files, "world2.txt");
|
||||||
|
|
||||||
assert!(is_file_in_use(&mut files, None, "hello"));
|
assert!(is_file_in_use(&files, None, "hello"));
|
||||||
assert!(!is_file_in_use(&mut files, Some(".txt"), "hello"));
|
assert!(!is_file_in_use(&files, Some(".txt"), "hello"));
|
||||||
assert!(is_file_in_use(
|
assert!(is_file_in_use(&files, Some("-suffix"), "world.txt-suffix"));
|
||||||
&mut files,
|
|
||||||
Some("-suffix"),
|
|
||||||
"world.txt-suffix"
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
97
src/stock.rs
97
src/stock.rs
@@ -5,9 +5,7 @@ use strum_macros::EnumProperty;
|
|||||||
|
|
||||||
use crate::contact::*;
|
use crate::contact::*;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::dc_tools::*;
|
use crate::error::Error;
|
||||||
use crate::events::Event;
|
|
||||||
use libc::free;
|
|
||||||
|
|
||||||
/// Stock strings
|
/// Stock strings
|
||||||
///
|
///
|
||||||
@@ -110,6 +108,8 @@ pub enum StockMessage {
|
|||||||
MsgLocationDisabled = 65,
|
MsgLocationDisabled = 65,
|
||||||
#[strum(props(fallback = "Location"))]
|
#[strum(props(fallback = "Location"))]
|
||||||
Location = 66,
|
Location = 66,
|
||||||
|
#[strum(props(fallback = "Sticker"))]
|
||||||
|
Sticker = 67,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StockMessage {
|
impl StockMessage {
|
||||||
@@ -117,24 +117,52 @@ impl StockMessage {
|
|||||||
///
|
///
|
||||||
/// These could be used in logging calls, so no logging here.
|
/// These could be used in logging calls, so no logging here.
|
||||||
fn fallback(&self) -> &'static str {
|
fn fallback(&self) -> &'static str {
|
||||||
self.get_str("fallback").unwrap()
|
self.get_str("fallback").unwrap_or_default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
|
/// Set the stock string for the [StockMessage].
|
||||||
|
///
|
||||||
|
pub fn set_stock_translation(
|
||||||
|
&self,
|
||||||
|
id: StockMessage,
|
||||||
|
stockstring: String,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if stockstring.contains("%1") && !id.fallback().contains("%1") {
|
||||||
|
bail!(
|
||||||
|
"translation {} contains invalid %1 placeholder, default is {}",
|
||||||
|
stockstring,
|
||||||
|
id.fallback()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if stockstring.contains("%2") && !id.fallback().contains("%2") {
|
||||||
|
bail!(
|
||||||
|
"translation {} contains invalid %2 placeholder, default is {}",
|
||||||
|
stockstring,
|
||||||
|
id.fallback()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.translated_stockstrings
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.insert(id as usize, stockstring);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the stock string for the [StockMessage].
|
/// Return the stock string for the [StockMessage].
|
||||||
///
|
///
|
||||||
/// If the context callback responds with a string to use, e.g. a
|
/// Return a translation (if it was set with set_stock_translation before)
|
||||||
/// translation, then this string will be returned. Otherwise a
|
/// or a default (English) string.
|
||||||
/// default (English) string is returned.
|
|
||||||
pub fn stock_str(&self, id: StockMessage) -> Cow<str> {
|
pub fn stock_str(&self, id: StockMessage) -> Cow<str> {
|
||||||
let ptr = self.call_cb(Event::GetString { id, count: 0 }) as *mut libc::c_char;
|
match self
|
||||||
if ptr.is_null() {
|
.translated_stockstrings
|
||||||
Cow::Borrowed(id.fallback())
|
.read()
|
||||||
} else {
|
.unwrap()
|
||||||
let ret = to_string(ptr);
|
.get(&(id as usize))
|
||||||
unsafe { free(ptr as *mut libc::c_void) };
|
{
|
||||||
Cow::Owned(ret)
|
Some(ref x) => Cow::Owned(x.to_string()),
|
||||||
|
None => Cow::Borrowed(id.fallback()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +266,6 @@ mod tests {
|
|||||||
use crate::test_utils::*;
|
use crate::test_utils::*;
|
||||||
|
|
||||||
use crate::constants::DC_CONTACT_ID_SELF;
|
use crate::constants::DC_CONTACT_ID_SELF;
|
||||||
use libc::uintptr_t;
|
|
||||||
|
|
||||||
use num_traits::ToPrimitive;
|
use num_traits::ToPrimitive;
|
||||||
|
|
||||||
@@ -254,25 +281,31 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stock_str() {
|
fn test_set_stock_translation() {
|
||||||
let t = dummy_context();
|
let t = dummy_context();
|
||||||
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "No messages.");
|
t.ctx
|
||||||
}
|
.set_stock_translation(StockMessage::NoMessages, "xyz".to_string())
|
||||||
|
.unwrap();
|
||||||
fn test_stock_str_no_fallback_cb(_ctx: &Context, evt: Event) -> uintptr_t {
|
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "xyz")
|
||||||
match evt {
|
|
||||||
Event::GetString {
|
|
||||||
id: StockMessage::NoMessages,
|
|
||||||
..
|
|
||||||
} => unsafe { "Hello there".strdup() as usize },
|
|
||||||
_ => 0,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stock_str_no_fallback() {
|
fn test_set_stock_translation_wrong_replacements() {
|
||||||
let t = test_context(Some(Box::new(test_stock_str_no_fallback_cb)));
|
let t = dummy_context();
|
||||||
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "Hello there");
|
assert!(t
|
||||||
|
.ctx
|
||||||
|
.set_stock_translation(StockMessage::NoMessages, "xyz %1$s ".to_string())
|
||||||
|
.is_err());
|
||||||
|
assert!(t
|
||||||
|
.ctx
|
||||||
|
.set_stock_translation(StockMessage::NoMessages, "xyz %2$s ".to_string())
|
||||||
|
.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stock_str() {
|
||||||
|
let t = dummy_context();
|
||||||
|
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "No messages.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -350,9 +383,7 @@ mod tests {
|
|||||||
let contact_id = {
|
let contact_id = {
|
||||||
Contact::create(&t.ctx, "Alice", "alice@example.com")
|
Contact::create(&t.ctx, "Alice", "alice@example.com")
|
||||||
.expect("Failed to create contact Alice");
|
.expect("Failed to create contact Alice");
|
||||||
let id =
|
Contact::create(&t.ctx, "Bob", "bob@example.com").expect("failed to create bob")
|
||||||
Contact::create(&t.ctx, "Bob", "bob@example.com").expect("failed to create bob");
|
|
||||||
id
|
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
t.ctx.stock_system_msg(
|
t.ctx.stock_system_msg(
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user