mirror of
https://github.com/chatmail/core.git
synced 2026-04-02 13:32:11 +03:00
Compare commits
468 Commits
remove_got
...
ci/python-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ef3a091fe | ||
|
|
977e3e08d7 | ||
|
|
bb23e1487d | ||
|
|
30783adef2 | ||
|
|
a2c585c7a5 | ||
|
|
ff331061a0 | ||
|
|
77cb0276a6 | ||
|
|
2747939b52 | ||
|
|
dfb2ebb533 | ||
|
|
4c579e6cf6 | ||
|
|
cc1d520580 | ||
|
|
c7686e0a97 | ||
|
|
d41bcccd41 | ||
|
|
3c1a4ebfe0 | ||
|
|
361f14bffe | ||
|
|
939ca7f7d3 | ||
|
|
cdacf6a40f | ||
|
|
feb4dfc3af | ||
|
|
8c13771d6c | ||
|
|
5c3e1a6593 | ||
|
|
37f854be3e | ||
|
|
e0e82e1877 | ||
|
|
95d8665dbe | ||
|
|
8667de994e | ||
|
|
cee0e22ce7 | ||
|
|
dc8a2f54e5 | ||
|
|
b3bc5b2520 | ||
|
|
00e929afac | ||
|
|
8a3bf6a5d9 | ||
|
|
d5f361d386 | ||
|
|
7bb4a27b60 | ||
|
|
3bd36feede | ||
|
|
fc1f1ce37c | ||
|
|
ee327dc87d | ||
|
|
d644ca5563 | ||
|
|
b3b1e37192 | ||
|
|
38f39c8d32 | ||
|
|
a773b7929c | ||
|
|
7b73103133 | ||
|
|
b6803191cb | ||
|
|
2b038a34c9 | ||
|
|
8c2c3f8bee | ||
|
|
e710836276 | ||
|
|
6be3c9a48a | ||
|
|
c0747bf68d | ||
|
|
0346dd15d9 | ||
|
|
ff0aa8423d | ||
|
|
0f718e0d08 | ||
|
|
9534a9ad30 | ||
|
|
d091857cef | ||
|
|
72a9ca0aa5 | ||
|
|
ecaae42b80 | ||
|
|
84bf1ec6e7 | ||
|
|
0bf3d20e07 | ||
|
|
5486ac5b9f | ||
|
|
ac12b2e643 | ||
|
|
16c281a9b7 | ||
|
|
413e3eb62d | ||
|
|
f31f341a50 | ||
|
|
c2501258b6 | ||
|
|
de1e3e1d4f | ||
|
|
a3f64d4e95 | ||
|
|
afc9a31080 | ||
|
|
e5699e8ba9 | ||
|
|
8a7143b791 | ||
|
|
79d23909b5 | ||
|
|
24fe4740d3 | ||
|
|
1e1d3f4aa8 | ||
|
|
51bf875826 | ||
|
|
f9ce6c7c81 | ||
|
|
2e91c3d334 | ||
|
|
d1b762af04 | ||
|
|
e1e02839d1 | ||
|
|
50a812ea5e | ||
|
|
27a4adb9c6 | ||
|
|
3932d8f48f | ||
|
|
d978a8e0a2 | ||
|
|
fad49e08d7 | ||
|
|
0f67b16f29 | ||
|
|
97edd23910 | ||
|
|
400ab2cdab | ||
|
|
db20ecf985 | ||
|
|
e59f421e79 | ||
|
|
75b7de712a | ||
|
|
fa8192177d | ||
|
|
48ffef7955 | ||
|
|
055796e6b3 | ||
|
|
34433c4962 | ||
|
|
6d1076e1f6 | ||
|
|
bb4081e503 | ||
|
|
bef25ad5f6 | ||
|
|
f47652e72d | ||
|
|
7c4d7fb3dd | ||
|
|
cdfc7281d0 | ||
|
|
500e80784a | ||
|
|
c5803d9b4c | ||
|
|
ebac00fbde | ||
|
|
f198ce29b1 | ||
|
|
df47e0ed63 | ||
|
|
477470ff70 | ||
|
|
aefddf7f5e | ||
|
|
5ce27b16f1 | ||
|
|
8302d6833d | ||
|
|
649c2eb676 | ||
|
|
a0b5e32f98 | ||
|
|
e73671a6ff | ||
|
|
9503aca78d | ||
|
|
dfaa8e4529 | ||
|
|
cbc3579e9a | ||
|
|
dd2e3d35fd | ||
|
|
ca9dccfcd7 | ||
|
|
64b00fce7d | ||
|
|
177ab0229a | ||
|
|
68b5e34fed | ||
|
|
2eda839303 | ||
|
|
71a01d3002 | ||
|
|
995548002b | ||
|
|
38ad16887b | ||
|
|
c20e8f7613 | ||
|
|
5bd4606854 | ||
|
|
e7b198849d | ||
|
|
3ab0d74af2 | ||
|
|
7ed5a8e72f | ||
|
|
57daa0f7f0 | ||
|
|
2dd3f169db | ||
|
|
b97b618b4b | ||
|
|
bb12488200 | ||
|
|
706a97b013 | ||
|
|
1cdb9c733a | ||
|
|
ffc525af9e | ||
|
|
1576dc1d13 | ||
|
|
4fbb5fbb25 | ||
|
|
e9da21a02e | ||
|
|
6c4d7ad8cc | ||
|
|
f1c026c5ec | ||
|
|
3d61c06ea9 | ||
|
|
22c1ee1f55 | ||
|
|
971960a242 | ||
|
|
69f8973339 | ||
|
|
bf1d9b6d06 | ||
|
|
6a2368f83c | ||
|
|
d0960f7f7f | ||
|
|
e5ad697466 | ||
|
|
188eab5faf | ||
|
|
2b257e3d0d | ||
|
|
77c9746be5 | ||
|
|
28cae607a4 | ||
|
|
814281ed7d | ||
|
|
5b0c8dd9dd | ||
|
|
650d8c45ec | ||
|
|
383d8980d6 | ||
|
|
6ea706c646 | ||
|
|
1ed2af08b8 | ||
|
|
7563a5abe0 | ||
|
|
0a8b187f80 | ||
|
|
275aa853f5 | ||
|
|
3614d57f9f | ||
|
|
1367873949 | ||
|
|
d933183e0a | ||
|
|
7e11def527 | ||
|
|
f3e53a05a6 | ||
|
|
dd381a5c1c | ||
|
|
8eee449305 | ||
|
|
96e02af0da | ||
|
|
60fb1478c3 | ||
|
|
aa7d0679df | ||
|
|
8e3cc192a5 | ||
|
|
0f939995d1 | ||
|
|
2e1bc9b14e | ||
|
|
fd1ac6ab2d | ||
|
|
d224924dc8 | ||
|
|
00e5ddd6f0 | ||
|
|
c603ca0e7a | ||
|
|
d07ef01204 | ||
|
|
d8630b5029 | ||
|
|
3b397326f8 | ||
|
|
81cabd08a9 | ||
|
|
5663c7dec3 | ||
|
|
06673b2108 | ||
|
|
6b7498a4b1 | ||
|
|
7f4ef493b9 | ||
|
|
d9d0dee0d5 | ||
|
|
9605370f0b | ||
|
|
7d9fc682a0 | ||
|
|
c4c08f2552 | ||
|
|
400740fdba | ||
|
|
42bce7c0bf | ||
|
|
a2281489a6 | ||
|
|
9bf7b0bf96 | ||
|
|
1f82ba74aa | ||
|
|
1062ac6ade | ||
|
|
aa5304a4f3 | ||
|
|
3a57ba1142 | ||
|
|
c0e7293360 | ||
|
|
dc1839760c | ||
|
|
a4e4b0fc17 | ||
|
|
743e4deb36 | ||
|
|
1d75f8478c | ||
|
|
cc0428aa50 | ||
|
|
4be481275f | ||
|
|
28cfe36f43 | ||
|
|
e0df78c5f7 | ||
|
|
4d8b058b65 | ||
|
|
da25611758 | ||
|
|
27732c85af | ||
|
|
5ffc84eb59 | ||
|
|
0a6e540394 | ||
|
|
9f09c73ec1 | ||
|
|
4bbab876ae | ||
|
|
b2fafeff19 | ||
|
|
79510a83de | ||
|
|
dd0afdfeb0 | ||
|
|
b6997c4455 | ||
|
|
2920732435 | ||
|
|
b9cfcce284 | ||
|
|
70d997964b | ||
|
|
4ffe71e1df | ||
|
|
cc2339fbe2 | ||
|
|
8fb859c0c4 | ||
|
|
5ff472dae0 | ||
|
|
6be4a6ed00 | ||
|
|
094d46293e | ||
|
|
c8d945db56 | ||
|
|
f78f0079c1 | ||
|
|
98d6bdb48a | ||
|
|
0391aebaeb | ||
|
|
46520edd87 | ||
|
|
14daa99802 | ||
|
|
4f9f67a477 | ||
|
|
85f182067c | ||
|
|
66ab6874f8 | ||
|
|
906b901e3d | ||
|
|
65adff4bdd | ||
|
|
e60fc0dc30 | ||
|
|
ffd719962c | ||
|
|
1a1f0c0a7c | ||
|
|
3944592c09 | ||
|
|
a5f862a564 | ||
|
|
489f25940f | ||
|
|
6288909481 | ||
|
|
c95f134963 | ||
|
|
024c2883c0 | ||
|
|
d7b0ecad75 | ||
|
|
50947b81c0 | ||
|
|
e5a1b721f1 | ||
|
|
aeb1a88e7a | ||
|
|
c406675d6a | ||
|
|
1ec193991b | ||
|
|
1d09d2f0d1 | ||
|
|
5b993f601f | ||
|
|
77be6c4294 | ||
|
|
1497f263dc | ||
|
|
d6e2798f5a | ||
|
|
8e41037eb1 | ||
|
|
0bcd3bc94e | ||
|
|
fb5d53226d | ||
|
|
b5a9cc1380 | ||
|
|
1ba7368c6d | ||
|
|
c1a78d9c3e | ||
|
|
ad36671adb | ||
|
|
8902d0843b | ||
|
|
010ac6a6ac | ||
|
|
c71589a710 | ||
|
|
9b70ea0595 | ||
|
|
a6928c29e8 | ||
|
|
3d9129c198 | ||
|
|
cf8f37449c | ||
|
|
886870964b | ||
|
|
6a5e107e64 | ||
|
|
723624b3f7 | ||
|
|
634dfaa1ca | ||
|
|
2d00495d29 | ||
|
|
ea964fd733 | ||
|
|
dd5803e576 | ||
|
|
85e16f6e82 | ||
|
|
0a9f61783d | ||
|
|
19a0071585 | ||
|
|
2fe07e86c7 | ||
|
|
22c1b34ebf | ||
|
|
eca11a74d7 | ||
|
|
b3df24d188 | ||
|
|
6fcd6419bd | ||
|
|
edb0fa17af | ||
|
|
86eb9cc058 | ||
|
|
47c0526026 | ||
|
|
f5416b1c2c | ||
|
|
a9f42f7a9e | ||
|
|
7650e1c7df | ||
|
|
0de76d6d3f | ||
|
|
db4f972315 | ||
|
|
8e4a01c98d | ||
|
|
21976b14a6 | ||
|
|
d3048aa06f | ||
|
|
a1a85270e6 | ||
|
|
2f6438f75a | ||
|
|
ff0fdd5837 | ||
|
|
25b0a26ff9 | ||
|
|
3f8abd2218 | ||
|
|
4ec214b3fc | ||
|
|
cefbd64f37 | ||
|
|
d25d839d6a | ||
|
|
23d49560bf | ||
|
|
bb16849eef | ||
|
|
ae17971599 | ||
|
|
f13b068479 | ||
|
|
850941bf8b | ||
|
|
11dd156594 | ||
|
|
78587ee6b1 | ||
|
|
3eeb184278 | ||
|
|
7632eb1ce0 | ||
|
|
3eea175d36 | ||
|
|
8f24ff74dd | ||
|
|
6fab7d0a27 | ||
|
|
7cc52e0a55 | ||
|
|
aa0801014a | ||
|
|
9e379338bc | ||
|
|
ba62d13a14 | ||
|
|
d7d7147549 | ||
|
|
8a73f84003 | ||
|
|
118599b4cc | ||
|
|
1d32e010ae | ||
|
|
91481caf89 | ||
|
|
491826556b | ||
|
|
6463019faf | ||
|
|
e3b2a7a69b | ||
|
|
51b54fce64 | ||
|
|
184c58bf36 | ||
|
|
39abb0b0ad | ||
|
|
cb6c8ac78b | ||
|
|
a906faeb35 | ||
|
|
1a8e08e429 | ||
|
|
d47a693611 | ||
|
|
886262539a | ||
|
|
401c5a7cb0 | ||
|
|
b5c66dd52a | ||
|
|
e7cf5a546a | ||
|
|
8c10aa287c | ||
|
|
6b2fe03d08 | ||
|
|
799d362654 | ||
|
|
64beb17fa6 | ||
|
|
2bddb8409c | ||
|
|
b1a056082a | ||
|
|
7dc82ba4d7 | ||
|
|
4645a4300e | ||
|
|
13d8306a7b | ||
|
|
9b1a74cc22 | ||
|
|
25e97df641 | ||
|
|
6b3245ddfc | ||
|
|
001880e1f0 | ||
|
|
ddfd067e97 | ||
|
|
64117c2964 | ||
|
|
c8ce099f22 | ||
|
|
4878192a25 | ||
|
|
c901ab844f | ||
|
|
eb2b4028ab | ||
|
|
64a0718032 | ||
|
|
b2a6876a50 | ||
|
|
5ea2da4245 | ||
|
|
ff1f286882 | ||
|
|
e013532e27 | ||
|
|
1a42a1e6b1 | ||
|
|
d946774741 | ||
|
|
3d080d2733 | ||
|
|
aa7a149560 | ||
|
|
6beea86df7 | ||
|
|
5917d05305 | ||
|
|
ceb4464a9b | ||
|
|
8f0e554bc4 | ||
|
|
7c7a1b64df | ||
|
|
ea661896a1 | ||
|
|
2bb2ef07e9 | ||
|
|
c71934215b | ||
|
|
f31884fc15 | ||
|
|
2b1ffb5fb9 | ||
|
|
fadcd43fee | ||
|
|
5af800b16a | ||
|
|
37622af55a | ||
|
|
d4dfc5443c | ||
|
|
18b70bff0e | ||
|
|
d4bd9187d6 | ||
|
|
712f5a9782 | ||
|
|
29d4f6888d | ||
|
|
5b47409fb0 | ||
|
|
8077fdeddb | ||
|
|
6f290e249f | ||
|
|
a655f2cbba | ||
|
|
0cb42f840d | ||
|
|
99aabef7f3 | ||
|
|
7d51c6e4f4 | ||
|
|
f463fb3759 | ||
|
|
cb0eb0e68e | ||
|
|
8009f220fc | ||
|
|
4f1551b91f | ||
|
|
6ba37a135e | ||
|
|
dab514d8bc | ||
|
|
8342b29618 | ||
|
|
e05944c6cb | ||
|
|
88a81f5737 | ||
|
|
9cf6ca045c | ||
|
|
ba381d0d0b | ||
|
|
033ebc7ce3 | ||
|
|
6292219551 | ||
|
|
ed237c8d25 | ||
|
|
d46a5345d2 | ||
|
|
523141597e | ||
|
|
cfed5c914c | ||
|
|
20f9bb3b14 | ||
|
|
6067160582 | ||
|
|
3175c4f7ba | ||
|
|
a29f06a730 | ||
|
|
c713474d1f | ||
|
|
89c874d4a9 | ||
|
|
5e3cba9b70 | ||
|
|
a7894fd785 | ||
|
|
c638a770f9 | ||
|
|
6ced6ac23b | ||
|
|
d0b77b61eb | ||
|
|
b440c3636b | ||
|
|
dfd58961f7 | ||
|
|
139c9f37b1 | ||
|
|
2445b12898 | ||
|
|
4d402f3a06 | ||
|
|
ab022ccc33 | ||
|
|
fb7bbac524 | ||
|
|
39fbff5fb6 | ||
|
|
3ac1eaf7d2 | ||
|
|
6c95d008e0 | ||
|
|
16f891c290 | ||
|
|
650bddd54b | ||
|
|
9e30df4b43 | ||
|
|
50c592e41f | ||
|
|
bdf8cd2dd5 | ||
|
|
5554df29fd | ||
|
|
2dd3088f50 | ||
|
|
b9bd128c7a | ||
|
|
adb67d1910 | ||
|
|
ce3b815bd8 | ||
|
|
b94f9ef496 | ||
|
|
77db475663 | ||
|
|
a3683be047 | ||
|
|
9dca19d6c9 | ||
|
|
3ba847ece2 | ||
|
|
91bf948d1e | ||
|
|
91fec77f4b | ||
|
|
8fb25a6340 | ||
|
|
cf49acff67 | ||
|
|
4f1a25e1bf | ||
|
|
8608daa7dc | ||
|
|
828e6e3fd0 | ||
|
|
ff021fed1f | ||
|
|
ed66f36cb5 | ||
|
|
b7ff996b15 | ||
|
|
faf53fe11e | ||
|
|
b23c4b4da6 | ||
|
|
966bb2271a | ||
|
|
5438be891b | ||
|
|
f31f603c8b | ||
|
|
ff39fa0fed | ||
|
|
cdf3809634 | ||
|
|
b2a2791f6f | ||
|
|
125df968d2 | ||
|
|
1c5d07a29f | ||
|
|
24b025f573 | ||
|
|
d323bd3593 | ||
|
|
b7174783f1 | ||
|
|
e3269616bd | ||
|
|
64051fca10 | ||
|
|
14ce55b1a8 |
@@ -1,5 +1,4 @@
|
||||
version: 2.1
|
||||
|
||||
executors:
|
||||
default:
|
||||
docker:
|
||||
@@ -13,7 +12,7 @@ restore-workspace: &restore-workspace
|
||||
restore-cache: &restore-cache
|
||||
restore_cache:
|
||||
keys:
|
||||
- cargo-v0-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||
- cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||
- repo-source-{{ .Branch }}-{{ .Revision }}
|
||||
|
||||
commands:
|
||||
@@ -24,20 +23,9 @@ commands:
|
||||
steps:
|
||||
- *restore-workspace
|
||||
- *restore-cache
|
||||
- setup_remote_docker:
|
||||
docker_layer_caching: true
|
||||
# TODO: move into image
|
||||
- run:
|
||||
name: Install Docker client
|
||||
command: |
|
||||
set -x
|
||||
VER="18.09.2"
|
||||
curl -L -o /tmp/docker-$VER.tgz https://download.docker.com/linux/static/stable/x86_64/docker-$VER.tgz
|
||||
tar -xz -C /tmp -f /tmp/docker-$VER.tgz
|
||||
mv /tmp/docker/* /usr/bin
|
||||
- run:
|
||||
name: Test (<< parameters.target >>)
|
||||
command: TARGET=<< parameters.target >> ci/run.sh
|
||||
command: TARGET=<< parameters.target >> ci_scripts/run-rust-test.sh
|
||||
no_output_timeout: 15m
|
||||
|
||||
jobs:
|
||||
@@ -53,21 +41,23 @@ jobs:
|
||||
command: cargo generate-lockfile
|
||||
- restore_cache:
|
||||
keys:
|
||||
- cargo-v0-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||
- cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||
- run: rustup install $(cat rust-toolchain)
|
||||
- run: rustup default $(cat rust-toolchain)
|
||||
- run: rustup component add --toolchain $(cat rust-toolchain) rustfmt
|
||||
- run: rustup component add --toolchain $(cat rust-toolchain) clippy-preview
|
||||
- run: cargo update
|
||||
- run: cargo fetch
|
||||
- run: rustc +stable --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:
|
||||
root: /mnt
|
||||
paths:
|
||||
- crate
|
||||
- save_cache:
|
||||
key: cargo-v0-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||
key: cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||
paths:
|
||||
- "~/.cargo"
|
||||
- "~/.rustup"
|
||||
@@ -102,7 +92,7 @@ jobs:
|
||||
- run: cargo fetch
|
||||
- run:
|
||||
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:
|
||||
executor: default
|
||||
@@ -124,18 +114,18 @@ jobs:
|
||||
|
||||
|
||||
build_test_docs_wheel:
|
||||
machine: True
|
||||
steps:
|
||||
- checkout
|
||||
# - run: docker pull deltachat/doxygen
|
||||
- run: docker pull deltachat/coredeps
|
||||
- run:
|
||||
name: build docs, run tests and build wheels
|
||||
command: ci_scripts/ci_run.sh
|
||||
environment:
|
||||
docker:
|
||||
- image: deltachat/coredeps
|
||||
environment:
|
||||
TESTS: 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:
|
||||
name: copying docs and wheels to workspace
|
||||
command: |
|
||||
@@ -143,7 +133,6 @@ jobs:
|
||||
# cp -av docs workspace/c-docs
|
||||
cp -av python/.docker-tox/wheelhouse workspace/
|
||||
cp -av python/doc/_build/ workspace/py-docs
|
||||
|
||||
- persist_to_workspace:
|
||||
root: workspace
|
||||
paths:
|
||||
@@ -152,28 +141,43 @@ jobs:
|
||||
- wheelhouse
|
||||
|
||||
upload_docs_wheels:
|
||||
machine: True
|
||||
machine: true
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: workspace
|
||||
- run: pyenv global 3.5.2
|
||||
- run: ls -laR workspace
|
||||
- run: ci_scripts/ci_upload.sh workspace/py-docs workspace/wheelhouse
|
||||
|
||||
clippy:
|
||||
executor: default
|
||||
steps:
|
||||
- *restore-workspace
|
||||
- *restore-cache
|
||||
- run:
|
||||
name: Run cargo clippy
|
||||
command: cargo clippy --all
|
||||
|
||||
|
||||
workflows:
|
||||
version: 2.1
|
||||
|
||||
test:
|
||||
jobs:
|
||||
- build_test_docs_wheel
|
||||
- cargo_fetch
|
||||
- build_test_docs_wheel:
|
||||
requires:
|
||||
- cargo_fetch
|
||||
- upload_docs_wheels:
|
||||
requires:
|
||||
- build_test_docs_wheel
|
||||
- cargo_fetch
|
||||
- rustfmt:
|
||||
requires:
|
||||
- cargo_fetch
|
||||
- clippy:
|
||||
requires:
|
||||
- cargo_fetch
|
||||
|
||||
# Linux Desktop 64bit
|
||||
- test_x86_64-unknown-linux-gnu:
|
||||
|
||||
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -2,6 +2,10 @@
|
||||
# ensures this even if the user has not set core.autocrlf.
|
||||
* text=auto
|
||||
|
||||
# This directory contains email messages verbatim, and changing CRLF to
|
||||
# LF will corrupt them.
|
||||
test-data/* text=false
|
||||
|
||||
# binary files should be detected by git, however, to be sure, you can add them here explicitly
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -18,3 +18,7 @@ __pycache__
|
||||
python/src/deltachat/capi*.so
|
||||
|
||||
python/liveconfig*
|
||||
|
||||
# ignore doxgen generated files
|
||||
deltachat-ffi/html
|
||||
deltachat-ffi/xml
|
||||
|
||||
1184
Cargo.lock
generated
1184
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
25
Cargo.toml
25
Cargo.toml
@@ -1,28 +1,26 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.0.0-alpha.3"
|
||||
version = "1.0.0-alpha.4"
|
||||
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
|
||||
edition = "2018"
|
||||
license = "MPL"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0.35"
|
||||
pkg-config = "0.3"
|
||||
|
||||
[dependencies]
|
||||
deltachat_derive = { path = "./deltachat_derive" }
|
||||
libc = "0.2.51"
|
||||
pgp = { version = "0.2", default-features = false }
|
||||
hex = "0.3.2"
|
||||
sha2 = "0.8.0"
|
||||
rand = "0.6.5"
|
||||
phf = { git = "https://github.com/sfackler/rust-phf", rev = "0d00821", features = ["macros"] }
|
||||
smallvec = "0.6.9"
|
||||
reqwest = "0.9.15"
|
||||
num-derive = "0.2.5"
|
||||
num-traits = "0.2.6"
|
||||
native-tls = "0.2.3"
|
||||
lettre = "0.9.0"
|
||||
imap = "1.0.1"
|
||||
mmime = "0.1.0"
|
||||
imap = { git = "https://github.com/jonhoo/rust-imap", rev = "281d2eb8ab50dc656ceff2ae749ca5045f334e15" }
|
||||
mmime = { git = "https://github.com/dignifiedquire/mmime", rev = "bccd2c2" }
|
||||
base64 = "0.10"
|
||||
charset = "0.1"
|
||||
percent-encoding = "2.0"
|
||||
@@ -33,10 +31,9 @@ failure = "0.1.5"
|
||||
failure_derive = "0.1.5"
|
||||
# TODO: make optional
|
||||
rustyline = "4.1.0"
|
||||
lazy_static = "1.3.0"
|
||||
lazy_static = "1.4.0"
|
||||
regex = "1.1.6"
|
||||
rusqlite = { version = "0.20", features = ["bundled"] }
|
||||
addr = "0.2.0"
|
||||
r2d2_sqlite = "0.12.0"
|
||||
r2d2 = "0.8.5"
|
||||
strum = "0.15.0"
|
||||
@@ -45,15 +42,23 @@ thread-local-object = "0.1.0"
|
||||
backtrace = "0.3.33"
|
||||
byteorder = "1.3.1"
|
||||
itertools = "0.8.0"
|
||||
image-meta = "0.1.0"
|
||||
quick-xml = "0.15.0"
|
||||
escaper = "0.1.0"
|
||||
bitflags = "1.1.0"
|
||||
jetscii = "0.4.4"
|
||||
debug_stub_derive = "0.3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.0"
|
||||
pretty_assertions = "0.6.1"
|
||||
pretty_env_logger = "0.3.0"
|
||||
proptest = "0.9.4"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"deltachat-ffi"
|
||||
"deltachat-ffi",
|
||||
"deltachat_derive",
|
||||
]
|
||||
|
||||
[[example]]
|
||||
|
||||
@@ -89,6 +89,14 @@ $ cargo test --all
|
||||
$ cargo build -p deltachat_ffi --release
|
||||
```
|
||||
|
||||
### Expensive tests
|
||||
|
||||
Some tests are expensive and marked with `#[ignore]`, to run these
|
||||
use the `--ignored` argument to the test binary (not to cargo itself):
|
||||
```sh
|
||||
$ cargo test -- --ignored
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- `vendored`: When using Openssl for TLS, this bundles a vendored version.
|
||||
|
||||
6
Xargo.toml
Normal file
6
Xargo.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[dependencies.std]
|
||||
features = ["panic-unwind"]
|
||||
|
||||
# if using `cargo test`
|
||||
[dependencies.test]
|
||||
stage = 1
|
||||
38
build.rs
38
build.rs
@@ -1,38 +0,0 @@
|
||||
extern crate cc;
|
||||
|
||||
fn link_static(lib: &str) {
|
||||
println!("cargo:rustc-link-lib=static={}", lib);
|
||||
}
|
||||
|
||||
fn link_framework(fw: &str) {
|
||||
println!("cargo:rustc-link-lib=framework={}", fw);
|
||||
}
|
||||
|
||||
fn add_search_path(p: &str) {
|
||||
println!("cargo:rustc-link-search={}", p);
|
||||
}
|
||||
|
||||
fn build_tools() {
|
||||
let mut config = cc::Build::new();
|
||||
config.file("misc.c").compile("libtools.a");
|
||||
|
||||
println!("rerun-if-changed=build.rs");
|
||||
println!("rerun-if-changed=misc.h");
|
||||
println!("rerun-if-changed=misc.c");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
build_tools();
|
||||
|
||||
add_search_path("/usr/local/lib");
|
||||
|
||||
let target = std::env::var("TARGET").unwrap();
|
||||
if target.contains("-apple") || target.contains("-darwin") {
|
||||
link_framework("CoreFoundation");
|
||||
link_framework("CoreServices");
|
||||
link_framework("Security");
|
||||
}
|
||||
|
||||
// local tools
|
||||
link_static("tools");
|
||||
}
|
||||
@@ -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 BRANCH -e TESTS -e DOCS \
|
||||
--rm -it -v $(pwd):/mnt -w /mnt \
|
||||
deltachat/coredeps ci_scripts/run_all.sh
|
||||
|
||||
@@ -15,6 +15,7 @@ export BRANCH=${CIRCLE_BRANCH:?specify branch for uploading purposes}
|
||||
|
||||
|
||||
# python docs to py.delta.chat
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@py.delta.chat mkdir -p build/${BRANCH}
|
||||
rsync -avz \
|
||||
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
|
||||
"$PYDOCDIR/html/" \
|
||||
@@ -33,15 +34,17 @@ echo -----------------------
|
||||
# Bundle external shared libraries into the wheels
|
||||
pushd $WHEELHOUSEDIR
|
||||
|
||||
pip install devpi-client
|
||||
pip3 install devpi-client
|
||||
devpi use https://m.devpi.net
|
||||
devpi login dc --password $DEVPI_LOGIN
|
||||
|
||||
devpi use dc/$BRANCH || {
|
||||
devpi index -c $BRANCH
|
||||
devpi use dc/$BRANCH
|
||||
N_BRANCH=${BRANCH//[\/]}
|
||||
|
||||
devpi use dc/$N_BRANCH || {
|
||||
devpi index -c $N_BRANCH
|
||||
devpi use dc/$N_BRANCH
|
||||
}
|
||||
devpi index $BRANCH bases=/root/pypi
|
||||
devpi index $N_BRANCH bases=/root/pypi
|
||||
devpi upload deltachat*.whl
|
||||
|
||||
popd
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
set -e -x
|
||||
|
||||
# Install Rust
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2019-04-19 -y
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2019-07-10 -y
|
||||
export PATH=/root/.cargo/bin:$PATH
|
||||
rustc --version
|
||||
|
||||
# remove some 300-400 MB that we don't need for automated builds
|
||||
rm -rf /root/.rustup/toolchains/nightly-2019-04-19-x86_64-unknown-linux-gnu/share/
|
||||
rm -rf /root/.rustup/toolchains/nightly-2019-07-10-x86_64-unknown-linux-gnu/share/
|
||||
|
||||
@@ -36,15 +36,21 @@ if [ -n "$TESTS" ]; then
|
||||
rm -rf src/deltachat/__pycache__
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# run tox
|
||||
tox --workdir "$TOXWORKDIR" -e lint,py27,py35,py36,py37,auditwheels
|
||||
# run tox. The circle-ci project env-var-setting DCC_PY_LIVECONFIG
|
||||
# allows running of "liveconfig" tests but for speed reasons
|
||||
# we run them only for the highest python version we support
|
||||
|
||||
tox --workdir "$TOXWORKDIR" -e py37
|
||||
unset DCC_PY_LIVECONFIG
|
||||
tox --workdir "$TOXWORKDIR" -p4 -e lint,py35,py36,doc
|
||||
tox --workdir "$TOXWORKDIR" -e auditwheels
|
||||
popd
|
||||
fi
|
||||
|
||||
|
||||
if [ -n "$DOCS" ]; then
|
||||
echo -----------------------
|
||||
echo generating python docs
|
||||
echo -----------------------
|
||||
(cd python && tox --workdir "$TOXWORKDIR" -e doc)
|
||||
fi
|
||||
# if [ -n "$DOCS" ]; then
|
||||
# echo -----------------------
|
||||
# echo generating python docs
|
||||
# echo -----------------------
|
||||
# (cd python && tox --workdir "$TOXWORKDIR" -e doc)
|
||||
# fi
|
||||
@@ -39,8 +39,4 @@ fi
|
||||
|
||||
# Run all the test configurations:
|
||||
$CARGO_CMD $CARGO_SUBCMD $OPT
|
||||
$CARGO_CMD $CARGO_SUBCMD $OPT_RELEASE
|
||||
$CARGO_CMD $CARGO_SUBCMD $OPT_RELEASE_IGNORED
|
||||
|
||||
# Build the ffi lib
|
||||
$CARGO_CMD $CARGO_SUBCMD $OPT_FFI_RELEASE
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.0.0-alpha.3"
|
||||
version = "1.0.0-alpha.4"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
|
||||
edition = "2018"
|
||||
@@ -16,6 +16,7 @@ crate-type = ["cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
deltachat = { path = "../", default-features = false }
|
||||
deltachat-provider-overview = { git = "https://github.com/deltachat/provider-overview", rev = "366b41a7503973e4ffac3aa5173b419f2f03c211" }
|
||||
libc = "0.2"
|
||||
human-panic = "1.0.1"
|
||||
num-traits = "0.2.6"
|
||||
@@ -25,4 +26,3 @@ default = ["vendored", "nightly", "ringbuf"]
|
||||
vendored = ["deltachat/vendored"]
|
||||
nightly = ["deltachat/nightly"]
|
||||
ringbuf = ["deltachat/ringbuf"]
|
||||
|
||||
|
||||
2423
deltachat-ffi/Doxyfile
Normal file
2423
deltachat-ffi/Doxyfile
Normal file
File diff suppressed because it is too large
Load Diff
BIN
deltachat-ffi/Doxyfile-logo.png
Normal file
BIN
deltachat-ffi/Doxyfile-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
7
deltachat-ffi/Doxyfile.css
Normal file
7
deltachat-ffi/Doxyfile.css
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
/* the code snippet frame, defaults to white which tends to get badly readable in combination with explaining text around */
|
||||
div.fragment {
|
||||
background-color: #e0e0e0;
|
||||
border: 0;
|
||||
padding: 1em;
|
||||
}
|
||||
@@ -1 +1,10 @@
|
||||
# Delta Chat C Interface
|
||||
|
||||
## Documentation
|
||||
|
||||
To generate the C Interface documentation,
|
||||
call doxygen in the `deltachat-ffi` directory
|
||||
and browse the `html` subdirectory.
|
||||
|
||||
If thinks work,
|
||||
the documentation is also available online at <https://c.delta.chat>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
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_overview;
|
||||
|
||||
use std::ptr;
|
||||
|
||||
use deltachat::dc_tools::{as_str, StrExt};
|
||||
use deltachat_provider_overview::StatusState;
|
||||
|
||||
#[no_mangle]
|
||||
pub type dc_provider_t = deltachat_provider_overview::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_overview::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_overview::get_domain_from_email(as_str(email));
|
||||
match deltachat_provider_overview::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_overview::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?
|
||||
12
deltachat_derive/Cargo.toml
Normal file
12
deltachat_derive/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "deltachat_derive"
|
||||
version = "0.1.0"
|
||||
authors = ["Dmitry Bogatov <KAction@debian.org>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = "0.14.4"
|
||||
quote = "0.6.3"
|
||||
44
deltachat_derive/src/lib.rs
Normal file
44
deltachat_derive/src/lib.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
#![recursion_limit = "128"]
|
||||
extern crate proc_macro;
|
||||
|
||||
use crate::proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn;
|
||||
|
||||
// For now, assume (not check) that these macroses are applied to enum without
|
||||
// data. If this assumption is violated, compiler error will point to
|
||||
// generated code, which is not very user-friendly.
|
||||
|
||||
#[proc_macro_derive(ToSql)]
|
||||
pub fn to_sql_derive(input: TokenStream) -> TokenStream {
|
||||
let ast: syn::DeriveInput = syn::parse(input).unwrap();
|
||||
let name = &ast.ident;
|
||||
|
||||
let gen = quote! {
|
||||
impl rusqlite::types::ToSql for #name {
|
||||
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
|
||||
let num = *self as i64;
|
||||
let value = rusqlite::types::Value::Integer(num);
|
||||
let output = rusqlite::types::ToSqlOutput::Owned(value);
|
||||
std::result::Result::Ok(output)
|
||||
}
|
||||
}
|
||||
};
|
||||
gen.into()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(FromSql)]
|
||||
pub fn from_sql_derive(input: TokenStream) -> TokenStream {
|
||||
let ast: syn::DeriveInput = syn::parse(input).unwrap();
|
||||
let name = &ast.ident;
|
||||
|
||||
let gen = quote! {
|
||||
impl rusqlite::types::FromSql for #name {
|
||||
fn column_result(col: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
|
||||
let inner = rusqlite::types::FromSql::column_result(col)?;
|
||||
Ok(num_traits::FromPrimitive::from_i64(inner).unwrap_or_default())
|
||||
}
|
||||
}
|
||||
};
|
||||
gen.into()
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,6 @@
|
||||
//!
|
||||
//! Usage: cargo run --example repl --release -- <databasefile>
|
||||
//! All further options can be set using the set-command (type ? for help).
|
||||
#![feature(ptr_cast)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate deltachat;
|
||||
@@ -15,19 +14,21 @@ extern crate lazy_static;
|
||||
extern crate rusqlite;
|
||||
|
||||
use std::borrow::Cow::{self, Borrowed, Owned};
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
use deltachat::config;
|
||||
use deltachat::constants::*;
|
||||
use deltachat::configure::*;
|
||||
use deltachat::context::*;
|
||||
use deltachat::dc_configure::*;
|
||||
use deltachat::dc_job::*;
|
||||
use deltachat::dc_securejoin::*;
|
||||
use deltachat::dc_tools::*;
|
||||
use deltachat::job::*;
|
||||
use deltachat::oauth2::*;
|
||||
use deltachat::types::*;
|
||||
use deltachat::securejoin::*;
|
||||
use deltachat::x::*;
|
||||
use deltachat::Event;
|
||||
use rustyline::completion::{Completer, FilenameCompleter, Pair};
|
||||
use rustyline::config::OutputStreamType;
|
||||
use rustyline::error::ReadlineError;
|
||||
@@ -42,96 +43,75 @@ use self::cmdline::*;
|
||||
|
||||
// Event Handler
|
||||
|
||||
unsafe extern "C" fn receive_event(
|
||||
_context: &Context,
|
||||
event: Event,
|
||||
data1: uintptr_t,
|
||||
data2: uintptr_t,
|
||||
) -> uintptr_t {
|
||||
fn receive_event(_context: &Context, event: Event) -> libc::uintptr_t {
|
||||
match event {
|
||||
Event::GET_STRING => {}
|
||||
Event::INFO => {
|
||||
Event::GetString { .. } => {}
|
||||
Event::Info(msg) => {
|
||||
/* do not show the event as this would fill the screen */
|
||||
println!("{}", to_string(data2 as *const _),);
|
||||
println!("{}", msg);
|
||||
}
|
||||
Event::SMTP_CONNECTED => {
|
||||
println!("[DC_EVENT_SMTP_CONNECTED] {}", to_string(data2 as *const _));
|
||||
Event::SmtpConnected(msg) => {
|
||||
println!("[DC_EVENT_SMTP_CONNECTED] {}", msg);
|
||||
}
|
||||
Event::IMAP_CONNECTED => {
|
||||
println!("[DC_EVENT_IMAP_CONNECTED] {}", to_string(data2 as *const _),);
|
||||
Event::ImapConnected(msg) => {
|
||||
println!("[DC_EVENT_IMAP_CONNECTED] {}", msg);
|
||||
}
|
||||
Event::SMTP_MESSAGE_SENT => {
|
||||
println!(
|
||||
"[DC_EVENT_SMTP_MESSAGE_SENT] {}",
|
||||
to_string(data2 as *const _),
|
||||
);
|
||||
Event::SmtpMessageSent(msg) => {
|
||||
println!("[DC_EVENT_SMTP_MESSAGE_SENT] {}", msg);
|
||||
}
|
||||
Event::WARNING => {
|
||||
println!("[Warning] {}", to_string(data2 as *const _),);
|
||||
Event::Warning(msg) => {
|
||||
println!("[Warning] {}", msg);
|
||||
}
|
||||
Event::ERROR => {
|
||||
println!(
|
||||
"\x1b[31m[DC_EVENT_ERROR] {}\x1b[0m",
|
||||
to_string(data2 as *const _),
|
||||
);
|
||||
Event::Error(msg) => {
|
||||
println!("\x1b[31m[DC_EVENT_ERROR] {}\x1b[0m", msg);
|
||||
}
|
||||
Event::ERROR_NETWORK => {
|
||||
println!(
|
||||
"\x1b[31m[DC_EVENT_ERROR_NETWORK] first={}, msg={}\x1b[0m",
|
||||
data1 as usize,
|
||||
to_string(data2 as *const _),
|
||||
);
|
||||
Event::ErrorNetwork(msg) => {
|
||||
println!("\x1b[31m[DC_EVENT_ERROR_NETWORK] msg={}\x1b[0m", msg);
|
||||
}
|
||||
Event::ERROR_SELF_NOT_IN_GROUP => {
|
||||
println!(
|
||||
"\x1b[31m[DC_EVENT_ERROR_SELF_NOT_IN_GROUP] {}\x1b[0m",
|
||||
to_string(data2 as *const _),
|
||||
);
|
||||
Event::ErrorSelfNotInGroup(msg) => {
|
||||
println!("\x1b[31m[DC_EVENT_ERROR_SELF_NOT_IN_GROUP] {}\x1b[0m", msg);
|
||||
}
|
||||
Event::MSGS_CHANGED => {
|
||||
Event::MsgsChanged { chat_id, msg_id } => {
|
||||
print!(
|
||||
"\x1b[33m{{Received DC_EVENT_MSGS_CHANGED({}, {})}}\n\x1b[0m",
|
||||
data1 as usize, data2 as usize,
|
||||
"\x1b[33m{{Received DC_EVENT_MSGS_CHANGED(chat_id={}, msg_id={})}}\n\x1b[0m",
|
||||
chat_id, msg_id,
|
||||
);
|
||||
}
|
||||
Event::CONTACTS_CHANGED => {
|
||||
Event::ContactsChanged(_) => {
|
||||
print!("\x1b[33m{{Received DC_EVENT_CONTACTS_CHANGED()}}\n\x1b[0m");
|
||||
}
|
||||
Event::LOCATION_CHANGED => {
|
||||
Event::LocationChanged(contact) => {
|
||||
print!(
|
||||
"\x1b[33m{{Received DC_EVENT_LOCATION_CHANGED(contact={})}}\n\x1b[0m",
|
||||
data1 as usize,
|
||||
"\x1b[33m{{Received DC_EVENT_LOCATION_CHANGED(contact={:?})}}\n\x1b[0m",
|
||||
contact,
|
||||
);
|
||||
}
|
||||
Event::CONFIGURE_PROGRESS => {
|
||||
Event::ConfigureProgress(progress) => {
|
||||
print!(
|
||||
"\x1b[33m{{Received DC_EVENT_CONFIGURE_PROGRESS({} ‰)}}\n\x1b[0m",
|
||||
data1 as usize,
|
||||
progress,
|
||||
);
|
||||
}
|
||||
Event::IMEX_PROGRESS => {
|
||||
Event::ImexProgress(progress) => {
|
||||
print!(
|
||||
"\x1b[33m{{Received DC_EVENT_IMEX_PROGRESS({} ‰)}}\n\x1b[0m",
|
||||
data1 as usize,
|
||||
progress,
|
||||
);
|
||||
}
|
||||
Event::IMEX_FILE_WRITTEN => {
|
||||
Event::ImexFileWritten(file) => {
|
||||
print!(
|
||||
"\x1b[33m{{Received DC_EVENT_IMEX_FILE_WRITTEN({})}}\n\x1b[0m",
|
||||
to_string(data1 as *const _)
|
||||
file.display()
|
||||
);
|
||||
}
|
||||
Event::CHAT_MODIFIED => {
|
||||
Event::ChatModified(chat) => {
|
||||
print!(
|
||||
"\x1b[33m{{Received DC_EVENT_CHAT_MODIFIED({})}}\n\x1b[0m",
|
||||
data1 as usize,
|
||||
chat
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
print!(
|
||||
"\x1b[33m{{Received {:?}({}, {})}}\n\x1b[0m",
|
||||
event, data1 as usize, data2 as usize,
|
||||
);
|
||||
print!("\x1b[33m{{Received {:?}}}\n\x1b[0m", event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,13 +153,11 @@ fn start_threads(c: Arc<RwLock<Context>>) {
|
||||
let ctx = c.clone();
|
||||
let handle_imap = std::thread::spawn(move || loop {
|
||||
while_running!({
|
||||
unsafe {
|
||||
dc_perform_imap_jobs(&ctx.read().unwrap());
|
||||
dc_perform_imap_fetch(&ctx.read().unwrap());
|
||||
}
|
||||
perform_imap_jobs(&ctx.read().unwrap());
|
||||
perform_imap_fetch(&ctx.read().unwrap());
|
||||
while_running!({
|
||||
let context = ctx.read().unwrap();
|
||||
dc_perform_imap_idle(&context);
|
||||
perform_imap_idle(&context);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -187,9 +165,9 @@ fn start_threads(c: Arc<RwLock<Context>>) {
|
||||
let ctx = c.clone();
|
||||
let handle_mvbox = std::thread::spawn(move || loop {
|
||||
while_running!({
|
||||
unsafe { dc_perform_mvbox_fetch(&ctx.read().unwrap()) };
|
||||
perform_mvbox_fetch(&ctx.read().unwrap());
|
||||
while_running!({
|
||||
unsafe { dc_perform_mvbox_idle(&ctx.read().unwrap()) };
|
||||
perform_mvbox_idle(&ctx.read().unwrap());
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -197,9 +175,9 @@ fn start_threads(c: Arc<RwLock<Context>>) {
|
||||
let ctx = c.clone();
|
||||
let handle_sentbox = std::thread::spawn(move || loop {
|
||||
while_running!({
|
||||
unsafe { dc_perform_sentbox_fetch(&ctx.read().unwrap()) };
|
||||
perform_sentbox_fetch(&ctx.read().unwrap());
|
||||
while_running!({
|
||||
unsafe { dc_perform_sentbox_idle(&ctx.read().unwrap()) };
|
||||
perform_sentbox_idle(&ctx.read().unwrap());
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -207,9 +185,9 @@ fn start_threads(c: Arc<RwLock<Context>>) {
|
||||
let ctx = c;
|
||||
let handle_smtp = std::thread::spawn(move || loop {
|
||||
while_running!({
|
||||
unsafe { dc_perform_smtp_jobs(&ctx.read().unwrap()) };
|
||||
perform_smtp_jobs(&ctx.read().unwrap());
|
||||
while_running!({
|
||||
unsafe { dc_perform_smtp_idle(&ctx.read().unwrap()) };
|
||||
perform_smtp_idle(&ctx.read().unwrap());
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -227,12 +205,10 @@ fn stop_threads(context: &Context) {
|
||||
println!("Stopping threads");
|
||||
IS_RUNNING.store(false, Ordering::Relaxed);
|
||||
|
||||
unsafe {
|
||||
dc_interrupt_imap_idle(context);
|
||||
dc_interrupt_mvbox_idle(context);
|
||||
dc_interrupt_sentbox_idle(context);
|
||||
dc_interrupt_smtp_idle(context);
|
||||
}
|
||||
interrupt_imap_idle(context);
|
||||
interrupt_mvbox_idle(context);
|
||||
interrupt_sentbox_idle(context);
|
||||
interrupt_smtp_idle(context);
|
||||
|
||||
handle.handle_imap.take().unwrap().join().unwrap();
|
||||
handle.handle_mvbox.take().unwrap().join().unwrap();
|
||||
@@ -335,8 +311,8 @@ const CONTACT_COMMANDS: [&'static str; 6] = [
|
||||
"delcontact",
|
||||
"cleanupcontacts",
|
||||
];
|
||||
const MISC_COMMANDS: [&'static str; 8] = [
|
||||
"getqr", "getbadqr", "checkqr", "event", "fileinfo", "clear", "exit", "help",
|
||||
const MISC_COMMANDS: [&'static str; 9] = [
|
||||
"getqr", "getbadqr", "checkqr", "event", "fileinfo", "clear", "exit", "quit", "help",
|
||||
];
|
||||
|
||||
impl Hinter for DcHelper {
|
||||
@@ -389,21 +365,15 @@ impl Highlighter for DcHelper {
|
||||
impl Helper for DcHelper {}
|
||||
|
||||
fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
|
||||
let mut context = dc_context_new(
|
||||
Some(receive_event),
|
||||
0 as *mut libc::c_void,
|
||||
Some("CLI".into()),
|
||||
);
|
||||
|
||||
unsafe { dc_cmdline_skip_auth() };
|
||||
|
||||
if args.len() == 2 {
|
||||
if unsafe { !dc_open(&mut context, &args[1], None) } {
|
||||
println!("Error: Cannot open {}.", args[0],);
|
||||
}
|
||||
} else if args.len() != 1 {
|
||||
if args.len() < 2 {
|
||||
println!("Error: Bad arguments, expected [db-name].");
|
||||
return Err(format_err!("No db-name specified"));
|
||||
}
|
||||
let context = Context::new(
|
||||
Box::new(receive_event),
|
||||
"CLI".into(),
|
||||
Path::new(&args[1]).to_path_buf(),
|
||||
)?;
|
||||
|
||||
println!("Delta Chat Core is awaiting your commands.");
|
||||
|
||||
@@ -456,12 +426,6 @@ fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
|
||||
println!("history saved");
|
||||
{
|
||||
stop_threads(&ctx.read().unwrap());
|
||||
|
||||
unsafe {
|
||||
let mut ctx = ctx.write().unwrap();
|
||||
dc_close(&mut ctx);
|
||||
dc_context_unref(&mut ctx);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -494,19 +458,19 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
|
||||
if HANDLE.clone().lock().unwrap().is_some() {
|
||||
println!("smtp-jobs are already running in a thread.",);
|
||||
} else {
|
||||
dc_perform_smtp_jobs(&ctx.read().unwrap());
|
||||
perform_smtp_jobs(&ctx.read().unwrap());
|
||||
}
|
||||
}
|
||||
"imap-jobs" => {
|
||||
if HANDLE.clone().lock().unwrap().is_some() {
|
||||
println!("imap-jobs are already running in a thread.");
|
||||
} else {
|
||||
dc_perform_imap_jobs(&ctx.read().unwrap());
|
||||
perform_imap_jobs(&ctx.read().unwrap());
|
||||
}
|
||||
}
|
||||
"configure" => {
|
||||
start_threads(ctx.clone());
|
||||
dc_configure(&ctx.read().unwrap());
|
||||
configure(&ctx.read().unwrap());
|
||||
}
|
||||
"oauth2" => {
|
||||
if let Some(addr) = ctx.read().unwrap().get_config(config::Config::Addr) {
|
||||
@@ -530,33 +494,30 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
|
||||
}
|
||||
"getqr" | "getbadqr" => {
|
||||
start_threads(ctx.clone());
|
||||
let qrstr =
|
||||
dc_get_securejoin_qr(&ctx.read().unwrap(), arg1.parse().unwrap_or_default());
|
||||
if !qrstr.is_null() && 0 != *qrstr.offset(0isize) as libc::c_int {
|
||||
if arg0 == "getbadqr" && strlen(qrstr) > 40 {
|
||||
let mut i: libc::c_int = 12i32;
|
||||
while i < 22i32 {
|
||||
*qrstr.offset(i as isize) = '0' as i32 as libc::c_char;
|
||||
i += 1
|
||||
if let Some(mut qr) =
|
||||
dc_get_securejoin_qr(&ctx.read().unwrap(), arg1.parse().unwrap_or_default())
|
||||
{
|
||||
if !qr.is_empty() {
|
||||
if arg0 == "getbadqr" && qr.len() > 40 {
|
||||
qr.replace_range(12..22, "0000000000")
|
||||
}
|
||||
println!("{}", qr);
|
||||
let output = Command::new("qrencode")
|
||||
.args(&["-t", "ansiutf8", qr.as_str(), "-o", "-"])
|
||||
.output()
|
||||
.expect("failed to execute process");
|
||||
io::stdout().write_all(&output.stdout).unwrap();
|
||||
io::stderr().write_all(&output.stderr).unwrap();
|
||||
}
|
||||
println!("{}", to_string(qrstr as *const _));
|
||||
let syscmd = dc_mprintf(
|
||||
b"qrencode -t ansiutf8 \"%s\" -o -\x00" as *const u8 as *const libc::c_char,
|
||||
qrstr,
|
||||
);
|
||||
system(syscmd);
|
||||
free(syscmd as *mut libc::c_void);
|
||||
}
|
||||
free(qrstr as *mut libc::c_void);
|
||||
}
|
||||
"joinqr" => {
|
||||
start_threads(ctx.clone());
|
||||
if !arg0.is_empty() {
|
||||
dc_join_securejoin(&ctx.read().unwrap(), arg1_c);
|
||||
dc_join_securejoin(&ctx.read().unwrap(), arg1);
|
||||
}
|
||||
}
|
||||
"exit" => return Ok(ExitResult::Exit),
|
||||
"exit" | "quit" => return Ok(ExitResult::Exit),
|
||||
_ => dc_cmdline(&ctx.read().unwrap(), line)?,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,64 +1,62 @@
|
||||
extern crate deltachat;
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::{thread, time};
|
||||
use tempfile::tempdir;
|
||||
|
||||
use deltachat::chat;
|
||||
use deltachat::chatlist::*;
|
||||
use deltachat::config;
|
||||
use deltachat::constants::Event;
|
||||
use deltachat::configure::*;
|
||||
use deltachat::contact::*;
|
||||
use deltachat::context::*;
|
||||
use deltachat::dc_chat::*;
|
||||
use deltachat::dc_configure::*;
|
||||
use deltachat::dc_job::{
|
||||
dc_perform_imap_fetch, dc_perform_imap_idle, dc_perform_imap_jobs, dc_perform_smtp_idle,
|
||||
dc_perform_smtp_jobs,
|
||||
use deltachat::job::{
|
||||
perform_imap_fetch, perform_imap_idle, perform_imap_jobs, perform_smtp_idle, perform_smtp_jobs,
|
||||
};
|
||||
use deltachat::dc_lot::*;
|
||||
use deltachat::Event;
|
||||
|
||||
extern "C" fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> usize {
|
||||
println!("[{:?}]", event);
|
||||
fn cb(_ctx: &Context, event: Event) -> usize {
|
||||
print!("[{:?}]", event);
|
||||
|
||||
match event {
|
||||
Event::CONFIGURE_PROGRESS => {
|
||||
println!(" progress: {}", data1);
|
||||
Event::ConfigureProgress(progress) => {
|
||||
print!(" progress: {}\n", progress);
|
||||
0
|
||||
}
|
||||
Event::INFO | Event::WARNING | Event::ERROR | Event::ERROR_NETWORK => {
|
||||
println!(
|
||||
" {}",
|
||||
unsafe { CStr::from_ptr(data2 as *const _) }
|
||||
.to_str()
|
||||
.unwrap()
|
||||
);
|
||||
Event::Info(msg) | Event::Warning(msg) | Event::Error(msg) | Event::ErrorNetwork(msg) => {
|
||||
print!(" {}\n", msg);
|
||||
0
|
||||
}
|
||||
_ => {
|
||||
print!("\n");
|
||||
0
|
||||
}
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
unsafe {
|
||||
let ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
println!("creating database {:?}", dbfile);
|
||||
let ctx =
|
||||
Context::new(Box::new(cb), "FakeOs".into(), dbfile).expect("Failed to create context");
|
||||
let running = Arc::new(RwLock::new(true));
|
||||
let info = dc_get_info(&ctx);
|
||||
let info_s = CStr::from_ptr(info);
|
||||
let info = ctx.get_info();
|
||||
let duration = time::Duration::from_millis(4000);
|
||||
println!("info: {}", info_s.to_str().unwrap());
|
||||
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() {
|
||||
dc_perform_imap_jobs(&ctx1);
|
||||
perform_imap_jobs(&ctx1);
|
||||
if *r1.read().unwrap() {
|
||||
dc_perform_imap_fetch(&ctx1);
|
||||
perform_imap_fetch(&ctx1);
|
||||
|
||||
if *r1.read().unwrap() {
|
||||
dc_perform_imap_idle(&ctx1);
|
||||
perform_imap_idle(&ctx1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,20 +66,13 @@ fn main() {
|
||||
let r1 = running.clone();
|
||||
let t2 = thread::spawn(move || {
|
||||
while *r1.read().unwrap() {
|
||||
dc_perform_smtp_jobs(&ctx1);
|
||||
perform_smtp_jobs(&ctx1);
|
||||
if *r1.read().unwrap() {
|
||||
dc_perform_smtp_idle(&ctx1);
|
||||
perform_smtp_idle(&ctx1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
|
||||
println!("opening database {:?}", dbfile);
|
||||
|
||||
assert!(dc_open(&ctx, dbfile.to_str().unwrap(), None));
|
||||
|
||||
println!("configuring");
|
||||
let args = std::env::args().collect::<Vec<String>>();
|
||||
assert_eq!(args.len(), 2, "missing password");
|
||||
@@ -89,36 +80,24 @@ fn main() {
|
||||
ctx.set_config(config::Config::Addr, Some("d@testrun.org"))
|
||||
.unwrap();
|
||||
ctx.set_config(config::Config::MailPw, Some(&pw)).unwrap();
|
||||
dc_configure(&ctx);
|
||||
configure(&ctx);
|
||||
|
||||
thread::sleep(duration);
|
||||
|
||||
println!("sending a message");
|
||||
let contact_id =
|
||||
Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com").unwrap();
|
||||
let chat_id = dc_create_chat_by_contact_id(&ctx, contact_id);
|
||||
dc_send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into());
|
||||
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(0, std::ptr::null_mut());
|
||||
let text1 = dc_lot_get_text1(summary);
|
||||
let text2 = dc_lot_get_text2(summary);
|
||||
|
||||
let text1_s = if !text1.is_null() {
|
||||
Some(CStr::from_ptr(text1))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let text2_s = if !text2.is_null() {
|
||||
Some(CStr::from_ptr(text2))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
println!("chat: {} - {:?} - {:?}", i, text1_s, text2_s,);
|
||||
dc_lot_unref(summary);
|
||||
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);
|
||||
@@ -136,14 +115,13 @@ fn main() {
|
||||
println!("stopping threads");
|
||||
|
||||
*running.clone().write().unwrap() = false;
|
||||
deltachat::dc_job::dc_interrupt_imap_idle(&ctx);
|
||||
deltachat::dc_job::dc_interrupt_smtp_idle(&ctx);
|
||||
deltachat::job::interrupt_imap_idle(&ctx);
|
||||
deltachat::job::interrupt_smtp_idle(&ctx);
|
||||
|
||||
println!("joining");
|
||||
t1.join().unwrap();
|
||||
t2.join().unwrap();
|
||||
|
||||
println!("closing");
|
||||
dc_close(&ctx);
|
||||
}
|
||||
}
|
||||
|
||||
52
misc.c
52
misc.c
@@ -1,52 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "misc.h"
|
||||
|
||||
|
||||
static char* internal_dc_strdup(const char* s) /* strdup(NULL) is undefined, save_strdup(NULL) returns an empty string in this case */
|
||||
{
|
||||
char* ret = NULL;
|
||||
if (s) {
|
||||
if ((ret=strdup(s))==NULL) {
|
||||
exit(16); /* cannot allocate (little) memory, unrecoverable error */
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((ret=(char*)calloc(1, 1))==NULL) {
|
||||
exit(17); /* cannot allocate little memory, unrecoverable error */
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
char* dc_mprintf(const char* format, ...)
|
||||
{
|
||||
char testbuf[1];
|
||||
char* buf = NULL;
|
||||
int char_cnt_without_zero = 0;
|
||||
|
||||
va_list argp;
|
||||
va_list argp_copy;
|
||||
va_start(argp, format);
|
||||
va_copy(argp_copy, argp);
|
||||
|
||||
char_cnt_without_zero = vsnprintf(testbuf, 0, format, argp);
|
||||
va_end(argp);
|
||||
if (char_cnt_without_zero < 0) {
|
||||
va_end(argp_copy);
|
||||
return internal_dc_strdup("ErrFmt");
|
||||
}
|
||||
|
||||
buf = malloc(char_cnt_without_zero+2 /* +1 would be enough, however, protect against off-by-one-errors */);
|
||||
if (buf==NULL) {
|
||||
va_end(argp_copy);
|
||||
return internal_dc_strdup("ErrMem");
|
||||
}
|
||||
|
||||
vsnprintf(buf, char_cnt_without_zero+1, format, argp_copy);
|
||||
va_end(argp_copy);
|
||||
return buf;
|
||||
}
|
||||
1
misc.h
1
misc.h
@@ -1 +0,0 @@
|
||||
char* dc_mprintf (const char* format, ...); /* The result must be free()'d. */
|
||||
7
proptest-regressions/dc_strencode.txt
Normal file
7
proptest-regressions/dc_strencode.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
# Seeds for failure cases proptest has generated in the past. It is
|
||||
# automatically read and these particular cases re-run before any
|
||||
# novel cases are generated.
|
||||
#
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc 679506fe9ac59df773f8cfa800fdab5f0a32fe49d2ab370394000a1aa5bc2a72 # shrinks to buf = "%0A"
|
||||
9
proptest-regressions/dc_tools.txt
Normal file
9
proptest-regressions/dc_tools.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
# Seeds for failure cases proptest has generated in the past. It is
|
||||
# automatically read and these particular cases re-run before any
|
||||
# novel cases are generated.
|
||||
#
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc c310754465ee0261807b96fa9bcc4861ff9aa286e94667524b5960c69f9b6620 # shrinks to buf = "", approx_chars = 0, do_unwrap = false
|
||||
cc 5fd8d730b0a9cdf7308ce58818ca9aefc0255c9ba2a0878944fc48d43a67315b # shrinks to buf = "𑒀ὐ¢🜀\u{1e01b}A a🟠", approx_chars = 0, do_unwrap = false
|
||||
cc c6a0029a54137a4b9efc9ef2ea6d9a7dd1d60d1c937bb472b66a174618ba8013 # shrinks to buf = "𐠈0Aᝮa𫝀®!ꫛa¡0A𐢧00𐹠®A 丽ⷐએ ", approx_chars = 0, do_unwrap = false
|
||||
@@ -15,7 +15,7 @@ without any "build-from-source" steps.
|
||||
1. `Install virtualenv <https://virtualenv.pypa.io/en/stable/installation/>`_,
|
||||
then create a fresh python environment and activate it in your shell::
|
||||
|
||||
virtualenv -p python3 venv
|
||||
virtualenv venv # or: python -m venv
|
||||
source venv/bin/activate
|
||||
|
||||
Afterwards, invoking ``python`` or ``pip install`` will only
|
||||
@@ -39,6 +39,12 @@ and push them to a python package index. To install the latest github ``master``
|
||||
|
||||
pip install -i https://m.devpi.net/dc/master deltachat
|
||||
|
||||
.. note::
|
||||
|
||||
If you can help to automate the building of wheels for Mac or Windows,
|
||||
that'd be much appreciated! please then get
|
||||
`in contact with us <https://delta.chat/en/contribute>`_.
|
||||
|
||||
|
||||
Installing bindings from source
|
||||
===============================
|
||||
@@ -48,34 +54,55 @@ to core deltachat library::
|
||||
|
||||
git clone https://github.com/deltachat/deltachat-core-rust
|
||||
cd deltachat-core-rust
|
||||
cargo build -p deltachat_ffi --release
|
||||
|
||||
This will result in a ``libdeltachat.so`` and ``libdeltachat.a`` files
|
||||
in the ``target/release`` directory. These files are needed for
|
||||
creating the python bindings for deltachat::
|
||||
|
||||
cd python
|
||||
DCC_RS_DEV=`pwd`/.. pip install -e .
|
||||
|
||||
Now test if the bindings find the correct library::
|
||||
If you don't have one active, create and activate a python "virtualenv":
|
||||
|
||||
python -c 'import deltachat ; print(deltachat.__version__)'
|
||||
python virtualenv venv # or python -m venv
|
||||
source venv/bin/activate
|
||||
|
||||
This should print your deltachat bindings version.
|
||||
Afterwards ``which python`` tells you that it comes out of the "venv"
|
||||
directory that contains all python install artifacts. Let's first
|
||||
install test tools::
|
||||
|
||||
pip install pytest pytest-timeout pytest-rerunfailures requests
|
||||
|
||||
then cargo-build and install the deltachat bindings::
|
||||
|
||||
python install_python_bindings.py
|
||||
|
||||
The bindings will be installed in release mode but with debug symbols.
|
||||
The release mode is necessary because some tests generate RSA keys
|
||||
which is prohibitively slow in debug mode.
|
||||
|
||||
After successful binding installation you can finally run the tests::
|
||||
|
||||
pytest -v tests
|
||||
|
||||
.. note::
|
||||
|
||||
If you can help to automate the building of wheels for Mac or Windows,
|
||||
that'd be much appreciated! please then get
|
||||
`in contact with us <https://delta.chat/en/contribute>`_.
|
||||
Some tests are sometimes failing/hanging because of
|
||||
https://github.com/deltachat/deltachat-core-rust/issues/331
|
||||
and
|
||||
https://github.com/deltachat/deltachat-core-rust/issues/326
|
||||
|
||||
Using a system-installed deltachat-core-rust
|
||||
--------------------------------------------
|
||||
|
||||
When calling ``pip`` without specifying the ``DCC_RS_DEV`` environment
|
||||
variable cffi will try to use a ``deltachat.h`` from a system location
|
||||
like ``/usr/local/include`` and will try to dynamically link against a
|
||||
``libdeltachat.so`` in a similar location (e.g. ``/usr/local/lib``).
|
||||
running "live" tests (experimental)
|
||||
-----------------------------------
|
||||
|
||||
If you want to run "liveconfig" functional tests you can set
|
||||
``DCC_PY_LIVECONFIG`` to:
|
||||
|
||||
- a particular https-url that you can ask for from the delta
|
||||
chat devs.
|
||||
|
||||
- or the path of a file that contains two lines, each describing
|
||||
via "addr=... mail_pwd=..." a test account login that will
|
||||
be used for the live tests.
|
||||
|
||||
With ``DCC_PY_LIVECONFIG`` set pytest invocations will use real
|
||||
e-mail accounts and run through all functional "liveconfig" tests.
|
||||
|
||||
|
||||
|
||||
Code examples
|
||||
@@ -84,68 +111,34 @@ Code examples
|
||||
You may look at `examples <https://py.delta.chat/examples.html>`_.
|
||||
|
||||
|
||||
Running tests
|
||||
=============
|
||||
|
||||
Get a checkout of the `deltachat-core-rust github repository`_ and type::
|
||||
|
||||
pip install tox
|
||||
./run-integration-tests.sh
|
||||
|
||||
If you want to run functional tests with real
|
||||
e-mail test accounts, generate a "liveconfig" file where each
|
||||
lines contains test account settings, for example::
|
||||
|
||||
# 'liveconfig' file specifying imap/smtp accounts
|
||||
addr=some-email@example.org mail_pw=password
|
||||
addr=other-email@example.org mail_pw=otherpassword
|
||||
|
||||
The "keyword=value" style allows to specify any
|
||||
`deltachat account config setting <https://c.delta.chat/classdc__context__t.html#aff3b894f6cfca46cab5248fdffdf083d>`_ so you can also specify smtp or imap servers, ports, ssl modes etc.
|
||||
Typically DC's automatic configuration allows to not specify these settings.
|
||||
|
||||
The ``run-integration-tests.sh`` script will automatically use
|
||||
``python/liveconfig`` if it exists, to manually run tests with this
|
||||
``liveconfig`` file use::
|
||||
|
||||
tox -- --liveconfig liveconfig
|
||||
|
||||
|
||||
.. _`deltachat-core-rust github repository`: https://github.com/deltachat/deltachat-core-rust
|
||||
.. _`deltachat-core`: https://github.com/deltachat/deltachat-core-rust
|
||||
|
||||
Running test using a debug build
|
||||
--------------------------------
|
||||
|
||||
If you need to examine e.g. a coredump you may want to run the tests
|
||||
using a debug build::
|
||||
|
||||
DCC_RS_TARGET=debug ./run-integration-tests.sh -e py37 -- -x -v -k failing_test
|
||||
|
||||
|
||||
Building manylinux1 wheels
|
||||
==========================
|
||||
|
||||
.. note::
|
||||
|
||||
This section may not fully work.
|
||||
|
||||
Building portable manylinux1 wheels which come with libdeltachat.so
|
||||
and all it's dependencies is easy using the provided docker tooling.
|
||||
|
||||
using docker pull / premade images
|
||||
------------------------------------
|
||||
|
||||
We publish a build environment under the ``deltachat/wheel`` tag so
|
||||
We publish a build environment under the ``deltachat/coredeps`` tag so
|
||||
that you can pull it from the ``hub.docker.com`` site's "deltachat"
|
||||
organization::
|
||||
|
||||
$ docker pull deltachat/wheel
|
||||
$ docker pull deltachat/coredeps
|
||||
|
||||
The ``deltachat/wheel`` image can be used to build both libdeltachat.so
|
||||
and the Python wheels::
|
||||
This docker image can be used to run tests and build Python wheels for all interpreters::
|
||||
|
||||
$ docker run --rm -it -v $(pwd):/io/ deltachat/wheel /io/python/wheelbuilder/build-wheels.sh
|
||||
$ bash ci_scripts/ci_run.sh
|
||||
|
||||
This command runs a script within the image, after mounting ``$(pwd)`` as ``/io`` within
|
||||
the docker image. The script is specified as a path within the docker image's filesystem.
|
||||
The resulting wheel files will be in ``python/wheelhouse``.
|
||||
This command runs tests and build-wheel scripts in a docker container.
|
||||
|
||||
|
||||
Optionally build your own docker image
|
||||
@@ -154,10 +147,10 @@ Optionally build your own docker image
|
||||
If you want to build your own custom docker image you can do this::
|
||||
|
||||
$ cd deltachat-core # cd to deltachat-core checkout directory
|
||||
$ docker build -t deltachat/wheel python/wheelbuilder/
|
||||
$ docker build -t deltachat/coredeps ci_scripts/docker_coredeps
|
||||
|
||||
This will use the ``python/wheelbuilder/Dockerfile`` to build
|
||||
up docker image called ``deltachat/wheel``. You can afterwards
|
||||
This will use the ``ci_scripts/docker_coredeps/Dockerfile`` to build
|
||||
up docker image called ``deltachat/coredeps``. You can afterwards
|
||||
find it with::
|
||||
|
||||
$ docker images
|
||||
|
||||
@@ -37,16 +37,3 @@ Message
|
||||
.. autoclass:: deltachat.message.Message
|
||||
:members:
|
||||
|
||||
MessageType
|
||||
------------
|
||||
|
||||
.. autoclass:: deltachat.message.MessageType
|
||||
:members:
|
||||
|
||||
MessageState
|
||||
------------
|
||||
|
||||
.. autoclass:: deltachat.message.MessageState
|
||||
:members:
|
||||
|
||||
|
||||
|
||||
@@ -8,8 +8,7 @@ Playing around on the commandline
|
||||
----------------------------------
|
||||
|
||||
Once you have :doc:`installed deltachat bindings <install>`
|
||||
you can start playing from the python interpreter commandline::
|
||||
|
||||
you can start playing from the python interpreter commandline.
|
||||
For example you can type ``python`` and then::
|
||||
|
||||
# instantiate and configure deltachat account
|
||||
@@ -23,7 +22,7 @@ For example you can type ``python`` and then::
|
||||
# create a contact and send a message
|
||||
contact = ac.create_contact("someother@email.address")
|
||||
chat = ac.create_chat_by_contact(contact)
|
||||
chat.send_text_message("hi from the python interpreter command line")
|
||||
chat.send_text("hi from the python interpreter command line")
|
||||
|
||||
Checkout our :doc:`api` for the various high-level things you can do
|
||||
to send/receive messages, create contacts and chats.
|
||||
|
||||
@@ -6,29 +6,18 @@
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ["DCC_RS_TARGET"] = target = "release"
|
||||
if "DCC_RS_DEV" not in os.environ:
|
||||
dn = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
os.environ["DCC_RS_DEV"] = dn
|
||||
|
||||
toml = os.path.join(os.getcwd(), "..", "Cargo.toml")
|
||||
assert os.path.exists(toml)
|
||||
with open(toml) as f:
|
||||
s = orig = f.read()
|
||||
s += "\n"
|
||||
s += "[profile.release]\n"
|
||||
s += "debug = true\n"
|
||||
with open(toml, "w") as f:
|
||||
f.write(s)
|
||||
print("temporarily modifying Cargo.toml to provide release build with debug symbols ")
|
||||
try:
|
||||
subprocess.check_call([
|
||||
"cargo", "build", "-p", "deltachat_ffi", "--" + target
|
||||
])
|
||||
finally:
|
||||
with open(toml, "w") as f:
|
||||
f.write(orig)
|
||||
print("\nreseted Cargo.toml to previous original state")
|
||||
|
||||
os.environ["RUSTFLAGS"] = "-g"
|
||||
subprocess.check_call([
|
||||
"cargo", "build", "-p", "deltachat_ffi", "--" + target
|
||||
])
|
||||
subprocess.check_call("rm -rf build/ src/deltachat/*.so" , shell=True)
|
||||
|
||||
subprocess.check_call([
|
||||
|
||||
@@ -34,13 +34,14 @@ def py_dc_callback(ctx, evt, data1, data2):
|
||||
if data1 and event_sig_types & 1:
|
||||
data1 = ffi.string(ffi.cast('char*', data1)).decode("utf8")
|
||||
if data2 and event_sig_types & 2:
|
||||
data2 = ffi.string(ffi.cast('char*', data2)).decode("utf8")
|
||||
try:
|
||||
data2 = ffi.string(ffi.cast('char*', data2)).decode("utf8")
|
||||
if isinstance(data2, bytes):
|
||||
data2 = data2.decode("utf8")
|
||||
except UnicodeDecodeError:
|
||||
# XXX ignoring this error is not quite correct but for now
|
||||
# XXX ignoring the decode error is not quite correct but for now
|
||||
# i don't want to hunt down encoding problems in the c lib
|
||||
data2 = ffi.string(ffi.cast('char*', data2))
|
||||
|
||||
pass
|
||||
try:
|
||||
ret = callback(ctx, evt_name, data1, data2)
|
||||
if ret is None:
|
||||
|
||||
@@ -6,10 +6,15 @@ import platform
|
||||
import os
|
||||
import cffi
|
||||
import shutil
|
||||
from os.path import dirname as dn
|
||||
from os.path import abspath
|
||||
|
||||
|
||||
def ffibuilder():
|
||||
projdir = os.environ.get('DCC_RS_DEV')
|
||||
if not projdir:
|
||||
p = dn(dn(dn(dn(abspath(__file__)))))
|
||||
projdir = os.environ["DCC_RS_DEV"] = p
|
||||
target = os.environ.get('DCC_RS_TARGET', 'release')
|
||||
if projdir:
|
||||
if platform.system() == 'Darwin':
|
||||
|
||||
@@ -14,7 +14,7 @@ except ImportError:
|
||||
import deltachat
|
||||
from . import const
|
||||
from .capi import ffi, lib
|
||||
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
|
||||
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array, DCLot
|
||||
from .chatting import Contact, Chat, Message
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ class Account(object):
|
||||
whose name or e-mail matches query.
|
||||
:param only_verified: if true only return verified contacts.
|
||||
:param with_self: if true the self-contact is also returned.
|
||||
:returns: list of :class:`deltachat.message.Message` objects.
|
||||
:returns: list of :class:`deltachat.chatting.Contact` objects.
|
||||
"""
|
||||
flags = 0
|
||||
query = as_dc_charpointer(query)
|
||||
@@ -328,6 +328,56 @@ class Account(object):
|
||||
raise RuntimeError("could not send out autocrypt setup message")
|
||||
return from_dc_charpointer(res)
|
||||
|
||||
def get_setup_contact_qr(self):
|
||||
""" get/create Setup-Contact QR Code as ascii-string.
|
||||
|
||||
this string needs to be transferred to another DC account
|
||||
in a second channel (typically used by mobiles with QRcode-show + scan UX)
|
||||
where qr_setup_contact(qr) is called.
|
||||
"""
|
||||
res = lib.dc_get_securejoin_qr(self._dc_context, 0)
|
||||
return from_dc_charpointer(res)
|
||||
|
||||
def check_qr(self, qr):
|
||||
""" check qr code and return :class:`ScannedQRCode` instance representing the result"""
|
||||
res = ffi.gc(
|
||||
lib.dc_check_qr(self._dc_context, as_dc_charpointer(qr)),
|
||||
lib.dc_lot_unref
|
||||
)
|
||||
lot = DCLot(res)
|
||||
if lot.state() == const.DC_QR_ERROR:
|
||||
raise ValueError("invalid or unknown QR code: {}".format(lot.text1()))
|
||||
return ScannedQRCode(lot)
|
||||
|
||||
def qr_setup_contact(self, qr):
|
||||
""" setup contact and return a Chat after contact is established.
|
||||
|
||||
Note that this function may block for a long time as messages are exchanged
|
||||
with the emitter of the QR code. On success a :class:`deltachat.chatting.Chat` instance
|
||||
is returned.
|
||||
:param qr: valid "setup contact" QR code (all other QR codes will result in an exception)
|
||||
"""
|
||||
assert self.check_qr(qr).is_ask_verifycontact()
|
||||
chat_id = lib.dc_join_securejoin(self._dc_context, as_dc_charpointer(qr))
|
||||
if chat_id == 0:
|
||||
raise ValueError("could not setup secure contact")
|
||||
return Chat(self, chat_id)
|
||||
|
||||
def qr_join_chat(self, qr):
|
||||
""" join a chat group through a QR code.
|
||||
|
||||
Note that this function may block for a long time as messages are exchanged
|
||||
with the emitter of the QR code. On success a :class:`deltachat.chatting.Chat` instance
|
||||
is returned which is the chat that we just joined.
|
||||
|
||||
:param qr: valid "join-group" QR code (all other QR codes will result in an exception)
|
||||
"""
|
||||
assert self.check_qr(qr).is_ask_verifygroup()
|
||||
chat_id = lib.dc_join_securejoin(self._dc_context, as_dc_charpointer(qr))
|
||||
if chat_id == 0:
|
||||
raise ValueError("could not join group")
|
||||
return Chat(self, chat_id)
|
||||
|
||||
def start_threads(self):
|
||||
""" start IMAP/SMTP threads (and configure account if it hasn't happened).
|
||||
|
||||
@@ -346,7 +396,8 @@ class Account(object):
|
||||
def shutdown(self, wait=True):
|
||||
""" stop threads and close and remove underlying dc_context and callbacks. """
|
||||
if hasattr(self, "_dc_context") and hasattr(self, "_threads"):
|
||||
self.stop_threads(wait=False) # to interrupt idle and tell python threads to stop
|
||||
# print("SHUTDOWN", self)
|
||||
self.stop_threads(wait=False)
|
||||
lib.dc_close(self._dc_context)
|
||||
self.stop_threads(wait=wait) # to wait for threads
|
||||
deltachat.clear_context_callback(self._dc_context)
|
||||
@@ -432,6 +483,10 @@ class EventLogger:
|
||||
def set_timeout(self, timeout):
|
||||
self._timeout = timeout
|
||||
|
||||
def consume_events(self, check_error=True):
|
||||
while not self._event_queue.empty():
|
||||
self.get()
|
||||
|
||||
def get(self, timeout=None, check_error=True):
|
||||
timeout = timeout or self._timeout
|
||||
ev = self._event_queue.get(timeout=timeout)
|
||||
@@ -491,3 +546,18 @@ def _destroy_dc_context(dc_context, dc_context_unref=lib.dc_context_unref):
|
||||
# we are deep into Python Interpreter shutdown,
|
||||
# so no need to clear the callback context mapping.
|
||||
pass
|
||||
|
||||
|
||||
class ScannedQRCode:
|
||||
def __init__(self, dc_lot):
|
||||
self._dc_lot = dc_lot
|
||||
|
||||
def is_ask_verifycontact(self):
|
||||
return self._dc_lot.state() == const.DC_QR_ASK_VERIFYCONTACT
|
||||
|
||||
def is_ask_verifygroup(self):
|
||||
return self._dc_lot.state() == const.DC_QR_ASK_VERIFYGROUP
|
||||
|
||||
@property
|
||||
def contact_id(self):
|
||||
return self._dc_lot.id()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
""" chatting related objects: Contact, Chat, Message. """
|
||||
|
||||
import mimetypes
|
||||
import os
|
||||
from . import props
|
||||
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
|
||||
from .capi import lib, ffi
|
||||
@@ -131,6 +132,16 @@ class Chat(object):
|
||||
"""
|
||||
return lib.dc_chat_get_type(self._dc_chat)
|
||||
|
||||
def get_join_qr(self):
|
||||
""" get/create Join-Group QR Code as ascii-string.
|
||||
|
||||
this string needs to be transferred to another DC account
|
||||
in a second channel (typically used by mobiles with QRcode-show + scan UX)
|
||||
where account.join_with_qrcode(qr) needs to be called.
|
||||
"""
|
||||
res = lib.dc_get_securejoin_qr(self._dc_context, self.id)
|
||||
return from_dc_charpointer(res)
|
||||
|
||||
# ------ chat messaging API ------------------------------
|
||||
|
||||
def send_text(self, text):
|
||||
@@ -294,7 +305,6 @@ class Chat(object):
|
||||
def get_contacts(self):
|
||||
""" get all contacts for this chat.
|
||||
:params: contact object.
|
||||
:raises ValueError: if contact could not be added
|
||||
:returns: list of :class:`deltachat.chatting.Contact` objects for this chat
|
||||
|
||||
"""
|
||||
@@ -305,3 +315,46 @@ class Chat(object):
|
||||
return list(iter_array(
|
||||
dc_array, lambda id: Contact(self._dc_context, id))
|
||||
)
|
||||
|
||||
def set_profile_image(self, img_path):
|
||||
"""Set group profile image.
|
||||
|
||||
If the group is already promoted (any message was sent to the group),
|
||||
all group members are informed by a special status message that is sent
|
||||
automatically by this function.
|
||||
:params img_path: path to image object
|
||||
:raises ValueError: if profile image could not be set
|
||||
:returns: None
|
||||
"""
|
||||
assert os.path.exists(img_path), img_path
|
||||
p = as_dc_charpointer(img_path)
|
||||
res = lib.dc_set_chat_profile_image(self._dc_context, self.id, p)
|
||||
if res != 1:
|
||||
raise ValueError("Setting Profile Image {!r} failed".format(p))
|
||||
|
||||
def remove_profile_image(self):
|
||||
"""remove group profile image.
|
||||
|
||||
If the group is already promoted (any message was sent to the group),
|
||||
all group members are informed by a special status message that is sent
|
||||
automatically by this function.
|
||||
:raises ValueError: if profile image could not be reset
|
||||
:returns: None
|
||||
"""
|
||||
res = lib.dc_set_chat_profile_image(self._dc_context, self.id, ffi.NULL)
|
||||
if res != 1:
|
||||
raise ValueError("Removing Profile Image failed")
|
||||
|
||||
def get_profile_image(self):
|
||||
"""Get group profile image.
|
||||
|
||||
For groups, this is the image set by any group member using
|
||||
set_chat_profile_image(). For normal chats, this is the image
|
||||
set by each remote user on their own using dc_set_config(context,
|
||||
"selfavatar", image).
|
||||
:returns: path to profile image, None if no profile image exists.
|
||||
"""
|
||||
dc_res = lib.dc_chat_get_profile_image(self._dc_chat)
|
||||
if dc_res == ffi.NULL:
|
||||
return None
|
||||
return from_dc_charpointer(dc_res)
|
||||
|
||||
@@ -8,11 +8,23 @@ from os.path import join as joinpath
|
||||
# this works well when you in a git-checkout
|
||||
# run "python deltachat/const.py" to regenerate events
|
||||
# begin const generated
|
||||
DC_PROVIDER_STATUS_OK = 1
|
||||
DC_PROVIDER_STATUS_PREPARATION = 2
|
||||
DC_PROVIDER_STATUS_BROKEN = 3
|
||||
DC_GCL_ARCHIVED_ONLY = 0x01
|
||||
DC_GCL_NO_SPECIALS = 0x02
|
||||
DC_GCL_ADD_ALLDONE_HINT = 0x04
|
||||
DC_GCL_VERIFIED_ONLY = 0x01
|
||||
DC_GCL_ADD_SELF = 0x02
|
||||
DC_QR_ASK_VERIFYCONTACT = 200
|
||||
DC_QR_ASK_VERIFYGROUP = 202
|
||||
DC_QR_FPR_OK = 210
|
||||
DC_QR_FPR_MISMATCH = 220
|
||||
DC_QR_FPR_WITHOUT_ADDR = 230
|
||||
DC_QR_ADDR = 320
|
||||
DC_QR_TEXT = 330
|
||||
DC_QR_URL = 332
|
||||
DC_QR_ERROR = 400
|
||||
DC_CHAT_ID_DEADDROP = 1
|
||||
DC_CHAT_ID_TRASH = 3
|
||||
DC_CHAT_ID_MSGS_IN_CREATION = 4
|
||||
@@ -69,15 +81,14 @@ DC_EVENT_IMEX_FILE_WRITTEN = 2052
|
||||
DC_EVENT_SECUREJOIN_INVITER_PROGRESS = 2060
|
||||
DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061
|
||||
DC_EVENT_GET_STRING = 2091
|
||||
DC_EVENT_HTTP_GET = 2100
|
||||
DC_EVENT_HTTP_POST = 2110
|
||||
DC_EVENT_FILE_COPIED = 2055
|
||||
DC_EVENT_IS_OFFLINE = 2081
|
||||
# end const generated
|
||||
|
||||
|
||||
def read_event_defines(f):
|
||||
rex = re.compile(r'#define\s+((?:DC_EVENT_|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_STATE_|'
|
||||
r'DC_CONTACT_ID_|DC_GCL|DC_CHAT|DC_PROVIDER)\S+)\s+([x\d]+).*')
|
||||
for line in f:
|
||||
m = rex.match(line)
|
||||
if m:
|
||||
@@ -90,7 +101,7 @@ if __name__ == "__main__":
|
||||
if len(sys.argv) >= 2:
|
||||
deltah = sys.argv[1]
|
||||
else:
|
||||
deltah = joinpath(dirname(dirname(dirname(here_dir))), "src", "deltachat.h")
|
||||
deltah = joinpath(dirname(dirname(dirname(here_dir))), "deltachat-ffi", "deltachat.h")
|
||||
assert os.path.exists(deltah)
|
||||
|
||||
lines = []
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from .capi import lib
|
||||
from .capi import ffi
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def as_dc_charpointer(obj):
|
||||
@@ -16,4 +17,30 @@ def iter_array(dc_array_t, constructor):
|
||||
|
||||
|
||||
def from_dc_charpointer(obj):
|
||||
return ffi.string(obj).decode("utf8")
|
||||
return ffi.string(ffi.gc(obj, lib.dc_str_unref)).decode("utf8")
|
||||
|
||||
|
||||
class DCLot:
|
||||
def __init__(self, dc_lot):
|
||||
self._dc_lot = dc_lot
|
||||
|
||||
def id(self):
|
||||
return lib.dc_lot_get_id(self._dc_lot)
|
||||
|
||||
def state(self):
|
||||
return lib.dc_lot_get_state(self._dc_lot)
|
||||
|
||||
def text1(self):
|
||||
return from_dc_charpointer(lib.dc_lot_get_text1(self._dc_lot))
|
||||
|
||||
def text1_meaning(self):
|
||||
return lib.dc_lot_get_text1_meaning(self._dc_lot)
|
||||
|
||||
def text2(self):
|
||||
return from_dc_charpointer(lib.dc_lot_get_text2(self._dc_lot))
|
||||
|
||||
def timestamp(self):
|
||||
ts = lib.dc_lot_get_timestamp(self._dc_lot)
|
||||
if ts == 0:
|
||||
return None
|
||||
return datetime.utcfromtimestamp(ts)
|
||||
|
||||
@@ -110,7 +110,13 @@ class Message(object):
|
||||
|
||||
def continue_key_transfer(self, setup_code):
|
||||
""" extract key and use it as primary key for this account. """
|
||||
lib.dc_continue_key_transfer(self._dc_context, self.id, as_dc_charpointer(setup_code))
|
||||
res = lib.dc_continue_key_transfer(
|
||||
self._dc_context,
|
||||
self.id,
|
||||
as_dc_charpointer(setup_code)
|
||||
)
|
||||
if res == 0:
|
||||
raise ValueError("could not decrypt")
|
||||
|
||||
@props.with_doc
|
||||
def time_sent(self):
|
||||
@@ -142,7 +148,7 @@ class Message(object):
|
||||
import email.parser
|
||||
mime_headers = lib.dc_get_mime_headers(self._dc_context, self.id)
|
||||
if mime_headers:
|
||||
s = ffi.string(mime_headers)
|
||||
s = ffi.string(ffi.gc(mime_headers, lib.dc_str_unref))
|
||||
if isinstance(s, bytes):
|
||||
s = s.decode("ascii")
|
||||
return email.message_from_string(s)
|
||||
|
||||
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)
|
||||
@@ -1,9 +1,9 @@
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import pytest
|
||||
import requests
|
||||
import time
|
||||
from deltachat import Account
|
||||
from deltachat import props
|
||||
from deltachat.capi import lib
|
||||
import tempfile
|
||||
|
||||
@@ -24,18 +24,9 @@ def pytest_configure(config):
|
||||
config.option.liveconfig = cfg
|
||||
|
||||
|
||||
@pytest.hookimpl(trylast=True)
|
||||
def pytest_runtest_call(item):
|
||||
# perform early finalization because we otherwise get cloberred
|
||||
# output from concurrent threads printing between execution
|
||||
# of the test function and the teardown phase of that test function
|
||||
if "acfactory" in item.funcargs:
|
||||
print("*"*30, "finalizing", "*"*30)
|
||||
acfactory = item.funcargs["acfactory"]
|
||||
acfactory.finalize()
|
||||
|
||||
|
||||
def pytest_report_header(config, startdir):
|
||||
summary = []
|
||||
|
||||
t = tempfile.mktemp()
|
||||
try:
|
||||
ac = Account(t, eventlogging=False)
|
||||
@@ -43,13 +34,18 @@ def pytest_report_header(config, startdir):
|
||||
ac.shutdown()
|
||||
finally:
|
||||
os.remove(t)
|
||||
summary = ['Deltachat core={} sqlite={}'.format(
|
||||
summary.extend(['Deltachat core={} sqlite={}'.format(
|
||||
info['deltachat_core_version'],
|
||||
info['sqlite_version'],
|
||||
)]
|
||||
cfg = config.getoption('--liveconfig')
|
||||
)])
|
||||
|
||||
cfg = config.option.liveconfig
|
||||
if cfg:
|
||||
summary.append('Liveconfig: {}'.format(os.path.abspath(cfg)))
|
||||
if "#" in cfg:
|
||||
url, token = cfg.split("#", 1)
|
||||
summary.append('Liveconfig provider: {}#<token ommitted>'.format(url))
|
||||
else:
|
||||
summary.append('Liveconfig file: {}'.format(cfg))
|
||||
return summary
|
||||
|
||||
|
||||
@@ -66,9 +62,56 @@ def data():
|
||||
return Data()
|
||||
|
||||
|
||||
class SessionLiveConfigFromFile:
|
||||
def __init__(self, fn):
|
||||
self.fn = fn
|
||||
self.configlist = []
|
||||
for line in open(fn):
|
||||
if line.strip() and not line.strip().startswith('#'):
|
||||
d = {}
|
||||
for part in line.split():
|
||||
name, value = part.split("=")
|
||||
d[name] = value
|
||||
self.configlist.append(d)
|
||||
|
||||
def get(self, index):
|
||||
return self.configlist[index]
|
||||
|
||||
def exists(self):
|
||||
return bool(self.configlist)
|
||||
|
||||
|
||||
class SessionLiveConfigFromURL:
|
||||
def __init__(self, url, create_token):
|
||||
self.configlist = []
|
||||
for i in range(2):
|
||||
res = requests.post(url, json={"token_create_user": int(create_token)})
|
||||
if res.status_code != 200:
|
||||
pytest.skip("creating newtmpuser failed {!r}".format(res))
|
||||
d = res.json()
|
||||
config = dict(addr=d["email"], mail_pw=d["password"])
|
||||
self.configlist.append(config)
|
||||
|
||||
def get(self, index):
|
||||
return self.configlist[index]
|
||||
|
||||
def exists(self):
|
||||
return bool(self.configlist)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def session_liveconfig(request):
|
||||
liveconfig_opt = request.config.option.liveconfig
|
||||
if liveconfig_opt:
|
||||
if liveconfig_opt.startswith("http"):
|
||||
url, create_token = liveconfig_opt.split("#", 1)
|
||||
return SessionLiveConfigFromURL(url, create_token)
|
||||
else:
|
||||
return SessionLiveConfigFromFile(liveconfig_opt)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def acfactory(pytestconfig, tmpdir, request):
|
||||
fn = pytestconfig.getoption("--liveconfig")
|
||||
def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
|
||||
|
||||
class AccountMaker:
|
||||
def __init__(self):
|
||||
@@ -82,25 +125,17 @@ def acfactory(pytestconfig, tmpdir, request):
|
||||
fin = self._finalizers.pop()
|
||||
fin()
|
||||
|
||||
@props.cached
|
||||
def configlist(self):
|
||||
configlist = []
|
||||
for line in open(fn):
|
||||
if line.strip() and not line.strip().startswith('#'):
|
||||
d = {}
|
||||
for part in line.split():
|
||||
name, value = part.split("=")
|
||||
d[name] = value
|
||||
configlist.append(d)
|
||||
return configlist
|
||||
def make_account(self, path, logid):
|
||||
ac = Account(path, logid=logid)
|
||||
self._finalizers.append(ac.shutdown)
|
||||
return ac
|
||||
|
||||
def get_unconfigured_account(self):
|
||||
self.offline_count += 1
|
||||
tmpdb = tmpdir.join("offlinedb%d" % self.offline_count)
|
||||
ac = Account(tmpdb.strpath, logid="ac{}".format(self.offline_count))
|
||||
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.offline_count))
|
||||
ac._evlogger.init_time = self.init_time
|
||||
ac._evlogger.set_timeout(2)
|
||||
self._finalizers.append(ac.shutdown)
|
||||
return ac
|
||||
|
||||
def get_configured_offline_account(self):
|
||||
@@ -116,33 +151,42 @@ def acfactory(pytestconfig, tmpdir, request):
|
||||
return ac
|
||||
|
||||
def get_online_configuring_account(self):
|
||||
if not fn:
|
||||
pytest.skip("specify a --liveconfig file to run tests with real accounts")
|
||||
if not session_liveconfig:
|
||||
pytest.skip("specify DCC_PY_LIVECONFIG or --liveconfig")
|
||||
configdict = session_liveconfig.get(self.live_count)
|
||||
self.live_count += 1
|
||||
configdict = self.configlist.pop(0)
|
||||
if "e2ee_enabled" not in configdict:
|
||||
configdict["e2ee_enabled"] = "1"
|
||||
tmpdb = tmpdir.join("livedb%d" % self.live_count)
|
||||
ac = 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.set_timeout(30)
|
||||
ac.configure(**configdict)
|
||||
ac.start_threads()
|
||||
self._finalizers.append(ac.shutdown)
|
||||
return ac
|
||||
|
||||
def get_two_online_accounts(self):
|
||||
ac1 = self.get_online_configuring_account()
|
||||
ac2 = self.get_online_configuring_account()
|
||||
wait_successful_IMAP_SMTP_connection(ac1)
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
wait_successful_IMAP_SMTP_connection(ac2)
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
return ac1, ac2
|
||||
|
||||
def clone_online_account(self, account):
|
||||
self.live_count += 1
|
||||
tmpdb = tmpdir.join("livedb%d" % self.live_count)
|
||||
ac = 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.set_timeout(30)
|
||||
ac.configure(addr=account.get_config("addr"), mail_pw=account.get_config("mail_pw"))
|
||||
ac.start_threads()
|
||||
self._finalizers.append(ac.shutdown)
|
||||
return ac
|
||||
|
||||
return AccountMaker()
|
||||
am = AccountMaker()
|
||||
request.addfinalizer(am.finalize)
|
||||
return am
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -171,6 +215,15 @@ def wait_configuration_progress(account, target):
|
||||
break
|
||||
|
||||
|
||||
def wait_securejoin_inviter_progress(account, target):
|
||||
while 1:
|
||||
evt_name, data1, data2 = \
|
||||
account._evlogger.get_matching("DC_EVENT_SECUREJOIN_INVITER_PROGRESS")
|
||||
if data2 >= target:
|
||||
print("** SECUREJOINT-INVITER PROGRESS {}".format(target), account)
|
||||
break
|
||||
|
||||
|
||||
def wait_successful_IMAP_SMTP_connection(account):
|
||||
imap_ok = smtp_ok = False
|
||||
while not imap_ok or not smtp_ok:
|
||||
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
from deltachat import const, Account
|
||||
from deltachat.message import Message
|
||||
from datetime import datetime, timedelta
|
||||
from conftest import wait_configuration_progress, wait_successful_IMAP_SMTP_connection
|
||||
from conftest import wait_configuration_progress, wait_successful_IMAP_SMTP_connection, wait_securejoin_inviter_progress
|
||||
|
||||
|
||||
class TestOfflineAccountBasic:
|
||||
@@ -140,6 +140,22 @@ class TestOfflineChat:
|
||||
chat.set_name("title2")
|
||||
assert chat.get_name() == "title2"
|
||||
|
||||
@pytest.mark.parametrize("verified", [True, False])
|
||||
def test_group_chat_qr(self, acfactory, ac1, verified):
|
||||
ac2 = acfactory.get_configured_offline_account()
|
||||
chat = ac1.create_group_chat(name="title1", verified=verified)
|
||||
qr = chat.get_join_qr()
|
||||
assert ac2.check_qr(qr).is_ask_verifygroup
|
||||
|
||||
def test_get_set_profile_image_simple(self, ac1, data):
|
||||
chat = ac1.create_group_chat(name="title1")
|
||||
p = data.get_path("d.png")
|
||||
chat.set_profile_image(p)
|
||||
p2 = chat.get_profile_image()
|
||||
assert open(p, "rb").read() == open(p2, "rb").read()
|
||||
chat.remove_profile_image()
|
||||
assert chat.get_profile_image() is None
|
||||
|
||||
def test_delete_and_send_fails(self, ac1, chat1):
|
||||
chat1.delete()
|
||||
ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
@@ -306,12 +322,23 @@ class TestOfflineChat:
|
||||
chat1.set_draft(None)
|
||||
assert chat1.get_draft() is None
|
||||
|
||||
def test_qr_setup_contact(self, acfactory, lp):
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
ac2 = acfactory.get_configured_offline_account()
|
||||
qr = ac1.get_setup_contact_qr()
|
||||
assert qr.startswith("OPENPGP4FPR:")
|
||||
res = ac2.check_qr(qr)
|
||||
assert res.is_ask_verifycontact()
|
||||
assert not res.is_ask_verifygroup()
|
||||
assert res.contact_id == 10
|
||||
|
||||
|
||||
class TestOnlineAccount:
|
||||
def test_one_account_init(self, acfactory):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
wait_successful_IMAP_SMTP_connection(ac1)
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
def get_chat(self, ac1, ac2):
|
||||
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||
chat = ac1.create_chat_by_contact(c2)
|
||||
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||
return chat
|
||||
|
||||
def test_one_account_send(self, acfactory):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
@@ -327,15 +354,8 @@ class TestOnlineAccount:
|
||||
assert ev[1] == msg_out.id
|
||||
|
||||
def test_two_accounts_send_receive(self, acfactory):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.get_online_configuring_account()
|
||||
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||
chat = ac1.create_chat_by_contact(c2)
|
||||
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||
wait_successful_IMAP_SMTP_connection(ac1)
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
wait_successful_IMAP_SMTP_connection(ac2)
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
|
||||
msg_out = chat.send_text("message1")
|
||||
|
||||
@@ -346,15 +366,8 @@ class TestOnlineAccount:
|
||||
assert msg_in.text == "message1"
|
||||
|
||||
def test_forward_messages(self, acfactory):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.get_online_configuring_account()
|
||||
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||
chat = ac1.create_chat_by_contact(c2)
|
||||
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||
wait_successful_IMAP_SMTP_connection(ac1)
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
wait_successful_IMAP_SMTP_connection(ac2)
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
|
||||
msg_out = chat.send_text("message2")
|
||||
|
||||
@@ -378,15 +391,10 @@ class TestOnlineAccount:
|
||||
assert not chat3.get_messages()
|
||||
|
||||
def test_send_and_receive_message(self, acfactory, lp):
|
||||
lp.sec("starting accounts, waiting for configuration")
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.get_online_configuring_account()
|
||||
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||
chat = ac1.create_chat_by_contact(c2)
|
||||
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
lp.sec("ac1: create chat with ac2")
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
|
||||
lp.sec("sending text message from ac1 to ac2")
|
||||
msg_out = chat.send_text("message1")
|
||||
@@ -428,15 +436,10 @@ class TestOnlineAccount:
|
||||
assert msg_out.is_out_mdn_received()
|
||||
|
||||
def test_send_and_receive_will_encrypt_decrypt(self, acfactory, lp):
|
||||
lp.sec("starting accounts, waiting for configuration")
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.get_online_configuring_account()
|
||||
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||
chat = ac1.create_chat_by_contact(c2)
|
||||
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
lp.sec("ac1: create chat with ac2")
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
|
||||
lp.sec("sending text message from ac1 to ac2")
|
||||
msg_out = chat.send_text("message1")
|
||||
@@ -459,14 +462,12 @@ class TestOnlineAccount:
|
||||
assert msg_back.text == "message-back"
|
||||
|
||||
def test_saved_mime_on_received_message(self, acfactory, lp):
|
||||
lp.sec("starting accounts, waiting for configuration")
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.get_online_configuring_account()
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
lp.sec("configure ac2 to save mime headers, create ac1/ac2 chat")
|
||||
ac2.set_config("save_mime_headers", "1")
|
||||
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||
chat = ac1.create_chat_by_contact(c2)
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
|
||||
lp.sec("sending text message from ac1 to ac2")
|
||||
msg_out = chat.send_text("message1")
|
||||
ac1._evlogger.get_matching("DC_EVENT_MSG_DELIVERED")
|
||||
@@ -480,14 +481,8 @@ class TestOnlineAccount:
|
||||
assert mime.get_all("Received")
|
||||
|
||||
def test_send_and_receive_image(self, acfactory, lp, data):
|
||||
lp.sec("starting accounts, waiting for configuration")
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.get_online_configuring_account()
|
||||
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||
chat = ac1.create_chat_by_contact(c2)
|
||||
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
|
||||
lp.sec("sending image message from ac1 to ac2")
|
||||
path = data.get_path("d.png")
|
||||
@@ -507,13 +502,13 @@ class TestOnlineAccount:
|
||||
assert os.stat(msg_in.filename).st_size == os.stat(path).st_size
|
||||
|
||||
def test_import_export_online(self, acfactory, tmpdir):
|
||||
backupdir = tmpdir.mkdir("backup")
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
|
||||
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||
chat = ac1.create_chat_by_contact(contact1)
|
||||
chat.send_text("msg1")
|
||||
backupdir = tmpdir.mkdir("backup")
|
||||
path = ac1.export_to_dir(backupdir.strpath)
|
||||
assert os.path.exists(path)
|
||||
|
||||
@@ -542,7 +537,82 @@ class TestOnlineAccount:
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
msg = ac2.get_message_by_id(ev[2])
|
||||
assert msg.is_setup_message()
|
||||
# first try a bad setup code
|
||||
with pytest.raises(ValueError):
|
||||
msg.continue_key_transfer(str(reversed(setup_code)))
|
||||
print("*************** Incoming ASM File at: ", msg.filename)
|
||||
print("*************** Setup Code: ", setup_code)
|
||||
msg.continue_key_transfer(setup_code)
|
||||
assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"]
|
||||
|
||||
def test_qr_setup_contact(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
lp.sec("ac1: create QR code and let ac2 scan it, starting the securejoin")
|
||||
qr = ac1.get_setup_contact_qr()
|
||||
lp.sec("ac2: start QR-code based setup contact protocol")
|
||||
ch = ac2.qr_setup_contact(qr)
|
||||
assert ch.id >= 10
|
||||
wait_securejoin_inviter_progress(ac1, 1000)
|
||||
|
||||
def test_qr_join_chat(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
lp.sec("ac1: create QR code and let ac2 scan it, starting the securejoin")
|
||||
chat = ac1.create_group_chat("hello")
|
||||
qr = chat.get_join_qr()
|
||||
lp.sec("ac2: start QR-code based join-group protocol")
|
||||
ch = ac2.qr_join_chat(qr)
|
||||
assert ch.id >= 10
|
||||
wait_securejoin_inviter_progress(ac1, 1000)
|
||||
|
||||
def test_set_get_profile_image(self, acfactory, data, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
lp.sec("create unpromoted group chat")
|
||||
chat = ac1.create_group_chat("hello")
|
||||
p = data.get_path("d.png")
|
||||
|
||||
lp.sec("ac1: set profile image on unpromoted chat")
|
||||
chat.set_profile_image(p)
|
||||
ac1._evlogger.get_matching("DC_EVENT_CHAT_MODIFIED")
|
||||
assert not chat.is_promoted()
|
||||
|
||||
lp.sec("ac1: send text to promote chat (XXX without contact added)")
|
||||
# XXX first promote the chat before adding contact
|
||||
# because DC does not send out profile images for unpromoted chats
|
||||
# otherwise
|
||||
chat.send_text("ac1: initial message to promote chat (workaround)")
|
||||
assert chat.is_promoted()
|
||||
|
||||
lp.sec("ac2: add ac1 to a chat so the message does not land in DEADDROP")
|
||||
c1 = ac2.create_contact(email=ac1.get_config("addr"))
|
||||
ac2.create_chat_by_contact(c1)
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
|
||||
lp.sec("ac1: add ac2 to promoted group chat")
|
||||
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||
chat.add_contact(c2)
|
||||
|
||||
lp.sec("ac1: send a first message to ac2")
|
||||
chat.send_text("hi")
|
||||
assert chat.is_promoted()
|
||||
|
||||
lp.sec("ac2: wait for receiving message from ac1")
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
|
||||
msg_in = ac2.get_message_by_id(ev[2])
|
||||
assert not msg_in.chat.is_deaddrop()
|
||||
|
||||
lp.sec("ac2: create chat and read profile image")
|
||||
chat2 = ac2.create_chat_by_message(msg_in)
|
||||
p2 = chat2.get_profile_image()
|
||||
assert p2 is not None
|
||||
assert open(p2, "rb").read() == open(p, "rb").read()
|
||||
|
||||
ac2._evlogger.consume_events()
|
||||
ac1._evlogger.consume_events()
|
||||
lp.sec("ac2: delete profile image from chat")
|
||||
chat2.remove_profile_image()
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
|
||||
assert ev[1] == chat.id
|
||||
chat1b = ac1.create_chat_by_message(ev[2])
|
||||
assert chat1b.get_profile_image() is None
|
||||
assert chat.get_profile_image() is None
|
||||
|
||||
@@ -4,7 +4,7 @@ from deltachat import const
|
||||
from conftest import wait_configuration_progress, wait_msgs_changed
|
||||
|
||||
|
||||
class TestInCreation:
|
||||
class TestOnlineInCreation:
|
||||
def test_forward_increation(self, acfactory, data, lp):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.get_online_configuring_account()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from __future__ import print_function
|
||||
from deltachat import capi, 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 lib
|
||||
from deltachat.account import EventLogger
|
||||
@@ -17,11 +17,19 @@ def test_callback_None2int():
|
||||
clear_context_callback(ctx)
|
||||
|
||||
|
||||
def test_dc_close_events():
|
||||
ctx = capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL)
|
||||
def test_dc_close_events(tmpdir):
|
||||
ctx = ffi.gc(
|
||||
capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
||||
lib.dc_context_unref,
|
||||
)
|
||||
evlog = EventLogger(ctx)
|
||||
evlog.set_timeout(5)
|
||||
set_context_callback(ctx, lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2))
|
||||
set_context_callback(
|
||||
ctx,
|
||||
lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2)
|
||||
)
|
||||
p = tmpdir.join("hello.db")
|
||||
lib.dc_open(ctx, p.strpath.encode("ascii"), ffi.NULL)
|
||||
capi.lib.dc_close(ctx)
|
||||
|
||||
def find(info_string):
|
||||
@@ -75,3 +83,18 @@ def test_markseen_invalid_message_ids(acfactory):
|
||||
msg_ids = [9]
|
||||
lib.dc_markseen_msgs(ac1._dc_context, msg_ids, len(msg_ids))
|
||||
ac1._evlogger.ensure_event_not_queued("DC_EVENT_WARNING|DC_EVENT_ERROR")
|
||||
|
||||
|
||||
def test_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
|
||||
|
||||
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")
|
||||
@@ -1,7 +1,6 @@
|
||||
[tox]
|
||||
# make sure to update environment list in travis.yml and appveyor.yml
|
||||
envlist =
|
||||
py27
|
||||
py35
|
||||
lint
|
||||
auditwheels
|
||||
@@ -17,11 +16,15 @@ passenv =
|
||||
DCC_PY_LIVECONFIG
|
||||
deps =
|
||||
pytest
|
||||
pytest-faulthandler
|
||||
pytest-rerunfailures
|
||||
pytest-timeout
|
||||
pytest-xdist
|
||||
pdbpp
|
||||
requests
|
||||
|
||||
[testenv:auditwheels]
|
||||
skipsdist = True
|
||||
deps = auditwheel
|
||||
commands =
|
||||
python tests/auditwheels.py {toxworkdir}/wheelhouse
|
||||
|
||||
@@ -42,7 +45,7 @@ commands =
|
||||
[testenv:doc]
|
||||
basepython = python3.5
|
||||
deps =
|
||||
sphinx==2.0.1
|
||||
sphinx==2.2.0
|
||||
breathe
|
||||
|
||||
changedir = doc
|
||||
@@ -51,10 +54,12 @@ commands =
|
||||
|
||||
|
||||
[pytest]
|
||||
addopts = -v -rs --reruns 3 --reruns-delay 2
|
||||
python_files = tests/test_*.py
|
||||
norecursedirs = .tox
|
||||
xfail_strict=true
|
||||
timeout = 60
|
||||
timeout = 60
|
||||
timeout_method = thread
|
||||
|
||||
[flake8]
|
||||
max-line-length = 120
|
||||
|
||||
@@ -23,7 +23,7 @@ if [ $? != 0 ]; then
|
||||
fi
|
||||
|
||||
pushd python
|
||||
if [ -e "./liveconfig" ]; then
|
||||
if [ -e "./liveconfig" && -z "$DCC_PY_LIVECONFIG" ]; then
|
||||
export DCC_PY_LIVECONFIG=liveconfig
|
||||
fi
|
||||
tox "$@"
|
||||
|
||||
@@ -1 +1 @@
|
||||
nightly-2019-07-10
|
||||
nightly-2019-08-13
|
||||
|
||||
372
spec.md
Normal file
372
spec.md
Normal file
@@ -0,0 +1,372 @@
|
||||
# Chat-over-Email specification
|
||||
|
||||
Version 0.19.0
|
||||
|
||||
This document describes how emails can be used
|
||||
to implement typical messenger functions
|
||||
while staying compatible to existing MUAs.
|
||||
|
||||
- [Encryption](#encryption)
|
||||
- [Outgoing messages](#outgoing-messages)
|
||||
- [Incoming messages](#incoming-messages)
|
||||
- [Forwarded messages](#forwarded-messages)
|
||||
- [Groups](#groups)
|
||||
- [Outgoing group messages](#outgoing-group-messages)
|
||||
- [Incoming group messages](#incoming-group-messages)
|
||||
- [Add and remove members](#add-and-remove-members)
|
||||
- [Change group name](#change-group-name)
|
||||
- [Set group image](#set-group-image)
|
||||
- [Set profile image](#set-profile-image)
|
||||
- [Miscellaneous](#miscellaneous)
|
||||
|
||||
|
||||
# Encryption
|
||||
|
||||
Messages SHOULD be encrypted by the
|
||||
[Autocrypt](https://autocrypt.org/level1.html) standard;
|
||||
`prefer-encrypt=mutual` MAY be set by default.
|
||||
|
||||
Meta data (at least the subject and all chat-headers) SHOULD be encrypted
|
||||
by the [Memoryhole](https://github.com/autocrypt/memoryhole) standard.
|
||||
If Memoryhole is not used,
|
||||
the subject of encrypted messages SHOULD be replaced by the string
|
||||
`Chat: Encrypted message` where the part after the colon MAY be localized.
|
||||
|
||||
|
||||
# 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,
|
||||
the `Subject` header SHOULD start with the characters `Chat:`
|
||||
and SHOULD be an excerpt of the message.
|
||||
Replies to messages MAY follow the typical `Re:`-format.
|
||||
|
||||
The body MAY contain text which MUST have the content type `text/plain`
|
||||
or `mulipart/alternative` containing `text/plain`.
|
||||
|
||||
The text MAY be divided into a user-text-part and a footer-part using the
|
||||
line `-- ` (minus, minus, space, lineend).
|
||||
|
||||
The user-text-part MUST contain only user generated content.
|
||||
User generated content are eg. texts a user has actually typed
|
||||
or pasted or forwarded from another user.
|
||||
Full quotes, footers or sth. like that MUST NOT go to the user-text-part.
|
||||
|
||||
From: sender@domain
|
||||
To: rcpt@domain
|
||||
Chat-Version: 1.0
|
||||
Content-Type: text/plain
|
||||
Subject: Chat: Hello ...
|
||||
|
||||
Hello world!
|
||||
|
||||
|
||||
# Incoming messages
|
||||
|
||||
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.
|
||||
|
||||
Messenger SHOULD show the `Subject`
|
||||
if the message comes from a normal MUA together with the email-body.
|
||||
The email-body SHOULD be converted
|
||||
to plain text, full-quotes and similar regions SHOULD be cut.
|
||||
|
||||
Attachments SHOULD be shown where possible.
|
||||
If an attachment cannot be shown, a non-distracting warning SHOULD be printed.
|
||||
|
||||
|
||||
# Forwarded messages
|
||||
|
||||
Forwarded messages are outgoing messages that contain a forwarded-header
|
||||
before the user generated content.
|
||||
|
||||
The forwarded header MUST contain two lines:
|
||||
The first line contains the text
|
||||
`---------- Forwarded message ----------`
|
||||
(10 minus, space, text `Forwarded message`, space, 10 minus).
|
||||
The second line starts with `From: ` followed by the original sender
|
||||
which SHOULD be anonymized or just a placeholder.
|
||||
|
||||
From: sender@domain
|
||||
To: rcpt@domain
|
||||
Chat-Version: 1.0
|
||||
Content-Type: text/plain
|
||||
Subject: Chat: Forwarded message
|
||||
|
||||
---------- Forwarded message ----------
|
||||
From: Messenger
|
||||
|
||||
Hello world!
|
||||
|
||||
Incoming forwarded messages are detected by the header.
|
||||
The messenger SHOULD mark these messages in a way that
|
||||
it becomes obvious that the message is not created by the sender.
|
||||
Note that most messengers do not show the original sender of forwarded messages
|
||||
but MUAs typically expose the sender in the UI.
|
||||
|
||||
|
||||
# Groups
|
||||
|
||||
Groups are chats with usually more than one recipient,
|
||||
each defined by an email-address.
|
||||
The sender plus the recipients are the group members.
|
||||
|
||||
To allow different groups with the same members,
|
||||
groups are identified by a group-id.
|
||||
The group-id MUST be created only from the characters
|
||||
`0`-`9`, `A`-`Z`, `a`-`z` `_` and `-`.
|
||||
|
||||
Groups MUST have a group-name.
|
||||
The group-name is any non-zero-length UTF-8 string.
|
||||
|
||||
Groups MAY have a group-image.
|
||||
|
||||
|
||||
## Outgoing groups messages
|
||||
|
||||
All group members MUST be added to the `From`/`To` headers.
|
||||
The group-id MUST be written to the `Chat-Group-ID` header.
|
||||
The group-name MUST be written to `Chat-Group-Name` header
|
||||
(the forced presence of this header makes it easier
|
||||
to join a group chat on a second device any time).
|
||||
|
||||
The `Subject` header of outgoing group messages
|
||||
SHOULD start with the characters `Chat:`
|
||||
followed by the group-name and a colon followed by an excerpt of the message.
|
||||
|
||||
To identify the group-id on replies from normal MUAs,
|
||||
the group-id MUST also be added to the message-id of outgoing messages.
|
||||
The message-id MUST have the format `Gr.<group-id>.<unique data>`.
|
||||
|
||||
From: member1@domain
|
||||
To: member2@domain, member3@domain
|
||||
Chat-Version: 1.0
|
||||
Chat-Group-ID: 1234xyZ
|
||||
Chat-Group-Name: My Group
|
||||
Message-ID: Gr.1234xyZ.0001@domain
|
||||
Subject: Chat: My Group: Hello group ...
|
||||
|
||||
Hello group - this group contains three members
|
||||
|
||||
Messengers adding the member list in the form `Name <email-address>`
|
||||
MUST take care only to spread the names authorized by the contacts themselves.
|
||||
Otherwise, names as _Daddy_ or _Honey_ may be spread
|
||||
(this issue is also true for normal MUAs, however,
|
||||
for more contact- and chat-centralized apps
|
||||
such situations happen more frequently).
|
||||
|
||||
|
||||
## Incoming group messages
|
||||
|
||||
The messenger MUST search incoming messages for the group-id
|
||||
in the following headers: `Chat-Group-ID`,
|
||||
`Message-ID`, `In-Reply-To` and `References` (in this order).
|
||||
|
||||
If the messenger finds a valid and existent group-id,
|
||||
the message SHOULD be assigned to the given group.
|
||||
If the messenger finds a valid but not existent group-id,
|
||||
the messenger MAY create a new group.
|
||||
If no group-id is found,
|
||||
the message MAY be assigned
|
||||
to a normal single-user chat with the email-address given in `From`.
|
||||
|
||||
|
||||
## Add and remove members
|
||||
|
||||
Messenger clients MUST construct the member list
|
||||
from the `From`/`To` headers only on the first group message
|
||||
or if they see a `Chat-Group-Member-Added`
|
||||
or `Chat-Group-Member-Removed` action header.
|
||||
Both headers MUST have the email-address
|
||||
of the added or removed member as the value.
|
||||
Messenger clients MUST NOT construct the member list
|
||||
on other group messages
|
||||
(this is to avoid accidentally altered To-lists in normal MUAs;
|
||||
the user does not expect adding a user to a _message_
|
||||
will also add him to the _group_ "forever").
|
||||
|
||||
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
|
||||
and the message SHOULD appear as a message or action from the sender.
|
||||
|
||||
From: member1@domain
|
||||
To: member2@domain, member3@domain, member4@domain
|
||||
Chat-Version: 1.0
|
||||
Chat-Group-ID: 1234xyZ
|
||||
Chat-Group-Name: My Group
|
||||
Chat-Group-Member-Added: member4@domain
|
||||
Message-ID: Gr.1234xyZ.0002@domain
|
||||
Subject: Chat: My Group: Hello, ...
|
||||
|
||||
Hello, I've added member4@domain to our group. Now we have 4 members.
|
||||
|
||||
To remove a member:
|
||||
|
||||
From: member1@domain
|
||||
To: member2@domain, member3@domain
|
||||
Chat-Version: 1.0
|
||||
Chat-Group-ID: 1234xyZ
|
||||
Chat-Group-Name: My Group
|
||||
Chat-Group-Member-Removed: member4@domain
|
||||
Message-ID: Gr.1234xyZ.0003@domain
|
||||
Subject: Chat: My Group: Hello, ...
|
||||
|
||||
Hello, I've removed member4@domain from our group. Now we have 3 members.
|
||||
|
||||
|
||||
## Change group name
|
||||
|
||||
To change the group-name,
|
||||
the messenger MUST send the action header `Chat-Group-Name-Changed`
|
||||
with the value set to the old group name to all group members.
|
||||
The new group name goes to the header `Chat-Group-Name`.
|
||||
|
||||
The messenger SHOULD send an explicit mail for each name change.
|
||||
The body of the message SHOULD contain
|
||||
a localized description about what happened
|
||||
and the message SHOULD appear as a message or action from the sender.
|
||||
|
||||
From: member1@domain
|
||||
To: member2@domain, member3@domain
|
||||
Chat-Version: 1.0
|
||||
Chat-Group-ID: 1234xyZ
|
||||
Chat-Group-Name: Our Group
|
||||
Chat-Group-Name-Changed: My Group
|
||||
Message-ID: Gr.1234xyZ.0004@domain
|
||||
Subject: Chat: Our Group: Hello, ...
|
||||
|
||||
Hello, I've changed the group name from "My Group" to "Our Group".
|
||||
|
||||
|
||||
## Set group image
|
||||
|
||||
A group MAY have a group-image.
|
||||
To change or set the group-image,
|
||||
the messenger MUST attach an image file to a message
|
||||
and MUST add the header `Chat-Group-Image`
|
||||
with the value set to the image name.
|
||||
|
||||
To remove the group-image,
|
||||
the messenger MUST add the header `Chat-Group-Image: 0`.
|
||||
|
||||
The messenger SHOULD send an explicit mail for each group image change.
|
||||
The body of the message SHOULD contain
|
||||
a localized description about what happened
|
||||
and the message SHOULD appear as a message or action from the sender.
|
||||
|
||||
|
||||
From: member1@domain
|
||||
To: member2@domain, member3@domain
|
||||
Chat-Version: 1.0
|
||||
Chat-Group-ID: 1234xyZ
|
||||
Chat-Group-Name: Our Group
|
||||
Chat-Group-Image: image.jpg
|
||||
Message-ID: Gr.1234xyZ.0005@domain
|
||||
Subject: Chat: Our Group: Hello, ...
|
||||
Content-Type: multipart/mixed; boundary="==break=="
|
||||
|
||||
--==break==
|
||||
Content-Type: text/plain
|
||||
|
||||
Hello, I've changed the group image.
|
||||
--==break==
|
||||
Content-Type: image/jpeg
|
||||
Content-Disposition: attachment; filename="image.jpg"
|
||||
|
||||
/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBw ...
|
||||
--==break==--
|
||||
|
||||
The image format SHOULD be image/jpeg or image/png.
|
||||
To save data, it is RECOMMENDED
|
||||
to add a `Chat-Group-Image` only on image changes.
|
||||
|
||||
|
||||
# Set profile image
|
||||
|
||||
A user MAY have a profile-image that MAY be spread to his contacts.
|
||||
To change or set the profile-image,
|
||||
the messenger MUST attach an image file to a message
|
||||
and MUST add the header `Chat-Profile-Image`
|
||||
with the value set to the image name.
|
||||
|
||||
To remove the profile-image,
|
||||
the messenger MUST add the header `Chat-Profile-Image: 0`.
|
||||
|
||||
To spread the image,
|
||||
the messenger MAY send the profile image
|
||||
together with the next mail to a given contact
|
||||
(to do this only once,
|
||||
the messenger has to keep a `profile_image_update_state` somewhere).
|
||||
Alternatively, the messenger MAY send an explicit mail
|
||||
for each profile-image change to all contacts using a compatible messenger.
|
||||
The messenger SHOULD NOT send an explicit mail to normal MUAs.
|
||||
|
||||
From: sender@domain
|
||||
To: rcpt@domain
|
||||
Chat-Version: 1.0
|
||||
Chat-Profile-Image: photo.jpg
|
||||
Subject: Chat: Hello, ...
|
||||
Content-Type: multipart/mixed; boundary="==break=="
|
||||
|
||||
--==break==
|
||||
Content-Type: text/plain
|
||||
|
||||
Hello, I've changed my profile image.
|
||||
--==break==
|
||||
Content-Type: image/jpeg
|
||||
Content-Disposition: attachment; filename="photo.jpg"
|
||||
|
||||
AKCgkJi3j4l5kjoldfUAKCgkJi3j4lldfHjgWICwgIEBQYFBA ...
|
||||
--==break==--
|
||||
|
||||
The image format SHOULD be image/jpeg or image/png.
|
||||
Note that `Chat-Profile-Image` may appear together with all other headers,
|
||||
eg. there may be a `Chat-Profile-Image` and a `Chat-Group-Image` header
|
||||
in the same message.
|
||||
To save data, it is RECOMMENDED to add a `Chat-Profile-Image` header
|
||||
only on image changes.
|
||||
|
||||
|
||||
# Miscellaneous
|
||||
|
||||
Messengers SHOULD use the header `Chat-Predecessor`
|
||||
instead of `In-Reply-To` as the latter one results
|
||||
in infinite threads on typical MUAs.
|
||||
|
||||
Messengers SHOULD add a `Chat-Voice-message: 1` header
|
||||
if an attached audio file is a voice message.
|
||||
|
||||
Messengers MAY add a `Chat-Duration` header
|
||||
to specify the duration of attached audio or video files.
|
||||
The value MUST be the duration in milliseconds.
|
||||
This allows the receiver to show the time without knowing the file format.
|
||||
|
||||
Chat-Predecessor: foo123@domain
|
||||
Chat-Voice-Message: 1
|
||||
Chat-Duration: 10000
|
||||
|
||||
Messengers MAY send and receive Message Disposition Notifications
|
||||
(MDNs, [RFC 8098](https://tools.ietf.org/html/rfc8098),
|
||||
[RFC 3503](https://tools.ietf.org/html/rfc3503))
|
||||
using the `Chat-Disposition-Notification-To` header
|
||||
instead of the `Disposition-Notification-To`
|
||||
(which unfortunately forces many other MUAs
|
||||
to send weird mails not following any standard).
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Copyright © 2017-2019 Delta Chat contributors.
|
||||
@@ -7,7 +7,6 @@ use mmime::mailimf_types::*;
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::dc_tools::as_str;
|
||||
use crate::key::*;
|
||||
|
||||
/// Possible values for encryption preference
|
||||
@@ -64,11 +63,8 @@ impl Aheader {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_imffields(
|
||||
wanted_from: *const libc::c_char,
|
||||
header: *const mailimf_fields,
|
||||
) -> Option<Self> {
|
||||
if wanted_from.is_null() || header.is_null() {
|
||||
pub fn from_imffields(wanted_from: &str, header: *const mailimf_fields) -> Option<Self> {
|
||||
if header.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -94,7 +90,7 @@ impl Aheader {
|
||||
|
||||
match Self::from_str(value) {
|
||||
Ok(test) => {
|
||||
if addr_cmp(&test.addr, as_str(wanted_from)) {
|
||||
if addr_cmp(&test.addr, wanted_from) {
|
||||
if fine_header.is_none() {
|
||||
fine_header = Some(test);
|
||||
} else {
|
||||
|
||||
1922
src/chat.rs
Normal file
1922
src/chat.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,10 @@
|
||||
use crate::chat::*;
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::*;
|
||||
use crate::dc_chat::*;
|
||||
use crate::dc_lot::*;
|
||||
use crate::dc_msg::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Result;
|
||||
use crate::lot::Lot;
|
||||
use crate::message::*;
|
||||
use crate::stock::StockMessage;
|
||||
|
||||
/// An object representing a single chatlist in memory.
|
||||
@@ -32,17 +31,13 @@ use crate::stock::StockMessage;
|
||||
/// first entry and only present on new messages, there is the rough idea that it can be optionally always
|
||||
/// present and sorted into the list by date. Rendering the deaddrop in the described way
|
||||
/// would not add extra work in the UI then.
|
||||
pub struct Chatlist<'a> {
|
||||
context: &'a Context,
|
||||
#[derive(Debug)]
|
||||
pub struct Chatlist {
|
||||
/// Stores pairs of `chat_id, message_id`
|
||||
ids: Vec<(u32, u32)>,
|
||||
}
|
||||
|
||||
impl<'a> Chatlist<'a> {
|
||||
pub fn get_context(&self) -> &Context {
|
||||
self.context
|
||||
}
|
||||
|
||||
impl Chatlist {
|
||||
/// Get a list of chats.
|
||||
/// The list can be filtered by query parameters.
|
||||
///
|
||||
@@ -86,7 +81,7 @@ impl<'a> Chatlist<'a> {
|
||||
/// `query_contact_id`: An optional contact ID for filtering the list. Only chats including this contact ID
|
||||
/// are returned.
|
||||
pub fn try_load(
|
||||
context: &'a Context,
|
||||
context: &Context,
|
||||
listflags: usize,
|
||||
query: Option<&str>,
|
||||
query_contact_id: Option<u32>,
|
||||
@@ -196,12 +191,12 @@ impl<'a> Chatlist<'a> {
|
||||
|
||||
if 0 != add_archived_link_item && dc_get_archived_cnt(context) > 0 {
|
||||
if ids.is_empty() && 0 != listflags & DC_GCL_ADD_ALLDONE_HINT {
|
||||
ids.push((DC_CHAT_ID_ALLDONE_HINT as u32, 0));
|
||||
ids.push((DC_CHAT_ID_ALLDONE_HINT, 0));
|
||||
}
|
||||
ids.push((DC_CHAT_ID_ARCHIVED_LINK as u32, 0));
|
||||
ids.push((DC_CHAT_ID_ARCHIVED_LINK, 0));
|
||||
}
|
||||
|
||||
Ok(Chatlist { context, ids })
|
||||
Ok(Chatlist { ids })
|
||||
}
|
||||
|
||||
/// Find out the number of chats.
|
||||
@@ -248,57 +243,58 @@ impl<'a> Chatlist<'a> {
|
||||
/// - dc_lot_t::timestamp: the timestamp of the message. 0 if not applicable.
|
||||
/// - dc_lot_t::state: The state of the message as one of the DC_STATE_* constants (see #dc_msg_get_state()).
|
||||
// 0 if not applicable.
|
||||
pub unsafe fn get_summary(&self, index: usize, mut chat: *mut Chat<'a>) -> *mut dc_lot_t {
|
||||
pub fn get_summary(&self, context: &Context, index: usize, chat: Option<&Chat>) -> Lot {
|
||||
// The summary is created by the chat, not by the last message.
|
||||
// This is because we may want to display drafts here or stuff as
|
||||
// "is typing".
|
||||
// Also, sth. as "No messages" would not work if the summary comes from a message.
|
||||
|
||||
let mut ret = dc_lot_new();
|
||||
let mut ret = Lot::new();
|
||||
if index >= self.ids.len() {
|
||||
(*ret).text2 = "ErrBadChatlistIndex".strdup();
|
||||
ret.text2 = Some("ErrBadChatlistIndex".to_string());
|
||||
return ret;
|
||||
}
|
||||
|
||||
let chat_loaded: Chat;
|
||||
let chat = if let Some(chat) = chat {
|
||||
chat
|
||||
} else {
|
||||
if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0) {
|
||||
chat_loaded = chat;
|
||||
&chat_loaded
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
let lastmsg_id = self.ids[index].1;
|
||||
let mut lastcontact = None;
|
||||
|
||||
if chat.is_null() {
|
||||
chat = dc_chat_new(self.context);
|
||||
let chat_to_delete = chat;
|
||||
if !dc_chat_load_from_db(chat, self.ids[index].0) {
|
||||
(*ret).text2 = "ErrCannotReadChat".strdup();
|
||||
dc_chat_unref(chat_to_delete);
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
let lastmsg = if 0 != lastmsg_id {
|
||||
let lastmsg = dc_msg_new_untyped(self.context);
|
||||
dc_msg_load_from_db(lastmsg, self.context, lastmsg_id);
|
||||
if let Ok(lastmsg) = dc_msg_load_from_db(context, lastmsg_id) {
|
||||
if lastmsg.from_id != 1 as libc::c_uint
|
||||
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
|
||||
{
|
||||
lastcontact = Contact::load_from_db(context, lastmsg.from_id).ok();
|
||||
}
|
||||
|
||||
if (*lastmsg).from_id != 1 as libc::c_uint
|
||||
&& ((*chat).type_0 == DC_CHAT_TYPE_GROUP
|
||||
|| (*chat).type_0 == DC_CHAT_TYPE_VERIFIED_GROUP)
|
||||
{
|
||||
lastcontact = Contact::load_from_db(self.context, (*lastmsg).from_id).ok();
|
||||
Some(lastmsg)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
lastmsg
|
||||
} else {
|
||||
std::ptr::null_mut()
|
||||
None
|
||||
};
|
||||
|
||||
if (*chat).id == DC_CHAT_ID_ARCHIVED_LINK as u32 {
|
||||
(*ret).text2 = dc_strdup(0 as *const libc::c_char)
|
||||
} else if lastmsg.is_null() || (*lastmsg).from_id == DC_CONTACT_ID_UNDEFINED as u32 {
|
||||
(*ret).text2 = self.context.stock_str(StockMessage::NoMessages).strdup();
|
||||
if chat.id == DC_CHAT_ID_ARCHIVED_LINK {
|
||||
ret.text2 = None;
|
||||
} else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED
|
||||
{
|
||||
ret.text2 = Some(context.stock_str(StockMessage::NoMessages).to_string());
|
||||
} else {
|
||||
dc_lot_fill(ret, lastmsg, chat, lastcontact.as_ref(), self.context);
|
||||
ret.fill(&mut lastmsg.unwrap(), chat, lastcontact.as_ref(), context);
|
||||
}
|
||||
|
||||
dc_msg_unref(lastmsg);
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
@@ -306,11 +302,10 @@ impl<'a> Chatlist<'a> {
|
||||
pub fn dc_get_archived_cnt(context: &Context) -> u32 {
|
||||
context
|
||||
.sql
|
||||
.query_row_col(
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
|
||||
params![],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
@@ -320,7 +315,7 @@ fn get_last_deaddrop_fresh_msg(context: &Context) -> u32 {
|
||||
// only few fresh messages.
|
||||
context
|
||||
.sql
|
||||
.query_row_col(
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT m.id FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
|
||||
WHERE m.state=10 \
|
||||
@@ -328,7 +323,6 @@ fn get_last_deaddrop_fresh_msg(context: &Context) -> u32 {
|
||||
AND c.blocked=2 \
|
||||
ORDER BY m.timestamp DESC, m.id DESC;",
|
||||
params![],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
|
||||
|
||||
use crate::constants::DC_VERSION_STR;
|
||||
use crate::context::Context;
|
||||
use crate::dc_job::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Error;
|
||||
use crate::job::*;
|
||||
use crate::stock::StockMessage;
|
||||
|
||||
/// The available configuration keys.
|
||||
@@ -33,6 +33,7 @@ pub enum Config {
|
||||
E2eeEnabled,
|
||||
#[strum(props(default = "1"))]
|
||||
MdnsEnabled,
|
||||
#[strum(props(default = "1"))]
|
||||
InboxWatch,
|
||||
#[strum(props(default = "1"))]
|
||||
SentboxWatch,
|
||||
@@ -48,11 +49,14 @@ pub enum Config {
|
||||
ConfiguredMailUser,
|
||||
ConfiguredMailPw,
|
||||
ConfiguredMailPort,
|
||||
ConfiguredMailSecurity,
|
||||
ConfiguredSendServer,
|
||||
ConfiguredSendUser,
|
||||
ConfiguredSendPw,
|
||||
ConfiguredSendPort,
|
||||
ConfiguredServerFlags,
|
||||
ConfiguredSendSecurity,
|
||||
ConfiguredE2EEEnabled,
|
||||
Configured,
|
||||
// Deprecated
|
||||
#[strum(serialize = "sys.version")]
|
||||
@@ -69,9 +73,9 @@ impl Context {
|
||||
let value = match key {
|
||||
Config::Selfavatar => {
|
||||
let rel_path = self.sql.get_config(self, key);
|
||||
rel_path.map(|p| dc_get_abs_path_safe(self, &p).to_str().unwrap().to_string())
|
||||
rel_path.map(|p| dc_get_abs_path(self, &p).to_str().unwrap().to_string())
|
||||
}
|
||||
Config::SysVersion => Some(std::str::from_utf8(DC_VERSION_STR).unwrap().into()),
|
||||
Config::SysVersion => Some((&*DC_VERSION_STR).clone()),
|
||||
Config::SysMsgsizeMaxRecommended => Some(format!("{}", 24 * 1024 * 1024 / 4 * 3)),
|
||||
Config::SysConfigKeys => Some(get_config_keys_string()),
|
||||
_ => self.sql.get_config(self, key),
|
||||
@@ -99,17 +103,17 @@ impl Context {
|
||||
}
|
||||
Config::InboxWatch => {
|
||||
let ret = self.sql.set_config(self, key, value);
|
||||
unsafe { dc_interrupt_imap_idle(self) };
|
||||
interrupt_imap_idle(self);
|
||||
ret
|
||||
}
|
||||
Config::SentboxWatch => {
|
||||
let ret = self.sql.set_config(self, key, value);
|
||||
unsafe { dc_interrupt_sentbox_idle(self) };
|
||||
interrupt_sentbox_idle(self);
|
||||
ret
|
||||
}
|
||||
Config::MvboxWatch => {
|
||||
let ret = self.sql.set_config(self, key, value);
|
||||
unsafe { dc_interrupt_mvbox_idle(self) };
|
||||
interrupt_mvbox_idle(self);
|
||||
ret
|
||||
}
|
||||
Config::Selfstatus => {
|
||||
@@ -120,8 +124,7 @@ impl Context {
|
||||
value
|
||||
};
|
||||
|
||||
let ret = self.sql.set_config(self, key, val);
|
||||
ret
|
||||
self.sql.set_config(self, key, val)
|
||||
}
|
||||
_ => self.sql.set_config(self, key, value),
|
||||
}
|
||||
|
||||
213
src/configure/auto_mozilla.rs
Normal file
213
src/configure/auto_mozilla.rs
Normal file
@@ -0,0 +1,213 @@
|
||||
use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::x::*;
|
||||
|
||||
use super::read_autoconf_file;
|
||||
/* ******************************************************************************
|
||||
* Thunderbird's Autoconfigure
|
||||
******************************************************************************/
|
||||
/* documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
|
||||
#[repr(C)]
|
||||
struct moz_autoconfigure_t<'a> {
|
||||
pub in_0: &'a LoginParam,
|
||||
pub in_emaildomain: &'a str,
|
||||
pub in_emaillocalpart: &'a str,
|
||||
pub out: LoginParam,
|
||||
pub out_imap_set: libc::c_int,
|
||||
pub out_smtp_set: libc::c_int,
|
||||
pub tag_server: libc::c_int,
|
||||
pub tag_config: libc::c_int,
|
||||
}
|
||||
|
||||
pub unsafe fn moz_autoconfigure(
|
||||
context: &Context,
|
||||
url: &str,
|
||||
param_in: &LoginParam,
|
||||
) -> Option<LoginParam> {
|
||||
let xml_raw = read_autoconf_file(context, url);
|
||||
if xml_raw.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Split address into local part and domain part.
|
||||
let p = param_in.addr.find("@");
|
||||
if p.is_none() {
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
return None;
|
||||
}
|
||||
let (in_emaillocalpart, in_emaildomain) = param_in.addr.split_at(p.unwrap());
|
||||
let in_emaildomain = &in_emaildomain[1..];
|
||||
|
||||
let mut reader = quick_xml::Reader::from_str(as_str(xml_raw));
|
||||
reader.trim_text(true);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
|
||||
let mut moz_ac = moz_autoconfigure_t {
|
||||
in_0: param_in,
|
||||
in_emaildomain,
|
||||
in_emaillocalpart,
|
||||
out: LoginParam::new(),
|
||||
out_imap_set: 0,
|
||||
out_smtp_set: 0,
|
||||
tag_server: 0,
|
||||
tag_config: 0,
|
||||
};
|
||||
loop {
|
||||
match reader.read_event(&mut buf) {
|
||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||
moz_autoconfigure_starttag_cb(e, &mut moz_ac, &reader)
|
||||
}
|
||||
Ok(quick_xml::events::Event::End(ref e)) => moz_autoconfigure_endtag_cb(e, &mut moz_ac),
|
||||
Ok(quick_xml::events::Event::Text(ref e)) => {
|
||||
moz_autoconfigure_text_cb(e, &mut moz_ac, &reader)
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
context,
|
||||
"Configure xml: Error at position {}: {:?}",
|
||||
reader.buffer_position(),
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(quick_xml::events::Event::Eof) => break,
|
||||
_ => (),
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
if moz_ac.out.mail_server.is_empty()
|
||||
|| moz_ac.out.mail_port == 0
|
||||
|| moz_ac.out.send_server.is_empty()
|
||||
|| moz_ac.out.send_port == 0
|
||||
{
|
||||
let r = moz_ac.out.to_string();
|
||||
warn!(context, "Bad or incomplete autoconfig: {}", r,);
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
return None;
|
||||
}
|
||||
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
Some(moz_ac.out)
|
||||
}
|
||||
|
||||
fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
||||
event: &BytesText,
|
||||
moz_ac: &mut moz_autoconfigure_t,
|
||||
reader: &quick_xml::Reader<B>,
|
||||
) {
|
||||
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
||||
|
||||
let addr = &moz_ac.in_0.addr;
|
||||
let email_local = moz_ac.in_emaillocalpart;
|
||||
let email_domain = moz_ac.in_emaildomain;
|
||||
|
||||
let val = val
|
||||
.trim()
|
||||
.replace("%EMAILADDRESS%", addr)
|
||||
.replace("%EMAILLOCALPART%", email_local)
|
||||
.replace("%EMAILDOMAIN%", email_domain);
|
||||
|
||||
if moz_ac.tag_server == 1 {
|
||||
match moz_ac.tag_config {
|
||||
10 => moz_ac.out.mail_server = val,
|
||||
11 => moz_ac.out.mail_port = val.parse().unwrap_or_default(),
|
||||
12 => moz_ac.out.mail_user = val,
|
||||
13 => {
|
||||
let val_lower = val.to_lowercase();
|
||||
if val_lower == "ssl" {
|
||||
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
|
||||
}
|
||||
if val_lower == "starttls" {
|
||||
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_STARTTLS as i32
|
||||
}
|
||||
if val_lower == "plain" {
|
||||
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else if moz_ac.tag_server == 2 {
|
||||
match moz_ac.tag_config {
|
||||
10 => moz_ac.out.send_server = val,
|
||||
11 => moz_ac.out.send_port = val.parse().unwrap_or_default(),
|
||||
12 => moz_ac.out.send_user = val,
|
||||
13 => {
|
||||
let val_lower = val.to_lowercase();
|
||||
if val_lower == "ssl" {
|
||||
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
|
||||
}
|
||||
if val_lower == "starttls" {
|
||||
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32
|
||||
}
|
||||
if val_lower == "plain" {
|
||||
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn moz_autoconfigure_endtag_cb(event: &BytesEnd, moz_ac: &mut moz_autoconfigure_t) {
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
|
||||
if tag == "incomingserver" {
|
||||
moz_ac.tag_server = 0;
|
||||
moz_ac.tag_config = 0;
|
||||
moz_ac.out_imap_set = 1;
|
||||
} else if tag == "outgoingserver" {
|
||||
moz_ac.tag_server = 0;
|
||||
moz_ac.tag_config = 0;
|
||||
moz_ac.out_smtp_set = 1;
|
||||
} else {
|
||||
moz_ac.tag_config = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn moz_autoconfigure_starttag_cb<B: std::io::BufRead>(
|
||||
event: &BytesStart,
|
||||
moz_ac: &mut moz_autoconfigure_t,
|
||||
reader: &quick_xml::Reader<B>,
|
||||
) {
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
|
||||
if tag == "incomingserver" {
|
||||
moz_ac.tag_server = if let Some(typ) = event.attributes().find(|attr| {
|
||||
attr.as_ref()
|
||||
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "type")
|
||||
.unwrap_or_default()
|
||||
}) {
|
||||
let typ = typ
|
||||
.unwrap()
|
||||
.unescape_and_decode_value(reader)
|
||||
.unwrap_or_default()
|
||||
.to_lowercase();
|
||||
|
||||
if typ == "imap" && moz_ac.out_imap_set == 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
moz_ac.tag_config = 0;
|
||||
} else if tag == "outgoingserver" {
|
||||
moz_ac.tag_server = if moz_ac.out_smtp_set == 0 { 2 } else { 0 };
|
||||
moz_ac.tag_config = 0;
|
||||
} else if tag == "hostname" {
|
||||
moz_ac.tag_config = 10;
|
||||
} else if tag == "port" {
|
||||
moz_ac.tag_config = 11;
|
||||
} else if tag == "sockettype" {
|
||||
moz_ac.tag_config = 13;
|
||||
} else if tag == "username" {
|
||||
moz_ac.tag_config = 12;
|
||||
}
|
||||
}
|
||||
213
src/configure/auto_outlook.rs
Normal file
213
src/configure/auto_outlook.rs
Normal file
@@ -0,0 +1,213 @@
|
||||
use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::x::*;
|
||||
use std::ptr;
|
||||
|
||||
use super::read_autoconf_file;
|
||||
/* ******************************************************************************
|
||||
* Outlook's Autodiscover
|
||||
******************************************************************************/
|
||||
#[repr(C)]
|
||||
struct outlk_autodiscover_t<'a> {
|
||||
pub in_0: &'a LoginParam,
|
||||
pub out: LoginParam,
|
||||
pub out_imap_set: libc::c_int,
|
||||
pub out_smtp_set: libc::c_int,
|
||||
pub tag_config: libc::c_int,
|
||||
pub config: [*mut libc::c_char; 6],
|
||||
pub redirect: *mut libc::c_char,
|
||||
}
|
||||
|
||||
pub unsafe fn outlk_autodiscover(
|
||||
context: &Context,
|
||||
url__: &str,
|
||||
param_in: &LoginParam,
|
||||
) -> Option<LoginParam> {
|
||||
let mut xml_raw: *mut libc::c_char = ptr::null_mut();
|
||||
let mut url = url__.strdup();
|
||||
let mut outlk_ad = outlk_autodiscover_t {
|
||||
in_0: param_in,
|
||||
out: LoginParam::new(),
|
||||
out_imap_set: 0,
|
||||
out_smtp_set: 0,
|
||||
tag_config: 0,
|
||||
config: [ptr::null_mut(); 6],
|
||||
redirect: ptr::null_mut(),
|
||||
};
|
||||
let ok_to_continue;
|
||||
let mut i = 0;
|
||||
loop {
|
||||
if !(i < 10) {
|
||||
ok_to_continue = true;
|
||||
break;
|
||||
}
|
||||
memset(
|
||||
&mut outlk_ad as *mut outlk_autodiscover_t as *mut libc::c_void,
|
||||
0,
|
||||
::std::mem::size_of::<outlk_autodiscover_t>(),
|
||||
);
|
||||
xml_raw = read_autoconf_file(context, as_str(url));
|
||||
if xml_raw.is_null() {
|
||||
ok_to_continue = false;
|
||||
break;
|
||||
}
|
||||
|
||||
let mut reader = quick_xml::Reader::from_str(as_str(xml_raw));
|
||||
reader.trim_text(true);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
|
||||
loop {
|
||||
match reader.read_event(&mut buf) {
|
||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||
outlk_autodiscover_starttag_cb(e, &mut outlk_ad)
|
||||
}
|
||||
Ok(quick_xml::events::Event::End(ref e)) => {
|
||||
outlk_autodiscover_endtag_cb(e, &mut outlk_ad)
|
||||
}
|
||||
Ok(quick_xml::events::Event::Text(ref e)) => {
|
||||
outlk_autodiscover_text_cb(e, &mut outlk_ad, &reader)
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
context,
|
||||
"Configure xml: Error at position {}: {:?}",
|
||||
reader.buffer_position(),
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(quick_xml::events::Event::Eof) => break,
|
||||
_ => (),
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
if !(!outlk_ad.config[5].is_null()
|
||||
&& 0 != *outlk_ad.config[5usize].offset(0isize) as libc::c_int)
|
||||
{
|
||||
ok_to_continue = true;
|
||||
break;
|
||||
}
|
||||
free(url as *mut libc::c_void);
|
||||
url = dc_strdup(outlk_ad.config[5usize]);
|
||||
|
||||
outlk_clean_config(&mut outlk_ad);
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
xml_raw = ptr::null_mut();
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if ok_to_continue {
|
||||
if outlk_ad.out.mail_server.is_empty()
|
||||
|| outlk_ad.out.mail_port == 0
|
||||
|| outlk_ad.out.send_server.is_empty()
|
||||
|| outlk_ad.out.send_port == 0
|
||||
{
|
||||
let r = outlk_ad.out.to_string();
|
||||
warn!(context, "Bad or incomplete autoconfig: {}", r,);
|
||||
free(url as *mut libc::c_void);
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
outlk_clean_config(&mut outlk_ad);
|
||||
|
||||
return None;
|
||||
}
|
||||
}
|
||||
free(url as *mut libc::c_void);
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
outlk_clean_config(&mut outlk_ad);
|
||||
Some(outlk_ad.out)
|
||||
}
|
||||
|
||||
unsafe fn outlk_clean_config(mut outlk_ad: *mut outlk_autodiscover_t) {
|
||||
for i in 0..6 {
|
||||
free((*outlk_ad).config[i] as *mut libc::c_void);
|
||||
(*outlk_ad).config[i] = ptr::null_mut();
|
||||
}
|
||||
}
|
||||
|
||||
fn outlk_autodiscover_text_cb<B: std::io::BufRead>(
|
||||
event: &BytesText,
|
||||
outlk_ad: &mut outlk_autodiscover_t,
|
||||
reader: &quick_xml::Reader<B>,
|
||||
) {
|
||||
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
||||
|
||||
unsafe {
|
||||
free(outlk_ad.config[outlk_ad.tag_config as usize].cast());
|
||||
outlk_ad.config[outlk_ad.tag_config as usize] = val.trim().strdup();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut outlk_autodiscover_t) {
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
|
||||
if tag == "protocol" {
|
||||
if !outlk_ad.config[1].is_null() {
|
||||
let port = dc_atoi_null_is_0(outlk_ad.config[3]);
|
||||
let ssl_on = (!outlk_ad.config[4].is_null()
|
||||
&& strcasecmp(
|
||||
outlk_ad.config[4],
|
||||
b"on\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0) as libc::c_int;
|
||||
let ssl_off = (!outlk_ad.config[4].is_null()
|
||||
&& strcasecmp(
|
||||
outlk_ad.config[4],
|
||||
b"off\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0) as libc::c_int;
|
||||
if strcasecmp(
|
||||
outlk_ad.config[1],
|
||||
b"imap\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0
|
||||
&& outlk_ad.out_imap_set == 0
|
||||
{
|
||||
outlk_ad.out.mail_server = to_string(outlk_ad.config[2]);
|
||||
outlk_ad.out.mail_port = port;
|
||||
if 0 != ssl_on {
|
||||
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
|
||||
} else if 0 != ssl_off {
|
||||
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
|
||||
}
|
||||
outlk_ad.out_imap_set = 1
|
||||
} else if strcasecmp(
|
||||
outlk_ad.config[1usize],
|
||||
b"smtp\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0
|
||||
&& outlk_ad.out_smtp_set == 0
|
||||
{
|
||||
outlk_ad.out.send_server = to_string(outlk_ad.config[2]);
|
||||
outlk_ad.out.send_port = port;
|
||||
if 0 != ssl_on {
|
||||
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
|
||||
} else if 0 != ssl_off {
|
||||
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
|
||||
}
|
||||
outlk_ad.out_smtp_set = 1
|
||||
}
|
||||
}
|
||||
outlk_clean_config(outlk_ad);
|
||||
}
|
||||
outlk_ad.tag_config = 0;
|
||||
}
|
||||
|
||||
fn outlk_autodiscover_starttag_cb(event: &BytesStart, outlk_ad: &mut outlk_autodiscover_t) {
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
|
||||
if tag == "protocol" {
|
||||
unsafe { outlk_clean_config(outlk_ad) };
|
||||
} else if tag == "type" {
|
||||
outlk_ad.tag_config = 1
|
||||
} else if tag == "server" {
|
||||
outlk_ad.tag_config = 2
|
||||
} else if tag == "port" {
|
||||
outlk_ad.tag_config = 3
|
||||
} else if tag == "ssl" {
|
||||
outlk_ad.tag_config = 4
|
||||
} else if tag == "redirecturl" {
|
||||
outlk_ad.tag_config = 5
|
||||
};
|
||||
}
|
||||
662
src/configure/mod.rs
Normal file
662
src/configure/mod.rs
Normal file
@@ -0,0 +1,662 @@
|
||||
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::e2ee;
|
||||
use crate::imap::*;
|
||||
use crate::job::*;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::oauth2::*;
|
||||
use crate::param::Params;
|
||||
|
||||
mod auto_outlook;
|
||||
use auto_outlook::outlk_autodiscover;
|
||||
mod auto_mozilla;
|
||||
use auto_mozilla::moz_autoconfigure;
|
||||
|
||||
macro_rules! progress {
|
||||
($context:tt, $progress:expr) => {
|
||||
assert!(
|
||||
$progress > 0 && $progress <= 1000,
|
||||
"value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
|
||||
);
|
||||
$context.call_cb($crate::events::Event::ConfigureProgress($progress));
|
||||
};
|
||||
}
|
||||
|
||||
// connect
|
||||
pub unsafe fn configure(context: &Context) {
|
||||
if dc_has_ongoing(context) {
|
||||
warn!(context, "There is already another ongoing process running.",);
|
||||
return;
|
||||
}
|
||||
job_kill_action(context, Action::ConfigureImap);
|
||||
job_add(context, Action::ConfigureImap, 0, Params::new(), 0);
|
||||
}
|
||||
|
||||
/// Check if the context is already configured.
|
||||
pub fn dc_is_configured(context: &Context) -> bool {
|
||||
context.sql.get_config_bool(context, "configured")
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Configure JOB
|
||||
******************************************************************************/
|
||||
// the other dc_job_do_DC_JOB_*() functions are declared static in the c-file
|
||||
#[allow(non_snake_case, unused_must_use)]
|
||||
pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
|
||||
let mut success = false;
|
||||
let mut imap_connected_here = false;
|
||||
let mut smtp_connected_here = false;
|
||||
let mut ongoing_allocated_here = false;
|
||||
|
||||
let mut param_autoconfig: Option<LoginParam> = None;
|
||||
if dc_alloc_ongoing(context) {
|
||||
ongoing_allocated_here = true;
|
||||
if !context.sql.is_open() {
|
||||
error!(context, "Cannot configure, database not opened.",);
|
||||
} else {
|
||||
context.inbox.read().unwrap().disconnect(context);
|
||||
context
|
||||
.sentbox_thread
|
||||
.read()
|
||||
.unwrap()
|
||||
.imap
|
||||
.disconnect(context);
|
||||
context
|
||||
.mvbox_thread
|
||||
.read()
|
||||
.unwrap()
|
||||
.imap
|
||||
.disconnect(context);
|
||||
context.smtp.clone().lock().unwrap().disconnect();
|
||||
info!(context, "Configure ...",);
|
||||
|
||||
let s_a = context.running_state.clone();
|
||||
let s = s_a.read().unwrap();
|
||||
|
||||
// Variables that are shared between steps:
|
||||
let mut param = LoginParam::from_database(context, "");
|
||||
// need all vars here to be mutable because rust thinks the same step could be called multiple times
|
||||
// and also initialize, because otherwise rust thinks it's used while unitilized, even if thats not the case as the loop goes only forward
|
||||
let mut param_domain = "undefined.undefined".to_owned();
|
||||
let mut param_addr_urlencoded: String =
|
||||
"Internal Error: this value should never be used".to_owned();
|
||||
let mut keep_flags = std::i32::MAX;
|
||||
|
||||
const STEP_3_INDEX: u8 = 13;
|
||||
let mut step_counter: u8 = 0;
|
||||
while !s.shall_stop_ongoing {
|
||||
step_counter = step_counter + 1;
|
||||
|
||||
let success = match step_counter {
|
||||
// Read login parameters from the database
|
||||
1 => {
|
||||
progress!(context, 1);
|
||||
if param.addr.is_empty() {
|
||||
error!(context, "Please enter an email address.",);
|
||||
}
|
||||
!param.addr.is_empty()
|
||||
}
|
||||
// Step 1: Load the parameters and check email-address and password
|
||||
2 => {
|
||||
if 0 != param.server_flags & 0x2 {
|
||||
// the used oauth2 addr may differ, check this.
|
||||
// if dc_get_oauth2_addr() is not available in the oauth2 implementation,
|
||||
// just use the given one.
|
||||
progress!(context, 10);
|
||||
if let Some(oauth2_addr) =
|
||||
dc_get_oauth2_addr(context, ¶m.addr, ¶m.mail_pw)
|
||||
.and_then(|e| e.parse().ok())
|
||||
{
|
||||
param.addr = oauth2_addr;
|
||||
context
|
||||
.sql
|
||||
.set_config(context, "addr", Some(param.addr.as_str()))
|
||||
.ok();
|
||||
}
|
||||
progress!(context, 20);
|
||||
}
|
||||
true // no oauth? - just continue it's no error
|
||||
}
|
||||
3 => {
|
||||
if let Ok(parsed) = param.addr.parse() {
|
||||
let parsed: EmailAddress = parsed;
|
||||
param_domain = parsed.domain;
|
||||
param_addr_urlencoded =
|
||||
utf8_percent_encode(¶m.addr, NON_ALPHANUMERIC).to_string();
|
||||
true
|
||||
} else {
|
||||
error!(context, "Bad email-address.");
|
||||
false
|
||||
}
|
||||
}
|
||||
// Step 2: Autoconfig
|
||||
4 => {
|
||||
progress!(context, 200);
|
||||
if param.mail_server.is_empty()
|
||||
&& param.mail_port == 0
|
||||
/*&¶m.mail_user.is_empty() -- the user can enter a loginname which is used by autoconfig then */
|
||||
&& param.send_server.is_empty()
|
||||
&& param.send_port == 0
|
||||
&& param.send_user.is_empty()
|
||||
/*&¶m.send_pw.is_empty() -- the password cannot be auto-configured and is no criterion for autoconfig or not */
|
||||
&& param.server_flags & !0x2 == 0
|
||||
{
|
||||
keep_flags = param.server_flags & 0x2;
|
||||
} else {
|
||||
// Autoconfig is not needed so skip it.
|
||||
step_counter = STEP_3_INDEX - 1;
|
||||
}
|
||||
true
|
||||
}
|
||||
/* A. Search configurations from the domain used in the email-address, prefer encrypted */
|
||||
5 => {
|
||||
if param_autoconfig.is_none() {
|
||||
let url = format!(
|
||||
"https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
|
||||
param_domain, param_addr_urlencoded
|
||||
);
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
||||
}
|
||||
true
|
||||
}
|
||||
6 => {
|
||||
progress!(context, 300);
|
||||
if param_autoconfig.is_none() {
|
||||
// the doc does not mention `emailaddress=`, however, Thunderbird adds it, see https://releases.mozilla.org/pub/thunderbird/ , which makes some sense
|
||||
let url = format!(
|
||||
"https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}",
|
||||
param_domain,
|
||||
param_addr_urlencoded
|
||||
);
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
||||
}
|
||||
true
|
||||
}
|
||||
/* Outlook section start ------------- */
|
||||
/* Outlook uses always SSL but different domains (this comment describes the next two steps) */
|
||||
7 => {
|
||||
progress!(context, 310);
|
||||
if param_autoconfig.is_none() {
|
||||
let url = format!(
|
||||
"https://{}{}/autodiscover/autodiscover.xml",
|
||||
"", param_domain
|
||||
);
|
||||
param_autoconfig = outlk_autodiscover(context, &url, ¶m);
|
||||
}
|
||||
true
|
||||
}
|
||||
8 => {
|
||||
progress!(context, 320);
|
||||
if param_autoconfig.is_none() {
|
||||
let url = format!(
|
||||
"https://{}{}/autodiscover/autodiscover.xml",
|
||||
"autodiscover.", param_domain
|
||||
);
|
||||
param_autoconfig = outlk_autodiscover(context, &url, ¶m);
|
||||
}
|
||||
true
|
||||
}
|
||||
/* ----------- Outlook section end */
|
||||
9 => {
|
||||
progress!(context, 330);
|
||||
if param_autoconfig.is_none() {
|
||||
let url = format!(
|
||||
"http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
|
||||
param_domain, param_addr_urlencoded
|
||||
);
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
||||
}
|
||||
true
|
||||
}
|
||||
10 => {
|
||||
progress!(context, 340);
|
||||
if param_autoconfig.is_none() {
|
||||
// do not transfer the email-address unencrypted
|
||||
let url = format!(
|
||||
"http://{}/.well-known/autoconfig/mail/config-v1.1.xml",
|
||||
param_domain
|
||||
);
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
||||
}
|
||||
true
|
||||
}
|
||||
/* B. If we have no configuration yet, search configuration in Thunderbird's centeral database */
|
||||
11 => {
|
||||
progress!(context, 350);
|
||||
if param_autoconfig.is_none() {
|
||||
/* always SSL for Thunderbird's database */
|
||||
let url =
|
||||
format!("https://autoconfig.thunderbird.net/v1.1/{}", param_domain);
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
||||
}
|
||||
true
|
||||
}
|
||||
/* C. Do we have any result? */
|
||||
12 => {
|
||||
progress!(context, 500);
|
||||
if let Some(ref cfg) = param_autoconfig {
|
||||
info!(context, "Got autoconfig: {}", &cfg);
|
||||
if !cfg.mail_user.is_empty() {
|
||||
param.mail_user = cfg.mail_user.clone();
|
||||
}
|
||||
param.mail_server = cfg.mail_server.clone(); /* all other values are always NULL when entering autoconfig */
|
||||
param.mail_port = cfg.mail_port;
|
||||
param.send_server = cfg.send_server.clone();
|
||||
param.send_port = cfg.send_port;
|
||||
param.send_user = cfg.send_user.clone();
|
||||
param.server_flags = cfg.server_flags;
|
||||
/* although param_autoconfig's data are no longer needed from, it is important to keep the object as
|
||||
we may enter "deep guessing" if we could not read a configuration */
|
||||
}
|
||||
param.server_flags |= keep_flags;
|
||||
true
|
||||
}
|
||||
// Step 3: Fill missing fields with defaults
|
||||
13 => {
|
||||
// if you move this, don't forget to update STEP_3_INDEX, too
|
||||
if param.mail_server.is_empty() {
|
||||
param.mail_server = format!("imap.{}", param_domain,)
|
||||
}
|
||||
if param.mail_port == 0 {
|
||||
param.mail_port = if 0 != param.server_flags & (0x100 | 0x400) {
|
||||
143
|
||||
} else {
|
||||
993
|
||||
}
|
||||
}
|
||||
if param.mail_user.is_empty() {
|
||||
param.mail_user = param.addr.clone();
|
||||
}
|
||||
if param.send_server.is_empty() && !param.mail_server.is_empty() {
|
||||
param.send_server = param.mail_server.clone();
|
||||
if param.send_server.starts_with("imap.") {
|
||||
param.send_server = param.send_server.replacen("imap", "smtp", 1);
|
||||
}
|
||||
}
|
||||
if param.send_port == 0 {
|
||||
param.send_port =
|
||||
if 0 != param.server_flags & DC_LP_SMTP_SOCKET_STARTTLS as i32 {
|
||||
587
|
||||
} else if 0 != param.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 {
|
||||
25
|
||||
} else {
|
||||
465
|
||||
}
|
||||
}
|
||||
if param.send_user.is_empty() && !param.mail_user.is_empty() {
|
||||
param.send_user = param.mail_user.clone();
|
||||
}
|
||||
if param.send_pw.is_empty() && !param.mail_pw.is_empty() {
|
||||
param.send_pw = param.mail_pw.clone()
|
||||
}
|
||||
if !dc_exactly_one_bit_set(param.server_flags & DC_LP_AUTH_FLAGS as i32) {
|
||||
param.server_flags &= !(DC_LP_AUTH_FLAGS as i32);
|
||||
param.server_flags |= DC_LP_AUTH_NORMAL as i32
|
||||
}
|
||||
if !dc_exactly_one_bit_set(
|
||||
param.server_flags & DC_LP_IMAP_SOCKET_FLAGS as i32,
|
||||
) {
|
||||
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32);
|
||||
param.server_flags |= if param.send_port == 143 {
|
||||
DC_LP_IMAP_SOCKET_STARTTLS as i32
|
||||
} else {
|
||||
DC_LP_IMAP_SOCKET_SSL as i32
|
||||
}
|
||||
}
|
||||
if !dc_exactly_one_bit_set(
|
||||
param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32),
|
||||
) {
|
||||
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
|
||||
param.server_flags |= if param.send_port == 587 {
|
||||
DC_LP_SMTP_SOCKET_STARTTLS as i32
|
||||
} else if param.send_port == 25 {
|
||||
DC_LP_SMTP_SOCKET_PLAIN as i32
|
||||
} else {
|
||||
DC_LP_SMTP_SOCKET_SSL as i32
|
||||
}
|
||||
}
|
||||
/* do we have a complete configuration? */
|
||||
if param.mail_server.is_empty()
|
||||
|| param.mail_port == 0
|
||||
|| param.mail_user.is_empty()
|
||||
|| param.mail_pw.is_empty()
|
||||
|| param.send_server.is_empty()
|
||||
|| param.send_port == 0
|
||||
|| param.send_user.is_empty()
|
||||
|| param.send_pw.is_empty()
|
||||
|| param.server_flags == 0
|
||||
{
|
||||
error!(context, "Account settings incomplete.");
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
14 => {
|
||||
progress!(context, 600);
|
||||
/* try to connect to IMAP - if we did not got an autoconfig,
|
||||
do some further tries with different settings and username variations */
|
||||
let ok_to_continue8;
|
||||
let mut username_variation = 0;
|
||||
loop {
|
||||
if !(username_variation <= 1) {
|
||||
ok_to_continue8 = true;
|
||||
break;
|
||||
}
|
||||
info!(context, "Trying: {}", ¶m);
|
||||
|
||||
if context.inbox.read().unwrap().connect(context, ¶m) {
|
||||
ok_to_continue8 = true;
|
||||
break;
|
||||
}
|
||||
if !param_autoconfig.is_none() {
|
||||
ok_to_continue8 = false;
|
||||
break;
|
||||
}
|
||||
// probe STARTTLS/993
|
||||
if s.shall_stop_ongoing {
|
||||
ok_to_continue8 = false;
|
||||
break;
|
||||
}
|
||||
progress!(context, 650 + username_variation * 30);
|
||||
param.server_flags &= !(0x100 | 0x200 | 0x400);
|
||||
param.server_flags |= 0x100;
|
||||
info!(context, "Trying: {}", ¶m);
|
||||
|
||||
if context.inbox.read().unwrap().connect(context, ¶m) {
|
||||
ok_to_continue8 = true;
|
||||
break;
|
||||
}
|
||||
// probe STARTTLS/143
|
||||
if s.shall_stop_ongoing {
|
||||
ok_to_continue8 = false;
|
||||
break;
|
||||
}
|
||||
progress!(context, 660 + username_variation * 30);
|
||||
param.mail_port = 143;
|
||||
info!(context, "Trying: {}", ¶m);
|
||||
|
||||
if context.inbox.read().unwrap().connect(context, ¶m) {
|
||||
ok_to_continue8 = true;
|
||||
break;
|
||||
}
|
||||
if 0 != username_variation {
|
||||
ok_to_continue8 = false;
|
||||
break;
|
||||
}
|
||||
// next probe round with only the localpart of the email-address as the loginname
|
||||
if s.shall_stop_ongoing {
|
||||
ok_to_continue8 = false;
|
||||
break;
|
||||
}
|
||||
progress!(context, 670 + username_variation * 30);
|
||||
param.server_flags &= !(0x100 | 0x200 | 0x400);
|
||||
param.server_flags |= 0x200;
|
||||
param.mail_port = 993;
|
||||
|
||||
if let Some(at) = param.mail_user.find('@') {
|
||||
param.mail_user = param.mail_user.split_at(at).0.to_string();
|
||||
}
|
||||
if let Some(at) = param.send_user.find('@') {
|
||||
param.send_user = param.send_user.split_at(at).0.to_string();
|
||||
}
|
||||
|
||||
username_variation += 1
|
||||
}
|
||||
if ok_to_continue8 {
|
||||
// success, so we are connected and should disconnect in cleanup
|
||||
imap_connected_here = true;
|
||||
}
|
||||
ok_to_continue8
|
||||
}
|
||||
15 => {
|
||||
progress!(context, 800);
|
||||
let success;
|
||||
/* try to connect to SMTP - if we did not got an autoconfig, the first try was SSL-465 and we do a second try with STARTTLS-587 */
|
||||
if !context
|
||||
.smtp
|
||||
.clone()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.connect(context, ¶m)
|
||||
{
|
||||
if !param_autoconfig.is_none() {
|
||||
success = false;
|
||||
} else if s.shall_stop_ongoing {
|
||||
success = false;
|
||||
} else {
|
||||
progress!(context, 850);
|
||||
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
|
||||
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
|
||||
param.send_port = 587;
|
||||
info!(context, "Trying: {}", ¶m);
|
||||
|
||||
if !context
|
||||
.smtp
|
||||
.clone()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.connect(context, ¶m)
|
||||
{
|
||||
if s.shall_stop_ongoing {
|
||||
success = false;
|
||||
} else {
|
||||
progress!(context, 860);
|
||||
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
|
||||
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
|
||||
param.send_port = 25;
|
||||
info!(context, "Trying: {}", ¶m);
|
||||
|
||||
if !context
|
||||
.smtp
|
||||
.clone()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.connect(context, ¶m)
|
||||
{
|
||||
success = false;
|
||||
} else {
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
success = true;
|
||||
}
|
||||
if success {
|
||||
smtp_connected_here = true;
|
||||
}
|
||||
success
|
||||
}
|
||||
16 => {
|
||||
progress!(context, 900);
|
||||
let flags: libc::c_int = if 0
|
||||
!= context
|
||||
.sql
|
||||
.get_config_int(context, "mvbox_watch")
|
||||
.unwrap_or_else(|| 1)
|
||||
|| 0 != context
|
||||
.sql
|
||||
.get_config_int(context, "mvbox_move")
|
||||
.unwrap_or_else(|| 1)
|
||||
{
|
||||
DC_CREATE_MVBOX as i32
|
||||
} else {
|
||||
0
|
||||
};
|
||||
context
|
||||
.inbox
|
||||
.read()
|
||||
.unwrap()
|
||||
.configure_folders(context, flags);
|
||||
true
|
||||
}
|
||||
17 => {
|
||||
progress!(context, 910);
|
||||
/* configuration success - write back the configured parameters with the "configured_" prefix; also write the "configured"-flag */
|
||||
param
|
||||
.save_to_database(
|
||||
context,
|
||||
"configured_", /*the trailing underscore is correct*/
|
||||
)
|
||||
.ok();
|
||||
|
||||
context.sql.set_config_bool(context, "configured", true);
|
||||
true
|
||||
}
|
||||
18 => {
|
||||
progress!(context, 920);
|
||||
// we generate the keypair just now - we could also postpone this until the first message is sent, however,
|
||||
// this may result in a unexpected and annoying delay when the user sends his very first message
|
||||
// (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow.
|
||||
e2ee::ensure_secret_key_exists(context);
|
||||
success = true;
|
||||
info!(context, "Configure completed.");
|
||||
progress!(context, 940);
|
||||
break; // We are done here
|
||||
}
|
||||
|
||||
_ => {
|
||||
error!(context, "Internal error: step counter out of bound",);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if !success {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if imap_connected_here {
|
||||
context.inbox.read().unwrap().disconnect(context);
|
||||
}
|
||||
if smtp_connected_here {
|
||||
context.smtp.clone().lock().unwrap().disconnect();
|
||||
}
|
||||
|
||||
/*
|
||||
if !success {
|
||||
// disconnect if configure did not succeed
|
||||
if imap_connected_here {
|
||||
// context.inbox.read().unwrap().disconnect(context);
|
||||
}
|
||||
if smtp_connected_here {
|
||||
// context.smtp.clone().lock().unwrap().disconnect();
|
||||
}
|
||||
} else {
|
||||
assert!(imap_connected_here && smtp_connected_here);
|
||||
info!(
|
||||
context,
|
||||
0, "Keeping IMAP/SMTP connections open after successful configuration"
|
||||
);
|
||||
}
|
||||
*/
|
||||
if ongoing_allocated_here {
|
||||
dc_free_ongoing(context);
|
||||
}
|
||||
|
||||
progress!(context, if success { 1000 } else { 0 });
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Ongoing process allocation/free/check
|
||||
******************************************************************************/
|
||||
|
||||
pub fn dc_alloc_ongoing(context: &Context) -> bool {
|
||||
if dc_has_ongoing(context) {
|
||||
warn!(context, "There is already another ongoing process running.",);
|
||||
|
||||
false
|
||||
} else {
|
||||
let s_a = context.running_state.clone();
|
||||
let mut s = s_a.write().unwrap();
|
||||
|
||||
s.ongoing_running = true;
|
||||
s.shall_stop_ongoing = false;
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_free_ongoing(context: &Context) {
|
||||
let s_a = context.running_state.clone();
|
||||
let mut s = s_a.write().unwrap();
|
||||
|
||||
s.ongoing_running = false;
|
||||
s.shall_stop_ongoing = true;
|
||||
}
|
||||
|
||||
fn dc_has_ongoing(context: &Context) -> bool {
|
||||
let s_a = context.running_state.clone();
|
||||
let s = s_a.read().unwrap();
|
||||
|
||||
s.ongoing_running || !s.shall_stop_ongoing
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Connect to configured account
|
||||
******************************************************************************/
|
||||
pub fn dc_connect_to_configured_imap(context: &Context, imap: &Imap) -> libc::c_int {
|
||||
let mut ret_connected = 0;
|
||||
|
||||
if imap.is_connected() {
|
||||
ret_connected = 1
|
||||
} else if context
|
||||
.sql
|
||||
.get_config_int(context, "configured")
|
||||
.unwrap_or_default()
|
||||
== 0
|
||||
{
|
||||
warn!(context, "Not configured, cannot connect.",);
|
||||
} else {
|
||||
let param = LoginParam::from_database(context, "configured_");
|
||||
// the trailing underscore is correct
|
||||
|
||||
if imap.connect(context, ¶m) {
|
||||
ret_connected = 2;
|
||||
}
|
||||
}
|
||||
|
||||
ret_connected
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Configure a Context
|
||||
******************************************************************************/
|
||||
|
||||
/// Signal an ongoing process to stop.
|
||||
pub fn dc_stop_ongoing_process(context: &Context) {
|
||||
let s_a = context.running_state.clone();
|
||||
let mut s = s_a.write().unwrap();
|
||||
|
||||
if s.ongoing_running && !s.shall_stop_ongoing {
|
||||
info!(context, "Signaling the ongoing process to stop ASAP.",);
|
||||
s.shall_stop_ongoing = true;
|
||||
} else {
|
||||
info!(context, "No ongoing process to stop.",);
|
||||
};
|
||||
}
|
||||
|
||||
pub fn read_autoconf_file(context: &Context, url: &str) -> *mut libc::c_char {
|
||||
info!(context, "Testing {} ...", url);
|
||||
|
||||
match reqwest::Client::new()
|
||||
.get(url)
|
||||
.send()
|
||||
.and_then(|mut res| res.text())
|
||||
{
|
||||
Ok(res) => unsafe { res.strdup() },
|
||||
Err(_err) => {
|
||||
info!(context, "Can\'t read file.",);
|
||||
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
547
src/constants.rs
547
src/constants.rs
@@ -1,102 +1,129 @@
|
||||
//! Constants
|
||||
#![allow(non_camel_case_types)]
|
||||
use num_traits::{FromPrimitive, ToPrimitive};
|
||||
use rusqlite as sql;
|
||||
use rusqlite::types::*;
|
||||
#![allow(non_camel_case_types, dead_code)]
|
||||
|
||||
pub const DC_VERSION_STR: &'static [u8; 14] = b"1.0.0-alpha.3\x00";
|
||||
use deltachat_derive::*;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
pub const DC_MOVE_STATE_MOVING: u32 = 3;
|
||||
pub const DC_MOVE_STATE_STAY: u32 = 2;
|
||||
pub const DC_MOVE_STATE_PENDING: u32 = 1;
|
||||
pub const DC_MOVE_STATE_UNDEFINED: u32 = 0;
|
||||
lazy_static! {
|
||||
pub static ref DC_VERSION_STR: String = env!("CARGO_PKG_VERSION").to_string();
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
|
||||
pub enum MoveState {
|
||||
Undefined = 0,
|
||||
Pending = 1,
|
||||
Stay = 2,
|
||||
Moving = 3,
|
||||
}
|
||||
|
||||
impl Default for MoveState {
|
||||
fn default() -> Self {
|
||||
MoveState::Undefined
|
||||
}
|
||||
}
|
||||
|
||||
// some defaults
|
||||
const DC_E2EE_DEFAULT_ENABLED: i32 = 1;
|
||||
pub const DC_MDNS_DEFAULT_ENABLED: i32 = 1;
|
||||
const DC_INBOX_WATCH_DEFAULT: i32 = 1;
|
||||
const DC_SENTBOX_WATCH_DEFAULT: i32 = 1;
|
||||
const DC_MVBOX_WATCH_DEFAULT: i32 = 1;
|
||||
const DC_MVBOX_MOVE_DEFAULT: i32 = 1;
|
||||
|
||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
|
||||
#[repr(u8)]
|
||||
pub enum Blocked {
|
||||
Not = 0,
|
||||
Manually = 1,
|
||||
Deaddrop = 2,
|
||||
}
|
||||
|
||||
impl Default for Blocked {
|
||||
fn default() -> Self {
|
||||
Blocked::Not
|
||||
}
|
||||
}
|
||||
|
||||
pub const DC_IMAP_SEEN: u32 = 0x1;
|
||||
|
||||
pub const DC_HANDSHAKE_CONTINUE_NORMAL_PROCESSING: i32 = 0x01;
|
||||
pub const DC_HANDSHAKE_STOP_NORMAL_PROCESSING: i32 = 0x02;
|
||||
pub const DC_HANDSHAKE_ADD_DELETE_JOB: i32 = 0x04;
|
||||
|
||||
pub const DC_GCL_ARCHIVED_ONLY: usize = 0x01;
|
||||
pub const DC_GCL_NO_SPECIALS: usize = 0x02;
|
||||
pub const DC_GCL_ADD_ALLDONE_HINT: usize = 0x04;
|
||||
|
||||
pub const DC_GCM_ADDDAYMARKER: usize = 0x01;
|
||||
const DC_GCM_ADDDAYMARKER: usize = 0x01;
|
||||
|
||||
pub const DC_GCL_VERIFIED_ONLY: usize = 0x01;
|
||||
pub const DC_GCL_ADD_SELF: usize = 0x02;
|
||||
|
||||
/// param1 is a directory where the keys are written to
|
||||
pub const DC_IMEX_EXPORT_SELF_KEYS: usize = 1;
|
||||
const DC_IMEX_EXPORT_SELF_KEYS: usize = 1;
|
||||
/// param1 is a directory where the keys are searched in and read from
|
||||
pub const DC_IMEX_IMPORT_SELF_KEYS: usize = 2;
|
||||
const DC_IMEX_IMPORT_SELF_KEYS: usize = 2;
|
||||
/// param1 is a directory where the backup is written to
|
||||
pub const DC_IMEX_EXPORT_BACKUP: usize = 11;
|
||||
const DC_IMEX_EXPORT_BACKUP: usize = 11;
|
||||
/// param1 is the file with the backup to import
|
||||
pub const DC_IMEX_IMPORT_BACKUP: usize = 12;
|
||||
|
||||
/// id=contact
|
||||
pub const DC_QR_ASK_VERIFYCONTACT: usize = 200;
|
||||
/// text1=groupname
|
||||
pub const DC_QR_ASK_VERIFYGROUP: usize = 202;
|
||||
/// id=contact
|
||||
pub const DC_QR_FPR_OK: usize = 210;
|
||||
/// id=contact
|
||||
pub const DC_QR_FPR_MISMATCH: usize = 220;
|
||||
/// test1=formatted fingerprint
|
||||
pub const DC_QR_FPR_WITHOUT_ADDR: usize = 230;
|
||||
/// id=contact
|
||||
pub const DC_QR_ADDR: usize = 320;
|
||||
/// text1=text
|
||||
pub const DC_QR_TEXT: usize = 330;
|
||||
/// text1=URL
|
||||
pub const DC_QR_URL: usize = 332;
|
||||
/// text1=error string
|
||||
pub const DC_QR_ERROR: usize = 400;
|
||||
const DC_IMEX_IMPORT_BACKUP: usize = 12;
|
||||
|
||||
/// virtual chat showing all messages belonging to chats flagged with chats.blocked=2
|
||||
pub const DC_CHAT_ID_DEADDROP: usize = 1;
|
||||
pub(crate) const DC_CHAT_ID_DEADDROP: u32 = 1;
|
||||
/// messages that should be deleted get this chat_id; the messages are deleted from the working thread later then. This is also needed as rfc724_mid should be preset as long as the message is not deleted on the server (otherwise it is downloaded again)
|
||||
pub const DC_CHAT_ID_TRASH: usize = 3;
|
||||
pub const DC_CHAT_ID_TRASH: u32 = 3;
|
||||
/// a message is just in creation but not yet assigned to a chat (eg. we may need the message ID to set up blobs; this avoids unready message to be sent and shown)
|
||||
pub const DC_CHAT_ID_MSGS_IN_CREATION: usize = 4;
|
||||
const DC_CHAT_ID_MSGS_IN_CREATION: u32 = 4;
|
||||
/// virtual chat showing all messages flagged with msgs.starred=2
|
||||
pub const DC_CHAT_ID_STARRED: usize = 5;
|
||||
pub const DC_CHAT_ID_STARRED: u32 = 5;
|
||||
/// only an indicator in a chatlist
|
||||
pub const DC_CHAT_ID_ARCHIVED_LINK: usize = 6;
|
||||
pub const DC_CHAT_ID_ARCHIVED_LINK: u32 = 6;
|
||||
/// only an indicator in a chatlist
|
||||
pub const DC_CHAT_ID_ALLDONE_HINT: usize = 7;
|
||||
pub const DC_CHAT_ID_ALLDONE_HINT: u32 = 7;
|
||||
/// larger chat IDs are "real" chats, their messages are "real" messages.
|
||||
pub const DC_CHAT_ID_LAST_SPECIAL: usize = 9;
|
||||
pub const DC_CHAT_ID_LAST_SPECIAL: u32 = 9;
|
||||
|
||||
pub const DC_CHAT_TYPE_UNDEFINED: i32 = 0;
|
||||
pub const DC_CHAT_TYPE_SINGLE: i32 = 100;
|
||||
pub const DC_CHAT_TYPE_GROUP: i32 = 120;
|
||||
pub const DC_CHAT_TYPE_VERIFIED_GROUP: i32 = 130;
|
||||
#[derive(
|
||||
Debug,
|
||||
Display,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
FromPrimitive,
|
||||
ToPrimitive,
|
||||
FromSql,
|
||||
ToSql,
|
||||
IntoStaticStr,
|
||||
)]
|
||||
#[repr(u32)]
|
||||
pub enum Chattype {
|
||||
Undefined = 0,
|
||||
Single = 100,
|
||||
Group = 120,
|
||||
VerifiedGroup = 130,
|
||||
}
|
||||
|
||||
pub const DC_MSG_ID_MARKER1: usize = 1;
|
||||
pub const DC_MSG_ID_DAYMARKER: usize = 9;
|
||||
pub const DC_MSG_ID_LAST_SPECIAL: usize = 9;
|
||||
impl Default for Chattype {
|
||||
fn default() -> Self {
|
||||
Chattype::Undefined
|
||||
}
|
||||
}
|
||||
|
||||
pub const DC_STATE_UNDEFINED: i32 = 0;
|
||||
pub const DC_STATE_IN_FRESH: i32 = 10;
|
||||
pub const DC_STATE_IN_NOTICED: i32 = 13;
|
||||
pub const DC_STATE_IN_SEEN: i32 = 16;
|
||||
pub const DC_STATE_OUT_PREPARING: i32 = 18;
|
||||
pub const DC_STATE_OUT_DRAFT: i32 = 19;
|
||||
pub const DC_STATE_OUT_PENDING: i32 = 20;
|
||||
pub const DC_STATE_OUT_FAILED: i32 = 24;
|
||||
/// to check if a mail was sent, use dc_msg_is_sent()
|
||||
pub const DC_STATE_OUT_DELIVERED: i32 = 26;
|
||||
pub const DC_STATE_OUT_MDN_RCVD: i32 = 28;
|
||||
pub const DC_MSG_ID_MARKER1: u32 = 1;
|
||||
const DC_MSG_ID_DAYMARKER: u32 = 9;
|
||||
pub const DC_MSG_ID_LAST_SPECIAL: u32 = 9;
|
||||
|
||||
/// approx. max. length returned by dc_msg_get_text()
|
||||
pub const DC_MAX_GET_TEXT_LEN: usize = 30000;
|
||||
const DC_MAX_GET_TEXT_LEN: usize = 30000;
|
||||
/// approx. max. length returned by dc_get_msg_info()
|
||||
pub const DC_MAX_GET_INFO_LEN: usize = 100000;
|
||||
const DC_MAX_GET_INFO_LEN: usize = 100000;
|
||||
|
||||
pub const DC_CONTACT_ID_UNDEFINED: usize = 0;
|
||||
pub const DC_CONTACT_ID_SELF: usize = 1;
|
||||
pub const DC_CONTACT_ID_DEVICE: usize = 2;
|
||||
pub const DC_CONTACT_ID_LAST_SPECIAL: usize = 9;
|
||||
|
||||
pub const DC_TEXT1_DRAFT: usize = 1;
|
||||
pub const DC_TEXT1_USERNAME: usize = 2;
|
||||
pub const DC_TEXT1_SELF: usize = 3;
|
||||
pub const DC_CONTACT_ID_UNDEFINED: u32 = 0;
|
||||
pub const DC_CONTACT_ID_SELF: u32 = 1;
|
||||
const DC_CONTACT_ID_DEVICE: u32 = 2;
|
||||
pub const DC_CONTACT_ID_LAST_SPECIAL: u32 = 9;
|
||||
|
||||
pub const DC_CREATE_MVBOX: usize = 1;
|
||||
|
||||
@@ -106,7 +133,7 @@ pub const DC_CREATE_MVBOX: usize = 1;
|
||||
// via dc_set_config() using the key "server_flags".
|
||||
|
||||
/// Force OAuth2 authorization. This flag does not skip automatic configuration.
|
||||
/// Before calling dc_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().
|
||||
pub const DC_LP_AUTH_OAUTH2: usize = 0x2;
|
||||
|
||||
@@ -147,7 +174,13 @@ pub const DC_LP_IMAP_SOCKET_FLAGS: usize =
|
||||
pub const DC_LP_SMTP_SOCKET_FLAGS: usize =
|
||||
(DC_LP_SMTP_SOCKET_STARTTLS | DC_LP_SMTP_SOCKET_SSL | DC_LP_SMTP_SOCKET_PLAIN);
|
||||
|
||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
||||
// QR code scanning (view from Bob, the joiner)
|
||||
pub const DC_VC_AUTH_REQUIRED: i32 = 2;
|
||||
pub const DC_VC_CONTACT_CONFIRM: i32 = 6;
|
||||
pub const DC_BOB_ERROR: i32 = 0;
|
||||
pub const DC_BOB_SUCCESS: i32 = 1;
|
||||
|
||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
|
||||
#[repr(i32)]
|
||||
pub enum Viewtype {
|
||||
Unknown = 0,
|
||||
@@ -192,6 +225,12 @@ pub enum Viewtype {
|
||||
File = 60,
|
||||
}
|
||||
|
||||
impl Default for Viewtype {
|
||||
fn default() -> Self {
|
||||
Viewtype::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -202,323 +241,70 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for Viewtype {
|
||||
fn to_sql(&self) -> sql::Result<ToSqlOutput> {
|
||||
let num: i64 = self
|
||||
.to_i64()
|
||||
.expect("impossible: Viewtype -> i64 conversion failed");
|
||||
|
||||
Ok(ToSqlOutput::Owned(Value::Integer(num)))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for Viewtype {
|
||||
fn column_result(col: ValueRef) -> FromSqlResult<Self> {
|
||||
let inner = FromSql::column_result(col)?;
|
||||
FromPrimitive::from_i64(inner).ok_or(FromSqlError::InvalidType)
|
||||
}
|
||||
}
|
||||
|
||||
// These constants are used as events
|
||||
// reported to the callback given to dc_context_new().
|
||||
// If you do not want to handle an event, it is always safe to return 0,
|
||||
// so there is no need to add a "case" for every event.
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
||||
#[repr(u32)]
|
||||
pub enum Event {
|
||||
/// The library-user may write an informational string to the log.
|
||||
/// Passed to the callback given to dc_context_new().
|
||||
/// This event should not be reported to the end-user using a popup or something like that.
|
||||
/// @param data1 0
|
||||
/// @param data2 (const char*) Info string in english language.
|
||||
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||
/// @return 0
|
||||
INFO = 100,
|
||||
|
||||
/// Emitted when SMTP connection is established and login was successful.
|
||||
///
|
||||
/// @param data1 0
|
||||
/// @param data2 (const char*) Info string in english language.
|
||||
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||
/// @return 0
|
||||
SMTP_CONNECTED = 101,
|
||||
|
||||
/// Emitted when IMAP connection is established and login was successful.
|
||||
///
|
||||
/// @param data1 0
|
||||
/// @param data2 (const char*) Info string in english language.
|
||||
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||
/// @return 0
|
||||
IMAP_CONNECTED = 102,
|
||||
|
||||
/// Emitted when a message was successfully sent to the SMTP server.
|
||||
///
|
||||
/// @param data1 0
|
||||
/// @param data2 (const char*) Info string in english language.
|
||||
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||
/// @return 0
|
||||
SMTP_MESSAGE_SENT = 103,
|
||||
|
||||
/// The library-user should write a warning string to the log.
|
||||
/// Passed to the callback given to dc_context_new().
|
||||
///
|
||||
/// This event should not be reported to the end-user using a popup or something like that.
|
||||
///
|
||||
/// @param data1 0
|
||||
/// @param data2 (const char*) Warning string in english language.
|
||||
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||
/// @return 0
|
||||
WARNING = 300,
|
||||
|
||||
/// The library-user should report an error to the end-user.
|
||||
/// Passed to the callback given to dc_context_new().
|
||||
///
|
||||
/// As most things are asynchronous, things may go wrong at any time and the user
|
||||
/// should not be disturbed by a dialog or so. Instead, use a bubble or so.
|
||||
///
|
||||
/// However, for ongoing processes (eg. dc_configure())
|
||||
/// or for functions that are expected to fail (eg. dc_continue_key_transfer())
|
||||
/// it might be better to delay showing these events until the function has really
|
||||
/// failed (returned false). It should be sufficient to report only the _last_ error
|
||||
/// in a messasge box then.
|
||||
///
|
||||
/// @param data1 0
|
||||
/// @param data2 (const char*) Error string, always set, never NULL. Frequent error strings are
|
||||
/// localized using #DC_EVENT_GET_STRING, however, most error strings will be in english language.
|
||||
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||
/// @return 0
|
||||
ERROR = 400,
|
||||
|
||||
/// An action cannot be performed because there is no network available.
|
||||
///
|
||||
/// The library will typically try over after a some time
|
||||
/// and when dc_maybe_network() is called.
|
||||
///
|
||||
/// Network errors should be reported to users in a non-disturbing way,
|
||||
/// however, as network errors may come in a sequence,
|
||||
/// it is not useful to raise each an every error to the user.
|
||||
/// For this purpose, data1 is set to 1 if the error is probably worth reporting.
|
||||
///
|
||||
/// Moreover, if the UI detects that the device is offline,
|
||||
/// it is probably more useful to report this to the user
|
||||
/// instead of the string from data2.
|
||||
///
|
||||
/// @param data1 (int) 1=first/new network error, should be reported the user;
|
||||
/// 0=subsequent network error, should be logged only
|
||||
/// @param data2 (const char*) Error string, always set, never NULL.
|
||||
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||
/// @return 0
|
||||
ERROR_NETWORK = 401,
|
||||
|
||||
/// An action cannot be performed because the user is not in the group.
|
||||
/// Reported eg. after a call to
|
||||
/// dc_set_chat_name(), dc_set_chat_profile_image(),
|
||||
/// dc_add_contact_to_chat(), dc_remove_contact_from_chat(),
|
||||
/// dc_send_text_msg() or another sending function.
|
||||
///
|
||||
/// @param data1 0
|
||||
/// @param data2 (const char*) Info string in english language.
|
||||
/// Must not be free()'d or modified
|
||||
/// and is valid only until the callback returns.
|
||||
/// @return 0
|
||||
ERROR_SELF_NOT_IN_GROUP = 410,
|
||||
|
||||
/// Messages or chats changed. One or more messages or chats changed for various
|
||||
/// reasons in the database:
|
||||
/// - Messages sent, received or removed
|
||||
/// - Chats created, deleted or archived
|
||||
/// - A draft has been set
|
||||
///
|
||||
/// @param data1 (int) chat_id for single added messages
|
||||
/// @param data2 (int) msg_id for single added messages
|
||||
/// @return 0
|
||||
MSGS_CHANGED = 2000,
|
||||
|
||||
/// There is a fresh message. Typically, the user will show an notification
|
||||
/// when receiving this message.
|
||||
///
|
||||
/// There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
|
||||
///
|
||||
/// @param data1 (int) chat_id
|
||||
/// @param data2 (int) msg_id
|
||||
/// @return 0
|
||||
INCOMING_MSG = 2005,
|
||||
|
||||
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
|
||||
/// DC_STATE_OUT_DELIVERED, see dc_msg_get_state().
|
||||
///
|
||||
/// @param data1 (int) chat_id
|
||||
/// @param data2 (int) msg_id
|
||||
/// @return 0
|
||||
MSG_DELIVERED = 2010,
|
||||
|
||||
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
|
||||
/// DC_STATE_OUT_FAILED, see dc_msg_get_state().
|
||||
///
|
||||
/// @param data1 (int) chat_id
|
||||
/// @param data2 (int) msg_id
|
||||
/// @return 0
|
||||
MSG_FAILED = 2012,
|
||||
|
||||
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
|
||||
/// DC_STATE_OUT_MDN_RCVD, see dc_msg_get_state().
|
||||
///
|
||||
/// @param data1 (int) chat_id
|
||||
/// @param data2 (int) msg_id
|
||||
/// @return 0
|
||||
MSG_READ = 2015,
|
||||
|
||||
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
|
||||
/// Or the verify state of a chat has changed.
|
||||
/// See dc_set_chat_name(), dc_set_chat_profile_image(), dc_add_contact_to_chat()
|
||||
/// and dc_remove_contact_from_chat().
|
||||
///
|
||||
/// @param data1 (int) chat_id
|
||||
/// @param data2 0
|
||||
/// @return 0
|
||||
CHAT_MODIFIED = 2020,
|
||||
|
||||
/// Contact(s) created, renamed, blocked or deleted.
|
||||
///
|
||||
/// @param data1 (int) If not 0, this is the contact_id of an added contact that should be selected.
|
||||
/// @param data2 0
|
||||
/// @return 0
|
||||
CONTACTS_CHANGED = 2030,
|
||||
|
||||
/// Location of one or more contact has changed.
|
||||
///
|
||||
/// @param data1 (int) contact_id of the contact for which the location has changed.
|
||||
/// If the locations of several contacts have been changed,
|
||||
/// eg. after calling dc_delete_all_locations(), this parameter is set to 0.
|
||||
/// @param data2 0
|
||||
/// @return 0
|
||||
LOCATION_CHANGED = 2035,
|
||||
|
||||
/// Inform about the configuration progress started by dc_configure().
|
||||
///
|
||||
/// @param data1 (int) 0=error, 1-999=progress in permille, 1000=success and done
|
||||
/// @param data2 0
|
||||
/// @return 0
|
||||
CONFIGURE_PROGRESS = 2041,
|
||||
|
||||
/// Inform about the import/export progress started by dc_imex().
|
||||
///
|
||||
/// @param data1 (int) 0=error, 1-999=progress in permille, 1000=success and done
|
||||
/// @param data2 0
|
||||
/// @return 0
|
||||
IMEX_PROGRESS = 2051,
|
||||
|
||||
/// A file has been exported. A file has been written by dc_imex().
|
||||
/// This event may be sent multiple times by a single call to dc_imex().
|
||||
///
|
||||
/// A typical purpose for a handler of this event may be to make the file public to some system
|
||||
/// services.
|
||||
///
|
||||
/// @param data1 (const char*) Path and file name.
|
||||
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||
/// @param data2 0
|
||||
/// @return 0
|
||||
IMEX_FILE_WRITTEN = 2052,
|
||||
|
||||
/// Progress information of a secure-join handshake from the view of the inviter
|
||||
/// (Alice, the person who shows the QR code).
|
||||
///
|
||||
/// These events are typically sent after a joiner has scanned the QR code
|
||||
/// generated by dc_get_securejoin_qr().
|
||||
///
|
||||
/// @param data1 (int) ID of the contact that wants to join.
|
||||
/// @param data2 (int) Progress as:
|
||||
/// 300=vg-/vc-request received, typically shown as "bob@addr joins".
|
||||
/// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
|
||||
/// 800=vg-member-added-received received, shown as "bob@addr securely joined GROUP", only sent for the verified-group-protocol.
|
||||
/// 1000=Protocol finished for this contact.
|
||||
/// @return 0
|
||||
SECUREJOIN_INVITER_PROGRESS = 2060,
|
||||
|
||||
/// Progress information of a secure-join handshake from the view of the joiner
|
||||
/// (Bob, the person who scans the QR code).
|
||||
/// The events are typically sent while dc_join_securejoin(), which
|
||||
/// may take some time, is executed.
|
||||
/// @param data1 (int) ID of the inviting contact.
|
||||
/// @param data2 (int) Progress as:
|
||||
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
|
||||
/// (Bob has verified alice and waits until Alice does the same for him)
|
||||
/// @return 0
|
||||
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.
|
||||
GET_STRING = 2091,
|
||||
}
|
||||
|
||||
pub const DC_EVENT_FILE_COPIED: usize = 2055; // deprecated;
|
||||
pub const DC_EVENT_IS_OFFLINE: usize = 2081; // deprecated;
|
||||
pub const DC_ERROR_SEE_STRING: usize = 0; // deprecated;
|
||||
pub const DC_ERROR_SELF_NOT_IN_GROUP: usize = 1; // deprecated;
|
||||
pub const DC_STR_SELFNOTINGRP: usize = 21; // deprecated;
|
||||
const DC_EVENT_FILE_COPIED: usize = 2055; // deprecated;
|
||||
const DC_EVENT_IS_OFFLINE: usize = 2081; // deprecated;
|
||||
const DC_ERROR_SEE_STRING: usize = 0; // deprecated;
|
||||
const DC_ERROR_SELF_NOT_IN_GROUP: usize = 1; // deprecated;
|
||||
const DC_STR_SELFNOTINGRP: usize = 21; // deprecated;
|
||||
|
||||
/// Values for dc_get|set_config("show_emails")
|
||||
pub const DC_SHOW_EMAILS_OFF: usize = 0;
|
||||
pub const DC_SHOW_EMAILS_ACCEPTED_CONTACTS: usize = 1;
|
||||
pub const DC_SHOW_EMAILS_ALL: usize = 2;
|
||||
const DC_SHOW_EMAILS_OFF: usize = 0;
|
||||
const DC_SHOW_EMAILS_ACCEPTED_CONTACTS: usize = 1;
|
||||
const DC_SHOW_EMAILS_ALL: usize = 2;
|
||||
|
||||
// TODO: Strings need some doumentation about used placeholders.
|
||||
// These constants are used to request strings using #DC_EVENT_GET_STRING.
|
||||
|
||||
pub const DC_STR_NOMESSAGES: usize = 1;
|
||||
pub const DC_STR_SELF: usize = 2;
|
||||
pub const DC_STR_DRAFT: usize = 3;
|
||||
pub const DC_STR_MEMBER: usize = 4;
|
||||
pub const DC_STR_CONTACT: usize = 6;
|
||||
pub const DC_STR_VOICEMESSAGE: usize = 7;
|
||||
pub const DC_STR_DEADDROP: usize = 8;
|
||||
pub const DC_STR_IMAGE: usize = 9;
|
||||
pub const DC_STR_VIDEO: usize = 10;
|
||||
pub const DC_STR_AUDIO: usize = 11;
|
||||
pub const DC_STR_FILE: usize = 12;
|
||||
pub const DC_STR_STATUSLINE: usize = 13;
|
||||
pub const DC_STR_NEWGROUPDRAFT: usize = 14;
|
||||
pub const DC_STR_MSGGRPNAME: usize = 15;
|
||||
pub const DC_STR_MSGGRPIMGCHANGED: usize = 16;
|
||||
pub const DC_STR_MSGADDMEMBER: usize = 17;
|
||||
pub const DC_STR_MSGDELMEMBER: usize = 18;
|
||||
pub const DC_STR_MSGGROUPLEFT: usize = 19;
|
||||
pub const DC_STR_GIF: usize = 23;
|
||||
pub const DC_STR_ENCRYPTEDMSG: usize = 24;
|
||||
pub const DC_STR_E2E_AVAILABLE: usize = 25;
|
||||
pub const DC_STR_ENCR_TRANSP: usize = 27;
|
||||
pub const DC_STR_ENCR_NONE: usize = 28;
|
||||
pub const DC_STR_CANTDECRYPT_MSG_BODY: usize = 29;
|
||||
pub const DC_STR_FINGERPRINTS: usize = 30;
|
||||
pub const DC_STR_READRCPT: usize = 31;
|
||||
pub const DC_STR_READRCPT_MAILBODY: usize = 32;
|
||||
pub const DC_STR_MSGGRPIMGDELETED: usize = 33;
|
||||
pub const DC_STR_E2E_PREFERRED: usize = 34;
|
||||
pub const DC_STR_CONTACT_VERIFIED: usize = 35;
|
||||
pub const DC_STR_CONTACT_NOT_VERIFIED: usize = 36;
|
||||
pub const DC_STR_CONTACT_SETUP_CHANGED: usize = 37;
|
||||
pub const DC_STR_ARCHIVEDCHATS: usize = 40;
|
||||
pub const DC_STR_STARREDMSGS: usize = 41;
|
||||
pub const DC_STR_AC_SETUP_MSG_SUBJECT: usize = 42;
|
||||
pub const DC_STR_AC_SETUP_MSG_BODY: usize = 43;
|
||||
pub const DC_STR_SELFTALK_SUBTITLE: usize = 50;
|
||||
pub const DC_STR_CANNOT_LOGIN: usize = 60;
|
||||
pub const DC_STR_SERVER_RESPONSE: usize = 61;
|
||||
pub const DC_STR_MSGACTIONBYUSER: usize = 62;
|
||||
pub const DC_STR_MSGACTIONBYME: usize = 63;
|
||||
pub const DC_STR_MSGLOCATIONENABLED: usize = 64;
|
||||
pub const DC_STR_MSGLOCATIONDISABLED: usize = 65;
|
||||
pub const DC_STR_LOCATION: usize = 66;
|
||||
pub const DC_STR_COUNT: usize = 66;
|
||||
const DC_STR_NOMESSAGES: usize = 1;
|
||||
const DC_STR_SELF: usize = 2;
|
||||
const DC_STR_DRAFT: usize = 3;
|
||||
const DC_STR_MEMBER: usize = 4;
|
||||
const DC_STR_CONTACT: usize = 6;
|
||||
const DC_STR_VOICEMESSAGE: usize = 7;
|
||||
const DC_STR_DEADDROP: usize = 8;
|
||||
const DC_STR_IMAGE: usize = 9;
|
||||
const DC_STR_VIDEO: usize = 10;
|
||||
const DC_STR_AUDIO: usize = 11;
|
||||
const DC_STR_FILE: usize = 12;
|
||||
const DC_STR_STATUSLINE: usize = 13;
|
||||
const DC_STR_NEWGROUPDRAFT: usize = 14;
|
||||
const DC_STR_MSGGRPNAME: usize = 15;
|
||||
const DC_STR_MSGGRPIMGCHANGED: usize = 16;
|
||||
const DC_STR_MSGADDMEMBER: usize = 17;
|
||||
const DC_STR_MSGDELMEMBER: usize = 18;
|
||||
const DC_STR_MSGGROUPLEFT: usize = 19;
|
||||
const DC_STR_GIF: usize = 23;
|
||||
const DC_STR_ENCRYPTEDMSG: usize = 24;
|
||||
const DC_STR_E2E_AVAILABLE: usize = 25;
|
||||
const DC_STR_ENCR_TRANSP: usize = 27;
|
||||
const DC_STR_ENCR_NONE: usize = 28;
|
||||
const DC_STR_CANTDECRYPT_MSG_BODY: usize = 29;
|
||||
const DC_STR_FINGERPRINTS: usize = 30;
|
||||
const DC_STR_READRCPT: usize = 31;
|
||||
const DC_STR_READRCPT_MAILBODY: usize = 32;
|
||||
const DC_STR_MSGGRPIMGDELETED: usize = 33;
|
||||
const DC_STR_E2E_PREFERRED: usize = 34;
|
||||
const DC_STR_CONTACT_VERIFIED: usize = 35;
|
||||
const DC_STR_CONTACT_NOT_VERIFIED: usize = 36;
|
||||
const DC_STR_CONTACT_SETUP_CHANGED: usize = 37;
|
||||
const DC_STR_ARCHIVEDCHATS: usize = 40;
|
||||
const DC_STR_STARREDMSGS: usize = 41;
|
||||
const DC_STR_AC_SETUP_MSG_SUBJECT: usize = 42;
|
||||
const DC_STR_AC_SETUP_MSG_BODY: usize = 43;
|
||||
const DC_STR_SELFTALK_SUBTITLE: usize = 50;
|
||||
const DC_STR_CANNOT_LOGIN: usize = 60;
|
||||
const DC_STR_SERVER_RESPONSE: usize = 61;
|
||||
const DC_STR_MSGACTIONBYUSER: usize = 62;
|
||||
const DC_STR_MSGACTIONBYME: usize = 63;
|
||||
const DC_STR_MSGLOCATIONENABLED: usize = 64;
|
||||
const DC_STR_MSGLOCATIONDISABLED: usize = 65;
|
||||
const DC_STR_LOCATION: usize = 66;
|
||||
const DC_STR_COUNT: usize = 66;
|
||||
|
||||
pub const DC_JOB_DELETE_MSG_ON_IMAP: i32 = 110;
|
||||
|
||||
@@ -528,12 +314,3 @@ pub enum KeyType {
|
||||
Public = 0,
|
||||
Private = 1,
|
||||
}
|
||||
|
||||
pub const DC_CMD_GROUPNAME_CHANGED: libc::c_int = 2;
|
||||
pub const DC_CMD_GROUPIMAGE_CHANGED: libc::c_int = 3;
|
||||
pub const DC_CMD_MEMBER_ADDED_TO_GROUP: libc::c_int = 4;
|
||||
pub const DC_CMD_MEMBER_REMOVED_FROM_GROUP: libc::c_int = 5;
|
||||
pub const DC_CMD_AUTOCRYPT_SETUP_MESSAGE: libc::c_int = 6;
|
||||
pub const DC_CMD_SECUREJOIN_MESSAGE: libc::c_int = 7;
|
||||
pub const DC_CMD_LOCATION_STREAMING_ENABLED: libc::c_int = 8;
|
||||
pub const DC_CMD_LOCATION_ONLY: libc::c_int = 9;
|
||||
|
||||
257
src/contact.rs
257
src/contact.rs
@@ -1,24 +1,23 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use deltachat_derive::*;
|
||||
use itertools::Itertools;
|
||||
use num_traits::{FromPrimitive, ToPrimitive};
|
||||
use rusqlite;
|
||||
use rusqlite::types::*;
|
||||
|
||||
use crate::aheader::EncryptPreference;
|
||||
use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_array::*;
|
||||
use crate::dc_e2ee::*;
|
||||
use crate::dc_loginparam::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::e2ee;
|
||||
use crate::error::Result;
|
||||
use crate::events::Event;
|
||||
use crate::key::*;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::message::MessageState;
|
||||
use crate::peerstate::*;
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::types::*;
|
||||
|
||||
const DC_GCL_VERIFIED_ONLY: u32 = 0x01;
|
||||
|
||||
/// Contacts with at least this origin value are shown in the contact list.
|
||||
const DC_ORIGIN_MIN_CONTACT_LIST: i32 = 0x100;
|
||||
@@ -33,8 +32,8 @@ const DC_ORIGIN_MIN_CONTACT_LIST: i32 = 0x100;
|
||||
/// For this purpose, internally, two names are tracked -
|
||||
/// authorized-name and given-name.
|
||||
/// By default, these names are equal, but functions working with contact names
|
||||
pub struct Contact<'a> {
|
||||
context: &'a Context,
|
||||
#[derive(Debug)]
|
||||
pub struct Contact {
|
||||
/// The contact ID.
|
||||
///
|
||||
/// Special message IDs:
|
||||
@@ -60,7 +59,9 @@ pub struct Contact<'a> {
|
||||
}
|
||||
|
||||
/// Possible origins of a contact.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive)]
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive, FromSql, ToSql,
|
||||
)]
|
||||
#[repr(i32)]
|
||||
pub enum Origin {
|
||||
Unknown = 0,
|
||||
@@ -98,20 +99,9 @@ pub enum Origin {
|
||||
ManuallyCreated = 0x4000000,
|
||||
}
|
||||
|
||||
impl ToSql for Origin {
|
||||
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
|
||||
let num: i64 = self
|
||||
.to_i64()
|
||||
.expect("impossible: Origin -> i64 conversion failed");
|
||||
|
||||
Ok(ToSqlOutput::Owned(Value::Integer(num)))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for Origin {
|
||||
fn column_result(col: ValueRef) -> FromSqlResult<Self> {
|
||||
let inner = FromSql::column_result(col)?;
|
||||
FromPrimitive::from_i64(inner).ok_or(FromSqlError::InvalidType)
|
||||
impl Default for Origin {
|
||||
fn default() -> Self {
|
||||
Origin::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +129,7 @@ pub enum Modifier {
|
||||
Created,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, FromPrimitive)]
|
||||
#[repr(u8)]
|
||||
pub enum VerifiedStatus {
|
||||
/// Contact is not verified.
|
||||
@@ -150,11 +140,10 @@ pub enum VerifiedStatus {
|
||||
BidirectVerified = 2,
|
||||
}
|
||||
|
||||
impl<'a> Contact<'a> {
|
||||
pub fn load_from_db(context: &'a Context, contact_id: u32) -> Result<Self> {
|
||||
if contact_id == DC_CONTACT_ID_SELF as u32 {
|
||||
impl Contact {
|
||||
pub fn load_from_db(context: &Context, contact_id: u32) -> Result<Self> {
|
||||
if contact_id == DC_CONTACT_ID_SELF {
|
||||
let contact = Contact {
|
||||
context,
|
||||
id: contact_id,
|
||||
name: context.stock_str(StockMessage::SelfMsg).into(),
|
||||
authname: "".into(),
|
||||
@@ -173,7 +162,6 @@ impl<'a> Contact<'a> {
|
||||
params![contact_id as i32],
|
||||
|row| {
|
||||
let contact = Self {
|
||||
context,
|
||||
id: contact_id,
|
||||
name: row.get::<_, String>(0)?,
|
||||
authname: row.get::<_, String>(4)?,
|
||||
@@ -192,7 +180,7 @@ impl<'a> Contact<'a> {
|
||||
}
|
||||
|
||||
/// Check if a contact is blocked.
|
||||
pub fn is_blocked_load(context: &'a Context, id: u32) -> bool {
|
||||
pub fn is_blocked_load(context: &Context, id: u32) -> bool {
|
||||
Self::load_from_db(context, id)
|
||||
.map(|contact| contact.blocked)
|
||||
.unwrap_or_default()
|
||||
@@ -226,15 +214,13 @@ impl<'a> Contact<'a> {
|
||||
let (contact_id, sth_modified) =
|
||||
Contact::add_or_lookup(context, name, addr, Origin::ManuallyCreated)?;
|
||||
let blocked = Contact::is_blocked_load(context, contact_id);
|
||||
context.call_cb(
|
||||
Event::CONTACTS_CHANGED,
|
||||
(if sth_modified == Modifier::Created {
|
||||
contact_id
|
||||
context.call_cb(Event::ContactsChanged(
|
||||
if sth_modified == Modifier::Created {
|
||||
Some(contact_id)
|
||||
} else {
|
||||
0
|
||||
}) as uintptr_t,
|
||||
0 as uintptr_t,
|
||||
);
|
||||
None
|
||||
},
|
||||
));
|
||||
if blocked {
|
||||
Contact::unblock(context, contact_id);
|
||||
}
|
||||
@@ -251,11 +237,14 @@ impl<'a> Contact<'a> {
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE msgs SET state=? WHERE from_id=? AND state=?;",
|
||||
params![DC_STATE_IN_NOTICED, id as i32, DC_STATE_IN_FRESH],
|
||||
params![MessageState::InNoticed, id as i32, MessageState::InFresh],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
context.call_cb(Event::MSGS_CHANGED, 0, 0);
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id: 0,
|
||||
msg_id: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,7 +267,7 @@ impl<'a> Contact<'a> {
|
||||
return 1;
|
||||
}
|
||||
|
||||
context.sql.query_row_col(
|
||||
context.sql.query_get_value(
|
||||
context,
|
||||
"SELECT id FROM contacts WHERE addr=?1 COLLATE NOCASE AND id>?2 AND origin>=?3 AND blocked=0;",
|
||||
params![
|
||||
@@ -286,7 +275,6 @@ impl<'a> Contact<'a> {
|
||||
DC_CONTACT_ID_LAST_SPECIAL as i32,
|
||||
DC_ORIGIN_MIN_CONTACT_LIST,
|
||||
],
|
||||
0
|
||||
).unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -319,7 +307,6 @@ impl<'a> Contact<'a> {
|
||||
if !may_be_valid_addr(&addr) {
|
||||
warn!(
|
||||
context,
|
||||
0,
|
||||
"Bad address \"{}\" for contact \"{}\".",
|
||||
addr,
|
||||
if !name.as_ref().is_empty() {
|
||||
@@ -328,7 +315,7 @@ impl<'a> Contact<'a> {
|
||||
"<unset>"
|
||||
},
|
||||
);
|
||||
bail!("Bad address supplied");
|
||||
bail!("Bad address supplied: {:?}", addr);
|
||||
}
|
||||
|
||||
let mut update_addr = false;
|
||||
@@ -343,19 +330,22 @@ impl<'a> Contact<'a> {
|
||||
let row_id = row.get(0)?;
|
||||
let row_name: String = row.get(1)?;
|
||||
let row_addr: String = row.get(2)?;
|
||||
let row_origin = row.get(3)?;
|
||||
let row_origin: Origin = row.get(3)?;
|
||||
let row_authname: String = row.get(4)?;
|
||||
|
||||
if !name.as_ref().is_empty() && !row_name.is_empty() {
|
||||
if origin >= row_origin && name.as_ref() != row_name {
|
||||
if !name.as_ref().is_empty() {
|
||||
if !row_name.is_empty() {
|
||||
if origin >= row_origin && name.as_ref() != row_name {
|
||||
update_name = true;
|
||||
}
|
||||
} else {
|
||||
update_name = true;
|
||||
}
|
||||
} else {
|
||||
update_name = true;
|
||||
}
|
||||
if origin == Origin::IncomingUnknownFrom && name.as_ref() != row_authname {
|
||||
update_authname = true;
|
||||
if origin == Origin::IncomingUnknownFrom && name.as_ref() != row_authname {
|
||||
update_authname = true;
|
||||
}
|
||||
}
|
||||
|
||||
Ok((row_id, row_name, row_addr, row_origin, row_authname))
|
||||
},
|
||||
) {
|
||||
@@ -395,7 +385,7 @@ impl<'a> Contact<'a> {
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats SET name=? WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
|
||||
params![name.as_ref(), 100, row_id]
|
||||
params![name.as_ref(), Chattype::Single, row_id]
|
||||
).ok();
|
||||
}
|
||||
sth_modified = Modifier::Modified;
|
||||
@@ -412,7 +402,7 @@ impl<'a> Contact<'a> {
|
||||
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
|
||||
sth_modified = Modifier::Created;
|
||||
} else {
|
||||
error!(context, 0, "Cannot add contact.");
|
||||
error!(context, "Cannot add contact.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,19 +423,13 @@ impl<'a> Contact<'a> {
|
||||
/// To add a single contact entered by the user, you should prefer `Contact::create`,
|
||||
/// however, for adding a bunch of addresses, this function is _much_ faster.
|
||||
///
|
||||
/// The `adr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
|
||||
/// The `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
|
||||
///
|
||||
/// Returns the number of modified contacts.
|
||||
pub fn add_address_book(context: &Context, adr_book: impl AsRef<str>) -> Result<usize> {
|
||||
pub fn add_address_book(context: &Context, addr_book: impl AsRef<str>) -> Result<usize> {
|
||||
let mut modify_cnt = 0;
|
||||
|
||||
for chunk in &adr_book.as_ref().lines().chunks(2) {
|
||||
let chunk = chunk.collect::<Vec<_>>();
|
||||
if chunk.len() < 2 {
|
||||
break;
|
||||
}
|
||||
let name = chunk[0];
|
||||
let addr = chunk[1];
|
||||
for (name, addr) in split_address_book(addr_book.as_ref()).into_iter() {
|
||||
let name = normalize_name(name);
|
||||
let (_, modified) = Contact::add_or_lookup(context, name, addr, Origin::AdressBook)?;
|
||||
if modified != Modifier::None {
|
||||
@@ -453,7 +437,7 @@ impl<'a> Contact<'a> {
|
||||
}
|
||||
}
|
||||
if modify_cnt > 0 {
|
||||
context.call_cb(Event::CONTACTS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
|
||||
context.call_cb(Event::ContactsChanged(None));
|
||||
}
|
||||
|
||||
Ok(modify_cnt)
|
||||
@@ -472,15 +456,17 @@ impl<'a> Contact<'a> {
|
||||
context: &Context,
|
||||
listflags: u32,
|
||||
query: Option<impl AsRef<str>>,
|
||||
) -> Result<*mut dc_array_t> {
|
||||
) -> Result<Vec<u32>> {
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut add_self = false;
|
||||
let mut ret = dc_array_t::new(100);
|
||||
let mut ret = Vec::new();
|
||||
let flag_verified_only = listflags_has(listflags, DC_GCL_VERIFIED_ONLY);
|
||||
let flag_add_self = listflags_has(listflags, DC_GCL_ADD_SELF);
|
||||
|
||||
if (listflags & DC_GCL_VERIFIED_ONLY) > 0 || query.is_some() {
|
||||
if flag_verified_only || query.is_some() {
|
||||
let s3str_like_cmd = format!(
|
||||
"%{}%",
|
||||
query
|
||||
@@ -504,12 +490,12 @@ impl<'a> Contact<'a> {
|
||||
0x100,
|
||||
&s3str_like_cmd,
|
||||
&s3str_like_cmd,
|
||||
if 0 != listflags & 0x1 { 0 } else { 1 },
|
||||
if flag_verified_only { 0 } else { 1 },
|
||||
],
|
||||
|row| row.get::<_, i32>(0),
|
||||
|ids| {
|
||||
for id in ids {
|
||||
ret.add_id(id? as u32);
|
||||
ret.push(id? as u32);
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
@@ -537,59 +523,58 @@ impl<'a> Contact<'a> {
|
||||
|row| row.get::<_, i32>(0),
|
||||
|ids| {
|
||||
for id in ids {
|
||||
ret.add_id(id? as u32);
|
||||
ret.push(id? as u32);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
)?;
|
||||
}
|
||||
|
||||
if 0 != listflags & DC_GCL_ADD_SELF as u32 && add_self {
|
||||
ret.add_id(DC_CONTACT_ID_SELF as u32);
|
||||
if flag_add_self && add_self {
|
||||
ret.push(DC_CONTACT_ID_SELF);
|
||||
}
|
||||
|
||||
Ok(ret.into_raw())
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
pub fn get_blocked_cnt(context: &Context) -> usize {
|
||||
context
|
||||
.sql
|
||||
.query_row_col::<_, isize>(
|
||||
.query_get_value::<_, isize>(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
|
||||
params![DC_CONTACT_ID_LAST_SPECIAL as i32],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default() as usize
|
||||
}
|
||||
|
||||
/// Get blocked contacts.
|
||||
pub fn get_all_blocked(context: &Context) -> *mut dc_array_t {
|
||||
pub fn get_all_blocked(context: &Context) -> Vec<u32> {
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY LOWER(name||addr),id;",
|
||||
params![DC_CONTACT_ID_LAST_SPECIAL as i32],
|
||||
|row| row.get::<_, i32>(0),
|
||||
|row| row.get::<_, u32>(0),
|
||||
|ids| {
|
||||
let mut ret = dc_array_t::new(100);
|
||||
|
||||
for id in ids {
|
||||
ret.add_id(id? as u32);
|
||||
}
|
||||
|
||||
Ok(ret.into_raw())
|
||||
ids.collect::<std::result::Result<Vec<_>, _>>()
|
||||
.map_err(Into::into)
|
||||
},
|
||||
)
|
||||
.unwrap_or_else(|_| std::ptr::null_mut())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn get_encrinfo(context: &Context, contact_id: u32) -> String {
|
||||
/// Returns a textual summary of the encryption state for the contact.
|
||||
///
|
||||
/// This function returns a string explaining the encryption state
|
||||
/// of the contact and if the connection is encrypted the
|
||||
/// fingerprints of the keys involved.
|
||||
pub fn get_encrinfo(context: &Context, contact_id: u32) -> Result<String> {
|
||||
let mut ret = String::new();
|
||||
|
||||
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
|
||||
let peerstate = Peerstate::from_addr(context, &context.sql, &contact.addr);
|
||||
let loginparam = dc_loginparam_read(context, &context.sql, "configured_");
|
||||
let loginparam = LoginParam::from_database(context, "configured_");
|
||||
|
||||
let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql);
|
||||
|
||||
@@ -603,7 +588,7 @@ impl<'a> Contact<'a> {
|
||||
});
|
||||
ret += &p;
|
||||
if self_key.is_none() {
|
||||
unsafe { dc_ensure_secret_key_exists(context) };
|
||||
e2ee::ensure_secret_key_exists(context)?;
|
||||
self_key = Key::from_self_public(context, &loginparam.addr, &context.sql);
|
||||
}
|
||||
let p = context.stock_str(StockMessage::FingerPrints);
|
||||
@@ -646,7 +631,7 @@ impl<'a> Contact<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
ret
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Delete a contact. The contact is deleted from the local device. It may happen that this is not
|
||||
@@ -655,28 +640,26 @@ impl<'a> Contact<'a> {
|
||||
/// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
|
||||
pub fn delete(context: &Context, contact_id: u32) -> Result<()> {
|
||||
ensure!(
|
||||
contact_id > DC_CONTACT_ID_LAST_SPECIAL as u32,
|
||||
contact_id > DC_CONTACT_ID_LAST_SPECIAL,
|
||||
"Can not delete special contact"
|
||||
);
|
||||
|
||||
let count_contacts: i32 = context
|
||||
.sql
|
||||
.query_row_col(
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;",
|
||||
params![contact_id as i32],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
|
||||
let count_msgs: i32 = if count_contacts > 0 {
|
||||
context
|
||||
.sql
|
||||
.query_row_col(
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;",
|
||||
params![contact_id as i32, contact_id as i32],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
@@ -691,11 +674,11 @@ impl<'a> Contact<'a> {
|
||||
params![contact_id as i32],
|
||||
) {
|
||||
Ok(_) => {
|
||||
context.call_cb(Event::CONTACTS_CHANGED, 0, 0);
|
||||
context.call_cb(Event::ContactsChanged(None));
|
||||
return Ok(());
|
||||
}
|
||||
Err(err) => {
|
||||
error!(context, 0, "delete_contact {} failed ({})", contact_id, err);
|
||||
error!(context, "delete_contact {} failed ({})", contact_id, err);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
@@ -703,7 +686,7 @@ impl<'a> Contact<'a> {
|
||||
|
||||
info!(
|
||||
context,
|
||||
0, "could not delete contact {}, there are {} messages with it", contact_id, count_msgs
|
||||
"could not delete contact {}, there are {} messages with it", contact_id, count_msgs
|
||||
);
|
||||
bail!("Could not delete contact with messages in it");
|
||||
}
|
||||
@@ -779,9 +762,11 @@ impl<'a> Contact<'a> {
|
||||
/// Get the contact's profile image.
|
||||
/// This is the image set by each remote user on their own
|
||||
/// using dc_set_config(context, "selfavatar", image).
|
||||
pub fn get_profile_image(&self) -> Option<String> {
|
||||
if self.id == DC_CONTACT_ID_SELF as u32 {
|
||||
return self.context.get_config(Config::Selfavatar);
|
||||
pub fn get_profile_image(&self, context: &Context) -> Option<PathBuf> {
|
||||
if self.id == DC_CONTACT_ID_SELF {
|
||||
if let Some(p) = context.get_config(Config::Selfavatar) {
|
||||
return Some(PathBuf::from(p));
|
||||
}
|
||||
}
|
||||
// TODO: else get image_abs from contact param
|
||||
None
|
||||
@@ -792,7 +777,7 @@ impl<'a> Contact<'a> {
|
||||
/// and can be used for an fallback avatar with white initials
|
||||
/// as well as for headlines in bubbles of group chats.
|
||||
pub fn get_color(&self) -> u32 {
|
||||
dc_str_to_color_safe(&self.addr)
|
||||
dc_str_to_color(&self.addr)
|
||||
}
|
||||
|
||||
/// Check if a contact was verified. E.g. by a secure-join QR code scan
|
||||
@@ -800,17 +785,21 @@ impl<'a> Contact<'a> {
|
||||
///
|
||||
/// The UI may draw a checkbox or something like that beside verified contacts.
|
||||
///
|
||||
pub fn is_verified(&self) -> VerifiedStatus {
|
||||
self.is_verified_ex(None)
|
||||
pub fn is_verified(&self, context: &Context) -> VerifiedStatus {
|
||||
self.is_verified_ex(context, None)
|
||||
}
|
||||
|
||||
/// Same as `Contact::is_verified` but allows speeding up things
|
||||
/// by adding the peerstate belonging to the contact.
|
||||
/// If you do not have the peerstate available, it is loaded automatically.
|
||||
pub fn is_verified_ex(&self, peerstate: Option<&Peerstate<'a>>) -> VerifiedStatus {
|
||||
pub fn is_verified_ex(
|
||||
&self,
|
||||
context: &Context,
|
||||
peerstate: Option<&Peerstate>,
|
||||
) -> VerifiedStatus {
|
||||
// We're always sort of secured-verified as we could verify the key on this device any time with the key
|
||||
// on this device
|
||||
if self.id == DC_CONTACT_ID_SELF as u32 {
|
||||
if self.id == DC_CONTACT_ID_SELF {
|
||||
return VerifiedStatus::BidirectVerified;
|
||||
}
|
||||
|
||||
@@ -820,7 +809,7 @@ impl<'a> Contact<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
let peerstate = Peerstate::from_addr(self.context, &self.context.sql, &self.addr);
|
||||
let peerstate = Peerstate::from_addr(context, &context.sql, &self.addr);
|
||||
if let Some(ps) = peerstate {
|
||||
if ps.verified_key().is_some() {
|
||||
return VerifiedStatus::BidirectVerified;
|
||||
@@ -854,11 +843,10 @@ impl<'a> Contact<'a> {
|
||||
|
||||
context
|
||||
.sql
|
||||
.query_row_col::<_, isize>(
|
||||
.query_get_value::<_, isize>(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM contacts WHERE id>?;",
|
||||
params![DC_CONTACT_ID_LAST_SPECIAL as i32],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default() as usize
|
||||
}
|
||||
@@ -904,26 +892,14 @@ impl<'a> Contact<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_first_name<'a>(full_name: &'a str) -> &'a str {
|
||||
fn get_first_name<'a>(full_name: &'a str) -> &'a str {
|
||||
full_name.splitn(2, ' ').next().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns false if addr is an invalid address, otherwise true.
|
||||
pub fn may_be_valid_addr(addr: &str) -> bool {
|
||||
if addr.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let at = addr.find('@').unwrap_or_default();
|
||||
if at < 1 {
|
||||
return false;
|
||||
}
|
||||
let dot = addr.find('.').unwrap_or_default();
|
||||
if dot < 1 || dot > addr.len() - 3 || dot < at + 2 {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
let res = addr.parse::<EmailAddress>();
|
||||
res.is_ok()
|
||||
}
|
||||
|
||||
pub fn addr_normalize(addr: &str) -> &str {
|
||||
@@ -963,11 +939,7 @@ fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
|
||||
params![new_blocking, 100, contact_id as i32],
|
||||
).is_ok() {
|
||||
Contact::mark_noticed(context, contact_id);
|
||||
context.call_cb(
|
||||
Event::CONTACTS_CHANGED,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
context.call_cb(Event::ContactsChanged(None));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1055,6 +1027,21 @@ pub fn addr_equals_self(context: &Context, addr: impl AsRef<str>) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn split_address_book(book: &str) -> Vec<(&str, &str)> {
|
||||
book.lines()
|
||||
.chunks(2)
|
||||
.into_iter()
|
||||
.filter_map(|mut chunk| {
|
||||
let name = chunk.next().unwrap();
|
||||
let addr = match chunk.next() {
|
||||
Some(a) => a,
|
||||
None => return None,
|
||||
};
|
||||
Some((name, addr))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1090,4 +1077,14 @@ mod tests {
|
||||
fn test_get_first_name() {
|
||||
assert_eq!(get_first_name("John Doe"), "John");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_address_book() {
|
||||
let book = "Name one\nAddress one\nName two\nAddress two\nrest name";
|
||||
let list = split_address_book(&book);
|
||||
assert_eq!(
|
||||
list,
|
||||
vec![("Name one", "Address one"), ("Name two", "Address two")]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
960
src/context.rs
960
src/context.rs
File diff suppressed because it is too large
Load Diff
393
src/dc_array.rs
393
src/dc_array.rs
@@ -1,13 +1,11 @@
|
||||
use crate::dc_location::dc_location;
|
||||
use crate::dc_tools::*;
|
||||
use crate::types::*;
|
||||
use crate::location::Location;
|
||||
|
||||
/* * the structure behind dc_array_t */
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum dc_array_t {
|
||||
Locations(Vec<dc_location>),
|
||||
Uint(Vec<uintptr_t>),
|
||||
Locations(Vec<Location>),
|
||||
Uint(Vec<u32>),
|
||||
}
|
||||
|
||||
impl dc_array_t {
|
||||
@@ -20,23 +18,15 @@ impl dc_array_t {
|
||||
dc_array_t::Locations(Vec::with_capacity(capacity))
|
||||
}
|
||||
|
||||
pub fn into_raw(self) -> *mut Self {
|
||||
Box::into_raw(Box::new(self))
|
||||
}
|
||||
|
||||
pub fn add_uint(&mut self, item: uintptr_t) {
|
||||
pub fn add_id(&mut self, item: u32) {
|
||||
if let Self::Uint(array) = self {
|
||||
array.push(item);
|
||||
} else {
|
||||
panic!("Attempt to add uint to array of other type");
|
||||
panic!("Attempt to add id to array of other type");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_id(&mut self, item: uint32_t) {
|
||||
self.add_uint(item as uintptr_t);
|
||||
}
|
||||
|
||||
pub fn add_location(&mut self, location: dc_location) {
|
||||
pub fn add_location(&mut self, location: Location) {
|
||||
if let Self::Locations(array) = self {
|
||||
array.push(location)
|
||||
} else {
|
||||
@@ -44,30 +34,14 @@ impl dc_array_t {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_uint(&self, index: usize) -> uintptr_t {
|
||||
if let Self::Uint(array) = self {
|
||||
array[index]
|
||||
} else {
|
||||
panic!("Attempt to get uint from array of other type");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_id(&self, index: usize) -> uint32_t {
|
||||
pub fn get_id(&self, index: usize) -> u32 {
|
||||
match self {
|
||||
Self::Locations(array) => array[index].location_id,
|
||||
Self::Uint(array) => array[index] as uint32_t,
|
||||
Self::Uint(array) => array[index] as u32,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_ptr(&self, index: size_t) -> *mut libc::c_void {
|
||||
if let Self::Uint(array) = self {
|
||||
array[index] as *mut libc::c_void
|
||||
} else {
|
||||
panic!("Not an array of pointers");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_location(&self, index: usize) -> &dc_location {
|
||||
pub fn get_location(&self, index: usize) -> &Location {
|
||||
if let Self::Locations(array) = self {
|
||||
&array[index]
|
||||
} else {
|
||||
@@ -75,34 +49,6 @@ impl dc_array_t {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_latitude(&self, index: usize) -> libc::c_double {
|
||||
self.get_location(index).latitude
|
||||
}
|
||||
|
||||
pub fn get_longitude(&self, index: size_t) -> libc::c_double {
|
||||
self.get_location(index).longitude
|
||||
}
|
||||
|
||||
pub fn get_accuracy(&self, index: size_t) -> libc::c_double {
|
||||
self.get_location(index).accuracy
|
||||
}
|
||||
|
||||
pub fn get_timestamp(&self, index: size_t) -> i64 {
|
||||
self.get_location(index).timestamp
|
||||
}
|
||||
|
||||
pub fn get_chat_id(&self, index: size_t) -> uint32_t {
|
||||
self.get_location(index).chat_id
|
||||
}
|
||||
|
||||
pub fn get_contact_id(&self, index: size_t) -> uint32_t {
|
||||
self.get_location(index).contact_id
|
||||
}
|
||||
|
||||
pub fn get_msg_id(&self, index: size_t) -> uint32_t {
|
||||
self.get_location(index).msg_id
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
Self::Locations(array) => array.is_empty(),
|
||||
@@ -125,7 +71,7 @@ impl dc_array_t {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn search_id(&self, needle: uintptr_t) -> Option<usize> {
|
||||
pub fn search_id(&self, needle: u32) -> Option<usize> {
|
||||
if let Self::Uint(array) = self {
|
||||
for (i, &u) in array.iter().enumerate() {
|
||||
if u == needle {
|
||||
@@ -145,297 +91,72 @@ impl dc_array_t {
|
||||
panic!("Attempt to sort array of something other than uints");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<dc_location>> for dc_array_t {
|
||||
fn from(array: Vec<dc_location>) -> Self {
|
||||
dc_array_t::Locations(array)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_unref(array: *mut dc_array_t) {
|
||||
if array.is_null() {
|
||||
return;
|
||||
}
|
||||
Box::from_raw(array);
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_add_uint(array: *mut dc_array_t, item: uintptr_t) {
|
||||
if !array.is_null() {
|
||||
(*array).add_uint(item);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_add_id(array: *mut dc_array_t, item: uint32_t) {
|
||||
if !array.is_null() {
|
||||
(*array).add_id(item);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_add_ptr(array: *mut dc_array_t, item: *mut libc::c_void) {
|
||||
dc_array_add_uint(array, item as uintptr_t);
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_cnt(array: *const dc_array_t) -> size_t {
|
||||
if array.is_null() {
|
||||
0
|
||||
} else {
|
||||
(*array).len()
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_uint(array: *const dc_array_t, index: size_t) -> uintptr_t {
|
||||
if array.is_null() || index >= (*array).len() {
|
||||
0
|
||||
} else {
|
||||
(*array).get_uint(index)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_id(array: *const dc_array_t, index: size_t) -> uint32_t {
|
||||
if array.is_null() || index >= (*array).len() {
|
||||
0
|
||||
} else {
|
||||
(*array).get_id(index)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_ptr(array: *const dc_array_t, index: size_t) -> *mut libc::c_void {
|
||||
if array.is_null() || index >= (*array).len() {
|
||||
std::ptr::null_mut()
|
||||
} else {
|
||||
(*array).get_ptr(index)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_latitude(array: *const dc_array_t, index: size_t) -> libc::c_double {
|
||||
if array.is_null() || index >= (*array).len() {
|
||||
0.0
|
||||
} else {
|
||||
(*array).get_latitude(index)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_longitude(array: *const dc_array_t, index: size_t) -> libc::c_double {
|
||||
if array.is_null() || index >= (*array).len() {
|
||||
0.0
|
||||
} else {
|
||||
(*array).get_longitude(index)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_accuracy(array: *const dc_array_t, index: size_t) -> libc::c_double {
|
||||
if array.is_null() || index >= (*array).len() {
|
||||
0.0
|
||||
} else {
|
||||
(*array).get_accuracy(index)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_timestamp(array: *const dc_array_t, index: size_t) -> i64 {
|
||||
if array.is_null() || index >= (*array).len() {
|
||||
0
|
||||
} else {
|
||||
(*array).get_timestamp(index)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_chat_id(array: *const dc_array_t, index: size_t) -> uint32_t {
|
||||
if array.is_null() || index >= (*array).len() {
|
||||
0
|
||||
} else {
|
||||
(*array).get_chat_id(index)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_contact_id(array: *const dc_array_t, index: size_t) -> uint32_t {
|
||||
if array.is_null() || index >= (*array).len() {
|
||||
0
|
||||
} else {
|
||||
(*array).get_contact_id(index)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_msg_id(array: *const dc_array_t, index: size_t) -> uint32_t {
|
||||
if array.is_null() || index >= (*array).len() {
|
||||
0
|
||||
} else {
|
||||
(*array).get_msg_id(index)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_marker(array: *const dc_array_t, index: size_t) -> *mut libc::c_char {
|
||||
if array.is_null() || index >= (*array).len() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
if let dc_array_t::Locations(v) = &*array {
|
||||
if let Some(s) = &v[index].marker {
|
||||
s.strdup()
|
||||
pub fn as_ptr(&self) -> *const u32 {
|
||||
if let dc_array_t::Uint(v) = self {
|
||||
v.as_ptr()
|
||||
} else {
|
||||
std::ptr::null_mut()
|
||||
panic!("Attempt to convert array of something other than uints to raw");
|
||||
}
|
||||
} else {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the independent-state of the location at the given index.
|
||||
* Independent locations do not belong to the track of the user.
|
||||
*
|
||||
* @memberof dc_array_t
|
||||
* @param array The array object.
|
||||
* @param index Index of the item. Must be between 0 and dc_array_get_cnt()-1.
|
||||
* @return 0=Location belongs to the track of the user,
|
||||
* 1=Location was reported independently.
|
||||
*/
|
||||
pub unsafe fn dc_array_is_independent(array: *const dc_array_t, index: size_t) -> libc::c_int {
|
||||
if array.is_null() || index >= (*array).len() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if let dc_array_t::Locations(v) = &*array {
|
||||
v[index].independent as libc::c_int
|
||||
} else {
|
||||
panic!("Attempt to get location independent field from array of something other than locations");
|
||||
impl From<Vec<u32>> for dc_array_t {
|
||||
fn from(array: Vec<u32>) -> Self {
|
||||
dc_array_t::Uint(array)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_search_id(
|
||||
array: *const dc_array_t,
|
||||
needle: uint32_t,
|
||||
ret_index: *mut size_t,
|
||||
) -> bool {
|
||||
if array.is_null() {
|
||||
return false;
|
||||
}
|
||||
if let Some(i) = (*array).search_id(needle as uintptr_t) {
|
||||
if !ret_index.is_null() {
|
||||
*ret_index = i
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_raw(array: *const dc_array_t) -> *const uintptr_t {
|
||||
if array.is_null() {
|
||||
return 0 as *const uintptr_t;
|
||||
}
|
||||
if let dc_array_t::Uint(v) = &*array {
|
||||
v.as_ptr()
|
||||
} else {
|
||||
panic!("Attempt to convert array of something other than uints to raw");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_array_new(initsize: size_t) -> *mut dc_array_t {
|
||||
dc_array_t::new(initsize).into_raw()
|
||||
}
|
||||
|
||||
pub fn dc_array_new_locations(initsize: size_t) -> *mut dc_array_t {
|
||||
dc_array_t::new_locations(initsize).into_raw()
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_empty(array: *mut dc_array_t) {
|
||||
if array.is_null() {
|
||||
return;
|
||||
}
|
||||
(*array).clear()
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_duplicate(array: *const dc_array_t) -> *mut dc_array_t {
|
||||
if array.is_null() {
|
||||
std::ptr::null_mut()
|
||||
} else {
|
||||
(*array).clone().into_raw()
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_string(
|
||||
array: *const dc_array_t,
|
||||
sep: *const libc::c_char,
|
||||
) -> *mut libc::c_char {
|
||||
if array.is_null() || sep.is_null() {
|
||||
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||
}
|
||||
if let dc_array_t::Uint(v) = &*array {
|
||||
let cnt = v.len();
|
||||
let sep = as_str(sep);
|
||||
|
||||
let res = v
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold(String::with_capacity(2 * cnt), |res, (i, n)| {
|
||||
if i == 0 {
|
||||
res + &n.to_string()
|
||||
} else {
|
||||
res + sep + &n.to_string()
|
||||
}
|
||||
});
|
||||
res.strdup()
|
||||
} else {
|
||||
panic!("Attempt to get string from array of other type");
|
||||
impl From<Vec<Location>> for dc_array_t {
|
||||
fn from(array: Vec<Location>) -> Self {
|
||||
dc_array_t::Locations(array)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::x::*;
|
||||
use std::ffi::CStr;
|
||||
|
||||
#[test]
|
||||
fn test_dc_array() {
|
||||
unsafe {
|
||||
let arr = dc_array_new(7 as size_t);
|
||||
assert_eq!(dc_array_get_cnt(arr), 0);
|
||||
let mut arr = dc_array_t::new(7);
|
||||
assert!(arr.is_empty());
|
||||
|
||||
for i in 0..1000 {
|
||||
dc_array_add_id(arr, (i + 2) as uint32_t);
|
||||
}
|
||||
|
||||
assert_eq!(dc_array_get_cnt(arr), 1000);
|
||||
|
||||
for i in 0..1000 {
|
||||
assert_eq!(
|
||||
dc_array_get_id(arr, i as size_t),
|
||||
(i + 1i32 * 2i32) as libc::c_uint
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(dc_array_get_id(arr, -1i32 as size_t), 0);
|
||||
assert_eq!(dc_array_get_id(arr, 1000 as size_t), 0);
|
||||
assert_eq!(dc_array_get_id(arr, 1001 as size_t), 0);
|
||||
|
||||
dc_array_empty(arr);
|
||||
|
||||
assert_eq!(dc_array_get_cnt(arr), 0);
|
||||
|
||||
dc_array_add_id(arr, 13 as uint32_t);
|
||||
dc_array_add_id(arr, 7 as uint32_t);
|
||||
dc_array_add_id(arr, 666 as uint32_t);
|
||||
dc_array_add_id(arr, 0 as uint32_t);
|
||||
dc_array_add_id(arr, 5000 as uint32_t);
|
||||
|
||||
(*arr).sort_ids();
|
||||
|
||||
assert_eq!(dc_array_get_id(arr, 0 as size_t), 0);
|
||||
assert_eq!(dc_array_get_id(arr, 1 as size_t), 7);
|
||||
assert_eq!(dc_array_get_id(arr, 2 as size_t), 13);
|
||||
assert_eq!(dc_array_get_id(arr, 3 as size_t), 666);
|
||||
|
||||
let str = dc_array_get_string(arr, b"-\x00" as *const u8 as *const libc::c_char);
|
||||
assert_eq!(
|
||||
CStr::from_ptr(str as *const libc::c_char).to_str().unwrap(),
|
||||
"0-7-13-666-5000"
|
||||
);
|
||||
free(str as *mut libc::c_void);
|
||||
|
||||
dc_array_unref(arr);
|
||||
for i in 0..1000 {
|
||||
arr.add_id(i + 2);
|
||||
}
|
||||
|
||||
assert_eq!(arr.len(), 1000);
|
||||
|
||||
for i in 0..1000 {
|
||||
assert_eq!(arr.get_id(i), (i + 2) as u32);
|
||||
}
|
||||
|
||||
arr.clear();
|
||||
|
||||
assert!(arr.is_empty());
|
||||
|
||||
arr.add_id(13);
|
||||
arr.add_id(7);
|
||||
arr.add_id(666);
|
||||
arr.add_id(0);
|
||||
arr.add_id(5000);
|
||||
|
||||
arr.sort_ids();
|
||||
|
||||
assert_eq!(arr.get_id(0), 0);
|
||||
assert_eq!(arr.get_id(1), 7);
|
||||
assert_eq!(arr.get_id(2), 13);
|
||||
assert_eq!(arr.get_id(3), 666);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_dc_array_out_of_bounds() {
|
||||
let mut arr = dc_array_t::new(7);
|
||||
for i in 0..1000 {
|
||||
arr.add_id(i + 2);
|
||||
}
|
||||
arr.get_id(1000);
|
||||
}
|
||||
}
|
||||
|
||||
2275
src/dc_chat.rs
2275
src/dc_chat.rs
File diff suppressed because it is too large
Load Diff
1535
src/dc_configure.rs
1535
src/dc_configure.rs
File diff suppressed because it is too large
Load Diff
161
src/dc_dehtml.rs
161
src/dc_dehtml.rs
@@ -1,8 +1,6 @@
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use crate::dc_saxparser::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::x::*;
|
||||
use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
lazy_static! {
|
||||
static ref LINE_RE: regex::Regex = regex::Regex::new(r"(\r?\n)+").unwrap();
|
||||
@@ -23,67 +21,79 @@ enum AddText {
|
||||
|
||||
// dc_dehtml() returns way too many lineends; however, an optimisation on this issue is not needed as
|
||||
// the lineends are typically remove in further processing by the caller
|
||||
pub unsafe fn dc_dehtml(buf_terminated: *mut libc::c_char) -> *mut libc::c_char {
|
||||
dc_trim(buf_terminated);
|
||||
if *buf_terminated.offset(0isize) as libc::c_int == 0i32 {
|
||||
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||
pub fn dc_dehtml(buf_terminated: &str) -> String {
|
||||
let buf_terminated = buf_terminated.trim();
|
||||
|
||||
if buf_terminated.is_empty() {
|
||||
return "".into();
|
||||
}
|
||||
|
||||
let mut dehtml = Dehtml {
|
||||
strbuilder: String::with_capacity(strlen(buf_terminated)),
|
||||
strbuilder: String::with_capacity(buf_terminated.len()),
|
||||
add_text: AddText::YesRemoveLineEnds,
|
||||
last_href: None,
|
||||
};
|
||||
let mut saxparser = dc_saxparser_t {
|
||||
starttag_cb: None,
|
||||
endtag_cb: None,
|
||||
text_cb: None,
|
||||
userdata: 0 as *mut libc::c_void,
|
||||
};
|
||||
dc_saxparser_init(
|
||||
&mut saxparser,
|
||||
&mut dehtml as *mut Dehtml as *mut libc::c_void,
|
||||
);
|
||||
dc_saxparser_set_tag_handler(
|
||||
&mut saxparser,
|
||||
Some(dehtml_starttag_cb),
|
||||
Some(dehtml_endtag_cb),
|
||||
);
|
||||
dc_saxparser_set_text_handler(&mut saxparser, Some(dehtml_text_cb));
|
||||
dc_saxparser_parse(&mut saxparser, buf_terminated);
|
||||
|
||||
dehtml.strbuilder.strdup()
|
||||
let mut reader = quick_xml::Reader::from_str(buf_terminated);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
|
||||
loop {
|
||||
match reader.read_event(&mut buf) {
|
||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||
dehtml_starttag_cb(e, &mut dehtml, &reader)
|
||||
}
|
||||
Ok(quick_xml::events::Event::End(ref e)) => dehtml_endtag_cb(e, &mut dehtml),
|
||||
Ok(quick_xml::events::Event::Text(ref e)) => dehtml_text_cb(e, &mut dehtml),
|
||||
Ok(quick_xml::events::Event::CData(ref e)) => dehtml_cdata_cb(e, &mut dehtml),
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"Parse html error: Error at position {}: {:?}",
|
||||
reader.buffer_position(),
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(quick_xml::events::Event::Eof) => break,
|
||||
_ => (),
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
dehtml.strbuilder
|
||||
}
|
||||
|
||||
unsafe fn dehtml_text_cb(
|
||||
userdata: *mut libc::c_void,
|
||||
text: *const libc::c_char,
|
||||
_len: libc::c_int,
|
||||
) {
|
||||
let dehtml = &mut *(userdata as *mut Dehtml);
|
||||
|
||||
fn dehtml_text_cb(event: &BytesText, dehtml: &mut Dehtml) {
|
||||
if dehtml.add_text == AddText::YesPreserveLineEnds
|
||||
|| dehtml.add_text == AddText::YesRemoveLineEnds
|
||||
{
|
||||
let last_added = std::ffi::CStr::from_ptr(text)
|
||||
.to_str()
|
||||
.expect("invalid utf8");
|
||||
// TODO: why does len does not match?
|
||||
// assert_eq!(last_added.len(), len as usize);
|
||||
let last_added = escaper::decode_html_buf_sloppy(event.escaped()).unwrap_or_default();
|
||||
|
||||
if dehtml.add_text == AddText::YesRemoveLineEnds {
|
||||
dehtml.strbuilder += LINE_RE.replace_all(last_added.as_ref(), "\r").as_ref();
|
||||
dehtml.strbuilder += LINE_RE.replace_all(&last_added, "\r").as_ref();
|
||||
} else {
|
||||
dehtml.strbuilder += last_added.as_ref();
|
||||
dehtml.strbuilder += &last_added;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn dehtml_endtag_cb(userdata: *mut libc::c_void, tag: *const libc::c_char) {
|
||||
let mut dehtml = &mut *(userdata as *mut Dehtml);
|
||||
let tag = std::ffi::CStr::from_ptr(tag).to_string_lossy();
|
||||
fn dehtml_cdata_cb(event: &BytesText, dehtml: &mut Dehtml) {
|
||||
if dehtml.add_text == AddText::YesPreserveLineEnds
|
||||
|| dehtml.add_text == AddText::YesRemoveLineEnds
|
||||
{
|
||||
let last_added = escaper::decode_html_buf_sloppy(event.escaped()).unwrap_or_default();
|
||||
|
||||
match tag.as_ref() {
|
||||
if dehtml.add_text == AddText::YesRemoveLineEnds {
|
||||
dehtml.strbuilder += LINE_RE.replace_all(&last_added, "\r").as_ref();
|
||||
} else {
|
||||
dehtml.strbuilder += &last_added;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dehtml_endtag_cb(event: &BytesEnd, dehtml: &mut Dehtml) {
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
|
||||
match tag.as_str() {
|
||||
"p" | "div" | "table" | "td" | "style" | "script" | "title" | "pre" => {
|
||||
dehtml.strbuilder += "\n\n";
|
||||
dehtml.add_text = AddText::YesRemoveLineEnds;
|
||||
@@ -105,15 +115,14 @@ unsafe fn dehtml_endtag_cb(userdata: *mut libc::c_void, tag: *const libc::c_char
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn dehtml_starttag_cb(
|
||||
userdata: *mut libc::c_void,
|
||||
tag: *const libc::c_char,
|
||||
attr: *mut *mut libc::c_char,
|
||||
fn dehtml_starttag_cb<B: std::io::BufRead>(
|
||||
event: &BytesStart,
|
||||
dehtml: &mut Dehtml,
|
||||
reader: &quick_xml::Reader<B>,
|
||||
) {
|
||||
let mut dehtml = &mut *(userdata as *mut Dehtml);
|
||||
let tag = std::ffi::CStr::from_ptr(tag).to_string_lossy();
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
|
||||
match tag.as_ref() {
|
||||
match tag.as_str() {
|
||||
"p" | "div" | "table" | "td" => {
|
||||
dehtml.strbuilder += "\n\n";
|
||||
dehtml.add_text = AddText::YesRemoveLineEnds;
|
||||
@@ -130,14 +139,21 @@ unsafe fn dehtml_starttag_cb(
|
||||
dehtml.add_text = AddText::YesPreserveLineEnds;
|
||||
}
|
||||
"a" => {
|
||||
let text_c = std::ffi::CStr::from_ptr(dc_attr_find(
|
||||
attr,
|
||||
b"href\x00" as *const u8 as *const libc::c_char,
|
||||
));
|
||||
let text_r = text_c.to_str().expect("invalid utf8");
|
||||
if !text_r.is_empty() {
|
||||
dehtml.last_href = Some(text_r.to_string());
|
||||
dehtml.strbuilder += "[";
|
||||
if let Some(href) = event.html_attributes().find(|attr| {
|
||||
attr.as_ref()
|
||||
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "href")
|
||||
.unwrap_or_default()
|
||||
}) {
|
||||
let href = href
|
||||
.unwrap()
|
||||
.unescape_and_decode_value(reader)
|
||||
.unwrap_or_default()
|
||||
.to_lowercase();
|
||||
|
||||
if !href.is_empty() {
|
||||
dehtml.last_href = Some(href);
|
||||
dehtml.strbuilder += "[";
|
||||
}
|
||||
}
|
||||
}
|
||||
"b" | "strong" => {
|
||||
@@ -149,3 +165,28 @@ unsafe fn dehtml_starttag_cb(
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_dc_dehtml() {
|
||||
let cases = vec",
|
||||
),
|
||||
("<img href='/foo.png'>", ""),
|
||||
("<b> bar </b>", "* bar *"),
|
||||
("<b> bar <i> foo", "* bar _ foo"),
|
||||
("& bar", "& bar"),
|
||||
// Note missing '
|
||||
("<a href='/foo.png>Hi</a> ", ""),
|
||||
("", ""),
|
||||
];
|
||||
for (input, output) in cases {
|
||||
assert_eq!(dc_dehtml(input), output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1135
src/dc_e2ee.rs
1135
src/dc_e2ee.rs
File diff suppressed because it is too large
Load Diff
943
src/dc_imex.rs
943
src/dc_imex.rs
File diff suppressed because it is too large
Load Diff
1285
src/dc_job.rs
1285
src/dc_job.rs
File diff suppressed because it is too large
Load Diff
@@ -1,209 +0,0 @@
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::dc_configure::*;
|
||||
use crate::imap::Imap;
|
||||
use crate::x::*;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct dc_jobthread_t {
|
||||
pub name: &'static str,
|
||||
pub folder_config_name: &'static str,
|
||||
pub imap: Imap,
|
||||
pub state: Arc<(Mutex<JobState>, Condvar)>,
|
||||
}
|
||||
|
||||
pub fn dc_jobthread_init(
|
||||
name: &'static str,
|
||||
folder_config_name: &'static str,
|
||||
imap: Imap,
|
||||
) -> dc_jobthread_t {
|
||||
dc_jobthread_t {
|
||||
name,
|
||||
folder_config_name,
|
||||
imap,
|
||||
state: Arc::new((Mutex::new(Default::default()), Condvar::new())),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct JobState {
|
||||
idle: bool,
|
||||
jobs_needed: i32,
|
||||
suspended: i32,
|
||||
using_handle: i32,
|
||||
}
|
||||
|
||||
pub unsafe fn dc_jobthread_suspend(
|
||||
context: &Context,
|
||||
jobthread: &dc_jobthread_t,
|
||||
suspend: libc::c_int,
|
||||
) {
|
||||
if 0 != suspend {
|
||||
info!(context, 0, "Suspending {}-thread.", jobthread.name,);
|
||||
{
|
||||
jobthread.state.0.lock().unwrap().suspended = 1;
|
||||
}
|
||||
dc_jobthread_interrupt_idle(context, jobthread);
|
||||
loop {
|
||||
let using_handle = jobthread.state.0.lock().unwrap().using_handle;
|
||||
if using_handle == 0 {
|
||||
return;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_micros(300 * 1000));
|
||||
}
|
||||
} else {
|
||||
info!(context, 0, "Unsuspending {}-thread.", jobthread.name);
|
||||
|
||||
let &(ref lock, ref cvar) = &*jobthread.state.clone();
|
||||
let mut state = lock.lock().unwrap();
|
||||
|
||||
state.suspended = 0;
|
||||
state.idle = true;
|
||||
cvar.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_jobthread_interrupt_idle(context: &Context, jobthread: &dc_jobthread_t) {
|
||||
{
|
||||
jobthread.state.0.lock().unwrap().jobs_needed = 1;
|
||||
}
|
||||
|
||||
info!(context, 0, "Interrupting {}-IDLE...", jobthread.name);
|
||||
|
||||
jobthread.imap.interrupt_idle();
|
||||
|
||||
let &(ref lock, ref cvar) = &*jobthread.state.clone();
|
||||
let mut state = lock.lock().unwrap();
|
||||
|
||||
state.idle = true;
|
||||
cvar.notify_one();
|
||||
}
|
||||
|
||||
pub unsafe fn dc_jobthread_fetch(
|
||||
context: &Context,
|
||||
jobthread: &mut dc_jobthread_t,
|
||||
use_network: libc::c_int,
|
||||
) {
|
||||
let start;
|
||||
|
||||
{
|
||||
let &(ref lock, _) = &*jobthread.state.clone();
|
||||
let mut state = lock.lock().unwrap();
|
||||
|
||||
if 0 != state.suspended {
|
||||
return;
|
||||
}
|
||||
|
||||
state.using_handle = 1;
|
||||
}
|
||||
|
||||
if 0 != use_network {
|
||||
start = clock();
|
||||
if !(0 == connect_to_imap(context, jobthread)) {
|
||||
info!(context, 0, "{}-fetch started...", jobthread.name);
|
||||
jobthread.imap.fetch(context);
|
||||
|
||||
if jobthread.imap.should_reconnect() {
|
||||
info!(
|
||||
context,
|
||||
0, "{}-fetch aborted, starting over...", jobthread.name,
|
||||
);
|
||||
jobthread.imap.fetch(context);
|
||||
}
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
"{}-fetch done in {:.3} ms.",
|
||||
jobthread.name,
|
||||
clock().wrapping_sub(start) as f64 / 1000.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
jobthread.state.0.lock().unwrap().using_handle = 0;
|
||||
}
|
||||
|
||||
/* ******************************************************************************
|
||||
* the typical fetch, idle, interrupt-idle
|
||||
******************************************************************************/
|
||||
|
||||
unsafe fn connect_to_imap(context: &Context, jobthread: &dc_jobthread_t) -> libc::c_int {
|
||||
if jobthread.imap.is_connected() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
let mut ret_connected = dc_connect_to_configured_imap(context, &jobthread.imap);
|
||||
|
||||
if !(0 == ret_connected) {
|
||||
if context
|
||||
.sql
|
||||
.get_config_int(context, "folders_configured")
|
||||
.unwrap_or_default()
|
||||
< 3
|
||||
{
|
||||
jobthread.imap.configure_folders(context, 0x1);
|
||||
}
|
||||
|
||||
if let Some(mvbox_name) = context
|
||||
.sql
|
||||
.get_config(context, jobthread.folder_config_name)
|
||||
{
|
||||
jobthread.imap.set_watch_folder(mvbox_name);
|
||||
} else {
|
||||
jobthread.imap.disconnect(context);
|
||||
ret_connected = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ret_connected
|
||||
}
|
||||
|
||||
pub unsafe fn dc_jobthread_idle(
|
||||
context: &Context,
|
||||
jobthread: &dc_jobthread_t,
|
||||
use_network: libc::c_int,
|
||||
) {
|
||||
{
|
||||
let &(ref lock, ref cvar) = &*jobthread.state.clone();
|
||||
let mut state = lock.lock().unwrap();
|
||||
|
||||
if 0 != state.jobs_needed {
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
"{}-IDLE will not be started as it was interrupted while not ideling.",
|
||||
jobthread.name,
|
||||
);
|
||||
state.jobs_needed = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if 0 != state.suspended {
|
||||
while !state.idle {
|
||||
state = cvar.wait(state).unwrap();
|
||||
}
|
||||
state.idle = false;
|
||||
return;
|
||||
}
|
||||
|
||||
state.using_handle = 1;
|
||||
|
||||
if 0 == use_network {
|
||||
state.using_handle = 0;
|
||||
|
||||
while !state.idle {
|
||||
state = cvar.wait(state).unwrap();
|
||||
}
|
||||
state.idle = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
connect_to_imap(context, jobthread);
|
||||
info!(context, 0, "{}-IDLE started...", jobthread.name,);
|
||||
jobthread.imap.idle(context);
|
||||
info!(context, 0, "{}-IDLE ended.", jobthread.name);
|
||||
|
||||
jobthread.state.0.lock().unwrap().using_handle = 0;
|
||||
}
|
||||
@@ -1,754 +0,0 @@
|
||||
use std::ffi::CString;
|
||||
|
||||
use crate::constants::Event;
|
||||
use crate::constants::*;
|
||||
use crate::context::*;
|
||||
use crate::dc_array::*;
|
||||
use crate::dc_chat::*;
|
||||
use crate::dc_job::*;
|
||||
use crate::dc_msg::*;
|
||||
use crate::dc_saxparser::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::param::*;
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
// location handling
|
||||
#[derive(Clone, Default)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct dc_location {
|
||||
pub location_id: uint32_t,
|
||||
pub latitude: libc::c_double,
|
||||
pub longitude: libc::c_double,
|
||||
pub accuracy: libc::c_double,
|
||||
pub timestamp: i64,
|
||||
pub contact_id: uint32_t,
|
||||
pub msg_id: uint32_t,
|
||||
pub chat_id: uint32_t,
|
||||
pub marker: Option<String>,
|
||||
pub independent: uint32_t,
|
||||
}
|
||||
|
||||
impl dc_location {
|
||||
pub fn new() -> Self {
|
||||
dc_location {
|
||||
location_id: 0,
|
||||
latitude: 0.0,
|
||||
longitude: 0.0,
|
||||
accuracy: 0.0,
|
||||
timestamp: 0,
|
||||
contact_id: 0,
|
||||
msg_id: 0,
|
||||
chat_id: 0,
|
||||
marker: None,
|
||||
independent: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct dc_kml_t {
|
||||
pub addr: *mut libc::c_char,
|
||||
pub locations: Option<Vec<dc_location>>,
|
||||
pub tag: libc::c_int,
|
||||
pub curr: dc_location,
|
||||
}
|
||||
|
||||
impl dc_kml_t {
|
||||
pub fn new() -> Self {
|
||||
dc_kml_t {
|
||||
addr: std::ptr::null_mut(),
|
||||
locations: None,
|
||||
tag: 0,
|
||||
curr: dc_location::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// location streaming
|
||||
pub unsafe fn dc_send_locations_to_chat(
|
||||
context: &Context,
|
||||
chat_id: uint32_t,
|
||||
seconds: libc::c_int,
|
||||
) {
|
||||
let now = time();
|
||||
let mut msg: *mut dc_msg_t = 0 as *mut dc_msg_t;
|
||||
let is_sending_locations_before: bool;
|
||||
if !(seconds < 0i32 || chat_id <= 9i32 as libc::c_uint) {
|
||||
is_sending_locations_before = dc_is_sending_locations_to_chat(context, chat_id);
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats \
|
||||
SET locations_send_begin=?, \
|
||||
locations_send_until=? \
|
||||
WHERE id=?",
|
||||
params![
|
||||
if 0 != seconds { now } else { 0 },
|
||||
if 0 != seconds {
|
||||
now + seconds as i64
|
||||
} else {
|
||||
0
|
||||
},
|
||||
chat_id as i32,
|
||||
],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
if 0 != seconds && !is_sending_locations_before {
|
||||
msg = dc_msg_new(context, Viewtype::Text);
|
||||
(*msg).text =
|
||||
Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0));
|
||||
(*msg).param.set_int(Param::Cmd, 8);
|
||||
dc_send_msg(context, chat_id, msg);
|
||||
} else if 0 == seconds && is_sending_locations_before {
|
||||
let stock_str = CString::new(context.stock_system_msg(
|
||||
StockMessage::MsgLocationDisabled,
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
))
|
||||
.unwrap();
|
||||
dc_add_device_msg(context, chat_id, stock_str.as_ptr());
|
||||
}
|
||||
context.call_cb(
|
||||
Event::CHAT_MODIFIED,
|
||||
chat_id as uintptr_t,
|
||||
0i32 as uintptr_t,
|
||||
);
|
||||
if 0 != seconds {
|
||||
schedule_MAYBE_SEND_LOCATIONS(context, 0i32);
|
||||
dc_job_add(
|
||||
context,
|
||||
5007i32,
|
||||
chat_id as libc::c_int,
|
||||
Params::new(),
|
||||
seconds + 1i32,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
dc_msg_unref(msg);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* job to send locations out to all chats that want them
|
||||
******************************************************************************/
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn schedule_MAYBE_SEND_LOCATIONS(context: &Context, flags: libc::c_int) {
|
||||
if 0 != flags & 0x1 || !dc_job_action_exists(context, 5005) {
|
||||
dc_job_add(context, 5005, 0, Params::new(), 60);
|
||||
};
|
||||
}
|
||||
|
||||
pub fn dc_is_sending_locations_to_chat(context: &Context, chat_id: u32) -> bool {
|
||||
context
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;",
|
||||
params![if chat_id == 0 { 1 } else { 0 }, chat_id as i32, time()],
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn dc_set_location(
|
||||
context: &Context,
|
||||
latitude: libc::c_double,
|
||||
longitude: libc::c_double,
|
||||
accuracy: libc::c_double,
|
||||
) -> libc::c_int {
|
||||
if latitude == 0.0 && longitude == 0.0 {
|
||||
return 1;
|
||||
}
|
||||
|
||||
context.sql.query_map(
|
||||
"SELECT id FROM chats WHERE locations_send_until>?;",
|
||||
params![time()], |row| row.get::<_, i32>(0),
|
||||
|chats| {
|
||||
let mut continue_streaming = false;
|
||||
|
||||
for chat in chats {
|
||||
let chat_id = chat?;
|
||||
context.sql.execute(
|
||||
"INSERT INTO locations \
|
||||
(latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);",
|
||||
params![
|
||||
latitude,
|
||||
longitude,
|
||||
accuracy,
|
||||
time(),
|
||||
chat_id,
|
||||
1,
|
||||
]
|
||||
)?;
|
||||
continue_streaming = true;
|
||||
}
|
||||
if continue_streaming {
|
||||
context.call_cb(Event::LOCATION_CHANGED, 1, 0);
|
||||
};
|
||||
unsafe { schedule_MAYBE_SEND_LOCATIONS(context, 0) };
|
||||
Ok(continue_streaming as libc::c_int)
|
||||
}
|
||||
).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn dc_get_locations(
|
||||
context: &Context,
|
||||
chat_id: uint32_t,
|
||||
contact_id: uint32_t,
|
||||
timestamp_from: i64,
|
||||
mut timestamp_to: i64,
|
||||
) -> *mut dc_array_t {
|
||||
if timestamp_to == 0 {
|
||||
timestamp_to = time() + 10;
|
||||
}
|
||||
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent, \
|
||||
m.id, l.from_id, l.chat_id, m.txt \
|
||||
FROM locations l LEFT JOIN msgs m ON l.id=m.location_id WHERE (? OR l.chat_id=?) \
|
||||
AND (? OR l.from_id=?) \
|
||||
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \
|
||||
ORDER BY l.timestamp DESC, l.id DESC, m.id DESC;",
|
||||
params![
|
||||
if chat_id == 0 { 1 } else { 0 },
|
||||
chat_id as i32,
|
||||
if contact_id == 0 { 1 } else { 0 },
|
||||
contact_id as i32,
|
||||
timestamp_from,
|
||||
timestamp_to,
|
||||
],
|
||||
|row| {
|
||||
let msg_id = row.get(6)?;
|
||||
let txt: String = row.get(9)?;
|
||||
let marker = if msg_id != 0 && is_marker(&txt) {
|
||||
Some(txt)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let loc = dc_location {
|
||||
location_id: row.get(0)?,
|
||||
latitude: row.get(1)?,
|
||||
longitude: row.get(2)?,
|
||||
accuracy: row.get(3)?,
|
||||
timestamp: row.get(4)?,
|
||||
independent: row.get(5)?,
|
||||
msg_id: msg_id,
|
||||
contact_id: row.get(7)?,
|
||||
chat_id: row.get(8)?,
|
||||
marker: marker,
|
||||
};
|
||||
Ok(loc)
|
||||
},
|
||||
|locations| {
|
||||
let mut ret = Vec::new();
|
||||
|
||||
for location in locations {
|
||||
ret.push(location?);
|
||||
}
|
||||
Ok(dc_array_t::from(ret).into_raw())
|
||||
},
|
||||
)
|
||||
.unwrap_or_else(|_| std::ptr::null_mut())
|
||||
}
|
||||
|
||||
fn is_marker(txt: &str) -> bool {
|
||||
txt.len() == 1 && txt.chars().next().unwrap() != ' '
|
||||
}
|
||||
|
||||
pub fn dc_delete_all_locations(context: &Context) -> bool {
|
||||
if sql::execute(context, &context.sql, "DELETE FROM locations;", params![]).is_err() {
|
||||
return false;
|
||||
}
|
||||
context.call_cb(Event::LOCATION_CHANGED, 0, 0);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn dc_get_location_kml(
|
||||
context: &Context,
|
||||
chat_id: uint32_t,
|
||||
last_added_location_id: *mut uint32_t,
|
||||
) -> *mut libc::c_char {
|
||||
let mut success: libc::c_int = 0;
|
||||
let now = time();
|
||||
let mut location_count: libc::c_int = 0;
|
||||
let mut ret = String::new();
|
||||
|
||||
let self_addr = context
|
||||
.sql
|
||||
.get_config(context, "configured_addr")
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Ok((locations_send_begin, locations_send_until, locations_last_sent)) = context.sql.query_row(
|
||||
"SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;",
|
||||
params![chat_id as i32], |row| {
|
||||
let send_begin: i64 = row.get(0)?;
|
||||
let send_until: i64 = row.get(1)?;
|
||||
let last_sent: i64 = row.get(2)?;
|
||||
|
||||
Ok((send_begin, send_until, last_sent))
|
||||
}
|
||||
) {
|
||||
if !(locations_send_begin == 0 || now > locations_send_until) {
|
||||
ret += &format!(
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"{}\">\n",
|
||||
self_addr,
|
||||
);
|
||||
|
||||
context.sql.query_map(
|
||||
"SELECT id, latitude, longitude, accuracy, timestamp\
|
||||
FROM locations WHERE from_id=? \
|
||||
AND timestamp>=? \
|
||||
AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?)) \
|
||||
AND independent=0 \
|
||||
GROUP BY timestamp \
|
||||
ORDER BY timestamp;",
|
||||
params![1, locations_send_begin, locations_last_sent, 1],
|
||||
|row| {
|
||||
let location_id: i32 = row.get(0)?;
|
||||
let latitude: f64 = row.get(1)?;
|
||||
let longitude: f64 = row.get(2)?;
|
||||
let accuracy: f64 = row.get(3)?;
|
||||
let timestamp = unsafe { get_kml_timestamp(row.get(4)?) };
|
||||
|
||||
Ok((location_id, latitude, longitude, accuracy, timestamp))
|
||||
},
|
||||
|rows| {
|
||||
for row in rows {
|
||||
let (location_id, latitude, longitude, accuracy, timestamp) = row?;
|
||||
ret += &format!(
|
||||
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n\x00",
|
||||
as_str(timestamp),
|
||||
accuracy,
|
||||
longitude,
|
||||
latitude
|
||||
);
|
||||
location_count += 1;
|
||||
if !last_added_location_id.is_null() {
|
||||
unsafe { *last_added_location_id = location_id as u32 };
|
||||
}
|
||||
unsafe { free(timestamp as *mut libc::c_void) };
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
).unwrap(); // TODO: better error handling
|
||||
}
|
||||
}
|
||||
|
||||
if location_count > 0 {
|
||||
ret += "</Document>\n</kml>";
|
||||
success = 1;
|
||||
}
|
||||
|
||||
if 0 != success {
|
||||
unsafe { ret.strdup() }
|
||||
} else {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* create kml-files
|
||||
******************************************************************************/
|
||||
unsafe fn get_kml_timestamp(utc: i64) -> *mut libc::c_char {
|
||||
// Returns a string formatted as YYYY-MM-DDTHH:MM:SSZ. The trailing `Z` indicates UTC.
|
||||
let res = chrono::NaiveDateTime::from_timestamp(utc, 0)
|
||||
.format("%Y-%m-%dT%H:%M:%SZ")
|
||||
.to_string();
|
||||
res.strdup()
|
||||
}
|
||||
|
||||
pub unsafe fn dc_get_message_kml(
|
||||
timestamp: i64,
|
||||
latitude: libc::c_double,
|
||||
longitude: libc::c_double,
|
||||
) -> *mut libc::c_char {
|
||||
let timestamp_str = get_kml_timestamp(timestamp);
|
||||
let latitude_str = dc_ftoa(latitude);
|
||||
let longitude_str = dc_ftoa(longitude);
|
||||
|
||||
let ret = dc_mprintf(
|
||||
b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
|
||||
<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n\
|
||||
<Document>\n\
|
||||
<Placemark>\
|
||||
<Timestamp><when>%s</when></Timestamp>\
|
||||
<Point><coordinates>%s,%s</coordinates></Point>\
|
||||
</Placemark>\n\
|
||||
</Document>\n\
|
||||
</kml>\x00" as *const u8 as *const libc::c_char,
|
||||
timestamp_str,
|
||||
longitude_str, // reverse order!
|
||||
latitude_str,
|
||||
);
|
||||
|
||||
free(latitude_str as *mut libc::c_void);
|
||||
free(longitude_str as *mut libc::c_void);
|
||||
free(timestamp_str as *mut libc::c_void);
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn dc_set_kml_sent_timestamp(context: &Context, chat_id: u32, timestamp: i64) -> bool {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats SET locations_last_sent=? WHERE id=?;",
|
||||
params![timestamp, chat_id as i32],
|
||||
)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub fn dc_set_msg_location_id(context: &Context, msg_id: u32, location_id: u32) -> bool {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE msgs SET location_id=? WHERE id=?;",
|
||||
params![location_id, msg_id as i32],
|
||||
)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub unsafe fn dc_save_locations(
|
||||
context: &Context,
|
||||
chat_id: u32,
|
||||
contact_id: u32,
|
||||
locations_opt: &Option<Vec<dc_location>>,
|
||||
independent: libc::c_int,
|
||||
) -> u32 {
|
||||
if chat_id <= 9 || locations_opt.is_none() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let locations = locations_opt.as_ref().unwrap();
|
||||
context
|
||||
.sql
|
||||
.prepare2(
|
||||
"SELECT id FROM locations WHERE timestamp=? AND from_id=?",
|
||||
"INSERT INTO locations\
|
||||
(timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \
|
||||
VALUES (?,?,?,?,?,?,?);",
|
||||
|mut stmt_test, mut stmt_insert, conn| {
|
||||
let mut newest_timestamp = 0;
|
||||
let mut newest_location_id = 0;
|
||||
|
||||
for location in locations {
|
||||
let exists =
|
||||
stmt_test.exists(params![location.timestamp, contact_id as i32])?;
|
||||
|
||||
if 0 != independent || !exists {
|
||||
stmt_insert.execute(params![
|
||||
location.timestamp,
|
||||
contact_id as i32,
|
||||
chat_id as i32,
|
||||
location.latitude,
|
||||
location.longitude,
|
||||
location.accuracy,
|
||||
independent,
|
||||
])?;
|
||||
|
||||
if location.timestamp > newest_timestamp {
|
||||
newest_timestamp = location.timestamp;
|
||||
newest_location_id = sql::get_rowid2_with_conn(
|
||||
context,
|
||||
conn,
|
||||
"locations",
|
||||
"timestamp",
|
||||
location.timestamp,
|
||||
"from_id",
|
||||
contact_id as i32,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(newest_location_id)
|
||||
},
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub unsafe fn dc_kml_parse(
|
||||
context: &Context,
|
||||
content: *const libc::c_char,
|
||||
content_bytes: size_t,
|
||||
) -> dc_kml_t {
|
||||
let mut kml = dc_kml_t::new();
|
||||
let mut content_nullterminated: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut saxparser: dc_saxparser_t = dc_saxparser_t {
|
||||
starttag_cb: None,
|
||||
endtag_cb: None,
|
||||
text_cb: None,
|
||||
userdata: 0 as *mut libc::c_void,
|
||||
};
|
||||
|
||||
if content_bytes > (1 * 1024 * 1024) {
|
||||
warn!(
|
||||
context,
|
||||
0, "A kml-files with {} bytes is larger than reasonably expected.", content_bytes,
|
||||
);
|
||||
} else {
|
||||
content_nullterminated = dc_null_terminate(content, content_bytes as libc::c_int);
|
||||
if !content_nullterminated.is_null() {
|
||||
kml.locations = Some(Vec::with_capacity(100));
|
||||
dc_saxparser_init(
|
||||
&mut saxparser,
|
||||
&mut kml as *mut dc_kml_t as *mut libc::c_void,
|
||||
);
|
||||
dc_saxparser_set_tag_handler(
|
||||
&mut saxparser,
|
||||
Some(kml_starttag_cb),
|
||||
Some(kml_endtag_cb),
|
||||
);
|
||||
dc_saxparser_set_text_handler(&mut saxparser, Some(kml_text_cb));
|
||||
dc_saxparser_parse(&mut saxparser, content_nullterminated);
|
||||
}
|
||||
}
|
||||
|
||||
free(content_nullterminated as *mut libc::c_void);
|
||||
|
||||
kml
|
||||
}
|
||||
|
||||
unsafe fn kml_text_cb(userdata: *mut libc::c_void, text: *const libc::c_char, _len: libc::c_int) {
|
||||
let mut kml: *mut dc_kml_t = userdata as *mut dc_kml_t;
|
||||
if 0 != (*kml).tag & (0x4 | 0x10) {
|
||||
let mut val: *mut libc::c_char = dc_strdup(text);
|
||||
dc_str_replace(
|
||||
&mut val,
|
||||
b"\n\x00" as *const u8 as *const libc::c_char,
|
||||
b"\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
dc_str_replace(
|
||||
&mut val,
|
||||
b"\r\x00" as *const u8 as *const libc::c_char,
|
||||
b"\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
dc_str_replace(
|
||||
&mut val,
|
||||
b"\t\x00" as *const u8 as *const libc::c_char,
|
||||
b"\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
dc_str_replace(
|
||||
&mut val,
|
||||
b" \x00" as *const u8 as *const libc::c_char,
|
||||
b"\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
if 0 != (*kml).tag & 0x4 && strlen(val) >= 19 {
|
||||
// YYYY-MM-DDTHH:MM:SSZ
|
||||
// 0 4 7 10 13 16 19
|
||||
let val_r = as_str(val);
|
||||
match chrono::NaiveDateTime::parse_from_str(val_r, "%Y-%m-%dT%H:%M:%SZ") {
|
||||
Ok(res) => {
|
||||
(*kml).curr.timestamp = res.timestamp();
|
||||
if (*kml).curr.timestamp > time() {
|
||||
(*kml).curr.timestamp = time();
|
||||
}
|
||||
}
|
||||
Err(_err) => {
|
||||
(*kml).curr.timestamp = time();
|
||||
}
|
||||
}
|
||||
} else if 0 != (*kml).tag & 0x10 {
|
||||
let mut comma: *mut libc::c_char = strchr(val, ',' as i32);
|
||||
if !comma.is_null() {
|
||||
let longitude: *mut libc::c_char = val;
|
||||
let latitude: *mut libc::c_char = comma.offset(1isize);
|
||||
*comma = 0 as libc::c_char;
|
||||
comma = strchr(latitude, ',' as i32);
|
||||
if !comma.is_null() {
|
||||
*comma = 0 as libc::c_char
|
||||
}
|
||||
(*kml).curr.latitude = dc_atof(latitude);
|
||||
(*kml).curr.longitude = dc_atof(longitude)
|
||||
}
|
||||
}
|
||||
free(val as *mut libc::c_void);
|
||||
};
|
||||
}
|
||||
|
||||
unsafe fn kml_endtag_cb(userdata: *mut libc::c_void, tag: *const libc::c_char) {
|
||||
let mut kml: *mut dc_kml_t = userdata as *mut dc_kml_t;
|
||||
if strcmp(tag, b"placemark\x00" as *const u8 as *const libc::c_char) == 0 {
|
||||
if 0 != (*kml).tag & 0x1
|
||||
&& 0 != (*kml).curr.timestamp
|
||||
&& 0. != (*kml).curr.latitude
|
||||
&& 0. != (*kml).curr.longitude
|
||||
{
|
||||
let location = (*kml).curr.clone();
|
||||
((*kml).locations.as_mut().unwrap()).push(location);
|
||||
}
|
||||
(*kml).tag = 0
|
||||
};
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* parse kml-files
|
||||
******************************************************************************/
|
||||
unsafe fn kml_starttag_cb(
|
||||
userdata: *mut libc::c_void,
|
||||
tag: *const libc::c_char,
|
||||
attr: *mut *mut libc::c_char,
|
||||
) {
|
||||
let mut kml: *mut dc_kml_t = userdata as *mut dc_kml_t;
|
||||
if strcmp(tag, b"document\x00" as *const u8 as *const libc::c_char) == 0 {
|
||||
let addr: *const libc::c_char =
|
||||
dc_attr_find(attr, b"addr\x00" as *const u8 as *const libc::c_char);
|
||||
if !addr.is_null() {
|
||||
(*kml).addr = dc_strdup(addr)
|
||||
}
|
||||
} else if strcmp(tag, b"placemark\x00" as *const u8 as *const libc::c_char) == 0 {
|
||||
(*kml).tag = 0x1;
|
||||
(*kml).curr.timestamp = 0;
|
||||
(*kml).curr.latitude = 0 as libc::c_double;
|
||||
(*kml).curr.longitude = 0.0f64;
|
||||
(*kml).curr.accuracy = 0.0f64
|
||||
} else if strcmp(tag, b"timestamp\x00" as *const u8 as *const libc::c_char) == 0
|
||||
&& 0 != (*kml).tag & 0x1
|
||||
{
|
||||
(*kml).tag = 0x1 | 0x2
|
||||
} else if strcmp(tag, b"when\x00" as *const u8 as *const libc::c_char) == 0
|
||||
&& 0 != (*kml).tag & 0x2
|
||||
{
|
||||
(*kml).tag = 0x1 | 0x2 | 0x4
|
||||
} else if strcmp(tag, b"point\x00" as *const u8 as *const libc::c_char) == 0
|
||||
&& 0 != (*kml).tag & 0x1
|
||||
{
|
||||
(*kml).tag = 0x1 | 0x8
|
||||
} else if strcmp(tag, b"coordinates\x00" as *const u8 as *const libc::c_char) == 0
|
||||
&& 0 != (*kml).tag & 0x8
|
||||
{
|
||||
(*kml).tag = 0x1 | 0x8 | 0x10;
|
||||
let accuracy: *const libc::c_char =
|
||||
dc_attr_find(attr, b"accuracy\x00" as *const u8 as *const libc::c_char);
|
||||
if !accuracy.is_null() {
|
||||
(*kml).curr.accuracy = dc_atof(accuracy)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub unsafe fn dc_kml_unref(kml: &mut dc_kml_t) {
|
||||
free((*kml).addr as *mut libc::c_void);
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub unsafe fn dc_job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: *mut dc_job_t) {
|
||||
let now = time();
|
||||
let mut continue_streaming: libc::c_int = 1;
|
||||
info!(
|
||||
context,
|
||||
0, " ----------------- MAYBE_SEND_LOCATIONS -------------- ",
|
||||
);
|
||||
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT id, locations_send_begin, locations_last_sent \
|
||||
FROM chats \
|
||||
WHERE locations_send_until>?;",
|
||||
params![now],
|
||||
|row| {
|
||||
let chat_id: i32 = row.get(0)?;
|
||||
let locations_send_begin: i64 = row.get(1)?;
|
||||
let locations_last_sent: i64 = row.get(2)?;
|
||||
continue_streaming = 1;
|
||||
|
||||
// be a bit tolerant as the timer may not align exactly with time(NULL)
|
||||
if now - locations_last_sent < (60 - 3) {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some((chat_id, locations_send_begin, locations_last_sent)))
|
||||
}
|
||||
},
|
||||
|rows| {
|
||||
context.sql.prepare(
|
||||
"SELECT id \
|
||||
FROM locations \
|
||||
WHERE from_id=? \
|
||||
AND timestamp>=? \
|
||||
AND timestamp>? \
|
||||
AND independent=0 \
|
||||
ORDER BY timestamp;",
|
||||
|mut stmt_locations, _| {
|
||||
for (chat_id, locations_send_begin, locations_last_sent) in
|
||||
rows.filter_map(|r| match r {
|
||||
Ok(Some(v)) => Some(v),
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
// TODO: do I need to reset?
|
||||
if !stmt_locations
|
||||
.exists(params![1, locations_send_begin, locations_last_sent,])
|
||||
.unwrap_or_default()
|
||||
{
|
||||
// if there is no new location, there's nothing to send.
|
||||
// however, maybe we want to bypass this test eg. 15 minutes
|
||||
continue;
|
||||
}
|
||||
// pending locations are attached automatically to every message,
|
||||
// so also to this empty text message.
|
||||
// DC_CMD_LOCATION is only needed to create a nicer subject.
|
||||
//
|
||||
// for optimisation and to avoid flooding the sending queue,
|
||||
// we could sending these messages only if we're really online.
|
||||
// the easiest way to determine this, is to check for an empty message queue.
|
||||
// (might not be 100%, however, as positions are sent combined later
|
||||
// and dc_set_location() is typically called periodically, this is ok)
|
||||
let mut msg = dc_msg_new(context, Viewtype::Text);
|
||||
(*msg).hidden = 1;
|
||||
(*msg).param.set_int(Param::Cmd, 9);
|
||||
dc_send_msg(context, chat_id as u32, msg);
|
||||
dc_msg_unref(msg);
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
.unwrap(); // TODO: Better error handling
|
||||
|
||||
if 0 != continue_streaming {
|
||||
schedule_MAYBE_SEND_LOCATIONS(context, 0x1);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub unsafe fn dc_job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut dc_job_t) {
|
||||
// this function is called when location-streaming _might_ have ended for a chat.
|
||||
// the function checks, if location-streaming is really ended;
|
||||
// if so, a device-message is added if not yet done.
|
||||
|
||||
let chat_id = (*job).foreign_id;
|
||||
|
||||
if let Ok((send_begin, send_until)) = context.sql.query_row(
|
||||
"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?",
|
||||
params![chat_id as i32],
|
||||
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)),
|
||||
) {
|
||||
if !(send_begin != 0 && time() <= send_until) {
|
||||
// still streaming -
|
||||
// may happen as several calls to dc_send_locations_to_chat()
|
||||
// do not un-schedule pending DC_MAYBE_SEND_LOC_ENDED jobs
|
||||
if !(send_begin == 0 && send_until == 0) {
|
||||
// not streaming, device-message already sent
|
||||
if context.sql.execute(
|
||||
"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?",
|
||||
params![chat_id as i32],
|
||||
).is_ok() {
|
||||
let stock_str = CString::new(context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0)).unwrap();
|
||||
dc_add_device_msg(context, chat_id, stock_str.as_ptr());
|
||||
context.call_cb(
|
||||
Event::CHAT_MODIFIED,
|
||||
chat_id as usize,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::sql::Sql;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct dc_loginparam_t {
|
||||
pub addr: String,
|
||||
pub mail_server: String,
|
||||
pub mail_user: String,
|
||||
pub mail_pw: String,
|
||||
pub mail_port: i32,
|
||||
pub send_server: String,
|
||||
pub send_user: String,
|
||||
pub send_pw: String,
|
||||
pub send_port: i32,
|
||||
pub server_flags: i32,
|
||||
}
|
||||
|
||||
impl dc_loginparam_t {
|
||||
pub fn addr_str(&self) -> &str {
|
||||
self.addr.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_loginparam_new() -> dc_loginparam_t {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn dc_loginparam_read(
|
||||
context: &Context,
|
||||
sql: &Sql,
|
||||
prefix: impl AsRef<str>,
|
||||
) -> dc_loginparam_t {
|
||||
let prefix = prefix.as_ref();
|
||||
|
||||
let key = format!("{}addr", prefix);
|
||||
let addr = sql
|
||||
.get_config(context, key)
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
let key = format!("{}mail_server", prefix);
|
||||
let mail_server = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}mail_port", prefix);
|
||||
let mail_port = sql.get_config_int(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}mail_user", prefix);
|
||||
let mail_user = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}mail_pw", prefix);
|
||||
let mail_pw = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_server", prefix);
|
||||
let send_server = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_port", prefix);
|
||||
let send_port = sql.get_config_int(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_user", prefix);
|
||||
let send_user = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_pw", prefix);
|
||||
let send_pw = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}server_flags", prefix);
|
||||
let server_flags = sql.get_config_int(context, key).unwrap_or_default();
|
||||
|
||||
dc_loginparam_t {
|
||||
addr: addr.to_string(),
|
||||
mail_server,
|
||||
mail_user,
|
||||
mail_pw,
|
||||
mail_port,
|
||||
send_server,
|
||||
send_user,
|
||||
send_pw,
|
||||
send_port,
|
||||
server_flags,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_loginparam_write(
|
||||
context: &Context,
|
||||
loginparam: &dc_loginparam_t,
|
||||
sql: &Sql,
|
||||
prefix: impl AsRef<str>,
|
||||
) {
|
||||
let prefix = prefix.as_ref();
|
||||
|
||||
let key = format!("{}addr", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.addr)).ok();
|
||||
|
||||
let key = format!("{}mail_server", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.mail_server))
|
||||
.ok();
|
||||
|
||||
let key = format!("{}mail_port", prefix);
|
||||
sql.set_config_int(context, key, loginparam.mail_port).ok();
|
||||
|
||||
let key = format!("{}mail_user", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.mail_user))
|
||||
.ok();
|
||||
|
||||
let key = format!("{}mail_pw", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.mail_pw)).ok();
|
||||
|
||||
let key = format!("{}send_server", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.send_server))
|
||||
.ok();
|
||||
|
||||
let key = format!("{}send_port", prefix);
|
||||
sql.set_config_int(context, key, loginparam.send_port).ok();
|
||||
|
||||
let key = format!("{}send_user", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.send_user))
|
||||
.ok();
|
||||
|
||||
let key = format!("{}send_pw", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.send_pw)).ok();
|
||||
|
||||
let key = format!("{}server_flags", prefix);
|
||||
sql.set_config_int(context, key, loginparam.server_flags)
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn unset_empty(s: &String) -> Cow<String> {
|
||||
if s.is_empty() {
|
||||
Cow::Owned("unset".to_string())
|
||||
} else {
|
||||
Cow::Borrowed(s)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_loginparam_get_readable(loginparam: &dc_loginparam_t) -> String {
|
||||
let unset = "0";
|
||||
let pw = "***";
|
||||
|
||||
let flags_readable = get_readable_flags(loginparam.server_flags);
|
||||
|
||||
format!(
|
||||
"{} {}:{}:{}:{} {}:{}:{}:{} {}",
|
||||
unset_empty(&loginparam.addr),
|
||||
unset_empty(&loginparam.mail_user),
|
||||
if !loginparam.mail_pw.is_empty() {
|
||||
pw
|
||||
} else {
|
||||
unset
|
||||
},
|
||||
unset_empty(&loginparam.mail_server),
|
||||
loginparam.mail_port,
|
||||
unset_empty(&loginparam.send_user),
|
||||
if !loginparam.send_pw.is_empty() {
|
||||
pw
|
||||
} else {
|
||||
unset
|
||||
},
|
||||
unset_empty(&loginparam.send_server),
|
||||
loginparam.send_port,
|
||||
flags_readable,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_readable_flags(flags: i32) -> String {
|
||||
let mut res = String::new();
|
||||
for bit in 0..31 {
|
||||
if 0 != flags & 1 << bit {
|
||||
let mut flag_added = 0;
|
||||
if 1 << bit == 0x2 {
|
||||
res += "OAUTH2 ";
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x4 {
|
||||
res += "AUTH_NORMAL ";
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x100 {
|
||||
res += "IMAP_STARTTLS ";
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x200 {
|
||||
res += "IMAP_SSL ";
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x400 {
|
||||
res += "IMAP_PLAIN ";
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x10000 {
|
||||
res += "SMTP_STARTTLS ";
|
||||
flag_added = 1
|
||||
}
|
||||
if 1 << bit == 0x20000 {
|
||||
res += "SMTP_SSL ";
|
||||
flag_added = 1
|
||||
}
|
||||
if 1 << bit == 0x40000 {
|
||||
res += "SMTP_PLAIN ";
|
||||
flag_added = 1
|
||||
}
|
||||
if 0 == flag_added {
|
||||
res += &format!("{:#0x}", 1 << bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
if res.is_empty() {
|
||||
res += "0";
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
184
src/dc_lot.rs
184
src/dc_lot.rs
@@ -1,184 +0,0 @@
|
||||
use crate::contact::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_chat::*;
|
||||
use crate::dc_msg::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
/* * Structure behind dc_lot_t */
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct dc_lot_t {
|
||||
pub magic: uint32_t,
|
||||
pub text1_meaning: libc::c_int,
|
||||
pub text1: *mut libc::c_char,
|
||||
pub text2: *mut libc::c_char,
|
||||
pub timestamp: i64,
|
||||
pub state: libc::c_int,
|
||||
pub id: uint32_t,
|
||||
pub fingerprint: *mut libc::c_char,
|
||||
pub invitenumber: *mut libc::c_char,
|
||||
pub auth: *mut libc::c_char,
|
||||
}
|
||||
|
||||
/* *
|
||||
* @class dc_lot_t
|
||||
*
|
||||
* An object containing a set of values.
|
||||
* The meaning of the values is defined by the function returning the object.
|
||||
* Lot objects are created
|
||||
* eg. by chatlist.get_summary() or dc_msg_get_summary().
|
||||
*
|
||||
* NB: _Lot_ is used in the meaning _heap_ here.
|
||||
*/
|
||||
pub unsafe fn dc_lot_new() -> *mut dc_lot_t {
|
||||
let mut lot: *mut dc_lot_t;
|
||||
lot = calloc(1, ::std::mem::size_of::<dc_lot_t>()) as *mut dc_lot_t;
|
||||
assert!(!lot.is_null());
|
||||
|
||||
(*lot).magic = 0x107107i32 as uint32_t;
|
||||
(*lot).text1_meaning = 0i32;
|
||||
|
||||
lot
|
||||
}
|
||||
|
||||
pub unsafe fn dc_lot_empty(mut lot: *mut dc_lot_t) {
|
||||
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
|
||||
return;
|
||||
}
|
||||
free((*lot).text1 as *mut libc::c_void);
|
||||
(*lot).text1 = 0 as *mut libc::c_char;
|
||||
(*lot).text1_meaning = 0i32;
|
||||
free((*lot).text2 as *mut libc::c_void);
|
||||
(*lot).text2 = 0 as *mut libc::c_char;
|
||||
free((*lot).fingerprint as *mut libc::c_void);
|
||||
(*lot).fingerprint = 0 as *mut libc::c_char;
|
||||
free((*lot).invitenumber as *mut libc::c_void);
|
||||
(*lot).invitenumber = 0 as *mut libc::c_char;
|
||||
free((*lot).auth as *mut libc::c_void);
|
||||
(*lot).auth = 0 as *mut libc::c_char;
|
||||
(*lot).timestamp = 0;
|
||||
(*lot).state = 0i32;
|
||||
(*lot).id = 0i32 as uint32_t;
|
||||
}
|
||||
|
||||
pub unsafe fn dc_lot_unref(mut set: *mut dc_lot_t) {
|
||||
if set.is_null() || (*set).magic != 0x107107i32 as libc::c_uint {
|
||||
return;
|
||||
}
|
||||
dc_lot_empty(set);
|
||||
(*set).magic = 0i32 as uint32_t;
|
||||
free(set as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn dc_lot_get_text1(lot: *const dc_lot_t) -> *mut libc::c_char {
|
||||
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
|
||||
return 0 as *mut libc::c_char;
|
||||
}
|
||||
|
||||
dc_strdup_keep_null((*lot).text1)
|
||||
}
|
||||
|
||||
pub unsafe fn dc_lot_get_text2(lot: *const dc_lot_t) -> *mut libc::c_char {
|
||||
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
|
||||
return 0 as *mut libc::c_char;
|
||||
}
|
||||
|
||||
dc_strdup_keep_null((*lot).text2)
|
||||
}
|
||||
|
||||
pub unsafe fn dc_lot_get_text1_meaning(lot: *const dc_lot_t) -> libc::c_int {
|
||||
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
|
||||
return 0i32;
|
||||
}
|
||||
|
||||
(*lot).text1_meaning
|
||||
}
|
||||
|
||||
pub unsafe fn dc_lot_get_state(lot: *const dc_lot_t) -> libc::c_int {
|
||||
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
|
||||
return 0i32;
|
||||
}
|
||||
|
||||
(*lot).state
|
||||
}
|
||||
|
||||
pub unsafe fn dc_lot_get_id(lot: *const dc_lot_t) -> uint32_t {
|
||||
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
|
||||
return 0i32 as uint32_t;
|
||||
}
|
||||
|
||||
(*lot).id
|
||||
}
|
||||
|
||||
pub unsafe fn dc_lot_get_timestamp(lot: *const dc_lot_t) -> i64 {
|
||||
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
|
||||
return 0;
|
||||
}
|
||||
|
||||
(*lot).timestamp
|
||||
}
|
||||
|
||||
/* library-internal */
|
||||
/* in practice, the user additionally cuts the string himself pixel-accurate */
|
||||
pub unsafe fn dc_lot_fill(
|
||||
mut lot: *mut dc_lot_t,
|
||||
msg: *mut dc_msg_t,
|
||||
chat: *const Chat,
|
||||
contact: Option<&Contact>,
|
||||
context: &Context,
|
||||
) {
|
||||
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint || msg.is_null() {
|
||||
return;
|
||||
}
|
||||
if (*msg).state == 19i32 {
|
||||
(*lot).text1 = context.stock_str(StockMessage::Draft).strdup();
|
||||
(*lot).text1_meaning = 1i32
|
||||
} else if (*msg).from_id == 1i32 as libc::c_uint {
|
||||
if 0 != dc_msg_is_info(msg) || 0 != dc_chat_is_self_talk(chat) {
|
||||
(*lot).text1 = 0 as *mut libc::c_char;
|
||||
(*lot).text1_meaning = 0i32
|
||||
} else {
|
||||
(*lot).text1 = context.stock_str(StockMessage::SelfMsg).strdup();
|
||||
(*lot).text1_meaning = 3i32
|
||||
}
|
||||
} else if chat.is_null() {
|
||||
(*lot).text1 = 0 as *mut libc::c_char;
|
||||
(*lot).text1_meaning = 0i32
|
||||
} else if (*chat).type_0 == 120i32 || (*chat).type_0 == 130i32 {
|
||||
if 0 != dc_msg_is_info(msg) || contact.is_none() {
|
||||
(*lot).text1 = 0 as *mut libc::c_char;
|
||||
(*lot).text1_meaning = 0i32
|
||||
} else {
|
||||
if !chat.is_null() && (*chat).id == 1i32 as libc::c_uint {
|
||||
if let Some(contact) = contact {
|
||||
(*lot).text1 = contact.get_display_name().strdup();
|
||||
} else {
|
||||
(*lot).text1 = std::ptr::null_mut();
|
||||
}
|
||||
} else {
|
||||
if let Some(contact) = contact {
|
||||
(*lot).text1 = contact.get_first_name().strdup();
|
||||
} else {
|
||||
(*lot).text1 = std::ptr::null_mut();
|
||||
}
|
||||
}
|
||||
(*lot).text1_meaning = 2i32;
|
||||
}
|
||||
}
|
||||
|
||||
let message_text = (*msg).text.as_ref().unwrap();
|
||||
|
||||
(*lot).text2 = dc_msg_get_summarytext_by_raw(
|
||||
(*msg).type_0,
|
||||
message_text.strdup(),
|
||||
&mut (*msg).param,
|
||||
160,
|
||||
context,
|
||||
);
|
||||
|
||||
(*lot).timestamp = dc_msg_get_timestamp(msg);
|
||||
(*lot).state = (*msg).state;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
2573
src/dc_mimeparser.rs
2573
src/dc_mimeparser.rs
File diff suppressed because it is too large
Load Diff
@@ -1,40 +0,0 @@
|
||||
use crate::constants::*;
|
||||
use crate::context::*;
|
||||
use crate::dc_job::*;
|
||||
use crate::dc_msg::*;
|
||||
use crate::param::Params;
|
||||
|
||||
pub unsafe fn dc_do_heuristics_moves(context: &Context, folder: &str, msg_id: u32) {
|
||||
if context
|
||||
.sql
|
||||
.get_config_int(context, "mvbox_move")
|
||||
.unwrap_or_else(|| 1)
|
||||
== 0
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if !dc_is_inbox(context, folder) && !dc_is_sentbox(context, folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
let msg = dc_msg_new_load(context, msg_id);
|
||||
if dc_msg_is_setupmessage(msg) {
|
||||
// do not move setup messages;
|
||||
// there may be a non-delta device that wants to handle it
|
||||
dc_msg_unref(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if dc_is_mvbox(context, folder) {
|
||||
dc_update_msg_move_state(context, (*msg).rfc724_mid, DC_MOVE_STATE_STAY);
|
||||
}
|
||||
|
||||
// 1 = dc message, 2 = reply to dc message
|
||||
if 0 != (*msg).is_dc_message {
|
||||
dc_job_add(context, 200, (*msg).id as libc::c_int, Params::new(), 0);
|
||||
dc_update_msg_move_state(context, (*msg).rfc724_mid, DC_MOVE_STATE_MOVING);
|
||||
}
|
||||
|
||||
dc_msg_unref(msg);
|
||||
}
|
||||
1587
src/dc_msg.rs
1587
src/dc_msg.rs
File diff suppressed because it is too large
Load Diff
346
src/dc_qr.rs
346
src/dc_qr.rs
@@ -1,346 +0,0 @@
|
||||
use percent_encoding::percent_decode_str;
|
||||
|
||||
use crate::contact::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_chat::*;
|
||||
use crate::dc_lot::*;
|
||||
use crate::dc_strencode::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::key::*;
|
||||
use crate::param::*;
|
||||
use crate::peerstate::*;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
// out-of-band verification
|
||||
// id=contact
|
||||
// text1=groupname
|
||||
// id=contact
|
||||
// id=contact
|
||||
// test1=formatted fingerprint
|
||||
// id=contact
|
||||
// text1=text
|
||||
// text1=URL
|
||||
// text1=error string
|
||||
pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc_lot_t {
|
||||
let mut current_block: u64;
|
||||
let mut payload: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
// must be normalized, if set
|
||||
let mut addr: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
// must be normalized, if set
|
||||
let mut fingerprint: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut name: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut invitenumber: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut auth: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut qr_parsed: *mut dc_lot_t = dc_lot_new();
|
||||
let mut chat_id: uint32_t = 0i32 as uint32_t;
|
||||
let mut device_msg: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut grpid: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut grpname: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
(*qr_parsed).state = 0i32;
|
||||
if !qr.is_null() {
|
||||
info!(context, 0, "Scanned QR code: {}", as_str(qr),);
|
||||
/* split parameters from the qr code
|
||||
------------------------------------ */
|
||||
if strncasecmp(
|
||||
qr,
|
||||
b"OPENPGP4FPR:\x00" as *const u8 as *const libc::c_char,
|
||||
strlen(b"OPENPGP4FPR:\x00" as *const u8 as *const libc::c_char),
|
||||
) == 0i32
|
||||
{
|
||||
payload =
|
||||
dc_strdup(&*qr.offset(strlen(
|
||||
b"OPENPGP4FPR:\x00" as *const u8 as *const libc::c_char,
|
||||
) as isize));
|
||||
let mut fragment: *mut libc::c_char = strchr(payload, '#' as i32);
|
||||
if !fragment.is_null() {
|
||||
*fragment = 0i32 as libc::c_char;
|
||||
fragment = fragment.offset(1isize);
|
||||
let param: Params = as_str(fragment).parse().expect("invalid params");
|
||||
addr = param
|
||||
.get(Param::Forwarded)
|
||||
.map(|s| s.strdup())
|
||||
.unwrap_or_else(|| std::ptr::null_mut());
|
||||
if !addr.is_null() {
|
||||
if let Some(ref name_enc) = param.get(Param::SetLongitude) {
|
||||
let name_r = percent_decode_str(name_enc)
|
||||
.decode_utf8()
|
||||
.expect("invalid name");
|
||||
name = normalize_name(name_r).strdup();
|
||||
}
|
||||
invitenumber = param
|
||||
.get(Param::ProfileImage)
|
||||
.map(|s| s.strdup())
|
||||
.unwrap_or_else(|| std::ptr::null_mut());
|
||||
auth = param
|
||||
.get(Param::Auth)
|
||||
.map(|s| s.strdup())
|
||||
.unwrap_or_else(|| std::ptr::null_mut());
|
||||
grpid = param
|
||||
.get(Param::GroupId)
|
||||
.map(|s| s.strdup())
|
||||
.unwrap_or_else(|| std::ptr::null_mut());
|
||||
if !grpid.is_null() {
|
||||
if let Some(grpname_enc) = param.get(Param::GroupName) {
|
||||
let grpname_r = percent_decode_str(grpname_enc)
|
||||
.decode_utf8()
|
||||
.expect("invalid groupname");
|
||||
grpname = grpname_r.strdup();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fingerprint = dc_normalize_fingerprint_c(payload);
|
||||
current_block = 5023038348526654800;
|
||||
} else if strncasecmp(
|
||||
qr,
|
||||
b"mailto:\x00" as *const u8 as *const libc::c_char,
|
||||
strlen(b"mailto:\x00" as *const u8 as *const libc::c_char),
|
||||
) == 0i32
|
||||
{
|
||||
payload = dc_strdup(
|
||||
&*qr.offset(strlen(b"mailto:\x00" as *const u8 as *const libc::c_char) as isize),
|
||||
);
|
||||
let query: *mut libc::c_char = strchr(payload, '?' as i32);
|
||||
if !query.is_null() {
|
||||
*query = 0i32 as libc::c_char
|
||||
}
|
||||
addr = dc_strdup(payload);
|
||||
current_block = 5023038348526654800;
|
||||
} else if strncasecmp(
|
||||
qr,
|
||||
b"SMTP:\x00" as *const u8 as *const libc::c_char,
|
||||
strlen(b"SMTP:\x00" as *const u8 as *const libc::c_char),
|
||||
) == 0i32
|
||||
{
|
||||
payload = dc_strdup(
|
||||
&*qr.offset(strlen(b"SMTP:\x00" as *const u8 as *const libc::c_char) as isize),
|
||||
);
|
||||
let colon: *mut libc::c_char = strchr(payload, ':' as i32);
|
||||
if !colon.is_null() {
|
||||
*colon = 0i32 as libc::c_char
|
||||
}
|
||||
addr = dc_strdup(payload);
|
||||
current_block = 5023038348526654800;
|
||||
} else if strncasecmp(
|
||||
qr,
|
||||
b"MATMSG:\x00" as *const u8 as *const libc::c_char,
|
||||
strlen(b"MATMSG:\x00" as *const u8 as *const libc::c_char),
|
||||
) == 0i32
|
||||
{
|
||||
/* scheme: `MATMSG:TO:addr...;SUB:subject...;BODY:body...;` - there may or may not be linebreaks after the fields */
|
||||
/* does not work when the text `TO:` is used in subject/body _and_ TO: is not the first field. we ignore this case. */
|
||||
let to: *mut libc::c_char = strstr(qr, b"TO:\x00" as *const u8 as *const libc::c_char);
|
||||
if !to.is_null() {
|
||||
addr = dc_strdup(&mut *to.offset(3isize));
|
||||
let semicolon: *mut libc::c_char = strchr(addr, ';' as i32);
|
||||
if !semicolon.is_null() {
|
||||
*semicolon = 0i32 as libc::c_char
|
||||
}
|
||||
current_block = 5023038348526654800;
|
||||
} else {
|
||||
(*qr_parsed).state = 400i32;
|
||||
(*qr_parsed).text1 =
|
||||
dc_strdup(b"Bad e-mail address.\x00" as *const u8 as *const libc::c_char);
|
||||
current_block = 16562876845594826114;
|
||||
}
|
||||
} else {
|
||||
if strncasecmp(
|
||||
qr,
|
||||
b"BEGIN:VCARD\x00" as *const u8 as *const libc::c_char,
|
||||
strlen(b"BEGIN:VCARD\x00" as *const u8 as *const libc::c_char),
|
||||
) == 0i32
|
||||
{
|
||||
let lines = dc_split_into_lines(qr);
|
||||
for &key in &lines {
|
||||
dc_trim(key);
|
||||
let mut value: *mut libc::c_char = strchr(key, ':' as i32);
|
||||
if !value.is_null() {
|
||||
*value = 0i32 as libc::c_char;
|
||||
value = value.offset(1isize);
|
||||
let mut semicolon_0: *mut libc::c_char = strchr(key, ';' as i32);
|
||||
if !semicolon_0.is_null() {
|
||||
*semicolon_0 = 0i32 as libc::c_char
|
||||
}
|
||||
if strcasecmp(key, b"EMAIL\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
{
|
||||
semicolon_0 = strchr(value, ';' as i32);
|
||||
if !semicolon_0.is_null() {
|
||||
*semicolon_0 = 0i32 as libc::c_char
|
||||
}
|
||||
addr = dc_strdup(value)
|
||||
} else if strcasecmp(key, b"N\x00" as *const u8 as *const libc::c_char)
|
||||
== 0i32
|
||||
{
|
||||
semicolon_0 = strchr(value, ';' as i32);
|
||||
if !semicolon_0.is_null() {
|
||||
semicolon_0 = strchr(semicolon_0.offset(1isize), ';' as i32);
|
||||
if !semicolon_0.is_null() {
|
||||
*semicolon_0 = 0i32 as libc::c_char
|
||||
}
|
||||
}
|
||||
name = dc_strdup(value);
|
||||
dc_str_replace(
|
||||
&mut name,
|
||||
b";\x00" as *const u8 as *const libc::c_char,
|
||||
b",\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
name = normalize_name(as_str(name)).strdup();
|
||||
}
|
||||
}
|
||||
}
|
||||
dc_free_splitted_lines(lines);
|
||||
}
|
||||
current_block = 5023038348526654800;
|
||||
}
|
||||
match current_block {
|
||||
16562876845594826114 => {}
|
||||
_ => {
|
||||
/* check the parameters
|
||||
---------------------- */
|
||||
if !addr.is_null() {
|
||||
/* urldecoding is needed at least for OPENPGP4FPR but should not hurt in the other cases */
|
||||
let mut temp: *mut libc::c_char = dc_urldecode(addr);
|
||||
free(addr as *mut libc::c_void);
|
||||
addr = temp;
|
||||
temp = addr_normalize(as_str(addr)).strdup();
|
||||
free(addr as *mut libc::c_void);
|
||||
addr = temp;
|
||||
if !may_be_valid_addr(as_str(addr)) {
|
||||
(*qr_parsed).state = 400i32;
|
||||
(*qr_parsed).text1 = dc_strdup(
|
||||
b"Bad e-mail address.\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
current_block = 16562876845594826114;
|
||||
} else {
|
||||
current_block = 14116432890150942211;
|
||||
}
|
||||
} else {
|
||||
current_block = 14116432890150942211;
|
||||
}
|
||||
match current_block {
|
||||
16562876845594826114 => {}
|
||||
_ => {
|
||||
if !fingerprint.is_null() {
|
||||
if strlen(fingerprint) != 40 {
|
||||
(*qr_parsed).state = 400i32;
|
||||
(*qr_parsed).text1 = dc_strdup(
|
||||
b"Bad fingerprint length in QR code.\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
);
|
||||
current_block = 16562876845594826114;
|
||||
} else {
|
||||
current_block = 5409161009579131794;
|
||||
}
|
||||
} else {
|
||||
current_block = 5409161009579131794;
|
||||
}
|
||||
match current_block {
|
||||
16562876845594826114 => {}
|
||||
_ => {
|
||||
if !fingerprint.is_null() {
|
||||
let peerstate = Peerstate::from_fingerprint(
|
||||
context,
|
||||
&context.sql,
|
||||
as_str(fingerprint),
|
||||
);
|
||||
if addr.is_null() || invitenumber.is_null() || auth.is_null() {
|
||||
if let Some(peerstate) = peerstate {
|
||||
(*qr_parsed).state = 210i32;
|
||||
let addr = peerstate
|
||||
.addr
|
||||
.as_ref()
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or_else(|| "");
|
||||
(*qr_parsed).id = Contact::add_or_lookup(
|
||||
context,
|
||||
"",
|
||||
addr,
|
||||
Origin::UnhandledQrScan,
|
||||
)
|
||||
.map(|(id, _)| id)
|
||||
.unwrap_or_default();
|
||||
dc_create_or_lookup_nchat_by_contact_id(
|
||||
context,
|
||||
(*qr_parsed).id,
|
||||
2i32,
|
||||
&mut chat_id,
|
||||
0 as *mut libc::c_int,
|
||||
);
|
||||
device_msg = dc_mprintf(
|
||||
b"%s verified.\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
peerstate.addr,
|
||||
)
|
||||
} else {
|
||||
(*qr_parsed).text1 =
|
||||
dc_format_fingerprint_c(fingerprint);
|
||||
(*qr_parsed).state = 230i32
|
||||
}
|
||||
} else {
|
||||
if !grpid.is_null() && !grpname.is_null() {
|
||||
(*qr_parsed).state = 202i32;
|
||||
(*qr_parsed).text1 = dc_strdup(grpname);
|
||||
(*qr_parsed).text2 = dc_strdup(grpid)
|
||||
} else {
|
||||
(*qr_parsed).state = 200i32
|
||||
}
|
||||
(*qr_parsed).id = Contact::add_or_lookup(
|
||||
context,
|
||||
as_str(name),
|
||||
as_str(addr),
|
||||
Origin::UnhandledQrScan,
|
||||
)
|
||||
.map(|(id, _)| id)
|
||||
.unwrap_or_default();
|
||||
(*qr_parsed).fingerprint = dc_strdup(fingerprint);
|
||||
(*qr_parsed).invitenumber = dc_strdup(invitenumber);
|
||||
(*qr_parsed).auth = dc_strdup(auth)
|
||||
}
|
||||
} else if !addr.is_null() {
|
||||
(*qr_parsed).state = 320i32;
|
||||
(*qr_parsed).id = Contact::add_or_lookup(
|
||||
context,
|
||||
as_str(name),
|
||||
as_str(addr),
|
||||
Origin::UnhandledQrScan,
|
||||
)
|
||||
.map(|(id, _)| id)
|
||||
.unwrap_or_default();
|
||||
} else if strstr(
|
||||
qr,
|
||||
b"http://\x00" as *const u8 as *const libc::c_char,
|
||||
) == qr as *mut libc::c_char
|
||||
|| strstr(
|
||||
qr,
|
||||
b"https://\x00" as *const u8 as *const libc::c_char,
|
||||
) == qr as *mut libc::c_char
|
||||
{
|
||||
(*qr_parsed).state = 332i32;
|
||||
(*qr_parsed).text1 = dc_strdup(qr)
|
||||
} else {
|
||||
(*qr_parsed).state = 330i32;
|
||||
(*qr_parsed).text1 = dc_strdup(qr)
|
||||
}
|
||||
if !device_msg.is_null() {
|
||||
dc_add_device_msg(context, chat_id, device_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
free(addr as *mut libc::c_void);
|
||||
free(fingerprint as *mut libc::c_void);
|
||||
free(payload as *mut libc::c_void);
|
||||
free(name as *mut libc::c_void);
|
||||
free(invitenumber as *mut libc::c_void);
|
||||
free(auth as *mut libc::c_void);
|
||||
free(device_msg as *mut libc::c_void);
|
||||
free(grpname as *mut libc::c_void);
|
||||
free(grpid as *mut libc::c_void);
|
||||
|
||||
qr_parsed
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
1089
src/dc_saxparser.rs
1089
src/dc_saxparser.rs
File diff suppressed because it is too large
Load Diff
@@ -1,966 +0,0 @@
|
||||
use std::ffi::CString;
|
||||
|
||||
use mmime::mailimf_types::*;
|
||||
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
||||
|
||||
use crate::aheader::EncryptPreference;
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_array::*;
|
||||
use crate::dc_chat::*;
|
||||
use crate::dc_configure::*;
|
||||
use crate::dc_e2ee::*;
|
||||
use crate::dc_lot::*;
|
||||
use crate::dc_mimeparser::*;
|
||||
use crate::dc_msg::*;
|
||||
use crate::dc_qr::*;
|
||||
use crate::dc_strencode::*;
|
||||
use crate::dc_token::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::key::*;
|
||||
use crate::param::*;
|
||||
use crate::peerstate::*;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
pub unsafe fn dc_get_securejoin_qr(
|
||||
context: &Context,
|
||||
group_chat_id: uint32_t,
|
||||
) -> *mut libc::c_char {
|
||||
/* =========================================================
|
||||
==== Alice - the inviter side ====
|
||||
==== Step 1 in "Setup verified contact" protocol ====
|
||||
========================================================= */
|
||||
|
||||
let mut fingerprint = 0 as *mut libc::c_char;
|
||||
let mut invitenumber: *mut libc::c_char;
|
||||
let mut auth: *mut libc::c_char;
|
||||
let mut chat = 0 as *mut Chat;
|
||||
let mut group_name = 0 as *mut libc::c_char;
|
||||
let mut group_name_urlencoded = 0 as *mut libc::c_char;
|
||||
let mut qr: Option<String> = None;
|
||||
|
||||
dc_ensure_secret_key_exists(context);
|
||||
invitenumber = dc_token_lookup(context, DC_TOKEN_INVITENUMBER, group_chat_id);
|
||||
if invitenumber.is_null() {
|
||||
invitenumber = dc_create_id().strdup();
|
||||
dc_token_save(context, DC_TOKEN_INVITENUMBER, group_chat_id, invitenumber);
|
||||
}
|
||||
auth = dc_token_lookup(context, DC_TOKEN_AUTH, group_chat_id);
|
||||
if auth.is_null() {
|
||||
auth = dc_create_id().strdup();
|
||||
dc_token_save(context, DC_TOKEN_AUTH, group_chat_id, auth);
|
||||
}
|
||||
let self_addr = context.sql.get_config(context, "configured_addr");
|
||||
|
||||
let cleanup = |fingerprint, chat, group_name, group_name_urlencoded| {
|
||||
free(fingerprint as *mut libc::c_void);
|
||||
free(invitenumber as *mut libc::c_void);
|
||||
free(auth as *mut libc::c_void);
|
||||
dc_chat_unref(chat);
|
||||
free(group_name as *mut libc::c_void);
|
||||
free(group_name_urlencoded as *mut libc::c_void);
|
||||
|
||||
if let Some(qr) = qr {
|
||||
qr.strdup()
|
||||
} else {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
};
|
||||
|
||||
if self_addr.is_none() {
|
||||
error!(context, 0, "Not configured, cannot generate QR code.",);
|
||||
return cleanup(fingerprint, chat, group_name, group_name_urlencoded);
|
||||
}
|
||||
|
||||
let self_addr = self_addr.unwrap();
|
||||
let self_name = context
|
||||
.sql
|
||||
.get_config(context, "displayname")
|
||||
.unwrap_or_default();
|
||||
|
||||
fingerprint = get_self_fingerprint(context);
|
||||
|
||||
if fingerprint.is_null() {
|
||||
return cleanup(fingerprint, chat, group_name, group_name_urlencoded);
|
||||
}
|
||||
|
||||
let self_addr_urlencoded = utf8_percent_encode(&self_addr, NON_ALPHANUMERIC).to_string();
|
||||
let self_name_urlencoded = utf8_percent_encode(&self_name, NON_ALPHANUMERIC).to_string();
|
||||
|
||||
qr = if 0 != group_chat_id {
|
||||
chat = dc_get_chat(context, group_chat_id);
|
||||
if chat.is_null() {
|
||||
error!(
|
||||
context,
|
||||
0, "Cannot get QR-code for chat-id {}", group_chat_id,
|
||||
);
|
||||
return cleanup(fingerprint, chat, group_name, group_name_urlencoded);
|
||||
}
|
||||
|
||||
group_name = dc_chat_get_name(chat);
|
||||
group_name_urlencoded = dc_urlencode(group_name);
|
||||
|
||||
Some(format!(
|
||||
"OPENPGP4FPR:{}#a={}&g={}&x={}&i={}&s={}",
|
||||
as_str(fingerprint),
|
||||
self_addr_urlencoded,
|
||||
as_str(group_name_urlencoded),
|
||||
as_str((*chat).grpid),
|
||||
as_str(invitenumber),
|
||||
as_str(auth),
|
||||
))
|
||||
} else {
|
||||
Some(format!(
|
||||
"OPENPGP4FPR:{}#a={}&n={}&i={}&s={}",
|
||||
as_str(fingerprint),
|
||||
self_addr_urlencoded,
|
||||
self_name_urlencoded,
|
||||
as_str(invitenumber),
|
||||
as_str(auth),
|
||||
))
|
||||
};
|
||||
|
||||
info!(context, 0, "Generated QR code: {}", qr.as_ref().unwrap());
|
||||
|
||||
cleanup(fingerprint, chat, group_name, group_name_urlencoded)
|
||||
}
|
||||
|
||||
fn get_self_fingerprint(context: &Context) -> *mut libc::c_char {
|
||||
if let Some(self_addr) = context.sql.get_config(context, "configured_addr") {
|
||||
if let Some(key) = Key::from_self_public(context, self_addr, &context.sql) {
|
||||
return key.fingerprint_c();
|
||||
}
|
||||
}
|
||||
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
|
||||
pub unsafe fn dc_join_securejoin(context: &Context, qr: *const libc::c_char) -> uint32_t {
|
||||
/* ==========================================================
|
||||
==== Bob - the joiner's side =====
|
||||
==== Step 2 in "Setup verified contact" protocol =====
|
||||
========================================================== */
|
||||
let mut ret_chat_id: libc::c_int = 0i32;
|
||||
let ongoing_allocated: libc::c_int;
|
||||
let mut contact_chat_id: uint32_t = 0i32 as uint32_t;
|
||||
let mut join_vg: libc::c_int = 0i32;
|
||||
let mut qr_scan: *mut dc_lot_t = 0 as *mut dc_lot_t;
|
||||
info!(context, 0, "Requesting secure-join ...",);
|
||||
dc_ensure_secret_key_exists(context);
|
||||
ongoing_allocated = dc_alloc_ongoing(context);
|
||||
if !(ongoing_allocated == 0i32) {
|
||||
qr_scan = dc_check_qr(context, qr);
|
||||
if qr_scan.is_null() || (*qr_scan).state != 200i32 && (*qr_scan).state != 202i32 {
|
||||
error!(context, 0, "Unknown QR code.",);
|
||||
} else {
|
||||
contact_chat_id = dc_create_chat_by_contact_id(context, (*qr_scan).id);
|
||||
if contact_chat_id == 0i32 as libc::c_uint {
|
||||
error!(context, 0, "Unknown contact.",);
|
||||
} else if !(context
|
||||
.running_state
|
||||
.clone()
|
||||
.read()
|
||||
.unwrap()
|
||||
.shall_stop_ongoing)
|
||||
{
|
||||
join_vg = ((*qr_scan).state == 202i32) as libc::c_int;
|
||||
{
|
||||
let bob_a = context.bob.clone();
|
||||
let mut bob = bob_a.write().unwrap();
|
||||
bob.status = 0;
|
||||
bob.qr_scan = qr_scan;
|
||||
}
|
||||
if 0 != fingerprint_equals_sender(context, (*qr_scan).fingerprint, contact_chat_id)
|
||||
{
|
||||
info!(context, 0, "Taking protocol shortcut.");
|
||||
context.bob.clone().write().unwrap().expects = 6;
|
||||
context.call_cb(
|
||||
Event::SECUREJOIN_JOINER_PROGRESS,
|
||||
chat_id_2_contact_id(context, contact_chat_id) as uintptr_t,
|
||||
400i32 as uintptr_t,
|
||||
);
|
||||
let own_fingerprint: *mut libc::c_char = get_self_fingerprint(context);
|
||||
send_handshake_msg(
|
||||
context,
|
||||
contact_chat_id,
|
||||
if 0 != join_vg {
|
||||
b"vg-request-with-auth\x00" as *const u8 as *const libc::c_char
|
||||
} else {
|
||||
b"vc-request-with-auth\x00" as *const u8 as *const libc::c_char
|
||||
},
|
||||
(*qr_scan).auth,
|
||||
own_fingerprint,
|
||||
if 0 != join_vg {
|
||||
(*qr_scan).text2
|
||||
} else {
|
||||
0 as *mut libc::c_char
|
||||
},
|
||||
);
|
||||
free(own_fingerprint as *mut libc::c_void);
|
||||
} else {
|
||||
context.bob.clone().write().unwrap().expects = 2;
|
||||
send_handshake_msg(
|
||||
context,
|
||||
contact_chat_id,
|
||||
if 0 != join_vg {
|
||||
b"vg-request\x00" as *const u8 as *const libc::c_char
|
||||
} else {
|
||||
b"vc-request\x00" as *const u8 as *const libc::c_char
|
||||
},
|
||||
(*qr_scan).invitenumber,
|
||||
0 as *const libc::c_char,
|
||||
0 as *const libc::c_char,
|
||||
);
|
||||
}
|
||||
|
||||
// Bob -> Alice
|
||||
while !(context
|
||||
.running_state
|
||||
.clone()
|
||||
.read()
|
||||
.unwrap()
|
||||
.shall_stop_ongoing)
|
||||
{
|
||||
std::thread::sleep(std::time::Duration::new(0, 3_000_000));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let bob_a = context.bob.clone();
|
||||
let mut bob = bob_a.write().unwrap();
|
||||
|
||||
bob.expects = 0;
|
||||
if bob.status == 1 {
|
||||
if 0 != join_vg {
|
||||
ret_chat_id = dc_get_chat_id_by_grpid(
|
||||
context,
|
||||
(*qr_scan).text2,
|
||||
0 as *mut libc::c_int,
|
||||
0 as *mut libc::c_int,
|
||||
) as libc::c_int
|
||||
} else {
|
||||
ret_chat_id = contact_chat_id as libc::c_int
|
||||
}
|
||||
}
|
||||
bob.qr_scan = std::ptr::null_mut();
|
||||
|
||||
dc_lot_unref(qr_scan);
|
||||
if 0 != ongoing_allocated {
|
||||
dc_free_ongoing(context);
|
||||
}
|
||||
ret_chat_id as uint32_t
|
||||
}
|
||||
|
||||
unsafe fn send_handshake_msg(
|
||||
context: &Context,
|
||||
contact_chat_id: uint32_t,
|
||||
step: *const libc::c_char,
|
||||
param2: *const libc::c_char,
|
||||
fingerprint: *const libc::c_char,
|
||||
grpid: *const libc::c_char,
|
||||
) {
|
||||
let mut msg: *mut dc_msg_t = dc_msg_new_untyped(context);
|
||||
(*msg).type_0 = Viewtype::Text;
|
||||
(*msg).text = Some(format!("Secure-Join: {}", to_string(step)));
|
||||
(*msg).hidden = 1;
|
||||
(*msg).param.set_int(Param::Cmd, 7);
|
||||
if step.is_null() {
|
||||
(*msg).param.remove(Param::Arg);
|
||||
} else {
|
||||
(*msg).param.set(Param::Arg, as_str(step));
|
||||
}
|
||||
if !param2.is_null() {
|
||||
(*msg).param.set(Param::Arg2, as_str(param2));
|
||||
}
|
||||
if !fingerprint.is_null() {
|
||||
(*msg).param.set(Param::Arg3, as_str(fingerprint));
|
||||
}
|
||||
if !grpid.is_null() {
|
||||
(*msg).param.set(Param::Arg4, as_str(grpid));
|
||||
}
|
||||
if strcmp(step, b"vg-request\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
|| strcmp(step, b"vc-request\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
{
|
||||
(*msg).param.set_int(
|
||||
Param::ForcePlaintext,
|
||||
ForcePlaintext::AddAutocryptHeader as i32,
|
||||
);
|
||||
} else {
|
||||
(*msg).param.set_int(Param::GuranteeE2ee, 1);
|
||||
}
|
||||
dc_send_msg(context, contact_chat_id, msg);
|
||||
dc_msg_unref(msg);
|
||||
}
|
||||
|
||||
unsafe fn chat_id_2_contact_id(context: &Context, contact_chat_id: uint32_t) -> uint32_t {
|
||||
let mut contact_id: uint32_t = 0i32 as uint32_t;
|
||||
let contacts: *mut dc_array_t = dc_get_chat_contacts(context, contact_chat_id);
|
||||
if !(dc_array_get_cnt(contacts) != 1) {
|
||||
contact_id = dc_array_get_id(contacts, 0i32 as size_t)
|
||||
}
|
||||
dc_array_unref(contacts);
|
||||
|
||||
contact_id
|
||||
}
|
||||
|
||||
unsafe fn fingerprint_equals_sender(
|
||||
context: &Context,
|
||||
fingerprint: *const libc::c_char,
|
||||
contact_chat_id: uint32_t,
|
||||
) -> libc::c_int {
|
||||
if fingerprint.is_null() {
|
||||
return 0;
|
||||
}
|
||||
let mut fingerprint_equal: libc::c_int = 0i32;
|
||||
let contacts = dc_get_chat_contacts(context, contact_chat_id);
|
||||
|
||||
if !(dc_array_get_cnt(contacts) != 1) {
|
||||
if let Ok(contact) = Contact::load_from_db(context, dc_array_get_id(contacts, 0)) {
|
||||
if let Some(peerstate) = Peerstate::from_addr(context, &context.sql, contact.get_addr())
|
||||
{
|
||||
let fingerprint_normalized = dc_normalize_fingerprint(as_str(fingerprint));
|
||||
if peerstate.public_key_fingerprint.is_some()
|
||||
&& &fingerprint_normalized == peerstate.public_key_fingerprint.as_ref().unwrap()
|
||||
{
|
||||
fingerprint_equal = 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
dc_array_unref(contacts);
|
||||
|
||||
fingerprint_equal
|
||||
}
|
||||
|
||||
/* library private: secure-join */
|
||||
pub unsafe fn dc_handle_securejoin_handshake(
|
||||
context: &Context,
|
||||
mimeparser: &dc_mimeparser_t,
|
||||
contact_id: uint32_t,
|
||||
) -> libc::c_int {
|
||||
let mut current_block: u64;
|
||||
let step: *const libc::c_char;
|
||||
let join_vg: libc::c_int;
|
||||
let mut scanned_fingerprint_of_alice: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut auth: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut own_fingerprint: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut contact_chat_id: uint32_t = 0i32 as uint32_t;
|
||||
let mut contact_chat_id_blocked: libc::c_int = 0i32;
|
||||
let mut grpid: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut ret: libc::c_int = 0i32;
|
||||
|
||||
if !(contact_id <= 9i32 as libc::c_uint) {
|
||||
step = lookup_field(mimeparser, "Secure-Join");
|
||||
if !step.is_null() {
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
">>>>>>>>>>>>>>>>>>>>>>>>> secure-join message \'{}\' received",
|
||||
as_str(step),
|
||||
);
|
||||
join_vg = (strncmp(step, b"vg-\x00" as *const u8 as *const libc::c_char, 3) == 0)
|
||||
as libc::c_int;
|
||||
dc_create_or_lookup_nchat_by_contact_id(
|
||||
context,
|
||||
contact_id,
|
||||
0i32,
|
||||
&mut contact_chat_id,
|
||||
&mut contact_chat_id_blocked,
|
||||
);
|
||||
if 0 != contact_chat_id_blocked {
|
||||
dc_unblock_chat(context, contact_chat_id);
|
||||
}
|
||||
ret = 0x2i32;
|
||||
if strcmp(step, b"vg-request\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
|| strcmp(step, b"vc-request\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
{
|
||||
/* =========================================================
|
||||
==== Alice - the inviter side ====
|
||||
==== Step 3 in "Setup verified contact" protocol ====
|
||||
========================================================= */
|
||||
// this message may be unencrypted (Bob, the joinder and the sender, might not have Alice's key yet)
|
||||
// it just ensures, we have Bobs key now. If we do _not_ have the key because eg. MitM has removed it,
|
||||
// send_message() will fail with the error "End-to-end-encryption unavailable unexpectedly.", so, there is no additional check needed here.
|
||||
// verify that the `Secure-Join-Invitenumber:`-header matches invitenumber written to the QR code
|
||||
let invitenumber: *const libc::c_char;
|
||||
invitenumber = lookup_field(mimeparser, "Secure-Join-Invitenumber");
|
||||
if invitenumber.is_null() {
|
||||
warn!(context, 0, "Secure-join denied (invitenumber missing).",);
|
||||
current_block = 4378276786830486580;
|
||||
} else if !dc_token_exists(context, DC_TOKEN_INVITENUMBER, invitenumber) {
|
||||
warn!(context, 0, "Secure-join denied (bad invitenumber).",);
|
||||
current_block = 4378276786830486580;
|
||||
} else {
|
||||
info!(context, 0, "Secure-join requested.",);
|
||||
|
||||
context.call_cb(
|
||||
Event::SECUREJOIN_INVITER_PROGRESS,
|
||||
contact_id as uintptr_t,
|
||||
300i32 as uintptr_t,
|
||||
);
|
||||
send_handshake_msg(
|
||||
context,
|
||||
contact_chat_id,
|
||||
if 0 != join_vg {
|
||||
b"vg-auth-required\x00" as *const u8 as *const libc::c_char
|
||||
} else {
|
||||
b"vc-auth-required\x00" as *const u8 as *const libc::c_char
|
||||
},
|
||||
0 as *const libc::c_char,
|
||||
0 as *const libc::c_char,
|
||||
0 as *const libc::c_char,
|
||||
);
|
||||
current_block = 10256747982273457880;
|
||||
}
|
||||
} else if strcmp(
|
||||
step,
|
||||
b"vg-auth-required\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
|| strcmp(
|
||||
step,
|
||||
b"vc-auth-required\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
let cond = {
|
||||
let bob_a = context.bob.clone();
|
||||
let bob = bob_a.read().unwrap();
|
||||
let scan = bob.qr_scan;
|
||||
scan.is_null() || bob.expects != 2 || 0 != join_vg && (*scan).state != 202
|
||||
};
|
||||
|
||||
if cond {
|
||||
warn!(context, 0, "auth-required message out of sync.",);
|
||||
// no error, just aborted somehow or a mail from another handshake
|
||||
current_block = 4378276786830486580;
|
||||
} else {
|
||||
{
|
||||
let scan = context.bob.clone().read().unwrap().qr_scan;
|
||||
scanned_fingerprint_of_alice = dc_strdup((*scan).fingerprint);
|
||||
auth = dc_strdup((*scan).auth);
|
||||
if 0 != join_vg {
|
||||
grpid = dc_strdup((*scan).text2)
|
||||
}
|
||||
}
|
||||
if 0 == encrypted_and_signed(mimeparser, scanned_fingerprint_of_alice) {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
if 0 != mimeparser.e2ee_helper.encrypted {
|
||||
b"No valid signature.\x00" as *const u8 as *const libc::c_char
|
||||
} else {
|
||||
b"Not encrypted.\x00" as *const u8 as *const libc::c_char
|
||||
},
|
||||
);
|
||||
end_bobs_joining(context, 0i32);
|
||||
current_block = 4378276786830486580;
|
||||
} else if 0
|
||||
== fingerprint_equals_sender(
|
||||
context,
|
||||
scanned_fingerprint_of_alice,
|
||||
contact_chat_id,
|
||||
)
|
||||
{
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
b"Fingerprint mismatch on joiner-side.\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
);
|
||||
end_bobs_joining(context, 0i32);
|
||||
current_block = 4378276786830486580;
|
||||
} else {
|
||||
info!(context, 0, "Fingerprint verified.",);
|
||||
own_fingerprint = get_self_fingerprint(context);
|
||||
context.call_cb(
|
||||
Event::SECUREJOIN_JOINER_PROGRESS,
|
||||
contact_id as uintptr_t,
|
||||
400i32 as uintptr_t,
|
||||
);
|
||||
context.bob.clone().write().unwrap().expects = 6;
|
||||
|
||||
send_handshake_msg(
|
||||
context,
|
||||
contact_chat_id,
|
||||
if 0 != join_vg {
|
||||
b"vg-request-with-auth\x00" as *const u8 as *const libc::c_char
|
||||
} else {
|
||||
b"vc-request-with-auth\x00" as *const u8 as *const libc::c_char
|
||||
},
|
||||
auth,
|
||||
own_fingerprint,
|
||||
grpid,
|
||||
);
|
||||
current_block = 10256747982273457880;
|
||||
}
|
||||
}
|
||||
} else if strcmp(
|
||||
step,
|
||||
b"vg-request-with-auth\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
|| strcmp(
|
||||
step,
|
||||
b"vc-request-with-auth\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
/* ============================================================
|
||||
==== Alice - the inviter side ====
|
||||
==== Steps 5+6 in "Setup verified contact" protocol ====
|
||||
==== Step 6 in "Out-of-band verified groups" protocol ====
|
||||
============================================================ */
|
||||
// verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob
|
||||
let fingerprint: *const libc::c_char;
|
||||
fingerprint = lookup_field(mimeparser, "Secure-Join-Fingerprint");
|
||||
if fingerprint.is_null() {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
b"Fingerprint not provided.\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
current_block = 4378276786830486580;
|
||||
} else if 0 == encrypted_and_signed(mimeparser, fingerprint) {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
b"Auth not encrypted.\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
current_block = 4378276786830486580;
|
||||
} else if 0 == fingerprint_equals_sender(context, fingerprint, contact_chat_id) {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
b"Fingerprint mismatch on inviter-side.\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
);
|
||||
current_block = 4378276786830486580;
|
||||
} else {
|
||||
info!(context, 0, "Fingerprint verified.",);
|
||||
// verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code
|
||||
let auth_0: *const libc::c_char;
|
||||
auth_0 = lookup_field(mimeparser, "Secure-Join-Auth");
|
||||
if auth_0.is_null() {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
b"Auth not provided.\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
current_block = 4378276786830486580;
|
||||
} else if !dc_token_exists(context, DC_TOKEN_AUTH, auth_0) {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
b"Auth invalid.\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
current_block = 4378276786830486580;
|
||||
} else if 0 == mark_peer_as_verified(context, fingerprint) {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
b"Fingerprint mismatch on inviter-side.\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
);
|
||||
current_block = 4378276786830486580;
|
||||
} else {
|
||||
Contact::scaleup_origin_by_id(
|
||||
context,
|
||||
contact_id,
|
||||
Origin::SecurejoinInvited,
|
||||
);
|
||||
info!(context, 0, "Auth verified.",);
|
||||
secure_connection_established(context, contact_chat_id);
|
||||
context.call_cb(
|
||||
Event::CONTACTS_CHANGED,
|
||||
contact_id as uintptr_t,
|
||||
0i32 as uintptr_t,
|
||||
);
|
||||
context.call_cb(
|
||||
Event::SECUREJOIN_INVITER_PROGRESS,
|
||||
contact_id as uintptr_t,
|
||||
600i32 as uintptr_t,
|
||||
);
|
||||
if 0 != join_vg {
|
||||
grpid = dc_strdup(lookup_field(mimeparser, "Secure-Join-Group"));
|
||||
let group_chat_id: uint32_t = dc_get_chat_id_by_grpid(
|
||||
context,
|
||||
grpid,
|
||||
0 as *mut libc::c_int,
|
||||
0 as *mut libc::c_int,
|
||||
);
|
||||
if group_chat_id == 0i32 as libc::c_uint {
|
||||
error!(context, 0, "Chat {} not found.", as_str(grpid),);
|
||||
current_block = 4378276786830486580;
|
||||
} else {
|
||||
dc_add_contact_to_chat_ex(
|
||||
context,
|
||||
group_chat_id,
|
||||
contact_id,
|
||||
0x1i32,
|
||||
);
|
||||
current_block = 10256747982273457880;
|
||||
}
|
||||
} else {
|
||||
send_handshake_msg(
|
||||
context,
|
||||
contact_chat_id,
|
||||
b"vc-contact-confirm\x00" as *const u8 as *const libc::c_char,
|
||||
0 as *const libc::c_char,
|
||||
0 as *const libc::c_char,
|
||||
0 as *const libc::c_char,
|
||||
);
|
||||
context.call_cb(
|
||||
Event::SECUREJOIN_INVITER_PROGRESS,
|
||||
contact_id as uintptr_t,
|
||||
1000i32 as uintptr_t,
|
||||
);
|
||||
current_block = 10256747982273457880;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if strcmp(
|
||||
step,
|
||||
b"vg-member-added\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
|| strcmp(
|
||||
step,
|
||||
b"vc-contact-confirm\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
if 0 != join_vg {
|
||||
ret = 0x1i32
|
||||
}
|
||||
if context.bob.clone().read().unwrap().expects != 6 {
|
||||
info!(context, 0, "Message belongs to a different handshake.",);
|
||||
current_block = 4378276786830486580;
|
||||
} else {
|
||||
let cond = {
|
||||
let scan = context.bob.clone().read().unwrap().qr_scan;
|
||||
scan.is_null() || 0 != join_vg && (*scan).state != 202
|
||||
};
|
||||
if cond {
|
||||
warn!(
|
||||
context,
|
||||
0, "Message out of sync or belongs to a different handshake.",
|
||||
);
|
||||
current_block = 4378276786830486580;
|
||||
} else {
|
||||
{
|
||||
let scan = context.bob.clone().read().unwrap().qr_scan;
|
||||
scanned_fingerprint_of_alice = dc_strdup((*scan).fingerprint);
|
||||
if 0 != join_vg {
|
||||
grpid = dc_strdup((*scan).text2)
|
||||
}
|
||||
}
|
||||
let mut vg_expect_encrypted: libc::c_int = 1i32;
|
||||
if 0 != join_vg {
|
||||
let mut is_verified_group: libc::c_int = 0i32;
|
||||
dc_get_chat_id_by_grpid(
|
||||
context,
|
||||
grpid,
|
||||
0 as *mut libc::c_int,
|
||||
&mut is_verified_group,
|
||||
);
|
||||
if 0 == is_verified_group {
|
||||
vg_expect_encrypted = 0i32
|
||||
}
|
||||
}
|
||||
if 0 != vg_expect_encrypted {
|
||||
if 0 == encrypted_and_signed(mimeparser, scanned_fingerprint_of_alice) {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
b"Contact confirm message not encrypted.\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
);
|
||||
end_bobs_joining(context, 0i32);
|
||||
current_block = 4378276786830486580;
|
||||
} else {
|
||||
current_block = 5195798230510548452;
|
||||
}
|
||||
} else {
|
||||
current_block = 5195798230510548452;
|
||||
}
|
||||
match current_block {
|
||||
4378276786830486580 => {}
|
||||
_ => {
|
||||
if 0 == mark_peer_as_verified(context, scanned_fingerprint_of_alice)
|
||||
{
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
b"Fingerprint mismatch on joiner-side.\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
);
|
||||
current_block = 4378276786830486580;
|
||||
} else {
|
||||
Contact::scaleup_origin_by_id(
|
||||
context,
|
||||
contact_id,
|
||||
Origin::SecurejoinJoined,
|
||||
);
|
||||
context.call_cb(
|
||||
Event::CONTACTS_CHANGED,
|
||||
0i32 as uintptr_t,
|
||||
0i32 as uintptr_t,
|
||||
);
|
||||
if 0 != join_vg {
|
||||
if !addr_equals_self(
|
||||
context,
|
||||
as_str(lookup_field(
|
||||
mimeparser,
|
||||
"Chat-Group-Member-Added",
|
||||
)),
|
||||
) {
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
"Message belongs to a different handshake (scaled up contact anyway to allow creation of group)."
|
||||
);
|
||||
current_block = 4378276786830486580;
|
||||
} else {
|
||||
current_block = 9180031981464905198;
|
||||
}
|
||||
} else {
|
||||
current_block = 9180031981464905198;
|
||||
}
|
||||
match current_block {
|
||||
4378276786830486580 => {}
|
||||
_ => {
|
||||
secure_connection_established(context, contact_chat_id);
|
||||
context.bob.clone().write().unwrap().expects = 0;
|
||||
if 0 != join_vg {
|
||||
send_handshake_msg(
|
||||
context,
|
||||
contact_chat_id,
|
||||
b"vg-member-added-received\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
0 as *const libc::c_char,
|
||||
0 as *const libc::c_char,
|
||||
0 as *const libc::c_char,
|
||||
);
|
||||
}
|
||||
end_bobs_joining(context, 1i32);
|
||||
current_block = 10256747982273457880;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if strcmp(
|
||||
step,
|
||||
b"vg-member-added-received\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
/* ============================================================
|
||||
==== Alice - the inviter side ====
|
||||
==== Step 8 in "Out-of-band verified groups" protocol ====
|
||||
============================================================ */
|
||||
if let Ok(contact) = Contact::get_by_id(context, contact_id) {
|
||||
if contact.is_verified() == VerifiedStatus::Unverified {
|
||||
warn!(context, 0, "vg-member-added-received invalid.",);
|
||||
current_block = 4378276786830486580;
|
||||
} else {
|
||||
context.call_cb(
|
||||
Event::SECUREJOIN_INVITER_PROGRESS,
|
||||
contact_id as uintptr_t,
|
||||
800i32 as uintptr_t,
|
||||
);
|
||||
context.call_cb(
|
||||
Event::SECUREJOIN_INVITER_PROGRESS,
|
||||
contact_id as uintptr_t,
|
||||
1000i32 as uintptr_t,
|
||||
);
|
||||
current_block = 10256747982273457880;
|
||||
}
|
||||
} else {
|
||||
warn!(context, 0, "vg-member-added-received invalid.",);
|
||||
current_block = 4378276786830486580;
|
||||
}
|
||||
} else {
|
||||
current_block = 10256747982273457880;
|
||||
}
|
||||
match current_block {
|
||||
4378276786830486580 => {}
|
||||
_ => {
|
||||
if 0 != ret & 0x2i32 {
|
||||
ret |= 0x4i32
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(scanned_fingerprint_of_alice as *mut libc::c_void);
|
||||
free(auth as *mut libc::c_void);
|
||||
free(own_fingerprint as *mut libc::c_void);
|
||||
free(grpid as *mut libc::c_void);
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
unsafe fn end_bobs_joining(context: &Context, status: libc::c_int) {
|
||||
context.bob.clone().write().unwrap().status = status;
|
||||
dc_stop_ongoing_process(context);
|
||||
}
|
||||
|
||||
unsafe fn secure_connection_established(context: &Context, contact_chat_id: uint32_t) {
|
||||
let contact_id: uint32_t = chat_id_2_contact_id(context, contact_chat_id);
|
||||
let contact = Contact::get_by_id(context, contact_id);
|
||||
let addr = if let Ok(ref contact) = contact {
|
||||
contact.get_addr()
|
||||
} else {
|
||||
"?"
|
||||
};
|
||||
let msg =
|
||||
CString::new(context.stock_string_repl_str(StockMessage::ContactVerified, addr)).unwrap();
|
||||
dc_add_device_msg(context, contact_chat_id, msg.as_ptr());
|
||||
context.call_cb(
|
||||
Event::CHAT_MODIFIED,
|
||||
contact_chat_id as uintptr_t,
|
||||
0i32 as uintptr_t,
|
||||
);
|
||||
}
|
||||
|
||||
unsafe fn lookup_field(mimeparser: &dc_mimeparser_t, key: &str) -> *const libc::c_char {
|
||||
let mut value: *const libc::c_char = 0 as *const libc::c_char;
|
||||
let field: *mut mailimf_field = dc_mimeparser_lookup_field(mimeparser, key);
|
||||
if field.is_null()
|
||||
|| (*field).fld_type != MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int
|
||||
|| (*field).fld_data.fld_optional_field.is_null()
|
||||
|| {
|
||||
value = (*(*field).fld_data.fld_optional_field).fld_value;
|
||||
value.is_null()
|
||||
}
|
||||
{
|
||||
return 0 as *const libc::c_char;
|
||||
}
|
||||
|
||||
value
|
||||
}
|
||||
|
||||
unsafe fn could_not_establish_secure_connection(
|
||||
context: &Context,
|
||||
contact_chat_id: uint32_t,
|
||||
details: *const libc::c_char,
|
||||
) {
|
||||
let contact_id: uint32_t = chat_id_2_contact_id(context, contact_chat_id);
|
||||
let contact = Contact::get_by_id(context, contact_id);
|
||||
let msg = context.stock_string_repl_str(
|
||||
StockMessage::ContactNotVerified,
|
||||
if let Ok(ref contact) = contact {
|
||||
contact.get_addr()
|
||||
} else {
|
||||
"?"
|
||||
},
|
||||
);
|
||||
let msg_c = CString::new(msg.as_str()).unwrap();
|
||||
dc_add_device_msg(context, contact_chat_id, msg_c.as_ptr());
|
||||
error!(context, 0, "{} ({})", msg, as_str(details));
|
||||
}
|
||||
|
||||
unsafe fn mark_peer_as_verified(
|
||||
context: &Context,
|
||||
fingerprint: *const libc::c_char,
|
||||
) -> libc::c_int {
|
||||
let mut success = 0;
|
||||
|
||||
if let Some(ref mut peerstate) =
|
||||
Peerstate::from_fingerprint(context, &context.sql, as_str(fingerprint))
|
||||
{
|
||||
if peerstate.set_verified(1, as_str(fingerprint), 2) {
|
||||
peerstate.prefer_encrypt = EncryptPreference::Mutual;
|
||||
peerstate.to_save = Some(ToSave::All);
|
||||
peerstate.save_to_db(&context.sql, false);
|
||||
success = 1;
|
||||
}
|
||||
}
|
||||
|
||||
success
|
||||
}
|
||||
|
||||
/* ******************************************************************************
|
||||
* Tools: Misc.
|
||||
******************************************************************************/
|
||||
|
||||
// TODO should return bool
|
||||
unsafe fn encrypted_and_signed(
|
||||
mimeparser: &dc_mimeparser_t,
|
||||
expected_fingerprint: *const libc::c_char,
|
||||
) -> libc::c_int {
|
||||
if 0 == mimeparser.e2ee_helper.encrypted {
|
||||
warn!(mimeparser.context, 0, "Message not encrypted.",);
|
||||
return 0i32;
|
||||
}
|
||||
if mimeparser.e2ee_helper.signatures.len() <= 0 {
|
||||
warn!(mimeparser.context, 0, "Message not signed.",);
|
||||
return 0i32;
|
||||
}
|
||||
if expected_fingerprint.is_null() {
|
||||
warn!(mimeparser.context, 0, "Fingerprint for comparison missing.",);
|
||||
return 0i32;
|
||||
}
|
||||
if !mimeparser
|
||||
.e2ee_helper
|
||||
.signatures
|
||||
.contains(as_str(expected_fingerprint))
|
||||
{
|
||||
warn!(
|
||||
mimeparser.context,
|
||||
0,
|
||||
"Message does not match expected fingerprint {}.",
|
||||
as_str(expected_fingerprint),
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
pub unsafe fn dc_handle_degrade_event(context: &Context, peerstate: &Peerstate) {
|
||||
let mut contact_chat_id = 0;
|
||||
|
||||
// - 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
|
||||
// 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
|
||||
// (and he will know this and can fix this)
|
||||
if Some(DegradeEvent::FingerprintChanged) == peerstate.degrade_event {
|
||||
let contact_id: i32 = context
|
||||
.sql
|
||||
.query_row_col(
|
||||
context,
|
||||
"SELECT id FROM contacts WHERE addr=?;",
|
||||
params![&peerstate.addr],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
if contact_id > 0 {
|
||||
dc_create_or_lookup_nchat_by_contact_id(
|
||||
context,
|
||||
contact_id as u32,
|
||||
2,
|
||||
&mut contact_chat_id,
|
||||
0 as *mut libc::c_int,
|
||||
);
|
||||
let peeraddr: &str = match peerstate.addr {
|
||||
Some(ref addr) => &addr,
|
||||
None => "",
|
||||
};
|
||||
let msg = CString::new(
|
||||
context.stock_string_repl_str(StockMessage::ContactSetupChanged, peeraddr),
|
||||
)
|
||||
.unwrap();
|
||||
dc_add_device_msg(context, contact_chat_id, msg.as_ptr());
|
||||
context.call_cb(
|
||||
Event::CHAT_MODIFIED,
|
||||
contact_chat_id as uintptr_t,
|
||||
0 as uintptr_t,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +1,49 @@
|
||||
use crate::dc_dehtml::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::x::*;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct dc_simplify_t {
|
||||
pub is_forwarded: libc::c_int,
|
||||
pub is_cut_at_begin: libc::c_int,
|
||||
pub is_cut_at_end: libc::c_int,
|
||||
pub struct Simplify {
|
||||
pub is_forwarded: bool,
|
||||
}
|
||||
|
||||
impl dc_simplify_t {
|
||||
/// Return index of footer line in vector of message lines, or vector length if
|
||||
/// no footer is found.
|
||||
///
|
||||
/// Also return whether not-standard (rfc3676, §4.3) footer is found.
|
||||
fn find_message_footer(lines: &[&str]) -> (usize, bool) {
|
||||
for ix in 0..lines.len() {
|
||||
let line = lines[ix];
|
||||
|
||||
// quoted-printable may encode `-- ` to `-- =20` which is converted
|
||||
// back to `-- `
|
||||
match line.as_ref() {
|
||||
"-- " | "-- " => return (ix, false),
|
||||
"--" | "---" | "----" => return (ix, true),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
return (lines.len(), false);
|
||||
}
|
||||
|
||||
impl Simplify {
|
||||
pub fn new() -> Self {
|
||||
dc_simplify_t {
|
||||
is_forwarded: 0,
|
||||
is_cut_at_begin: 0,
|
||||
is_cut_at_end: 0,
|
||||
Simplify {
|
||||
is_forwarded: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Simplify and normalise text: Remove quotes, signatures, unnecessary
|
||||
/// lineends etc.
|
||||
/// The data returned from simplify() must be free()'d when no longer used.
|
||||
pub unsafe fn simplify(
|
||||
&mut self,
|
||||
in_unterminated: *const libc::c_char,
|
||||
in_bytes: libc::c_int,
|
||||
is_html: libc::c_int,
|
||||
is_msgrmsg: libc::c_int,
|
||||
) -> *mut libc::c_char {
|
||||
if in_bytes <= 0 {
|
||||
return "".strdup();
|
||||
}
|
||||
pub fn simplify(&mut self, input: &str, is_html: bool, is_msgrmsg: bool) -> String {
|
||||
let mut out = if is_html {
|
||||
dc_dehtml(input)
|
||||
} else {
|
||||
input.to_string()
|
||||
};
|
||||
|
||||
/* create a copy of the given buffer */
|
||||
let mut out: *mut libc::c_char;
|
||||
let mut temp: *mut libc::c_char;
|
||||
self.is_forwarded = 0i32;
|
||||
self.is_cut_at_begin = 0i32;
|
||||
self.is_cut_at_end = 0i32;
|
||||
out = strndup(
|
||||
in_unterminated as *mut libc::c_char,
|
||||
in_bytes as libc::c_ulong,
|
||||
);
|
||||
if out.is_null() {
|
||||
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||
}
|
||||
if 0 != is_html {
|
||||
temp = dc_dehtml(out);
|
||||
if !temp.is_null() {
|
||||
free(out as *mut libc::c_void);
|
||||
out = temp
|
||||
}
|
||||
}
|
||||
dc_remove_cr_chars(out);
|
||||
temp = self.simplify_plain_text(out, is_msgrmsg);
|
||||
if !temp.is_null() {
|
||||
free(out as *mut libc::c_void);
|
||||
out = temp
|
||||
}
|
||||
dc_remove_cr_chars(out);
|
||||
out.retain(|c| c != '\r');
|
||||
out = self.simplify_plain_text(&out, is_msgrmsg);
|
||||
out.retain(|c| c != '\r');
|
||||
|
||||
out
|
||||
}
|
||||
@@ -68,102 +52,75 @@ impl dc_simplify_t {
|
||||
* Simplify Plain Text
|
||||
*/
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn simplify_plain_text(
|
||||
&mut self,
|
||||
buf_terminated: *const libc::c_char,
|
||||
is_msgrmsg: libc::c_int,
|
||||
) -> *mut libc::c_char {
|
||||
fn simplify_plain_text(&mut self, buf_terminated: &str, is_msgrmsg: bool) -> String {
|
||||
/* This function ...
|
||||
... removes all text after the line `-- ` (footer mark)
|
||||
... removes full quotes at the beginning and at the end of the text -
|
||||
these are all lines starting with the character `>`
|
||||
... remove a non-empty line before the removed quote (contains sth. like "On 2.9.2016, Bjoern wrote:" in different formats and lanugages) */
|
||||
/* split the given buffer into lines */
|
||||
let lines = dc_split_into_lines(buf_terminated);
|
||||
let lines: Vec<_> = buf_terminated.split('\n').collect();
|
||||
let mut l_first: usize = 0;
|
||||
let mut l_last = lines.len();
|
||||
let mut line: *mut libc::c_char;
|
||||
let mut footer_mark: libc::c_int = 0i32;
|
||||
for l in l_first..l_last {
|
||||
line = lines[l];
|
||||
if strcmp(line, b"-- \x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
|| strcmp(line, b"-- \x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
{
|
||||
footer_mark = 1i32
|
||||
}
|
||||
if strcmp(line, b"--\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
|| strcmp(line, b"---\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
|| strcmp(line, b"----\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
{
|
||||
footer_mark = 1i32;
|
||||
self.is_cut_at_end = 1i32
|
||||
}
|
||||
if 0 != footer_mark {
|
||||
l_last = l;
|
||||
/* done */
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut is_cut_at_begin = false;
|
||||
let (mut l_last, mut is_cut_at_end) = find_message_footer(&lines);
|
||||
|
||||
if l_last > l_first + 2 {
|
||||
let line0: *mut libc::c_char = lines[l_first];
|
||||
let line1: *mut libc::c_char = lines[l_first + 1];
|
||||
let line2: *mut libc::c_char = lines[l_first + 2];
|
||||
if strcmp(
|
||||
line0,
|
||||
b"---------- Forwarded message ----------\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
&& strncmp(line1, b"From: \x00" as *const u8 as *const libc::c_char, 6) == 0i32
|
||||
&& *line2.offset(0isize) as libc::c_int == 0i32
|
||||
let line0 = lines[l_first];
|
||||
let line1 = lines[l_first + 1];
|
||||
let line2 = lines[l_first + 2];
|
||||
if line0 == "---------- Forwarded message ----------"
|
||||
&& line1.starts_with("From: ")
|
||||
&& line2.is_empty()
|
||||
{
|
||||
self.is_forwarded = 1i32;
|
||||
self.is_forwarded = true;
|
||||
l_first += 3
|
||||
}
|
||||
}
|
||||
for l in l_first..l_last {
|
||||
line = lines[l];
|
||||
if strncmp(line, b"-----\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|
||||
|| strncmp(line, b"_____\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|
||||
|| strncmp(line, b"=====\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|
||||
|| strncmp(line, b"*****\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|
||||
|| strncmp(line, b"~~~~~\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|
||||
let line = lines[l];
|
||||
if line == "-----"
|
||||
|| line == "_____"
|
||||
|| line == "====="
|
||||
|| line == "*****"
|
||||
|| line == "~~~~~"
|
||||
{
|
||||
l_last = l;
|
||||
self.is_cut_at_end = 1i32;
|
||||
is_cut_at_end = true;
|
||||
/* done */
|
||||
break;
|
||||
}
|
||||
}
|
||||
if 0 == is_msgrmsg {
|
||||
if !is_msgrmsg {
|
||||
let mut l_lastQuotedLine = None;
|
||||
for l in (l_first..l_last).rev() {
|
||||
line = lines[l];
|
||||
let line = lines[l];
|
||||
if is_plain_quote(line) {
|
||||
l_lastQuotedLine = Some(l)
|
||||
} else if !is_empty_line(line) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if l_lastQuotedLine.is_some() {
|
||||
l_last = l_lastQuotedLine.unwrap();
|
||||
self.is_cut_at_end = 1i32;
|
||||
if let Some(last_quoted_line) = l_lastQuotedLine {
|
||||
l_last = last_quoted_line;
|
||||
is_cut_at_end = true;
|
||||
if l_last > 1 {
|
||||
if is_empty_line(lines[l_last - 1]) {
|
||||
l_last -= 1
|
||||
}
|
||||
}
|
||||
if l_last > 1 {
|
||||
line = lines[l_last - 1];
|
||||
let line = lines[l_last - 1];
|
||||
if is_quoted_headline(line) {
|
||||
l_last -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if 0 == is_msgrmsg {
|
||||
if !is_msgrmsg {
|
||||
let mut l_lastQuotedLine_0 = None;
|
||||
let mut hasQuotedHeadline = 0;
|
||||
for l in l_first..l_last {
|
||||
line = lines[l];
|
||||
let line = lines[l];
|
||||
if is_plain_quote(line) {
|
||||
l_lastQuotedLine_0 = Some(l)
|
||||
} else if !is_empty_line(line) {
|
||||
@@ -178,21 +135,21 @@ impl dc_simplify_t {
|
||||
}
|
||||
}
|
||||
}
|
||||
if l_lastQuotedLine_0.is_some() {
|
||||
l_first = l_lastQuotedLine_0.unwrap() + 1;
|
||||
self.is_cut_at_begin = 1i32
|
||||
if let Some(last_quoted_line) = l_lastQuotedLine_0 {
|
||||
l_first = last_quoted_line + 1;
|
||||
is_cut_at_begin = true
|
||||
}
|
||||
}
|
||||
/* re-create buffer from the remaining lines */
|
||||
let mut ret = String::new();
|
||||
if 0 != self.is_cut_at_begin {
|
||||
if is_cut_at_begin {
|
||||
ret += "[...]";
|
||||
}
|
||||
/* we write empty lines only in case and non-empty line follows */
|
||||
let mut pending_linebreaks: libc::c_int = 0i32;
|
||||
let mut content_lines_added: libc::c_int = 0i32;
|
||||
for l in l_first..l_last {
|
||||
line = lines[l];
|
||||
let line = lines[l];
|
||||
if is_empty_line(line) {
|
||||
pending_linebreaks += 1
|
||||
} else {
|
||||
@@ -206,144 +163,115 @@ impl dc_simplify_t {
|
||||
}
|
||||
}
|
||||
// the incoming message might contain invalid UTF8
|
||||
ret += &to_string_lossy(line);
|
||||
ret += line;
|
||||
content_lines_added += 1;
|
||||
pending_linebreaks = 1i32
|
||||
}
|
||||
}
|
||||
if 0 != self.is_cut_at_end && (0 == self.is_cut_at_begin || 0 != content_lines_added) {
|
||||
if is_cut_at_end && (!is_cut_at_begin || 0 != content_lines_added) {
|
||||
ret += " [...]";
|
||||
}
|
||||
dc_free_splitted_lines(lines);
|
||||
|
||||
ret.strdup()
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tools
|
||||
*/
|
||||
unsafe fn is_empty_line(buf: *const libc::c_char) -> bool {
|
||||
/* force unsigned - otherwise the `> ' '` comparison will fail */
|
||||
let mut p1: *const libc::c_uchar = buf as *const libc::c_uchar;
|
||||
while 0 != *p1 {
|
||||
if *p1 as libc::c_int > ' ' as i32 {
|
||||
fn is_empty_line(buf: &str) -> bool {
|
||||
// XXX: can it be simplified to buf.chars().all(|c| c.is_whitespace())?
|
||||
//
|
||||
// Strictly speaking, it is not equivalent (^A is not whitespace, but less than ' '),
|
||||
// but having control sequences in email body?!
|
||||
//
|
||||
// See discussion at: https://github.com/deltachat/deltachat-core-rust/pull/402#discussion_r317062392
|
||||
for c in buf.chars() {
|
||||
if c > ' ' {
|
||||
return false;
|
||||
}
|
||||
p1 = p1.offset(1isize)
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
unsafe fn is_quoted_headline(buf: *const libc::c_char) -> bool {
|
||||
fn is_quoted_headline(buf: &str) -> bool {
|
||||
/* This function may be called for the line _directly_ before a quote.
|
||||
The function checks if the line contains sth. like "On 01.02.2016, xy@z wrote:" in various languages.
|
||||
- Currently, we simply check if the last character is a ':'.
|
||||
- Checking for the existence of an email address may fail (headlines may show the user's name instead of the address) */
|
||||
let buf_len: libc::c_int = strlen(buf) as libc::c_int;
|
||||
if buf_len > 80i32 {
|
||||
return false;
|
||||
}
|
||||
if buf_len > 0i32 && *buf.offset((buf_len - 1i32) as isize) as libc::c_int == ':' as i32 {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
buf.len() <= 80 && buf.ends_with(':')
|
||||
}
|
||||
|
||||
unsafe fn is_plain_quote(buf: *const libc::c_char) -> bool {
|
||||
if *buf.offset(0isize) as libc::c_int == '>' as i32 {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
fn is_plain_quote(buf: &str) -> bool {
|
||||
buf.starts_with(">")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::ffi::CStr;
|
||||
use proptest::prelude::*;
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
// proptest does not support [[:graphical:][:space:]] regex.
|
||||
fn test_simplify_plain_text_fuzzy(input in "[!-~\t \n]+") {
|
||||
let output = Simplify::new().simplify_plain_text(&input, true);
|
||||
assert!(output.split('\n').all(|s| s != "-- "));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_trim() {
|
||||
unsafe {
|
||||
let mut simplify = dc_simplify_t::new();
|
||||
let html: *const libc::c_char =
|
||||
b"\r\r\nline1<br>\r\n\r\n\r\rline2\n\r\x00" as *const u8 as *const libc::c_char;
|
||||
let plain: *mut libc::c_char =
|
||||
simplify.simplify(html, strlen(html) as libc::c_int, 1, 0);
|
||||
let mut simplify = Simplify::new();
|
||||
let html = "\r\r\nline1<br>\r\n\r\n\r\rline2\n\r";
|
||||
let plain = simplify.simplify(html, true, false);
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr(plain as *const libc::c_char)
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"line1\nline2",
|
||||
);
|
||||
|
||||
free(plain as *mut libc::c_void);
|
||||
}
|
||||
assert_eq!(plain, "line1\nline2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_parse_href() {
|
||||
unsafe {
|
||||
let mut simplify = dc_simplify_t::new();
|
||||
let html: *const libc::c_char =
|
||||
b"<a href=url>text</a\x00" as *const u8 as *const libc::c_char;
|
||||
let plain: *mut libc::c_char =
|
||||
simplify.simplify(html, strlen(html) as libc::c_int, 1, 0);
|
||||
let mut simplify = Simplify::new();
|
||||
let html = "<a href=url>text</a";
|
||||
let plain = simplify.simplify(html, true, false);
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr(plain as *const libc::c_char)
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"[text](url)",
|
||||
);
|
||||
|
||||
free(plain as *mut libc::c_void);
|
||||
}
|
||||
assert_eq!(plain, "[text](url)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_bold_text() {
|
||||
unsafe {
|
||||
let mut simplify = dc_simplify_t::new();
|
||||
let html: *const libc::c_char =
|
||||
b"<!DOCTYPE name [<!DOCTYPE ...>]><!-- comment -->text <b><?php echo ... ?>bold</b><![CDATA[<>]]>\x00"
|
||||
as *const u8 as *const libc::c_char;
|
||||
let plain: *mut libc::c_char =
|
||||
simplify.simplify(html, strlen(html) as libc::c_int, 1, 0);
|
||||
let mut simplify = Simplify::new();
|
||||
let html = "<!DOCTYPE name [<!DOCTYPE ...>]><!-- comment -->text <b><?php echo ... ?>bold</b><![CDATA[<>]]>";
|
||||
let plain = simplify.simplify(html, true, false);
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr(plain as *const libc::c_char)
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"text *bold*<>",
|
||||
);
|
||||
|
||||
free(plain as *mut libc::c_void);
|
||||
}
|
||||
assert_eq!(plain, "text *bold*<>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_html_encoded() {
|
||||
unsafe {
|
||||
let mut simplify = dc_simplify_t::new();
|
||||
let html: *const libc::c_char =
|
||||
b"<>"'& äÄöÖüÜß fooÆçÇ ♦&noent;‎‏‌‍\x00"
|
||||
as *const u8 as *const libc::c_char;
|
||||
let plain: *mut libc::c_char =
|
||||
simplify.simplify(html, strlen(html) as libc::c_int, 1, 0);
|
||||
let mut simplify = Simplify::new();
|
||||
let html =
|
||||
"<>"'& äÄöÖüÜß fooÆçÇ ♦‎‏‌&noent;‍";
|
||||
|
||||
assert_eq!(
|
||||
strcmp(plain,
|
||||
b"<>\"\'& \xc3\xa4\xc3\x84\xc3\xb6\xc3\x96\xc3\xbc\xc3\x9c\xc3\x9f foo\xc3\x86\xc3\xa7\xc3\x87 \xe2\x99\xa6&noent;\x00"
|
||||
as *const u8 as *const libc::c_char),
|
||||
0,
|
||||
);
|
||||
let plain = simplify.simplify(html, true, false);
|
||||
|
||||
free(plain as *mut libc::c_void);
|
||||
}
|
||||
assert_eq!(
|
||||
plain,
|
||||
"<>\"\'& äÄöÖüÜß fooÆçÇ \u{2666}\u{200e}\u{200f}\u{200c}&noent;\u{200d}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_utilities() {
|
||||
assert!(is_empty_line(" \t"));
|
||||
assert!(is_empty_line(""));
|
||||
assert!(is_empty_line(" \r"));
|
||||
assert!(!is_empty_line(" x"));
|
||||
assert!(is_plain_quote("> hello world"));
|
||||
assert!(is_plain_quote(">>"));
|
||||
assert!(!is_plain_quote("Life is pain"));
|
||||
assert!(!is_plain_quote(""));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,130 +1,39 @@
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
use charset::Charset;
|
||||
use mmime::mailmime_decode::*;
|
||||
use mmime::mmapstring::*;
|
||||
use mmime::other::*;
|
||||
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
|
||||
|
||||
use crate::dc_tools::*;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
#[inline]
|
||||
pub fn isalnum(c: libc::c_int) -> libc::c_int {
|
||||
if c < std::u8::MAX as libc::c_int {
|
||||
(c as u8 as char).is_ascii_alphanumeric() as libc::c_int
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Encode non-ascii-strings as `=?UTF-8?Q?Bj=c3=b6rn_Petersen?=`.
|
||||
* Belongs to RFC 2047: https://tools.ietf.org/html/rfc2047
|
||||
*
|
||||
* We do not fold at position 72; this would result in empty words as `=?utf-8?Q??=` which are correct,
|
||||
* but cannot be displayed by some mail programs (eg. Android Stock Mail).
|
||||
* however, this is not needed, as long as _one_ word is not longer than 72 characters.
|
||||
* _if_ it is, the display may get weird. This affects the subject only.
|
||||
* the best solution wor all this would be if libetpan encodes the line as only libetpan knowns when a header line is full.
|
||||
*
|
||||
* @param to_encode Null-terminated UTF-8-string to encode.
|
||||
* @return Returns the encoded string which must be free()'d when no longed needed.
|
||||
* On errors, NULL is returned.
|
||||
*/
|
||||
pub unsafe fn dc_encode_header_words(to_encode_r: impl AsRef<str>) -> *mut libc::c_char {
|
||||
let to_encode =
|
||||
CString::new(to_encode_r.as_ref().as_bytes()).expect("invalid cstring to_encode");
|
||||
|
||||
pub unsafe fn dc_urlencode(to_encode: *const libc::c_char) -> *mut libc::c_char {
|
||||
let mut pstr: *const libc::c_char = to_encode;
|
||||
if to_encode.is_null() {
|
||||
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||
}
|
||||
let buf: *mut libc::c_char =
|
||||
malloc(strlen(to_encode).wrapping_mul(3).wrapping_add(1)) as *mut libc::c_char;
|
||||
let mut pbuf: *mut libc::c_char = buf;
|
||||
assert!(!buf.is_null());
|
||||
|
||||
while 0 != *pstr {
|
||||
if 0 != isalnum(*pstr as libc::c_int)
|
||||
|| *pstr as libc::c_int == '-' as i32
|
||||
|| *pstr as libc::c_int == '_' as i32
|
||||
|| *pstr as libc::c_int == '.' as i32
|
||||
|| *pstr as libc::c_int == '~' as i32
|
||||
{
|
||||
let fresh0 = pbuf;
|
||||
pbuf = pbuf.offset(1);
|
||||
*fresh0 = *pstr
|
||||
} else if *pstr as libc::c_int == ' ' as i32 {
|
||||
let fresh1 = pbuf;
|
||||
pbuf = pbuf.offset(1);
|
||||
*fresh1 = '+' as i32 as libc::c_char
|
||||
} else {
|
||||
let fresh2 = pbuf;
|
||||
pbuf = pbuf.offset(1);
|
||||
*fresh2 = '%' as i32 as libc::c_char;
|
||||
let fresh3 = pbuf;
|
||||
pbuf = pbuf.offset(1);
|
||||
*fresh3 = int_2_uppercase_hex((*pstr as libc::c_int >> 4i32) as libc::c_char);
|
||||
let fresh4 = pbuf;
|
||||
pbuf = pbuf.offset(1);
|
||||
*fresh4 = int_2_uppercase_hex((*pstr as libc::c_int & 15i32) as libc::c_char)
|
||||
}
|
||||
pstr = pstr.offset(1isize)
|
||||
}
|
||||
*pbuf = '\u{0}' as i32 as libc::c_char;
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
/* ******************************************************************************
|
||||
* URL encoding and decoding, RFC 3986
|
||||
******************************************************************************/
|
||||
unsafe fn int_2_uppercase_hex(code: libc::c_char) -> libc::c_char {
|
||||
static mut HEX: [libc::c_char; 17] = [
|
||||
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 0,
|
||||
];
|
||||
|
||||
HEX[(code as libc::c_int & 15i32) as usize]
|
||||
}
|
||||
|
||||
pub unsafe fn dc_urldecode(to_decode: *const libc::c_char) -> *mut libc::c_char {
|
||||
let mut pstr: *const libc::c_char = to_decode;
|
||||
if to_decode.is_null() {
|
||||
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||
}
|
||||
let buf: *mut libc::c_char = malloc(strlen(to_decode).wrapping_add(1)) as *mut libc::c_char;
|
||||
let mut pbuf: *mut libc::c_char = buf;
|
||||
assert!(!buf.is_null());
|
||||
|
||||
while 0 != *pstr {
|
||||
if *pstr as libc::c_int == '%' as i32 {
|
||||
if 0 != *pstr.offset(1isize) as libc::c_int && 0 != *pstr.offset(2isize) as libc::c_int
|
||||
{
|
||||
let fresh5 = pbuf;
|
||||
pbuf = pbuf.offset(1);
|
||||
*fresh5 = ((hex_2_int(*pstr.offset(1isize)) as libc::c_int) << 4i32
|
||||
| hex_2_int(*pstr.offset(2isize)) as libc::c_int)
|
||||
as libc::c_char;
|
||||
pstr = pstr.offset(2isize)
|
||||
}
|
||||
} else if *pstr as libc::c_int == '+' as i32 {
|
||||
let fresh6 = pbuf;
|
||||
pbuf = pbuf.offset(1);
|
||||
*fresh6 = ' ' as i32 as libc::c_char
|
||||
} else {
|
||||
let fresh7 = pbuf;
|
||||
pbuf = pbuf.offset(1);
|
||||
*fresh7 = *pstr
|
||||
}
|
||||
pstr = pstr.offset(1isize)
|
||||
}
|
||||
*pbuf = '\u{0}' as i32 as libc::c_char;
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
fn hex_2_int(ch: libc::c_char) -> libc::c_char {
|
||||
let ch = ch as u8 as char;
|
||||
if !ch.is_ascii_hexdigit() {
|
||||
return (ch.to_ascii_lowercase() as i32 - 'a' as i32 + 10) as libc::c_char;
|
||||
}
|
||||
|
||||
match ch.to_digit(16) {
|
||||
Some(res) => res as libc::c_char,
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut libc::c_char {
|
||||
let mut ok_to_continue = true;
|
||||
let mut ret_str: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut cur: *const libc::c_char = to_encode;
|
||||
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 to_encode.is_null() || mmapstr.is_null() {
|
||||
if mmapstr.is_null() {
|
||||
ok_to_continue = false;
|
||||
}
|
||||
loop {
|
||||
@@ -159,7 +68,7 @@ pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut lib
|
||||
b"utf-8\x00" as *const u8 as *const libc::c_char,
|
||||
mmapstr,
|
||||
begin,
|
||||
end.wrapping_offset_from(begin) as size_t,
|
||||
end.wrapping_offset_from(begin) as libc::size_t,
|
||||
) {
|
||||
ok_to_continue = false;
|
||||
continue;
|
||||
@@ -175,7 +84,7 @@ pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut lib
|
||||
if mmap_string_append_len(
|
||||
mmapstr,
|
||||
end,
|
||||
cur.wrapping_offset_from(end) as size_t,
|
||||
cur.wrapping_offset_from(end) as libc::size_t,
|
||||
)
|
||||
.is_null()
|
||||
{
|
||||
@@ -186,7 +95,7 @@ pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut lib
|
||||
} else if mmap_string_append_len(
|
||||
mmapstr,
|
||||
begin,
|
||||
cur.wrapping_offset_from(begin) as size_t,
|
||||
cur.wrapping_offset_from(begin) as libc::size_t,
|
||||
)
|
||||
.is_null()
|
||||
{
|
||||
@@ -215,10 +124,10 @@ unsafe fn quote_word(
|
||||
display_charset: *const libc::c_char,
|
||||
mmapstr: *mut MMAPString,
|
||||
word: *const libc::c_char,
|
||||
size: size_t,
|
||||
size: libc::size_t,
|
||||
) -> bool {
|
||||
let mut cur: *const libc::c_char;
|
||||
let mut i: size_t = 0i32 as size_t;
|
||||
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() {
|
||||
@@ -281,7 +190,7 @@ unsafe fn get_word(
|
||||
{
|
||||
cur = cur.offset(1isize)
|
||||
}
|
||||
*pto_be_quoted = to_be_quoted(begin, cur.wrapping_offset_from(begin) as size_t);
|
||||
*pto_be_quoted = to_be_quoted(begin, cur.wrapping_offset_from(begin) as libc::size_t);
|
||||
*pend = cur;
|
||||
}
|
||||
|
||||
@@ -290,9 +199,9 @@ unsafe fn get_word(
|
||||
******************************************************************************/
|
||||
|
||||
/* see comment below */
|
||||
unsafe fn to_be_quoted(word: *const libc::c_char, size: size_t) -> bool {
|
||||
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: size_t = 0i32 as size_t;
|
||||
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
|
||||
@@ -312,10 +221,10 @@ unsafe fn to_be_quoted(word: *const libc::c_char, size: size_t) -> bool {
|
||||
|
||||
pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_char {
|
||||
if in_0.is_null() {
|
||||
return 0 as *mut libc::c_char;
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let mut out: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut cur_token: size_t = 0i32 as size_t;
|
||||
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,
|
||||
@@ -331,371 +240,88 @@ pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_
|
||||
out
|
||||
}
|
||||
|
||||
pub unsafe fn dc_encode_modified_utf7(
|
||||
mut to_encode: *const libc::c_char,
|
||||
change_spaces: libc::c_int,
|
||||
) -> *mut libc::c_char {
|
||||
let mut utf8pos: libc::c_uint;
|
||||
let mut utf8total: libc::c_uint;
|
||||
let mut c: libc::c_uint;
|
||||
let mut utf7mode: libc::c_uint;
|
||||
let mut bitstogo: libc::c_uint;
|
||||
let mut utf16flag: libc::c_uint;
|
||||
let mut ucs4: libc::c_ulong = 0;
|
||||
let mut bitbuf: libc::c_ulong = 0;
|
||||
let mut dst: *mut libc::c_char;
|
||||
let res: *mut libc::c_char;
|
||||
if to_encode.is_null() {
|
||||
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||
}
|
||||
res = malloc(2usize.wrapping_mul(strlen(to_encode)).wrapping_add(1)) as *mut libc::c_char;
|
||||
dst = res;
|
||||
assert!(!dst.is_null());
|
||||
|
||||
utf7mode = 0i32 as libc::c_uint;
|
||||
utf8total = 0i32 as libc::c_uint;
|
||||
bitstogo = 0i32 as libc::c_uint;
|
||||
utf8pos = 0i32 as libc::c_uint;
|
||||
loop {
|
||||
c = *to_encode as libc::c_uchar as libc::c_uint;
|
||||
if !(c != '\u{0}' as i32 as libc::c_uint) {
|
||||
break;
|
||||
}
|
||||
to_encode = to_encode.offset(1isize);
|
||||
// normal character?
|
||||
if c >= ' ' as i32 as libc::c_uint
|
||||
&& c <= '~' as i32 as libc::c_uint
|
||||
&& (c != '_' as i32 as libc::c_uint || 0 == change_spaces)
|
||||
{
|
||||
if 0 != utf7mode {
|
||||
if 0 != bitstogo {
|
||||
let fresh8 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh8 = BASE64CHARS
|
||||
[(bitbuf << (6i32 as libc::c_uint).wrapping_sub(bitstogo) & 0x3f) as usize]
|
||||
}
|
||||
let fresh9 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh9 = '-' as i32 as libc::c_char;
|
||||
utf7mode = 0i32 as libc::c_uint;
|
||||
utf8pos = 0i32 as libc::c_uint;
|
||||
bitstogo = 0i32 as libc::c_uint;
|
||||
utf8total = 0i32 as libc::c_uint
|
||||
}
|
||||
if 0 != change_spaces && c == ' ' as i32 as libc::c_uint {
|
||||
let fresh10 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh10 = '_' as i32 as libc::c_char
|
||||
} else {
|
||||
let fresh11 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh11 = c as libc::c_char
|
||||
}
|
||||
if c == '&' as i32 as libc::c_uint {
|
||||
let fresh12 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh12 = '-' as i32 as libc::c_char
|
||||
}
|
||||
pub fn dc_decode_header_words_safe(input: &str) -> String {
|
||||
static FROM_ENCODING: &[u8] = b"iso-8859-1\x00";
|
||||
static TO_ENCODING: &[u8] = b"utf-8\x00";
|
||||
let mut out = ptr::null_mut();
|
||||
let mut cur_token = 0;
|
||||
let input_c = CString::yolo(input);
|
||||
unsafe {
|
||||
let r = mailmime_encoded_phrase_parse(
|
||||
FROM_ENCODING.as_ptr().cast(),
|
||||
input_c.as_ptr(),
|
||||
input.len(),
|
||||
&mut cur_token,
|
||||
TO_ENCODING.as_ptr().cast(),
|
||||
&mut out,
|
||||
);
|
||||
if r as u32 != MAILIMF_NO_ERROR || out.is_null() {
|
||||
input.to_string()
|
||||
} else {
|
||||
if 0 == utf7mode {
|
||||
let fresh13 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh13 = '&' as i32 as libc::c_char;
|
||||
utf7mode = 1i32 as libc::c_uint
|
||||
}
|
||||
// encode ascii characters as themselves
|
||||
if c < 0x80i32 as libc::c_uint {
|
||||
ucs4 = c as libc::c_ulong
|
||||
} else if 0 != utf8total {
|
||||
ucs4 = ucs4 << 6i32 | c as libc::c_ulong & 0x3f;
|
||||
utf8pos = utf8pos.wrapping_add(1);
|
||||
if utf8pos < utf8total {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
utf8pos = 1i32 as libc::c_uint;
|
||||
if c < 0xe0i32 as libc::c_uint {
|
||||
utf8total = 2i32 as libc::c_uint;
|
||||
ucs4 = (c & 0x1fi32 as libc::c_uint) as libc::c_ulong
|
||||
} else if c < 0xf0i32 as libc::c_uint {
|
||||
utf8total = 3i32 as libc::c_uint;
|
||||
ucs4 = (c & 0xfi32 as libc::c_uint) as libc::c_ulong
|
||||
} else {
|
||||
utf8total = 4i32 as libc::c_uint;
|
||||
ucs4 = (c & 0x3i32 as libc::c_uint) as libc::c_ulong
|
||||
}
|
||||
continue;
|
||||
}
|
||||
utf8total = 0i32 as libc::c_uint;
|
||||
loop {
|
||||
if ucs4 >= 0x10000 {
|
||||
ucs4 = ucs4.wrapping_sub(0x10000);
|
||||
bitbuf = bitbuf << 16 | (ucs4 >> 10).wrapping_add(0xd800);
|
||||
ucs4 = (ucs4 & 0x3ff).wrapping_add(0xdc00);
|
||||
utf16flag = 1i32 as libc::c_uint
|
||||
} else {
|
||||
bitbuf = bitbuf << 16 | ucs4;
|
||||
utf16flag = 0i32 as libc::c_uint
|
||||
}
|
||||
bitstogo = bitstogo.wrapping_add(16i32 as libc::c_uint);
|
||||
while bitstogo >= 6i32 as libc::c_uint {
|
||||
bitstogo = bitstogo.wrapping_sub(6i32 as libc::c_uint);
|
||||
let fresh14 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh14 = BASE64CHARS[(if 0 != bitstogo {
|
||||
bitbuf >> bitstogo
|
||||
} else {
|
||||
bitbuf
|
||||
} & 0x3f) as usize]
|
||||
}
|
||||
if !(0 != utf16flag) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let res = to_string(out);
|
||||
free(out.cast());
|
||||
res
|
||||
}
|
||||
}
|
||||
if 0 != utf7mode {
|
||||
if 0 != bitstogo {
|
||||
let fresh15 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh15 = BASE64CHARS
|
||||
[(bitbuf << (6i32 as libc::c_uint).wrapping_sub(bitstogo) & 0x3f) as usize]
|
||||
}
|
||||
let fresh16 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh16 = '-' as i32 as libc::c_char
|
||||
}
|
||||
*dst = '\u{0}' as i32 as libc::c_char;
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
/* ******************************************************************************
|
||||
* Encode/decode modified UTF-7 as needed for IMAP, see RFC 2192
|
||||
******************************************************************************/
|
||||
pub fn dc_needs_ext_header(to_check: impl AsRef<str>) -> bool {
|
||||
let to_check = to_check.as_ref();
|
||||
|
||||
// UTF7 modified base64 alphabet
|
||||
static mut BASE64CHARS: [libc::c_char; 65] = [
|
||||
65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
|
||||
89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114,
|
||||
115, 116, 117, 118, 119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 44, 0,
|
||||
];
|
||||
|
||||
pub unsafe fn dc_decode_modified_utf7(
|
||||
to_decode: *const libc::c_char,
|
||||
change_spaces: libc::c_int,
|
||||
) -> *mut libc::c_char {
|
||||
let mut c: libc::c_uint;
|
||||
let mut i: libc::c_uint;
|
||||
let mut bitcount: libc::c_uint;
|
||||
let mut ucs4: libc::c_ulong;
|
||||
let mut utf16: libc::c_ulong;
|
||||
let mut bitbuf: libc::c_ulong;
|
||||
let mut base64: [libc::c_uchar; 256] = [0; 256];
|
||||
let mut src: *const libc::c_char;
|
||||
let mut dst: *mut libc::c_char;
|
||||
let res: *mut libc::c_char;
|
||||
if to_decode.is_null() {
|
||||
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||
if to_check.is_empty() {
|
||||
return false;
|
||||
}
|
||||
res = malloc(4usize.wrapping_mul(strlen(to_decode)).wrapping_add(1)) as *mut libc::c_char;
|
||||
dst = res;
|
||||
src = to_decode;
|
||||
assert!(!dst.is_null());
|
||||
|
||||
libc::memset(
|
||||
base64.as_mut_ptr() as *mut libc::c_void,
|
||||
64,
|
||||
::std::mem::size_of::<[libc::c_uchar; 256]>(),
|
||||
);
|
||||
i = 0i32 as libc::c_uint;
|
||||
while (i as libc::c_ulong) < ::std::mem::size_of::<[libc::c_char; 65]>() as libc::c_ulong {
|
||||
base64[BASE64CHARS[i as usize] as libc::c_uint as usize] = i as libc::c_uchar;
|
||||
i = i.wrapping_add(1)
|
||||
}
|
||||
while *src as libc::c_int != '\u{0}' as i32 {
|
||||
let fresh17 = src;
|
||||
src = src.offset(1);
|
||||
c = *fresh17 as libc::c_uint;
|
||||
if c != '&' as i32 as libc::c_uint || *src as libc::c_int == '-' as i32 {
|
||||
if 0 != change_spaces && c == '_' as i32 as libc::c_uint {
|
||||
let fresh18 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh18 = ' ' as i32 as libc::c_char
|
||||
} else {
|
||||
let fresh19 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh19 = c as libc::c_char
|
||||
}
|
||||
if c == '&' as i32 as libc::c_uint {
|
||||
src = src.offset(1isize)
|
||||
}
|
||||
} else {
|
||||
bitbuf = 0;
|
||||
bitcount = 0i32 as libc::c_uint;
|
||||
ucs4 = 0;
|
||||
loop {
|
||||
c = base64[*src as libc::c_uchar as usize] as libc::c_uint;
|
||||
if !(c != 64i32 as libc::c_uint) {
|
||||
break;
|
||||
}
|
||||
src = src.offset(1isize);
|
||||
bitbuf = bitbuf << 6i32 | c as libc::c_ulong;
|
||||
bitcount = bitcount.wrapping_add(6i32 as libc::c_uint);
|
||||
// enough bits for a UTF-16 character?
|
||||
if !(bitcount >= 16i32 as libc::c_uint) {
|
||||
continue;
|
||||
}
|
||||
bitcount = bitcount.wrapping_sub(16i32 as libc::c_uint);
|
||||
utf16 = if 0 != bitcount {
|
||||
bitbuf >> bitcount
|
||||
} else {
|
||||
bitbuf
|
||||
} & 0xffff;
|
||||
|
||||
// convert UTF16 to UCS4
|
||||
if utf16 >= 0xd800 && utf16 <= 0xdbff {
|
||||
ucs4 = utf16.wrapping_sub(0xd800) << 10i32
|
||||
} else {
|
||||
if utf16 >= 0xdc00 && utf16 <= 0xdfff {
|
||||
ucs4 = ucs4.wrapping_add(utf16.wrapping_sub(0xdc00).wrapping_add(0x10000))
|
||||
} else {
|
||||
ucs4 = utf16
|
||||
}
|
||||
if ucs4 <= 0x7f {
|
||||
*dst.offset(0isize) = ucs4 as libc::c_char;
|
||||
dst = dst.offset(1isize)
|
||||
} else if ucs4 <= 0x7ff {
|
||||
*dst.offset(0isize) = (0xc0 | ucs4 >> 6i32) as libc::c_char;
|
||||
*dst.offset(1isize) = (0x80 | ucs4 & 0x3f) as libc::c_char;
|
||||
dst = dst.offset(2isize)
|
||||
} else if ucs4 <= 0xffff {
|
||||
*dst.offset(0isize) = (0xe0 | ucs4 >> 12i32) as libc::c_char;
|
||||
*dst.offset(1isize) = (0x80 | ucs4 >> 6i32 & 0x3f) as libc::c_char;
|
||||
*dst.offset(2isize) = (0x80 | ucs4 & 0x3f) as libc::c_char;
|
||||
dst = dst.offset(3isize)
|
||||
} else {
|
||||
*dst.offset(0isize) = (0xf0 | ucs4 >> 18i32) as libc::c_char;
|
||||
*dst.offset(1isize) = (0x80 | ucs4 >> 12i32 & 0x3f) as libc::c_char;
|
||||
*dst.offset(2isize) = (0x80 | ucs4 >> 6i32 & 0x3f) as libc::c_char;
|
||||
*dst.offset(3isize) = (0x80 | ucs4 & 0x3f) as libc::c_char;
|
||||
dst = dst.offset(4isize)
|
||||
}
|
||||
}
|
||||
}
|
||||
if *src as libc::c_int == '-' as i32 {
|
||||
src = src.offset(1isize)
|
||||
}
|
||||
}
|
||||
}
|
||||
*dst = '\u{0}' as i32 as libc::c_char;
|
||||
|
||||
res
|
||||
to_check.chars().any(|c| {
|
||||
!(c.is_ascii_alphanumeric()
|
||||
|| c == '-'
|
||||
|| c == '_'
|
||||
|| c == '_'
|
||||
|| c == '.'
|
||||
|| c == '~'
|
||||
|| c == '%')
|
||||
})
|
||||
}
|
||||
|
||||
pub unsafe fn dc_needs_ext_header(mut to_check: *const libc::c_char) -> bool {
|
||||
if !to_check.is_null() {
|
||||
while 0 != *to_check {
|
||||
if 0 == isalnum(*to_check as libc::c_int)
|
||||
&& *to_check as libc::c_int != '-' as i32
|
||||
&& *to_check as libc::c_int != '_' as i32
|
||||
&& *to_check as libc::c_int != '.' as i32
|
||||
&& *to_check as libc::c_int != '~' as i32
|
||||
{
|
||||
return true;
|
||||
}
|
||||
to_check = to_check.offset(1isize)
|
||||
}
|
||||
}
|
||||
const EXT_ASCII_ST: &AsciiSet = &CONTROLS
|
||||
.add(b' ')
|
||||
.add(b'-')
|
||||
.add(b'_')
|
||||
.add(b'.')
|
||||
.add(b'~')
|
||||
.add(b'%');
|
||||
|
||||
false
|
||||
/// Encode an UTF-8 string to the extended header format.
|
||||
pub fn dc_encode_ext_header(to_encode: impl AsRef<str>) -> String {
|
||||
let encoded = utf8_percent_encode(to_encode.as_ref(), &EXT_ASCII_ST);
|
||||
format!("utf-8''{}", encoded)
|
||||
}
|
||||
|
||||
pub unsafe fn dc_encode_ext_header(to_encode: *const libc::c_char) -> *mut libc::c_char {
|
||||
let mut pstr: *const libc::c_char = to_encode;
|
||||
if to_encode.is_null() {
|
||||
return dc_strdup(b"utf-8\'\'\x00" as *const u8 as *const libc::c_char);
|
||||
}
|
||||
let buf: *mut libc::c_char = malloc(
|
||||
strlen(b"utf-8\'\'\x00" as *const u8 as *const libc::c_char)
|
||||
.wrapping_add(strlen(to_encode).wrapping_mul(3))
|
||||
.wrapping_add(1),
|
||||
) as *mut libc::c_char;
|
||||
assert!(!buf.is_null());
|
||||
|
||||
let mut pbuf: *mut libc::c_char = buf;
|
||||
strcpy(pbuf, b"utf-8\'\'\x00" as *const u8 as *const libc::c_char);
|
||||
pbuf = pbuf.offset(strlen(pbuf) as isize);
|
||||
while 0 != *pstr {
|
||||
if 0 != isalnum(*pstr as libc::c_int)
|
||||
|| *pstr as libc::c_int == '-' as i32
|
||||
|| *pstr as libc::c_int == '_' as i32
|
||||
|| *pstr as libc::c_int == '.' as i32
|
||||
|| *pstr as libc::c_int == '~' as i32
|
||||
{
|
||||
let fresh20 = pbuf;
|
||||
pbuf = pbuf.offset(1);
|
||||
*fresh20 = *pstr
|
||||
} else {
|
||||
let fresh21 = pbuf;
|
||||
pbuf = pbuf.offset(1);
|
||||
*fresh21 = '%' as i32 as libc::c_char;
|
||||
let fresh22 = pbuf;
|
||||
pbuf = pbuf.offset(1);
|
||||
*fresh22 = int_2_uppercase_hex((*pstr as libc::c_int >> 4i32) as libc::c_char);
|
||||
let fresh23 = pbuf;
|
||||
pbuf = pbuf.offset(1);
|
||||
*fresh23 = int_2_uppercase_hex((*pstr as libc::c_int & 15i32) as libc::c_char)
|
||||
}
|
||||
pstr = pstr.offset(1isize)
|
||||
}
|
||||
*pbuf = '\u{0}' as i32 as libc::c_char;
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
pub unsafe fn dc_decode_ext_header(to_decode: *const libc::c_char) -> *mut libc::c_char {
|
||||
let mut decoded: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut charset: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut p2: *const libc::c_char;
|
||||
if !to_decode.is_null() {
|
||||
// get char set
|
||||
p2 = strchr(to_decode, '\'' as i32);
|
||||
if !(p2.is_null() || p2 == to_decode) {
|
||||
/*no empty charset allowed*/
|
||||
charset =
|
||||
dc_null_terminate(to_decode, p2.wrapping_offset_from(to_decode) as libc::c_int);
|
||||
p2 = p2.offset(1isize);
|
||||
/// Decode an extended-header-format strings to UTF-8.
|
||||
pub fn dc_decode_ext_header(to_decode: &[u8]) -> Cow<str> {
|
||||
if let Some(index) = bytes!(b'\'').find(to_decode) {
|
||||
let (charset, rest) = to_decode.split_at(index);
|
||||
if !charset.is_empty() {
|
||||
// skip language
|
||||
p2 = strchr(p2, '\'' as i32);
|
||||
if !p2.is_null() {
|
||||
p2 = p2.offset(1isize);
|
||||
decoded = dc_urldecode(p2);
|
||||
if !charset.is_null()
|
||||
&& strcmp(charset, b"utf-8\x00" as *const u8 as *const libc::c_char) != 0i32
|
||||
&& strcmp(charset, b"UTF-8\x00" as *const u8 as *const libc::c_char) != 0i32
|
||||
{
|
||||
if let Some(encoding) =
|
||||
Charset::for_label(CStr::from_ptr(charset).to_str().unwrap().as_bytes())
|
||||
{
|
||||
let data =
|
||||
std::slice::from_raw_parts(decoded as *const u8, strlen(decoded));
|
||||
if let Some(index2) = bytes!(b'\'').find(&rest[1..]) {
|
||||
let decoded = percent_decode(&rest[index2 + 2..]);
|
||||
|
||||
let (res, _, _) = encoding.decode(data);
|
||||
free(decoded as *mut _);
|
||||
let r = std::ffi::CString::new(res.as_bytes()).unwrap();
|
||||
decoded = dc_strdup(r.as_ptr());
|
||||
if charset != b"utf-8" && charset != b"UTF-8" {
|
||||
if let Some(encoding) = Charset::for_label(charset) {
|
||||
let bytes = decoded.collect::<Vec<u8>>();
|
||||
let (res, _, _) = encoding.decode(&bytes);
|
||||
return Cow::Owned(res.into_owned());
|
||||
} else {
|
||||
return decoded.decode_utf8_lossy();
|
||||
}
|
||||
} else {
|
||||
return decoded.decode_utf8_lossy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
free(charset as *mut libc::c_void);
|
||||
return if !decoded.is_null() {
|
||||
decoded
|
||||
} else {
|
||||
dc_strdup(to_decode)
|
||||
};
|
||||
|
||||
String::from_utf8_lossy(to_decode)
|
||||
}
|
||||
|
||||
unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) {
|
||||
@@ -712,13 +338,6 @@ mod tests {
|
||||
use super::*;
|
||||
use std::ffi::CStr;
|
||||
|
||||
#[test]
|
||||
fn test_isalnum() {
|
||||
assert_eq!(isalnum(0), 0);
|
||||
assert_eq!(isalnum('5' as libc::c_int), 1);
|
||||
assert_eq!(isalnum('Q' as libc::c_int), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_decode_header_words() {
|
||||
unsafe {
|
||||
@@ -739,12 +358,13 @@ mod tests {
|
||||
assert_eq!(CStr::from_ptr(buf1).to_str().unwrap(), "just ascii test");
|
||||
free(buf1 as *mut libc::c_void);
|
||||
|
||||
buf1 = dc_encode_header_words(b"abcdef\x00" as *const u8 as *const libc::c_char);
|
||||
buf1 = dc_encode_header_words("abcdef");
|
||||
assert_eq!(CStr::from_ptr(buf1).to_str().unwrap(), "abcdef");
|
||||
free(buf1 as *mut libc::c_void);
|
||||
|
||||
buf1 = dc_encode_header_words(
|
||||
b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\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!(
|
||||
strncmp(buf1, b"=?utf-8\x00" as *const u8 as *const libc::c_char, 7),
|
||||
@@ -779,99 +399,43 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_dc_encode_ext_header() {
|
||||
unsafe {
|
||||
let mut buf1 = dc_encode_ext_header(
|
||||
b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
assert_eq!(
|
||||
CStr::from_ptr(buf1).to_str().unwrap(),
|
||||
"utf-8\'\'Bj%C3%B6rn%20Petersen"
|
||||
);
|
||||
let buf2 = dc_decode_ext_header(buf1);
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
buf2,
|
||||
b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char,
|
||||
),
|
||||
0
|
||||
);
|
||||
free(buf1 as *mut libc::c_void);
|
||||
free(buf2 as *mut libc::c_void);
|
||||
let buf1 = dc_encode_ext_header("Björn Petersen");
|
||||
assert_eq!(&buf1, "utf-8\'\'Bj%C3%B6rn%20Petersen");
|
||||
let buf2 = dc_decode_ext_header(buf1.as_bytes());
|
||||
assert_eq!(&buf2, "Björn Petersen",);
|
||||
|
||||
buf1 = dc_decode_ext_header(
|
||||
b"iso-8859-1\'en\'%A3%20rates\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
buf1,
|
||||
b"\xc2\xa3 rates\x00" as *const u8 as *const libc::c_char,
|
||||
),
|
||||
0
|
||||
);
|
||||
free(buf1 as *mut libc::c_void);
|
||||
let buf1 = dc_decode_ext_header(b"iso-8859-1\'en\'%A3%20rates");
|
||||
assert_eq!(buf1, "£ rates",);
|
||||
|
||||
buf1 = dc_decode_ext_header(b"wrong\'format\x00" as *const u8 as *const libc::c_char);
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
buf1,
|
||||
b"wrong\'format\x00" as *const u8 as *const libc::c_char,
|
||||
),
|
||||
0
|
||||
);
|
||||
free(buf1 as *mut libc::c_void);
|
||||
let buf1 = dc_decode_ext_header(b"wrong\'format");
|
||||
assert_eq!(buf1, "wrong\'format",);
|
||||
|
||||
buf1 = dc_decode_ext_header(b"\'\'\x00" as *const u8 as *const libc::c_char);
|
||||
assert_eq!(
|
||||
strcmp(buf1, b"\'\'\x00" as *const u8 as *const libc::c_char),
|
||||
0
|
||||
);
|
||||
free(buf1 as *mut libc::c_void);
|
||||
let buf1 = dc_decode_ext_header(b"\'\'");
|
||||
assert_eq!(buf1, "\'\'");
|
||||
|
||||
buf1 = dc_decode_ext_header(b"x\'\'\x00" as *const u8 as *const libc::c_char);
|
||||
assert_eq!(strcmp(buf1, b"\x00" as *const u8 as *const libc::c_char), 0);
|
||||
free(buf1 as *mut libc::c_void);
|
||||
let buf1 = dc_decode_ext_header(b"x\'\'");
|
||||
assert_eq!(buf1, "");
|
||||
|
||||
buf1 = dc_decode_ext_header(b"\'\x00" as *const u8 as *const libc::c_char);
|
||||
assert_eq!(
|
||||
strcmp(buf1, b"\'\x00" as *const u8 as *const libc::c_char),
|
||||
0
|
||||
);
|
||||
free(buf1 as *mut libc::c_void);
|
||||
let buf1 = dc_decode_ext_header(b"\'");
|
||||
assert_eq!(buf1, "\'");
|
||||
|
||||
buf1 = dc_decode_ext_header(b"\x00" as *const u8 as *const libc::c_char);
|
||||
assert_eq!(strcmp(buf1, b"\x00" as *const u8 as *const libc::c_char), 0);
|
||||
free(buf1 as *mut libc::c_void);
|
||||
}
|
||||
let buf1 = dc_decode_ext_header(b"");
|
||||
assert_eq!(buf1, "");
|
||||
|
||||
// regressions
|
||||
assert_eq!(
|
||||
dc_decode_ext_header(dc_encode_ext_header("%0A").as_bytes()),
|
||||
"%0A"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_needs_ext_header() {
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
dc_needs_ext_header(b"Bj\xc3\xb6rn\x00" as *const u8 as *const libc::c_char),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
dc_needs_ext_header(b"Bjoern\x00" as *const u8 as *const libc::c_char),
|
||||
false
|
||||
);
|
||||
assert_eq!(
|
||||
dc_needs_ext_header(b"\x00" as *const u8 as *const libc::c_char),
|
||||
false
|
||||
);
|
||||
assert_eq!(
|
||||
dc_needs_ext_header(b" \x00" as *const u8 as *const libc::c_char),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
dc_needs_ext_header(b"a b\x00" as *const u8 as *const libc::c_char),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
dc_needs_ext_header(0 as *const u8 as *const libc::c_char),
|
||||
false
|
||||
);
|
||||
}
|
||||
assert_eq!(dc_needs_ext_header("Björn"), true);
|
||||
assert_eq!(dc_needs_ext_header("Bjoern"), false);
|
||||
assert_eq!(dc_needs_ext_header(""), false);
|
||||
assert_eq!(dc_needs_ext_header(" "), true);
|
||||
assert_eq!(dc_needs_ext_header("a b"), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -886,68 +450,20 @@ mod tests {
|
||||
assert_eq!(to_string(hex.as_ptr() as *const _), "=3A");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_urlencode_urldecode() {
|
||||
unsafe {
|
||||
let buf1 =
|
||||
dc_urlencode(b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char);
|
||||
use proptest::prelude::*;
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr(buf1 as *const libc::c_char)
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"Bj%C3%B6rn+Petersen"
|
||||
);
|
||||
|
||||
let buf2 = dc_urldecode(buf1);
|
||||
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
buf2,
|
||||
b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char
|
||||
),
|
||||
0
|
||||
);
|
||||
|
||||
free(buf1 as *mut libc::c_void);
|
||||
free(buf2 as *mut libc::c_void);
|
||||
proptest! {
|
||||
#[test]
|
||||
fn test_ext_header_roundtrip(buf: String) {
|
||||
let encoded = dc_encode_ext_header(&buf);
|
||||
let decoded = dc_decode_ext_header(encoded.as_bytes());
|
||||
assert_eq!(buf, decoded);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_encode_decode_modified_utf7() {
|
||||
unsafe {
|
||||
let buf1 = dc_encode_modified_utf7(
|
||||
b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char,
|
||||
1,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr(buf1 as *const libc::c_char)
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"Bj&APY-rn_Petersen"
|
||||
);
|
||||
|
||||
let buf2 = dc_decode_modified_utf7(buf1, 1);
|
||||
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
buf2,
|
||||
b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char
|
||||
),
|
||||
0
|
||||
);
|
||||
|
||||
free(buf1 as *mut libc::c_void);
|
||||
free(buf2 as *mut libc::c_void);
|
||||
#[test]
|
||||
fn test_ext_header_decode_anything(buf: Vec<u8>) {
|
||||
// make sure this never panics
|
||||
let _decoded = dc_decode_ext_header(&buf);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_hex_to_int() {
|
||||
assert_eq!(hex_2_int(b'A' as libc::c_char), 10);
|
||||
assert_eq!(hex_2_int(b'a' as libc::c_char), 10);
|
||||
assert_eq!(hex_2_int(b'4' as libc::c_char), 4);
|
||||
assert_eq!(hex_2_int(b'K' as libc::c_char), 20);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::sql;
|
||||
|
||||
// Token namespaces
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type dc_tokennamespc_t = usize;
|
||||
pub const DC_TOKEN_AUTH: dc_tokennamespc_t = 110;
|
||||
pub const DC_TOKEN_INVITENUMBER: dc_tokennamespc_t = 100;
|
||||
|
||||
// Functions to read/write token from/to the database. A token is any string associated with a key.
|
||||
|
||||
pub fn dc_token_save(
|
||||
context: &Context,
|
||||
namespc: dc_tokennamespc_t,
|
||||
foreign_id: u32,
|
||||
token: *const libc::c_char,
|
||||
) -> bool {
|
||||
if token.is_null() {
|
||||
return false;
|
||||
}
|
||||
// foreign_id may be 0
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);",
|
||||
params![namespc as i32, foreign_id as i32, as_str(token), time()],
|
||||
)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub fn dc_token_lookup(
|
||||
context: &Context,
|
||||
namespc: dc_tokennamespc_t,
|
||||
foreign_id: u32,
|
||||
) -> *mut libc::c_char {
|
||||
context
|
||||
.sql
|
||||
.query_row_col::<_, String>(
|
||||
context,
|
||||
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;",
|
||||
params![namespc as i32, foreign_id as i32],
|
||||
0,
|
||||
)
|
||||
.map(|s| unsafe { s.strdup() })
|
||||
.unwrap_or_else(|| std::ptr::null_mut())
|
||||
}
|
||||
|
||||
pub fn dc_token_exists(
|
||||
context: &Context,
|
||||
namespc: dc_tokennamespc_t,
|
||||
token: *const libc::c_char,
|
||||
) -> bool {
|
||||
if token.is_null() {
|
||||
return false;
|
||||
}
|
||||
|
||||
context
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT id FROM tokens WHERE namespc=? AND token=?;",
|
||||
params![namespc as i32, as_str(token)],
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
1381
src/dc_tools.rs
1381
src/dc_tools.rs
File diff suppressed because it is too large
Load Diff
1163
src/e2ee.rs
Normal file
1163
src/e2ee.rs
Normal file
File diff suppressed because it is too large
Load Diff
32
src/error.rs
32
src/error.rs
@@ -18,6 +18,14 @@ pub enum Error {
|
||||
Io(std::io::Error),
|
||||
#[fail(display = "{:?}", _0)]
|
||||
Message(String),
|
||||
#[fail(display = "{:?}", _0)]
|
||||
Image(image_meta::ImageError),
|
||||
#[fail(display = "{:?}", _0)]
|
||||
Utf8(std::str::Utf8Error),
|
||||
#[fail(display = "{:?}", _0)]
|
||||
CStringError(crate::dc_tools::CStringError),
|
||||
#[fail(display = "PGP: {:?}", _0)]
|
||||
Pgp(pgp::errors::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -46,6 +54,30 @@ impl From<std::io::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::str::Utf8Error> for Error {
|
||||
fn from(err: std::str::Utf8Error) -> Error {
|
||||
Error::Utf8(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<image_meta::ImageError> for Error {
|
||||
fn from(err: image_meta::ImageError) -> Error {
|
||||
Error::Image(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::dc_tools::CStringError> for Error {
|
||||
fn from(err: crate::dc_tools::CStringError) -> Error {
|
||||
Error::CStringError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<pgp::errors::Error> for Error {
|
||||
fn from(err: pgp::errors::Error) -> Error {
|
||||
Error::Pgp(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! bail {
|
||||
($e:expr) => {
|
||||
|
||||
229
src/events.rs
Normal file
229
src/events.rs
Normal file
@@ -0,0 +1,229 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use strum::EnumProperty;
|
||||
|
||||
use crate::stock::StockMessage;
|
||||
|
||||
impl Event {
|
||||
/// Returns the corresponding Event id.
|
||||
pub fn as_id(&self) -> i32 {
|
||||
self.get_str("id")
|
||||
.expect("missing id")
|
||||
.parse()
|
||||
.expect("invalid id")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, EnumProperty)]
|
||||
pub enum Event {
|
||||
/// The library-user may write an informational string to the log.
|
||||
/// Passed to the callback given to dc_context_new().
|
||||
/// This event should not be reported to the end-user using a popup or something like that.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "100"))]
|
||||
Info(String),
|
||||
|
||||
/// Emitted when SMTP connection is established and login was successful.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "101"))]
|
||||
SmtpConnected(String),
|
||||
|
||||
/// Emitted when IMAP connection is established and login was successful.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "102"))]
|
||||
ImapConnected(String),
|
||||
|
||||
/// Emitted when a message was successfully sent to the SMTP server.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "103"))]
|
||||
SmtpMessageSent(String),
|
||||
|
||||
/// The library-user should write a warning string to the log.
|
||||
/// Passed to the callback given to dc_context_new().
|
||||
///
|
||||
/// This event should not be reported to the end-user using a popup or something like that.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "300"))]
|
||||
Warning(String),
|
||||
|
||||
/// The library-user should report an error to the end-user.
|
||||
/// Passed to the callback given to dc_context_new().
|
||||
///
|
||||
/// As most things are asynchronous, things may go wrong at any time and the user
|
||||
/// should not be disturbed by a dialog or so. Instead, use a bubble or so.
|
||||
///
|
||||
/// However, for ongoing processes (eg. configure())
|
||||
/// or for functions that are expected to fail (eg. dc_continue_key_transfer())
|
||||
/// it might be better to delay showing these events until the function has really
|
||||
/// failed (returned false). It should be sufficient to report only the _last_ error
|
||||
/// in a messasge box then.
|
||||
///
|
||||
/// @return
|
||||
#[strum(props(id = "400"))]
|
||||
Error(String),
|
||||
|
||||
/// An action cannot be performed because there is no network available.
|
||||
///
|
||||
/// The library will typically try over after a some time
|
||||
/// and when dc_maybe_network() is called.
|
||||
///
|
||||
/// Network errors should be reported to users in a non-disturbing way,
|
||||
/// however, as network errors may come in a sequence,
|
||||
/// it is not useful to raise each an every error to the user.
|
||||
/// For this purpose, data1 is set to 1 if the error is probably worth reporting.
|
||||
///
|
||||
/// Moreover, if the UI detects that the device is offline,
|
||||
/// it is probably more useful to report this to the user
|
||||
/// instead of the string from data2.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "401"))]
|
||||
ErrorNetwork(String),
|
||||
|
||||
/// An action cannot be performed because the user is not in the group.
|
||||
/// Reported eg. after a call to
|
||||
/// dc_set_chat_name(), dc_set_chat_profile_image(),
|
||||
/// dc_add_contact_to_chat(), dc_remove_contact_from_chat(),
|
||||
/// dc_send_text_msg() or another sending function.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "410"))]
|
||||
ErrorSelfNotInGroup(String),
|
||||
|
||||
/// Messages or chats changed. One or more messages or chats changed for various
|
||||
/// reasons in the database:
|
||||
/// - Messages sent, received or removed
|
||||
/// - Chats created, deleted or archived
|
||||
/// - A draft has been set
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2000"))]
|
||||
MsgsChanged { chat_id: u32, msg_id: u32 },
|
||||
|
||||
/// There is a fresh message. Typically, the user will show an notification
|
||||
/// when receiving this message.
|
||||
///
|
||||
/// There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2005"))]
|
||||
IncomingMsg { chat_id: u32, msg_id: u32 },
|
||||
|
||||
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
|
||||
/// DC_STATE_OUT_DELIVERED, see dc_msg_get_state().
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2010"))]
|
||||
MsgDelivered { chat_id: u32, msg_id: u32 },
|
||||
|
||||
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
|
||||
/// DC_STATE_OUT_FAILED, see dc_msg_get_state().
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2012"))]
|
||||
MsgFailed { chat_id: u32, msg_id: u32 },
|
||||
|
||||
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
|
||||
/// DC_STATE_OUT_MDN_RCVD, see dc_msg_get_state().
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2015"))]
|
||||
MsgRead { chat_id: u32, msg_id: u32 },
|
||||
|
||||
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
|
||||
/// Or the verify state of a chat has changed.
|
||||
/// See dc_set_chat_name(), dc_set_chat_profile_image(), dc_add_contact_to_chat()
|
||||
/// and dc_remove_contact_from_chat().
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2020"))]
|
||||
ChatModified(u32),
|
||||
|
||||
/// Contact(s) created, renamed, blocked or deleted.
|
||||
///
|
||||
/// @param data1 (int) If set, this is the contact_id of an added contact that should be selected.
|
||||
/// @return 0
|
||||
#[strum(props(id = "2030"))]
|
||||
ContactsChanged(Option<u32>),
|
||||
|
||||
/// Location of one or more contact has changed.
|
||||
///
|
||||
/// @param data1 (u32) contact_id of the contact for which the location has changed.
|
||||
/// If the locations of several contacts have been changed,
|
||||
/// eg. after calling dc_delete_all_locations(), this parameter is set to `None`.
|
||||
/// @return 0
|
||||
#[strum(props(id = "2035"))]
|
||||
LocationChanged(Option<u32>),
|
||||
|
||||
/// Inform about the configuration progress started by configure().
|
||||
///
|
||||
/// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
|
||||
/// @return 0
|
||||
#[strum(props(id = "2041"))]
|
||||
ConfigureProgress(usize),
|
||||
|
||||
/// Inform about the import/export progress started by dc_imex().
|
||||
///
|
||||
/// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
|
||||
/// @param data2 0
|
||||
/// @return 0
|
||||
#[strum(props(id = "2051"))]
|
||||
ImexProgress(usize),
|
||||
|
||||
/// A file has been exported. A file has been written by dc_imex().
|
||||
/// This event may be sent multiple times by a single call to dc_imex().
|
||||
///
|
||||
/// A typical purpose for a handler of this event may be to make the file public to some system
|
||||
/// services.
|
||||
///
|
||||
/// @param data2 0
|
||||
/// @return 0
|
||||
#[strum(props(id = "2052"))]
|
||||
ImexFileWritten(PathBuf),
|
||||
|
||||
/// Progress information of a secure-join handshake from the view of the inviter
|
||||
/// (Alice, the person who shows the QR code).
|
||||
///
|
||||
/// These events are typically sent after a joiner has scanned the QR code
|
||||
/// generated by dc_get_securejoin_qr().
|
||||
///
|
||||
/// @param data1 (int) ID of the contact that wants to join.
|
||||
/// @param data2 (int) Progress as:
|
||||
/// 300=vg-/vc-request received, typically shown as "bob@addr joins".
|
||||
/// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
|
||||
/// 800=vg-member-added-received received, shown as "bob@addr securely joined GROUP", only sent for the verified-group-protocol.
|
||||
/// 1000=Protocol finished for this contact.
|
||||
/// @return 0
|
||||
#[strum(props(id = "2060"))]
|
||||
SecurejoinInviterProgress { contact_id: u32, progress: usize },
|
||||
|
||||
/// Progress information of a secure-join handshake from the view of the joiner
|
||||
/// (Bob, the person who scans the QR code).
|
||||
/// The events are typically sent while dc_join_securejoin(), which
|
||||
/// may take some time, is executed.
|
||||
/// @param data1 (int) ID of the inviting contact.
|
||||
/// @param data2 (int) Progress as:
|
||||
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
|
||||
/// (Bob has verified alice and waits until Alice does the same for him)
|
||||
/// @return 0
|
||||
#[strum(props(id = "2061"))]
|
||||
SecurejoinJoinerProgress { contact_id: u32, progress: usize },
|
||||
|
||||
// the following events are functions that should be provided by the frontends
|
||||
/// Requeste a localized string from the frontend.
|
||||
/// @param data1 (int) ID of the string to request, one of the DC_STR_/// constants.
|
||||
/// @param data2 (int) The count. If the requested string contains a placeholder for a numeric value,
|
||||
/// the ui may use this value to return different strings on different plural forms.
|
||||
/// @return (const char*) Null-terminated UTF-8 string.
|
||||
/// The string will be free()'d by the core,
|
||||
/// so it must be allocated using malloc() or a compatible function.
|
||||
/// Return 0 if the ui cannot provide the requested string
|
||||
/// the core will use a default string in english language then.
|
||||
#[strum(props(id = "2091"))]
|
||||
GetString { id: StockMessage, count: usize },
|
||||
}
|
||||
381
src/imap.rs
381
src/imap.rs
@@ -1,42 +1,48 @@
|
||||
use std::ffi::CString;
|
||||
use std::net;
|
||||
use std::sync::{Arc, Condvar, Mutex, RwLock};
|
||||
use std::ptr;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Condvar, Mutex, RwLock,
|
||||
};
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_loginparam::*;
|
||||
use crate::dc_receive_imf::dc_receive_imf;
|
||||
use crate::dc_tools::CStringExt;
|
||||
use crate::dc_tools::*;
|
||||
use crate::events::Event;
|
||||
use crate::job::{job_add, Action};
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::message::{dc_rfc724_mid_exists, dc_update_msg_move_state, dc_update_server_uid};
|
||||
use crate::oauth2::dc_get_oauth2_access_token;
|
||||
use crate::types::*;
|
||||
use crate::param::Params;
|
||||
|
||||
pub const DC_IMAP_SEEN: usize = 0x0001;
|
||||
pub const DC_REGENERATE: usize = 0x01;
|
||||
const DC_IMAP_SEEN: usize = 0x0001;
|
||||
|
||||
pub const DC_SUCCESS: usize = 3;
|
||||
pub const DC_ALREADY_DONE: usize = 2;
|
||||
pub const DC_RETRY_LATER: usize = 1;
|
||||
pub const DC_FAILED: usize = 0;
|
||||
const DC_SUCCESS: usize = 3;
|
||||
const DC_ALREADY_DONE: usize = 2;
|
||||
const DC_RETRY_LATER: usize = 1;
|
||||
const DC_FAILED: usize = 0;
|
||||
|
||||
const PREFETCH_FLAGS: &'static str = "(UID ENVELOPE)";
|
||||
const BODY_FLAGS: &'static str = "(FLAGS BODY.PEEK[])";
|
||||
const FETCH_FLAGS: &'static str = "(FLAGS)";
|
||||
const PREFETCH_FLAGS: &str = "(UID ENVELOPE)";
|
||||
const BODY_FLAGS: &str = "(FLAGS BODY.PEEK[])";
|
||||
const FETCH_FLAGS: &str = "(FLAGS)";
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct Imap {
|
||||
config: Arc<RwLock<ImapConfig>>,
|
||||
watch: Arc<(Mutex<bool>, Condvar)>,
|
||||
|
||||
get_config: dc_get_config_t,
|
||||
set_config: dc_set_config_t,
|
||||
precheck_imf: dc_precheck_imf_t,
|
||||
receive_imf: dc_receive_imf_t,
|
||||
|
||||
session: Arc<Mutex<Option<Session>>>,
|
||||
stream: Arc<RwLock<Option<net::TcpStream>>>,
|
||||
connected: Arc<Mutex<bool>>,
|
||||
|
||||
should_reconnect: AtomicBool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct OAuth2 {
|
||||
user: String,
|
||||
access_token: String,
|
||||
@@ -55,13 +61,14 @@ impl imap::Authenticator for OAuth2 {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FolderMeaning {
|
||||
enum FolderMeaning {
|
||||
Unknown,
|
||||
SentObjects,
|
||||
Other,
|
||||
}
|
||||
|
||||
pub enum Client {
|
||||
#[derive(Debug)]
|
||||
enum Client {
|
||||
Secure(
|
||||
imap::Client<native_tls::TlsStream<net::TcpStream>>,
|
||||
net::TcpStream,
|
||||
@@ -69,12 +76,14 @@ pub enum Client {
|
||||
Insecure(imap::Client<net::TcpStream>, net::TcpStream),
|
||||
}
|
||||
|
||||
pub enum Session {
|
||||
#[derive(Debug)]
|
||||
enum Session {
|
||||
Secure(imap::Session<native_tls::TlsStream<net::TcpStream>>),
|
||||
Insecure(imap::Session<net::TcpStream>),
|
||||
}
|
||||
|
||||
pub enum IdleHandle<'a> {
|
||||
#[derive(Debug)]
|
||||
enum IdleHandle<'a> {
|
||||
Secure(imap::extensions::idle::Handle<'a, native_tls::TlsStream<net::TcpStream>>),
|
||||
Insecure(imap::extensions::idle::Handle<'a, net::TcpStream>),
|
||||
}
|
||||
@@ -199,8 +208,8 @@ impl Session {
|
||||
|
||||
pub fn create<S: AsRef<str>>(&mut self, mailbox_name: S) -> imap::error::Result<()> {
|
||||
match self {
|
||||
Session::Secure(i) => i.subscribe(mailbox_name),
|
||||
Session::Insecure(i) => i.subscribe(mailbox_name),
|
||||
Session::Secure(i) => i.create(mailbox_name),
|
||||
Session::Insecure(i) => i.create(mailbox_name),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,8 +269,8 @@ impl Session {
|
||||
|
||||
pub fn idle(&mut self) -> imap::error::Result<IdleHandle> {
|
||||
match self {
|
||||
Session::Secure(i) => i.idle().map(|h| IdleHandle::Secure(h)),
|
||||
Session::Insecure(i) => i.idle().map(|h| IdleHandle::Insecure(h)),
|
||||
Session::Secure(i) => i.idle().map(IdleHandle::Secure),
|
||||
Session::Insecure(i) => i.idle().map(IdleHandle::Insecure),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,7 +312,8 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ImapConfig {
|
||||
#[derive(Debug)]
|
||||
struct ImapConfig {
|
||||
pub addr: String,
|
||||
pub imap_server: String,
|
||||
pub imap_port: u16,
|
||||
@@ -313,7 +323,6 @@ pub struct ImapConfig {
|
||||
pub selected_folder: Option<String>,
|
||||
pub selected_mailbox: Option<imap::types::Mailbox>,
|
||||
pub selected_folder_needs_expunge: bool,
|
||||
pub should_reconnect: bool,
|
||||
pub can_idle: bool,
|
||||
pub has_xlist: bool,
|
||||
pub imap_delimiter: char,
|
||||
@@ -332,7 +341,6 @@ impl Default for ImapConfig {
|
||||
selected_folder: None,
|
||||
selected_mailbox: None,
|
||||
selected_folder_needs_expunge: false,
|
||||
should_reconnect: false,
|
||||
can_idle: false,
|
||||
has_xlist: false,
|
||||
imap_delimiter: '.',
|
||||
@@ -344,22 +352,14 @@ impl Default for ImapConfig {
|
||||
}
|
||||
|
||||
impl Imap {
|
||||
pub fn new(
|
||||
get_config: dc_get_config_t,
|
||||
set_config: dc_set_config_t,
|
||||
precheck_imf: dc_precheck_imf_t,
|
||||
receive_imf: dc_receive_imf_t,
|
||||
) -> Self {
|
||||
pub fn new() -> Self {
|
||||
Imap {
|
||||
session: Arc::new(Mutex::new(None)),
|
||||
stream: Arc::new(RwLock::new(None)),
|
||||
config: Arc::new(RwLock::new(ImapConfig::default())),
|
||||
watch: Arc::new((Mutex::new(false), Condvar::new())),
|
||||
get_config,
|
||||
set_config,
|
||||
precheck_imf,
|
||||
receive_imf,
|
||||
connected: Arc::new(Mutex::new(false)),
|
||||
should_reconnect: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,7 +368,7 @@ impl Imap {
|
||||
}
|
||||
|
||||
pub fn should_reconnect(&self) -> bool {
|
||||
self.config.read().unwrap().should_reconnect
|
||||
self.should_reconnect.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn setup_handle_if_needed(&self, context: &Context) -> bool {
|
||||
@@ -381,7 +381,7 @@ impl Imap {
|
||||
}
|
||||
|
||||
if self.is_connected() && self.stream.read().unwrap().is_some() {
|
||||
self.config.write().unwrap().should_reconnect = false;
|
||||
self.should_reconnect.store(false, Ordering::Relaxed);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -417,9 +417,7 @@ impl Imap {
|
||||
if (server_flags & DC_LP_AUTH_OAUTH2) != 0 {
|
||||
let addr: &str = config.addr.as_ref();
|
||||
|
||||
if let Some(token) =
|
||||
dc_get_oauth2_access_token(context, addr, imap_pw, DC_REGENERATE as usize)
|
||||
{
|
||||
if let Some(token) = dc_get_oauth2_access_token(context, addr, imap_pw, true) {
|
||||
let auth = OAuth2 {
|
||||
user: imap_user.into(),
|
||||
access_token: token,
|
||||
@@ -437,21 +435,19 @@ impl Imap {
|
||||
let imap_server: &str = config.imap_server.as_ref();
|
||||
let imap_port = config.imap_port;
|
||||
|
||||
log_event!(
|
||||
emit_event!(
|
||||
context,
|
||||
Event::ERROR_NETWORK,
|
||||
0,
|
||||
"Could not connect to IMAP-server {}:{}. ({})",
|
||||
imap_server,
|
||||
imap_port,
|
||||
err
|
||||
Event::ErrorNetwork(format!(
|
||||
"Could not connect to IMAP-server {}:{}. ({})",
|
||||
imap_server, imap_port, err
|
||||
))
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
self.config.write().unwrap().should_reconnect = false;
|
||||
self.should_reconnect.store(false, Ordering::Relaxed);
|
||||
|
||||
match login_res {
|
||||
Ok((session, stream)) => {
|
||||
@@ -460,7 +456,10 @@ impl Imap {
|
||||
true
|
||||
}
|
||||
Err((err, _)) => {
|
||||
log_event!(context, Event::ERROR_NETWORK, 0, "Cannot login ({})", err);
|
||||
emit_event!(
|
||||
context,
|
||||
Event::ErrorNetwork(format!("Cannot login ({})", err))
|
||||
);
|
||||
self.unsetup_handle(context);
|
||||
|
||||
false
|
||||
@@ -469,39 +468,30 @@ impl Imap {
|
||||
}
|
||||
|
||||
fn unsetup_handle(&self, context: &Context) {
|
||||
info!(context, 0, "IMAP unsetup_handle starts");
|
||||
info!(context, "IMAP unsetup_handle starts");
|
||||
|
||||
info!(
|
||||
context,
|
||||
0, "IMAP unsetup_handle step 1 (closing down stream)."
|
||||
);
|
||||
info!(context, "IMAP unsetup_handle step 1 (closing down stream).");
|
||||
let stream = self.stream.write().unwrap().take();
|
||||
if stream.is_some() {
|
||||
match stream.unwrap().shutdown(net::Shutdown::Both) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
eprintln!("failed to shutdown connection: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
info!(
|
||||
context,
|
||||
0, "IMAP unsetup_handle step 2 (acquiring session.lock)"
|
||||
);
|
||||
let session = self.session.lock().unwrap().take();
|
||||
if session.is_some() {
|
||||
match session.unwrap().close() {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
eprintln!("failed to close connection: {:?}", err);
|
||||
}
|
||||
if let Some(stream) = stream {
|
||||
if let Err(err) = stream.shutdown(net::Shutdown::Both) {
|
||||
eprintln!("failed to shutdown connection: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
info!(context, 0, "IMAP unsetup_handle step 3 (clearing config).");
|
||||
info!(
|
||||
context,
|
||||
"IMAP unsetup_handle step 2 (acquiring session.lock)"
|
||||
);
|
||||
if let Some(mut session) = self.session.lock().unwrap().take() {
|
||||
if let Err(err) = session.close() {
|
||||
eprintln!("failed to close connection: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
info!(context, "IMAP unsetup_handle step 3 (clearing config).");
|
||||
self.config.write().unwrap().selected_folder = None;
|
||||
self.config.write().unwrap().selected_mailbox = None;
|
||||
info!(context, 0, "IMAP unsetup_handle step 4 (disconnected).",);
|
||||
info!(context, "IMAP unsetup_handle step 4 (disconnected).",);
|
||||
}
|
||||
|
||||
fn free_connect_params(&self) {
|
||||
@@ -519,7 +509,7 @@ impl Imap {
|
||||
cfg.watch_folder = None;
|
||||
}
|
||||
|
||||
pub fn connect(&self, context: &Context, lp: &dc_loginparam_t) -> bool {
|
||||
pub fn connect(&self, context: &Context, lp: &LoginParam) -> bool {
|
||||
if lp.mail_server.is_empty() || lp.mail_user.is_empty() || lp.mail_pw.is_empty() {
|
||||
return false;
|
||||
}
|
||||
@@ -539,7 +529,7 @@ impl Imap {
|
||||
let mut config = self.config.write().unwrap();
|
||||
config.addr = addr.to_string();
|
||||
config.imap_server = imap_server.to_string();
|
||||
config.imap_port = imap_port.into();
|
||||
config.imap_port = imap_port;
|
||||
config.imap_user = imap_user.to_string();
|
||||
config.imap_pw = imap_pw.to_string();
|
||||
config.server_flags = server_flags;
|
||||
@@ -550,48 +540,44 @@ impl Imap {
|
||||
return false;
|
||||
}
|
||||
|
||||
let teardown: bool;
|
||||
|
||||
match &mut *self.session.lock().unwrap() {
|
||||
Some(ref mut session) => {
|
||||
if let Ok(caps) = session.capabilities() {
|
||||
let (teardown, can_idle, has_xlist) = match &mut *self.session.lock().unwrap() {
|
||||
Some(ref mut session) => match session.capabilities() {
|
||||
Ok(caps) => {
|
||||
if !context.sql.is_open() {
|
||||
warn!(context, 0, "IMAP-LOGIN as {} ok but ABORTING", lp.mail_user,);
|
||||
teardown = true;
|
||||
warn!(context, "IMAP-LOGIN as {} ok but ABORTING", lp.mail_user,);
|
||||
(true, false, false)
|
||||
} else {
|
||||
let can_idle = caps.has("IDLE");
|
||||
let has_xlist = caps.has("XLIST");
|
||||
let caps_list = caps.iter().fold(String::new(), |mut s, c| {
|
||||
s += " ";
|
||||
s += c;
|
||||
s
|
||||
});
|
||||
log_event!(
|
||||
let can_idle = caps.has_str("IDLE");
|
||||
let has_xlist = caps.has_str("XLIST");
|
||||
let caps_list = caps
|
||||
.iter()
|
||||
.fold(String::new(), |s, c| s + &format!(" {:?}", c));
|
||||
emit_event!(
|
||||
context,
|
||||
Event::IMAP_CONNECTED,
|
||||
0,
|
||||
"IMAP-LOGIN as {}, capabilities: {}",
|
||||
lp.mail_user,
|
||||
caps_list,
|
||||
Event::ImapConnected(format!(
|
||||
"IMAP-LOGIN as {}, capabilities: {}",
|
||||
lp.mail_user, caps_list,
|
||||
))
|
||||
);
|
||||
self.config.write().unwrap().can_idle = can_idle;
|
||||
self.config.write().unwrap().has_xlist = has_xlist;
|
||||
*self.connected.lock().unwrap() = true;
|
||||
teardown = false;
|
||||
(false, can_idle, has_xlist)
|
||||
}
|
||||
} else {
|
||||
teardown = true;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
teardown = true;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
info!(context, "CAPABILITY command error: {}", err);
|
||||
(true, false, false)
|
||||
}
|
||||
},
|
||||
None => (true, false, false),
|
||||
};
|
||||
|
||||
if teardown {
|
||||
self.unsetup_handle(context);
|
||||
self.free_connect_params();
|
||||
false
|
||||
} else {
|
||||
self.config.write().unwrap().can_idle = can_idle;
|
||||
self.config.write().unwrap().has_xlist = has_xlist;
|
||||
*self.connected.lock().unwrap() = true;
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -653,7 +639,7 @@ impl Imap {
|
||||
// deselect existing folder, if needed (it's also done implicitly by SELECT, however, without EXPUNGE then)
|
||||
if self.config.read().unwrap().selected_folder_needs_expunge {
|
||||
if let Some(ref folder) = self.config.read().unwrap().selected_folder {
|
||||
info!(context, 0, "Expunge messages in \"{}\".", folder);
|
||||
info!(context, "Expunge messages in \"{}\".", folder);
|
||||
|
||||
// A CLOSE-SELECT is considerably faster than an EXPUNGE-SELECT, see
|
||||
// https://tools.ietf.org/html/rfc3501#section-6.4.2
|
||||
@@ -683,15 +669,13 @@ impl Imap {
|
||||
Err(err) => {
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
"Cannot select folder: {}; {:?}.",
|
||||
folder.as_ref(),
|
||||
err
|
||||
);
|
||||
|
||||
let mut config = self.config.write().unwrap();
|
||||
config.selected_folder = None;
|
||||
config.should_reconnect = true;
|
||||
self.config.write().unwrap().selected_folder = None;
|
||||
self.should_reconnect.store(true, Ordering::Relaxed);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -705,7 +689,7 @@ impl Imap {
|
||||
|
||||
fn get_config_last_seen_uid<S: AsRef<str>>(&self, context: &Context, folder: S) -> (u32, u32) {
|
||||
let key = format!("imap.mailbox.{}", folder.as_ref());
|
||||
if let Some(entry) = (self.get_config)(context, &key) {
|
||||
if let Some(entry) = context.sql.get_config(context, &key) {
|
||||
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
|
||||
let mut parts = entry.split(':');
|
||||
(
|
||||
@@ -721,7 +705,6 @@ impl Imap {
|
||||
if !self.is_connected() {
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
"Cannot fetch from \"{}\" - not connected.",
|
||||
folder.as_ref()
|
||||
);
|
||||
@@ -732,7 +715,6 @@ impl Imap {
|
||||
if self.select_folder(context, Some(&folder)) == 0 {
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
"Cannot select folder \"{}\" for fetching.",
|
||||
folder.as_ref()
|
||||
);
|
||||
@@ -749,7 +731,6 @@ impl Imap {
|
||||
if mailbox.uid_validity.is_none() {
|
||||
error!(
|
||||
context,
|
||||
0,
|
||||
"Cannot get UIDVALIDITY for folder \"{}\".",
|
||||
folder.as_ref(),
|
||||
);
|
||||
@@ -761,7 +742,7 @@ impl Imap {
|
||||
// first time this folder is selected or UIDVALIDITY has changed, init lastseenuid and save it to config
|
||||
|
||||
if mailbox.exists == 0 {
|
||||
info!(context, 0, "Folder \"{}\" is empty.", folder.as_ref());
|
||||
info!(context, "Folder \"{}\" is empty.", folder.as_ref());
|
||||
|
||||
// set lastseenuid=0 for empty folders.
|
||||
// id we do not do this here, we'll miss the first message
|
||||
@@ -777,10 +758,9 @@ impl Imap {
|
||||
match session.fetch(set, PREFETCH_FLAGS) {
|
||||
Ok(list) => list,
|
||||
Err(_err) => {
|
||||
self.config.write().unwrap().should_reconnect = true;
|
||||
self.should_reconnect.store(true, Ordering::Relaxed);
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
"No result returned for folder \"{}\".",
|
||||
folder.as_ref()
|
||||
);
|
||||
@@ -803,7 +783,6 @@ impl Imap {
|
||||
self.set_config_last_seen_uid(context, &folder, uid_validity, last_seen_uid);
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
"lastseenuid initialized to {} for {}@{}",
|
||||
last_seen_uid,
|
||||
folder.as_ref(),
|
||||
@@ -822,7 +801,7 @@ impl Imap {
|
||||
match session.uid_fetch(set, PREFETCH_FLAGS) {
|
||||
Ok(list) => list,
|
||||
Err(err) => {
|
||||
eprintln!("fetch err: {:?}", err);
|
||||
warn!(context, "failed to fetch uids: {}", err);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -844,13 +823,12 @@ impl Imap {
|
||||
|
||||
if 0 == unsafe {
|
||||
let message_id_c = CString::yolo(message_id);
|
||||
(self.precheck_imf)(context, message_id_c.as_ptr(), folder.as_ref(), cur_uid)
|
||||
precheck_imf(context, message_id_c.as_ptr(), folder.as_ref(), cur_uid)
|
||||
} {
|
||||
// check passed, go fetch the rest
|
||||
if self.fetch_single_msg(context, &folder, cur_uid) == 0 {
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
"Read error for message {} from \"{}\", trying over later.",
|
||||
message_id,
|
||||
folder.as_ref()
|
||||
@@ -862,7 +840,6 @@ impl Imap {
|
||||
// check failed
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
"Skipping message {} from \"{}\" by precheck.",
|
||||
message_id,
|
||||
folder.as_ref(),
|
||||
@@ -883,7 +860,6 @@ impl Imap {
|
||||
if read_errors > 0 {
|
||||
warn!(
|
||||
context,
|
||||
0,
|
||||
"{} mails read from \"{}\" with {} errors.",
|
||||
read_cnt,
|
||||
folder.as_ref(),
|
||||
@@ -892,7 +868,6 @@ impl Imap {
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
"{} mails read from \"{}\".",
|
||||
read_cnt,
|
||||
folder.as_ref()
|
||||
@@ -912,7 +887,7 @@ impl Imap {
|
||||
let key = format!("imap.mailbox.{}", folder.as_ref());
|
||||
let val = format!("{}:{}", uidvalidity, lastseenuid);
|
||||
|
||||
(self.set_config)(context, &key, Some(&val));
|
||||
context.sql.set_config(context, &key, Some(&val)).ok();
|
||||
}
|
||||
|
||||
fn fetch_single_msg<S: AsRef<str>>(
|
||||
@@ -928,41 +903,31 @@ impl Imap {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mut retry_later = false;
|
||||
|
||||
let set = format!("{}", server_uid);
|
||||
|
||||
let msgs = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||
match session.uid_fetch(set, BODY_FLAGS) {
|
||||
Ok(msgs) => msgs,
|
||||
Err(err) => {
|
||||
self.config.write().unwrap().should_reconnect = true;
|
||||
self.should_reconnect.store(true, Ordering::Relaxed);
|
||||
warn!(
|
||||
context,
|
||||
0,
|
||||
"Error on fetching message #{} from folder \"{}\"; retry={}; error={}.",
|
||||
server_uid,
|
||||
folder.as_ref(),
|
||||
self.should_reconnect(),
|
||||
err
|
||||
);
|
||||
|
||||
if self.should_reconnect() {
|
||||
// maybe we should also retry on other errors, however, we should check this carefully, as this may result in a dead lock!
|
||||
retry_later = true;
|
||||
}
|
||||
|
||||
return if retry_later { 0 } else { 1 };
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return if retry_later { 0 } else { 1 };
|
||||
return 1;
|
||||
};
|
||||
|
||||
if msgs.is_empty() {
|
||||
warn!(
|
||||
context,
|
||||
0,
|
||||
"Message #{} does not exist in folder \"{}\".",
|
||||
server_uid,
|
||||
folder.as_ref()
|
||||
@@ -992,7 +957,7 @@ impl Imap {
|
||||
if !is_deleted && msg.body().is_some() {
|
||||
let body = msg.body().unwrap();
|
||||
unsafe {
|
||||
(self.receive_imf)(
|
||||
dc_receive_imf(
|
||||
context,
|
||||
body.as_ptr() as *const libc::c_char,
|
||||
body.len(),
|
||||
@@ -1004,11 +969,7 @@ impl Imap {
|
||||
}
|
||||
}
|
||||
|
||||
if retry_later {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
1
|
||||
}
|
||||
|
||||
pub fn idle(&self, context: &Context) {
|
||||
@@ -1020,7 +981,7 @@ impl Imap {
|
||||
|
||||
let watch_folder = self.config.read().unwrap().watch_folder.clone();
|
||||
if self.select_folder(context, watch_folder.as_ref()) == 0 {
|
||||
warn!(context, 0, "IMAP-IDLE not setup.",);
|
||||
warn!(context, "IMAP-IDLE not setup.",);
|
||||
|
||||
return self.fake_idle(context);
|
||||
}
|
||||
@@ -1030,7 +991,7 @@ impl Imap {
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
let v = self.watch.clone();
|
||||
|
||||
info!(context, 0, "IMAP-IDLE SPAWNING");
|
||||
info!(context, "IMAP-IDLE SPAWNING");
|
||||
std::thread::spawn(move || {
|
||||
let &(ref lock, ref cvar) = &*v;
|
||||
if let Some(ref mut session) = &mut *session.lock().unwrap() {
|
||||
@@ -1065,18 +1026,15 @@ impl Imap {
|
||||
|
||||
let handle_res = |res| match res {
|
||||
Ok(()) => {
|
||||
info!(context, 0, "IMAP-IDLE has data.");
|
||||
info!(context, "IMAP-IDLE has data.");
|
||||
}
|
||||
Err(err) => match err {
|
||||
imap::error::Error::ConnectionLost => {
|
||||
info!(
|
||||
context,
|
||||
0, "IMAP-IDLE wait cancelled, we will reconnect soon."
|
||||
);
|
||||
self.config.write().unwrap().should_reconnect = true;
|
||||
info!(context, "IMAP-IDLE wait cancelled, we will reconnect soon.");
|
||||
self.should_reconnect.store(true, Ordering::Relaxed);
|
||||
}
|
||||
_ => {
|
||||
warn!(context, 0, "IMAP-IDLE returns unknown value: {}", err);
|
||||
warn!(context, "IMAP-IDLE returns unknown value: {}", err);
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -1092,7 +1050,7 @@ impl Imap {
|
||||
if let Ok(res) = worker.as_ref().unwrap().try_recv() {
|
||||
handle_res(res);
|
||||
} else {
|
||||
info!(context, 0, "IMAP-IDLE interrupted");
|
||||
info!(context, "IMAP-IDLE interrupted");
|
||||
}
|
||||
|
||||
drop(worker.take());
|
||||
@@ -1110,7 +1068,7 @@ impl Imap {
|
||||
let fake_idle_start_time = SystemTime::now();
|
||||
let mut wait_long = false;
|
||||
|
||||
info!(context, 0, "IMAP-fake-IDLEing...");
|
||||
info!(context, "IMAP-fake-IDLEing...");
|
||||
|
||||
let mut do_fake_idle = true;
|
||||
while do_fake_idle {
|
||||
@@ -1186,7 +1144,6 @@ impl Imap {
|
||||
} else if folder.as_ref() == dest_folder.as_ref() {
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
"Skip moving message; message {}/{} is already in {}...",
|
||||
folder.as_ref(),
|
||||
uid,
|
||||
@@ -1197,7 +1154,6 @@ impl Imap {
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
"Moving message {}/{} to {}...",
|
||||
folder.as_ref(),
|
||||
uid,
|
||||
@@ -1207,7 +1163,6 @@ impl Imap {
|
||||
if self.select_folder(context, Some(folder.as_ref())) == 0 {
|
||||
warn!(
|
||||
context,
|
||||
0,
|
||||
"Cannot select folder {} for moving message.",
|
||||
folder.as_ref()
|
||||
);
|
||||
@@ -1221,7 +1176,6 @@ impl Imap {
|
||||
Err(err) => {
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
"Cannot move message, fallback to COPY/DELETE {}/{} to {}: {}",
|
||||
folder.as_ref(),
|
||||
uid,
|
||||
@@ -1242,7 +1196,7 @@ impl Imap {
|
||||
Ok(_) => true,
|
||||
Err(err) => {
|
||||
eprintln!("error copy: {:?}", err);
|
||||
info!(context, 0, "Cannot copy message.",);
|
||||
info!(context, "Cannot copy message.",);
|
||||
|
||||
false
|
||||
}
|
||||
@@ -1253,7 +1207,7 @@ impl Imap {
|
||||
|
||||
if copied {
|
||||
if self.add_flag(context, uid, "\\Deleted") == 0 {
|
||||
warn!(context, 0, "Cannot mark message as \"Deleted\".",);
|
||||
warn!(context, "Cannot mark message as \"Deleted\".",);
|
||||
}
|
||||
self.config.write().unwrap().selected_folder_needs_expunge = true;
|
||||
res = DC_SUCCESS;
|
||||
@@ -1290,7 +1244,7 @@ impl Imap {
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
0, "IMAP failed to store: ({}, {}) {:?}", set, query, err
|
||||
"IMAP failed to store: ({}, {}) {:?}", set, query, err
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1313,7 +1267,6 @@ impl Imap {
|
||||
} else if self.is_connected() {
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
"Marking message {}/{} as seen...",
|
||||
folder.as_ref(),
|
||||
uid,
|
||||
@@ -1322,12 +1275,11 @@ impl Imap {
|
||||
if self.select_folder(context, Some(folder.as_ref())) == 0 {
|
||||
warn!(
|
||||
context,
|
||||
0,
|
||||
"Cannot select folder {} for setting SEEN flag.",
|
||||
folder.as_ref(),
|
||||
);
|
||||
} else if self.add_flag(context, uid, "\\Seen") == 0 {
|
||||
warn!(context, 0, "Cannot mark message as seen.",);
|
||||
warn!(context, "Cannot mark message as seen.",);
|
||||
} else {
|
||||
res = DC_SUCCESS
|
||||
}
|
||||
@@ -1354,7 +1306,6 @@ impl Imap {
|
||||
} else if self.is_connected() {
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
"Marking message {}/{} as $MDNSent...",
|
||||
folder.as_ref(),
|
||||
uid,
|
||||
@@ -1363,7 +1314,6 @@ impl Imap {
|
||||
if self.select_folder(context, Some(folder.as_ref())) == 0 {
|
||||
warn!(
|
||||
context,
|
||||
0,
|
||||
"Cannot select folder {} for setting $MDNSent flag.",
|
||||
folder.as_ref()
|
||||
);
|
||||
@@ -1430,16 +1380,16 @@ impl Imap {
|
||||
};
|
||||
|
||||
if res == DC_SUCCESS {
|
||||
info!(context, 0, "$MDNSent just set and MDN will be sent.");
|
||||
info!(context, "$MDNSent just set and MDN will be sent.");
|
||||
} else {
|
||||
info!(context, 0, "$MDNSent already set and MDN already sent.");
|
||||
info!(context, "$MDNSent already set and MDN already sent.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res = DC_SUCCESS;
|
||||
info!(
|
||||
context,
|
||||
0, "Cannot store $MDNSent flags, risk sending duplicate MDN.",
|
||||
"Cannot store $MDNSent flags, risk sending duplicate MDN.",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1470,7 +1420,6 @@ impl Imap {
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
"Marking message \"{}\", {}/{} for deletion...",
|
||||
message_id.as_ref(),
|
||||
folder.as_ref(),
|
||||
@@ -1480,7 +1429,6 @@ impl Imap {
|
||||
if self.select_folder(context, Some(&folder)) == 0 {
|
||||
warn!(
|
||||
context,
|
||||
0,
|
||||
"Cannot select folder {} for deleting message.",
|
||||
folder.as_ref()
|
||||
);
|
||||
@@ -1501,7 +1449,6 @@ impl Imap {
|
||||
{
|
||||
warn!(
|
||||
context,
|
||||
0,
|
||||
"Cannot delete on IMAP, {}/{} does not match {}.",
|
||||
folder.as_ref(),
|
||||
server_uid,
|
||||
@@ -1515,7 +1462,6 @@ impl Imap {
|
||||
|
||||
warn!(
|
||||
context,
|
||||
0,
|
||||
"Cannot delete on IMAP, {}/{} not found.",
|
||||
folder.as_ref(),
|
||||
server_uid,
|
||||
@@ -1527,7 +1473,7 @@ impl Imap {
|
||||
|
||||
// mark the message for deletion
|
||||
if self.add_flag(context, *server_uid, "\\Deleted") == 0 {
|
||||
warn!(context, 0, "Cannot mark message as \"Deleted\".");
|
||||
warn!(context, "Cannot mark message as \"Deleted\".");
|
||||
} else {
|
||||
self.config.write().unwrap().selected_folder_needs_expunge = true;
|
||||
success = true
|
||||
@@ -1547,7 +1493,7 @@ impl Imap {
|
||||
return;
|
||||
}
|
||||
|
||||
info!(context, 0, "Configuring IMAP-folders.");
|
||||
info!(context, "Configuring IMAP-folders.");
|
||||
|
||||
let folders = self.list_folders(context).unwrap();
|
||||
let delimiter = self.config.read().unwrap().imap_delimiter;
|
||||
@@ -1566,30 +1512,31 @@ impl Imap {
|
||||
});
|
||||
|
||||
if mvbox_folder.is_none() && 0 != (flags as usize & DC_CREATE_MVBOX) {
|
||||
info!(context, 0, "Creating MVBOX-folder \"DeltaChat\"...",);
|
||||
info!(context, "Creating MVBOX-folder \"DeltaChat\"...",);
|
||||
|
||||
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||
match session.create("DeltaChat") {
|
||||
Ok(_) => {
|
||||
mvbox_folder = Some("DeltaChat".into());
|
||||
|
||||
info!(context, 0, "MVBOX-folder created.",);
|
||||
info!(context, "MVBOX-folder created.",);
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("create error: {:?}", err);
|
||||
warn!(
|
||||
context,
|
||||
0, "Cannot create MVBOX-folder, using trying INBOX subfolder."
|
||||
"Cannot create MVBOX-folder, using trying INBOX subfolder. ({})", err
|
||||
);
|
||||
|
||||
match session.create(&fallback_folder) {
|
||||
Ok(_) => {
|
||||
mvbox_folder = Some(fallback_folder);
|
||||
info!(context, 0, "MVBOX-folder created as INBOX subfolder.",);
|
||||
info!(
|
||||
context,
|
||||
"MVBOX-folder created as INBOX subfolder. ({})", err
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("create error: {:?}", err);
|
||||
warn!(context, 0, "Cannot create MVBOX-folder.",);
|
||||
warn!(context, "Cannot create MVBOX-folder. ({})", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1635,13 +1582,13 @@ impl Imap {
|
||||
match session.list(Some(""), Some("*")) {
|
||||
Ok(list) => {
|
||||
if list.is_empty() {
|
||||
warn!(context, 0, "Folder list is empty.",);
|
||||
warn!(context, "Folder list is empty.",);
|
||||
}
|
||||
Some(list)
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("list error: {:?}", err);
|
||||
warn!(context, 0, "Cannot get folder list.",);
|
||||
warn!(context, "Cannot get folder list.",);
|
||||
|
||||
None
|
||||
}
|
||||
@@ -1695,3 +1642,53 @@ fn get_folder_meaning(folder_name: &imap::types::Name) -> FolderMeaning {
|
||||
_ => res,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn precheck_imf(
|
||||
context: &Context,
|
||||
rfc724_mid: *const libc::c_char,
|
||||
server_folder: &str,
|
||||
server_uid: u32,
|
||||
) -> libc::c_int {
|
||||
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(
|
||||
context,
|
||||
Action::MarkseenMsgOnImap,
|
||||
msg_id as libc::c_int,
|
||||
Params::new(),
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
libc::free(old_server_folder as *mut libc::c_void);
|
||||
rfc724_mid_exists
|
||||
}
|
||||
|
||||
1101
src/job.rs
Normal file
1101
src/job.rs
Normal file
File diff suppressed because it is too large
Load Diff
180
src/job_thread.rs
Normal file
180
src/job_thread.rs
Normal file
@@ -0,0 +1,180 @@
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
|
||||
use crate::configure::*;
|
||||
use crate::context::Context;
|
||||
use crate::imap::Imap;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct JobThread {
|
||||
pub name: &'static str,
|
||||
pub folder_config_name: &'static str,
|
||||
pub imap: Imap,
|
||||
pub state: Arc<(Mutex<JobState>, Condvar)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct JobState {
|
||||
idle: bool,
|
||||
jobs_needed: i32,
|
||||
suspended: bool,
|
||||
using_handle: bool,
|
||||
}
|
||||
|
||||
impl JobThread {
|
||||
pub fn new(name: &'static str, folder_config_name: &'static str, imap: Imap) -> Self {
|
||||
JobThread {
|
||||
name,
|
||||
folder_config_name,
|
||||
imap,
|
||||
state: Arc::new((Mutex::new(Default::default()), Condvar::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn suspend(&self, context: &Context) {
|
||||
info!(context, "Suspending {}-thread.", self.name,);
|
||||
{
|
||||
self.state.0.lock().unwrap().suspended = true;
|
||||
}
|
||||
self.interrupt_idle(context);
|
||||
loop {
|
||||
let using_handle = self.state.0.lock().unwrap().using_handle;
|
||||
if !using_handle {
|
||||
return;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_micros(300 * 1000));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unsuspend(&self, context: &Context) {
|
||||
info!(context, "Unsuspending {}-thread.", self.name);
|
||||
|
||||
let &(ref lock, ref cvar) = &*self.state.clone();
|
||||
let mut state = lock.lock().unwrap();
|
||||
|
||||
state.suspended = false;
|
||||
state.idle = true;
|
||||
cvar.notify_one();
|
||||
}
|
||||
|
||||
pub fn interrupt_idle(&self, context: &Context) {
|
||||
{
|
||||
self.state.0.lock().unwrap().jobs_needed = 1;
|
||||
}
|
||||
|
||||
info!(context, "Interrupting {}-IDLE...", self.name);
|
||||
|
||||
self.imap.interrupt_idle();
|
||||
|
||||
let &(ref lock, ref cvar) = &*self.state.clone();
|
||||
let mut state = lock.lock().unwrap();
|
||||
|
||||
state.idle = true;
|
||||
cvar.notify_one();
|
||||
}
|
||||
|
||||
pub fn fetch(&mut self, context: &Context, use_network: bool) {
|
||||
{
|
||||
let &(ref lock, _) = &*self.state.clone();
|
||||
let mut state = lock.lock().unwrap();
|
||||
|
||||
if state.suspended {
|
||||
return;
|
||||
}
|
||||
|
||||
state.using_handle = true;
|
||||
}
|
||||
|
||||
if use_network {
|
||||
let start = std::time::Instant::now();
|
||||
if self.connect_to_imap(context) {
|
||||
info!(context, "{}-fetch started...", self.name);
|
||||
self.imap.fetch(context);
|
||||
|
||||
if self.imap.should_reconnect() {
|
||||
info!(context, "{}-fetch aborted, starting over...", self.name,);
|
||||
self.imap.fetch(context);
|
||||
}
|
||||
info!(
|
||||
context,
|
||||
"{}-fetch done in {:.3} ms.",
|
||||
self.name,
|
||||
start.elapsed().as_millis(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.state.0.lock().unwrap().using_handle = false;
|
||||
}
|
||||
|
||||
fn connect_to_imap(&self, context: &Context) -> bool {
|
||||
if self.imap.is_connected() {
|
||||
return true;
|
||||
}
|
||||
|
||||
let mut ret_connected = dc_connect_to_configured_imap(context, &self.imap) != 0;
|
||||
|
||||
if ret_connected {
|
||||
if context
|
||||
.sql
|
||||
.get_config_int(context, "folders_configured")
|
||||
.unwrap_or_default()
|
||||
< 3
|
||||
{
|
||||
self.imap.configure_folders(context, 0x1);
|
||||
}
|
||||
|
||||
if let Some(mvbox_name) = context.sql.get_config(context, self.folder_config_name) {
|
||||
self.imap.set_watch_folder(mvbox_name);
|
||||
} else {
|
||||
self.imap.disconnect(context);
|
||||
ret_connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
ret_connected
|
||||
}
|
||||
|
||||
pub fn idle(&self, context: &Context, use_network: bool) {
|
||||
{
|
||||
let &(ref lock, ref cvar) = &*self.state.clone();
|
||||
let mut state = lock.lock().unwrap();
|
||||
|
||||
if 0 != state.jobs_needed {
|
||||
info!(
|
||||
context,
|
||||
"{}-IDLE will not be started as it was interrupted while not ideling.",
|
||||
self.name,
|
||||
);
|
||||
state.jobs_needed = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if state.suspended {
|
||||
while !state.idle {
|
||||
state = cvar.wait(state).unwrap();
|
||||
}
|
||||
state.idle = false;
|
||||
return;
|
||||
}
|
||||
|
||||
state.using_handle = true;
|
||||
|
||||
if !use_network {
|
||||
state.using_handle = false;
|
||||
|
||||
while !state.idle {
|
||||
state = cvar.wait(state).unwrap();
|
||||
}
|
||||
state.idle = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.connect_to_imap(context);
|
||||
info!(context, "{}-IDLE started...", self.name,);
|
||||
self.imap.idle(context);
|
||||
info!(context, "{}-IDLE ended.", self.name);
|
||||
|
||||
self.state.0.lock().unwrap().using_handle = false;
|
||||
}
|
||||
}
|
||||
101
src/key.rs
101
src/key.rs
@@ -1,7 +1,6 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::io::Cursor;
|
||||
use std::slice;
|
||||
use std::path::Path;
|
||||
|
||||
use libc;
|
||||
use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey};
|
||||
@@ -12,7 +11,6 @@ use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::sql::{self, Sql};
|
||||
use crate::x::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Key {
|
||||
@@ -89,7 +87,7 @@ impl Key {
|
||||
}
|
||||
|
||||
pub fn from_slice(bytes: &[u8], key_type: KeyType) -> Option<Self> {
|
||||
if 0 == bytes.len() {
|
||||
if bytes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let res: Result<Key, _> = match key_type {
|
||||
@@ -106,15 +104,6 @@ impl Key {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_binary(data: *const u8, len: libc::c_int, key_type: KeyType) -> Option<Self> {
|
||||
if data.is_null() || len == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let bytes = unsafe { slice::from_raw_parts(data, len as usize) };
|
||||
Self::from_slice(bytes, key_type)
|
||||
}
|
||||
|
||||
pub fn from_armored_string(
|
||||
data: &str,
|
||||
key_type: KeyType,
|
||||
@@ -152,11 +141,10 @@ impl Key {
|
||||
) -> Option<Self> {
|
||||
let addr = self_addr.as_ref();
|
||||
|
||||
sql.query_row_col(
|
||||
sql.query_get_value(
|
||||
context,
|
||||
"SELECT public_key FROM keypairs WHERE addr=? AND is_default=1;",
|
||||
&[addr],
|
||||
0,
|
||||
)
|
||||
.and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Public))
|
||||
}
|
||||
@@ -166,11 +154,10 @@ impl Key {
|
||||
self_addr: impl AsRef<str>,
|
||||
sql: &Sql,
|
||||
) -> Option<Self> {
|
||||
sql.query_row_col(
|
||||
sql.query_get_value(
|
||||
context,
|
||||
"SELECT private_key FROM keypairs WHERE addr=? AND is_default=1;",
|
||||
&[self_addr.as_ref()],
|
||||
0,
|
||||
)
|
||||
.and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Private))
|
||||
}
|
||||
@@ -228,30 +215,15 @@ impl Key {
|
||||
.expect("failed to serialize key")
|
||||
}
|
||||
|
||||
pub fn write_asc_to_file(&self, file: *const libc::c_char, context: &Context) -> bool {
|
||||
if file.is_null() {
|
||||
pub fn write_asc_to_file(&self, file: impl AsRef<Path>, context: &Context) -> bool {
|
||||
let file_content = self.to_asc(None).into_bytes();
|
||||
|
||||
if dc_write_file(context, &file, &file_content) {
|
||||
return true;
|
||||
} else {
|
||||
error!(context, "Cannot write key to {}", file.as_ref().display());
|
||||
return false;
|
||||
}
|
||||
|
||||
let file_content = self.to_asc(None);
|
||||
let file_content_c = CString::new(file_content).unwrap();
|
||||
|
||||
let success = if 0
|
||||
== unsafe {
|
||||
dc_write_file(
|
||||
context,
|
||||
file,
|
||||
file_content_c.as_ptr() as *const libc::c_void,
|
||||
file_content_c.as_bytes().len(),
|
||||
)
|
||||
} {
|
||||
error!(context, 0, "Cannot write key to {}", to_string(file));
|
||||
false
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
success
|
||||
}
|
||||
|
||||
pub fn fingerprint(&self) -> String {
|
||||
@@ -261,29 +233,17 @@ impl Key {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fingerprint_c(&self) -> *mut libc::c_char {
|
||||
let res = CString::new(self.fingerprint()).unwrap();
|
||||
|
||||
unsafe { strdup(res.as_ptr()) }
|
||||
}
|
||||
|
||||
pub fn formatted_fingerprint(&self) -> String {
|
||||
let rawhex = self.fingerprint();
|
||||
dc_format_fingerprint(&rawhex)
|
||||
}
|
||||
|
||||
pub fn formatted_fingerprint_c(&self) -> *mut libc::c_char {
|
||||
let res = CString::new(self.formatted_fingerprint()).unwrap();
|
||||
|
||||
unsafe { strdup(res.as_ptr()) }
|
||||
}
|
||||
|
||||
pub fn split_key(&self) -> Option<Key> {
|
||||
match self {
|
||||
Key::Public(_) => None,
|
||||
Key::Secret(k) => {
|
||||
let pub_key = k.public_key();
|
||||
pub_key.sign(k, || "".into()).map(|k| Key::Public(k)).ok()
|
||||
pub_key.sign(k, || "".into()).map(Key::Public).ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -323,14 +283,6 @@ pub fn dc_format_fingerprint(fingerprint: &str) -> String {
|
||||
res
|
||||
}
|
||||
|
||||
pub fn dc_format_fingerprint_c(fp: *const libc::c_char) -> *mut libc::c_char {
|
||||
let input = unsafe { CStr::from_ptr(fp).to_str().unwrap() };
|
||||
let res = dc_format_fingerprint(input);
|
||||
let res_c = CString::new(res).unwrap();
|
||||
|
||||
unsafe { strdup(res_c.as_ptr()) }
|
||||
}
|
||||
|
||||
/// Bring a human-readable or otherwise formatted fingerprint back to the 40-characters-uppercase-hex format.
|
||||
pub fn dc_normalize_fingerprint(fp: &str) -> String {
|
||||
fp.to_uppercase()
|
||||
@@ -339,14 +291,6 @@ pub fn dc_normalize_fingerprint(fp: &str) -> String {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn dc_normalize_fingerprint_c(fp: *const libc::c_char) -> *mut libc::c_char {
|
||||
let input = unsafe { CStr::from_ptr(fp).to_str().unwrap() };
|
||||
let res = dc_normalize_fingerprint(input);
|
||||
let res_c = CString::new(res).unwrap();
|
||||
|
||||
unsafe { strdup(res_c.as_ptr()) }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -449,6 +393,27 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
||||
assert_eq!(private_key, private_key2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_slice_bad_data() {
|
||||
let mut bad_data: [u8; 4096] = [0; 4096];
|
||||
|
||||
for i in 0..4096 {
|
||||
bad_data[i] = (i & 0xff) as u8;
|
||||
}
|
||||
|
||||
for j in 0..(4096 / 40) {
|
||||
let bad_key = Key::from_slice(
|
||||
&bad_data[j..j + 4096 / 2 + j],
|
||||
if 0 != j & 1 {
|
||||
KeyType::Public
|
||||
} else {
|
||||
KeyType::Private
|
||||
},
|
||||
);
|
||||
assert!(bad_key.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // is too expensive
|
||||
fn test_ascii_roundtrip() {
|
||||
|
||||
@@ -33,11 +33,10 @@ impl<'a> Keyring<'a> {
|
||||
self_addr: impl AsRef<str>,
|
||||
sql: &Sql,
|
||||
) -> bool {
|
||||
sql.query_row_col(
|
||||
sql.query_get_value(
|
||||
context,
|
||||
"SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;",
|
||||
&[self_addr.as_ref()],
|
||||
0,
|
||||
)
|
||||
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Private))
|
||||
.map(|key| self.add_owned(key))
|
||||
|
||||
63
src/lib.rs
63
src/lib.rs
@@ -1,4 +1,9 @@
|
||||
#![feature(c_variadic, ptr_wrapping_offset_from, ptr_cast)]
|
||||
#![deny(clippy::correctness, missing_debug_implementations)]
|
||||
// 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)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate failure_derive;
|
||||
@@ -11,56 +16,58 @@ extern crate rusqlite;
|
||||
extern crate strum;
|
||||
#[macro_use]
|
||||
extern crate strum_macros;
|
||||
#[macro_use]
|
||||
extern crate jetscii;
|
||||
#[macro_use]
|
||||
extern crate debug_stub_derive;
|
||||
|
||||
#[macro_use]
|
||||
mod log;
|
||||
#[macro_use]
|
||||
pub mod error;
|
||||
|
||||
pub mod aheader;
|
||||
pub(crate) mod events;
|
||||
pub use events::*;
|
||||
|
||||
mod aheader;
|
||||
pub mod chat;
|
||||
pub mod chatlist;
|
||||
pub mod config;
|
||||
pub mod configure;
|
||||
pub mod constants;
|
||||
pub mod contact;
|
||||
pub mod context;
|
||||
pub mod imap;
|
||||
mod e2ee;
|
||||
mod imap;
|
||||
pub mod job;
|
||||
mod job_thread;
|
||||
pub mod key;
|
||||
pub mod keyring;
|
||||
pub mod location;
|
||||
pub mod lot;
|
||||
pub mod message;
|
||||
pub mod oauth2;
|
||||
pub mod param;
|
||||
mod param;
|
||||
pub mod peerstate;
|
||||
pub mod pgp;
|
||||
pub mod smtp;
|
||||
pub mod qr;
|
||||
mod smtp;
|
||||
pub mod sql;
|
||||
pub mod stock;
|
||||
pub mod types;
|
||||
mod stock;
|
||||
pub mod x;
|
||||
|
||||
pub mod dc_array;
|
||||
pub mod dc_chat;
|
||||
pub mod dc_configure;
|
||||
pub mod dc_dehtml;
|
||||
pub mod dc_e2ee;
|
||||
mod dc_dehtml;
|
||||
pub mod dc_imex;
|
||||
pub mod dc_job;
|
||||
pub mod dc_jobthread;
|
||||
pub mod dc_location;
|
||||
pub mod dc_loginparam;
|
||||
pub mod dc_lot;
|
||||
pub mod dc_mimefactory;
|
||||
mod dc_mimefactory;
|
||||
pub mod dc_mimeparser;
|
||||
pub mod dc_move;
|
||||
pub mod dc_msg;
|
||||
pub mod dc_qr;
|
||||
pub mod dc_receive_imf;
|
||||
pub mod dc_saxparser;
|
||||
pub mod dc_securejoin;
|
||||
pub mod dc_simplify;
|
||||
pub mod dc_strencode;
|
||||
pub mod dc_token;
|
||||
mod dc_simplify;
|
||||
mod dc_strencode;
|
||||
pub mod dc_tools;
|
||||
|
||||
pub use self::constants::*;
|
||||
mod login_param;
|
||||
pub mod securejoin;
|
||||
mod token;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test_utils;
|
||||
mod test_utils;
|
||||
|
||||
694
src/location.rs
Normal file
694
src/location.rs
Normal file
@@ -0,0 +1,694 @@
|
||||
use bitflags::bitflags;
|
||||
use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::chat;
|
||||
use crate::constants::*;
|
||||
use crate::context::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
use crate::job::*;
|
||||
use crate::message::*;
|
||||
use crate::param::*;
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
|
||||
// location handling
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Location {
|
||||
pub location_id: u32,
|
||||
pub latitude: f64,
|
||||
pub longitude: f64,
|
||||
pub accuracy: f64,
|
||||
pub timestamp: i64,
|
||||
pub contact_id: u32,
|
||||
pub msg_id: u32,
|
||||
pub chat_id: u32,
|
||||
pub marker: Option<String>,
|
||||
pub independent: u32,
|
||||
}
|
||||
|
||||
impl Location {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Kml {
|
||||
pub addr: Option<String>,
|
||||
pub locations: Vec<Location>,
|
||||
tag: KmlTag,
|
||||
pub curr: Location,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
struct KmlTag: i32 {
|
||||
const UNDEFINED = 0x00;
|
||||
const PLACEMARK = 0x01;
|
||||
const TIMESTAMP = 0x02;
|
||||
const WHEN = 0x04;
|
||||
const POINT = 0x08;
|
||||
const COORDINATES = 0x10;
|
||||
}
|
||||
}
|
||||
|
||||
impl Kml {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn parse(context: &Context, content: impl AsRef<str>) -> Result<Self, Error> {
|
||||
ensure!(
|
||||
content.as_ref().len() <= (1 * 1024 * 1024),
|
||||
"A kml-files with {} bytes is larger than reasonably expected.",
|
||||
content.as_ref().len()
|
||||
);
|
||||
|
||||
let mut reader = quick_xml::Reader::from_str(content.as_ref());
|
||||
reader.trim_text(true);
|
||||
|
||||
let mut kml = Kml::new();
|
||||
kml.locations = Vec::with_capacity(100);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
|
||||
loop {
|
||||
match reader.read_event(&mut buf) {
|
||||
Ok(quick_xml::events::Event::Start(ref e)) => kml.starttag_cb(e, &reader),
|
||||
Ok(quick_xml::events::Event::End(ref e)) => kml.endtag_cb(e),
|
||||
Ok(quick_xml::events::Event::Text(ref e)) => kml.text_cb(e, &reader),
|
||||
Err(e) => {
|
||||
error!(
|
||||
context,
|
||||
"Location parsing: Error at position {}: {:?}",
|
||||
reader.buffer_position(),
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(quick_xml::events::Event::Eof) => break,
|
||||
_ => (),
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
Ok(kml)
|
||||
}
|
||||
|
||||
fn text_cb<B: std::io::BufRead>(&mut self, event: &BytesText, reader: &quick_xml::Reader<B>) {
|
||||
if self.tag.contains(KmlTag::WHEN) || self.tag.contains(KmlTag::COORDINATES) {
|
||||
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
||||
|
||||
let val = val
|
||||
.replace("\n", "")
|
||||
.replace("\r", "")
|
||||
.replace("\t", "")
|
||||
.replace(" ", "");
|
||||
|
||||
if self.tag.contains(KmlTag::WHEN) && val.len() >= 19 {
|
||||
// YYYY-MM-DDTHH:MM:SSZ
|
||||
// 0 4 7 10 13 16 19
|
||||
match chrono::NaiveDateTime::parse_from_str(&val, "%Y-%m-%dT%H:%M:%SZ") {
|
||||
Ok(res) => {
|
||||
self.curr.timestamp = res.timestamp();
|
||||
if self.curr.timestamp > time() {
|
||||
self.curr.timestamp = time();
|
||||
}
|
||||
}
|
||||
Err(_err) => {
|
||||
self.curr.timestamp = time();
|
||||
}
|
||||
}
|
||||
} else if self.tag.contains(KmlTag::COORDINATES) {
|
||||
let parts = val.splitn(2, ',').collect::<Vec<_>>();
|
||||
if parts.len() == 2 {
|
||||
self.curr.longitude = parts[0].parse().unwrap_or_default();
|
||||
self.curr.latitude = parts[1].parse().unwrap_or_default();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn endtag_cb(&mut self, event: &BytesEnd) {
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
|
||||
if tag == "placemark" {
|
||||
if self.tag.contains(KmlTag::PLACEMARK)
|
||||
&& 0 != self.curr.timestamp
|
||||
&& 0. != self.curr.latitude
|
||||
&& 0. != self.curr.longitude
|
||||
{
|
||||
self.locations
|
||||
.push(std::mem::replace(&mut self.curr, Location::new()));
|
||||
}
|
||||
self.tag = KmlTag::UNDEFINED;
|
||||
};
|
||||
}
|
||||
|
||||
fn starttag_cb<B: std::io::BufRead>(
|
||||
&mut self,
|
||||
event: &BytesStart,
|
||||
reader: &quick_xml::Reader<B>,
|
||||
) {
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
if tag == "document" {
|
||||
if let Some(addr) = event.attributes().find(|attr| {
|
||||
attr.as_ref()
|
||||
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "addr")
|
||||
.unwrap_or_default()
|
||||
}) {
|
||||
self.addr = addr.unwrap().unescape_and_decode_value(reader).ok();
|
||||
}
|
||||
} else if tag == "placemark" {
|
||||
self.tag = KmlTag::PLACEMARK;
|
||||
self.curr.timestamp = 0;
|
||||
self.curr.latitude = 0.0;
|
||||
self.curr.longitude = 0.0;
|
||||
self.curr.accuracy = 0.0
|
||||
} else if tag == "timestamp" && self.tag.contains(KmlTag::PLACEMARK) {
|
||||
self.tag = KmlTag::PLACEMARK | KmlTag::TIMESTAMP
|
||||
} else if tag == "when" && self.tag.contains(KmlTag::TIMESTAMP) {
|
||||
self.tag = KmlTag::PLACEMARK | KmlTag::TIMESTAMP | KmlTag::WHEN
|
||||
} else if tag == "point" && self.tag.contains(KmlTag::PLACEMARK) {
|
||||
self.tag = KmlTag::PLACEMARK | KmlTag::POINT
|
||||
} else if tag == "coordinates" && self.tag.contains(KmlTag::POINT) {
|
||||
self.tag = KmlTag::PLACEMARK | KmlTag::POINT | KmlTag::COORDINATES;
|
||||
if let Some(acc) = event.attributes().find(|attr| {
|
||||
attr.as_ref()
|
||||
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "accuracy")
|
||||
.unwrap_or_default()
|
||||
}) {
|
||||
let v = acc
|
||||
.unwrap()
|
||||
.unescape_and_decode_value(reader)
|
||||
.unwrap_or_default();
|
||||
|
||||
self.curr.accuracy = v.trim().parse().unwrap_or_default();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// location streaming
|
||||
pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
|
||||
let now = time();
|
||||
let mut msg: Message;
|
||||
let is_sending_locations_before: bool;
|
||||
if !(seconds < 0 || chat_id <= 9i32 as libc::c_uint) {
|
||||
is_sending_locations_before = is_sending_locations_to_chat(context, chat_id);
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats \
|
||||
SET locations_send_begin=?, \
|
||||
locations_send_until=? \
|
||||
WHERE id=?",
|
||||
params![
|
||||
if 0 != seconds { now } else { 0 },
|
||||
if 0 != seconds { now + seconds } else { 0 },
|
||||
chat_id as i32,
|
||||
],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
if 0 != seconds && !is_sending_locations_before {
|
||||
msg = dc_msg_new(Viewtype::Text);
|
||||
msg.text =
|
||||
Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0));
|
||||
msg.param.set_int(Param::Cmd, 8);
|
||||
chat::send_msg(context, chat_id, &mut msg).unwrap();
|
||||
} else if 0 == seconds && is_sending_locations_before {
|
||||
let stock_str =
|
||||
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
||||
chat::add_device_msg(context, chat_id, stock_str);
|
||||
}
|
||||
context.call_cb(Event::ChatModified(chat_id));
|
||||
if 0 != seconds {
|
||||
schedule_MAYBE_SEND_LOCATIONS(context, 0i32);
|
||||
job_add(
|
||||
context,
|
||||
Action::MaybeSendLocationsEnded,
|
||||
chat_id as libc::c_int,
|
||||
Params::new(),
|
||||
seconds + 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn schedule_MAYBE_SEND_LOCATIONS(context: &Context, flags: i32) {
|
||||
if 0 != flags & 0x1 || !job_action_exists(context, Action::MaybeSendLocations) {
|
||||
job_add(context, Action::MaybeSendLocations, 0, Params::new(), 60);
|
||||
};
|
||||
}
|
||||
|
||||
pub fn is_sending_locations_to_chat(context: &Context, chat_id: u32) -> bool {
|
||||
context
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;",
|
||||
params![if chat_id == 0 { 1 } else { 0 }, chat_id as i32, time()],
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> libc::c_int {
|
||||
if latitude == 0.0 && longitude == 0.0 {
|
||||
return 1;
|
||||
}
|
||||
let mut continue_streaming = false;
|
||||
|
||||
if let Ok(chats) = context.sql.query_map(
|
||||
"SELECT id FROM chats WHERE locations_send_until>?;",
|
||||
params![time()],
|
||||
|row| row.get::<_, i32>(0),
|
||||
|chats| chats.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
) {
|
||||
for chat_id in chats {
|
||||
if let Err(err) = context.sql.execute(
|
||||
"INSERT INTO locations \
|
||||
(latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);",
|
||||
params![
|
||||
latitude,
|
||||
longitude,
|
||||
accuracy,
|
||||
time(),
|
||||
chat_id,
|
||||
1,
|
||||
]
|
||||
) {
|
||||
warn!(context, "failed to store location {:?}", err);
|
||||
} else {
|
||||
continue_streaming = true;
|
||||
}
|
||||
}
|
||||
if continue_streaming {
|
||||
context.call_cb(Event::LocationChanged(Some(1)));
|
||||
};
|
||||
schedule_MAYBE_SEND_LOCATIONS(context, 0);
|
||||
}
|
||||
|
||||
continue_streaming as libc::c_int
|
||||
}
|
||||
|
||||
pub fn get_range(
|
||||
context: &Context,
|
||||
chat_id: u32,
|
||||
contact_id: u32,
|
||||
timestamp_from: i64,
|
||||
mut timestamp_to: i64,
|
||||
) -> Vec<Location> {
|
||||
if timestamp_to == 0 {
|
||||
timestamp_to = time() + 10;
|
||||
}
|
||||
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent, \
|
||||
m.id, l.from_id, l.chat_id, m.txt \
|
||||
FROM locations l LEFT JOIN msgs m ON l.id=m.location_id WHERE (? OR l.chat_id=?) \
|
||||
AND (? OR l.from_id=?) \
|
||||
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \
|
||||
ORDER BY l.timestamp DESC, l.id DESC, m.id DESC;",
|
||||
params![
|
||||
if chat_id == 0 { 1 } else { 0 },
|
||||
chat_id as i32,
|
||||
if contact_id == 0 { 1 } else { 0 },
|
||||
contact_id as i32,
|
||||
timestamp_from,
|
||||
timestamp_to,
|
||||
],
|
||||
|row| {
|
||||
let msg_id = row.get(6)?;
|
||||
let txt: String = row.get(9)?;
|
||||
let marker = if msg_id != 0 && is_marker(&txt) {
|
||||
Some(txt)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let loc = Location {
|
||||
location_id: row.get(0)?,
|
||||
latitude: row.get(1)?,
|
||||
longitude: row.get(2)?,
|
||||
accuracy: row.get(3)?,
|
||||
timestamp: row.get(4)?,
|
||||
independent: row.get(5)?,
|
||||
msg_id,
|
||||
contact_id: row.get(7)?,
|
||||
chat_id: row.get(8)?,
|
||||
marker,
|
||||
};
|
||||
Ok(loc)
|
||||
},
|
||||
|locations| {
|
||||
let mut ret = Vec::new();
|
||||
|
||||
for location in locations {
|
||||
ret.push(location?);
|
||||
}
|
||||
Ok(ret)
|
||||
},
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn is_marker(txt: &str) -> bool {
|
||||
txt.len() == 1 && txt.chars().next().unwrap() != ' '
|
||||
}
|
||||
|
||||
pub fn delete_all(context: &Context) -> Result<(), Error> {
|
||||
sql::execute(context, &context.sql, "DELETE FROM locations;", params![])?;
|
||||
context.call_cb(Event::LocationChanged(None));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error> {
|
||||
let now = time();
|
||||
let mut location_count = 0;
|
||||
let mut ret = String::new();
|
||||
let mut last_added_location_id = 0;
|
||||
|
||||
let self_addr = context
|
||||
.sql
|
||||
.get_config(context, "configured_addr")
|
||||
.unwrap_or_default();
|
||||
|
||||
let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row(
|
||||
"SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;",
|
||||
params![chat_id as i32], |row| {
|
||||
let send_begin: i64 = row.get(0)?;
|
||||
let send_until: i64 = row.get(1)?;
|
||||
let last_sent: i64 = row.get(2)?;
|
||||
|
||||
Ok((send_begin, send_until, last_sent))
|
||||
})?;
|
||||
|
||||
if !(locations_send_begin == 0 || now > locations_send_until) {
|
||||
ret += &format!(
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"{}\">\n",
|
||||
self_addr,
|
||||
);
|
||||
|
||||
context.sql.query_map(
|
||||
"SELECT id, latitude, longitude, accuracy, timestamp\
|
||||
FROM locations WHERE from_id=? \
|
||||
AND timestamp>=? \
|
||||
AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?)) \
|
||||
AND independent=0 \
|
||||
GROUP BY timestamp \
|
||||
ORDER BY timestamp;",
|
||||
params![1, locations_send_begin, locations_last_sent, 1],
|
||||
|row| {
|
||||
let location_id: i32 = row.get(0)?;
|
||||
let latitude: f64 = row.get(1)?;
|
||||
let longitude: f64 = row.get(2)?;
|
||||
let accuracy: f64 = row.get(3)?;
|
||||
let timestamp = get_kml_timestamp(row.get(4)?);
|
||||
|
||||
Ok((location_id, latitude, longitude, accuracy, timestamp))
|
||||
},
|
||||
|rows| {
|
||||
for row in rows {
|
||||
let (location_id, latitude, longitude, accuracy, timestamp) = row?;
|
||||
ret += &format!(
|
||||
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n\x00",
|
||||
timestamp,
|
||||
accuracy,
|
||||
longitude,
|
||||
latitude
|
||||
);
|
||||
location_count += 1;
|
||||
last_added_location_id = location_id as u32;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
)?;
|
||||
}
|
||||
|
||||
ensure!(location_count > 0, "No locations processed");
|
||||
ret += "</Document>\n</kml>";
|
||||
|
||||
Ok((ret, last_added_location_id))
|
||||
}
|
||||
|
||||
fn get_kml_timestamp(utc: i64) -> String {
|
||||
// Returns a string formatted as YYYY-MM-DDTHH:MM:SSZ. The trailing `Z` indicates UTC.
|
||||
chrono::NaiveDateTime::from_timestamp(utc, 0)
|
||||
.format("%Y-%m-%dT%H:%M:%SZ")
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn get_message_kml(timestamp: i64, latitude: f64, longitude: f64) -> String {
|
||||
format!(
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
|
||||
<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n\
|
||||
<Document>\n\
|
||||
<Placemark>\
|
||||
<Timestamp><when>{}</when></Timestamp>\
|
||||
<Point><coordinates>{:.2},{:.2}</coordinates></Point>\
|
||||
</Placemark>\n\
|
||||
</Document>\n\
|
||||
</kml>",
|
||||
get_kml_timestamp(timestamp),
|
||||
longitude,
|
||||
latitude,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_kml_sent_timestamp(
|
||||
context: &Context,
|
||||
chat_id: u32,
|
||||
timestamp: i64,
|
||||
) -> Result<(), Error> {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats SET locations_last_sent=? WHERE id=?;",
|
||||
params![timestamp, chat_id as i32],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_msg_location_id(context: &Context, msg_id: u32, location_id: u32) -> Result<(), Error> {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE msgs SET location_id=? WHERE id=?;",
|
||||
params![location_id, msg_id as i32],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn save(
|
||||
context: &Context,
|
||||
chat_id: u32,
|
||||
contact_id: u32,
|
||||
locations: &[Location],
|
||||
independent: i32,
|
||||
) -> Result<u32, Error> {
|
||||
ensure!(chat_id > 9, "Invalid chat id");
|
||||
context.sql.prepare2(
|
||||
"SELECT id FROM locations WHERE timestamp=? AND from_id=?",
|
||||
"INSERT INTO locations\
|
||||
(timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \
|
||||
VALUES (?,?,?,?,?,?,?);",
|
||||
|mut stmt_test, mut stmt_insert, conn| {
|
||||
let mut newest_timestamp = 0;
|
||||
let mut newest_location_id = 0;
|
||||
|
||||
for location in locations {
|
||||
let exists = stmt_test.exists(params![location.timestamp, contact_id as i32])?;
|
||||
|
||||
if 0 != independent || !exists {
|
||||
stmt_insert.execute(params![
|
||||
location.timestamp,
|
||||
contact_id as i32,
|
||||
chat_id as i32,
|
||||
location.latitude,
|
||||
location.longitude,
|
||||
location.accuracy,
|
||||
independent,
|
||||
])?;
|
||||
|
||||
if location.timestamp > newest_timestamp {
|
||||
newest_timestamp = location.timestamp;
|
||||
newest_location_id = sql::get_rowid2_with_conn(
|
||||
context,
|
||||
conn,
|
||||
"locations",
|
||||
"timestamp",
|
||||
location.timestamp,
|
||||
"from_id",
|
||||
contact_id as i32,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(newest_location_id)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
|
||||
let now = time();
|
||||
let mut continue_streaming: libc::c_int = 1;
|
||||
info!(
|
||||
context,
|
||||
" ----------------- MAYBE_SEND_LOCATIONS -------------- ",
|
||||
);
|
||||
|
||||
if let Ok(rows) = context.sql.query_map(
|
||||
"SELECT id, locations_send_begin, locations_last_sent \
|
||||
FROM chats \
|
||||
WHERE locations_send_until>?;",
|
||||
params![now],
|
||||
|row| {
|
||||
let chat_id: i32 = row.get(0)?;
|
||||
let locations_send_begin: i64 = row.get(1)?;
|
||||
let locations_last_sent: i64 = row.get(2)?;
|
||||
continue_streaming = 1;
|
||||
|
||||
// be a bit tolerant as the timer may not align exactly with time(NULL)
|
||||
if now - locations_last_sent < (60 - 3) {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some((chat_id, locations_send_begin, locations_last_sent)))
|
||||
}
|
||||
},
|
||||
|rows| {
|
||||
rows.filter_map(|v| v.transpose())
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(Into::into)
|
||||
},
|
||||
) {
|
||||
let msgs = context
|
||||
.sql
|
||||
.prepare(
|
||||
"SELECT id \
|
||||
FROM locations \
|
||||
WHERE from_id=? \
|
||||
AND timestamp>=? \
|
||||
AND timestamp>? \
|
||||
AND independent=0 \
|
||||
ORDER BY timestamp;",
|
||||
|mut stmt_locations, _| {
|
||||
let msgs = rows
|
||||
.into_iter()
|
||||
.filter_map(|(chat_id, locations_send_begin, locations_last_sent)| {
|
||||
if !stmt_locations
|
||||
.exists(params![1, locations_send_begin, locations_last_sent,])
|
||||
.unwrap_or_default()
|
||||
{
|
||||
// if there is no new location, there's nothing to send.
|
||||
// however, maybe we want to bypass this test eg. 15 minutes
|
||||
None
|
||||
} else {
|
||||
// pending locations are attached automatically to every message,
|
||||
// so also to this empty text message.
|
||||
// DC_CMD_LOCATION is only needed to create a nicer subject.
|
||||
//
|
||||
// for optimisation and to avoid flooding the sending queue,
|
||||
// we could sending these messages only if we're really online.
|
||||
// the easiest way to determine this, is to check for an empty message queue.
|
||||
// (might not be 100%, however, as positions are sent combined later
|
||||
// and dc_set_location() is typically called periodically, this is ok)
|
||||
let mut msg = dc_msg_new(Viewtype::Text);
|
||||
msg.hidden = true;
|
||||
msg.param.set_int(Param::Cmd, 9);
|
||||
Some((chat_id, msg))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Ok(msgs)
|
||||
},
|
||||
)
|
||||
.unwrap_or_default(); // TODO: Better error handling
|
||||
|
||||
for (chat_id, mut msg) in msgs.into_iter() {
|
||||
// TODO: better error handling
|
||||
chat::send_msg(context, chat_id as u32, &mut msg).unwrap();
|
||||
}
|
||||
}
|
||||
if 0 != continue_streaming {
|
||||
schedule_MAYBE_SEND_LOCATIONS(context, 0x1);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut Job) {
|
||||
// this function is called when location-streaming _might_ have ended for a chat.
|
||||
// the function checks, if location-streaming is really ended;
|
||||
// if so, a device-message is added if not yet done.
|
||||
|
||||
let chat_id = job.foreign_id;
|
||||
|
||||
if let Ok((send_begin, send_until)) = context.sql.query_row(
|
||||
"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?",
|
||||
params![chat_id as i32],
|
||||
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)),
|
||||
) {
|
||||
if !(send_begin != 0 && time() <= send_until) {
|
||||
// still streaming -
|
||||
// may happen as several calls to dc_send_locations_to_chat()
|
||||
// do not un-schedule pending DC_MAYBE_SEND_LOC_ENDED jobs
|
||||
if !(send_begin == 0 && send_until == 0) {
|
||||
// not streaming, device-message already sent
|
||||
if context.sql.execute(
|
||||
"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?",
|
||||
params![chat_id as i32],
|
||||
).is_ok() {
|
||||
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
||||
chat::add_device_msg(context, chat_id, stock_str);
|
||||
context.call_cb(Event::ChatModified(chat_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::dummy_context;
|
||||
|
||||
#[test]
|
||||
fn test_kml_parse() {
|
||||
let context = dummy_context();
|
||||
|
||||
let xml =
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"user@example.org\">\n<Placemark><Timestamp><when>2019-03-06T21:09:57Z</when></Timestamp><Point><coordinates accuracy=\"32.000000\">9.423110,53.790302</coordinates></Point></Placemark>\n<PlaceMARK>\n<Timestamp><WHEN > \n\t2018-12-13T22:11:12Z\t</WHEN></Timestamp><Point><coordinates aCCuracy=\"2.500000\"> 19.423110 \t , \n 63.790302\n </coordinates></Point></PlaceMARK>\n</Document>\n</kml>";
|
||||
|
||||
let kml = Kml::parse(&context.ctx, &xml).expect("parsing failed");
|
||||
|
||||
assert!(kml.addr.is_some());
|
||||
assert_eq!(kml.addr.as_ref().unwrap(), "user@example.org",);
|
||||
|
||||
let locations_ref = &kml.locations;
|
||||
assert_eq!(locations_ref.len(), 2);
|
||||
|
||||
assert!(locations_ref[0].latitude > 53.6f64);
|
||||
assert!(locations_ref[0].latitude < 53.8f64);
|
||||
assert!(locations_ref[0].longitude > 9.3f64);
|
||||
assert!(locations_ref[0].longitude < 9.5f64);
|
||||
assert!(locations_ref[0].accuracy > 31.9f64);
|
||||
assert!(locations_ref[0].accuracy < 32.1f64);
|
||||
assert_eq!(locations_ref[0].timestamp, 1551906597);
|
||||
|
||||
assert!(locations_ref[1].latitude > 63.6f64);
|
||||
assert!(locations_ref[1].latitude < 63.8f64);
|
||||
assert!(locations_ref[1].longitude > 19.3f64);
|
||||
assert!(locations_ref[1].longitude < 19.5f64);
|
||||
assert!(locations_ref[1].accuracy > 2.4f64);
|
||||
assert!(locations_ref[1].accuracy < 2.6f64);
|
||||
assert_eq!(locations_ref[1].timestamp, 1544739072);
|
||||
}
|
||||
}
|
||||
60
src/log.rs
60
src/log.rs
@@ -1,59 +1,39 @@
|
||||
#[macro_export]
|
||||
macro_rules! info {
|
||||
($ctx:expr, $data1:expr, $msg:expr) => {
|
||||
info!($ctx, $data1, $msg,)
|
||||
($ctx:expr, $msg:expr) => {
|
||||
info!($ctx, $msg,)
|
||||
};
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
|
||||
let formatted = format!($msg, $($args),*);
|
||||
emit_event!($ctx, $crate::Event::Info(formatted));
|
||||
};
|
||||
($ctx:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
|
||||
#[allow(unused_unsafe)]
|
||||
unsafe {
|
||||
let formatted = format!($msg, $($args),*);
|
||||
let formatted_c = std::ffi::CString::new(formatted).unwrap();
|
||||
$ctx.call_cb($crate::constants::Event::INFO, $data1 as libc::uintptr_t,
|
||||
formatted_c.as_ptr() as libc::uintptr_t);
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! warn {
|
||||
($ctx:expr, $data1:expr, $msg:expr) => {
|
||||
warn!($ctx, $data1, $msg,)
|
||||
($ctx:expr, $msg:expr) => {
|
||||
warn!($ctx, $msg,)
|
||||
};
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
|
||||
let formatted = format!($msg, $($args),*);
|
||||
emit_event!($ctx, $crate::Event::Warning(formatted));
|
||||
};
|
||||
($ctx:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
|
||||
#[allow(unused_unsafe)]
|
||||
unsafe {
|
||||
let formatted = format!($msg, $($args),*);
|
||||
let formatted_c = std::ffi::CString::new(formatted).unwrap();
|
||||
$ctx.call_cb($crate::constants::Event::WARNING, $data1 as libc::uintptr_t,
|
||||
formatted_c.as_ptr() as libc::uintptr_t);
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
($ctx:expr, $data1:expr, $msg:expr) => {
|
||||
error!($ctx, $data1, $msg,)
|
||||
($ctx:expr, $msg:expr) => {
|
||||
error!($ctx, $msg,)
|
||||
};
|
||||
($ctx:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
|
||||
#[allow(unused_unsafe)]
|
||||
unsafe {
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
|
||||
let formatted = format!($msg, $($args),*);
|
||||
let formatted_c = std::ffi::CString::new(formatted).unwrap();
|
||||
$ctx.call_cb($crate::constants::Event::ERROR, $data1 as libc::uintptr_t,
|
||||
formatted_c.as_ptr() as libc::uintptr_t);
|
||||
}};
|
||||
emit_event!($ctx, $crate::Event::Error(formatted));
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log_event {
|
||||
($ctx:expr, $data1:expr, $msg:expr) => {
|
||||
log_event!($ctx, $data1, $msg,)
|
||||
macro_rules! emit_event {
|
||||
($ctx:expr, $event:expr) => {
|
||||
$ctx.call_cb($event);
|
||||
};
|
||||
($ctx:expr, $event:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
|
||||
#[allow(unused_unsafe)]
|
||||
unsafe {
|
||||
let formatted = format!($msg, $($args),*);
|
||||
let formatted_c = std::ffi::CString::new(formatted).unwrap();
|
||||
$ctx.call_cb($event, $data1 as libc::uintptr_t,
|
||||
formatted_c.as_ptr() as libc::uintptr_t);
|
||||
}};
|
||||
}
|
||||
|
||||
206
src/login_param.rs
Normal file
206
src/login_param.rs
Normal file
@@ -0,0 +1,206 @@
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct LoginParam {
|
||||
pub addr: String,
|
||||
pub mail_server: String,
|
||||
pub mail_user: String,
|
||||
pub mail_pw: String,
|
||||
pub mail_port: i32,
|
||||
pub send_server: String,
|
||||
pub send_user: String,
|
||||
pub send_pw: String,
|
||||
pub send_port: i32,
|
||||
pub server_flags: i32,
|
||||
}
|
||||
|
||||
impl LoginParam {
|
||||
/// Create a new `LoginParam` with default values.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Read the login parameters from the database.
|
||||
pub fn from_database(context: &Context, prefix: impl AsRef<str>) -> Self {
|
||||
let prefix = prefix.as_ref();
|
||||
let sql = &context.sql;
|
||||
|
||||
let key = format!("{}addr", prefix);
|
||||
let addr = sql
|
||||
.get_config(context, key)
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
let key = format!("{}mail_server", prefix);
|
||||
let mail_server = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}mail_port", prefix);
|
||||
let mail_port = sql.get_config_int(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}mail_user", prefix);
|
||||
let mail_user = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}mail_pw", prefix);
|
||||
let mail_pw = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_server", prefix);
|
||||
let send_server = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_port", prefix);
|
||||
let send_port = sql.get_config_int(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_user", prefix);
|
||||
let send_user = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_pw", prefix);
|
||||
let send_pw = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}server_flags", prefix);
|
||||
let server_flags = sql.get_config_int(context, key).unwrap_or_default();
|
||||
|
||||
LoginParam {
|
||||
addr: addr.to_string(),
|
||||
mail_server,
|
||||
mail_user,
|
||||
mail_pw,
|
||||
mail_port,
|
||||
send_server,
|
||||
send_user,
|
||||
send_pw,
|
||||
send_port,
|
||||
server_flags,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addr_str(&self) -> &str {
|
||||
self.addr.as_str()
|
||||
}
|
||||
|
||||
/// Save this loginparam to the database.
|
||||
pub fn save_to_database(
|
||||
&self,
|
||||
context: &Context,
|
||||
prefix: impl AsRef<str>,
|
||||
) -> Result<(), Error> {
|
||||
let prefix = prefix.as_ref();
|
||||
let sql = &context.sql;
|
||||
|
||||
let key = format!("{}addr", prefix);
|
||||
sql.set_config(context, key, Some(&self.addr))?;
|
||||
|
||||
let key = format!("{}mail_server", prefix);
|
||||
sql.set_config(context, key, Some(&self.mail_server))?;
|
||||
|
||||
let key = format!("{}mail_port", prefix);
|
||||
sql.set_config_int(context, key, self.mail_port)?;
|
||||
|
||||
let key = format!("{}mail_user", prefix);
|
||||
sql.set_config(context, key, Some(&self.mail_user))?;
|
||||
|
||||
let key = format!("{}mail_pw", prefix);
|
||||
sql.set_config(context, key, Some(&self.mail_pw))?;
|
||||
|
||||
let key = format!("{}send_server", prefix);
|
||||
sql.set_config(context, key, Some(&self.send_server))?;
|
||||
|
||||
let key = format!("{}send_port", prefix);
|
||||
sql.set_config_int(context, key, self.send_port)?;
|
||||
|
||||
let key = format!("{}send_user", prefix);
|
||||
sql.set_config(context, key, Some(&self.send_user))?;
|
||||
|
||||
let key = format!("{}send_pw", prefix);
|
||||
sql.set_config(context, key, Some(&self.send_pw))?;
|
||||
|
||||
let key = format!("{}server_flags", prefix);
|
||||
sql.set_config_int(context, key, self.server_flags)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LoginParam {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let unset = "0";
|
||||
let pw = "***";
|
||||
|
||||
let flags_readable = get_readable_flags(self.server_flags);
|
||||
|
||||
write!(
|
||||
f,
|
||||
"{} {}:{}:{}:{} {}:{}:{}:{} {}",
|
||||
unset_empty(&self.addr),
|
||||
unset_empty(&self.mail_user),
|
||||
if !self.mail_pw.is_empty() { pw } else { unset },
|
||||
unset_empty(&self.mail_server),
|
||||
self.mail_port,
|
||||
unset_empty(&self.send_user),
|
||||
if !self.send_pw.is_empty() { pw } else { unset },
|
||||
unset_empty(&self.send_server),
|
||||
self.send_port,
|
||||
flags_readable,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn unset_empty(s: &String) -> Cow<String> {
|
||||
if s.is_empty() {
|
||||
Cow::Owned("unset".to_string())
|
||||
} else {
|
||||
Cow::Borrowed(s)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_readable_flags(flags: i32) -> String {
|
||||
let mut res = String::new();
|
||||
for bit in 0..31 {
|
||||
if 0 != flags & 1 << bit {
|
||||
let mut flag_added = 0;
|
||||
if 1 << bit == 0x2 {
|
||||
res += "OAUTH2 ";
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x4 {
|
||||
res += "AUTH_NORMAL ";
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x100 {
|
||||
res += "IMAP_STARTTLS ";
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x200 {
|
||||
res += "IMAP_SSL ";
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x400 {
|
||||
res += "IMAP_PLAIN ";
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x10000 {
|
||||
res += "SMTP_STARTTLS ";
|
||||
flag_added = 1
|
||||
}
|
||||
if 1 << bit == 0x20000 {
|
||||
res += "SMTP_SSL ";
|
||||
flag_added = 1
|
||||
}
|
||||
if 1 << bit == 0x40000 {
|
||||
res += "SMTP_PLAIN ";
|
||||
flag_added = 1
|
||||
}
|
||||
if 0 == flag_added {
|
||||
res += &format!("{:#0x}", 1 << bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
if res.is_empty() {
|
||||
res += "0";
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
109
src/lot.rs
Normal file
109
src/lot.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
|
||||
/// An object containing a set of values.
|
||||
/// The meaning of the values is defined by the function returning the object.
|
||||
/// Lot objects are created
|
||||
/// eg. by chatlist.get_summary() or dc_msg_get_summary().
|
||||
///
|
||||
/// _Lot_ is used in the meaning _heap_ here.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Lot {
|
||||
pub(crate) text1_meaning: Meaning,
|
||||
pub(crate) text1: Option<String>,
|
||||
pub(crate) text2: Option<String>,
|
||||
pub(crate) timestamp: i64,
|
||||
pub(crate) state: LotState,
|
||||
pub(crate) id: u32,
|
||||
pub(crate) fingerprint: Option<String>,
|
||||
pub(crate) invitenumber: Option<String>,
|
||||
pub(crate) auth: Option<String>,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
|
||||
pub enum Meaning {
|
||||
None = 0,
|
||||
Text1Draft = 1,
|
||||
Text1Username = 2,
|
||||
Text1Self = 3,
|
||||
}
|
||||
|
||||
impl Default for Meaning {
|
||||
fn default() -> Self {
|
||||
Meaning::None
|
||||
}
|
||||
}
|
||||
|
||||
impl Lot {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn get_text1(&self) -> Option<&str> {
|
||||
self.text1.as_ref().map(|s| s.as_str())
|
||||
}
|
||||
|
||||
pub fn get_text2(&self) -> Option<&str> {
|
||||
self.text2.as_ref().map(|s| s.as_str())
|
||||
}
|
||||
|
||||
pub fn get_text1_meaning(&self) -> Meaning {
|
||||
self.text1_meaning
|
||||
}
|
||||
|
||||
pub fn get_state(&self) -> LotState {
|
||||
self.state
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> u32 {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn get_timestamp(&self) -> i64 {
|
||||
self.timestamp
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
|
||||
pub enum LotState {
|
||||
// Default
|
||||
Undefined = 0,
|
||||
|
||||
// Qr States
|
||||
/// id=contact
|
||||
QrAskVerifyContact = 200,
|
||||
/// text1=groupname
|
||||
QrAskVerifyGroup = 202,
|
||||
/// id=contact
|
||||
QrFprOk = 210,
|
||||
/// id=contact
|
||||
QrFprMissmatch = 220,
|
||||
/// test1=formatted fingerprint
|
||||
QrFprWithoutAddr = 230,
|
||||
/// id=contact
|
||||
QrAddr = 320,
|
||||
/// text1=text
|
||||
QrText = 330,
|
||||
/// text1=URL
|
||||
QrUrl = 332,
|
||||
/// text1=error string
|
||||
QrError = 400,
|
||||
|
||||
// Message States
|
||||
MsgInFresh = 10,
|
||||
MsgInNoticed = 13,
|
||||
MsgInSeen = 16,
|
||||
MsgOutPreparing = 18,
|
||||
MsgOutDraft = 19,
|
||||
MsgOutPending = 20,
|
||||
MsgOutFailed = 24,
|
||||
MsgOutDelivered = 26,
|
||||
MsgOutMdnRcvd = 28,
|
||||
}
|
||||
|
||||
impl Default for LotState {
|
||||
fn default() -> Self {
|
||||
LotState::Undefined
|
||||
}
|
||||
}
|
||||
1273
src/message.rs
Normal file
1273
src/message.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,7 @@ const OAUTH2_YANDEX: Oauth2 = Oauth2 {
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Oauth2 {
|
||||
struct Oauth2 {
|
||||
client_id: &'static str,
|
||||
get_code: &'static str,
|
||||
init_token: &'static str,
|
||||
@@ -74,14 +74,14 @@ pub fn dc_get_oauth2_access_token(
|
||||
context: &Context,
|
||||
addr: impl AsRef<str>,
|
||||
code: impl AsRef<str>,
|
||||
flags: usize,
|
||||
regenerate: bool,
|
||||
) -> Option<String> {
|
||||
if let Some(oauth2) = Oauth2::from_address(addr) {
|
||||
let lock = context.oauth2_critical.clone();
|
||||
let _l = lock.lock().unwrap();
|
||||
|
||||
// read generated token
|
||||
if 0 == flags & 0x1 && !is_expired(context) {
|
||||
if !regenerate && !is_expired(context) {
|
||||
let access_token = context.sql.get_config(context, "oauth2_access_token");
|
||||
if access_token.is_some() {
|
||||
// success
|
||||
@@ -97,10 +97,7 @@ pub fn dc_get_oauth2_access_token(
|
||||
|
||||
let (redirect_uri, token_url, update_redirect_uri_on_success) =
|
||||
if refresh_token.is_none() || refresh_token_for != code.as_ref() {
|
||||
info!(
|
||||
context,
|
||||
0, "Generate OAuth2 refresh_token and access_token...",
|
||||
);
|
||||
info!(context, "Generate OAuth2 refresh_token and access_token...",);
|
||||
(
|
||||
context
|
||||
.sql
|
||||
@@ -112,7 +109,7 @@ pub fn dc_get_oauth2_access_token(
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
0, "Regenerate OAuth2 access_token by refresh_token...",
|
||||
"Regenerate OAuth2 access_token by refresh_token...",
|
||||
);
|
||||
(
|
||||
context
|
||||
@@ -134,7 +131,7 @@ pub fn dc_get_oauth2_access_token(
|
||||
if response.is_err() {
|
||||
warn!(
|
||||
context,
|
||||
0, "Error calling OAuth2 at {}: {:?}", token_url, response
|
||||
"Error calling OAuth2 at {}: {:?}", token_url, response
|
||||
);
|
||||
return None;
|
||||
}
|
||||
@@ -142,7 +139,6 @@ pub fn dc_get_oauth2_access_token(
|
||||
if !response.status().is_success() {
|
||||
warn!(
|
||||
context,
|
||||
0,
|
||||
"Error calling OAuth2 at {}: {:?}",
|
||||
token_url,
|
||||
response.status()
|
||||
@@ -154,7 +150,7 @@ pub fn dc_get_oauth2_access_token(
|
||||
if parsed.is_err() {
|
||||
warn!(
|
||||
context,
|
||||
0, "Failed to parse OAuth2 JSON response from {}: error: {:?}", token_url, parsed
|
||||
"Failed to parse OAuth2 JSON response from {}: error: {:?}", token_url, parsed
|
||||
);
|
||||
return None;
|
||||
}
|
||||
@@ -195,12 +191,12 @@ pub fn dc_get_oauth2_access_token(
|
||||
.ok();
|
||||
}
|
||||
} else {
|
||||
warn!(context, 0, "Failed to find OAuth2 access token");
|
||||
warn!(context, "Failed to find OAuth2 access token");
|
||||
}
|
||||
|
||||
response.access_token
|
||||
} else {
|
||||
warn!(context, 0, "Internal OAuth2 error: 2");
|
||||
warn!(context, "Internal OAuth2 error: 2");
|
||||
|
||||
None
|
||||
}
|
||||
@@ -220,12 +216,13 @@ pub fn dc_get_oauth2_addr(
|
||||
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);
|
||||
if addr_out.is_none() {
|
||||
// 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)
|
||||
} else {
|
||||
None
|
||||
@@ -268,17 +265,12 @@ impl Oauth2 {
|
||||
// }
|
||||
let response = reqwest::Client::new().get(&userinfo_url).send();
|
||||
if response.is_err() {
|
||||
warn!(context, 0, "Error getting userinfo: {:?}", response);
|
||||
warn!(context, "Error getting userinfo: {:?}", response);
|
||||
return None;
|
||||
}
|
||||
let mut response = response.unwrap();
|
||||
if !response.status().is_success() {
|
||||
warn!(
|
||||
context,
|
||||
0,
|
||||
"Error getting userinfo: {:?}",
|
||||
response.status()
|
||||
);
|
||||
warn!(context, "Error getting userinfo: {:?}", response.status());
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -286,19 +278,19 @@ impl Oauth2 {
|
||||
if parsed.is_err() {
|
||||
warn!(
|
||||
context,
|
||||
0, "Failed to parse userinfo JSON response: {:?}", parsed
|
||||
"Failed to parse userinfo JSON response: {:?}", parsed
|
||||
);
|
||||
return None;
|
||||
}
|
||||
if let Ok(response) = parsed {
|
||||
let addr = response.get("email");
|
||||
if addr.is_none() {
|
||||
warn!(context, 0, "E-mail missing in userinfo.");
|
||||
warn!(context, "E-mail missing in userinfo.");
|
||||
}
|
||||
|
||||
addr.map(|addr| addr.to_string())
|
||||
} else {
|
||||
warn!(context, 0, "Failed to parse userinfo.");
|
||||
warn!(context, "Failed to parse userinfo.");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
20
src/param.rs
20
src/param.rs
@@ -4,6 +4,7 @@ use std::str;
|
||||
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
use crate::dc_mimeparser::SystemMessage;
|
||||
use crate::error;
|
||||
|
||||
/// Available param keys.
|
||||
@@ -94,7 +95,7 @@ impl fmt::Display for Params {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for (i, (key, value)) in self.inner.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, "\n")?;
|
||||
writeln!(f)?;
|
||||
}
|
||||
write!(f, "{}={}", *key as u8 as char, value)?;
|
||||
}
|
||||
@@ -113,7 +114,7 @@ impl str::FromStr for Params {
|
||||
continue;
|
||||
}
|
||||
// TODO: probably nicer using a regex
|
||||
ensure!(pair.len() > 2, "Invalid key pair: '{}'", pair);
|
||||
ensure!(pair.len() > 1, "Invalid key pair: '{}'", pair);
|
||||
let mut split = pair.splitn(2, '=');
|
||||
let key = split.next();
|
||||
let value = split.next();
|
||||
@@ -178,6 +179,13 @@ impl Params {
|
||||
self.get(key).and_then(|s| s.parse().ok())
|
||||
}
|
||||
|
||||
/// Get the parameter behind `Param::Cmd` interpreted as `SystemMessage`.
|
||||
pub fn get_cmd(&self) -> SystemMessage {
|
||||
self.get_int(Param::Cmd)
|
||||
.and_then(SystemMessage::from_i32)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Get the given parameter and parse as `f64`.
|
||||
pub fn get_float(&self, key: Param) -> Option<f64> {
|
||||
self.get(key).and_then(|s| s.parse().ok())
|
||||
@@ -235,4 +243,12 @@ mod tests {
|
||||
assert!(p1.is_empty());
|
||||
assert_eq!(p1.len(), 0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_regression() {
|
||||
let p1: Params = "a=cli%40deltachat.de\nn=\ni=TbnwJ6lSvD5\ns=0ejvbdFSQxB"
|
||||
.parse()
|
||||
.unwrap();
|
||||
assert_eq!(p1.get(Param::Forwarded).unwrap(), "cli%40deltachat.de");
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user