Compare commits
351 Commits
ci-clean-g
...
provider_j
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a6f2529b1 | ||
|
|
e25b409c7e | ||
|
|
5479877f65 | ||
|
|
7da5fe2726 | ||
|
|
3afa37c6ea | ||
|
|
8c0ce38301 | ||
|
|
cc6aa3209c | ||
|
|
76a86763dd | ||
|
|
09fb039528 | ||
|
|
174d3300c4 | ||
|
|
8b57ce1792 | ||
|
|
6c14e429eb | ||
|
|
5f200c6bc3 | ||
|
|
d52347ee1d | ||
|
|
d0d9aa4400 | ||
|
|
c3d909c818 | ||
|
|
d9e718b0e0 | ||
|
|
a7b55edb9b | ||
|
|
000479d55e | ||
|
|
7ef22f2940 | ||
|
|
a242fcfd2c | ||
|
|
73c21ae0a9 | ||
|
|
2398454838 | ||
|
|
c1ba5a30c5 | ||
|
|
2c0f847d3e | ||
|
|
7d5e95f013 | ||
|
|
9000342de8 | ||
|
|
61b47aa0de | ||
|
|
22f5c5fb74 | ||
|
|
801162d7be | ||
|
|
f81f3fb060 | ||
|
|
15792d8426 | ||
|
|
d7f345eef8 | ||
|
|
e3031462c1 | ||
|
|
47d14271ab | ||
|
|
2bf9fd6cbc | ||
|
|
19e716b522 | ||
|
|
1ee15942cc | ||
|
|
898e641256 | ||
|
|
cda4ccff2a | ||
|
|
ba274482f7 | ||
|
|
435f734d60 | ||
|
|
a5f949c4e2 | ||
|
|
9fc556864e | ||
|
|
a0645dc713 | ||
|
|
5893cd309d | ||
|
|
09c7ab1ee6 | ||
|
|
4bacae3711 | ||
|
|
8d3e536582 | ||
|
|
697cc0a79b | ||
|
|
a34ed5c02a | ||
|
|
256bb01606 | ||
|
|
1de535363d | ||
|
|
24d9011939 | ||
|
|
6284bdc98f | ||
|
|
e7351b1bb8 | ||
|
|
b3ee89c6e5 | ||
|
|
3f49492ccf | ||
|
|
ad700b45d0 | ||
|
|
74923b4575 | ||
|
|
81199e7ee0 | ||
|
|
ccca1b0bea | ||
|
|
ba1ced5e08 | ||
|
|
558466d506 | ||
|
|
b424ef9ebb | ||
|
|
69f5fd86a4 | ||
|
|
f9ba9ae912 | ||
|
|
b33fec6236 | ||
|
|
70e12485aa | ||
|
|
b2b7674b59 | ||
|
|
8fa175f36d | ||
|
|
95fbc904d1 | ||
|
|
ce37a8dda2 | ||
|
|
e0499c9552 | ||
|
|
4825f2510f | ||
|
|
7a12134795 | ||
|
|
66adfa074b | ||
|
|
9d201eb9c6 | ||
|
|
789fc0a7e0 | ||
|
|
9e309132f8 | ||
|
|
88923173c2 | ||
|
|
7b55863418 | ||
|
|
38d5fad4e5 | ||
|
|
4d1357568b | ||
|
|
ccc190f991 | ||
|
|
0a9f880fb4 | ||
|
|
b8faf54f0b | ||
|
|
888507f7ba | ||
|
|
29866092de | ||
|
|
36fc2c83f2 | ||
|
|
5e777b3c51 | ||
|
|
5690c48863 | ||
|
|
8ab3363097 | ||
|
|
f6861ca5f5 | ||
|
|
3bb58be2b5 | ||
|
|
409c96e571 | ||
|
|
d681fa6cba | ||
|
|
7c3d8356c4 | ||
|
|
a8842da50a | ||
|
|
ff29b84146 | ||
|
|
ab12a4eb39 | ||
|
|
c3fd0889e2 | ||
|
|
c62532a665 | ||
|
|
ca63d6ba1c | ||
|
|
a1f496b019 | ||
|
|
da421438cd | ||
|
|
7f723ef2bf | ||
|
|
3cf39dace0 | ||
|
|
021ad4f12c | ||
|
|
36edf447e7 | ||
|
|
ea2273aef4 | ||
|
|
251aa22c4c | ||
|
|
541710147a | ||
|
|
a197ca4cf7 | ||
|
|
775c36bb65 | ||
|
|
1953b95c57 | ||
|
|
542a33952f | ||
|
|
1819712667 | ||
|
|
69a596fdff | ||
|
|
4f88b93495 | ||
|
|
a388052e2a | ||
|
|
8b81ea3b6f | ||
|
|
b913de5928 | ||
|
|
4e551cde66 | ||
|
|
b681cbd47f | ||
|
|
551253b4e0 | ||
|
|
4ad9166b5a | ||
|
|
8487255c33 | ||
|
|
2792d4ea1e | ||
|
|
95180a850f | ||
|
|
56ee7a0abd | ||
|
|
d0a04be825 | ||
|
|
2cbf287998 | ||
|
|
054cf98754 | ||
|
|
a95fbfe271 | ||
|
|
17ce02a87c | ||
|
|
4dc5e0378f | ||
|
|
c33797ff84 | ||
|
|
f242b40d0a | ||
|
|
5f916f5a9c | ||
|
|
6edb525540 | ||
|
|
0c04d5b2ab | ||
|
|
301852fd87 | ||
|
|
0e8df7d633 | ||
|
|
e4155e0e16 | ||
|
|
2c4dbe6e68 | ||
|
|
a99b96e36e | ||
|
|
0889467c7b | ||
|
|
d141e228de | ||
|
|
9b15c42801 | ||
|
|
a781b631e1 | ||
|
|
08af5c8e09 | ||
|
|
93e8cca02f | ||
|
|
a8e9a1fbe5 | ||
|
|
54eb30f3db | ||
|
|
c08a1adc9b | ||
|
|
cd951ad396 | ||
|
|
33793d878b | ||
|
|
357955015d | ||
|
|
d6d94adab0 | ||
|
|
099cc9f727 | ||
|
|
61f8d6f171 | ||
|
|
a7af4685f1 | ||
|
|
2cebed4f77 | ||
|
|
3f2a371599 | ||
|
|
a3ca3d9179 | ||
|
|
86ace1a4af | ||
|
|
1abdf62045 | ||
|
|
9e2a96675d | ||
|
|
f32876708a | ||
|
|
84e0e7e020 | ||
|
|
38cddff6e9 | ||
|
|
f1aba8115b | ||
|
|
0db5ff55fd | ||
|
|
7421b0de6c | ||
|
|
6c275c30a7 | ||
|
|
91f1d793e9 | ||
|
|
cddfd04a7f | ||
|
|
7fa94c33bf | ||
|
|
30dd20dc7b | ||
|
|
339c0d3dc7 | ||
|
|
2304d63bb3 | ||
|
|
36014f9fe5 | ||
|
|
351383dfa4 | ||
|
|
defad94f5a | ||
|
|
a0517478e3 | ||
|
|
7f4e68f21c | ||
|
|
c0a425c26d | ||
|
|
e756859b16 | ||
|
|
174bc017ad | ||
|
|
4a9fb0212f | ||
|
|
212848409f | ||
|
|
e45ee0eb81 | ||
|
|
3b8e37de58 | ||
|
|
c2e8cc9bd6 | ||
|
|
ec81d29580 | ||
|
|
c4de0f3b17 | ||
|
|
965d41990e | ||
|
|
d96dba336b | ||
|
|
a7e1b4653e | ||
|
|
e38b42bc21 | ||
|
|
36bd502292 | ||
|
|
594bf3dfc8 | ||
|
|
6d30ccfc63 | ||
|
|
1b79f513a3 | ||
|
|
74825a0f57 | ||
|
|
4a23d12df2 | ||
|
|
7f117574ab | ||
|
|
f91474c2f8 | ||
|
|
2a081aac2b | ||
|
|
9b10f31fb3 | ||
|
|
63ad7b8d34 | ||
|
|
86baaab2e9 | ||
|
|
3e66d23367 | ||
|
|
609b5588fa | ||
|
|
798072b8ba | ||
|
|
d950a58613 | ||
|
|
4c68e6fe41 | ||
|
|
914ce77b50 | ||
|
|
d09989a4ed | ||
|
|
6999a4e3a9 | ||
|
|
069541f374 | ||
|
|
49b9b28c99 | ||
|
|
b274482125 | ||
|
|
b766a55b0a | ||
|
|
b8057b7ddb | ||
|
|
98266f47d6 | ||
|
|
7b83979e10 | ||
|
|
79cdcca2d2 | ||
|
|
4c0d00640b | ||
|
|
ad87b7c4a5 | ||
|
|
74f36b264b | ||
|
|
2df43b6c5d | ||
|
|
73f80624b2 | ||
|
|
fe2a4b7a4a | ||
|
|
affdf7241f | ||
|
|
1c5a3e6698 | ||
|
|
db88212a64 | ||
|
|
43074464ac | ||
|
|
1e7afa9da0 | ||
|
|
e985887739 | ||
|
|
d5287256e0 | ||
|
|
6bb2adaf45 | ||
|
|
64fcd56998 | ||
|
|
7d3a56a870 | ||
|
|
48dd3b8506 | ||
|
|
734bbff04d | ||
|
|
5adde12dff | ||
|
|
5079e15401 | ||
|
|
9196ed1e9f | ||
|
|
c69a2406ad | ||
|
|
47197aa495 | ||
|
|
dbd6303829 | ||
|
|
1f8d2531cc | ||
|
|
d2de2aef07 | ||
|
|
e22b4e8430 | ||
|
|
af0b7e4701 | ||
|
|
acef386403 | ||
|
|
15ba9b6295 | ||
|
|
2f47cf0be5 | ||
|
|
774106fc26 | ||
|
|
1b0ff9f5be | ||
|
|
dfaa6895ae | ||
|
|
cb52a299cc | ||
|
|
0ac0851ef3 | ||
|
|
c0f177548a | ||
|
|
8923c9e5af | ||
|
|
e609ebe3f9 | ||
|
|
8617c3d359 | ||
|
|
61e97e5e6f | ||
|
|
521e44bd54 | ||
|
|
18e91c480b | ||
|
|
9d2e6e1c31 | ||
|
|
eb02100d68 | ||
|
|
ee18d60644 | ||
|
|
612600278a | ||
|
|
ea8adf39c2 | ||
|
|
f7f61e0f85 | ||
|
|
a7bb249070 | ||
|
|
dfe7dfcfd3 | ||
|
|
7223a36a71 | ||
|
|
2423d197cd | ||
|
|
2582791501 | ||
|
|
3a08c92433 | ||
|
|
694d8fd6fb | ||
|
|
369bb9166e | ||
|
|
5f1e5ef206 | ||
|
|
5d04fd0d97 | ||
|
|
d05b5e40df | ||
|
|
0dd436ac54 | ||
|
|
d5359fb9ba | ||
|
|
72c29aca6a | ||
|
|
32216a334d | ||
|
|
603d55114b | ||
|
|
9d18db9cae | ||
|
|
d14c6ea202 | ||
|
|
7be5fe925a | ||
|
|
8f43d7fa34 | ||
|
|
b6e9bcee3c | ||
|
|
2dbef704e9 | ||
|
|
74a4691f29 | ||
|
|
084a87ed61 | ||
|
|
657b53ae0b | ||
|
|
17cb1226c6 | ||
|
|
02e281e465 | ||
|
|
4e6d0c9c69 | ||
|
|
81d069209c | ||
|
|
63e3c82f9d | ||
|
|
e8f2f7b24e | ||
|
|
b7f7e607c1 | ||
|
|
ac4108b05b | ||
|
|
14287b12ae | ||
|
|
20ce5f6967 | ||
|
|
1a296cbd4e | ||
|
|
642276c90c | ||
|
|
e4b2fd87de | ||
|
|
dacde72456 | ||
|
|
7e66af05ff | ||
|
|
b6bb5b79af | ||
|
|
f0486eb820 | ||
|
|
cdc2847b96 | ||
|
|
1d996d9ed9 | ||
|
|
e06ac87c0d | ||
|
|
0c19fcd79f | ||
|
|
02fe3d1b99 | ||
|
|
95b90a59dc | ||
|
|
fe7852b64e | ||
|
|
f87cb4231c | ||
|
|
7484fb6120 | ||
|
|
430d4e5f6e | ||
|
|
6f6791c1b5 | ||
|
|
bc11ae7245 | ||
|
|
de228bdb4b | ||
|
|
25fb199ba0 | ||
|
|
d0795f5770 | ||
|
|
fbb8c8e80c | ||
|
|
76610f1e72 | ||
|
|
618d74cd67 | ||
|
|
59700cb477 | ||
|
|
fc1a136448 | ||
|
|
42ef43bdf6 | ||
|
|
f53b3c2e7b | ||
|
|
22a0e3fe9c | ||
|
|
e0601bab3d | ||
|
|
69369b02ea | ||
|
|
966b9fac49 | ||
|
|
e2a86dd803 | ||
|
|
1b88cfd053 | ||
|
|
6412adcfa7 | ||
|
|
76de8f55f2 | ||
|
|
ae43b83162 |
@@ -36,12 +36,6 @@ jobs:
|
||||
executor: default
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Update submodules
|
||||
command: git submodule update --init --recursive
|
||||
- run:
|
||||
name: Calculate dependencies
|
||||
command: cargo generate-lockfile
|
||||
- restore_cache:
|
||||
keys:
|
||||
- cargo-v3-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||
@@ -49,7 +43,6 @@ jobs:
|
||||
- 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
|
||||
@@ -91,7 +84,6 @@ jobs:
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
- run: rustup install $(cat rust-toolchain)
|
||||
- run: rustup default $(cat rust-toolchain)
|
||||
- run: cargo update
|
||||
- run: cargo fetch
|
||||
- run:
|
||||
name: Test
|
||||
@@ -187,7 +179,7 @@ jobs:
|
||||
- *restore-cache
|
||||
- run:
|
||||
name: Run cargo clippy
|
||||
command: cargo clippy --all
|
||||
command: cargo clippy
|
||||
|
||||
|
||||
workflows:
|
||||
@@ -195,7 +187,7 @@ workflows:
|
||||
|
||||
test:
|
||||
jobs:
|
||||
- cargo_fetch
|
||||
# - cargo_fetch
|
||||
|
||||
- remote_tests_rust
|
||||
|
||||
@@ -205,12 +197,12 @@ workflows:
|
||||
# requires:
|
||||
# - build_test_docs_wheel
|
||||
# - build_doxygen
|
||||
- rustfmt:
|
||||
requires:
|
||||
- cargo_fetch
|
||||
- clippy:
|
||||
requires:
|
||||
- cargo_fetch
|
||||
# - rustfmt:
|
||||
# requires:
|
||||
# - cargo_fetch
|
||||
# - clippy:
|
||||
# requires:
|
||||
# - cargo_fetch
|
||||
|
||||
- build_doxygen
|
||||
|
||||
|
||||
47
.github/workflows/code-quality.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
on: push
|
||||
name: Code Quality
|
||||
jobs:
|
||||
|
||||
check:
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly-2019-11-06
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
|
||||
fmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly-2019-11-06
|
||||
override: true
|
||||
- run: rustup component add rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
run_clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly-2019-11-06
|
||||
components: clippy
|
||||
override: true
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --all-features
|
||||
2
.gitignore
vendored
@@ -23,3 +23,5 @@ python/liveconfig*
|
||||
# ignore doxgen generated files
|
||||
deltachat-ffi/html
|
||||
deltachat-ffi/xml
|
||||
|
||||
.rsynclist
|
||||
|
||||
202
CHANGELOG.md
@@ -1,5 +1,207 @@
|
||||
# Changelog
|
||||
|
||||
## 1.0.0-beta.20
|
||||
|
||||
- #1074 fix OAUTH2/gmail
|
||||
- #1072 fix group members not appearing in contact list
|
||||
- #1071 never block interrupt_idle (thus hopefully also not on maybe_network())
|
||||
- #1069 reduce smtp-timeout to 30 seconds
|
||||
- #1066 #1065 avoid unwrap in dehtml, make literals more readable
|
||||
|
||||
## 1.0.0-beta.19
|
||||
|
||||
- #1058 timeout smtp-send if it doesn't complete in 15 minutes
|
||||
|
||||
- #1059 trim down logging
|
||||
|
||||
## 1.0.0-beta.18
|
||||
|
||||
- #1056 avoid panicking when we couldn't read imap-server's greeting
|
||||
message
|
||||
|
||||
- #1055 avoid panicking when we don't have a selected folder
|
||||
|
||||
- #1052 #1049 #1051 improve logging to add thread-id/name and
|
||||
file/lineno to each info/warn message.
|
||||
|
||||
- #1050 allow python bindings to initialize Account with "os_name".
|
||||
|
||||
|
||||
## 1.0.0-beta.17
|
||||
|
||||
- #1044 implement avatar recoding to 192x192 in core to keep file sizes small.
|
||||
|
||||
- #1024 fix #1021 SQL/injection malformed Chat-Group-Name breakage
|
||||
|
||||
- #1036 fix smtp crash by pulling in a fixed async-smtp
|
||||
|
||||
- #1039 fix read-receipts appearing as normal messages when you change
|
||||
MDN settings
|
||||
|
||||
- #1040 do not panic on SystemTimeDifference
|
||||
|
||||
- #1043 avoid potential crashes in malformed From/Chat-Disposition... headers
|
||||
|
||||
- #1045 #1041 #1038 #1035 #1034 #1029 #1025 various cleanups and doc
|
||||
improvments
|
||||
|
||||
## 1.0.0-beta.16
|
||||
|
||||
- alleviate login problems with providers which only
|
||||
support RSA1024 keys by switching back from Rustls
|
||||
to native-tls, by using the new async-email/async-native-tls
|
||||
crate from @dignifiedquire. thanks @link2xt.
|
||||
|
||||
- introduce per-contact profile images to send out
|
||||
own profile image heuristically, and fix sending
|
||||
out of profile images in "in-prepare" groups.
|
||||
this also extends the Chat-spec that is maintained
|
||||
in core to specify Chat-Group-Image and Chat-Group-Avatar
|
||||
headers. thanks @r10s and @hpk42.
|
||||
|
||||
- fix merging of protected headers from the encrypted
|
||||
to the unencrypted parts, now not happening recursively
|
||||
anymore. thanks @hpk and @r10s
|
||||
|
||||
- fix/optimize autocrypt gossip headers to only get
|
||||
sent when there are more than 2 people in a chat.
|
||||
thanks @link2xt
|
||||
|
||||
- fix displayname to use the authenticated name
|
||||
when available (displayname as coming from contacts
|
||||
themselves). thanks @simon-laux
|
||||
|
||||
- introduce preliminary support for offline autoconfig
|
||||
for nauta provider. thanks @hpk42 @r10s
|
||||
|
||||
## 1.0.0-beta.15
|
||||
|
||||
- fix #994 attachment appeared doubled in chats (and where actually
|
||||
downloaded after smtp-send). @hpk42
|
||||
|
||||
## 1.0.0-beta.14
|
||||
|
||||
- fix packaging issue with our rust-email fork, now we are tracking
|
||||
master again there. hpk42
|
||||
|
||||
## 1.0.0-beta.13
|
||||
|
||||
- fix #976 -- unicode-issues in display-name of email addresses. @hpk42
|
||||
|
||||
- fix #985 group add/remove member bugs resulting in broken groups. @hpk42
|
||||
|
||||
- fix hanging IMAP connections -- we now detect with a 15second timeout
|
||||
if we cannot terminate the IDLE IMAP protocol. @hpk42 @link2xt
|
||||
|
||||
- fix incoming multipart/mixed containing html, to show up as
|
||||
attachments again. Fixes usage for simplebot which sends html
|
||||
files for users to interact with the bot. @adbenitez @hpk42
|
||||
|
||||
- refinements to internal autocrypt-handling code, do not send
|
||||
prefer-encrypt=nopreference as it is the default if no attribute
|
||||
is present. @linkxt
|
||||
|
||||
- simplify, modularize and rustify several parts
|
||||
of dc-core (general WIP). @link2xt @flub @hpk42 @r10s
|
||||
|
||||
- use async-email/async-smtp to handle SMTP connections, might
|
||||
fix connection/reconnection issues. @link2xt
|
||||
|
||||
- more tests and refinements for dealing with blobstorage @flub @hpk42
|
||||
|
||||
- use a dedicated build-server for CI testing of core PRs
|
||||
|
||||
|
||||
## 1.0.0-beta.12
|
||||
|
||||
- fix python bindings to use core for copying attachments to blobdir
|
||||
and fix core to actually do it. @hpk42
|
||||
|
||||
## 1.0.0-beta.11
|
||||
|
||||
- trigger reconnect more often on imap error states. Should fix an
|
||||
issue observed when trying to empty a folder. @hpk42
|
||||
|
||||
- un-split qr tests: we fixed qr-securejoin protocol flakyness
|
||||
last weeks. @hpk42
|
||||
|
||||
## 1.0.0-beta.10
|
||||
|
||||
- fix grpid-determination from in-reply-to and references headers. @hpk42
|
||||
|
||||
- only send Autocrypt-gossip headers on encrypted messages. @dignifiedquire
|
||||
|
||||
- fix reply-to-encrypted message to also be encrypted. @hpk42
|
||||
|
||||
- remove last unsafe code from dc_receive_imf :) @hpk42
|
||||
|
||||
- add experimental new dc_chat_get_info_json FFI/API so that desktop devs
|
||||
can play with using it. @jikstra
|
||||
|
||||
- fix encoding of subjects and attachment-filenames @hpk42
|
||||
@dignifiedquire .
|
||||
|
||||
## 1.0.0-beta.9
|
||||
|
||||
- historic: we now use the mailparse crate and lettre-email to generate mime
|
||||
messages. This got rid of mmime completely, the C2rust generated port of the libetpan
|
||||
mime-parse -- IOW 22KLocs of cumbersome code removed! see
|
||||
https://github.com/deltachat/deltachat-core-rust/pull/904#issuecomment-561163330
|
||||
many thanks @dignifiedquire for making everybody's life easier
|
||||
and @jonhoo (from rust-imap fame) for suggesting to use the mailparse crate :)
|
||||
|
||||
- lots of improvements and better error handling in many rust modules
|
||||
thanks @link2xt @flub @r10s, @hpk42 and @dignifiedquire
|
||||
|
||||
- @r10s introduced a new device chat which has an initial
|
||||
welcome message. See
|
||||
https://c.delta.chat/classdc__context__t.html#a1a2aad98bd23c1d21ee42374e241f389
|
||||
for the main new FFI-API.
|
||||
|
||||
- fix moving self-sent messages, thanks @r10s, @flub, @hpk42
|
||||
|
||||
- fix flakyness/sometimes-failing verified/join-protocols,
|
||||
thanks @flub, @r10s, @hpk42
|
||||
|
||||
- fix reply-to-encrypted message to keep encryption
|
||||
|
||||
- new DC_EVENT_SECUREJOIN_MEMBER_ADDED event
|
||||
|
||||
- many little fixes and rustifications (@link2xt, @flub, @hpk42)
|
||||
|
||||
|
||||
## 1.0.0-beta.8
|
||||
|
||||
- now uses async-email/async-imap as the new base
|
||||
which makes imap-idle interruptible and thus fixes
|
||||
several issues around the imap thread being in zombie state .
|
||||
thanks @dignifiedquire, @hpk42 and @link2xt.
|
||||
|
||||
- fixes imap-protocol parsing bugs that lead to infinitely
|
||||
repeated crashing while trying to receive messages with
|
||||
a subjec that contained non-utf8. thanks @link2xt
|
||||
|
||||
- fixed logic to find encryption subkey -- previously
|
||||
delta chat would use the primary key for encryption
|
||||
(which works with RSA but not ECC). thanks @link2xt
|
||||
|
||||
- introduce a new device chat where core and UIs can
|
||||
add "device" messages. Android uses it for an initial
|
||||
welcome message. thanks @r10s
|
||||
|
||||
- fix time smearing (when two message are virtually send
|
||||
in the same second, there would be misbehaviour because
|
||||
we didn't persist smeared time). thanks @r10s
|
||||
|
||||
- fix double-dotted extensions like .html.zip or .tar.gz
|
||||
to not mangle them when creating blobfiles. thanks @flub
|
||||
|
||||
- fix backup/exports where the wrong sql file would be modified,
|
||||
leading to problems when exporting twice. thanks @hpk42
|
||||
|
||||
- several other little fixes and improvements
|
||||
|
||||
|
||||
## 1.0.0-beta.7
|
||||
|
||||
- fix location-streaming #782
|
||||
|
||||
1411
Cargo.lock
generated
40
Cargo.toml
@@ -1,28 +1,32 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.0.0-beta.7"
|
||||
version = "1.0.0-beta.20"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
license = "MPL"
|
||||
|
||||
[dependencies]
|
||||
deltachat_derive = { path = "./deltachat_derive" }
|
||||
mmime = { version = "0.1.2", path = "./mmime" }
|
||||
|
||||
libc = "0.2.51"
|
||||
pgp = { git = "https://github.com/rpgp/rpgp", branch = "master", default-features = false }
|
||||
pgp = { version = "0.4.0", default-features = false }
|
||||
hex = "0.4.0"
|
||||
sha2 = "0.8.0"
|
||||
rand = "0.6.5"
|
||||
smallvec = "0.6.9"
|
||||
reqwest = { version = "0.9.15", default-features = false, features = ["rustls-tls"] }
|
||||
num-derive = "0.2.5"
|
||||
rand = "0.7.0"
|
||||
smallvec = "1.0.0"
|
||||
reqwest = { version = "0.9.15" }
|
||||
num-derive = "0.3.0"
|
||||
num-traits = "0.2.6"
|
||||
lettre = { git = "https://github.com/deltachat/lettre", branch = "feat/rustls" }
|
||||
async-imap = "0.1.1"
|
||||
async-tls = "0.6"
|
||||
async-smtp = { git = "https://github.com/async-email/async-smtp" }
|
||||
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
|
||||
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "native_tls" }
|
||||
|
||||
# XXX newer commits of async-imap lead to import-export tests hanging
|
||||
async-imap = { git = "https://github.com/async-email/async-imap", branch = "dcc-stable" }
|
||||
|
||||
async-native-tls = "0.1.1"
|
||||
async-std = { version = "1.0", features = ["unstable"] }
|
||||
base64 = "0.10"
|
||||
base64 = "0.11"
|
||||
charset = "0.1"
|
||||
percent-encoding = "2.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
@@ -30,6 +34,7 @@ serde_json = "1.0"
|
||||
chrono = "0.4.6"
|
||||
failure = "0.1.5"
|
||||
failure_derive = "0.1.5"
|
||||
indexmap = "1.3.0"
|
||||
# TODO: make optional
|
||||
rustyline = "4.1.0"
|
||||
lazy_static = "1.4.0"
|
||||
@@ -44,16 +49,16 @@ backtrace = "0.3.33"
|
||||
byteorder = "1.3.1"
|
||||
itertools = "0.8.0"
|
||||
image-meta = "0.1.0"
|
||||
quick-xml = "0.15.0"
|
||||
quick-xml = "0.17.1"
|
||||
escaper = "0.1.0"
|
||||
bitflags = "1.1.0"
|
||||
jetscii = "0.4.4"
|
||||
debug_stub_derive = "0.3.0"
|
||||
sanitize-filename = "0.2.1"
|
||||
stop-token = { version = "0.1.1", features = ["unstable"] }
|
||||
rustls = "0.16.0"
|
||||
webpki-roots = "0.18.0"
|
||||
webpki = "0.21.0"
|
||||
mailparse = "0.10.1"
|
||||
encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" }
|
||||
native-tls = "0.2.3"
|
||||
image = "0.22.3"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.0"
|
||||
@@ -65,7 +70,6 @@ proptest = "0.9.4"
|
||||
members = [
|
||||
"deltachat-ffi",
|
||||
"deltachat_derive",
|
||||
"mmime",
|
||||
]
|
||||
|
||||
[[example]]
|
||||
@@ -79,6 +83,6 @@ path = "examples/repl/main.rs"
|
||||
|
||||
[features]
|
||||
default = ["nightly", "ringbuf"]
|
||||
vendored = []
|
||||
vendored = ["native-tls/vendored", "reqwest/default-tls-vendored"]
|
||||
nightly = ["pgp/nightly"]
|
||||
ringbuf = ["pgp/ringbuf"]
|
||||
|
||||
11
README.md
@@ -14,7 +14,7 @@ curl https://sh.rustup.rs -sSf | sh
|
||||
|
||||
## Using the CLI client
|
||||
|
||||
Compile and run Delta Chat Core using `cargo`:
|
||||
Compile and run Delta Chat Core command line utility, using `cargo`:
|
||||
|
||||
```
|
||||
cargo run --example repl -- /path/to/db
|
||||
@@ -87,6 +87,15 @@ $ cargo test --all
|
||||
$ cargo build -p deltachat_ffi --release
|
||||
```
|
||||
|
||||
## Debugging environment variables
|
||||
|
||||
- `DCC_IMAP_DEBUG`: if set IMAP protocol commands and responses will be
|
||||
printed
|
||||
|
||||
- `DCC_MIME_DEBUG`: if set outgoing and incoming message will be printed
|
||||
|
||||
|
||||
|
||||
### Expensive tests
|
||||
|
||||
Some tests are expensive and marked with `#[ignore]`, to run these
|
||||
|
||||
@@ -8,7 +8,6 @@ install:
|
||||
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
||||
- rustc -vV
|
||||
- cargo -vV
|
||||
- cargo update
|
||||
|
||||
build: false
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 4.8 KiB |
@@ -5,66 +5,44 @@
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:version="1.0beta1 (32d4812, 2019-09-19)"
|
||||
sodipodi:docname="icon-device.svg"
|
||||
id="svg4344"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
viewBox="0 0 45 45"
|
||||
height="60"
|
||||
width="60"
|
||||
inkscape:export-ydpi="409.60001"
|
||||
inkscape:export-xdpi="409.60001"
|
||||
inkscape:export-filename="/Users/bpetersen/projects/deltachat-core-rust/assets/icon-device.png"
|
||||
version="1.0"
|
||||
inkscape:export-filename="/Users/bpetersen/Documents/deltachat/icons/device4.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
width="60"
|
||||
height="60"
|
||||
viewBox="0 0 45 45"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
id="svg4344"
|
||||
sodipodi:docname="icon-device.svg"
|
||||
inkscape:version="1.0beta1 (32d4812, 2019-09-19)">
|
||||
<defs
|
||||
id="defs4348">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient865">
|
||||
<stop
|
||||
style="stop-color:#364e59;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop861" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop863" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient865"
|
||||
id="linearGradient867"
|
||||
x1="10.227795"
|
||||
y1="43.5238"
|
||||
x2="-25.458139"
|
||||
y2="-7.4257693"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
id="defs4348" />
|
||||
<sodipodi:namedview
|
||||
inkscape:current-layer="svg4344"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:window-y="23"
|
||||
inkscape:window-x="45"
|
||||
inkscape:cy="28.471578"
|
||||
inkscape:cx="41.455164"
|
||||
inkscape:zoom="5.6"
|
||||
units="px"
|
||||
showgrid="false"
|
||||
id="namedview4346"
|
||||
inkscape:window-height="1035"
|
||||
inkscape:window-width="1600"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:snap-global="false"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff" />
|
||||
inkscape:document-rotation="0"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1600"
|
||||
inkscape:window-height="1035"
|
||||
id="namedview4346"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:cx="28.322498"
|
||||
inkscape:cy="24.898474"
|
||||
inkscape:window-x="45"
|
||||
inkscape:window-y="23"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg4344" />
|
||||
<metadata
|
||||
id="metadata4336">
|
||||
Created by potrace 1.15, written by Peter Selinger 2001-2017
|
||||
@@ -78,33 +56,28 @@ Created by potrace 1.15, written by Peter Selinger 2001-2017
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<circle
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke-width:0.731071"
|
||||
id="path859"
|
||||
cx="22.5"
|
||||
cy="22.5"
|
||||
r="22.5" />
|
||||
<circle
|
||||
r="22.5"
|
||||
cy="22.5"
|
||||
cx="22.5"
|
||||
id="path4917"
|
||||
style="fill:url(#linearGradient867);fill-opacity:1;stroke-width:0.588554" />
|
||||
<rect
|
||||
y="-4.4408921e-16"
|
||||
x="0"
|
||||
height="45"
|
||||
width="45"
|
||||
id="rect860"
|
||||
style="opacity:1;fill:#76868b;fill-opacity:1;stroke-width:0.819271" />
|
||||
<g
|
||||
id="g4342"
|
||||
stroke="none"
|
||||
fill="#000000"
|
||||
stroke="none"
|
||||
style="fill:#ffffff;fill-opacity:1"
|
||||
transform="matrix(0.00255113,0,0,-0.00255113,5.586152,38.200477)"
|
||||
style="fill:#ffffff;fill-opacity:1">
|
||||
id="g4342">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4338"
|
||||
style="fill:#ffffff;fill-opacity:1"
|
||||
d="m 8175,12765 c -703,-114 -1248,-608 -1387,-1258 -17,-82 -21,-136 -22,-277 0,-202 15,-307 70,-470 149,-446 499,-733 1009,-828 142,-26 465,-23 619,6 691,131 1201,609 1328,1244 31,158 31,417 0,565 -114,533 -482,889 -1038,1004 -133,27 -448,35 -579,14 z"
|
||||
style="fill:#ffffff;fill-opacity:1" />
|
||||
id="path4338"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4340"
|
||||
style="fill:#ffffff;fill-opacity:1"
|
||||
d="m 7070,9203 c -212,-20 -275,-27 -397,-48 -691,-117 -1400,-444 -2038,-940 -182,-142 -328,-270 -585,-517 -595,-571 -911,-974 -927,-1181 -6,-76 11,-120 69,-184 75,-80 159,-108 245,-79 109,37 263,181 632,595 539,606 774,826 1035,969 135,75 231,105 341,106 82,1 94,-2 138,-27 116,-68 161,-209 122,-376 -9,-36 -349,-868 -757,-1850 -407,-982 -785,-1892 -838,-2021 -287,-694 -513,-1389 -615,-1889 -70,-342 -90,-683 -52,-874 88,-440 381,-703 882,-792 124,-23 401,-30 562,-16 783,69 1674,461 2561,1125 796,596 1492,1354 1607,1751 43,146 -33,308 -168,360 -61,23 -100,15 -173,-36 -105,-74 -202,-170 -539,-529 -515,-551 -762,-783 -982,-927 -251,-164 -437,-186 -543,-65 -56,64 -74,131 -67,247 13,179 91,434 249,815 135,324 1588,4102 1646,4280 106,325 151,561 159,826 9,281 -22,463 -112,652 -58,122 -114,199 -211,292 -245,233 -582,343 -1044,338 -91,-1 -181,-3 -200,-5 z"
|
||||
style="fill:#ffffff;fill-opacity:1" />
|
||||
id="path4340"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 3.9 KiB |
@@ -5,44 +5,21 @@
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:export-ydpi="204.8"
|
||||
inkscape:export-xdpi="204.8"
|
||||
inkscape:export-filename="/Users/bpetersen/Documents/deltachat/icons/saved-messages.png"
|
||||
inkscape:export-ydpi="409.60001"
|
||||
inkscape:export-xdpi="409.60001"
|
||||
inkscape:export-filename="/home/kerle/test-icon.png"
|
||||
version="1.0"
|
||||
width="60"
|
||||
height="60"
|
||||
viewBox="0 0 45 45"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
id="svg4344"
|
||||
sodipodi:docname="saved-messages.svg"
|
||||
sodipodi:docname="icon-saved-messages.svg"
|
||||
inkscape:version="1.0beta1 (32d4812, 2019-09-19)">
|
||||
<defs
|
||||
id="defs4348">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient906">
|
||||
<stop
|
||||
style="stop-color:#046cdb;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop902" />
|
||||
<stop
|
||||
id="stop845"
|
||||
offset="1"
|
||||
style="stop-color:#ffffff;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
y2="-11.049108"
|
||||
x2="-3.1473193"
|
||||
y1="31.473215"
|
||||
x1="36.160713"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient847"
|
||||
xlink:href="#linearGradient906"
|
||||
inkscape:collect="always" />
|
||||
</defs>
|
||||
id="defs4348" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
@@ -53,16 +30,16 @@
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1600"
|
||||
inkscape:window-height="1035"
|
||||
inkscape:window-width="1395"
|
||||
inkscape:window-height="855"
|
||||
id="namedview4346"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:zoom="2.8"
|
||||
inkscape:cx="48.214285"
|
||||
inkscape:cy="55"
|
||||
inkscape:window-x="45"
|
||||
inkscape:window-y="23"
|
||||
inkscape:zoom="4"
|
||||
inkscape:cx="29.308676"
|
||||
inkscape:cy="49.03624"
|
||||
inkscape:window-x="89"
|
||||
inkscape:window-y="108"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg4344"
|
||||
inkscape:lockguides="false" />
|
||||
@@ -79,21 +56,16 @@ Created by potrace 1.15, written by Peter Selinger 2001-2017
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<circle
|
||||
r="22.5"
|
||||
cy="22.5"
|
||||
cx="22.5"
|
||||
id="path859"
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke-width:0.731071" />
|
||||
<circle
|
||||
style="fill:url(#linearGradient847);fill-opacity:1;stroke-width:0.588554"
|
||||
id="path4917"
|
||||
cx="22.5"
|
||||
cy="22.5"
|
||||
r="22.5" />
|
||||
<rect
|
||||
y="0"
|
||||
x="0"
|
||||
height="45"
|
||||
width="45"
|
||||
id="rect1420"
|
||||
style="fill:#87aade;fill-opacity:1;stroke:none;stroke-width:0.968078" />
|
||||
<path
|
||||
id="rect846"
|
||||
style="fill:#ffffff;stroke-width:0.84832"
|
||||
d="M 17 9 L 17 55 L 17.125 55 L 30.048828 40.9375 L 42.974609 55 L 43 55 L 43 9 L 17 9 z M 30.005859 15 L 32.720703 22.121094 L 40 22.646484 L 34.396484 27.558594 L 36.169922 35 L 29.992188 30.916016 L 23.808594 34.988281 L 25.595703 27.550781 L 20 22.626953 L 27.28125 22.115234 L 30.005859 15 z "
|
||||
transform="scale(0.75)" />
|
||||
style="fill:#ffffff;stroke-width:0.58409804"
|
||||
d="M 13.5,7.5 V 39 h 0.08654 L 22.533801,29.370239 31.482419,39 h 0.01758 V 7.5 Z m 9.004056,4.108698 1.879508,4.876388 5.039514,0.359779 -3.879358,3.363728 1.227764,5.095749 -4.276893,-2.796643 -4.280949,2.788618 1.237229,-5.093073 -3.873949,-3.371754 5.040866,-0.350417 z"
|
||||
inkscape:connector-curvature="0" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.3 KiB |
BIN
assets/welcome-image.jpg
Normal file
|
After Width: | Height: | Size: 113 KiB |
@@ -22,7 +22,7 @@ export BRANCH=${CIRCLE_BRANCH:?specify branch for uploading purposes}
|
||||
# delta@py.delta.chat:build/${BRANCH}
|
||||
|
||||
# C docs to c.delta.chat
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@c.delta.chat mkdir -p build-c/${BRANCH}
|
||||
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@c.delta.chat mkdir -p build-c/${BRANCH}
|
||||
rsync -avz \
|
||||
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
|
||||
"$DOXYDOCDIR/html/" \
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
export BRANCH=${CIRCLE_BRANCH:?branch to build}
|
||||
export REPONAME=${CIRCLE_PROJECT_REPONAME:?repository name}
|
||||
export SSHTARGET=ci@b1.delta.chat
|
||||
export SSHTARGET=${SSHTARGET-ci@b1.delta.chat}
|
||||
|
||||
# we construct the BUILDDIR such that we can easily share the
|
||||
# CARGO_TARGET_DIR between runs ("..")
|
||||
@@ -12,7 +12,7 @@ echo "--- Copying files to $SSHTARGET:$BUILDDIR"
|
||||
|
||||
set -xe
|
||||
|
||||
ssh -oStrictHostKeyChecking=no $SSHTARGET mkdir -p "$BUILDDIR"
|
||||
ssh -oBatchMode=yes -oStrictHostKeyChecking=no $SSHTARGET mkdir -p "$BUILDDIR"
|
||||
git ls-files >.rsynclist
|
||||
# we seem to need .git for setuptools_scm versioning
|
||||
find .git >>.rsynclist
|
||||
@@ -25,8 +25,6 @@ echo "--- Running $CIRCLE_JOB remotely"
|
||||
ssh $SSHTARGET <<_HERE
|
||||
set +x -e
|
||||
cd $BUILDDIR
|
||||
# make sure git checkouts are clean (no dangling revspcs)
|
||||
cargo cache --gc
|
||||
# let's share the target dir with our last run on this branch/job-type
|
||||
# cargo will make sure to block/unblock us properly
|
||||
export CARGO_TARGET_DIR=\`pwd\`/../target
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
export BRANCH=${CIRCLE_BRANCH:?branch to build}
|
||||
export REPONAME=${CIRCLE_PROJECT_REPONAME:?repository name}
|
||||
export SSHTARGET=ci@b1.delta.chat
|
||||
export SSHTARGET=${SSHTARGET-ci@b1.delta.chat}
|
||||
|
||||
# we construct the BUILDDIR such that we can easily share the
|
||||
# CARGO_TARGET_DIR between runs ("..")
|
||||
@@ -12,7 +12,7 @@ set -e
|
||||
|
||||
echo "--- Copying files to $SSHTARGET:$BUILDDIR"
|
||||
|
||||
ssh -oStrictHostKeyChecking=no $SSHTARGET mkdir -p "$BUILDDIR"
|
||||
ssh -oBatchMode=yes -oStrictHostKeyChecking=no $SSHTARGET mkdir -p "$BUILDDIR"
|
||||
git ls-files >.rsynclist
|
||||
rsync --delete --files-from=.rsynclist -az ./ "$SSHTARGET:$BUILDDIR"
|
||||
|
||||
@@ -21,8 +21,6 @@ echo "--- Running $CIRCLE_JOB remotely"
|
||||
ssh $SSHTARGET <<_HERE
|
||||
set +x -e
|
||||
cd $BUILDDIR
|
||||
# make sure git checkouts are clean (no dangling revspecs)
|
||||
cargo cache --gc
|
||||
# let's share the target dir with our last run on this branch/job-type
|
||||
# cargo will make sure to block/unblock us properly
|
||||
export CARGO_TARGET_DIR=\`pwd\`/../target
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.0.0-beta.7"
|
||||
version = "1.0.0-beta.20"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
@@ -16,10 +16,12 @@ crate-type = ["cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
deltachat = { path = "../", default-features = false }
|
||||
deltachat-provider-database = "0.2.1"
|
||||
deltachat-provider-database = "0.3.0"
|
||||
libc = "0.2"
|
||||
human-panic = "1.0.1"
|
||||
num-traits = "0.2.6"
|
||||
failure = "0.1.6"
|
||||
serde_json = "1.0"
|
||||
|
||||
[features]
|
||||
default = ["vendored", "nightly", "ringbuf"]
|
||||
|
||||
@@ -18,7 +18,6 @@ typedef struct _dc_chat dc_chat_t;
|
||||
typedef struct _dc_msg dc_msg_t;
|
||||
typedef struct _dc_contact dc_contact_t;
|
||||
typedef struct _dc_lot dc_lot_t;
|
||||
typedef struct _dc_provider dc_provider_t;
|
||||
|
||||
|
||||
/**
|
||||
@@ -994,11 +993,20 @@ uint32_t dc_get_chat_id_by_contact_id (dc_context_t* context, uint32_t co
|
||||
*
|
||||
* Example:
|
||||
* ~~~
|
||||
* char* blobdir = dc_get_blobdir(context);
|
||||
* char* file_to_send = mprintf("%s/%s", blobdir, "send.mp4")
|
||||
*
|
||||
* dc_msg_t* msg = dc_msg_new(context, DC_MSG_VIDEO);
|
||||
* dc_msg_set_file(msg, "/file/to/send.mp4", NULL);
|
||||
* dc_msg_set_file(msg, file_to_send, NULL);
|
||||
* dc_prepare_msg(context, chat_id, msg);
|
||||
* // ... after /file/to/send.mp4 is ready:
|
||||
*
|
||||
* // ... create the file ...
|
||||
*
|
||||
* dc_send_msg(context, chat_id, msg);
|
||||
*
|
||||
* dc_msg_unref(msg);
|
||||
* free(file_to_send);
|
||||
* dc_str_unref(file_to_send);
|
||||
* ~~~
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
@@ -1024,8 +1032,11 @@ uint32_t dc_prepare_msg (dc_context_t* context, uint32_t ch
|
||||
* Example:
|
||||
* ~~~
|
||||
* dc_msg_t* msg = dc_msg_new(context, DC_MSG_IMAGE);
|
||||
*
|
||||
* dc_msg_set_file(msg, "/file/to/send.jpg", NULL);
|
||||
* dc_send_msg(context, chat_id, msg);
|
||||
*
|
||||
* dc_msg_unref(msg);
|
||||
* ~~~
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
@@ -1134,11 +1145,32 @@ void dc_set_draft (dc_context_t* context, uint32_t ch
|
||||
* // add a changelog
|
||||
* dc_add_device_msg(context, "update-123", changelog_msg);
|
||||
* }
|
||||
*
|
||||
* dc_msg_unref(changelog_msg);
|
||||
* dc_msg_unref(welome_msg);
|
||||
* ~~~
|
||||
*/
|
||||
uint32_t dc_add_device_msg (dc_context_t* context, const char* label, dc_msg_t* msg);
|
||||
|
||||
|
||||
/**
|
||||
* Init device-messages and saved-messages chat.
|
||||
* This function adds the device-chat and saved-messages chat
|
||||
* and adds one or more welcome or update-messages.
|
||||
* The ui can add messages on its own using dc_add_device_msg() -
|
||||
* for ordering, either before or after or even without calling this function.
|
||||
*
|
||||
* Chat and message creation is done only once.
|
||||
* So if the user has manually deleted things, they won't be re-created
|
||||
* (however, not seen device messages are added and may re-create the device-chat).
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context as created by dc_context_new().
|
||||
* @return None.
|
||||
*/
|
||||
void dc_update_device_chats (dc_context_t* context);
|
||||
|
||||
|
||||
/**
|
||||
* Check if a device-message with a given label was ever added.
|
||||
* Device-messages can be added dc_add_device_msg().
|
||||
@@ -1929,7 +1961,7 @@ void dc_imex (dc_context_t* context, int what, c
|
||||
* }
|
||||
* while (!configure_succeeded())
|
||||
* }
|
||||
* free(file);
|
||||
* dc_str_unref(file);
|
||||
* }
|
||||
* ~~~
|
||||
*
|
||||
@@ -2584,6 +2616,25 @@ dc_lot_t* dc_chatlist_get_summary (const dc_chatlist_t* chatlist, siz
|
||||
dc_context_t* dc_chatlist_get_context (dc_chatlist_t* chatlist);
|
||||
|
||||
|
||||
/**
|
||||
* Get info summary for a chat, in json format.
|
||||
*
|
||||
* The returned json string has the following key/values:
|
||||
*
|
||||
* id: chat id
|
||||
* name: chat/group name
|
||||
* color: color of this chat
|
||||
* last-message-from: who sent the last message
|
||||
* last-message-text: message (truncated)
|
||||
* last-message-state: DC_STATE* constant
|
||||
* last-message-date:
|
||||
* avatar-path: path-to-blobfile
|
||||
* is_verified: yes/no
|
||||
|
||||
* @return a utf8-encoded json string containing all requested info. Must be freed using dc_str_unref(). NULL is never returned.
|
||||
*/
|
||||
char* dc_chat_get_info_json (dc_context_t* context, size_t chat_id);
|
||||
|
||||
/**
|
||||
* @class dc_chat_t
|
||||
*
|
||||
@@ -3593,117 +3644,45 @@ int dc_contact_is_blocked (const dc_contact_t* contact);
|
||||
|
||||
|
||||
/**
|
||||
* Same as dc_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.
|
||||
* Check if a contact was verified. E.g. by a secure-join QR code scan
|
||||
* and if the key has not changed since this verification.
|
||||
*
|
||||
* @private @memberof dc_context_t
|
||||
* The UI may draw a checkbox or something like that beside verified contacts.
|
||||
*
|
||||
* @memberof dc_contact_t
|
||||
* @param contact The contact object.
|
||||
* @return 0: contact is not verified.
|
||||
* 2: SELF and contact have verified their fingerprints in both directions; in the UI typically checkmarks are shown.
|
||||
*/
|
||||
int dc_contact_is_verified (dc_contact_t* contact);
|
||||
|
||||
|
||||
/**
|
||||
* @class dc_provider_t
|
||||
*
|
||||
* Opaque object containing information about one single email provider.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Create a provider struct for the given domain.
|
||||
* Get the provider json object for the given domain.
|
||||
* For more documentation see https://docs.rs/deltachat-provider-database/
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param domain The domain to get provider info for.
|
||||
* @return a dc_provider_t struct which can be used with the dc_provider_get_*
|
||||
* accessor functions. If no provider info is found, NULL will be
|
||||
* returned.
|
||||
* @return a provider json object as string. If no provider info is found, an empty string will be returned.
|
||||
*/
|
||||
dc_provider_t* dc_provider_new_from_domain (const char* domain);
|
||||
char* dc_provider_json_from_domain (const char* domain);
|
||||
|
||||
|
||||
/**
|
||||
* Create a provider struct for the given email address.
|
||||
* Get the provider json object for the given email address.
|
||||
* For more documentation see https://docs.rs/deltachat-provider-database/
|
||||
*
|
||||
* The provider is extracted from the email address and it's information is returned.
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param email The user's email address to extract the provider info form.
|
||||
* @return a dc_provider_t struct which can be used with the dc_provider_get_*
|
||||
* accessor functions. If no provider info is found, NULL will be
|
||||
* returned.
|
||||
* @return a provider json object as string. If no provider info is found, an empty string will be returned.
|
||||
*/
|
||||
dc_provider_t* dc_provider_new_from_email (const char* email);
|
||||
|
||||
|
||||
/**
|
||||
* URL of the overview page.
|
||||
*
|
||||
* This URL allows linking to the providers page on providers.delta.chat.
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param provider The dc_provider_t struct.
|
||||
* @return A string which must be released using dc_str_unref().
|
||||
*/
|
||||
char* dc_provider_get_overview_page (const dc_provider_t* provider);
|
||||
|
||||
|
||||
/**
|
||||
* The provider's name.
|
||||
*
|
||||
* The name of the provider, e.g. "POSTEO".
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param provider The dc_provider_t struct.
|
||||
* @return A string which must be released using dc_str_unref().
|
||||
*/
|
||||
char* dc_provider_get_name (const dc_provider_t* provider);
|
||||
|
||||
|
||||
/**
|
||||
* The markdown content of the providers page.
|
||||
*
|
||||
* This contains the preparation steps or additional information if the status
|
||||
* is @ref DC_PROVIDER_STATUS_BROKEN.
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param provider The dc_provider_t struct.
|
||||
* @return A string which must be released using dc_str_unref().
|
||||
*/
|
||||
char* dc_provider_get_markdown (const dc_provider_t* provider);
|
||||
|
||||
|
||||
/**
|
||||
* Date of when the state was last checked/updated.
|
||||
*
|
||||
* This is returned as a string.
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param provider The dc_provider_t struct.
|
||||
* @return A string which must be released using dc_str_unref().
|
||||
*/
|
||||
char* dc_provider_get_status_date (const dc_provider_t* provider);
|
||||
|
||||
|
||||
/**
|
||||
* Whether DC works with this provider.
|
||||
*
|
||||
* Can be one of @ref DC_PROVIDER_STATUS_OK, @ref
|
||||
* DC_PROVIDER_STATUS_PREPARATION and @ref DC_PROVIDER_STATUS_BROKEN.
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param provider The dc_provider_t struct.
|
||||
* @return The status as a constant number.
|
||||
*/
|
||||
int dc_provider_get_status (const dc_provider_t* provider);
|
||||
|
||||
|
||||
/**
|
||||
* Free the provider info struct.
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param provider The dc_provider_t struct.
|
||||
*/
|
||||
void dc_provider_unref (const dc_provider_t* provider);
|
||||
char* dc_provider_json_from_email (const char* email);
|
||||
|
||||
|
||||
/**
|
||||
@@ -3998,11 +3977,6 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
*/
|
||||
#define DC_CERTCK_STRICT 1
|
||||
|
||||
/**
|
||||
* Accept invalid hostnames, but not invalid certificates.
|
||||
*/
|
||||
#define DC_CERTCK_ACCEPT_INVALID_HOSTNAMES 2
|
||||
|
||||
/**
|
||||
* Accept invalid certificates, including self-signed ones
|
||||
* or having incorrect hostname.
|
||||
@@ -4387,6 +4361,16 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
#define DC_EVENT_SECUREJOIN_JOINER_PROGRESS 2061
|
||||
|
||||
|
||||
/**
|
||||
* This event is sent out to the inviter when a joiner successfully joined a group.
|
||||
*
|
||||
* @param data1 (int) chat_id
|
||||
* @param data2 (int) contact_id
|
||||
* @return 0
|
||||
*/
|
||||
#define DC_EVENT_SECUREJOIN_MEMBER_ADDED 2062
|
||||
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
@@ -4414,41 +4398,6 @@ void dc_array_add_id (dc_array_t*, uint32_t); // depreca
|
||||
#define DC_SHOW_EMAILS_ALL 2
|
||||
|
||||
|
||||
/**
|
||||
* @defgroup DC_PROVIDER_STATUS DC_PROVIDER_STATUS
|
||||
*
|
||||
* These constants are used as return values for dc_provider_get_status().
|
||||
*
|
||||
* @addtogroup DC_PROVIDER_STATUS
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provider status returned by dc_provider_get_status().
|
||||
*
|
||||
* Works right out of the box without any preperation steps needed
|
||||
*/
|
||||
#define DC_PROVIDER_STATUS_OK 1
|
||||
|
||||
/**
|
||||
* Provider status returned by dc_provider_get_status().
|
||||
*
|
||||
* Works, but preparation steps are needed
|
||||
*/
|
||||
#define DC_PROVIDER_STATUS_PREPARATION 2
|
||||
|
||||
/**
|
||||
* Provider status returned by dc_provider_get_status().
|
||||
*
|
||||
* Doesn't work (too unstable to use falls also in this category)
|
||||
*/
|
||||
#define DC_PROVIDER_STATUS_BROKEN 3
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* TODO: Strings need some doumentation about used placeholders.
|
||||
*
|
||||
|
||||
@@ -25,13 +25,15 @@ use num_traits::{FromPrimitive, ToPrimitive};
|
||||
use deltachat::constants::DC_MSG_ID_LAST_SPECIAL;
|
||||
use deltachat::contact::Contact;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::dc_tools::{
|
||||
as_path, dc_strdup, to_opt_string_lossy, to_string_lossy, OsStrExt, StrExt,
|
||||
};
|
||||
use deltachat::message::MsgId;
|
||||
use deltachat::stock::StockMessage;
|
||||
use deltachat::*;
|
||||
|
||||
mod dc_array;
|
||||
|
||||
mod string;
|
||||
use self::string::*;
|
||||
|
||||
// as C lacks a good and portable error handling,
|
||||
// in general, the C Interface is forgiving wrt to bad parameters.
|
||||
// - objects returned by some functions
|
||||
@@ -175,6 +177,15 @@ impl ContextWrapper {
|
||||
contact_id as uintptr_t,
|
||||
progress as uintptr_t,
|
||||
),
|
||||
Event::SecurejoinMemberAdded {
|
||||
chat_id,
|
||||
contact_id,
|
||||
} => ffi_cb(
|
||||
self,
|
||||
event_id,
|
||||
chat_id as uintptr_t,
|
||||
contact_id as uintptr_t,
|
||||
),
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
@@ -498,7 +509,7 @@ pub unsafe extern "C" fn dc_interrupt_imap_idle(context: *mut dc_context_t) {
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| job::interrupt_inbox_idle(ctx, true))
|
||||
.with_inner(|ctx| job::interrupt_inbox_idle(ctx))
|
||||
.unwrap_or(())
|
||||
}
|
||||
|
||||
@@ -842,6 +853,21 @@ pub unsafe extern "C" fn dc_add_device_msg(
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_update_device_chats(context: *mut dc_context_t) {
|
||||
if context.is_null() {
|
||||
eprintln!("ignoring careless call to dc_update_device_chats()");
|
||||
return;
|
||||
}
|
||||
let ffi_context = &mut *context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| {
|
||||
ctx.update_device_chats()
|
||||
.unwrap_or_log_default(ctx, "Failed to add device message")
|
||||
})
|
||||
.unwrap_or(())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_was_device_msg_ever_added(
|
||||
context: *mut dc_context_t,
|
||||
@@ -2353,6 +2379,27 @@ pub unsafe extern "C" fn dc_chat_is_sending_locations(chat: *mut dc_chat_t) -> l
|
||||
ffi_chat.chat.is_sending_locations() as libc::c_int
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_chat_get_info_json(
|
||||
context: *mut dc_context_t,
|
||||
chat_id: u32,
|
||||
) -> *mut libc::c_char {
|
||||
if context.is_null() {
|
||||
eprintln!("ignoring careless call to dc_chat_get_info_json()");
|
||||
return "".strdup();
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| match chat::get_info_json(ctx, chat_id) {
|
||||
Ok(s) => s.strdup(),
|
||||
Err(err) => {
|
||||
error!(ctx, "get_info_json({}) returned: {}", chat_id, err);
|
||||
return "".strdup();
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|_| "".strdup())
|
||||
}
|
||||
|
||||
// dc_msg_t
|
||||
|
||||
/// FFI struct for [dc_msg_t]
|
||||
|
||||
@@ -1,93 +1,35 @@
|
||||
extern crate deltachat_provider_database;
|
||||
|
||||
use std::ptr;
|
||||
|
||||
use deltachat::dc_tools::{to_string_lossy, StrExt};
|
||||
use deltachat_provider_database::StatusState;
|
||||
use crate::string::{to_string_lossy, StrExt};
|
||||
|
||||
#[no_mangle]
|
||||
pub type dc_provider_t = deltachat_provider_database::Provider;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_new_from_domain(
|
||||
pub unsafe extern "C" fn dc_provider_json_from_domain(
|
||||
domain: *const libc::c_char,
|
||||
) -> *const dc_provider_t {
|
||||
match deltachat_provider_database::get_provider_info(&to_string_lossy(domain)) {
|
||||
Some(provider) => provider,
|
||||
None => ptr::null(),
|
||||
) -> *mut libc::c_char {
|
||||
let domain = to_string_lossy(domain);
|
||||
match deltachat_provider_database::get_provider_info(&domain) {
|
||||
Some(provider) => serde_json::to_string(provider)
|
||||
.unwrap_or("".to_owned())
|
||||
.strdup(),
|
||||
None => "".strdup(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_new_from_email(
|
||||
pub unsafe extern "C" fn dc_provider_json_from_email(
|
||||
email: *const libc::c_char,
|
||||
) -> *const dc_provider_t {
|
||||
) -> *mut libc::c_char {
|
||||
let email = to_string_lossy(email);
|
||||
let domain = deltachat_provider_database::get_domain_from_email(&email);
|
||||
match deltachat_provider_database::get_provider_info(domain) {
|
||||
Some(provider) => provider,
|
||||
None => ptr::null(),
|
||||
Some(provider) => serde_json::to_string(provider)
|
||||
.unwrap_or("".to_owned())
|
||||
.strdup(),
|
||||
None => "".strdup(),
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! null_guard {
|
||||
($context:tt) => {
|
||||
if $context.is_null() {
|
||||
return ptr::null_mut() as *mut libc::c_char;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_get_overview_page(
|
||||
provider: *const dc_provider_t,
|
||||
) -> *mut libc::c_char {
|
||||
null_guard!(provider);
|
||||
format!(
|
||||
"{}/{}",
|
||||
deltachat_provider_database::PROVIDER_OVERVIEW_URL,
|
||||
(*provider).overview_page
|
||||
)
|
||||
.strdup()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_get_name(provider: *const dc_provider_t) -> *mut libc::c_char {
|
||||
null_guard!(provider);
|
||||
(*provider).name.strdup()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_get_markdown(
|
||||
provider: *const dc_provider_t,
|
||||
) -> *mut libc::c_char {
|
||||
null_guard!(provider);
|
||||
(*provider).markdown.strdup()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_get_status_date(
|
||||
provider: *const dc_provider_t,
|
||||
) -> *mut libc::c_char {
|
||||
null_guard!(provider);
|
||||
(*provider).status.date.strdup()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_get_status(provider: *const dc_provider_t) -> u32 {
|
||||
if provider.is_null() {
|
||||
return 0;
|
||||
}
|
||||
match (*provider).status.state {
|
||||
StatusState::OK => 1,
|
||||
StatusState::PREPARATION => 2,
|
||||
StatusState::BROKEN => 3,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_unref(_provider: *const dc_provider_t) {
|
||||
()
|
||||
}
|
||||
|
||||
// TODO expose general provider overview url?
|
||||
|
||||
350
deltachat-ffi/src/string.rs
Normal file
@@ -0,0 +1,350 @@
|
||||
use failure::Fail;
|
||||
use std::ffi::{CStr, CString};
|
||||
|
||||
/// Duplicates a string
|
||||
///
|
||||
/// returns an empty string if NULL is given, never returns NULL (exits on errors)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,norun
|
||||
/// use deltachat::dc_tools::{dc_strdup, to_string_lossy};
|
||||
/// unsafe {
|
||||
/// let str_a = b"foobar\x00" as *const u8 as *const libc::c_char;
|
||||
/// let str_a_copy = dc_strdup(str_a);
|
||||
/// assert_eq!(to_string_lossy(str_a_copy), "foobar");
|
||||
/// assert_ne!(str_a, str_a_copy);
|
||||
/// }
|
||||
/// ```
|
||||
pub unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
|
||||
let ret: *mut libc::c_char;
|
||||
if !s.is_null() {
|
||||
ret = libc::strdup(s);
|
||||
assert!(!ret.is_null());
|
||||
} else {
|
||||
ret = libc::calloc(1, 1) as *mut libc::c_char;
|
||||
assert!(!ret.is_null());
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
/// Error type for the [OsStrExt] trait
|
||||
#[derive(Debug, Fail, PartialEq)]
|
||||
pub enum CStringError {
|
||||
/// The string contains an interior null byte
|
||||
#[fail(display = "String contains an interior null byte")]
|
||||
InteriorNullByte,
|
||||
/// The string is not valid Unicode
|
||||
#[fail(display = "String is not valid unicode")]
|
||||
NotUnicode,
|
||||
}
|
||||
|
||||
/// Extra convenience methods on [std::ffi::OsStr] to work with `*libc::c_char`.
|
||||
///
|
||||
/// The primary function of this trait is to more easily convert
|
||||
/// [OsStr], [OsString] or [Path] into pointers to C strings. This always
|
||||
/// allocates a new string since it is very common for the source
|
||||
/// string not to have the required terminal null byte.
|
||||
///
|
||||
/// It is implemented for `AsRef<std::ffi::OsStr>>` trait, which
|
||||
/// allows any type which implements this trait to transparently use
|
||||
/// this. This is how the conversion for [Path] works.
|
||||
///
|
||||
/// [OsStr]: std::ffi::OsStr
|
||||
/// [OsString]: std::ffi::OsString
|
||||
/// [Path]: std::path::Path
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use deltachat::dc_tools::{dc_strdup, OsStrExt};
|
||||
/// let path = std::path::Path::new("/some/path");
|
||||
/// let path_c = path.to_c_string().unwrap();
|
||||
/// unsafe {
|
||||
/// let mut c_ptr: *mut libc::c_char = dc_strdup(path_c.as_ptr());
|
||||
/// }
|
||||
/// ```
|
||||
pub trait OsStrExt {
|
||||
/// Convert a [std::ffi::OsStr] to an [std::ffi::CString]
|
||||
///
|
||||
/// This is useful to convert e.g. a [std::path::Path] to
|
||||
/// [*libc::c_char] by using
|
||||
/// [Path::as_os_str()](std::path::Path::as_os_str) and
|
||||
/// [CStr::as_ptr()](std::ffi::CStr::as_ptr).
|
||||
///
|
||||
/// This returns [CString] and not [&CStr] because not all [OsStr]
|
||||
/// slices end with a null byte, particularly those coming from
|
||||
/// [Path] do not have a null byte and having to handle this as
|
||||
/// the caller would defeat the point of this function.
|
||||
///
|
||||
/// On Windows this requires that the [OsStr] contains valid
|
||||
/// unicode, which should normally be the case for a [Path].
|
||||
///
|
||||
/// [CString]: std::ffi::CString
|
||||
/// [CStr]: std::ffi::CStr
|
||||
/// [OsStr]: std::ffi::OsStr
|
||||
/// [Path]: std::path::Path
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Since a C `*char` is terminated by a NULL byte this conversion
|
||||
/// will fail, when the [OsStr] has an interior null byte. The
|
||||
/// function will return
|
||||
/// `[Err]([CStringError::InteriorNullByte])`. When converting
|
||||
/// from a [Path] it should be safe to
|
||||
/// [`.unwrap()`](std::result::Result::unwrap) this anyway since a
|
||||
/// [Path] should not contain interior null bytes.
|
||||
///
|
||||
/// On windows when the string contains invalid Unicode
|
||||
/// `[Err]([CStringError::NotUnicode])` is returned.
|
||||
fn to_c_string(&self) -> Result<CString, CStringError>;
|
||||
}
|
||||
|
||||
impl<T: AsRef<std::ffi::OsStr>> OsStrExt for T {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn to_c_string(&self) -> Result<CString, CStringError> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
CString::new(self.as_ref().as_bytes()).map_err(|err| match err {
|
||||
std::ffi::NulError { .. } => CStringError::InteriorNullByte,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn to_c_string(&self) -> Result<CString, CStringError> {
|
||||
os_str_to_c_string_unicode(&self)
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation for os_str_to_c_string on windows.
|
||||
#[allow(dead_code)]
|
||||
fn os_str_to_c_string_unicode(
|
||||
os_str: &dyn AsRef<std::ffi::OsStr>,
|
||||
) -> Result<CString, CStringError> {
|
||||
match os_str.as_ref().to_str() {
|
||||
Some(val) => CString::new(val.as_bytes()).map_err(|err| match err {
|
||||
std::ffi::NulError { .. } => CStringError::InteriorNullByte,
|
||||
}),
|
||||
None => Err(CStringError::NotUnicode),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience methods/associated functions for working with [CString]
|
||||
///
|
||||
/// This is helps transitioning from unsafe code.
|
||||
pub trait CStringExt {
|
||||
/// Create a new [CString], yolo style
|
||||
///
|
||||
/// This unwrap the result, panicking when there are embedded NULL
|
||||
/// bytes.
|
||||
fn yolo<T: Into<Vec<u8>>>(t: T) -> CString {
|
||||
CString::new(t).expect("String contains null byte, can not be CString")
|
||||
}
|
||||
}
|
||||
|
||||
impl CStringExt for CString {}
|
||||
|
||||
/// Convenience methods to make transitioning from raw C strings easier.
|
||||
///
|
||||
/// To interact with (legacy) C APIs we often need to convert from
|
||||
/// Rust strings to raw C strings. This can be clumsy to do correctly
|
||||
/// and the compiler sometimes allows it in an unsafe way. These
|
||||
/// methods make it more succinct and help you get it right.
|
||||
pub trait StrExt {
|
||||
/// Allocate a new raw C `*char` version of this string.
|
||||
///
|
||||
/// This allocates a new raw C string which must be freed using
|
||||
/// `free`. It takes care of some common pitfalls with using
|
||||
/// [CString.as_ptr].
|
||||
///
|
||||
/// [CString.as_ptr]: std::ffi::CString.as_ptr
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic when the original string contains an
|
||||
/// interior null byte as this can not be represented in raw C
|
||||
/// strings.
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char;
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> StrExt for T {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||
let tmp = CString::yolo(self.as_ref());
|
||||
dc_strdup(tmp.as_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_string_lossy(s: *const libc::c_char) -> String {
|
||||
if s.is_null() {
|
||||
return "".into();
|
||||
}
|
||||
|
||||
let cstr = unsafe { CStr::from_ptr(s) };
|
||||
|
||||
cstr.to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
pub fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
|
||||
if s.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(to_string_lossy(s))
|
||||
}
|
||||
|
||||
/// Convert a C `*char` pointer to a [std::path::Path] slice.
|
||||
///
|
||||
/// This converts a `*libc::c_char` pointer to a [Path] slice. This
|
||||
/// essentially has to convert the pointer to [std::ffi::OsStr] to do
|
||||
/// so and thus is the inverse of [OsStrExt::to_c_string]. Just like
|
||||
/// [OsStrExt::to_c_string] requires valid Unicode on Windows, this
|
||||
/// requires that the pointer contains valid UTF-8 on Windows.
|
||||
///
|
||||
/// Because this returns a reference the [Path] silce can not outlive
|
||||
/// the original pointer.
|
||||
///
|
||||
/// [Path]: std::path::Path
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
assert!(!s.is_null(), "cannot be used on null pointers");
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
unsafe {
|
||||
let c_str = std::ffi::CStr::from_ptr(s).to_bytes();
|
||||
let os_str = std::ffi::OsStr::from_bytes(c_str);
|
||||
std::path::Path::new(os_str)
|
||||
}
|
||||
}
|
||||
|
||||
// as_path() implementation for windows, documented above.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
as_path_unicode(s)
|
||||
}
|
||||
|
||||
// Implementation for as_path() on Windows.
|
||||
//
|
||||
// Having this as a separate function means it can be tested on unix
|
||||
// too.
|
||||
#[allow(dead_code)]
|
||||
fn as_path_unicode<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
assert!(!s.is_null(), "cannot be used on null pointers");
|
||||
|
||||
let cstr = unsafe { CStr::from_ptr(s) };
|
||||
let str = cstr.to_str().unwrap_or_else(|err| panic!("{}", err));
|
||||
|
||||
std::path::Path::new(str)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use libc::{free, strcmp};
|
||||
|
||||
#[test]
|
||||
fn test_os_str_to_c_string_cwd() {
|
||||
let some_dir = std::env::current_dir().unwrap();
|
||||
some_dir.as_os_str().to_c_string().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_os_str_to_c_string_unicode() {
|
||||
let some_str = String::from("/some/valid/utf8");
|
||||
let some_dir = std::path::Path::new(&some_str);
|
||||
assert_eq!(
|
||||
some_dir.as_os_str().to_c_string().unwrap(),
|
||||
CString::new("/some/valid/utf8").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_os_str_to_c_string_nul() {
|
||||
let some_str = std::ffi::OsString::from("foo\x00bar");
|
||||
assert_eq!(
|
||||
some_str.to_c_string().err().unwrap(),
|
||||
CStringError::InteriorNullByte
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_to_c_string_cwd() {
|
||||
let some_dir = std::env::current_dir().unwrap();
|
||||
some_dir.to_c_string().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_to_c_string_unicode() {
|
||||
let some_str = String::from("/some/valid/utf8");
|
||||
let some_dir = std::path::Path::new(&some_str);
|
||||
assert_eq!(
|
||||
some_dir.as_os_str().to_c_string().unwrap(),
|
||||
CString::new("/some/valid/utf8").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_os_str_to_c_string_unicode_fn() {
|
||||
let some_str = std::ffi::OsString::from("foo");
|
||||
assert_eq!(
|
||||
os_str_to_c_string_unicode(&some_str).unwrap(),
|
||||
CString::new("foo").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_to_c_string_unicode_fn() {
|
||||
let some_str = String::from("/some/path");
|
||||
let some_path = std::path::Path::new(&some_str);
|
||||
assert_eq!(
|
||||
os_str_to_c_string_unicode(&some_path).unwrap(),
|
||||
CString::new("/some/path").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_os_str_to_c_string_unicode_fn_nul() {
|
||||
let some_str = std::ffi::OsString::from("fooz\x00bar");
|
||||
assert_eq!(
|
||||
os_str_to_c_string_unicode(&some_str).err().unwrap(),
|
||||
CStringError::InteriorNullByte
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_as_path() {
|
||||
let some_path = CString::new("/some/path").unwrap();
|
||||
let ptr = some_path.as_ptr();
|
||||
assert_eq!(as_path(ptr), std::ffi::OsString::from("/some/path"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_as_path_unicode_fn() {
|
||||
let some_path = CString::new("/some/path").unwrap();
|
||||
let ptr = some_path.as_ptr();
|
||||
assert_eq!(as_path_unicode(ptr), std::ffi::OsString::from("/some/path"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cstring_yolo() {
|
||||
assert_eq!(CString::new("hello").unwrap(), CString::yolo("hello"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strdup_str() {
|
||||
unsafe {
|
||||
let s = "hello".strdup();
|
||||
let cmp = strcmp(s, b"hello\x00" as *const u8 as *const libc::c_char);
|
||||
free(s as *mut libc::c_void);
|
||||
assert_eq!(cmp, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strdup_string() {
|
||||
unsafe {
|
||||
let s = String::from("hello").strdup();
|
||||
let cmp = strcmp(s, b"hello\x00" as *const u8 as *const libc::c_char);
|
||||
free(s as *mut libc::c_void);
|
||||
assert_eq!(cmp, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,11 @@ use deltachat::peerstate::*;
|
||||
use deltachat::qr::*;
|
||||
use deltachat::sql;
|
||||
use deltachat::Event;
|
||||
use libc::free;
|
||||
|
||||
/// Reset database tables. This function is called from Core cmdline.
|
||||
/// Argument is a bitmask, executing single or multiple actions in one call.
|
||||
/// e.g. bitmask 7 triggers actions definded with bits 1, 2 and 4.
|
||||
pub unsafe fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
|
||||
pub fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
|
||||
info!(context, "Resetting tables ({})...", bits);
|
||||
if 0 != bits & 1 {
|
||||
sql::execute(context, &context.sql, "DELETE FROM jobs;", params![]).unwrap();
|
||||
@@ -95,7 +94,9 @@ pub unsafe fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
|
||||
fn dc_poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<(), Error> {
|
||||
let data = dc_read_file(context, filename)?;
|
||||
|
||||
unsafe { dc_receive_imf(context, &data, "import", 0, 0) };
|
||||
if let Err(err) = dc_receive_imf(context, &data, "import", 0, 0) {
|
||||
println!("dc_receive_imf errored: {:?}", err);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -107,18 +108,19 @@ fn dc_poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<(),
|
||||
/// @param context The context as created by dc_context_new().
|
||||
/// @param spec The file or directory to import. NULL for the last command.
|
||||
/// @return 1=success, 0=error.
|
||||
fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int {
|
||||
fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int {
|
||||
if !context.sql.is_open() {
|
||||
error!(context, "Import: Database not opened.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
let real_spec: String;
|
||||
let mut read_cnt = 0;
|
||||
|
||||
let real_spec: String;
|
||||
|
||||
/* if `spec` is given, remember it for later usage; if it is not given, try to use the last one */
|
||||
if !spec.is_null() {
|
||||
real_spec = to_string_lossy(spec);
|
||||
if let Some(spec) = spec {
|
||||
real_spec = spec.to_string();
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(context, "import_spec", Some(&real_spec))
|
||||
@@ -132,10 +134,8 @@ fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int {
|
||||
real_spec = rs.unwrap();
|
||||
}
|
||||
if let Some(suffix) = dc_get_filesuffix_lc(&real_spec) {
|
||||
if suffix == "eml" {
|
||||
if dc_poke_eml_file(context, &real_spec).is_ok() {
|
||||
read_cnt += 1
|
||||
}
|
||||
if suffix == "eml" && dc_poke_eml_file(context, &real_spec).is_ok() {
|
||||
read_cnt += 1
|
||||
}
|
||||
} else {
|
||||
/* import a directory */
|
||||
@@ -176,7 +176,7 @@ fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int {
|
||||
1
|
||||
}
|
||||
|
||||
unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
let contact = Contact::get_by_id(context, msg.get_from_id()).expect("invalid contact");
|
||||
let contact_name = contact.get_name();
|
||||
let contact_id = contact.get_id();
|
||||
@@ -221,7 +221,7 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
);
|
||||
}
|
||||
|
||||
unsafe fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Error> {
|
||||
fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Error> {
|
||||
let mut lines_out = 0;
|
||||
for &msg_id in msglist {
|
||||
if msg_id.is_daymarker() {
|
||||
@@ -252,7 +252,7 @@ unsafe fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Err
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn log_contactlist(context: &Context, contacts: &Vec<u32>) {
|
||||
fn log_contactlist(context: &Context, contacts: &Vec<u32>) {
|
||||
let mut contacts = contacts.clone();
|
||||
if !contacts.contains(&1) {
|
||||
contacts.push(1);
|
||||
@@ -304,7 +304,7 @@ fn chat_prefix(chat: &Chat) -> &'static str {
|
||||
chat.typ.into()
|
||||
}
|
||||
|
||||
pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
||||
pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
||||
let chat_id = *context.cmdline_sel_chat_id.read().unwrap();
|
||||
let mut sel_chat = if chat_id > 0 {
|
||||
Chat::load_from_db(context, chat_id).ok()
|
||||
@@ -315,11 +315,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
let mut args = line.splitn(3, ' ');
|
||||
let arg0 = args.next().unwrap_or_default();
|
||||
let arg1 = args.next().unwrap_or_default();
|
||||
let arg1_c = if arg1.is_empty() {
|
||||
std::ptr::null()
|
||||
} else {
|
||||
arg1.strdup() as *const _
|
||||
};
|
||||
let arg2 = args.next().unwrap_or_default();
|
||||
|
||||
let blobdir = context.get_blobdir();
|
||||
@@ -469,7 +464,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
);
|
||||
}
|
||||
"poke" => {
|
||||
ensure!(0 != poke_spec(context, arg1_c), "Poke failed");
|
||||
ensure!(0 != poke_spec(context, Some(arg1)), "Poke failed");
|
||||
}
|
||||
"reset" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <bits> missing: 1=jobs, 2=peerstates, 4=private keys, 8=rest but server config");
|
||||
@@ -496,7 +491,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
println!("{:#?}", context.get_info());
|
||||
}
|
||||
"interrupt" => {
|
||||
interrupt_inbox_idle(context, true);
|
||||
interrupt_inbox_idle(context);
|
||||
}
|
||||
"maybenetwork" => {
|
||||
maybe_network(context);
|
||||
@@ -588,7 +583,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
let members = chat::get_chat_contacts(context, sel_chat.id);
|
||||
let subtitle = if sel_chat.is_device_talk() {
|
||||
"device-talk".to_string()
|
||||
} else if sel_chat.get_type() == Chattype::Single && members.len() >= 1 {
|
||||
} else if sel_chat.get_type() == Chattype::Single && !members.is_empty() {
|
||||
let contact = Contact::get_by_id(context, members[0])?;
|
||||
contact.get_addr().to_string()
|
||||
} else {
|
||||
@@ -839,6 +834,9 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
msg.set_text(Some(arg1.to_string()));
|
||||
chat::add_device_msg(context, None, Some(&mut msg))?;
|
||||
}
|
||||
"updatedevicechats" => {
|
||||
context.update_device_chats()?;
|
||||
}
|
||||
"listmedia" => {
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
|
||||
@@ -862,11 +860,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
"archive" | "unarchive" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
|
||||
let chat_id = arg1.parse()?;
|
||||
chat::archive(
|
||||
context,
|
||||
chat_id,
|
||||
if arg0 == "archive" { true } else { false },
|
||||
)?;
|
||||
chat::archive(context, chat_id, arg0 == "archive")?;
|
||||
}
|
||||
"delchat" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
|
||||
@@ -887,7 +881,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
}
|
||||
"forward" => {
|
||||
ensure!(
|
||||
!arg1.is_empty() && arg2.is_empty(),
|
||||
!arg1.is_empty() && !arg2.is_empty(),
|
||||
"Arguments <msg-id> <chat-id> expected"
|
||||
);
|
||||
|
||||
@@ -944,7 +938,14 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
let contact = Contact::get_by_id(context, contact_id)?;
|
||||
let name_n_addr = contact.get_name_n_addr();
|
||||
|
||||
let mut res = format!("Contact info for: {}:\n\n", name_n_addr);
|
||||
let mut res = format!(
|
||||
"Contact info for: {}:\nIcon: {}\n",
|
||||
name_n_addr,
|
||||
match contact.get_profile_image(context) {
|
||||
Some(image) => image.to_str().unwrap().to_string(),
|
||||
None => "NoIcon".to_string(),
|
||||
}
|
||||
);
|
||||
|
||||
res += &Contact::get_encrinfo(context, contact_id)?;
|
||||
|
||||
@@ -1011,7 +1012,5 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
_ => bail!("Unknown command: \"{}\" type ? for help.", arg0),
|
||||
}
|
||||
|
||||
free(arg1_c as *mut _);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ fn stop_threads(context: &Context) {
|
||||
println!("Stopping threads");
|
||||
IS_RUNNING.store(false, Ordering::Relaxed);
|
||||
|
||||
interrupt_inbox_idle(context, true);
|
||||
interrupt_inbox_idle(context);
|
||||
interrupt_mvbox_idle(context);
|
||||
interrupt_sentbox_idle(context);
|
||||
interrupt_smtp_idle(context);
|
||||
@@ -235,7 +235,7 @@ impl Completer for DcHelper {
|
||||
}
|
||||
}
|
||||
|
||||
const IMEX_COMMANDS: [&'static str; 12] = [
|
||||
const IMEX_COMMANDS: [&str; 12] = [
|
||||
"initiate-key-transfer",
|
||||
"get-setupcodebegin",
|
||||
"continue-key-transfer",
|
||||
@@ -250,7 +250,7 @@ const IMEX_COMMANDS: [&'static str; 12] = [
|
||||
"stop",
|
||||
];
|
||||
|
||||
const DB_COMMANDS: [&'static str; 11] = [
|
||||
const DB_COMMANDS: [&str; 11] = [
|
||||
"info",
|
||||
"open",
|
||||
"close",
|
||||
@@ -264,7 +264,7 @@ const DB_COMMANDS: [&'static str; 11] = [
|
||||
"housekeeping",
|
||||
];
|
||||
|
||||
const CHAT_COMMANDS: [&'static str; 24] = [
|
||||
const CHAT_COMMANDS: [&str; 24] = [
|
||||
"listchats",
|
||||
"listarchived",
|
||||
"chat",
|
||||
@@ -290,7 +290,7 @@ const CHAT_COMMANDS: [&'static str; 24] = [
|
||||
"unarchive",
|
||||
"delchat",
|
||||
];
|
||||
const MESSAGE_COMMANDS: [&'static str; 8] = [
|
||||
const MESSAGE_COMMANDS: [&str; 8] = [
|
||||
"listmsgs",
|
||||
"msginfo",
|
||||
"listfresh",
|
||||
@@ -300,7 +300,7 @@ const MESSAGE_COMMANDS: [&'static str; 8] = [
|
||||
"unstar",
|
||||
"delmsg",
|
||||
];
|
||||
const CONTACT_COMMANDS: [&'static str; 6] = [
|
||||
const CONTACT_COMMANDS: [&str; 6] = [
|
||||
"listcontacts",
|
||||
"listverified",
|
||||
"addcontact",
|
||||
@@ -308,7 +308,7 @@ const CONTACT_COMMANDS: [&'static str; 6] = [
|
||||
"delcontact",
|
||||
"cleanupcontacts",
|
||||
];
|
||||
const MISC_COMMANDS: [&'static str; 9] = [
|
||||
const MISC_COMMANDS: [&str; 9] = [
|
||||
"getqr", "getbadqr", "checkqr", "event", "fileinfo", "clear", "exit", "quit", "help",
|
||||
];
|
||||
|
||||
@@ -334,8 +334,8 @@ impl Hinter for DcHelper {
|
||||
}
|
||||
}
|
||||
|
||||
static COLORED_PROMPT: &'static str = "\x1b[1;32m> \x1b[0m";
|
||||
static PROMPT: &'static str = "> ";
|
||||
static COLORED_PROMPT: &str = "\x1b[1;32m> \x1b[0m";
|
||||
static PROMPT: &str = "> ";
|
||||
|
||||
impl Highlighter for DcHelper {
|
||||
fn highlight_prompt<'p>(&self, prompt: &'p str) -> Cow<'p, str> {
|
||||
@@ -403,7 +403,7 @@ fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
|
||||
// TODO: ignore "set mail_pw"
|
||||
rl.add_history_entry(line.as_str());
|
||||
let ctx = ctx.clone();
|
||||
match unsafe { handle_cmd(line.trim(), ctx) } {
|
||||
match handle_cmd(line.trim(), ctx) {
|
||||
Ok(ExitResult::Continue) => {}
|
||||
Ok(ExitResult::Exit) => break,
|
||||
Err(err) => println!("Error: {}", err),
|
||||
@@ -434,7 +434,7 @@ enum ExitResult {
|
||||
Exit,
|
||||
}
|
||||
|
||||
unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult, failure::Error> {
|
||||
fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult, failure::Error> {
|
||||
let mut args = line.splitn(2, ' ');
|
||||
let arg0 = args.next().unwrap_or_default();
|
||||
let arg1 = args.next().unwrap_or_default();
|
||||
|
||||
@@ -101,20 +101,10 @@ fn main() {
|
||||
|
||||
thread::sleep(duration);
|
||||
|
||||
// let msglist = dc_get_chat_msgs(&ctx, chat_id, 0, 0);
|
||||
// for i in 0..dc_array_get_cnt(msglist) {
|
||||
// let msg_id = dc_array_get_id(msglist, i);
|
||||
// let msg = dc_get_msg(context, msg_id);
|
||||
// let text = CStr::from_ptr(dc_msg_get_text(msg)).unwrap();
|
||||
// println!("Message {}: {}\n", i + 1, text.to_str().unwrap());
|
||||
// dc_msg_unref(msg);
|
||||
// }
|
||||
// dc_array_unref(msglist);
|
||||
|
||||
println!("stopping threads");
|
||||
|
||||
*running.clone().write().unwrap() = false;
|
||||
deltachat::job::interrupt_inbox_idle(&ctx, true);
|
||||
*running.write().unwrap() = false;
|
||||
deltachat::job::interrupt_inbox_idle(&ctx);
|
||||
deltachat::job::interrupt_smtp_idle(&ctx);
|
||||
|
||||
println!("joining");
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
# copied from http://koushiro.me/2019/04/30/Building-and-Testing-Rust-projects-on-CircleCI/
|
||||
|
||||
version: 2.1
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: ubuntu:18.04
|
||||
|
||||
working_directory: ~/deltachat-core-rust
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Setup build environment
|
||||
command: |
|
||||
apt update
|
||||
apt install -y curl build-essential autoconf libtool git python pkg-config
|
||||
# this will pick default toolchain from `rust-toolchain` file
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --no-modify-path --default-toolchain none -y;
|
||||
source $HOME/.cargo/env
|
||||
no_output_timeout: 1800s
|
||||
|
||||
- run:
|
||||
name: Format
|
||||
command: |
|
||||
export PATH=~/.cargo/bin:$PATH
|
||||
rustup component add rustfmt
|
||||
cargo fmt -- --check
|
||||
|
||||
- run:
|
||||
name: Test
|
||||
command: |
|
||||
export PATH=~/.cargo/bin:$PATH
|
||||
export RUST_BACKTRACE=1
|
||||
cargo test
|
||||
|
||||
workflows:
|
||||
version: 2.1
|
||||
build:
|
||||
jobs:
|
||||
- build
|
||||
@@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "mmime"
|
||||
version = "0.1.2"
|
||||
authors = ["dignifiedquire <dignifiedquire@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT OR Apache-2.0"
|
||||
homepage = "https://github.com/deltachat/deltachat-core-rust"
|
||||
repository = "https://github.com/deltachat/deltachat-core-rust"
|
||||
readme = "README.md"
|
||||
description = "Mime parsing for email"
|
||||
|
||||
|
||||
keywords = ["mail", "mim", "email", "imap", "smtp"]
|
||||
categories = ["std", "email"]
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.54"
|
||||
charset = "0.1.2"
|
||||
memmap = "0.7.0"
|
||||
lazy_static = "1.3.0"
|
||||
rand = "0.6.5"
|
||||
chrono = "0.4.6"
|
||||
hex = "0.4.0"
|
||||
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,23 +0,0 @@
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,4 +0,0 @@
|
||||
This library is primarly distributed under the terms of both the MIT license and
|
||||
the Apache License (Version 2.0).
|
||||
|
||||
See LICENSE-MIT and LICENSE-APACHE for details.
|
||||
@@ -1,16 +0,0 @@
|
||||
# mmime
|
||||
|
||||
[![CircleCI build status][circle-shield]][circle] [![Appveyor build status][appveyor-shield]][appveyor] [![License][license-shield]][license]
|
||||
|
||||
> mmmmmmime parsing
|
||||
|
||||
Base code was compiled using c2rust from libetpan.
|
||||
|
||||
|
||||
|
||||
[circle-shield]: https://img.shields.io/circleci/project/github/dignifiedquire/mmime/master.svg?style=flat-square
|
||||
[circle]: https://circleci.com/gh/dignifiedquire/mmime/
|
||||
[appveyor-shield]: https://ci.appveyor.com/api/projects/status/l26co5rba32knrlu/branch/master?style=flat-square
|
||||
[appveyor]: https://ci.appveyor.com/project/dignifiedquire/mmime/branch/master
|
||||
[license-shield]: https://img.shields.io/badge/License-MIT%2FApache2.0-green.svg?style=flat-square
|
||||
[license]: https://github.com/rpgp/rpgp/blob/master/LICENSE.md
|
||||
@@ -1,32 +0,0 @@
|
||||
use crate::other::*;
|
||||
use libc;
|
||||
use std::ffi::{CStr, CString};
|
||||
|
||||
pub const MAIL_CHARCONV_ERROR_CONV: libc::c_uint = 3;
|
||||
pub const MAIL_CHARCONV_ERROR_MEMORY: libc::c_uint = 2;
|
||||
pub const MAIL_CHARCONV_ERROR_UNKNOWN_CHARSET: libc::c_uint = 1;
|
||||
pub const MAIL_CHARCONV_NO_ERROR: libc::c_uint = 0;
|
||||
|
||||
pub unsafe fn charconv(
|
||||
tocode: *const libc::c_char,
|
||||
fromcode: *const libc::c_char,
|
||||
s: *const libc::c_char,
|
||||
length: size_t,
|
||||
result: *mut *mut libc::c_char,
|
||||
) -> libc::c_int {
|
||||
assert!(!fromcode.is_null(), "invalid fromcode");
|
||||
assert!(!s.is_null(), "invalid input string");
|
||||
if let Some(encoding) =
|
||||
charset::Charset::for_label(CStr::from_ptr(fromcode).to_string_lossy().as_bytes())
|
||||
{
|
||||
let data = std::slice::from_raw_parts(s as *const u8, strlen(s));
|
||||
|
||||
let (res, _, _) = encoding.decode(data);
|
||||
let res_c = CString::new(res.as_bytes()).unwrap_or_default();
|
||||
*result = strdup(res_c.as_ptr()) as *mut _;
|
||||
|
||||
MAIL_CHARCONV_NO_ERROR as libc::c_int
|
||||
} else {
|
||||
MAIL_CHARCONV_ERROR_UNKNOWN_CHARSET as libc::c_int
|
||||
}
|
||||
}
|
||||
@@ -1,427 +0,0 @@
|
||||
use libc;
|
||||
|
||||
use crate::other::*;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct chashdatum {
|
||||
pub data: *mut libc::c_void,
|
||||
pub len: libc::c_uint,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct chash {
|
||||
pub size: libc::c_uint,
|
||||
pub count: libc::c_uint,
|
||||
pub copyvalue: libc::c_int,
|
||||
pub copykey: libc::c_int,
|
||||
pub cells: *mut *mut chashcell,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct chashcell {
|
||||
pub func: libc::c_uint,
|
||||
pub key: chashdatum,
|
||||
pub value: chashdatum,
|
||||
pub next: *mut chashcell,
|
||||
}
|
||||
|
||||
pub type chashiter = chashcell;
|
||||
/* Allocates a new (empty) hash using this initial size and the given flags,
|
||||
specifying which data should be copied in the hash.
|
||||
CHASH_COPYNONE : Keys/Values are not copied.
|
||||
CHASH_COPYKEY : Keys are dupped and freed as needed in the hash.
|
||||
CHASH_COPYVALUE : Values are dupped and freed as needed in the hash.
|
||||
CHASH_COPYALL : Both keys and values are dupped in the hash.
|
||||
*/
|
||||
pub unsafe fn chash_new(mut size: libc::c_uint, mut flags: libc::c_int) -> *mut chash {
|
||||
let mut h: *mut chash = 0 as *mut chash;
|
||||
h = malloc(::std::mem::size_of::<chash>() as libc::size_t) as *mut chash;
|
||||
if h.is_null() {
|
||||
return 0 as *mut chash;
|
||||
}
|
||||
if size < 13i32 as libc::c_uint {
|
||||
size = 13i32 as libc::c_uint
|
||||
}
|
||||
(*h).count = 0i32 as libc::c_uint;
|
||||
(*h).cells = calloc(
|
||||
size as libc::size_t,
|
||||
::std::mem::size_of::<*mut chashcell>() as libc::size_t,
|
||||
) as *mut *mut chashcell;
|
||||
if (*h).cells.is_null() {
|
||||
free(h as *mut libc::c_void);
|
||||
return 0 as *mut chash;
|
||||
}
|
||||
(*h).size = size;
|
||||
(*h).copykey = flags & 1i32;
|
||||
(*h).copyvalue = flags & 2i32;
|
||||
return h;
|
||||
}
|
||||
|
||||
/* Frees a hash */
|
||||
pub unsafe fn chash_free(mut hash: *mut chash) {
|
||||
let mut indx: libc::c_uint = 0;
|
||||
let mut iter: *mut chashiter = 0 as *mut chashiter;
|
||||
let mut next: *mut chashiter = 0 as *mut chashiter;
|
||||
indx = 0i32 as libc::c_uint;
|
||||
while indx < (*hash).size {
|
||||
iter = *(*hash).cells.offset(indx as isize);
|
||||
while !iter.is_null() {
|
||||
next = (*iter).next;
|
||||
if 0 != (*hash).copykey {
|
||||
free((*iter).key.data);
|
||||
}
|
||||
if 0 != (*hash).copyvalue {
|
||||
free((*iter).value.data);
|
||||
}
|
||||
free(iter as *mut libc::c_void);
|
||||
iter = next
|
||||
}
|
||||
indx = indx.wrapping_add(1)
|
||||
}
|
||||
free((*hash).cells as *mut libc::c_void);
|
||||
free(hash as *mut libc::c_void);
|
||||
}
|
||||
|
||||
/* Removes all elements from a hash */
|
||||
pub unsafe fn chash_clear(mut hash: *mut chash) {
|
||||
let mut indx: libc::c_uint = 0;
|
||||
let mut iter: *mut chashiter = 0 as *mut chashiter;
|
||||
let mut next: *mut chashiter = 0 as *mut chashiter;
|
||||
indx = 0i32 as libc::c_uint;
|
||||
while indx < (*hash).size {
|
||||
iter = *(*hash).cells.offset(indx as isize);
|
||||
while !iter.is_null() {
|
||||
next = (*iter).next;
|
||||
if 0 != (*hash).copykey {
|
||||
free((*iter).key.data);
|
||||
}
|
||||
if 0 != (*hash).copyvalue {
|
||||
free((*iter).value.data);
|
||||
}
|
||||
free(iter as *mut libc::c_void);
|
||||
iter = next
|
||||
}
|
||||
indx = indx.wrapping_add(1)
|
||||
}
|
||||
memset(
|
||||
(*hash).cells as *mut libc::c_void,
|
||||
0i32,
|
||||
((*hash).size as libc::size_t)
|
||||
.wrapping_mul(::std::mem::size_of::<*mut chashcell>() as libc::size_t),
|
||||
);
|
||||
(*hash).count = 0i32 as libc::c_uint;
|
||||
}
|
||||
/* Adds an entry in the hash table.
|
||||
Length can be 0 if key/value are strings.
|
||||
If an entry already exists for this key, it is replaced, and its value
|
||||
is returned. Otherwise, the data pointer will be NULL and the length
|
||||
field be set to TRUE or FALSe to indicate success or failure. */
|
||||
pub unsafe fn chash_set(
|
||||
mut hash: *mut chash,
|
||||
mut key: *mut chashdatum,
|
||||
mut value: *mut chashdatum,
|
||||
mut oldvalue: *mut chashdatum,
|
||||
) -> libc::c_int {
|
||||
let mut current_block: u64;
|
||||
let mut func: libc::c_uint = 0;
|
||||
let mut indx: libc::c_uint = 0;
|
||||
let mut iter: *mut chashiter = 0 as *mut chashiter;
|
||||
let mut cell: *mut chashiter = 0 as *mut chashiter;
|
||||
let mut r: libc::c_int = 0;
|
||||
if (*hash).count > (*hash).size.wrapping_mul(3i32 as libc::c_uint) {
|
||||
r = chash_resize(
|
||||
hash,
|
||||
(*hash)
|
||||
.count
|
||||
.wrapping_div(3i32 as libc::c_uint)
|
||||
.wrapping_mul(2i32 as libc::c_uint)
|
||||
.wrapping_add(1i32 as libc::c_uint),
|
||||
);
|
||||
if r < 0i32 {
|
||||
current_block = 17701753836843438419;
|
||||
} else {
|
||||
current_block = 7095457783677275021;
|
||||
}
|
||||
} else {
|
||||
current_block = 7095457783677275021;
|
||||
}
|
||||
match current_block {
|
||||
7095457783677275021 => {
|
||||
func = chash_func((*key).data as *const libc::c_char, (*key).len);
|
||||
indx = func.wrapping_rem((*hash).size);
|
||||
iter = *(*hash).cells.offset(indx as isize);
|
||||
loop {
|
||||
if iter.is_null() {
|
||||
current_block = 17788412896529399552;
|
||||
break;
|
||||
}
|
||||
if (*iter).key.len == (*key).len
|
||||
&& (*iter).func == func
|
||||
&& 0 == memcmp((*iter).key.data, (*key).data, (*key).len as libc::size_t)
|
||||
{
|
||||
/* found, replacing entry */
|
||||
if 0 != (*hash).copyvalue {
|
||||
let mut data: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
data = chash_dup((*value).data, (*value).len);
|
||||
if data.is_null() {
|
||||
current_block = 17701753836843438419;
|
||||
break;
|
||||
}
|
||||
free((*iter).value.data);
|
||||
(*iter).value.data = data as *mut libc::c_void;
|
||||
(*iter).value.len = (*value).len
|
||||
} else {
|
||||
if !oldvalue.is_null() {
|
||||
(*oldvalue).data = (*iter).value.data;
|
||||
(*oldvalue).len = (*iter).value.len
|
||||
}
|
||||
(*iter).value.data = (*value).data;
|
||||
(*iter).value.len = (*value).len
|
||||
}
|
||||
if 0 == (*hash).copykey {
|
||||
(*iter).key.data = (*key).data
|
||||
}
|
||||
if !oldvalue.is_null() {
|
||||
(*oldvalue).data = (*value).data;
|
||||
(*oldvalue).len = (*value).len
|
||||
}
|
||||
return 0i32;
|
||||
} else {
|
||||
iter = (*iter).next
|
||||
}
|
||||
}
|
||||
match current_block {
|
||||
17701753836843438419 => {}
|
||||
_ => {
|
||||
if !oldvalue.is_null() {
|
||||
(*oldvalue).data = 0 as *mut libc::c_void;
|
||||
(*oldvalue).len = 0i32 as libc::c_uint
|
||||
}
|
||||
cell = malloc(::std::mem::size_of::<chashcell>() as libc::size_t)
|
||||
as *mut chashcell;
|
||||
if !cell.is_null() {
|
||||
if 0 != (*hash).copykey {
|
||||
(*cell).key.data =
|
||||
chash_dup((*key).data, (*key).len) as *mut libc::c_void;
|
||||
if (*cell).key.data.is_null() {
|
||||
current_block = 4267898785354516004;
|
||||
} else {
|
||||
current_block = 7226443171521532240;
|
||||
}
|
||||
} else {
|
||||
(*cell).key.data = (*key).data;
|
||||
current_block = 7226443171521532240;
|
||||
}
|
||||
match current_block {
|
||||
7226443171521532240 => {
|
||||
(*cell).key.len = (*key).len;
|
||||
if 0 != (*hash).copyvalue {
|
||||
(*cell).value.data =
|
||||
chash_dup((*value).data, (*value).len) as *mut libc::c_void;
|
||||
if (*cell).value.data.is_null() {
|
||||
if 0 != (*hash).copykey {
|
||||
free((*cell).key.data);
|
||||
}
|
||||
current_block = 4267898785354516004;
|
||||
} else {
|
||||
current_block = 6717214610478484138;
|
||||
}
|
||||
} else {
|
||||
(*cell).value.data = (*value).data;
|
||||
current_block = 6717214610478484138;
|
||||
}
|
||||
match current_block {
|
||||
4267898785354516004 => {}
|
||||
_ => {
|
||||
(*cell).value.len = (*value).len;
|
||||
(*cell).func = func;
|
||||
(*cell).next = *(*hash).cells.offset(indx as isize);
|
||||
let ref mut fresh0 = *(*hash).cells.offset(indx as isize);
|
||||
*fresh0 = cell;
|
||||
(*hash).count = (*hash).count.wrapping_add(1);
|
||||
return 0i32;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
free(cell as *mut libc::c_void);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
return -1i32;
|
||||
}
|
||||
#[inline]
|
||||
unsafe fn chash_dup(mut data: *const libc::c_void, mut len: libc::c_uint) -> *mut libc::c_char {
|
||||
let mut r: *mut libc::c_void = 0 as *mut libc::c_void;
|
||||
r = malloc(len as libc::size_t) as *mut libc::c_char as *mut libc::c_void;
|
||||
if r.is_null() {
|
||||
return 0 as *mut libc::c_char;
|
||||
}
|
||||
memcpy(r, data, len as libc::size_t);
|
||||
return r as *mut libc::c_char;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn chash_func(mut key: *const libc::c_char, mut len: libc::c_uint) -> libc::c_uint {
|
||||
let mut c: libc::c_uint = 5381i32 as libc::c_uint;
|
||||
let mut k: *const libc::c_char = key;
|
||||
loop {
|
||||
let fresh1 = len;
|
||||
len = len.wrapping_sub(1);
|
||||
if !(0 != fresh1) {
|
||||
break;
|
||||
}
|
||||
let fresh2 = k;
|
||||
k = k.offset(1);
|
||||
c = (c << 5i32)
|
||||
.wrapping_add(c)
|
||||
.wrapping_add(*fresh2 as libc::c_uint)
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/* Resizes the hash table to the passed size. */
|
||||
pub unsafe fn chash_resize(mut hash: *mut chash, mut size: libc::c_uint) -> libc::c_int {
|
||||
let mut cells: *mut *mut chashcell = 0 as *mut *mut chashcell;
|
||||
let mut indx: libc::c_uint = 0;
|
||||
let mut nindx: libc::c_uint = 0;
|
||||
let mut iter: *mut chashiter = 0 as *mut chashiter;
|
||||
let mut next: *mut chashiter = 0 as *mut chashiter;
|
||||
if (*hash).size == size {
|
||||
return 0i32;
|
||||
}
|
||||
cells = calloc(
|
||||
size as libc::size_t,
|
||||
::std::mem::size_of::<*mut chashcell>() as libc::size_t,
|
||||
) as *mut *mut chashcell;
|
||||
if cells.is_null() {
|
||||
return -1i32;
|
||||
}
|
||||
indx = 0i32 as libc::c_uint;
|
||||
while indx < (*hash).size {
|
||||
iter = *(*hash).cells.offset(indx as isize);
|
||||
while !iter.is_null() {
|
||||
next = (*iter).next;
|
||||
nindx = (*iter).func.wrapping_rem(size);
|
||||
(*iter).next = *cells.offset(nindx as isize);
|
||||
let ref mut fresh3 = *cells.offset(nindx as isize);
|
||||
*fresh3 = iter;
|
||||
iter = next
|
||||
}
|
||||
indx = indx.wrapping_add(1)
|
||||
}
|
||||
free((*hash).cells as *mut libc::c_void);
|
||||
(*hash).size = size;
|
||||
(*hash).cells = cells;
|
||||
return 0i32;
|
||||
}
|
||||
|
||||
/* Retrieves the data associated to the key if it is found in the hash table.
|
||||
The data pointer and the length will be NULL if not found*/
|
||||
pub unsafe fn chash_get(
|
||||
mut hash: *mut chash,
|
||||
mut key: *mut chashdatum,
|
||||
mut result: *mut chashdatum,
|
||||
) -> libc::c_int {
|
||||
let mut func: libc::c_uint = 0;
|
||||
let mut iter: *mut chashiter = 0 as *mut chashiter;
|
||||
func = chash_func((*key).data as *const libc::c_char, (*key).len);
|
||||
iter = *(*hash)
|
||||
.cells
|
||||
.offset(func.wrapping_rem((*hash).size) as isize);
|
||||
while !iter.is_null() {
|
||||
if (*iter).key.len == (*key).len
|
||||
&& (*iter).func == func
|
||||
&& 0 == memcmp((*iter).key.data, (*key).data, (*key).len as libc::size_t)
|
||||
{
|
||||
*result = (*iter).value;
|
||||
return 0i32;
|
||||
}
|
||||
iter = (*iter).next
|
||||
}
|
||||
return -1i32;
|
||||
}
|
||||
/* Removes the entry associated to this key if it is found in the hash table,
|
||||
and returns its contents if not dupped (otherwise, pointer will be NULL
|
||||
and len TRUE). If entry is not found both pointer and len will be NULL. */
|
||||
pub unsafe fn chash_delete(
|
||||
mut hash: *mut chash,
|
||||
mut key: *mut chashdatum,
|
||||
mut oldvalue: *mut chashdatum,
|
||||
) -> libc::c_int {
|
||||
/* chashdatum result = { NULL, TRUE }; */
|
||||
let mut func: libc::c_uint = 0;
|
||||
let mut indx: libc::c_uint = 0;
|
||||
let mut iter: *mut chashiter = 0 as *mut chashiter;
|
||||
let mut old: *mut chashiter = 0 as *mut chashiter;
|
||||
func = chash_func((*key).data as *const libc::c_char, (*key).len);
|
||||
indx = func.wrapping_rem((*hash).size);
|
||||
old = 0 as *mut chashiter;
|
||||
iter = *(*hash).cells.offset(indx as isize);
|
||||
while !iter.is_null() {
|
||||
if (*iter).key.len == (*key).len
|
||||
&& (*iter).func == func
|
||||
&& 0 == memcmp((*iter).key.data, (*key).data, (*key).len as libc::size_t)
|
||||
{
|
||||
if !old.is_null() {
|
||||
(*old).next = (*iter).next
|
||||
} else {
|
||||
let ref mut fresh4 = *(*hash).cells.offset(indx as isize);
|
||||
*fresh4 = (*iter).next
|
||||
}
|
||||
if 0 != (*hash).copykey {
|
||||
free((*iter).key.data);
|
||||
}
|
||||
if 0 != (*hash).copyvalue {
|
||||
free((*iter).value.data);
|
||||
} else if !oldvalue.is_null() {
|
||||
(*oldvalue).data = (*iter).value.data;
|
||||
(*oldvalue).len = (*iter).value.len
|
||||
}
|
||||
free(iter as *mut libc::c_void);
|
||||
(*hash).count = (*hash).count.wrapping_sub(1);
|
||||
return 0i32;
|
||||
}
|
||||
old = iter;
|
||||
iter = (*iter).next
|
||||
}
|
||||
return -1i32;
|
||||
}
|
||||
/* Returns an iterator to the first non-empty entry of the hash table */
|
||||
pub unsafe fn chash_begin(mut hash: *mut chash) -> *mut chashiter {
|
||||
let mut iter: *mut chashiter = 0 as *mut chashiter;
|
||||
let mut indx: libc::c_uint = 0i32 as libc::c_uint;
|
||||
iter = *(*hash).cells.offset(0isize);
|
||||
while iter.is_null() {
|
||||
indx = indx.wrapping_add(1);
|
||||
if indx >= (*hash).size {
|
||||
return 0 as *mut chashiter;
|
||||
}
|
||||
iter = *(*hash).cells.offset(indx as isize)
|
||||
}
|
||||
return iter;
|
||||
}
|
||||
/* Returns the next non-empty entry of the hash table */
|
||||
pub unsafe fn chash_next(mut hash: *mut chash, mut iter: *mut chashiter) -> *mut chashiter {
|
||||
let mut indx: libc::c_uint = 0;
|
||||
if iter.is_null() {
|
||||
return 0 as *mut chashiter;
|
||||
}
|
||||
indx = (*iter).func.wrapping_rem((*hash).size);
|
||||
iter = (*iter).next;
|
||||
while iter.is_null() {
|
||||
indx = indx.wrapping_add(1);
|
||||
if indx >= (*hash).size {
|
||||
return 0 as *mut chashiter;
|
||||
}
|
||||
iter = *(*hash).cells.offset(indx as isize)
|
||||
}
|
||||
return iter;
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
use libc;
|
||||
|
||||
use crate::other::*;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct clistcell {
|
||||
pub data: *mut libc::c_void,
|
||||
pub previous: *mut clistcell,
|
||||
pub next: *mut clistcell,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[repr(C)]
|
||||
pub struct clist {
|
||||
pub first: *mut clistcell,
|
||||
pub last: *mut clistcell,
|
||||
pub count: libc::c_int,
|
||||
}
|
||||
|
||||
impl Default for clist {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
first: std::ptr::null_mut(),
|
||||
last: std::ptr::null_mut(),
|
||||
count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for clist {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let mut l1 = self.first;
|
||||
while !l1.is_null() {
|
||||
let l2 = (*l1).next;
|
||||
free(l1 as *mut libc::c_void);
|
||||
l1 = l2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type clistiter = clistcell;
|
||||
pub struct CListIterator {
|
||||
cur: *mut clistiter,
|
||||
}
|
||||
impl Iterator for CListIterator {
|
||||
type Item = *mut libc::c_void;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
unsafe {
|
||||
if self.cur.is_null() {
|
||||
None
|
||||
} else {
|
||||
let data = (*self.cur).data;
|
||||
self.cur = (*self.cur).next;
|
||||
Some(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for &clist {
|
||||
type Item = *mut libc::c_void;
|
||||
type IntoIter = CListIterator;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
return CListIterator { cur: self.first };
|
||||
}
|
||||
}
|
||||
|
||||
pub type clist_func =
|
||||
Option<unsafe extern "C" fn(_: *mut libc::c_void, _: *mut libc::c_void) -> ()>;
|
||||
|
||||
/* Allocate a new pointer list */
|
||||
pub fn clist_new() -> *mut clist {
|
||||
Box::into_raw(Box::new(Default::default()))
|
||||
}
|
||||
/* Destroys a list. Data pointed by data pointers is NOT freed. */
|
||||
pub unsafe fn clist_free(mut lst: *mut clist) {
|
||||
Box::from_raw(lst);
|
||||
}
|
||||
/* Inserts this data pointer after the element pointed by the iterator */
|
||||
pub unsafe fn clist_insert_after(
|
||||
mut lst: *mut clist,
|
||||
mut iter: *mut clistiter,
|
||||
mut data: *mut libc::c_void,
|
||||
) -> libc::c_int {
|
||||
let mut c: *mut clistcell = 0 as *mut clistcell;
|
||||
c = malloc(::std::mem::size_of::<clistcell>() as libc::size_t) as *mut clistcell;
|
||||
if c.is_null() {
|
||||
return -1i32;
|
||||
}
|
||||
(*c).data = data;
|
||||
(*lst).count += 1;
|
||||
if (*lst).first == (*lst).last && (*lst).last.is_null() {
|
||||
(*c).next = 0 as *mut clistcell;
|
||||
(*c).previous = (*c).next;
|
||||
(*lst).last = c;
|
||||
(*lst).first = (*lst).last;
|
||||
return 0i32;
|
||||
}
|
||||
if iter.is_null() {
|
||||
(*c).previous = (*lst).last;
|
||||
(*(*c).previous).next = c;
|
||||
(*c).next = 0 as *mut clistcell;
|
||||
(*lst).last = c;
|
||||
return 0i32;
|
||||
}
|
||||
(*c).previous = iter;
|
||||
(*c).next = (*iter).next;
|
||||
if !(*c).next.is_null() {
|
||||
(*(*c).next).previous = c
|
||||
} else {
|
||||
(*lst).last = c
|
||||
}
|
||||
(*(*c).previous).next = c;
|
||||
return 0i32;
|
||||
}
|
||||
/* Deletes the element pointed by the iterator.
|
||||
Returns an iterator to the next element. */
|
||||
pub unsafe fn clist_delete(mut lst: *mut clist, mut iter: *mut clistiter) -> *mut clistiter {
|
||||
let mut ret: *mut clistiter = 0 as *mut clistiter;
|
||||
if iter.is_null() {
|
||||
return 0 as *mut clistiter;
|
||||
}
|
||||
if !(*iter).previous.is_null() {
|
||||
(*(*iter).previous).next = (*iter).next
|
||||
} else {
|
||||
(*lst).first = (*iter).next
|
||||
}
|
||||
if !(*iter).next.is_null() {
|
||||
(*(*iter).next).previous = (*iter).previous;
|
||||
ret = (*iter).next
|
||||
} else {
|
||||
(*lst).last = (*iter).previous;
|
||||
ret = 0 as *mut clistiter
|
||||
}
|
||||
free(iter as *mut libc::c_void);
|
||||
(*lst).count -= 1;
|
||||
return ret;
|
||||
}
|
||||
pub unsafe fn clist_foreach(
|
||||
mut lst: *mut clist,
|
||||
mut func: clist_func,
|
||||
mut data: *mut libc::c_void,
|
||||
) {
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
cur = (*lst).first;
|
||||
while !cur.is_null() {
|
||||
func.expect("non-null function pointer")((*cur).data, data);
|
||||
cur = (*cur).next
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn clist_nth_data(mut lst: *mut clist, mut indx: libc::c_int) -> *mut libc::c_void {
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
cur = internal_clist_nth(lst, indx);
|
||||
if cur.is_null() {
|
||||
return 0 as *mut libc::c_void;
|
||||
}
|
||||
return (*cur).data;
|
||||
}
|
||||
#[inline]
|
||||
unsafe fn internal_clist_nth(mut lst: *mut clist, mut indx: libc::c_int) -> *mut clistiter {
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
cur = (*lst).first;
|
||||
while indx > 0i32 && !cur.is_null() {
|
||||
cur = (*cur).next;
|
||||
indx -= 1
|
||||
}
|
||||
if cur.is_null() {
|
||||
return 0 as *mut clistiter;
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
pub unsafe fn clist_nth(mut lst: *mut clist, mut indx: libc::c_int) -> *mut clistiter {
|
||||
return internal_clist_nth(lst, indx);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::ptr;
|
||||
#[test]
|
||||
fn test_clist_iterator() {
|
||||
unsafe {
|
||||
let mut c = clist_new();
|
||||
assert!(!c.is_null());
|
||||
clist_insert_after(c, ptr::null_mut(), clist_nth as _);
|
||||
assert_eq!((*c).count, 1);
|
||||
|
||||
/* Only one iteration */
|
||||
for data in &*c {
|
||||
assert_eq!(data, clist_nth as _);
|
||||
}
|
||||
assert_eq!((*c).count, 1);
|
||||
|
||||
clist_free(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
pub const MAIL_ERROR_SSL: libc::c_uint = 58;
|
||||
pub const MAIL_ERROR_FOLDER: libc::c_uint = 57;
|
||||
pub const MAIL_ERROR_UNABLE: libc::c_uint = 56;
|
||||
pub const MAIL_ERROR_SYSTEM: libc::c_uint = 55;
|
||||
pub const MAIL_ERROR_COMMAND: libc::c_uint = 54;
|
||||
pub const MAIL_ERROR_SEND: libc::c_uint = 53;
|
||||
pub const MAIL_ERROR_CHAR_ENCODING_FAILED: libc::c_uint = 52;
|
||||
pub const MAIL_ERROR_SUBJECT_NOT_FOUND: libc::c_uint = 51;
|
||||
/* 50 */
|
||||
pub const MAIL_ERROR_PROGRAM_ERROR: libc::c_uint = 50;
|
||||
pub const MAIL_ERROR_NO_PERMISSION: libc::c_uint = 49;
|
||||
pub const MAIL_ERROR_COMMAND_NOT_SUPPORTED: libc::c_uint = 48;
|
||||
pub const MAIL_ERROR_NO_APOP: libc::c_uint = 47;
|
||||
pub const MAIL_ERROR_READONLY: libc::c_uint = 46;
|
||||
pub const MAIL_ERROR_FATAL: libc::c_uint = 45;
|
||||
pub const MAIL_ERROR_CLOSE: libc::c_uint = 44;
|
||||
pub const MAIL_ERROR_CAPABILITY: libc::c_uint = 43;
|
||||
pub const MAIL_ERROR_PROTOCOL: libc::c_uint = 42;
|
||||
/* misc errors */
|
||||
pub const MAIL_ERROR_MISC: libc::c_uint = 41;
|
||||
/* 40 */
|
||||
pub const MAIL_ERROR_EXPUNGE: libc::c_uint = 40;
|
||||
pub const MAIL_ERROR_NO_TLS: libc::c_uint = 39;
|
||||
pub const MAIL_ERROR_CACHE_MISS: libc::c_uint = 38;
|
||||
pub const MAIL_ERROR_STARTTLS: libc::c_uint = 37;
|
||||
pub const MAIL_ERROR_MOVE: libc::c_uint = 36;
|
||||
pub const MAIL_ERROR_FOLDER_NOT_FOUND: libc::c_uint = 35;
|
||||
pub const MAIL_ERROR_REMOVE: libc::c_uint = 34;
|
||||
pub const MAIL_ERROR_PART_NOT_FOUND: libc::c_uint = 33;
|
||||
pub const MAIL_ERROR_INVAL: libc::c_uint = 32;
|
||||
pub const MAIL_ERROR_PARSE: libc::c_uint = 31;
|
||||
/* 30 */
|
||||
pub const MAIL_ERROR_MSG_NOT_FOUND: libc::c_uint = 30;
|
||||
pub const MAIL_ERROR_DISKSPACE: libc::c_uint = 29;
|
||||
pub const MAIL_ERROR_SEARCH: libc::c_uint = 28;
|
||||
pub const MAIL_ERROR_STORE: libc::c_uint = 27;
|
||||
pub const MAIL_ERROR_FETCH: libc::c_uint = 26;
|
||||
pub const MAIL_ERROR_COPY: libc::c_uint = 25;
|
||||
pub const MAIL_ERROR_APPEND: libc::c_uint = 24;
|
||||
pub const MAIL_ERROR_LSUB: libc::c_uint = 23;
|
||||
pub const MAIL_ERROR_LIST: libc::c_uint = 22;
|
||||
pub const MAIL_ERROR_UNSUBSCRIBE: libc::c_uint = 21;
|
||||
/* 20 */
|
||||
pub const MAIL_ERROR_SUBSCRIBE: libc::c_uint = 20;
|
||||
pub const MAIL_ERROR_STATUS: libc::c_uint = 19;
|
||||
pub const MAIL_ERROR_MEMORY: libc::c_uint = 18;
|
||||
pub const MAIL_ERROR_SELECT: libc::c_uint = 17;
|
||||
pub const MAIL_ERROR_EXAMINE: libc::c_uint = 16;
|
||||
pub const MAIL_ERROR_CHECK: libc::c_uint = 15;
|
||||
pub const MAIL_ERROR_RENAME: libc::c_uint = 14;
|
||||
pub const MAIL_ERROR_NOOP: libc::c_uint = 13;
|
||||
pub const MAIL_ERROR_LOGOUT: libc::c_uint = 12;
|
||||
pub const MAIL_ERROR_DELETE: libc::c_uint = 11;
|
||||
/* 10 */
|
||||
pub const MAIL_ERROR_CREATE: libc::c_uint = 10;
|
||||
pub const MAIL_ERROR_LOGIN: libc::c_uint = 9;
|
||||
pub const MAIL_ERROR_STREAM: libc::c_uint = 8;
|
||||
pub const MAIL_ERROR_FILE: libc::c_uint = 7;
|
||||
pub const MAIL_ERROR_BAD_STATE: libc::c_uint = 6;
|
||||
pub const MAIL_ERROR_CONNECT: libc::c_uint = 5;
|
||||
pub const MAIL_ERROR_UNKNOWN: libc::c_uint = 4;
|
||||
pub const MAIL_ERROR_NOT_IMPLEMENTED: libc::c_uint = 3;
|
||||
pub const MAIL_NO_ERROR_NON_AUTHENTICATED: libc::c_uint = 2;
|
||||
pub const MAIL_NO_ERROR_AUTHENTICATED: libc::c_uint = 1;
|
||||
pub const MAIL_NO_ERROR: libc::c_uint = 0;
|
||||
|
||||
pub const MAILIMF_ERROR_FILE: libc::c_uint = 4;
|
||||
pub const MAILIMF_ERROR_INVAL: libc::c_uint = 3;
|
||||
pub const MAILIMF_ERROR_MEMORY: libc::c_uint = 2;
|
||||
pub const MAILIMF_ERROR_PARSE: libc::c_uint = 1;
|
||||
pub const MAILIMF_NO_ERROR: libc::c_uint = 0;
|
||||
@@ -1,386 +0,0 @@
|
||||
use crate::clist::*;
|
||||
|
||||
use crate::mailimf::types::*;
|
||||
use crate::mailmime::types::*;
|
||||
|
||||
use std::ffi::CStr;
|
||||
|
||||
pub unsafe fn display_mime(mut mime: *mut Mailmime) {
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
println!("{}", (*mime).mm_type);
|
||||
|
||||
match (*mime).mm_type as u32 {
|
||||
MAILMIME_SINGLE => {
|
||||
println!("single part");
|
||||
}
|
||||
MAILMIME_MULTIPLE => {
|
||||
println!("multipart");
|
||||
}
|
||||
MAILMIME_MESSAGE => println!("message"),
|
||||
_ => {}
|
||||
}
|
||||
if !(*mime).mm_mime_fields.is_null() {
|
||||
if !(*(*(*mime).mm_mime_fields).fld_list).first.is_null() {
|
||||
print!("MIME headers begin");
|
||||
display_mime_fields((*mime).mm_mime_fields);
|
||||
println!("MIME headers end");
|
||||
}
|
||||
}
|
||||
display_mime_content((*mime).mm_content_type);
|
||||
match (*mime).mm_type as u32 {
|
||||
MAILMIME_SINGLE => {
|
||||
display_mime_data((*mime).mm_data.mm_single);
|
||||
}
|
||||
MAILMIME_MULTIPLE => {
|
||||
cur = (*(*mime).mm_data.mm_multipart.mm_mp_list).first;
|
||||
while !cur.is_null() {
|
||||
display_mime(
|
||||
(if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
}) as *mut Mailmime,
|
||||
);
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
}
|
||||
MAILMIME_MESSAGE => {
|
||||
if !(*mime).mm_data.mm_message.mm_fields.is_null() {
|
||||
if !(*(*(*mime).mm_data.mm_message.mm_fields).fld_list)
|
||||
.first
|
||||
.is_null()
|
||||
{
|
||||
println!("headers begin");
|
||||
display_fields((*mime).mm_data.mm_message.mm_fields);
|
||||
println!("headers end");
|
||||
}
|
||||
if !(*mime).mm_data.mm_message.mm_msg_mime.is_null() {
|
||||
display_mime((*mime).mm_data.mm_message.mm_msg_mime);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
unsafe fn display_mime_content(mut content_type: *mut mailmime_content) {
|
||||
print!("type: ");
|
||||
display_mime_type((*content_type).ct_type);
|
||||
println!(
|
||||
"/{}",
|
||||
CStr::from_ptr((*content_type).ct_subtype).to_str().unwrap()
|
||||
);
|
||||
}
|
||||
unsafe fn display_mime_type(mut type_0: *mut mailmime_type) {
|
||||
match (*type_0).tp_type {
|
||||
1 => {
|
||||
display_mime_discrete_type((*type_0).tp_data.tp_discrete_type);
|
||||
}
|
||||
2 => {
|
||||
display_mime_composite_type((*type_0).tp_data.tp_composite_type);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
unsafe fn display_mime_composite_type(mut ct: *mut mailmime_composite_type) {
|
||||
match (*ct).ct_type {
|
||||
1 => {
|
||||
print!("message");
|
||||
}
|
||||
2 => {
|
||||
print!("multipart");
|
||||
}
|
||||
3 => {
|
||||
print!("{}", CStr::from_ptr((*ct).ct_token).to_str().unwrap());
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
unsafe fn display_mime_discrete_type(mut discrete_type: *mut mailmime_discrete_type) {
|
||||
match (*discrete_type).dt_type {
|
||||
1 => {
|
||||
print!("text");
|
||||
}
|
||||
2 => {
|
||||
print!("image");
|
||||
}
|
||||
3 => {
|
||||
print!("audio");
|
||||
}
|
||||
4 => {
|
||||
print!("video");
|
||||
}
|
||||
5 => {
|
||||
print!("application");
|
||||
}
|
||||
6 => {
|
||||
print!("{}", (*discrete_type).dt_extension as u8 as char);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
pub unsafe fn display_mime_data(mut data: *mut mailmime_data) {
|
||||
match (*data).dt_type {
|
||||
0 => {
|
||||
println!(
|
||||
"data : {} bytes",
|
||||
(*data).dt_data.dt_text.dt_length as libc::c_uint,
|
||||
);
|
||||
}
|
||||
1 => {
|
||||
println!(
|
||||
"data (file) : {}",
|
||||
CStr::from_ptr((*data).dt_data.dt_filename)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
unsafe fn display_mime_dsp_parm(mut param: *mut mailmime_disposition_parm) {
|
||||
match (*param).pa_type {
|
||||
0 => {
|
||||
println!(
|
||||
"filename: {}",
|
||||
CStr::from_ptr((*param).pa_data.pa_filename)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
unsafe fn display_mime_disposition(mut disposition: *mut mailmime_disposition) {
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
cur = (*(*disposition).dsp_parms).first;
|
||||
while !cur.is_null() {
|
||||
let mut param: *mut mailmime_disposition_parm = 0 as *mut mailmime_disposition_parm;
|
||||
param = (if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
}) as *mut mailmime_disposition_parm;
|
||||
display_mime_dsp_parm(param);
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe fn display_mime_field(mut field: *mut mailmime_field) {
|
||||
match (*field).fld_type {
|
||||
1 => {
|
||||
print!("content-type: ");
|
||||
display_mime_content((*field).fld_data.fld_content);
|
||||
println!("");
|
||||
}
|
||||
6 => {
|
||||
display_mime_disposition((*field).fld_data.fld_disposition);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
unsafe fn display_mime_fields(mut fields: *mut mailmime_fields) {
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
cur = (*(*fields).fld_list).first;
|
||||
while !cur.is_null() {
|
||||
let mut field: *mut mailmime_field = 0 as *mut mailmime_field;
|
||||
field = (if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
}) as *mut mailmime_field;
|
||||
display_mime_field(field);
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe fn display_date_time(mut d: *mut mailimf_date_time) {
|
||||
print!(
|
||||
"{:02}/{:02}/{:02} {:02}:{:02}:{:02} +{:04}",
|
||||
(*d).dt_day,
|
||||
(*d).dt_month,
|
||||
(*d).dt_year,
|
||||
(*d).dt_hour,
|
||||
(*d).dt_min,
|
||||
(*d).dt_sec,
|
||||
(*d).dt_zone,
|
||||
);
|
||||
}
|
||||
unsafe fn display_orig_date(mut orig_date: *mut mailimf_orig_date) {
|
||||
display_date_time((*orig_date).dt_date_time);
|
||||
}
|
||||
unsafe fn display_mailbox(mut mb: *mut mailimf_mailbox) {
|
||||
if !(*mb).mb_display_name.is_null() {
|
||||
print!(
|
||||
"{}",
|
||||
CStr::from_ptr((*mb).mb_display_name).to_str().unwrap()
|
||||
);
|
||||
}
|
||||
print!("<{}>", CStr::from_ptr((*mb).mb_addr_spec).to_str().unwrap());
|
||||
}
|
||||
unsafe fn display_mailbox_list(mut mb_list: *mut mailimf_mailbox_list) {
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
cur = (*(*mb_list).mb_list).first;
|
||||
while !cur.is_null() {
|
||||
let mut mb: *mut mailimf_mailbox = 0 as *mut mailimf_mailbox;
|
||||
mb = (if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
}) as *mut mailimf_mailbox;
|
||||
display_mailbox(mb);
|
||||
if !if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
}
|
||||
.is_null()
|
||||
{
|
||||
print!(", ");
|
||||
}
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe fn display_group(mut group: *mut mailimf_group) {
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
print!(
|
||||
"{}: ",
|
||||
CStr::from_ptr((*group).grp_display_name).to_str().unwrap()
|
||||
);
|
||||
cur = (*(*(*group).grp_mb_list).mb_list).first;
|
||||
while !cur.is_null() {
|
||||
let mut mb: *mut mailimf_mailbox = 0 as *mut mailimf_mailbox;
|
||||
mb = (if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
}) as *mut mailimf_mailbox;
|
||||
display_mailbox(mb);
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
print!("; ");
|
||||
}
|
||||
unsafe fn display_address(mut a: *mut mailimf_address) {
|
||||
match (*a).ad_type {
|
||||
2 => {
|
||||
display_group((*a).ad_data.ad_group);
|
||||
}
|
||||
1 => {
|
||||
display_mailbox((*a).ad_data.ad_mailbox);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
unsafe fn display_address_list(mut addr_list: *mut mailimf_address_list) {
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
cur = (*(*addr_list).ad_list).first;
|
||||
while !cur.is_null() {
|
||||
let mut addr: *mut mailimf_address = 0 as *mut mailimf_address;
|
||||
addr = (if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
}) as *mut mailimf_address;
|
||||
display_address(addr);
|
||||
if !if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
}
|
||||
.is_null()
|
||||
{
|
||||
print!(", ");
|
||||
}
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe fn display_from(mut from: *mut mailimf_from) {
|
||||
display_mailbox_list((*from).frm_mb_list);
|
||||
}
|
||||
unsafe fn display_to(mut to: *mut mailimf_to) {
|
||||
display_address_list((*to).to_addr_list);
|
||||
}
|
||||
unsafe fn display_cc(mut cc: *mut mailimf_cc) {
|
||||
display_address_list((*cc).cc_addr_list);
|
||||
}
|
||||
unsafe fn display_subject(mut subject: *mut mailimf_subject) {
|
||||
print!("{}", CStr::from_ptr((*subject).sbj_value).to_str().unwrap());
|
||||
}
|
||||
unsafe fn display_field(mut field: *mut mailimf_field) {
|
||||
match (*field).fld_type {
|
||||
9 => {
|
||||
print!("Date: ");
|
||||
display_orig_date((*field).fld_data.fld_orig_date);
|
||||
println!("");
|
||||
}
|
||||
10 => {
|
||||
print!("From: ");
|
||||
display_from((*field).fld_data.fld_from);
|
||||
println!("");
|
||||
}
|
||||
13 => {
|
||||
print!("To: ");
|
||||
display_to((*field).fld_data.fld_to);
|
||||
println!("");
|
||||
}
|
||||
14 => {
|
||||
print!("Cc: ");
|
||||
display_cc((*field).fld_data.fld_cc);
|
||||
println!("");
|
||||
}
|
||||
19 => {
|
||||
print!("Subject: ");
|
||||
display_subject((*field).fld_data.fld_subject);
|
||||
println!("");
|
||||
}
|
||||
16 => {
|
||||
println!(
|
||||
"Message-ID: {}",
|
||||
CStr::from_ptr((*(*field).fld_data.fld_message_id).mid_value)
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
unsafe fn display_fields(mut fields: *mut mailimf_fields) {
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
cur = (*(*fields).fld_list).first;
|
||||
while !cur.is_null() {
|
||||
let mut f: *mut mailimf_field = 0 as *mut mailimf_field;
|
||||
f = (if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
}) as *mut mailimf_field;
|
||||
display_field(f);
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
#![deny(clippy::correctness)]
|
||||
// TODO: make all of these errors, such that clippy actually passes.
|
||||
#![warn(clippy::all, clippy::perf, clippy::not_unsafe_ptr_arg_deref)]
|
||||
// This is nice, but for now just annoying.
|
||||
#![allow(clippy::unreadable_literal)]
|
||||
#![feature(ptr_wrapping_offset_from)]
|
||||
#![allow(unused_attributes)]
|
||||
#![allow(unused_variables)]
|
||||
#![allow(mutable_transmutes)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(unused_assignments)]
|
||||
#![allow(unused_mut)]
|
||||
#![allow(unused_must_use)]
|
||||
#![feature(extern_types)]
|
||||
#![feature(const_raw_ptr_to_usize_cast)]
|
||||
|
||||
pub mod charconv;
|
||||
pub mod chash;
|
||||
pub mod clist;
|
||||
pub mod display;
|
||||
pub mod mailimf;
|
||||
pub mod mailmime;
|
||||
pub mod mmapstring;
|
||||
pub mod other;
|
||||
|
||||
pub use self::charconv::*;
|
||||
pub use self::chash::*;
|
||||
pub use self::clist::*;
|
||||
pub use self::display::*;
|
||||
pub use self::mailimf::*;
|
||||
pub use self::mailmime::*;
|
||||
pub use self::mmapstring::*;
|
||||
pub use self::other::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn mailmime_parse_test() {
|
||||
unsafe {
|
||||
let data = "MIME-Version: 1.0\
|
||||
Content-Type: multipart/mixed; boundary=frontier\
|
||||
\
|
||||
This is a message with multiple parts in MIME format.\
|
||||
--frontier\
|
||||
Content-Type: text/plain\
|
||||
\
|
||||
This is the body of the message.\
|
||||
--frontier\
|
||||
Content-Type: application/octet-stream\
|
||||
Content-Transfer-Encoding: base64\
|
||||
\
|
||||
PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\
|
||||
Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==\
|
||||
--frontier--";
|
||||
let c_data = std::ffi::CString::new(data).unwrap();
|
||||
|
||||
let mut current_index = 0;
|
||||
let mut mime = std::ptr::null_mut();
|
||||
let res = crate::mailmime::content::mailmime_parse(
|
||||
c_data.as_ptr(),
|
||||
data.len() as usize,
|
||||
&mut current_index,
|
||||
&mut mime,
|
||||
);
|
||||
|
||||
assert_eq!(res, MAIL_NO_ERROR as libc::c_int);
|
||||
assert!(!mime.is_null());
|
||||
|
||||
display_mime(mime);
|
||||
|
||||
mailmime::types::mailmime_free(mime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
use crate::clist::*;
|
||||
use crate::mailimf::types::*;
|
||||
use crate::other::*;
|
||||
|
||||
/*
|
||||
this function creates a new mailimf_fields structure with no fields
|
||||
*/
|
||||
pub unsafe fn mailimf_fields_new_empty() -> *mut mailimf_fields {
|
||||
let mut list: *mut clist = 0 as *mut clist;
|
||||
let mut fields_list: *mut mailimf_fields = 0 as *mut mailimf_fields;
|
||||
list = clist_new();
|
||||
if list.is_null() {
|
||||
return 0 as *mut mailimf_fields;
|
||||
}
|
||||
fields_list = mailimf_fields_new(list);
|
||||
if fields_list.is_null() {
|
||||
return 0 as *mut mailimf_fields;
|
||||
}
|
||||
return fields_list;
|
||||
}
|
||||
/*
|
||||
this function adds a field to the mailimf_fields structure
|
||||
|
||||
@return MAILIMF_NO_ERROR will be returned on success,
|
||||
other code will be returned otherwise
|
||||
*/
|
||||
pub unsafe fn mailimf_fields_add(
|
||||
mut fields: *mut mailimf_fields,
|
||||
mut field: *mut mailimf_field,
|
||||
) -> libc::c_int {
|
||||
let mut r: libc::c_int = 0;
|
||||
r = clist_insert_after(
|
||||
(*fields).fld_list,
|
||||
(*(*fields).fld_list).last,
|
||||
field as *mut libc::c_void,
|
||||
);
|
||||
if r < 0i32 {
|
||||
return MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
}
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
|
||||
/*
|
||||
mailimf_field_new_custom creates a new field of type optional
|
||||
|
||||
@param name should be allocated with malloc()
|
||||
@param value should be allocated with malloc()
|
||||
*/
|
||||
pub unsafe fn mailimf_field_new_custom(
|
||||
mut name: *mut libc::c_char,
|
||||
mut value: *mut libc::c_char,
|
||||
) -> *mut mailimf_field {
|
||||
let mut opt_field: *mut mailimf_optional_field = 0 as *mut mailimf_optional_field;
|
||||
let mut field: *mut mailimf_field = 0 as *mut mailimf_field;
|
||||
opt_field = mailimf_optional_field_new(name, value);
|
||||
if !opt_field.is_null() {
|
||||
field = mailimf_field_new(
|
||||
MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int,
|
||||
0 as *mut mailimf_return,
|
||||
0 as *mut mailimf_orig_date,
|
||||
0 as *mut mailimf_from,
|
||||
0 as *mut mailimf_sender,
|
||||
0 as *mut mailimf_to,
|
||||
0 as *mut mailimf_cc,
|
||||
0 as *mut mailimf_bcc,
|
||||
0 as *mut mailimf_message_id,
|
||||
0 as *mut mailimf_orig_date,
|
||||
0 as *mut mailimf_from,
|
||||
0 as *mut mailimf_sender,
|
||||
0 as *mut mailimf_reply_to,
|
||||
0 as *mut mailimf_to,
|
||||
0 as *mut mailimf_cc,
|
||||
0 as *mut mailimf_bcc,
|
||||
0 as *mut mailimf_message_id,
|
||||
0 as *mut mailimf_in_reply_to,
|
||||
0 as *mut mailimf_references,
|
||||
0 as *mut mailimf_subject,
|
||||
0 as *mut mailimf_comments,
|
||||
0 as *mut mailimf_keywords,
|
||||
opt_field,
|
||||
);
|
||||
if field.is_null() {
|
||||
mailimf_optional_field_free(opt_field);
|
||||
} else {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
return 0 as *mut mailimf_field;
|
||||
}
|
||||
@@ -1,860 +0,0 @@
|
||||
use libc;
|
||||
use libc::toupper;
|
||||
|
||||
use crate::charconv::*;
|
||||
use crate::mailimf::*;
|
||||
use crate::mailmime::content::*;
|
||||
use crate::mailmime::types::*;
|
||||
use crate::mmapstring::*;
|
||||
use crate::other::*;
|
||||
|
||||
pub const TYPE_WORD: libc::c_uint = 1;
|
||||
pub const TYPE_ENCODED_WORD: libc::c_uint = 2;
|
||||
pub const MAILMIME_ENCODING_Q: libc::c_uint = 1;
|
||||
pub const MAILMIME_ENCODING_B: libc::c_uint = 0;
|
||||
pub const TYPE_ERROR: libc::c_uint = 0;
|
||||
|
||||
pub unsafe fn mailmime_encoded_phrase_parse(
|
||||
mut default_fromcode: *const libc::c_char,
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut tocode: *const libc::c_char,
|
||||
mut result: *mut *mut libc::c_char,
|
||||
) -> libc::c_int {
|
||||
let mut current_block: u64;
|
||||
let mut gphrase: *mut MMAPString = 0 as *mut MMAPString;
|
||||
let mut word: *mut mailmime_encoded_word = 0 as *mut mailmime_encoded_word;
|
||||
let mut first: libc::c_int = 0;
|
||||
let mut cur_token: size_t = 0;
|
||||
let mut r: libc::c_int = 0;
|
||||
let mut res: libc::c_int = 0;
|
||||
let mut str: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut wordutf8: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut type_0: libc::c_int = 0;
|
||||
let mut missing_closing_quote: libc::c_int = 0;
|
||||
cur_token = *indx;
|
||||
gphrase = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
|
||||
if gphrase.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int
|
||||
} else {
|
||||
first = 1i32;
|
||||
type_0 = TYPE_ERROR as libc::c_int;
|
||||
loop {
|
||||
let mut has_fwd: libc::c_int = 0;
|
||||
word = 0 as *mut mailmime_encoded_word;
|
||||
r = mailmime_encoded_word_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
&mut word,
|
||||
&mut has_fwd,
|
||||
&mut missing_closing_quote,
|
||||
);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
if 0 == first && 0 != has_fwd {
|
||||
if type_0 != TYPE_ENCODED_WORD as libc::c_int {
|
||||
if mmap_string_append_c(gphrase, ' ' as i32 as libc::c_char).is_null() {
|
||||
mailmime_encoded_word_free(word);
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
type_0 = TYPE_ENCODED_WORD as libc::c_int;
|
||||
wordutf8 = 0 as *mut libc::c_char;
|
||||
r = charconv(
|
||||
tocode,
|
||||
(*word).wd_charset,
|
||||
(*word).wd_text,
|
||||
strlen((*word).wd_text),
|
||||
&mut wordutf8,
|
||||
);
|
||||
match r {
|
||||
2 => {
|
||||
mailmime_encoded_word_free(word);
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
}
|
||||
1 => {
|
||||
r = charconv(
|
||||
tocode,
|
||||
b"iso-8859-1\x00" as *const u8 as *const libc::c_char,
|
||||
(*word).wd_text,
|
||||
strlen((*word).wd_text),
|
||||
&mut wordutf8,
|
||||
)
|
||||
}
|
||||
3 => {
|
||||
mailmime_encoded_word_free(word);
|
||||
res = MAILIMF_ERROR_PARSE as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
match r {
|
||||
2 => {
|
||||
mailmime_encoded_word_free(word);
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
}
|
||||
3 => {
|
||||
mailmime_encoded_word_free(word);
|
||||
res = MAILIMF_ERROR_PARSE as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
if !wordutf8.is_null() {
|
||||
if mmap_string_append(gphrase, wordutf8).is_null() {
|
||||
mailmime_encoded_word_free(word);
|
||||
free(wordutf8 as *mut libc::c_void);
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
} else {
|
||||
free(wordutf8 as *mut libc::c_void);
|
||||
}
|
||||
}
|
||||
mailmime_encoded_word_free(word);
|
||||
first = 0i32
|
||||
}
|
||||
}
|
||||
} else if !(r == MAILIMF_ERROR_PARSE as libc::c_int) {
|
||||
/* do nothing */
|
||||
res = r;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
}
|
||||
if !(r == MAILIMF_ERROR_PARSE as libc::c_int) {
|
||||
continue;
|
||||
}
|
||||
let mut raw_word: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
raw_word = 0 as *mut libc::c_char;
|
||||
r = mailmime_non_encoded_word_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
&mut raw_word,
|
||||
&mut has_fwd,
|
||||
);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
if 0 == first && 0 != has_fwd {
|
||||
if mmap_string_append_c(gphrase, ' ' as i32 as libc::c_char).is_null() {
|
||||
free(raw_word as *mut libc::c_void);
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
}
|
||||
}
|
||||
type_0 = TYPE_WORD as libc::c_int;
|
||||
wordutf8 = 0 as *mut libc::c_char;
|
||||
r = charconv(
|
||||
tocode,
|
||||
default_fromcode,
|
||||
raw_word,
|
||||
strlen(raw_word),
|
||||
&mut wordutf8,
|
||||
);
|
||||
match r {
|
||||
2 => {
|
||||
free(raw_word as *mut libc::c_void);
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
}
|
||||
1 | 3 => {
|
||||
free(raw_word as *mut libc::c_void);
|
||||
res = MAILIMF_ERROR_PARSE as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
if mmap_string_append(gphrase, wordutf8).is_null() {
|
||||
free(wordutf8 as *mut libc::c_void);
|
||||
free(raw_word as *mut libc::c_void);
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
} else {
|
||||
free(wordutf8 as *mut libc::c_void);
|
||||
free(raw_word as *mut libc::c_void);
|
||||
first = 0i32
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
r = mailimf_fws_parse(message, length, &mut cur_token);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
current_block = 5005389895767293342;
|
||||
break;
|
||||
}
|
||||
if mmap_string_append_c(gphrase, ' ' as i32 as libc::c_char).is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
} else {
|
||||
first = 0i32;
|
||||
current_block = 5005389895767293342;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
res = r;
|
||||
current_block = 13246848547199022064;
|
||||
break;
|
||||
}
|
||||
}
|
||||
match current_block {
|
||||
5005389895767293342 => {
|
||||
if 0 != first {
|
||||
if cur_token != length {
|
||||
res = MAILIMF_ERROR_PARSE as libc::c_int;
|
||||
current_block = 13246848547199022064;
|
||||
} else {
|
||||
current_block = 7072655752890836508;
|
||||
}
|
||||
} else {
|
||||
current_block = 7072655752890836508;
|
||||
}
|
||||
match current_block {
|
||||
13246848547199022064 => {}
|
||||
_ => {
|
||||
str = strdup((*gphrase).str_0);
|
||||
if str.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int
|
||||
} else {
|
||||
mmap_string_free(gphrase);
|
||||
*result = str;
|
||||
*indx = cur_token;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
mmap_string_free(gphrase);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
unsafe fn mailmime_non_encoded_word_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut libc::c_char,
|
||||
mut p_has_fwd: *mut libc::c_int,
|
||||
) -> libc::c_int {
|
||||
let mut end: libc::c_int = 0;
|
||||
let mut cur_token: size_t = 0;
|
||||
let mut res: libc::c_int = 0;
|
||||
let mut text: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut r: libc::c_int = 0;
|
||||
let mut begin: size_t = 0;
|
||||
let mut state: libc::c_int = 0;
|
||||
let mut has_fwd: libc::c_int = 0;
|
||||
cur_token = *indx;
|
||||
has_fwd = 0i32;
|
||||
r = mailimf_fws_parse(message, length, &mut cur_token);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
has_fwd = 1i32
|
||||
}
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
begin = cur_token;
|
||||
state = 0i32;
|
||||
end = 0i32;
|
||||
while !(cur_token >= length) {
|
||||
let mut current_block_17: u64;
|
||||
match *message.offset(cur_token as isize) as libc::c_int {
|
||||
32 | 9 | 13 | 10 => {
|
||||
state = 0i32;
|
||||
end = 1i32;
|
||||
current_block_17 = 16924917904204750491;
|
||||
}
|
||||
61 => {
|
||||
state = 1i32;
|
||||
current_block_17 = 16924917904204750491;
|
||||
}
|
||||
63 => {
|
||||
if state == 1i32 {
|
||||
cur_token = cur_token.wrapping_sub(1);
|
||||
end = 1i32
|
||||
}
|
||||
current_block_17 = 10192508258555769664;
|
||||
}
|
||||
_ => {
|
||||
current_block_17 = 10192508258555769664;
|
||||
}
|
||||
}
|
||||
match current_block_17 {
|
||||
10192508258555769664 => state = 0i32,
|
||||
_ => {}
|
||||
}
|
||||
if 0 != end {
|
||||
break;
|
||||
}
|
||||
cur_token = cur_token.wrapping_add(1)
|
||||
}
|
||||
if cur_token.wrapping_sub(begin) == 0i32 as libc::size_t {
|
||||
res = MAILIMF_ERROR_PARSE as libc::c_int
|
||||
} else {
|
||||
text = malloc(
|
||||
cur_token
|
||||
.wrapping_sub(begin)
|
||||
.wrapping_add(1i32 as libc::size_t),
|
||||
) as *mut libc::c_char;
|
||||
if text.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int
|
||||
} else {
|
||||
memcpy(
|
||||
text as *mut libc::c_void,
|
||||
message.offset(begin as isize) as *const libc::c_void,
|
||||
cur_token.wrapping_sub(begin),
|
||||
);
|
||||
*text.offset(cur_token.wrapping_sub(begin) as isize) =
|
||||
'\u{0}' as i32 as libc::c_char;
|
||||
*indx = cur_token;
|
||||
*result = text;
|
||||
*p_has_fwd = has_fwd;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_encoded_word_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut mailmime_encoded_word,
|
||||
mut p_has_fwd: *mut libc::c_int,
|
||||
mut p_missing_closing_quote: *mut libc::c_int,
|
||||
) -> libc::c_int {
|
||||
let mut current_block: u64;
|
||||
/*
|
||||
Parse the following, when a unicode character encoding is split.
|
||||
=?UTF-8?B?4Lij4Liw4LmA4Lia4Li04LiU4LiE4Lin4Liy4Lih4Lih4Lix4LiZ4Liq4LmM?=
|
||||
=?UTF-8?B?4LmA4LiV4LmH4Lih4Lie4Li04LiB4Lix4LiUIFRSQU5TRk9STUVSUyA0IOC4?=
|
||||
=?UTF-8?B?oeC4seC4meC4quC5jOC4hOC4o+C4muC4l+C4uOC4geC4o+C4sOC4muC4miDg?=
|
||||
=?UTF-8?B?uJfguLXguYjguYDguJTguLXguKLguKfguYPguJnguYDguKHguLfguK3guIfg?=
|
||||
=?UTF-8?B?uYTguJfguKI=?=
|
||||
Expected result:
|
||||
ระเบิดความมันส์เต็มพิกัด TRANSFORMERS 4 มันส์ครบทุกระบบ ที่เดียวในเมืองไทย
|
||||
libetpan result:
|
||||
ระเบิดความมันส์เต็มพิกัด TRANSFORMERS 4 ?ันส์ครบทุกระบบ ??ี่เดียวในเมือง??ทย
|
||||
|
||||
See https://github.com/dinhviethoa/libetpan/pull/211
|
||||
*/
|
||||
let mut cur_token: size_t = 0;
|
||||
let mut charset: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut encoding: libc::c_int = 0;
|
||||
let mut body: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut old_body_len: size_t = 0;
|
||||
let mut text: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut end_encoding: size_t = 0;
|
||||
let mut lookfwd_cur_token: size_t = 0;
|
||||
let mut lookfwd_charset: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut lookfwd_encoding: libc::c_int = 0;
|
||||
let mut copy_len: size_t = 0;
|
||||
let mut decoded_token: size_t = 0;
|
||||
let mut decoded: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut decoded_len: size_t = 0;
|
||||
let mut ew: *mut mailmime_encoded_word = 0 as *mut mailmime_encoded_word;
|
||||
let mut r: libc::c_int = 0;
|
||||
let mut res: libc::c_int = 0;
|
||||
let mut opening_quote: libc::c_int = 0;
|
||||
let mut end: libc::c_int = 0;
|
||||
let mut has_fwd: libc::c_int = 0;
|
||||
let mut missing_closing_quote: libc::c_int = 0;
|
||||
cur_token = *indx;
|
||||
text = 0 as *mut libc::c_char;
|
||||
lookfwd_charset = 0 as *mut libc::c_char;
|
||||
missing_closing_quote = 0i32;
|
||||
has_fwd = 0i32;
|
||||
r = mailimf_fws_parse(message, length, &mut cur_token);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
has_fwd = 1i32
|
||||
}
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
opening_quote = 0i32;
|
||||
r = mailimf_char_parse(message, length, &mut cur_token, '\"' as i32 as libc::c_char);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
opening_quote = 1i32;
|
||||
current_block = 17788412896529399552;
|
||||
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
current_block = 17788412896529399552;
|
||||
} else {
|
||||
/* do nothing */
|
||||
res = r;
|
||||
current_block = 7995813543095296079;
|
||||
}
|
||||
match current_block {
|
||||
7995813543095296079 => {}
|
||||
_ => {
|
||||
r = mailimf_token_case_insensitive_len_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
b"=?\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
|
||||
strlen(b"=?\x00" as *const u8 as *const libc::c_char),
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
r = mailmime_charset_parse(message, length, &mut cur_token, &mut charset);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
r = mailimf_char_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
'?' as i32 as libc::c_char,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
r = mailmime_encoding_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
&mut encoding,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
r = mailimf_char_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
'?' as i32 as libc::c_char,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
lookfwd_cur_token = cur_token;
|
||||
body = 0 as *mut libc::c_char;
|
||||
old_body_len = 0i32 as size_t;
|
||||
loop {
|
||||
let mut has_base64_padding: libc::c_int = 0;
|
||||
end = 0i32;
|
||||
has_base64_padding = 0i32;
|
||||
end_encoding = cur_token;
|
||||
while !(end_encoding >= length) {
|
||||
if end_encoding.wrapping_add(1i32 as libc::size_t)
|
||||
< length
|
||||
{
|
||||
if *message.offset(end_encoding as isize)
|
||||
as libc::c_int
|
||||
== '?' as i32
|
||||
&& *message.offset(
|
||||
end_encoding
|
||||
.wrapping_add(1i32 as libc::size_t)
|
||||
as isize,
|
||||
)
|
||||
as libc::c_int
|
||||
== '=' as i32
|
||||
{
|
||||
end = 1i32
|
||||
}
|
||||
}
|
||||
if 0 != end {
|
||||
break;
|
||||
}
|
||||
end_encoding = end_encoding.wrapping_add(1)
|
||||
}
|
||||
copy_len = end_encoding.wrapping_sub(lookfwd_cur_token);
|
||||
if copy_len > 0i32 as libc::size_t {
|
||||
if encoding == MAILMIME_ENCODING_B as libc::c_int {
|
||||
if end_encoding >= 1i32 as libc::size_t {
|
||||
if *message.offset(
|
||||
end_encoding
|
||||
.wrapping_sub(1i32 as libc::size_t)
|
||||
as isize,
|
||||
)
|
||||
as libc::c_int
|
||||
== '=' as i32
|
||||
{
|
||||
has_base64_padding = 1i32
|
||||
}
|
||||
}
|
||||
}
|
||||
body = realloc(
|
||||
body as *mut libc::c_void,
|
||||
old_body_len
|
||||
.wrapping_add(copy_len)
|
||||
.wrapping_add(1i32 as libc::size_t),
|
||||
)
|
||||
as *mut libc::c_char;
|
||||
if body.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 13900684162107791171;
|
||||
break;
|
||||
} else {
|
||||
memcpy(
|
||||
body.offset(old_body_len as isize)
|
||||
as *mut libc::c_void,
|
||||
&*message.offset(cur_token as isize)
|
||||
as *const libc::c_char
|
||||
as *const libc::c_void,
|
||||
copy_len,
|
||||
);
|
||||
*body
|
||||
.offset(old_body_len.wrapping_add(copy_len)
|
||||
as isize) = '\u{0}' as i32 as libc::c_char;
|
||||
old_body_len = (old_body_len as libc::size_t)
|
||||
.wrapping_add(copy_len)
|
||||
as size_t
|
||||
as size_t
|
||||
}
|
||||
}
|
||||
cur_token = end_encoding;
|
||||
r = mailimf_token_case_insensitive_len_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
b"?=\x00" as *const u8 as *const libc::c_char
|
||||
as *mut libc::c_char,
|
||||
strlen(b"?=\x00" as *const u8 as *const libc::c_char),
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
current_block = 2652804691515851435;
|
||||
break;
|
||||
}
|
||||
if 0 != has_base64_padding {
|
||||
current_block = 2652804691515851435;
|
||||
break;
|
||||
}
|
||||
lookfwd_cur_token = cur_token;
|
||||
r = mailimf_fws_parse(
|
||||
message,
|
||||
length,
|
||||
&mut lookfwd_cur_token,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int
|
||||
&& r != MAILIMF_ERROR_PARSE as libc::c_int
|
||||
{
|
||||
current_block = 2652804691515851435;
|
||||
break;
|
||||
}
|
||||
r = mailimf_token_case_insensitive_len_parse(
|
||||
message,
|
||||
length,
|
||||
&mut lookfwd_cur_token,
|
||||
b"=?\x00" as *const u8 as *const libc::c_char
|
||||
as *mut libc::c_char,
|
||||
strlen(b"=?\x00" as *const u8 as *const libc::c_char),
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
current_block = 2652804691515851435;
|
||||
break;
|
||||
}
|
||||
r = mailmime_charset_parse(
|
||||
message,
|
||||
length,
|
||||
&mut lookfwd_cur_token,
|
||||
&mut lookfwd_charset,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
current_block = 2652804691515851435;
|
||||
break;
|
||||
}
|
||||
r = mailimf_char_parse(
|
||||
message,
|
||||
length,
|
||||
&mut lookfwd_cur_token,
|
||||
'?' as i32 as libc::c_char,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
current_block = 2652804691515851435;
|
||||
break;
|
||||
}
|
||||
r = mailmime_encoding_parse(
|
||||
message,
|
||||
length,
|
||||
&mut lookfwd_cur_token,
|
||||
&mut lookfwd_encoding,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
current_block = 2652804691515851435;
|
||||
break;
|
||||
}
|
||||
r = mailimf_char_parse(
|
||||
message,
|
||||
length,
|
||||
&mut lookfwd_cur_token,
|
||||
'?' as i32 as libc::c_char,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
current_block = 2652804691515851435;
|
||||
break;
|
||||
}
|
||||
if strcasecmp(charset, lookfwd_charset) == 0i32
|
||||
&& encoding == lookfwd_encoding
|
||||
{
|
||||
cur_token = lookfwd_cur_token;
|
||||
mailmime_charset_free(lookfwd_charset);
|
||||
lookfwd_charset = 0 as *mut libc::c_char
|
||||
} else {
|
||||
/* the next charset is not matched with the current one,
|
||||
therefore exit the loop to decode the body appended so far */
|
||||
current_block = 2652804691515851435;
|
||||
break;
|
||||
}
|
||||
}
|
||||
match current_block {
|
||||
2652804691515851435 => {
|
||||
if !lookfwd_charset.is_null() {
|
||||
mailmime_charset_free(lookfwd_charset);
|
||||
lookfwd_charset = 0 as *mut libc::c_char
|
||||
}
|
||||
if body.is_null() {
|
||||
body = strdup(
|
||||
b"\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
if body.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 13900684162107791171;
|
||||
} else {
|
||||
current_block = 16778110326724371720;
|
||||
}
|
||||
} else {
|
||||
current_block = 16778110326724371720;
|
||||
}
|
||||
match current_block {
|
||||
13900684162107791171 => {}
|
||||
_ => {
|
||||
decoded_token = 0i32 as size_t;
|
||||
decoded_len = 0i32 as size_t;
|
||||
decoded = 0 as *mut libc::c_char;
|
||||
match encoding {
|
||||
0 => {
|
||||
r = mailmime_base64_body_parse(
|
||||
body,
|
||||
strlen(body),
|
||||
&mut decoded_token,
|
||||
&mut decoded,
|
||||
&mut decoded_len,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int
|
||||
{
|
||||
res = r;
|
||||
current_block =
|
||||
13900684162107791171;
|
||||
} else {
|
||||
current_block = 7337917895049117968;
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
r =
|
||||
mailmime_quoted_printable_body_parse(body,
|
||||
strlen(body),
|
||||
&mut decoded_token,
|
||||
&mut decoded,
|
||||
&mut decoded_len,
|
||||
1i32);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int
|
||||
{
|
||||
res = r;
|
||||
current_block =
|
||||
13900684162107791171;
|
||||
} else {
|
||||
current_block = 7337917895049117968;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
current_block = 7337917895049117968;
|
||||
}
|
||||
}
|
||||
match current_block {
|
||||
13900684162107791171 => {}
|
||||
_ => {
|
||||
text =
|
||||
malloc(decoded_len.wrapping_add(
|
||||
1i32 as libc::size_t,
|
||||
))
|
||||
as *mut libc::c_char;
|
||||
if text.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY
|
||||
as libc::c_int
|
||||
} else {
|
||||
if decoded_len
|
||||
> 0i32 as libc::size_t
|
||||
{
|
||||
memcpy(
|
||||
text as *mut libc::c_void,
|
||||
decoded
|
||||
as *const libc::c_void,
|
||||
decoded_len,
|
||||
);
|
||||
}
|
||||
*text
|
||||
.offset(decoded_len as isize) =
|
||||
'\u{0}' as i32 as libc::c_char;
|
||||
if 0 != opening_quote {
|
||||
r = mailimf_char_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
'\"' as i32 as libc::c_char,
|
||||
);
|
||||
if r == MAILIMF_ERROR_PARSE
|
||||
as libc::c_int
|
||||
{
|
||||
missing_closing_quote = 1i32
|
||||
}
|
||||
}
|
||||
if strcasecmp(
|
||||
charset,
|
||||
b"utf8\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
free(
|
||||
charset
|
||||
as *mut libc::c_void,
|
||||
);
|
||||
charset = strdup(
|
||||
b"utf-8\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
)
|
||||
}
|
||||
ew = mailmime_encoded_word_new(
|
||||
charset, text,
|
||||
);
|
||||
if ew.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY
|
||||
as libc::c_int
|
||||
} else {
|
||||
*result = ew;
|
||||
*indx = cur_token;
|
||||
*p_has_fwd = has_fwd;
|
||||
*p_missing_closing_quote =
|
||||
missing_closing_quote;
|
||||
mailmime_decoded_part_free(
|
||||
decoded,
|
||||
);
|
||||
free(body as *mut libc::c_void);
|
||||
return MAILIMF_NO_ERROR
|
||||
as libc::c_int;
|
||||
}
|
||||
}
|
||||
mailmime_decoded_part_free(decoded);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
free(body as *mut libc::c_void);
|
||||
mailmime_encoded_text_free(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
mailmime_charset_free(charset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
unsafe fn mailmime_encoding_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut libc::c_int,
|
||||
) -> libc::c_int {
|
||||
let mut cur_token: size_t = 0;
|
||||
let mut encoding: libc::c_int = 0;
|
||||
cur_token = *indx;
|
||||
if cur_token >= length {
|
||||
return MAILIMF_ERROR_PARSE as libc::c_int;
|
||||
}
|
||||
match toupper(*message.offset(cur_token as isize) as libc::c_uchar as libc::c_int)
|
||||
as libc::c_char as libc::c_int
|
||||
{
|
||||
81 => encoding = MAILMIME_ENCODING_Q as libc::c_int,
|
||||
66 => encoding = MAILMIME_ENCODING_B as libc::c_int,
|
||||
_ => return MAILIMF_ERROR_INVAL as libc::c_int,
|
||||
}
|
||||
cur_token = cur_token.wrapping_add(1);
|
||||
*result = encoding;
|
||||
*indx = cur_token;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
|
||||
/*
|
||||
* libEtPan! -- a mail stuff library
|
||||
*
|
||||
* Copyright (C) 2001, 2005 - DINH Viet Hoa
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the libEtPan! project nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
/*
|
||||
* $Id: mailmime_decode.c,v 1.37 2010/11/16 20:52:28 hoa Exp $
|
||||
*/
|
||||
/*
|
||||
RFC 2047 : MIME (Multipurpose Internet Mail Extensions) Part Three:
|
||||
Message Header Extensions for Non-ASCII Text
|
||||
*/
|
||||
unsafe fn mailmime_charset_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut charset: *mut *mut libc::c_char,
|
||||
) -> libc::c_int {
|
||||
return mailmime_etoken_parse(message, length, indx, charset);
|
||||
}
|
||||
unsafe fn mailmime_etoken_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut libc::c_char,
|
||||
) -> libc::c_int {
|
||||
return mailimf_custom_string_parse(message, length, indx, result, Some(is_etoken_char));
|
||||
}
|
||||
|
||||
unsafe fn is_etoken_char(mut ch: libc::c_char) -> libc::c_int {
|
||||
let mut uch: libc::c_uchar = ch as libc::c_uchar;
|
||||
if (uch as libc::c_int) < 31i32 {
|
||||
return 0i32;
|
||||
}
|
||||
match uch as libc::c_int {
|
||||
32 | 40 | 41 | 60 | 62 | 64 | 44 | 59 | 58 | 34 | 47 | 91 | 93 | 63 | 61 => return 0i32,
|
||||
_ => {}
|
||||
}
|
||||
return 1i32;
|
||||
}
|
||||
@@ -1,583 +0,0 @@
|
||||
use libc::{self, toupper};
|
||||
|
||||
use crate::clist::*;
|
||||
use crate::mailimf::*;
|
||||
use crate::mailmime::types::*;
|
||||
use crate::mailmime::*;
|
||||
use crate::other::*;
|
||||
|
||||
pub const MAILMIME_DISPOSITION_TYPE_EXTENSION: libc::c_uint = 3;
|
||||
pub const MAILMIME_DISPOSITION_TYPE_ATTACHMENT: libc::c_uint = 2;
|
||||
pub const MAILMIME_DISPOSITION_TYPE_INLINE: libc::c_uint = 1;
|
||||
pub const MAILMIME_DISPOSITION_TYPE_ERROR: libc::c_uint = 0;
|
||||
|
||||
pub unsafe fn mailmime_disposition_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut mailmime_disposition,
|
||||
) -> libc::c_int {
|
||||
let mut current_block: u64;
|
||||
let mut final_token: size_t = 0;
|
||||
let mut cur_token: size_t = 0;
|
||||
let mut dsp_type: *mut mailmime_disposition_type = 0 as *mut mailmime_disposition_type;
|
||||
let mut list: *mut clist = 0 as *mut clist;
|
||||
let mut dsp: *mut mailmime_disposition = 0 as *mut mailmime_disposition;
|
||||
let mut r: libc::c_int = 0;
|
||||
let mut res: libc::c_int = 0;
|
||||
cur_token = *indx;
|
||||
r = mailmime_disposition_type_parse(message, length, &mut cur_token, &mut dsp_type);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
list = clist_new();
|
||||
if list.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int
|
||||
} else {
|
||||
loop {
|
||||
let mut param: *mut mailmime_disposition_parm = 0 as *mut mailmime_disposition_parm;
|
||||
final_token = cur_token;
|
||||
r = mailimf_unstrict_char_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
';' as i32 as libc::c_char,
|
||||
);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
param = 0 as *mut mailmime_disposition_parm;
|
||||
r = mailmime_disposition_parm_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
&mut param,
|
||||
);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
r = clist_insert_after(list, (*list).last, param as *mut libc::c_void);
|
||||
if !(r < 0i32) {
|
||||
continue;
|
||||
}
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
current_block = 18290070879695007868;
|
||||
break;
|
||||
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
cur_token = final_token;
|
||||
current_block = 652864300344834934;
|
||||
break;
|
||||
} else {
|
||||
res = r;
|
||||
current_block = 18290070879695007868;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/* do nothing */
|
||||
if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
current_block = 652864300344834934;
|
||||
break;
|
||||
}
|
||||
res = r;
|
||||
current_block = 18290070879695007868;
|
||||
break;
|
||||
}
|
||||
}
|
||||
match current_block {
|
||||
652864300344834934 => {
|
||||
dsp = mailmime_disposition_new(dsp_type, list);
|
||||
if dsp.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int
|
||||
} else {
|
||||
*result = dsp;
|
||||
*indx = cur_token;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
clist_foreach(
|
||||
list,
|
||||
::std::mem::transmute::<
|
||||
Option<unsafe fn(_: *mut mailmime_disposition_parm) -> ()>,
|
||||
clist_func,
|
||||
>(Some(mailmime_disposition_parm_free)),
|
||||
0 as *mut libc::c_void,
|
||||
);
|
||||
clist_free(list);
|
||||
}
|
||||
mailmime_disposition_type_free(dsp_type);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
/*
|
||||
* libEtPan! -- a mail stuff library
|
||||
*
|
||||
* Copyright (C) 2001, 2005 - DINH Viet Hoa
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the libEtPan! project nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
/*
|
||||
* $Id: mailmime_disposition.c,v 1.17 2011/05/03 16:30:22 hoa Exp $
|
||||
*/
|
||||
unsafe fn mailmime_disposition_parm_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut mailmime_disposition_parm,
|
||||
) -> libc::c_int {
|
||||
let mut current_block: u64;
|
||||
let mut filename: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut creation_date: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut modification_date: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut read_date: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut size: size_t = 0;
|
||||
let mut parameter: *mut mailmime_parameter = 0 as *mut mailmime_parameter;
|
||||
let mut cur_token: size_t = 0;
|
||||
let mut dsp_parm: *mut mailmime_disposition_parm = 0 as *mut mailmime_disposition_parm;
|
||||
let mut type_0: libc::c_int = 0;
|
||||
let mut guessed_type: libc::c_int = 0;
|
||||
let mut r: libc::c_int = 0;
|
||||
let mut res: libc::c_int = 0;
|
||||
cur_token = *indx;
|
||||
filename = 0 as *mut libc::c_char;
|
||||
creation_date = 0 as *mut libc::c_char;
|
||||
modification_date = 0 as *mut libc::c_char;
|
||||
read_date = 0 as *mut libc::c_char;
|
||||
size = 0i32 as size_t;
|
||||
parameter = 0 as *mut mailmime_parameter;
|
||||
r = mailimf_cfws_parse(message, length, &mut cur_token);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
guessed_type = mailmime_disposition_guess_type(message, length, cur_token);
|
||||
type_0 = MAILMIME_DISPOSITION_PARM_PARAMETER as libc::c_int;
|
||||
match guessed_type {
|
||||
0 => {
|
||||
r = mailmime_filename_parm_parse(message, length, &mut cur_token, &mut filename);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
type_0 = guessed_type;
|
||||
current_block = 13826291924415791078;
|
||||
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
current_block = 13826291924415791078;
|
||||
} else {
|
||||
/* do nothing */
|
||||
res = r;
|
||||
current_block = 9120900589700563584;
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
r = mailmime_creation_date_parm_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
&mut creation_date,
|
||||
);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
type_0 = guessed_type;
|
||||
current_block = 13826291924415791078;
|
||||
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
current_block = 13826291924415791078;
|
||||
} else {
|
||||
/* do nothing */
|
||||
res = r;
|
||||
current_block = 9120900589700563584;
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
r = mailmime_modification_date_parm_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
&mut modification_date,
|
||||
);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
type_0 = guessed_type;
|
||||
current_block = 13826291924415791078;
|
||||
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
current_block = 13826291924415791078;
|
||||
} else {
|
||||
/* do nothing */
|
||||
res = r;
|
||||
current_block = 9120900589700563584;
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
r = mailmime_read_date_parm_parse(message, length, &mut cur_token, &mut read_date);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
type_0 = guessed_type;
|
||||
current_block = 13826291924415791078;
|
||||
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
current_block = 13826291924415791078;
|
||||
} else {
|
||||
/* do nothing */
|
||||
res = r;
|
||||
current_block = 9120900589700563584;
|
||||
}
|
||||
}
|
||||
4 => {
|
||||
r = mailmime_size_parm_parse(message, length, &mut cur_token, &mut size);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
type_0 = guessed_type;
|
||||
current_block = 13826291924415791078;
|
||||
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
current_block = 13826291924415791078;
|
||||
} else {
|
||||
/* do nothing */
|
||||
res = r;
|
||||
current_block = 9120900589700563584;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
current_block = 13826291924415791078;
|
||||
}
|
||||
}
|
||||
match current_block {
|
||||
9120900589700563584 => {}
|
||||
_ => {
|
||||
if type_0 == MAILMIME_DISPOSITION_PARM_PARAMETER as libc::c_int {
|
||||
r = mailmime_parameter_parse(message, length, &mut cur_token, &mut parameter);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
type_0 = guessed_type;
|
||||
res = r;
|
||||
current_block = 9120900589700563584;
|
||||
} else {
|
||||
current_block = 6721012065216013753;
|
||||
}
|
||||
} else {
|
||||
current_block = 6721012065216013753;
|
||||
}
|
||||
match current_block {
|
||||
9120900589700563584 => {}
|
||||
_ => {
|
||||
dsp_parm = mailmime_disposition_parm_new(
|
||||
type_0,
|
||||
filename,
|
||||
creation_date,
|
||||
modification_date,
|
||||
read_date,
|
||||
size,
|
||||
parameter,
|
||||
);
|
||||
if dsp_parm.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
if !filename.is_null() {
|
||||
mailmime_filename_parm_free(filename);
|
||||
}
|
||||
if !creation_date.is_null() {
|
||||
mailmime_creation_date_parm_free(creation_date);
|
||||
}
|
||||
if !modification_date.is_null() {
|
||||
mailmime_modification_date_parm_free(modification_date);
|
||||
}
|
||||
if !read_date.is_null() {
|
||||
mailmime_read_date_parm_free(read_date);
|
||||
}
|
||||
if !parameter.is_null() {
|
||||
mailmime_parameter_free(parameter);
|
||||
}
|
||||
} else {
|
||||
*result = dsp_parm;
|
||||
*indx = cur_token;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
unsafe fn mailmime_size_parm_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut size_t,
|
||||
) -> libc::c_int {
|
||||
let mut value: uint32_t = 0;
|
||||
let mut cur_token: size_t = 0;
|
||||
let mut r: libc::c_int = 0;
|
||||
cur_token = *indx;
|
||||
r = mailimf_token_case_insensitive_len_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
b"size\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
|
||||
strlen(b"size\x00" as *const u8 as *const libc::c_char),
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_unstrict_char_parse(message, length, &mut cur_token, '=' as i32 as libc::c_char);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_cfws_parse(message, length, &mut cur_token);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_number_parse(message, length, &mut cur_token, &mut value);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
*indx = cur_token;
|
||||
*result = value as size_t;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
unsafe fn mailmime_read_date_parm_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut libc::c_char,
|
||||
) -> libc::c_int {
|
||||
let mut value: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut cur_token: size_t = 0;
|
||||
let mut r: libc::c_int = 0;
|
||||
cur_token = *indx;
|
||||
r = mailimf_token_case_insensitive_len_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
b"read-date\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
|
||||
strlen(b"read-date\x00" as *const u8 as *const libc::c_char),
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_unstrict_char_parse(message, length, &mut cur_token, '=' as i32 as libc::c_char);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_cfws_parse(message, length, &mut cur_token);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailmime_quoted_date_time_parse(message, length, &mut cur_token, &mut value);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
*indx = cur_token;
|
||||
*result = value;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
unsafe fn mailmime_quoted_date_time_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut libc::c_char,
|
||||
) -> libc::c_int {
|
||||
return mailimf_quoted_string_parse(message, length, indx, result);
|
||||
}
|
||||
unsafe fn mailmime_modification_date_parm_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut libc::c_char,
|
||||
) -> libc::c_int {
|
||||
let mut value: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut cur_token: size_t = 0;
|
||||
let mut r: libc::c_int = 0;
|
||||
cur_token = *indx;
|
||||
r = mailimf_token_case_insensitive_len_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
b"modification-date\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
|
||||
strlen(b"modification-date\x00" as *const u8 as *const libc::c_char),
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_unstrict_char_parse(message, length, &mut cur_token, '=' as i32 as libc::c_char);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_cfws_parse(message, length, &mut cur_token);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailmime_quoted_date_time_parse(message, length, &mut cur_token, &mut value);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
*indx = cur_token;
|
||||
*result = value;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
unsafe fn mailmime_creation_date_parm_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut libc::c_char,
|
||||
) -> libc::c_int {
|
||||
let mut value: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut r: libc::c_int = 0;
|
||||
let mut cur_token: size_t = 0;
|
||||
cur_token = *indx;
|
||||
r = mailimf_token_case_insensitive_len_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
b"creation-date\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
|
||||
strlen(b"creation-date\x00" as *const u8 as *const libc::c_char),
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_unstrict_char_parse(message, length, &mut cur_token, '=' as i32 as libc::c_char);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_cfws_parse(message, length, &mut cur_token);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailmime_quoted_date_time_parse(message, length, &mut cur_token, &mut value);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
*indx = cur_token;
|
||||
*result = value;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
unsafe fn mailmime_filename_parm_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut libc::c_char,
|
||||
) -> libc::c_int {
|
||||
let mut value: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut r: libc::c_int = 0;
|
||||
let mut cur_token: size_t = 0;
|
||||
cur_token = *indx;
|
||||
r = mailimf_token_case_insensitive_len_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
b"filename\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
|
||||
strlen(b"filename\x00" as *const u8 as *const libc::c_char),
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_unstrict_char_parse(message, length, &mut cur_token, '=' as i32 as libc::c_char);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailimf_cfws_parse(message, length, &mut cur_token);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
r = mailmime_value_parse(message, length, &mut cur_token, &mut value);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
return r;
|
||||
}
|
||||
*indx = cur_token;
|
||||
*result = value;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_disposition_guess_type(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: size_t,
|
||||
) -> libc::c_int {
|
||||
if indx >= length {
|
||||
return MAILMIME_DISPOSITION_PARM_PARAMETER as libc::c_int;
|
||||
}
|
||||
match toupper(*message.offset(indx as isize) as libc::c_uchar as libc::c_int) as libc::c_char
|
||||
as libc::c_int
|
||||
{
|
||||
70 => return MAILMIME_DISPOSITION_PARM_FILENAME as libc::c_int,
|
||||
67 => return MAILMIME_DISPOSITION_PARM_CREATION_DATE as libc::c_int,
|
||||
77 => return MAILMIME_DISPOSITION_PARM_MODIFICATION_DATE as libc::c_int,
|
||||
82 => return MAILMIME_DISPOSITION_PARM_READ_DATE as libc::c_int,
|
||||
83 => return MAILMIME_DISPOSITION_PARM_SIZE as libc::c_int,
|
||||
_ => return MAILMIME_DISPOSITION_PARM_PARAMETER as libc::c_int,
|
||||
};
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_disposition_type_parse(
|
||||
mut message: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
mut indx: *mut size_t,
|
||||
mut result: *mut *mut mailmime_disposition_type,
|
||||
) -> libc::c_int {
|
||||
let mut cur_token: size_t = 0;
|
||||
let mut type_0: libc::c_int = 0;
|
||||
let mut extension: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut dsp_type: *mut mailmime_disposition_type = 0 as *mut mailmime_disposition_type;
|
||||
let mut r: libc::c_int = 0;
|
||||
let mut res: libc::c_int = 0;
|
||||
cur_token = *indx;
|
||||
r = mailimf_cfws_parse(message, length, &mut cur_token);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
type_0 = MAILMIME_DISPOSITION_TYPE_ERROR as libc::c_int;
|
||||
extension = 0 as *mut libc::c_char;
|
||||
r = mailimf_token_case_insensitive_len_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
b"inline\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
|
||||
strlen(b"inline\x00" as *const u8 as *const libc::c_char),
|
||||
);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
type_0 = MAILMIME_DISPOSITION_TYPE_INLINE as libc::c_int
|
||||
}
|
||||
if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
r = mailimf_token_case_insensitive_len_parse(
|
||||
message,
|
||||
length,
|
||||
&mut cur_token,
|
||||
b"attachment\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
|
||||
strlen(b"attachment\x00" as *const u8 as *const libc::c_char),
|
||||
);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
type_0 = MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int
|
||||
}
|
||||
}
|
||||
if r == MAILIMF_ERROR_PARSE as libc::c_int {
|
||||
r = mailmime_extension_token_parse(message, length, &mut cur_token, &mut extension);
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int {
|
||||
type_0 = MAILMIME_DISPOSITION_TYPE_EXTENSION as libc::c_int
|
||||
}
|
||||
}
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
res = r
|
||||
} else {
|
||||
dsp_type = mailmime_disposition_type_new(type_0, extension);
|
||||
if dsp_type.is_null() {
|
||||
res = MAILIMF_ERROR_MEMORY as libc::c_int;
|
||||
if !extension.is_null() {
|
||||
free(extension as *mut libc::c_void);
|
||||
}
|
||||
} else {
|
||||
*result = dsp_type;
|
||||
*indx = cur_token;
|
||||
return MAILIMF_NO_ERROR as libc::c_int;
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -1,891 +0,0 @@
|
||||
use crate::clist::*;
|
||||
use crate::mailimf::types::*;
|
||||
use crate::mmapstring::*;
|
||||
use crate::other::*;
|
||||
|
||||
pub const MAILMIME_MECHANISM_TOKEN: libc::c_uint = 6;
|
||||
pub const MAILMIME_MECHANISM_BASE64: libc::c_uint = 5;
|
||||
pub const MAILMIME_MECHANISM_QUOTED_PRINTABLE: libc::c_uint = 4;
|
||||
pub const MAILMIME_MECHANISM_BINARY: libc::c_uint = 3;
|
||||
pub const MAILMIME_MECHANISM_8BIT: libc::c_uint = 2;
|
||||
pub const MAILMIME_MECHANISM_7BIT: libc::c_uint = 1;
|
||||
pub const MAILMIME_MECHANISM_ERROR: libc::c_uint = 0;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_composite_type {
|
||||
pub ct_type: libc::c_int,
|
||||
pub ct_token: *mut libc::c_char,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_content {
|
||||
pub ct_type: *mut mailmime_type,
|
||||
pub ct_subtype: *mut libc::c_char,
|
||||
pub ct_parameters: *mut clist,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_type {
|
||||
pub tp_type: libc::c_int,
|
||||
pub tp_data: unnamed,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub union unnamed {
|
||||
pub tp_discrete_type: *mut mailmime_discrete_type,
|
||||
pub tp_composite_type: *mut mailmime_composite_type,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_discrete_type {
|
||||
pub dt_type: libc::c_int,
|
||||
pub dt_extension: *mut libc::c_char,
|
||||
}
|
||||
pub type unnamed_0 = libc::c_uint;
|
||||
pub const MAILMIME_FIELD_LOCATION: unnamed_0 = 8;
|
||||
pub const MAILMIME_FIELD_LANGUAGE: unnamed_0 = 7;
|
||||
pub const MAILMIME_FIELD_DISPOSITION: unnamed_0 = 6;
|
||||
pub const MAILMIME_FIELD_VERSION: unnamed_0 = 5;
|
||||
pub const MAILMIME_FIELD_DESCRIPTION: unnamed_0 = 4;
|
||||
pub const MAILMIME_FIELD_ID: unnamed_0 = 3;
|
||||
pub const MAILMIME_FIELD_TRANSFER_ENCODING: unnamed_0 = 2;
|
||||
pub const MAILMIME_FIELD_TYPE: unnamed_0 = 1;
|
||||
pub const MAILMIME_FIELD_NONE: unnamed_0 = 0;
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_field {
|
||||
pub fld_type: libc::c_int,
|
||||
pub fld_data: unnamed_1,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub union unnamed_1 {
|
||||
pub fld_content: *mut mailmime_content,
|
||||
pub fld_encoding: *mut mailmime_mechanism,
|
||||
pub fld_id: *mut libc::c_char,
|
||||
pub fld_description: *mut libc::c_char,
|
||||
pub fld_version: uint32_t,
|
||||
pub fld_disposition: *mut mailmime_disposition,
|
||||
pub fld_language: *mut mailmime_language,
|
||||
pub fld_location: *mut libc::c_char,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_language {
|
||||
pub lg_list: *mut clist,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_disposition {
|
||||
pub dsp_type: *mut mailmime_disposition_type,
|
||||
pub dsp_parms: *mut clist,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_disposition_type {
|
||||
pub dsp_type: libc::c_int,
|
||||
pub dsp_extension: *mut libc::c_char,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_mechanism {
|
||||
pub enc_type: libc::c_int,
|
||||
pub enc_token: *mut libc::c_char,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_fields {
|
||||
pub fld_list: *mut clist,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_parameter {
|
||||
pub pa_name: *mut libc::c_char,
|
||||
pub pa_value: *mut libc::c_char,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_disposition_parm {
|
||||
pub pa_type: libc::c_int,
|
||||
pub pa_data: unnamed_3,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub union unnamed_3 {
|
||||
pub pa_filename: *mut libc::c_char,
|
||||
pub pa_creation_date: *mut libc::c_char,
|
||||
pub pa_modification_date: *mut libc::c_char,
|
||||
pub pa_read_date: *mut libc::c_char,
|
||||
pub pa_size: size_t,
|
||||
pub pa_parameter: *mut mailmime_parameter,
|
||||
}
|
||||
pub const MAILMIME_DISPOSITION_PARM_PARAMETER: unnamed_11 = 5;
|
||||
pub const MAILMIME_DISPOSITION_PARM_READ_DATE: unnamed_11 = 3;
|
||||
pub const MAILMIME_DISPOSITION_PARM_MODIFICATION_DATE: unnamed_11 = 2;
|
||||
pub const MAILMIME_DISPOSITION_PARM_CREATION_DATE: unnamed_11 = 1;
|
||||
pub const MAILMIME_DISPOSITION_PARM_FILENAME: unnamed_11 = 0;
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_multipart_body {
|
||||
pub bd_list: *mut clist,
|
||||
}
|
||||
pub type unnamed_4 = libc::c_uint;
|
||||
pub const MAILMIME_DATA_FILE: unnamed_4 = 1;
|
||||
pub const MAILMIME_DATA_TEXT: unnamed_4 = 0;
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_data {
|
||||
pub dt_type: libc::c_int,
|
||||
pub dt_encoding: libc::c_int,
|
||||
pub dt_encoded: libc::c_int,
|
||||
pub dt_data: unnamed_5,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub union unnamed_5 {
|
||||
pub dt_text: unnamed_6,
|
||||
pub dt_filename: *mut libc::c_char,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct unnamed_6 {
|
||||
pub dt_data: *const libc::c_char,
|
||||
pub dt_length: size_t,
|
||||
}
|
||||
pub type unnamed_7 = libc::c_uint;
|
||||
pub const MAILMIME_MESSAGE: unnamed_7 = 3;
|
||||
pub const MAILMIME_MULTIPLE: unnamed_7 = 2;
|
||||
pub const MAILMIME_SINGLE: unnamed_7 = 1;
|
||||
pub const MAILMIME_NONE: unnamed_7 = 0;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct Mailmime {
|
||||
pub mm_parent_type: libc::c_int,
|
||||
pub mm_parent: *mut Mailmime,
|
||||
pub mm_multipart_pos: *mut clistiter,
|
||||
pub mm_type: libc::c_int,
|
||||
pub mm_mime_start: *const libc::c_char,
|
||||
pub mm_length: size_t,
|
||||
pub mm_mime_fields: *mut mailmime_fields,
|
||||
pub mm_content_type: *mut mailmime_content,
|
||||
pub mm_body: *mut mailmime_data,
|
||||
pub mm_data: unnamed_8,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub union unnamed_8 {
|
||||
pub mm_single: *mut mailmime_data,
|
||||
pub mm_multipart: unnamed_10,
|
||||
pub mm_message: unnamed_9,
|
||||
}
|
||||
/* message */
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct unnamed_9 {
|
||||
pub mm_fields: *mut mailimf_fields,
|
||||
pub mm_msg_mime: *mut Mailmime,
|
||||
}
|
||||
/* multi-part */
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct unnamed_10 {
|
||||
pub mm_preamble: *mut mailmime_data,
|
||||
pub mm_epilogue: *mut mailmime_data,
|
||||
pub mm_mp_list: *mut clist,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_encoded_word {
|
||||
pub wd_charset: *mut libc::c_char,
|
||||
pub wd_text: *mut libc::c_char,
|
||||
}
|
||||
pub type unnamed_11 = libc::c_uint;
|
||||
pub const MAILMIME_DISPOSITION_PARM_SIZE: unnamed_11 = 4;
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct mailmime_section {
|
||||
pub sec_list: *mut clist,
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_attribute_free(mut attribute: *mut libc::c_char) {
|
||||
mailmime_token_free(attribute);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_token_free(mut token: *mut libc::c_char) {
|
||||
free(token as *mut libc::c_void);
|
||||
}
|
||||
pub unsafe fn mailmime_composite_type_new(
|
||||
mut ct_type: libc::c_int,
|
||||
mut ct_token: *mut libc::c_char,
|
||||
) -> *mut mailmime_composite_type {
|
||||
let mut ct: *mut mailmime_composite_type = 0 as *mut mailmime_composite_type;
|
||||
ct = malloc(::std::mem::size_of::<mailmime_composite_type>() as libc::size_t)
|
||||
as *mut mailmime_composite_type;
|
||||
if ct.is_null() {
|
||||
return 0 as *mut mailmime_composite_type;
|
||||
}
|
||||
(*ct).ct_type = ct_type;
|
||||
(*ct).ct_token = ct_token;
|
||||
return ct;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_composite_type_free(mut ct: *mut mailmime_composite_type) {
|
||||
if !(*ct).ct_token.is_null() {
|
||||
mailmime_extension_token_free((*ct).ct_token);
|
||||
}
|
||||
free(ct as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_extension_token_free(mut extension: *mut libc::c_char) {
|
||||
mailmime_token_free(extension);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_content_new(
|
||||
mut ct_type: *mut mailmime_type,
|
||||
mut ct_subtype: *mut libc::c_char,
|
||||
mut ct_parameters: *mut clist,
|
||||
) -> *mut mailmime_content {
|
||||
let mut content: *mut mailmime_content = 0 as *mut mailmime_content;
|
||||
content =
|
||||
malloc(::std::mem::size_of::<mailmime_content>() as libc::size_t) as *mut mailmime_content;
|
||||
if content.is_null() {
|
||||
return 0 as *mut mailmime_content;
|
||||
}
|
||||
(*content).ct_type = ct_type;
|
||||
(*content).ct_subtype = ct_subtype;
|
||||
(*content).ct_parameters = ct_parameters;
|
||||
return content;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_content_free(mut content: *mut mailmime_content) {
|
||||
mailmime_type_free((*content).ct_type);
|
||||
mailmime_subtype_free((*content).ct_subtype);
|
||||
if !(*content).ct_parameters.is_null() {
|
||||
clist_foreach(
|
||||
(*content).ct_parameters,
|
||||
::std::mem::transmute::<Option<unsafe fn(_: *mut mailmime_parameter) -> ()>, clist_func>(
|
||||
Some(mailmime_parameter_free),
|
||||
),
|
||||
0 as *mut libc::c_void,
|
||||
);
|
||||
clist_free((*content).ct_parameters);
|
||||
}
|
||||
free(content as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_parameter_free(mut parameter: *mut mailmime_parameter) {
|
||||
mailmime_attribute_free((*parameter).pa_name);
|
||||
mailmime_value_free((*parameter).pa_value);
|
||||
free(parameter as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_value_free(mut value: *mut libc::c_char) {
|
||||
free(value as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_subtype_free(mut subtype: *mut libc::c_char) {
|
||||
mailmime_extension_token_free(subtype);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_type_free(mut type_0: *mut mailmime_type) {
|
||||
match (*type_0).tp_type {
|
||||
1 => {
|
||||
mailmime_discrete_type_free((*type_0).tp_data.tp_discrete_type);
|
||||
}
|
||||
2 => {
|
||||
mailmime_composite_type_free((*type_0).tp_data.tp_composite_type);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
free(type_0 as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_discrete_type_free(mut discrete_type: *mut mailmime_discrete_type) {
|
||||
if !(*discrete_type).dt_extension.is_null() {
|
||||
mailmime_extension_token_free((*discrete_type).dt_extension);
|
||||
}
|
||||
free(discrete_type as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_description_free(mut description: *mut libc::c_char) {
|
||||
free(description as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_location_free(mut location: *mut libc::c_char) {
|
||||
free(location as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_discrete_type_new(
|
||||
mut dt_type: libc::c_int,
|
||||
mut dt_extension: *mut libc::c_char,
|
||||
) -> *mut mailmime_discrete_type {
|
||||
let mut discrete_type: *mut mailmime_discrete_type = 0 as *mut mailmime_discrete_type;
|
||||
discrete_type = malloc(::std::mem::size_of::<mailmime_discrete_type>() as libc::size_t)
|
||||
as *mut mailmime_discrete_type;
|
||||
if discrete_type.is_null() {
|
||||
return 0 as *mut mailmime_discrete_type;
|
||||
}
|
||||
(*discrete_type).dt_type = dt_type;
|
||||
(*discrete_type).dt_extension = dt_extension;
|
||||
return discrete_type;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_encoding_free(mut encoding: *mut mailmime_mechanism) {
|
||||
mailmime_mechanism_free(encoding);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_mechanism_free(mut mechanism: *mut mailmime_mechanism) {
|
||||
if !(*mechanism).enc_token.is_null() {
|
||||
mailmime_token_free((*mechanism).enc_token);
|
||||
}
|
||||
free(mechanism as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_id_free(mut id: *mut libc::c_char) {
|
||||
mailimf_msg_id_free(id);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_mechanism_new(
|
||||
mut enc_type: libc::c_int,
|
||||
mut enc_token: *mut libc::c_char,
|
||||
) -> *mut mailmime_mechanism {
|
||||
let mut mechanism: *mut mailmime_mechanism = 0 as *mut mailmime_mechanism;
|
||||
mechanism = malloc(::std::mem::size_of::<mailmime_mechanism>() as libc::size_t)
|
||||
as *mut mailmime_mechanism;
|
||||
if mechanism.is_null() {
|
||||
return 0 as *mut mailmime_mechanism;
|
||||
}
|
||||
(*mechanism).enc_type = enc_type;
|
||||
(*mechanism).enc_token = enc_token;
|
||||
return mechanism;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_parameter_new(
|
||||
mut pa_name: *mut libc::c_char,
|
||||
mut pa_value: *mut libc::c_char,
|
||||
) -> *mut mailmime_parameter {
|
||||
let mut parameter: *mut mailmime_parameter = 0 as *mut mailmime_parameter;
|
||||
parameter = malloc(::std::mem::size_of::<mailmime_parameter>() as libc::size_t)
|
||||
as *mut mailmime_parameter;
|
||||
if parameter.is_null() {
|
||||
return 0 as *mut mailmime_parameter;
|
||||
}
|
||||
(*parameter).pa_name = pa_name;
|
||||
(*parameter).pa_value = pa_value;
|
||||
return parameter;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_type_new(
|
||||
mut tp_type: libc::c_int,
|
||||
mut tp_discrete_type: *mut mailmime_discrete_type,
|
||||
mut tp_composite_type: *mut mailmime_composite_type,
|
||||
) -> *mut mailmime_type {
|
||||
let mut mime_type: *mut mailmime_type = 0 as *mut mailmime_type;
|
||||
mime_type =
|
||||
malloc(::std::mem::size_of::<mailmime_type>() as libc::size_t) as *mut mailmime_type;
|
||||
if mime_type.is_null() {
|
||||
return 0 as *mut mailmime_type;
|
||||
}
|
||||
(*mime_type).tp_type = tp_type;
|
||||
match tp_type {
|
||||
1 => (*mime_type).tp_data.tp_discrete_type = tp_discrete_type,
|
||||
2 => (*mime_type).tp_data.tp_composite_type = tp_composite_type,
|
||||
_ => {}
|
||||
}
|
||||
return mime_type;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_language_new(mut lg_list: *mut clist) -> *mut mailmime_language {
|
||||
let mut lang: *mut mailmime_language = 0 as *mut mailmime_language;
|
||||
lang = malloc(::std::mem::size_of::<mailmime_language>() as libc::size_t)
|
||||
as *mut mailmime_language;
|
||||
if lang.is_null() {
|
||||
return 0 as *mut mailmime_language;
|
||||
}
|
||||
(*lang).lg_list = lg_list;
|
||||
return lang;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_language_free(mut lang: *mut mailmime_language) {
|
||||
clist_foreach(
|
||||
(*lang).lg_list,
|
||||
::std::mem::transmute::<Option<unsafe fn(_: *mut libc::c_char) -> ()>, clist_func>(Some(
|
||||
mailimf_atom_free,
|
||||
)),
|
||||
0 as *mut libc::c_void,
|
||||
);
|
||||
clist_free((*lang).lg_list);
|
||||
free(lang as *mut libc::c_void);
|
||||
}
|
||||
/*
|
||||
void mailmime_x_token_free(gchar * x_token);
|
||||
*/
|
||||
pub unsafe fn mailmime_field_new(
|
||||
mut fld_type: libc::c_int,
|
||||
mut fld_content: *mut mailmime_content,
|
||||
mut fld_encoding: *mut mailmime_mechanism,
|
||||
mut fld_id: *mut libc::c_char,
|
||||
mut fld_description: *mut libc::c_char,
|
||||
mut fld_version: uint32_t,
|
||||
mut fld_disposition: *mut mailmime_disposition,
|
||||
mut fld_language: *mut mailmime_language,
|
||||
mut fld_location: *mut libc::c_char,
|
||||
) -> *mut mailmime_field {
|
||||
let mut field: *mut mailmime_field = 0 as *mut mailmime_field;
|
||||
field = malloc(::std::mem::size_of::<mailmime_field>() as libc::size_t) as *mut mailmime_field;
|
||||
if field.is_null() {
|
||||
return 0 as *mut mailmime_field;
|
||||
}
|
||||
(*field).fld_type = fld_type;
|
||||
match fld_type {
|
||||
1 => (*field).fld_data.fld_content = fld_content,
|
||||
2 => (*field).fld_data.fld_encoding = fld_encoding,
|
||||
3 => (*field).fld_data.fld_id = fld_id,
|
||||
4 => (*field).fld_data.fld_description = fld_description,
|
||||
5 => (*field).fld_data.fld_version = fld_version,
|
||||
6 => (*field).fld_data.fld_disposition = fld_disposition,
|
||||
7 => (*field).fld_data.fld_language = fld_language,
|
||||
8 => (*field).fld_data.fld_location = fld_location,
|
||||
_ => {}
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_field_free(mut field: *mut mailmime_field) {
|
||||
match (*field).fld_type {
|
||||
1 => {
|
||||
if !(*field).fld_data.fld_content.is_null() {
|
||||
mailmime_content_free((*field).fld_data.fld_content);
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
if !(*field).fld_data.fld_encoding.is_null() {
|
||||
mailmime_encoding_free((*field).fld_data.fld_encoding);
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
if !(*field).fld_data.fld_id.is_null() {
|
||||
mailmime_id_free((*field).fld_data.fld_id);
|
||||
}
|
||||
}
|
||||
4 => {
|
||||
if !(*field).fld_data.fld_description.is_null() {
|
||||
mailmime_description_free((*field).fld_data.fld_description);
|
||||
}
|
||||
}
|
||||
6 => {
|
||||
if !(*field).fld_data.fld_disposition.is_null() {
|
||||
mailmime_disposition_free((*field).fld_data.fld_disposition);
|
||||
}
|
||||
}
|
||||
7 => {
|
||||
if !(*field).fld_data.fld_language.is_null() {
|
||||
mailmime_language_free((*field).fld_data.fld_language);
|
||||
}
|
||||
}
|
||||
8 => {
|
||||
if !(*field).fld_data.fld_location.is_null() {
|
||||
mailmime_location_free((*field).fld_data.fld_location);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
free(field as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_disposition_free(mut dsp: *mut mailmime_disposition) {
|
||||
mailmime_disposition_type_free((*dsp).dsp_type);
|
||||
clist_foreach(
|
||||
(*dsp).dsp_parms,
|
||||
::std::mem::transmute::<
|
||||
Option<unsafe fn(_: *mut mailmime_disposition_parm) -> ()>,
|
||||
clist_func,
|
||||
>(Some(mailmime_disposition_parm_free)),
|
||||
0 as *mut libc::c_void,
|
||||
);
|
||||
clist_free((*dsp).dsp_parms);
|
||||
free(dsp as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_disposition_parm_free(mut dsp_parm: *mut mailmime_disposition_parm) {
|
||||
match (*dsp_parm).pa_type {
|
||||
0 => {
|
||||
mailmime_filename_parm_free((*dsp_parm).pa_data.pa_filename);
|
||||
}
|
||||
1 => {
|
||||
mailmime_creation_date_parm_free((*dsp_parm).pa_data.pa_creation_date);
|
||||
}
|
||||
2 => {
|
||||
mailmime_modification_date_parm_free((*dsp_parm).pa_data.pa_modification_date);
|
||||
}
|
||||
3 => {
|
||||
mailmime_read_date_parm_free((*dsp_parm).pa_data.pa_read_date);
|
||||
}
|
||||
5 => {
|
||||
mailmime_parameter_free((*dsp_parm).pa_data.pa_parameter);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
free(dsp_parm as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_read_date_parm_free(mut date: *mut libc::c_char) {
|
||||
mailmime_quoted_date_time_free(date);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_quoted_date_time_free(mut date: *mut libc::c_char) {
|
||||
mailimf_quoted_string_free(date);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_modification_date_parm_free(mut date: *mut libc::c_char) {
|
||||
mailmime_quoted_date_time_free(date);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_creation_date_parm_free(mut date: *mut libc::c_char) {
|
||||
mailmime_quoted_date_time_free(date);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_filename_parm_free(mut filename: *mut libc::c_char) {
|
||||
mailmime_value_free(filename);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_disposition_type_free(mut dsp_type: *mut mailmime_disposition_type) {
|
||||
if !(*dsp_type).dsp_extension.is_null() {
|
||||
free((*dsp_type).dsp_extension as *mut libc::c_void);
|
||||
}
|
||||
free(dsp_type as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_fields_new(mut fld_list: *mut clist) -> *mut mailmime_fields {
|
||||
let mut fields: *mut mailmime_fields = 0 as *mut mailmime_fields;
|
||||
fields =
|
||||
malloc(::std::mem::size_of::<mailmime_fields>() as libc::size_t) as *mut mailmime_fields;
|
||||
if fields.is_null() {
|
||||
return 0 as *mut mailmime_fields;
|
||||
}
|
||||
(*fields).fld_list = fld_list;
|
||||
return fields;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_fields_free(mut fields: *mut mailmime_fields) {
|
||||
clist_foreach(
|
||||
(*fields).fld_list,
|
||||
::std::mem::transmute::<Option<unsafe fn(_: *mut mailmime_field) -> ()>, clist_func>(Some(
|
||||
mailmime_field_free,
|
||||
)),
|
||||
0 as *mut libc::c_void,
|
||||
);
|
||||
clist_free((*fields).fld_list);
|
||||
free(fields as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_multipart_body_new(mut bd_list: *mut clist) -> *mut mailmime_multipart_body {
|
||||
let mut mp_body: *mut mailmime_multipart_body = 0 as *mut mailmime_multipart_body;
|
||||
mp_body = malloc(::std::mem::size_of::<mailmime_multipart_body>() as libc::size_t)
|
||||
as *mut mailmime_multipart_body;
|
||||
if mp_body.is_null() {
|
||||
return 0 as *mut mailmime_multipart_body;
|
||||
}
|
||||
(*mp_body).bd_list = bd_list;
|
||||
return mp_body;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_multipart_body_free(mut mp_body: *mut mailmime_multipart_body) {
|
||||
clist_foreach(
|
||||
(*mp_body).bd_list,
|
||||
::std::mem::transmute::<Option<unsafe fn(_: *mut mailimf_body) -> ()>, clist_func>(Some(
|
||||
mailimf_body_free,
|
||||
)),
|
||||
0 as *mut libc::c_void,
|
||||
);
|
||||
clist_free((*mp_body).bd_list);
|
||||
free(mp_body as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_data_new(
|
||||
mut dt_type: libc::c_int,
|
||||
mut dt_encoding: libc::c_int,
|
||||
mut dt_encoded: libc::c_int,
|
||||
mut dt_data: *const libc::c_char,
|
||||
mut dt_length: size_t,
|
||||
mut dt_filename: *mut libc::c_char,
|
||||
) -> *mut mailmime_data {
|
||||
let mut mime_data: *mut mailmime_data = 0 as *mut mailmime_data;
|
||||
mime_data =
|
||||
malloc(::std::mem::size_of::<mailmime_data>() as libc::size_t) as *mut mailmime_data;
|
||||
if mime_data.is_null() {
|
||||
return 0 as *mut mailmime_data;
|
||||
}
|
||||
(*mime_data).dt_type = dt_type;
|
||||
(*mime_data).dt_encoding = dt_encoding;
|
||||
(*mime_data).dt_encoded = dt_encoded;
|
||||
match dt_type {
|
||||
0 => {
|
||||
(*mime_data).dt_data.dt_text.dt_data = dt_data;
|
||||
(*mime_data).dt_data.dt_text.dt_length = dt_length
|
||||
}
|
||||
1 => (*mime_data).dt_data.dt_filename = dt_filename,
|
||||
_ => {}
|
||||
}
|
||||
return mime_data;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_data_free(mut mime_data: *mut mailmime_data) {
|
||||
match (*mime_data).dt_type {
|
||||
1 => {
|
||||
free((*mime_data).dt_data.dt_filename as *mut libc::c_void);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
free(mime_data as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_new(
|
||||
mut mm_type: libc::c_int,
|
||||
mut mm_mime_start: *const libc::c_char,
|
||||
mut mm_length: size_t,
|
||||
mut mm_mime_fields: *mut mailmime_fields,
|
||||
mut mm_content_type: *mut mailmime_content,
|
||||
mut mm_body: *mut mailmime_data,
|
||||
mut mm_preamble: *mut mailmime_data,
|
||||
mut mm_epilogue: *mut mailmime_data,
|
||||
mut mm_mp_list: *mut clist,
|
||||
mut mm_fields: *mut mailimf_fields,
|
||||
mut mm_msg_mime: *mut Mailmime,
|
||||
) -> *mut Mailmime {
|
||||
let mut mime: *mut Mailmime = 0 as *mut Mailmime;
|
||||
let mut cur: *mut clistiter = 0 as *mut clistiter;
|
||||
mime = malloc(::std::mem::size_of::<Mailmime>() as libc::size_t) as *mut Mailmime;
|
||||
if mime.is_null() {
|
||||
return 0 as *mut Mailmime;
|
||||
}
|
||||
(*mime).mm_parent = 0 as *mut Mailmime;
|
||||
(*mime).mm_parent_type = MAILMIME_NONE as libc::c_int;
|
||||
(*mime).mm_multipart_pos = 0 as *mut clistiter;
|
||||
(*mime).mm_type = mm_type;
|
||||
(*mime).mm_mime_start = mm_mime_start;
|
||||
(*mime).mm_length = mm_length;
|
||||
(*mime).mm_mime_fields = mm_mime_fields;
|
||||
(*mime).mm_content_type = mm_content_type;
|
||||
(*mime).mm_body = mm_body;
|
||||
match mm_type {
|
||||
1 => (*mime).mm_data.mm_single = mm_body,
|
||||
2 => {
|
||||
(*mime).mm_data.mm_multipart.mm_preamble = mm_preamble;
|
||||
(*mime).mm_data.mm_multipart.mm_epilogue = mm_epilogue;
|
||||
(*mime).mm_data.mm_multipart.mm_mp_list = mm_mp_list;
|
||||
cur = (*mm_mp_list).first;
|
||||
while !cur.is_null() {
|
||||
let mut submime: *mut Mailmime = 0 as *mut Mailmime;
|
||||
submime = (if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
}) as *mut Mailmime;
|
||||
(*submime).mm_parent = mime;
|
||||
(*submime).mm_parent_type = MAILMIME_MULTIPLE as libc::c_int;
|
||||
(*submime).mm_multipart_pos = cur;
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
(*mime).mm_data.mm_message.mm_fields = mm_fields;
|
||||
(*mime).mm_data.mm_message.mm_msg_mime = mm_msg_mime;
|
||||
if !mm_msg_mime.is_null() {
|
||||
(*mm_msg_mime).mm_parent = mime;
|
||||
(*mm_msg_mime).mm_parent_type = MAILMIME_MESSAGE as libc::c_int
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
return mime;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_new_simple(
|
||||
mut mm_type: libc::c_int,
|
||||
mut mm_mime_fields: *mut mailmime_fields,
|
||||
mut mm_content_type: *mut mailmime_content,
|
||||
mut mm_fields: *mut mailimf_fields,
|
||||
mut mm_msg_mime: *mut Mailmime,
|
||||
) -> *mut Mailmime {
|
||||
mailmime_new(
|
||||
mm_type,
|
||||
std::ptr::null(),
|
||||
0,
|
||||
mm_mime_fields,
|
||||
mm_content_type,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
mm_fields,
|
||||
mm_msg_mime,
|
||||
)
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_free(mut mime: *mut Mailmime) {
|
||||
match (*mime).mm_type {
|
||||
1 => {
|
||||
if (*mime).mm_body.is_null() && !(*mime).mm_data.mm_single.is_null() {
|
||||
mailmime_data_free((*mime).mm_data.mm_single);
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
/* do nothing */
|
||||
if !(*mime).mm_data.mm_multipart.mm_preamble.is_null() {
|
||||
mailmime_data_free((*mime).mm_data.mm_multipart.mm_preamble);
|
||||
}
|
||||
if !(*mime).mm_data.mm_multipart.mm_epilogue.is_null() {
|
||||
mailmime_data_free((*mime).mm_data.mm_multipart.mm_epilogue);
|
||||
}
|
||||
clist_foreach(
|
||||
(*mime).mm_data.mm_multipart.mm_mp_list,
|
||||
::std::mem::transmute::<Option<unsafe fn(_: *mut Mailmime) -> ()>, clist_func>(
|
||||
Some(mailmime_free),
|
||||
),
|
||||
0 as *mut libc::c_void,
|
||||
);
|
||||
clist_free((*mime).mm_data.mm_multipart.mm_mp_list);
|
||||
}
|
||||
3 => {
|
||||
if !(*mime).mm_data.mm_message.mm_fields.is_null() {
|
||||
mailimf_fields_free((*mime).mm_data.mm_message.mm_fields);
|
||||
}
|
||||
if !(*mime).mm_data.mm_message.mm_msg_mime.is_null() {
|
||||
mailmime_free((*mime).mm_data.mm_message.mm_msg_mime);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if !(*mime).mm_body.is_null() {
|
||||
mailmime_data_free((*mime).mm_body);
|
||||
}
|
||||
if !(*mime).mm_mime_fields.is_null() {
|
||||
mailmime_fields_free((*mime).mm_mime_fields);
|
||||
}
|
||||
if !(*mime).mm_content_type.is_null() {
|
||||
mailmime_content_free((*mime).mm_content_type);
|
||||
}
|
||||
free(mime as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_encoded_word_new(
|
||||
mut wd_charset: *mut libc::c_char,
|
||||
mut wd_text: *mut libc::c_char,
|
||||
) -> *mut mailmime_encoded_word {
|
||||
let mut ew: *mut mailmime_encoded_word = 0 as *mut mailmime_encoded_word;
|
||||
ew = malloc(::std::mem::size_of::<mailmime_encoded_word>() as libc::size_t)
|
||||
as *mut mailmime_encoded_word;
|
||||
if ew.is_null() {
|
||||
return 0 as *mut mailmime_encoded_word;
|
||||
}
|
||||
(*ew).wd_charset = wd_charset;
|
||||
(*ew).wd_text = wd_text;
|
||||
return ew;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_encoded_word_free(mut ew: *mut mailmime_encoded_word) {
|
||||
mailmime_charset_free((*ew).wd_charset);
|
||||
mailmime_encoded_text_free((*ew).wd_text);
|
||||
free(ew as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_encoded_text_free(mut text: *mut libc::c_char) {
|
||||
free(text as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_charset_free(mut charset: *mut libc::c_char) {
|
||||
free(charset as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_disposition_new(
|
||||
mut dsp_type: *mut mailmime_disposition_type,
|
||||
mut dsp_parms: *mut clist,
|
||||
) -> *mut mailmime_disposition {
|
||||
let mut dsp: *mut mailmime_disposition = 0 as *mut mailmime_disposition;
|
||||
dsp = malloc(::std::mem::size_of::<mailmime_disposition>() as libc::size_t)
|
||||
as *mut mailmime_disposition;
|
||||
if dsp.is_null() {
|
||||
return 0 as *mut mailmime_disposition;
|
||||
}
|
||||
(*dsp).dsp_type = dsp_type;
|
||||
(*dsp).dsp_parms = dsp_parms;
|
||||
return dsp;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_disposition_type_new(
|
||||
mut dsp_type: libc::c_int,
|
||||
mut dsp_extension: *mut libc::c_char,
|
||||
) -> *mut mailmime_disposition_type {
|
||||
let mut m_dsp_type: *mut mailmime_disposition_type = 0 as *mut mailmime_disposition_type;
|
||||
m_dsp_type = malloc(::std::mem::size_of::<mailmime_disposition_type>() as libc::size_t)
|
||||
as *mut mailmime_disposition_type;
|
||||
if m_dsp_type.is_null() {
|
||||
return 0 as *mut mailmime_disposition_type;
|
||||
}
|
||||
(*m_dsp_type).dsp_type = dsp_type;
|
||||
(*m_dsp_type).dsp_extension = dsp_extension;
|
||||
return m_dsp_type;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_disposition_parm_new(
|
||||
mut pa_type: libc::c_int,
|
||||
mut pa_filename: *mut libc::c_char,
|
||||
mut pa_creation_date: *mut libc::c_char,
|
||||
mut pa_modification_date: *mut libc::c_char,
|
||||
mut pa_read_date: *mut libc::c_char,
|
||||
mut pa_size: size_t,
|
||||
mut pa_parameter: *mut mailmime_parameter,
|
||||
) -> *mut mailmime_disposition_parm {
|
||||
let mut dsp_parm: *mut mailmime_disposition_parm = 0 as *mut mailmime_disposition_parm;
|
||||
dsp_parm = malloc(::std::mem::size_of::<mailmime_disposition_parm>() as libc::size_t)
|
||||
as *mut mailmime_disposition_parm;
|
||||
if dsp_parm.is_null() {
|
||||
return 0 as *mut mailmime_disposition_parm;
|
||||
}
|
||||
(*dsp_parm).pa_type = pa_type;
|
||||
match pa_type {
|
||||
0 => (*dsp_parm).pa_data.pa_filename = pa_filename,
|
||||
1 => (*dsp_parm).pa_data.pa_creation_date = pa_creation_date,
|
||||
2 => (*dsp_parm).pa_data.pa_modification_date = pa_modification_date,
|
||||
3 => (*dsp_parm).pa_data.pa_read_date = pa_read_date,
|
||||
4 => (*dsp_parm).pa_data.pa_size = pa_size,
|
||||
5 => (*dsp_parm).pa_data.pa_parameter = pa_parameter,
|
||||
_ => {}
|
||||
}
|
||||
return dsp_parm;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_section_new(mut sec_list: *mut clist) -> *mut mailmime_section {
|
||||
let mut section: *mut mailmime_section = 0 as *mut mailmime_section;
|
||||
section =
|
||||
malloc(::std::mem::size_of::<mailmime_section>() as libc::size_t) as *mut mailmime_section;
|
||||
if section.is_null() {
|
||||
return 0 as *mut mailmime_section;
|
||||
}
|
||||
(*section).sec_list = sec_list;
|
||||
return section;
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_section_free(mut section: *mut mailmime_section) {
|
||||
clist_foreach(
|
||||
(*section).sec_list,
|
||||
::std::mem::transmute::<Option<unsafe extern "C" fn(_: *mut libc::c_void) -> ()>, clist_func>(
|
||||
Some(free),
|
||||
),
|
||||
0 as *mut libc::c_void,
|
||||
);
|
||||
clist_free((*section).sec_list);
|
||||
free(section as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_decoded_part_free(mut part: *mut libc::c_char) {
|
||||
mmap_string_unref(part);
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
use crate::mailmime::types::*;
|
||||
use crate::mailmime::write_generic::*;
|
||||
use crate::mmapstring::*;
|
||||
use crate::other::*;
|
||||
|
||||
unsafe fn do_write(
|
||||
mut data: *mut libc::c_void,
|
||||
mut str: *const libc::c_char,
|
||||
mut length: size_t,
|
||||
) -> libc::c_int {
|
||||
let mut f: *mut MMAPString = 0 as *mut MMAPString;
|
||||
f = data as *mut MMAPString;
|
||||
if mmap_string_append_len(f, str, length).is_null() {
|
||||
return 0i32;
|
||||
} else {
|
||||
return length as libc::c_int;
|
||||
};
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_content_write_mem(
|
||||
mut f: *mut MMAPString,
|
||||
mut col: *mut libc::c_int,
|
||||
mut content: *mut mailmime_content,
|
||||
) -> libc::c_int {
|
||||
return mailmime_content_write_driver(Some(do_write), f as *mut libc::c_void, col, content);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_content_type_write_mem(
|
||||
mut f: *mut MMAPString,
|
||||
mut col: *mut libc::c_int,
|
||||
mut content: *mut mailmime_content,
|
||||
) -> libc::c_int {
|
||||
return mailmime_content_type_write_driver(
|
||||
Some(do_write),
|
||||
f as *mut libc::c_void,
|
||||
col,
|
||||
content,
|
||||
);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_write_mem(
|
||||
mut f: *mut MMAPString,
|
||||
mut col: *mut libc::c_int,
|
||||
mut build_info: *mut Mailmime,
|
||||
) -> libc::c_int {
|
||||
return mailmime_write_driver(Some(do_write), f as *mut libc::c_void, col, build_info);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_quoted_printable_write_mem(
|
||||
mut f: *mut MMAPString,
|
||||
mut col: *mut libc::c_int,
|
||||
mut istext: libc::c_int,
|
||||
mut text: *const libc::c_char,
|
||||
mut size: size_t,
|
||||
) -> libc::c_int {
|
||||
return mailmime_quoted_printable_write_driver(
|
||||
Some(do_write),
|
||||
f as *mut libc::c_void,
|
||||
col,
|
||||
istext,
|
||||
text,
|
||||
size,
|
||||
);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_base64_write_mem(
|
||||
mut f: *mut MMAPString,
|
||||
mut col: *mut libc::c_int,
|
||||
mut text: *const libc::c_char,
|
||||
mut size: size_t,
|
||||
) -> libc::c_int {
|
||||
return mailmime_base64_write_driver(Some(do_write), f as *mut libc::c_void, col, text, size);
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_data_write_mem(
|
||||
mut f: *mut MMAPString,
|
||||
mut col: *mut libc::c_int,
|
||||
mut data: *mut mailmime_data,
|
||||
mut istext: libc::c_int,
|
||||
) -> libc::c_int {
|
||||
return mailmime_data_write_driver(Some(do_write), f as *mut libc::c_void, col, data, istext);
|
||||
}
|
||||
@@ -1,397 +0,0 @@
|
||||
use std::sync::Mutex;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use libc;
|
||||
|
||||
use crate::chash::*;
|
||||
use crate::other::*;
|
||||
|
||||
lazy_static! {
|
||||
static ref mmapstring_lock: Mutex<()> = Mutex::new(());
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct MMAPString {
|
||||
pub str_0: *mut libc::c_char,
|
||||
pub len: size_t,
|
||||
pub allocated_len: size_t,
|
||||
pub fd: libc::c_int,
|
||||
pub mmapped_size: size_t,
|
||||
}
|
||||
|
||||
pub const TMPDIR: &'static str = "/tmp";
|
||||
|
||||
pub unsafe fn mmap_string_new(mut init: *const libc::c_char) -> *mut MMAPString {
|
||||
let mut string: *mut MMAPString = 0 as *mut MMAPString;
|
||||
string = mmap_string_sized_new(if !init.is_null() {
|
||||
strlen(init).wrapping_add(2i32 as libc::size_t)
|
||||
} else {
|
||||
2i32 as libc::size_t
|
||||
});
|
||||
if string.is_null() {
|
||||
return 0 as *mut MMAPString;
|
||||
}
|
||||
if !init.is_null() {
|
||||
mmap_string_append(string, init);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_append(
|
||||
mut string: *mut MMAPString,
|
||||
mut val: *const libc::c_char,
|
||||
) -> *mut MMAPString {
|
||||
return mmap_string_insert_len(string, (*string).len, val, strlen(val));
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_insert_len(
|
||||
mut string: *mut MMAPString,
|
||||
mut pos: size_t,
|
||||
mut val: *const libc::c_char,
|
||||
mut len: size_t,
|
||||
) -> *mut MMAPString {
|
||||
if mmap_string_maybe_expand(string, len).is_null() {
|
||||
return 0 as *mut MMAPString;
|
||||
}
|
||||
if pos < (*string).len {
|
||||
memmove(
|
||||
(*string).str_0.offset(pos as isize).offset(len as isize) as *mut libc::c_void,
|
||||
(*string).str_0.offset(pos as isize) as *const libc::c_void,
|
||||
(*string).len.wrapping_sub(pos),
|
||||
);
|
||||
}
|
||||
memmove(
|
||||
(*string).str_0.offset(pos as isize) as *mut libc::c_void,
|
||||
val as *const libc::c_void,
|
||||
len,
|
||||
);
|
||||
(*string).len = ((*string).len as libc::size_t).wrapping_add(len) as size_t as size_t;
|
||||
*(*string).str_0.offset((*string).len as isize) = 0i32 as libc::c_char;
|
||||
return string;
|
||||
}
|
||||
unsafe fn mmap_string_maybe_expand(
|
||||
mut string: *mut MMAPString,
|
||||
mut len: size_t,
|
||||
) -> *mut MMAPString {
|
||||
if (*string).len.wrapping_add(len) >= (*string).allocated_len {
|
||||
let mut old_size: size_t = 0;
|
||||
let mut newstring: *mut MMAPString = 0 as *mut MMAPString;
|
||||
old_size = (*string).allocated_len;
|
||||
(*string).allocated_len = nearest_power(
|
||||
1i32 as size_t,
|
||||
(*string)
|
||||
.len
|
||||
.wrapping_add(len)
|
||||
.wrapping_add(1i32 as libc::size_t),
|
||||
);
|
||||
newstring = mmap_string_realloc_memory(string);
|
||||
if newstring.is_null() {
|
||||
(*string).allocated_len = old_size
|
||||
}
|
||||
return newstring;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
/* Strings.
|
||||
*/
|
||||
/* SEB */
|
||||
unsafe fn mmap_string_realloc_memory(mut string: *mut MMAPString) -> *mut MMAPString {
|
||||
let mut tmp: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
tmp = realloc(
|
||||
(*string).str_0 as *mut libc::c_void,
|
||||
(*string).allocated_len,
|
||||
) as *mut libc::c_char;
|
||||
if tmp.is_null() {
|
||||
string = 0 as *mut MMAPString
|
||||
} else {
|
||||
(*string).str_0 = tmp
|
||||
}
|
||||
return string;
|
||||
}
|
||||
/* MMAPString */
|
||||
#[inline]
|
||||
unsafe fn nearest_power(mut base: size_t, mut num: size_t) -> size_t {
|
||||
if num > (-1i32 as size_t).wrapping_div(2i32 as libc::size_t) {
|
||||
return -1i32 as size_t;
|
||||
} else {
|
||||
let mut n: size_t = base;
|
||||
while n < num {
|
||||
n <<= 1i32
|
||||
}
|
||||
return n;
|
||||
};
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_sized_new(mut dfl_size: size_t) -> *mut MMAPString {
|
||||
let mut string: *mut MMAPString = 0 as *mut MMAPString;
|
||||
string = malloc(::std::mem::size_of::<MMAPString>() as libc::size_t) as *mut MMAPString;
|
||||
if string.is_null() {
|
||||
return 0 as *mut MMAPString;
|
||||
}
|
||||
(*string).allocated_len = 0i32 as size_t;
|
||||
(*string).len = 0i32 as size_t;
|
||||
(*string).str_0 = 0 as *mut libc::c_char;
|
||||
(*string).fd = -1i32;
|
||||
(*string).mmapped_size = 0i32 as size_t;
|
||||
if mmap_string_maybe_expand(
|
||||
string,
|
||||
if dfl_size > 2i32 as libc::size_t {
|
||||
dfl_size
|
||||
} else {
|
||||
2i32 as libc::size_t
|
||||
},
|
||||
)
|
||||
.is_null()
|
||||
{
|
||||
free(string as *mut libc::c_void);
|
||||
return 0 as *mut MMAPString;
|
||||
}
|
||||
*(*string).str_0.offset(0isize) = 0i32 as libc::c_char;
|
||||
return string;
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_new_len(
|
||||
mut init: *const libc::c_char,
|
||||
mut len: size_t,
|
||||
) -> *mut MMAPString {
|
||||
let mut string: *mut MMAPString = 0 as *mut MMAPString;
|
||||
if len <= 0i32 as libc::size_t {
|
||||
return mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
|
||||
} else {
|
||||
string = mmap_string_sized_new(len);
|
||||
if string.is_null() {
|
||||
return string;
|
||||
}
|
||||
if !init.is_null() {
|
||||
mmap_string_append_len(string, init, len);
|
||||
}
|
||||
return string;
|
||||
};
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_append_len(
|
||||
mut string: *mut MMAPString,
|
||||
mut val: *const libc::c_char,
|
||||
mut len: size_t,
|
||||
) -> *mut MMAPString {
|
||||
return mmap_string_insert_len(string, (*string).len, val, len);
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_free(mut string: *mut MMAPString) {
|
||||
if string.is_null() {
|
||||
return;
|
||||
}
|
||||
free((*string).str_0 as *mut libc::c_void);
|
||||
free(string as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_assign(
|
||||
mut string: *mut MMAPString,
|
||||
mut rval: *const libc::c_char,
|
||||
) -> *mut MMAPString {
|
||||
mmap_string_truncate(string, 0i32 as size_t);
|
||||
if mmap_string_append(string, rval).is_null() {
|
||||
return 0 as *mut MMAPString;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_truncate(
|
||||
mut string: *mut MMAPString,
|
||||
mut len: size_t,
|
||||
) -> *mut MMAPString {
|
||||
(*string).len = if len < (*string).len {
|
||||
len
|
||||
} else {
|
||||
(*string).len
|
||||
};
|
||||
*(*string).str_0.offset((*string).len as isize) = 0i32 as libc::c_char;
|
||||
return string;
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_set_size(
|
||||
mut string: *mut MMAPString,
|
||||
mut len: size_t,
|
||||
) -> *mut MMAPString {
|
||||
if len >= (*string).allocated_len {
|
||||
if mmap_string_maybe_expand(string, len.wrapping_sub((*string).len)).is_null() {
|
||||
return 0 as *mut MMAPString;
|
||||
}
|
||||
}
|
||||
(*string).len = len;
|
||||
*(*string).str_0.offset(len as isize) = 0i32 as libc::c_char;
|
||||
return string;
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_append_c(
|
||||
mut string: *mut MMAPString,
|
||||
mut c: libc::c_char,
|
||||
) -> *mut MMAPString {
|
||||
return mmap_string_insert_c(string, (*string).len, c);
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_insert_c(
|
||||
mut string: *mut MMAPString,
|
||||
mut pos: size_t,
|
||||
mut c: libc::c_char,
|
||||
) -> *mut MMAPString {
|
||||
if mmap_string_maybe_expand(string, 1i32 as size_t).is_null() {
|
||||
return 0 as *mut MMAPString;
|
||||
}
|
||||
if pos < (*string).len {
|
||||
memmove(
|
||||
(*string).str_0.offset(pos as isize).offset(1isize) as *mut libc::c_void,
|
||||
(*string).str_0.offset(pos as isize) as *const libc::c_void,
|
||||
(*string).len.wrapping_sub(pos),
|
||||
);
|
||||
}
|
||||
*(*string).str_0.offset(pos as isize) = c;
|
||||
(*string).len =
|
||||
((*string).len as libc::size_t).wrapping_add(1i32 as libc::size_t) as size_t as size_t;
|
||||
*(*string).str_0.offset((*string).len as isize) = 0i32 as libc::c_char;
|
||||
return string;
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_prepend(
|
||||
mut string: *mut MMAPString,
|
||||
mut val: *const libc::c_char,
|
||||
) -> *mut MMAPString {
|
||||
return mmap_string_insert_len(string, 0i32 as size_t, val, strlen(val));
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_prepend_c(
|
||||
mut string: *mut MMAPString,
|
||||
mut c: libc::c_char,
|
||||
) -> *mut MMAPString {
|
||||
return mmap_string_insert_c(string, 0i32 as size_t, c);
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_prepend_len(
|
||||
mut string: *mut MMAPString,
|
||||
mut val: *const libc::c_char,
|
||||
mut len: size_t,
|
||||
) -> *mut MMAPString {
|
||||
return mmap_string_insert_len(string, 0i32 as size_t, val, len);
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_insert(
|
||||
mut string: *mut MMAPString,
|
||||
mut pos: size_t,
|
||||
mut val: *const libc::c_char,
|
||||
) -> *mut MMAPString {
|
||||
return mmap_string_insert_len(string, pos, val, strlen(val));
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_erase(
|
||||
mut string: *mut MMAPString,
|
||||
mut pos: size_t,
|
||||
mut len: size_t,
|
||||
) -> *mut MMAPString {
|
||||
if pos.wrapping_add(len) < (*string).len {
|
||||
memmove(
|
||||
(*string).str_0.offset(pos as isize) as *mut libc::c_void,
|
||||
(*string).str_0.offset(pos as isize).offset(len as isize) as *const libc::c_void,
|
||||
(*string).len.wrapping_sub(pos.wrapping_add(len)),
|
||||
);
|
||||
}
|
||||
(*string).len = ((*string).len as libc::size_t).wrapping_sub(len) as size_t as size_t;
|
||||
*(*string).str_0.offset((*string).len as isize) = 0i32 as libc::c_char;
|
||||
return string;
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_set_ceil(mut ceil: size_t) {
|
||||
mmap_string_ceil = ceil;
|
||||
}
|
||||
static mut mmap_string_ceil: size_t = (8i32 * 1024i32 * 1024i32) as size_t;
|
||||
|
||||
pub unsafe fn mmap_string_ref(mut string: *mut MMAPString) -> libc::c_int {
|
||||
let mut ht: *mut chash = 0 as *mut chash;
|
||||
let mut r: libc::c_int = 0;
|
||||
let mut key: chashdatum = chashdatum {
|
||||
data: 0 as *mut libc::c_void,
|
||||
len: 0,
|
||||
};
|
||||
let mut data: chashdatum = chashdatum {
|
||||
data: 0 as *mut libc::c_void,
|
||||
len: 0,
|
||||
};
|
||||
mmapstring_lock.lock().unwrap();
|
||||
if mmapstring_hashtable.is_null() {
|
||||
mmapstring_hashtable_init();
|
||||
}
|
||||
ht = mmapstring_hashtable;
|
||||
if ht.is_null() {
|
||||
return -1i32;
|
||||
}
|
||||
key.data = &mut (*string).str_0 as *mut *mut libc::c_char as *mut libc::c_void;
|
||||
key.len = ::std::mem::size_of::<*mut libc::c_char>() as libc::size_t as libc::c_uint;
|
||||
data.data = string as *mut libc::c_void;
|
||||
data.len = 0i32 as libc::c_uint;
|
||||
r = chash_set(
|
||||
mmapstring_hashtable,
|
||||
&mut key,
|
||||
&mut data,
|
||||
0 as *mut chashdatum,
|
||||
);
|
||||
|
||||
if r < 0i32 {
|
||||
return r;
|
||||
}
|
||||
return 0i32;
|
||||
}
|
||||
|
||||
static mut mmapstring_hashtable: *mut chash = 0 as *const chash as *mut chash;
|
||||
unsafe fn mmapstring_hashtable_init() {
|
||||
mmapstring_hashtable = chash_new(13i32 as libc::c_uint, 1i32);
|
||||
}
|
||||
|
||||
pub unsafe fn mmap_string_unref(mut str: *mut libc::c_char) -> libc::c_int {
|
||||
let mut string: *mut MMAPString = 0 as *mut MMAPString;
|
||||
let mut ht: *mut chash = 0 as *mut chash;
|
||||
let mut key: chashdatum = chashdatum {
|
||||
data: 0 as *mut libc::c_void,
|
||||
len: 0,
|
||||
};
|
||||
let mut data: chashdatum = chashdatum {
|
||||
data: 0 as *mut libc::c_void,
|
||||
len: 0,
|
||||
};
|
||||
let mut r: libc::c_int = 0;
|
||||
if str.is_null() {
|
||||
return -1i32;
|
||||
}
|
||||
mmapstring_lock.lock().unwrap();
|
||||
ht = mmapstring_hashtable;
|
||||
if ht.is_null() {
|
||||
return -1i32;
|
||||
}
|
||||
key.data = &mut str as *mut *mut libc::c_char as *mut libc::c_void;
|
||||
key.len = ::std::mem::size_of::<*mut libc::c_char>() as libc::size_t as libc::c_uint;
|
||||
r = chash_get(ht, &mut key, &mut data);
|
||||
if r < 0i32 {
|
||||
string = 0 as *mut MMAPString
|
||||
} else {
|
||||
string = data.data as *mut MMAPString
|
||||
}
|
||||
if !string.is_null() {
|
||||
chash_delete(ht, &mut key, 0 as *mut chashdatum);
|
||||
if chash_count(ht) == 0i32 as libc::c_uint {
|
||||
chash_free(ht);
|
||||
mmapstring_hashtable = 0 as *mut chash
|
||||
}
|
||||
}
|
||||
if !string.is_null() {
|
||||
mmap_string_free(string);
|
||||
return 0i32;
|
||||
} else {
|
||||
return -1i32;
|
||||
};
|
||||
}
|
||||
#[inline]
|
||||
unsafe fn chash_count(mut hash: *mut chash) -> libc::c_uint {
|
||||
return (*hash).count;
|
||||
}
|
||||
|
||||
pub unsafe fn mmapstring_init_lock() {}
|
||||
pub unsafe fn mmapstring_uninit_lock() {}
|
||||
1728
mmime/src/other.rs
@@ -3,6 +3,7 @@
|
||||
from __future__ import print_function
|
||||
import atexit
|
||||
import threading
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from array import array
|
||||
@@ -25,7 +26,7 @@ class Account(object):
|
||||
by the underlying deltachat core library. All public Account methods are
|
||||
meant to be memory-safe and return memory-safe objects.
|
||||
"""
|
||||
def __init__(self, db_path, logid=None, eventlogging=True, debug=True):
|
||||
def __init__(self, db_path, logid=None, eventlogging=True, os_name=None, debug=True):
|
||||
""" initialize account object.
|
||||
|
||||
:param db_path: a path to the account database. The database
|
||||
@@ -33,10 +34,11 @@ class Account(object):
|
||||
:param logid: an optional logging prefix that should be used with
|
||||
the default internal logging.
|
||||
:param eventlogging: if False no eventlogging and no context callback will be configured
|
||||
:param os_name: this will be put to the X-Mailer header in outgoing messages
|
||||
:param debug: turn on debug logging for events.
|
||||
"""
|
||||
self._dc_context = ffi.gc(
|
||||
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
||||
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, as_dc_charpointer(os_name)),
|
||||
_destroy_dc_context,
|
||||
)
|
||||
if eventlogging:
|
||||
@@ -94,9 +96,12 @@ class Account(object):
|
||||
"""
|
||||
self._check_config_key(name)
|
||||
name = name.encode("utf8")
|
||||
value = value.encode("utf8")
|
||||
if name == b"addr" and self.is_configured():
|
||||
raise ValueError("can not change 'addr' after account is configured.")
|
||||
if value is not None:
|
||||
value = value.encode("utf8")
|
||||
else:
|
||||
value = ffi.NULL
|
||||
lib.dc_set_config(self._dc_context, name, value)
|
||||
|
||||
def get_config(self, name):
|
||||
@@ -132,6 +137,18 @@ class Account(object):
|
||||
"""
|
||||
return lib.dc_is_configured(self._dc_context)
|
||||
|
||||
def set_avatar(self, img_path):
|
||||
"""Set self avatar.
|
||||
|
||||
:raises ValueError: if profile image could not be set
|
||||
:returns: None
|
||||
"""
|
||||
if img_path is None:
|
||||
self.set_config("selfavatar", None)
|
||||
else:
|
||||
assert os.path.exists(img_path), img_path
|
||||
self.set_config("selfavatar", img_path)
|
||||
|
||||
def check_is_configured(self):
|
||||
""" Raise ValueError if this account is not configured. """
|
||||
if not self.is_configured():
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import mimetypes
|
||||
import calendar
|
||||
import json
|
||||
from datetime import datetime
|
||||
import os
|
||||
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
|
||||
@@ -108,6 +109,30 @@ class Chat(object):
|
||||
|
||||
# ------ chat messaging API ------------------------------
|
||||
|
||||
def send_msg(self, msg):
|
||||
"""send a message by using a ready Message object.
|
||||
|
||||
:param msg: a :class:`deltachat.message.Message` instance
|
||||
previously returned by
|
||||
e.g. :meth:`deltachat.message.Message.new_empty` or
|
||||
:meth:`prepare_file`.
|
||||
:raises ValueError: if message can not be sent.
|
||||
|
||||
:returns: a :class:`deltachat.message.Message` instance as
|
||||
sent out. This is the same object as was passed in, which
|
||||
has been modified with the new state of the core.
|
||||
"""
|
||||
if msg.is_out_preparing():
|
||||
assert msg.id != 0
|
||||
# get a fresh copy of dc_msg, the core needs it
|
||||
msg = Message.from_db(self.account, msg.id)
|
||||
sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
|
||||
if sent_id == 0:
|
||||
raise ValueError("message could not be sent")
|
||||
# modify message in place to avoid bad state for the caller
|
||||
msg._dc_msg = Message.from_db(self.account, sent_id)._dc_msg
|
||||
return msg
|
||||
|
||||
def send_text(self, text):
|
||||
""" send a text message and return the resulting Message instance.
|
||||
|
||||
@@ -129,9 +154,12 @@ class Chat(object):
|
||||
:raises ValueError: if message can not be send/chat does not exist.
|
||||
:returns: the resulting :class:`deltachat.message.Message` instance
|
||||
"""
|
||||
msg = self.prepare_message_file(path=path, mime_type=mime_type)
|
||||
self.send_prepared(msg)
|
||||
return msg
|
||||
msg = Message.new_empty(self.account, view_type="file")
|
||||
msg.set_file(path, mime_type)
|
||||
sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
|
||||
if sent_id == 0:
|
||||
raise ValueError("message could not be sent")
|
||||
return Message.from_db(self.account, sent_id)
|
||||
|
||||
def send_image(self, path):
|
||||
""" send an image message and return the resulting Message instance.
|
||||
@@ -141,9 +169,12 @@ class Chat(object):
|
||||
:returns: the resulting :class:`deltachat.message.Message` instance
|
||||
"""
|
||||
mime_type = mimetypes.guess_type(path)[0]
|
||||
msg = self.prepare_message_file(path=path, mime_type=mime_type, view_type="image")
|
||||
self.send_prepared(msg)
|
||||
return msg
|
||||
msg = Message.new_empty(self.account, view_type="image")
|
||||
msg.set_file(path, mime_type)
|
||||
sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
|
||||
if sent_id == 0:
|
||||
raise ValueError("message could not be sent")
|
||||
return Message.from_db(self.account, sent_id)
|
||||
|
||||
def prepare_message(self, msg):
|
||||
""" create a new prepared message.
|
||||
@@ -242,6 +273,12 @@ class Chat(object):
|
||||
"""
|
||||
return lib.dc_marknoticed_chat(self._dc_context, self.id)
|
||||
|
||||
def get_summary(self):
|
||||
""" return dictionary with summary information. """
|
||||
dc_res = lib.dc_chat_get_info_json(self._dc_context, self.id)
|
||||
s = from_dc_charpointer(dc_res)
|
||||
return json.loads(s)
|
||||
|
||||
# ------ group management API ------------------------------
|
||||
|
||||
def add_contact(self, contact):
|
||||
@@ -324,6 +361,18 @@ class Chat(object):
|
||||
return None
|
||||
return from_dc_charpointer(dc_res)
|
||||
|
||||
def get_color(self):
|
||||
"""return the color of the chat.
|
||||
:returns: color as 0x00rrggbb
|
||||
"""
|
||||
return lib.dc_chat_get_color(self._dc_chat)
|
||||
|
||||
def get_subtitle(self):
|
||||
"""return the subtitle of the chat
|
||||
:returns: the subtitle
|
||||
"""
|
||||
return from_dc_charpointer(lib.dc_chat_get_subtitle(self._dc_chat))
|
||||
|
||||
# ------ location streaming API ------------------------------
|
||||
|
||||
def is_sending_locations(self):
|
||||
@@ -332,6 +381,12 @@ class Chat(object):
|
||||
"""
|
||||
return lib.dc_is_sending_locations_to_chat(self._dc_context, self.id)
|
||||
|
||||
def is_archived(self):
|
||||
"""return True if this chat is archived.
|
||||
:returns: True if archived.
|
||||
"""
|
||||
return lib.dc_chat_get_archived(self._dc_chat)
|
||||
|
||||
def enable_sending_locations(self, seconds):
|
||||
"""enable sending locations for this chat.
|
||||
|
||||
|
||||
@@ -68,7 +68,6 @@ DC_LP_SMTP_SOCKET_SSL = 0x20000
|
||||
DC_LP_SMTP_SOCKET_PLAIN = 0x40000
|
||||
DC_CERTCK_AUTO = 0
|
||||
DC_CERTCK_STRICT = 1
|
||||
DC_CERTCK_ACCEPT_INVALID_HOSTNAMES = 2
|
||||
DC_CERTCK_ACCEPT_INVALID_CERTIFICATES = 3
|
||||
DC_EMPTY_MVBOX = 0x01
|
||||
DC_EMPTY_INBOX = 0x02
|
||||
@@ -98,6 +97,7 @@ DC_EVENT_IMEX_PROGRESS = 2051
|
||||
DC_EVENT_IMEX_FILE_WRITTEN = 2052
|
||||
DC_EVENT_SECUREJOIN_INVITER_PROGRESS = 2060
|
||||
DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061
|
||||
DC_EVENT_SECUREJOIN_MEMBER_ADDED = 2062
|
||||
DC_EVENT_FILE_COPIED = 2055
|
||||
DC_EVENT_IS_OFFLINE = 2081
|
||||
DC_EVENT_GET_STRING = 2091
|
||||
@@ -150,7 +150,8 @@ DC_STR_MSGLOCATIONENABLED = 64
|
||||
DC_STR_MSGLOCATIONDISABLED = 65
|
||||
DC_STR_LOCATION = 66
|
||||
DC_STR_STICKER = 67
|
||||
DC_STR_COUNT = 67
|
||||
DC_STR_DEVICE_MESSAGES = 68
|
||||
DC_STR_COUNT = 68
|
||||
# end const generated
|
||||
|
||||
|
||||
|
||||
@@ -47,3 +47,13 @@ class Contact(object):
|
||||
def is_verified(self):
|
||||
""" Return True if the contact is verified. """
|
||||
return lib.dc_contact_is_verified(self._dc_contact)
|
||||
|
||||
def get_profile_image(self):
|
||||
"""Get contact profile image.
|
||||
|
||||
:returns: path to profile image, None if no profile image exists.
|
||||
"""
|
||||
dc_res = lib.dc_contact_get_profile_image(self._dc_contact)
|
||||
if dc_res == ffi.NULL:
|
||||
return None
|
||||
return from_dc_charpointer(dc_res)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
""" The Message object. """
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from . import props
|
||||
from .cutil import from_dc_charpointer, as_dc_charpointer
|
||||
from .capi import lib, ffi
|
||||
@@ -58,8 +57,6 @@ class Message(object):
|
||||
|
||||
def set_text(self, text):
|
||||
"""set text of this message. """
|
||||
assert self.id > 0, "message not prepared"
|
||||
assert self.is_out_preparing()
|
||||
lib.dc_msg_set_text(self._dc_msg, as_dc_charpointer(text))
|
||||
|
||||
@props.with_doc
|
||||
@@ -72,19 +69,6 @@ class Message(object):
|
||||
mtype = ffi.NULL if mime_type is None else as_dc_charpointer(mime_type)
|
||||
if not os.path.exists(path):
|
||||
raise ValueError("path does not exist: {!r}".format(path))
|
||||
blobdir = self.account.get_blobdir()
|
||||
if not path.startswith(blobdir):
|
||||
for i in range(50):
|
||||
ext = "" if i == 0 else "-" + str(i)
|
||||
dest = os.path.join(blobdir, os.path.basename(path) + ext)
|
||||
if os.path.exists(dest):
|
||||
continue
|
||||
shutil.copyfile(path, dest)
|
||||
break
|
||||
else:
|
||||
raise ValueError("could not create blobdir-path for {}".format(path))
|
||||
path = dest
|
||||
assert path.startswith(blobdir), path
|
||||
lib.dc_msg_set_file(self._dc_msg, as_dc_charpointer(path), mtype)
|
||||
|
||||
@props.with_doc
|
||||
@@ -162,7 +146,7 @@ class Message(object):
|
||||
if 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_bytes(s)
|
||||
return email.message_from_string(s)
|
||||
|
||||
@property
|
||||
@@ -190,7 +174,7 @@ class Message(object):
|
||||
@property
|
||||
def _msgstate(self):
|
||||
if self.id == 0:
|
||||
dc_msg = self.message._dc_msg
|
||||
dc_msg = self._dc_msg
|
||||
else:
|
||||
# load message from db to get a fresh/current state
|
||||
dc_msg = ffi.gc(
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"""Provider info class."""
|
||||
|
||||
from .capi import ffi, lib
|
||||
from .capi import lib
|
||||
from .cutil import as_dc_charpointer, from_dc_charpointer
|
||||
import json
|
||||
|
||||
|
||||
class ProviderNotFoundError(Exception):
|
||||
@@ -16,13 +17,12 @@ class Provider(object):
|
||||
"""
|
||||
|
||||
def __init__(self, domain):
|
||||
provider = ffi.gc(
|
||||
lib.dc_provider_new_from_domain(as_dc_charpointer(domain)),
|
||||
lib.dc_provider_unref,
|
||||
provider = from_dc_charpointer(
|
||||
lib.dc_provider_json_from_domain(as_dc_charpointer(domain))
|
||||
)
|
||||
if provider == ffi.NULL:
|
||||
if provider == "":
|
||||
raise ProviderNotFoundError("Provider not found")
|
||||
self._provider = provider
|
||||
self._provider = json.loads(provider)
|
||||
|
||||
@classmethod
|
||||
def from_email(cls, email):
|
||||
@@ -35,33 +35,30 @@ class Provider(object):
|
||||
@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))
|
||||
return "https://providers.delta.chat/" + self._provider['overview_page']
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name of the provider."""
|
||||
return from_dc_charpointer(lib.dc_provider_get_name(self._provider))
|
||||
return self._provider['name']
|
||||
|
||||
@property
|
||||
def markdown(self):
|
||||
"""Content of the information page, formatted as markdown."""
|
||||
return from_dc_charpointer(
|
||||
lib.dc_provider_get_markdown(self._provider))
|
||||
return self._provider['markdown']
|
||||
|
||||
@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))
|
||||
return self._provider['status']['date']
|
||||
|
||||
@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.
|
||||
This is
|
||||
:attr:`"OK"`,
|
||||
:attr:`"PREPARATION"` or
|
||||
:attr:`"BROKEN"`.
|
||||
"""
|
||||
return lib.dc_provider_get_status(self._provider)
|
||||
return self._provider['status']['state']
|
||||
|
||||
@@ -85,16 +85,21 @@ class SessionLiveConfigFromFile:
|
||||
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)})
|
||||
self.url = url
|
||||
self.create_token = create_token
|
||||
|
||||
def get(self, index):
|
||||
try:
|
||||
return self.configlist[index]
|
||||
except IndexError:
|
||||
assert index == len(self.configlist), index
|
||||
res = requests.post(self.url, json={"token_create_user": int(self.create_token)})
|
||||
if res.status_code != 200:
|
||||
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]
|
||||
return config
|
||||
|
||||
def exists(self):
|
||||
return bool(self.configlist)
|
||||
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.7 KiB |
@@ -16,6 +16,14 @@ class TestOfflineAccountBasic:
|
||||
with pytest.raises(ValueError):
|
||||
Account(p.strpath)
|
||||
|
||||
def test_os_name(self, tmpdir):
|
||||
p = tmpdir.join("hello.db")
|
||||
# we can't easily test if os_name is used in X-Mailer
|
||||
# outgoing messages without a full Online test
|
||||
# but we at least check Account accepts the arg
|
||||
ac1 = Account(p.strpath, os_name="solarpunk")
|
||||
ac1.get_info()
|
||||
|
||||
def test_getinfo(self, acfactory):
|
||||
ac1 = acfactory.get_unconfigured_account()
|
||||
d = ac1.get_info()
|
||||
@@ -155,6 +163,18 @@ class TestOfflineChat:
|
||||
chat.set_name("title2")
|
||||
assert chat.get_name() == "title2"
|
||||
|
||||
d = chat.get_summary()
|
||||
print(d)
|
||||
assert d["id"] == chat.id
|
||||
assert d["type"] == chat.get_type()
|
||||
assert d["name"] == chat.get_name()
|
||||
assert d["archived"] == chat.is_archived()
|
||||
# assert d["param"] == chat.param
|
||||
assert d["color"] == chat.get_color()
|
||||
assert d["profile_image"] == "" if chat.get_profile_image() is None else chat.get_profile_image()
|
||||
assert d["subtitle"] == chat.get_subtitle()
|
||||
assert d["draft"] == "" if chat.get_draft() is None else chat.get_draft()
|
||||
|
||||
def test_group_chat_creation_with_translation(self, ac1):
|
||||
ac1.set_stock_translation(const.DC_STR_NEWGROUPDRAFT, "xyz %1$s")
|
||||
ac1._evlogger.consume_events()
|
||||
@@ -370,6 +390,23 @@ class TestOfflineChat:
|
||||
assert not res.is_ask_verifygroup()
|
||||
assert res.contact_id == 10
|
||||
|
||||
def test_group_chat_many_members_add_remove(self, ac1, lp):
|
||||
lp.sec("ac1: creating group chat with 10 other members")
|
||||
chat = ac1.create_group_chat(name="title1")
|
||||
contacts = []
|
||||
for i in range(10):
|
||||
contact = ac1.create_contact("some{}@example.org".format(i))
|
||||
contacts.append(contact)
|
||||
chat.add_contact(contact)
|
||||
|
||||
num_contacts = len(chat.get_contacts())
|
||||
assert num_contacts == 11
|
||||
|
||||
lp.sec("ac1: removing two contacts and checking things are right")
|
||||
chat.remove_contact(contacts[9])
|
||||
chat.remove_contact(contacts[3])
|
||||
assert len(chat.get_contacts()) == 9
|
||||
|
||||
|
||||
class TestOnlineAccount:
|
||||
def get_chat(self, ac1, ac2, both_created=False):
|
||||
@@ -430,6 +467,62 @@ class TestOnlineAccount:
|
||||
assert self_addr not in ev[2]
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_DELETED_BLOB_FILE")
|
||||
|
||||
def test_send_file_twice_unicode_filename_mangling(self, tmpdir, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
|
||||
basename = "somedäüta.html.zip"
|
||||
p = os.path.join(tmpdir.strpath, basename)
|
||||
with open(p, "w") as f:
|
||||
f.write("some data")
|
||||
|
||||
def send_and_receive_message():
|
||||
lp.sec("ac1: prepare and send attachment + text to ac2")
|
||||
msg1 = Message.new_empty(ac1, "file")
|
||||
msg1.set_text("withfile")
|
||||
msg1.set_file(p)
|
||||
chat.send_msg(msg1)
|
||||
|
||||
lp.sec("ac2: receive message")
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
return ac2.get_message_by_id(ev[2])
|
||||
|
||||
msg = send_and_receive_message()
|
||||
assert msg.text == "withfile"
|
||||
assert open(msg.filename).read() == "some data"
|
||||
assert msg.filename.endswith(basename)
|
||||
|
||||
msg2 = send_and_receive_message()
|
||||
assert msg2.text == "withfile"
|
||||
assert open(msg2.filename).read() == "some data"
|
||||
assert msg2.filename.endswith("html.zip")
|
||||
assert msg.filename != msg2.filename
|
||||
|
||||
def test_send_file_html_attachment(self, tmpdir, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
|
||||
basename = "test.html"
|
||||
content = "<html><body>text</body>data"
|
||||
|
||||
p = os.path.join(tmpdir.strpath, basename)
|
||||
with open(p, "w") as f:
|
||||
# write wrong html to see if core tries to parse it
|
||||
# (it shouldn't as it's a file attachment)
|
||||
f.write(content)
|
||||
|
||||
lp.sec("ac1: prepare and send attachment + text to ac2")
|
||||
chat.send_file(p, mime_type="text/html")
|
||||
|
||||
lp.sec("ac2: receive message")
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
msg = ac2.get_message_by_id(ev[2])
|
||||
|
||||
assert open(msg.filename).read() == content
|
||||
assert msg.filename.endswith(basename)
|
||||
|
||||
def test_mvbox_sentbox_threads(self, acfactory, lp):
|
||||
lp.sec("ac1: start with mvbox thread")
|
||||
ac1 = acfactory.get_online_configuring_account(mvbox=True, sentbox=True)
|
||||
@@ -461,27 +554,46 @@ class TestOnlineAccount:
|
||||
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
|
||||
def test_forward_messages(self, acfactory):
|
||||
def test_move_works_on_self_sent(self, acfactory):
|
||||
ac1 = acfactory.get_online_configuring_account(mvbox=True)
|
||||
ac1.set_config("bcc_self", "1")
|
||||
ac2 = acfactory.get_online_configuring_account()
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
chat.send_text("message1")
|
||||
chat.send_text("message2")
|
||||
chat.send_text("message3")
|
||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
|
||||
def test_forward_messages(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
|
||||
lp.sec("ac1: send message to ac2")
|
||||
msg_out = chat.send_text("message2")
|
||||
|
||||
# wait for other account to receive
|
||||
lp.sec("ac2: wait for receive")
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[2] == msg_out.id
|
||||
msg_in = ac2.get_message_by_id(msg_out.id)
|
||||
assert msg_in.text == "message2"
|
||||
|
||||
# check the message arrived in contact-requests/deaddrop
|
||||
lp.sec("ac2: check that the message arrive in deaddrop")
|
||||
chat2 = msg_in.chat
|
||||
assert msg_in in chat2.get_messages()
|
||||
assert not msg_in.is_forwarded()
|
||||
assert chat2.is_deaddrop()
|
||||
assert chat2 == ac2.get_deaddrop_chat()
|
||||
|
||||
lp.sec("ac2: create new chat and forward message to it")
|
||||
chat3 = ac2.create_group_chat("newgroup")
|
||||
assert not chat3.is_promoted()
|
||||
ac2.forward_messages([msg_in], chat3)
|
||||
|
||||
lp.sec("ac2: check new chat has a forwarded message")
|
||||
assert chat3.is_promoted()
|
||||
messages = chat3.get_messages()
|
||||
msg = messages[-1]
|
||||
@@ -528,6 +640,9 @@ class TestOnlineAccount:
|
||||
def test_send_and_receive_message_markseen(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
# make DC's life harder wrt to encodings
|
||||
ac1.set_config("displayname", "ä name")
|
||||
|
||||
lp.sec("ac1: create chat with ac2")
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
|
||||
@@ -545,6 +660,7 @@ class TestOnlineAccount:
|
||||
msg_in = ac2.get_message_by_id(msg_out.id)
|
||||
assert msg_in.text == "message1"
|
||||
assert not msg_in.is_forwarded()
|
||||
assert msg_in.get_sender_contact().display_name == ac1.get_config("displayname")
|
||||
|
||||
lp.sec("check the message arrived in contact-requets/deaddrop")
|
||||
chat2 = msg_in.chat
|
||||
@@ -579,6 +695,39 @@ class TestOnlineAccount:
|
||||
except queue.Empty:
|
||||
pass # mark_seen_messages() has generated events before it returns
|
||||
|
||||
def test_mdn_asymetric(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
lp.sec("ac1: create chat with ac2")
|
||||
chat = self.get_chat(ac1, ac2, both_created=True)
|
||||
|
||||
# make sure mdns are enabled (usually enabled by default already)
|
||||
ac1.set_config("mdns_enabled", "1")
|
||||
ac2.set_config("mdns_enabled", "1")
|
||||
|
||||
lp.sec("sending text message from ac1 to ac2")
|
||||
msg_out = chat.send_text("message1")
|
||||
|
||||
assert len(chat.get_messages()) == 1
|
||||
|
||||
lp.sec("disable ac1 MDNs")
|
||||
ac1.set_config("mdns_enabled", "0")
|
||||
|
||||
lp.sec("wait for ac2 to receive message")
|
||||
msg = ac2.wait_next_incoming_message()
|
||||
|
||||
assert len(msg.chat.get_messages()) == 1
|
||||
|
||||
lp.sec("ac2: mark incoming message as seen")
|
||||
ac2.mark_seen_messages([msg])
|
||||
|
||||
lp.sec("ac1: waiting for incoming activity")
|
||||
# wait for MOVED event because even ignored read-receipts should be moved
|
||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
|
||||
assert len(chat.get_messages()) == 1
|
||||
assert not msg_out.is_out_mdn_received()
|
||||
|
||||
def test_send_and_receive_will_encrypt_decrypt(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
@@ -607,6 +756,12 @@ class TestOnlineAccount:
|
||||
assert msg_back.text == "message-back"
|
||||
assert msg_back.is_encrypted()
|
||||
|
||||
# Test that we do not gossip peer keys in 1-to-1 chat,
|
||||
# as it makes no sense to gossip to peers their own keys.
|
||||
# Gossip is only sent in encrypted messages,
|
||||
# and we sent encrypted msg_back right above.
|
||||
assert chat2b.get_summary()["gossiped_timestamp"] == 0
|
||||
|
||||
lp.sec("create group chat with two members, one of which has no encrypt state")
|
||||
chat = ac1.create_group_chat("encryption test")
|
||||
chat.add_contact(ac1.create_contact(ac2.get_config("addr")))
|
||||
@@ -615,6 +770,71 @@ class TestOnlineAccount:
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
|
||||
assert not msg.is_encrypted()
|
||||
|
||||
def test_send_first_message_as_long_unicode_with_cr(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
ac2.set_config("save_mime_headers", "1")
|
||||
|
||||
lp.sec("ac1: create chat with ac2")
|
||||
chat = self.get_chat(ac1, ac2, both_created=True)
|
||||
|
||||
lp.sec("sending multi-line non-unicode message from ac1 to ac2")
|
||||
text1 = "hello\nworld"
|
||||
msg_out = chat.send_text(text1)
|
||||
assert not msg_out.is_encrypted()
|
||||
|
||||
lp.sec("sending multi-line unicode text message from ac1 to ac2")
|
||||
text2 = "äalis\nthis is ßßÄ"
|
||||
msg_out = chat.send_text(text2)
|
||||
assert not msg_out.is_encrypted()
|
||||
|
||||
lp.sec("wait for ac2 to receive multi-line non-unicode message")
|
||||
msg_in = ac2.wait_next_incoming_message()
|
||||
assert msg_in.text == text1
|
||||
|
||||
lp.sec("wait for ac2 to receive multi-line unicode message")
|
||||
msg_in = ac2.wait_next_incoming_message()
|
||||
assert msg_in.text == text2
|
||||
assert ac1.get_config("addr") in msg_in.chat.get_name()
|
||||
|
||||
def test_reply_encrypted(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
lp.sec("ac1: create chat with ac2")
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
|
||||
lp.sec("sending text message from ac1 to ac2")
|
||||
msg_out = chat.send_text("message1")
|
||||
assert not msg_out.is_encrypted()
|
||||
|
||||
lp.sec("wait for ac2 to receive message")
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
msg_in = ac2.get_message_by_id(msg_out.id)
|
||||
assert msg_in.text == "message1"
|
||||
assert not msg_in.is_encrypted()
|
||||
|
||||
lp.sec("create new chat with contact and send back (encrypted) message")
|
||||
chat2b = ac2.create_chat_by_message(msg_in)
|
||||
chat2b.send_text("message-back")
|
||||
|
||||
lp.sec("wait for ac1 to receive message")
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
|
||||
assert ev[1] == chat.id
|
||||
msg_back = ac1.get_message_by_id(ev[2])
|
||||
assert msg_back.text == "message-back"
|
||||
assert msg_back.is_encrypted()
|
||||
|
||||
lp.sec("ac1: e2ee_enabled=0 and see if reply is encrypted")
|
||||
print("ac1: e2ee_enabled={}".format(ac1.get_config("e2ee_enabled")))
|
||||
print("ac2: e2ee_enabled={}".format(ac2.get_config("e2ee_enabled")))
|
||||
ac1.set_config("e2ee_enabled", "0")
|
||||
chat.send_text("message2 -- should be encrypted")
|
||||
|
||||
lp.sec("wait for ac2 to receive message")
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
|
||||
msg_in = ac2.get_message_by_id(ev[2])
|
||||
assert msg_in.text == "message2 -- should be encrypted"
|
||||
assert msg_in.is_encrypted()
|
||||
|
||||
def test_saved_mime_on_received_message(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
@@ -768,6 +988,7 @@ class TestOnlineAccount:
|
||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
||||
ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
||||
wait_securejoin_inviter_progress(ac1, 1000)
|
||||
ac1._evlogger.get_matching("DC_EVENT_SECUREJOIN_MEMBER_ADDED")
|
||||
|
||||
def test_qr_verified_group_and_chatting(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
@@ -779,6 +1000,7 @@ class TestOnlineAccount:
|
||||
chat2 = ac2.qr_join_chat(qr)
|
||||
assert chat2.id >= 10
|
||||
wait_securejoin_inviter_progress(ac1, 1000)
|
||||
ac1._evlogger.get_matching("DC_EVENT_SECUREJOIN_MEMBER_ADDED")
|
||||
|
||||
lp.sec("ac2: read member added message")
|
||||
msg = ac2.wait_next_incoming_message()
|
||||
@@ -801,7 +1023,54 @@ class TestOnlineAccount:
|
||||
assert msg.text == "world"
|
||||
assert msg.is_encrypted()
|
||||
|
||||
def test_set_get_profile_image(self, acfactory, data, lp):
|
||||
def test_set_get_contact_avatar(self, acfactory, data, lp):
|
||||
lp.sec("configuring ac1 and ac2")
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
lp.sec("ac1: set own profile image")
|
||||
p = data.get_path("d.png")
|
||||
ac1.set_avatar(p)
|
||||
|
||||
lp.sec("ac1: create 1:1 chat with ac2")
|
||||
chat = self.get_chat(ac1, ac2, both_created=True)
|
||||
|
||||
msg = chat.send_text("hi -- do you see my brand new avatar?")
|
||||
assert not msg.is_encrypted()
|
||||
|
||||
lp.sec("ac2: wait for receiving message and avatar from ac1")
|
||||
msg1 = ac2.wait_next_incoming_message()
|
||||
assert not msg1.chat.is_deaddrop()
|
||||
received_path = msg1.get_sender_contact().get_profile_image()
|
||||
assert open(received_path, "rb").read() == open(p, "rb").read()
|
||||
|
||||
lp.sec("ac2: set own profile image")
|
||||
p = data.get_path("d.png")
|
||||
ac2.set_avatar(p)
|
||||
|
||||
lp.sec("ac2: send back message")
|
||||
m = msg1.chat.send_text("yes, i received your avatar -- how do you like mine?")
|
||||
assert m.is_encrypted()
|
||||
|
||||
lp.sec("ac1: wait for receiving message and avatar from ac2")
|
||||
msg2 = ac1.wait_next_incoming_message()
|
||||
received_path = msg2.get_sender_contact().get_profile_image()
|
||||
assert received_path is not None, "did not get avatar through encrypted message"
|
||||
assert open(received_path, "rb").read() == open(p, "rb").read()
|
||||
|
||||
ac2._evlogger.consume_events()
|
||||
ac1._evlogger.consume_events()
|
||||
|
||||
# XXX not sure if the following is correct / possible. you may remove it
|
||||
lp.sec("ac1: delete profile image from chat, and send message to ac2")
|
||||
ac1.set_avatar(None)
|
||||
m = msg2.chat.send_text("i don't like my avatar anymore and removed it")
|
||||
assert m.is_encrypted()
|
||||
|
||||
lp.sec("ac2: wait for message along with avatar deletion of ac1")
|
||||
msg3 = ac2.wait_next_incoming_message()
|
||||
assert msg3.get_sender_contact().get_profile_image() is None
|
||||
|
||||
def test_set_get_group_image(self, acfactory, data, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
lp.sec("create unpromoted group chat")
|
||||
@@ -903,6 +1172,61 @@ class TestOnlineAccount:
|
||||
assert not locations3
|
||||
|
||||
|
||||
class TestGroupStressTests:
|
||||
def test_group_many_members_add_leave_remove(self, acfactory, lp):
|
||||
lp.sec("creating and configuring five accounts")
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
accounts = [acfactory.get_online_configuring_account() for i in range(3)]
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
for acc in accounts:
|
||||
wait_configuration_progress(acc, 1000)
|
||||
|
||||
lp.sec("ac1: creating group chat with 3 other members")
|
||||
chat = ac1.create_group_chat("title1")
|
||||
contacts = []
|
||||
chars = list("äöüsr")
|
||||
for acc in accounts:
|
||||
contact = ac1.create_contact(acc.get_config("addr"), name=chars.pop())
|
||||
contacts.append(contact)
|
||||
chat.add_contact(contact)
|
||||
# make sure the other side accepts our messages
|
||||
c1 = acc.create_contact(ac1.get_config("addr"), "ä member")
|
||||
acc.create_chat_by_contact(c1)
|
||||
|
||||
assert not chat.is_promoted()
|
||||
|
||||
lp.sec("ac1: send mesage to new group chat")
|
||||
chat.send_text("hello")
|
||||
assert chat.is_promoted()
|
||||
|
||||
num_contacts = len(chat.get_contacts())
|
||||
assert num_contacts == 3 + 1
|
||||
|
||||
lp.sec("ac2: checking that the chat arrived correctly")
|
||||
ac2 = accounts[0]
|
||||
msg = ac2.wait_next_incoming_message()
|
||||
assert msg.text == "hello"
|
||||
print("chat is", msg.chat)
|
||||
assert len(msg.chat.get_contacts()) == 4
|
||||
|
||||
lp.sec("ac3: checking that 'ac4' is a known contact")
|
||||
ac3 = accounts[1]
|
||||
msg3 = ac3.wait_next_incoming_message()
|
||||
assert msg3.text == "hello"
|
||||
contacts = ac3.get_contacts()
|
||||
assert len(contacts) == 3
|
||||
ac4_contacts = ac3.get_contacts(query=accounts[2].get_config("addr"))
|
||||
assert len(ac4_contacts) == 1
|
||||
|
||||
lp.sec("ac1: removing one contacts and checking things are right")
|
||||
to_remove = msg.chat.get_contacts()[-1]
|
||||
msg.chat.remove_contact(to_remove)
|
||||
|
||||
sysmsg = ac1.wait_next_incoming_message()
|
||||
assert to_remove.addr in sysmsg.text
|
||||
assert len(sysmsg.chat.get_contacts()) == 3
|
||||
|
||||
|
||||
class TestOnlineConfigureFails:
|
||||
def test_invalid_password(self, acfactory):
|
||||
ac1, configdict = acfactory.get_online_config()
|
||||
|
||||
@@ -1,10 +1,49 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import os.path
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
from filecmp import cmp
|
||||
from deltachat import const
|
||||
|
||||
from conftest import wait_configuration_progress, wait_msgs_changed
|
||||
from deltachat import const
|
||||
|
||||
|
||||
class TestOnlineInCreation:
|
||||
def test_increation_not_blobdir(self, tmpdir, acfactory, lp):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.get_online_configuring_account()
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
|
||||
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||
chat = ac1.create_chat_by_contact(c2)
|
||||
|
||||
lp.sec("Creating in-creation file outside of blobdir")
|
||||
assert tmpdir.strpath != ac1.get_blobdir()
|
||||
src = tmpdir.join('file.txt').ensure(file=1)
|
||||
with pytest.raises(Exception):
|
||||
chat.prepare_message_file(src.strpath)
|
||||
|
||||
def test_no_increation_copies_to_blobdir(self, tmpdir, acfactory, lp):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.get_online_configuring_account()
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
|
||||
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||
chat = ac1.create_chat_by_contact(c2)
|
||||
|
||||
lp.sec("Creating file outside of blobdir")
|
||||
assert tmpdir.strpath != ac1.get_blobdir()
|
||||
src = tmpdir.join('file.txt')
|
||||
src.write("hello there\n")
|
||||
chat.send_file(src.strpath)
|
||||
|
||||
blob_src = os.path.join(ac1.get_blobdir(), 'file.txt')
|
||||
assert os.path.exists(blob_src), "file.txt not copied to blobdir"
|
||||
|
||||
def test_forward_increation(self, acfactory, data, lp):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.get_online_configuring_account()
|
||||
@@ -17,7 +56,10 @@ class TestOnlineInCreation:
|
||||
wait_msgs_changed(ac1, 0, 0) # why no chat id?
|
||||
|
||||
lp.sec("create a message with a file in creation")
|
||||
path = data.get_path("d.png")
|
||||
orig = data.get_path("d.png")
|
||||
path = os.path.join(ac1.get_blobdir(), 'd.png')
|
||||
with open(path, "x") as fp:
|
||||
fp.write("preparing")
|
||||
prepared_original = chat.prepare_message_file(path)
|
||||
assert prepared_original.is_out_preparing()
|
||||
wait_msgs_changed(ac1, chat.id, prepared_original.id)
|
||||
@@ -38,6 +80,7 @@ class TestOnlineInCreation:
|
||||
|
||||
lp.sec("finish creating the file and send it")
|
||||
assert prepared_original.is_out_preparing()
|
||||
shutil.copyfile(orig, path)
|
||||
chat.send_prepared(prepared_original)
|
||||
assert prepared_original.is_out_pending() or prepared_original.is_out_delivered()
|
||||
wait_msgs_changed(ac1, chat.id, prepared_original.id)
|
||||
@@ -57,13 +100,13 @@ class TestOnlineInCreation:
|
||||
|
||||
lp.sec("wait1 for original or forwarded messages to arrive")
|
||||
ev1 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
assert ev1[1] >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||
assert ev1[1] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
received_original = ac2.get_message_by_id(ev1[2])
|
||||
assert cmp(received_original.filename, path, False)
|
||||
assert cmp(received_original.filename, orig, shallow=False)
|
||||
|
||||
lp.sec("wait2 for original or forwarded messages to arrive")
|
||||
ev2 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
assert ev2[1] >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||
assert ev2[1] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
assert ev2[1] != ev1[1]
|
||||
received_copy = ac2.get_message_by_id(ev2[2])
|
||||
assert cmp(received_copy.filename, path, False)
|
||||
assert cmp(received_copy.filename, orig, shallow=False)
|
||||
|
||||
@@ -3,6 +3,7 @@ from deltachat import capi, cutil, const, set_context_callback, clear_context_ca
|
||||
from deltachat.capi import ffi
|
||||
from deltachat.capi import lib
|
||||
from deltachat.account import EventLogger
|
||||
import json
|
||||
|
||||
|
||||
def test_empty_context():
|
||||
@@ -103,18 +104,23 @@ def test_get_special_message_id_returns_empty_message(acfactory):
|
||||
|
||||
|
||||
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
|
||||
provider_json = cutil.from_dc_charpointer(
|
||||
lib.dc_provider_json_from_email(cutil.as_dc_charpointer("ex@example.com"))
|
||||
)
|
||||
provider = json.loads(provider_json)
|
||||
|
||||
assert provider['overview_page'] == "example.com"
|
||||
assert provider['name'] == "Example"
|
||||
assert provider['markdown'] == "\n..."
|
||||
assert provider['status']['date'] == "2018-09"
|
||||
assert provider['status']['state'] == "PREPARATION"
|
||||
|
||||
|
||||
def test_provider_info_none():
|
||||
assert lib.dc_provider_new_from_email(cutil.as_dc_charpointer("email@unexistent.no")) == ffi.NULL
|
||||
provider_json = cutil.from_dc_charpointer(
|
||||
lib.dc_provider_json_from_email(cutil.as_dc_charpointer("email@unexistent.no"))
|
||||
)
|
||||
assert provider_json == ""
|
||||
|
||||
|
||||
def test_get_info_closed():
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import pytest
|
||||
|
||||
from deltachat import const
|
||||
from deltachat import provider
|
||||
|
||||
|
||||
@@ -10,7 +9,7 @@ def test_provider_info_from_email():
|
||||
assert example.name == "Example"
|
||||
assert example.markdown == "\n..."
|
||||
assert example.status_date == "2018-09"
|
||||
assert example.status == const.DC_PROVIDER_STATUS_PREPARATION
|
||||
assert example.status == "PREPARATION"
|
||||
|
||||
|
||||
def test_provider_info_from_domain():
|
||||
@@ -19,7 +18,7 @@ def test_provider_info_from_domain():
|
||||
assert example.name == "Example"
|
||||
assert example.markdown == "\n..."
|
||||
assert example.status_date == "2018-09"
|
||||
assert example.status == const.DC_PROVIDER_STATUS_PREPARATION
|
||||
assert example.status == "PREPARATION"
|
||||
|
||||
|
||||
def test_provider_info_none():
|
||||
|
||||
@@ -7,11 +7,7 @@ envlist =
|
||||
|
||||
[testenv]
|
||||
commands =
|
||||
# (some qr tests are pretty heavy in terms of send/received
|
||||
# messages and async-imap's likely has concurrency problems,
|
||||
# eg https://github.com/async-email/async-imap/issues/4 )
|
||||
pytest -n6 --reruns 3 --reruns-delay 5 -v -rsXx -k "not qr" {posargs:tests}
|
||||
pytest -n6 --reruns 5 --reruns-delay 5 -v -rsXx -k "qr" {posargs:tests}
|
||||
pytest -n6 --reruns 2 --reruns-delay 5 -v -rsXx {posargs:tests}
|
||||
# python tests/package_wheels.py {toxworkdir}/wheelhouse
|
||||
passenv =
|
||||
TRAVIS
|
||||
@@ -36,7 +32,7 @@ commands =
|
||||
|
||||
[testenv:lint]
|
||||
skipsdist = True
|
||||
usedevelop = True
|
||||
skip_install = True
|
||||
deps =
|
||||
flake8
|
||||
# pygments required by rst-lint
|
||||
@@ -53,7 +49,7 @@ deps =
|
||||
sphinx==2.2.0
|
||||
breathe
|
||||
commands =
|
||||
sphinx-build -w toxdoc-warnings.log -b html . _build/html
|
||||
sphinx-build -Q -w toxdoc-warnings.log -b html . _build/html
|
||||
|
||||
|
||||
[testenv:lintdoc]
|
||||
@@ -69,8 +65,8 @@ commands =
|
||||
|
||||
|
||||
[pytest]
|
||||
addopts = -v -rs
|
||||
python_files = tests/test_*.py
|
||||
addopts = -v -ra
|
||||
python_files = tests/test_*.py
|
||||
norecursedirs = .tox
|
||||
xfail_strict=true
|
||||
timeout = 60
|
||||
|
||||
@@ -33,6 +33,8 @@ def replace_toml_version(relpath, newversion):
|
||||
if __name__ == "__main__":
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
for x in ("Cargo.toml", "deltachat-ffi/Cargo.toml"):
|
||||
print("{}: {}".format(x, read_toml_version(x)))
|
||||
raise SystemExit("need argument: new version, example 1.0.0-beta.27")
|
||||
newversion = sys.argv[1]
|
||||
if newversion.count(".") < 2:
|
||||
@@ -53,7 +55,9 @@ if __name__ == "__main__":
|
||||
replace_toml_version("Cargo.toml", newversion)
|
||||
replace_toml_version("deltachat-ffi/Cargo.toml", newversion)
|
||||
|
||||
subprocess.call(["cargo", "update", "-p", "deltachat"])
|
||||
subprocess.call(["cargo", "check"])
|
||||
subprocess.call(["git", "add", "-u"])
|
||||
# subprocess.call(["cargo", "update", "-p", "deltachat"])
|
||||
|
||||
print("after commit make sure to: ")
|
||||
print("")
|
||||
|
||||
26
spec.md
@@ -1,6 +1,6 @@
|
||||
# Chat-over-Email specification
|
||||
|
||||
Version 0.19.0
|
||||
Version 0.20.0
|
||||
|
||||
This document describes how emails can be used
|
||||
to implement typical messenger functions
|
||||
@@ -248,11 +248,11 @@ and the message SHOULD appear as a message or action from the sender.
|
||||
A group MAY have a group-image.
|
||||
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`
|
||||
and MUST add the header `Chat-Group-Avatar`
|
||||
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 MUST add the header `Chat-Group-Avatar: 0`.
|
||||
|
||||
The messenger SHOULD send an explicit mail for each group image change.
|
||||
The body of the message SHOULD contain
|
||||
@@ -265,7 +265,7 @@ and the message SHOULD appear as a message or action from the sender.
|
||||
Chat-Version: 1.0
|
||||
Chat-Group-ID: 12345uvwxyZ
|
||||
Chat-Group-Name: Our Group
|
||||
Chat-Group-Image: image.jpg
|
||||
Chat-Group-Avatar: image.jpg
|
||||
Message-ID: Gr.12345uvwxyZ.0005@domain
|
||||
Subject: Chat: Our Group: Hello, ...
|
||||
Content-Type: multipart/mixed; boundary="==break=="
|
||||
@@ -283,25 +283,25 @@ and the message SHOULD appear as a message or action from the sender.
|
||||
|
||||
The image format SHOULD be image/jpeg or image/png.
|
||||
To save data, it is RECOMMENDED
|
||||
to add a `Chat-Group-Image` only on image changes.
|
||||
to add a `Chat-Group-Avatar` only on image changes.
|
||||
|
||||
|
||||
# Set profile image
|
||||
|
||||
A user MAY have a profile-image that MAY be spread to his contacts.
|
||||
A user MAY have a profile-image that MAY be spread to their 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`
|
||||
and MUST add the header `Chat-User-Avatar`
|
||||
with the value set to the image name.
|
||||
|
||||
To remove the profile-image,
|
||||
the messenger MUST add the header `Chat-Profile-Image: 0`.
|
||||
the messenger MUST add the header `Chat-User-Avatar: 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).
|
||||
the messenger has to keep a `user_avatar_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.
|
||||
@@ -309,7 +309,7 @@ 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
|
||||
Chat-User-Avatar: photo.jpg
|
||||
Subject: Chat: Hello, ...
|
||||
Content-Type: multipart/mixed; boundary="==break=="
|
||||
|
||||
@@ -325,10 +325,10 @@ The messenger SHOULD NOT send an explicit mail to normal MUAs.
|
||||
--==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
|
||||
Note that `Chat-User-Avatar` may appear together with all other headers,
|
||||
eg. there may be a `Chat-User-Avatar` and a `Chat-Group-Avatar` header
|
||||
in the same message.
|
||||
To save data, it is RECOMMENDED to add a `Chat-Profile-Image` header
|
||||
To save data, it is RECOMMENDED to add a `Chat-User-Avatar` header
|
||||
only on image changes.
|
||||
|
||||
|
||||
|
||||
174
src/aheader.rs
@@ -3,14 +3,12 @@
|
||||
//! Parse and create [Autocrypt-headers](https://autocrypt.org/en/latest/level1.html#the-autocrypt-header).
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::ffi::CStr;
|
||||
use std::str::FromStr;
|
||||
use std::{fmt, str};
|
||||
|
||||
use mmime::mailimf::types::*;
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::Context;
|
||||
use crate::key::*;
|
||||
|
||||
/// Possible values for encryption preference
|
||||
@@ -44,8 +42,8 @@ impl str::FromStr for EncryptPreference {
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"mutual" => Ok(EncryptPreference::Mutual),
|
||||
"reset" => Ok(EncryptPreference::Reset),
|
||||
_ => Ok(EncryptPreference::NoPreference),
|
||||
"nopreference" => Ok(EncryptPreference::NoPreference),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,60 +66,54 @@ impl Aheader {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_imffields(wanted_from: &str, header: *const mailimf_fields) -> Option<Self> {
|
||||
if header.is_null() {
|
||||
return None;
|
||||
}
|
||||
pub fn from_headers(
|
||||
context: &Context,
|
||||
wanted_from: &str,
|
||||
headers: &[mailparse::MailHeader<'_>],
|
||||
) -> Option<Self> {
|
||||
use mailparse::MailHeaderMap;
|
||||
|
||||
let mut fine_header = None;
|
||||
let mut cur = unsafe { (*(*header).fld_list).first };
|
||||
|
||||
while !cur.is_null() {
|
||||
let field = unsafe { (*cur).data as *mut mailimf_field };
|
||||
if !field.is_null()
|
||||
&& unsafe { (*field).fld_type } == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int
|
||||
{
|
||||
let optional_field = unsafe { (*field).fld_data.fld_optional_field };
|
||||
if !optional_field.is_null()
|
||||
&& unsafe { !(*optional_field).fld_name.is_null() }
|
||||
&& unsafe { CStr::from_ptr((*optional_field).fld_name).to_string_lossy() }
|
||||
== "Autocrypt"
|
||||
{
|
||||
let value =
|
||||
unsafe { CStr::from_ptr((*optional_field).fld_value).to_string_lossy() };
|
||||
|
||||
if let Ok(test) = Self::from_str(&value) {
|
||||
if addr_cmp(&test.addr, wanted_from) {
|
||||
if fine_header.is_none() {
|
||||
fine_header = Some(test);
|
||||
} else {
|
||||
// TODO: figure out what kind of error case this is
|
||||
return None;
|
||||
}
|
||||
}
|
||||
if let Ok(Some(value)) = headers.get_first_value("Autocrypt") {
|
||||
match Self::from_str(&value) {
|
||||
Ok(header) => {
|
||||
if addr_cmp(&header.addr, wanted_from) {
|
||||
return Some(header);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"found invalid autocrypt header {}: {:?}", value, err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
cur = unsafe { (*cur).next };
|
||||
}
|
||||
|
||||
fine_header
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Aheader {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
// TODO replace 78 with enum /rtn
|
||||
// adds a whitespace every 78 characters, this allows libEtPan to
|
||||
// wrap the lines according to RFC 5322
|
||||
write!(fmt, "addr={};", self.addr)?;
|
||||
if self.prefer_encrypt == EncryptPreference::Mutual {
|
||||
write!(fmt, " prefer-encrypt=mutual;")?;
|
||||
}
|
||||
|
||||
// adds a whitespace every 78 characters, this allows
|
||||
// email crate to wrap the lines according to RFC 5322
|
||||
// (which may insert a linebreak before every whitespace)
|
||||
let keydata = self.public_key.to_base64(78);
|
||||
write!(
|
||||
fmt,
|
||||
"addr={}; prefer-encrypt={}; keydata={}",
|
||||
self.addr, self.prefer_encrypt, keydata
|
||||
)
|
||||
let keydata = self.public_key.to_base64().chars().enumerate().fold(
|
||||
String::new(),
|
||||
|mut res, (i, c)| {
|
||||
if i % 78 == 78 - "keydata=".len() {
|
||||
res.push(' ')
|
||||
}
|
||||
res.push(c);
|
||||
res
|
||||
},
|
||||
);
|
||||
write!(fmt, " keydata={}", keydata)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,13 +159,10 @@ impl str::FromStr for Aheader {
|
||||
}
|
||||
};
|
||||
|
||||
let prefer_encrypt = match attributes
|
||||
let prefer_encrypt = attributes
|
||||
.remove("prefer-encrypt")
|
||||
.and_then(|raw| raw.parse().ok())
|
||||
{
|
||||
Some(pref) => pref,
|
||||
None => EncryptPreference::NoPreference,
|
||||
};
|
||||
.unwrap_or_default();
|
||||
|
||||
// Autocrypt-Level0: unknown attributes starting with an underscore can be safely ignored
|
||||
// Autocrypt-Level0: unknown attribute, treat the header as invalid
|
||||
@@ -193,15 +182,13 @@ impl str::FromStr for Aheader {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn rawkey() -> String {
|
||||
"xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJgWL7O3y/g4HdDW/dN2z26Y6W13IMzx9gLInn1KQZChtqWAcr/ReUucXcymwcfg1mdkBGk3TSLeLihN6CJx8Wsv8ig+kgAzte4f5rqEEAJVQ9WZHuti7UiYs6oRzqTo06CRe9owVXxzdMf0VDQtf7ZFm9dpzKKbhH7Lu8880iiotQ9/yRCkDGp9fNThsrLdZiK6OIAcIBAqi2rI89aS1dAmnRbktQieCx5izzyYkR1KvVL3gTTllHOzfKVEC2asmtWu2e4se/+O4WMIS1eGrn7GeWVb0Vwc5ABEBAAHNETxhQEBiLmV4YW1wbGUuZGU+wsCJBBABCAAzAhkBBQJcxt5FAhsDBAsJCAcGFQgJCgsCAxYCARYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiML938H/18F+3Wf9/JaAy/8hCO1v4S2PVBhxaKCokaNFtkfaMRne2l087LscCFPiFNyb4mv6Z3YeK8Xpxlp2sI0ecvdiqLUOGfnxS6tQrj+83EjtIrZ/hXOk1h121QFWH9Zg2VNHtODXjAgdLDC0NWUrclR0ZOqEDQHeo0ibTILdokVfXFN25wakPmGaYJP2y729cb1ve7RzvIvwn+Dddfxo3ao72rBfLi7l4NQ4S0KsY4cw+/6l5bRCKYCP77wZtvCwUvfVVosLdT43agtSiBI49+ayqvZ8OCvSJa61i+v81brTiEy9GBod4eAp45Ibsuemkw+gon4ZOvUXHTjwFB+h63MrozOwE0EXMbePQEIAL/vauf1zK8JgCu3V+G+SOX0iWw5xUlCPX+ERpBbWfwu3uAqn4wYXD3JDE/fVAF668xiV4eTPtlSUd5h0mn+G7uXMMOtkb+20SoEt50f8zw8TrL9t+ZsV11GKZWJpCar5AhXWsn6EEi8I2hLL5vn55ZZmHuGgN4jjmkRl3ToKCLhaXwTBjCJem7N5EH7F75wErEITa55v4Lb4Nfca7vnvtYrI1OA446xa8gHra0SINelTD09/JM/Fw4sWVPBaRZmJK/Tnu79N23No9XBUubmFPv1pNexZsQclicnTpt/BEWhiun7d6lfGB63K1aoHRTR1pcrWvBuALuuz0gqar2zlI0AEQEAAcLAdgQYAQgAIAUCXMbeRQIbDBYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiMLKSEIAIyLCRO2OyZ0IYRvRPpMn4p7E+7Pfcz/0mSkOy+1hshgJnqivXurm8zwGrwdMqeV4eslKR9H1RUdWGUQJNbtwmmjrt5DHpIhYHl5t3FpCBaGbV20Omo00Q38lBl9MtrmZkZw+ktEk6X+0xCKssMF+2MADkSOIufbR5HrDVB89VZOHCO9DeXvCUUAw2hyJiL/LHmLzJ40zYoTmb+F//f0k0j+tRdbkefyRoCmwG7YGiT+2hnCdgcezswnzah5J3ZKlrg7jOGo1LxtbvNUzxNBbC6S/aNgwm6qxo7xegRhmEl5uZ16zwyj4qz+xkjGy25Of5mWfUDoNw7OT7sjUbHOOMc=".into()
|
||||
}
|
||||
const RAWKEY: &str = "xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJgWL7O3y/g4HdDW/dN2z26Y6W13IMzx9gLInn1KQZChtqWAcr/ReUucXcymwcfg1mdkBGk3TSLeLihN6CJx8Wsv8ig+kgAzte4f5rqEEAJVQ9WZHuti7UiYs6oRzqTo06CRe9owVXxzdMf0VDQtf7ZFm9dpzKKbhH7Lu8880iiotQ9/yRCkDGp9fNThsrLdZiK6OIAcIBAqi2rI89aS1dAmnRbktQieCx5izzyYkR1KvVL3gTTllHOzfKVEC2asmtWu2e4se/+O4WMIS1eGrn7GeWVb0Vwc5ABEBAAHNETxhQEBiLmV4YW1wbGUuZGU+wsCJBBABCAAzAhkBBQJcxt5FAhsDBAsJCAcGFQgJCgsCAxYCARYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiML938H/18F+3Wf9/JaAy/8hCO1v4S2PVBhxaKCokaNFtkfaMRne2l087LscCFPiFNyb4mv6Z3YeK8Xpxlp2sI0ecvdiqLUOGfnxS6tQrj+83EjtIrZ/hXOk1h121QFWH9Zg2VNHtODXjAgdLDC0NWUrclR0ZOqEDQHeo0ibTILdokVfXFN25wakPmGaYJP2y729cb1ve7RzvIvwn+Dddfxo3ao72rBfLi7l4NQ4S0KsY4cw+/6l5bRCKYCP77wZtvCwUvfVVosLdT43agtSiBI49+ayqvZ8OCvSJa61i+v81brTiEy9GBod4eAp45Ibsuemkw+gon4ZOvUXHTjwFB+h63MrozOwE0EXMbePQEIAL/vauf1zK8JgCu3V+G+SOX0iWw5xUlCPX+ERpBbWfwu3uAqn4wYXD3JDE/fVAF668xiV4eTPtlSUd5h0mn+G7uXMMOtkb+20SoEt50f8zw8TrL9t+ZsV11GKZWJpCar5AhXWsn6EEi8I2hLL5vn55ZZmHuGgN4jjmkRl3ToKCLhaXwTBjCJem7N5EH7F75wErEITa55v4Lb4Nfca7vnvtYrI1OA446xa8gHra0SINelTD09/JM/Fw4sWVPBaRZmJK/Tnu79N23No9XBUubmFPv1pNexZsQclicnTpt/BEWhiun7d6lfGB63K1aoHRTR1pcrWvBuALuuz0gqar2zlI0AEQEAAcLAdgQYAQgAIAUCXMbeRQIbDBYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiMLKSEIAIyLCRO2OyZ0IYRvRPpMn4p7E+7Pfcz/0mSkOy+1hshgJnqivXurm8zwGrwdMqeV4eslKR9H1RUdWGUQJNbtwmmjrt5DHpIhYHl5t3FpCBaGbV20Omo00Q38lBl9MtrmZkZw+ktEk6X+0xCKssMF+2MADkSOIufbR5HrDVB89VZOHCO9DeXvCUUAw2hyJiL/LHmLzJ40zYoTmb+F//f0k0j+tRdbkefyRoCmwG7YGiT+2hnCdgcezswnzah5J3ZKlrg7jOGo1LxtbvNUzxNBbC6S/aNgwm6qxo7xegRhmEl5uZ16zwyj4qz+xkjGy25Of5mWfUDoNw7OT7sjUbHOOMc=";
|
||||
|
||||
#[test]
|
||||
fn test_from_str() {
|
||||
let h: Aheader = format!(
|
||||
"addr=me@mail.com; prefer-encrypt=mutual; keydata={}",
|
||||
rawkey()
|
||||
RAWKEY
|
||||
)
|
||||
.parse()
|
||||
.expect("failed to parse");
|
||||
@@ -210,9 +197,22 @@ mod tests {
|
||||
assert_eq!(h.prefer_encrypt, EncryptPreference::Mutual);
|
||||
}
|
||||
|
||||
// EncryptPreference::Reset is an internal value, parser should never return it
|
||||
#[test]
|
||||
fn test_from_str_reset() {
|
||||
let raw = format!(
|
||||
"addr=reset@example.com; prefer-encrypt=reset; keydata={}",
|
||||
RAWKEY
|
||||
);
|
||||
let h: Aheader = raw.parse().expect("failed to parse");
|
||||
|
||||
assert_eq!(h.addr, "reset@example.com");
|
||||
assert_eq!(h.prefer_encrypt, EncryptPreference::NoPreference);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_str_non_critical() {
|
||||
let raw = format!("addr=me@mail.com; _foo=one; _bar=two; keydata={}", rawkey());
|
||||
let raw = format!("addr=me@mail.com; _foo=one; _bar=two; keydata={}", RAWKEY);
|
||||
let h: Aheader = raw.parse().expect("failed to parse");
|
||||
|
||||
assert_eq!(h.addr, "me@mail.com");
|
||||
@@ -223,33 +223,57 @@ mod tests {
|
||||
fn test_from_str_superflous_critical() {
|
||||
let raw = format!(
|
||||
"addr=me@mail.com; _foo=one; _bar=two; other=me; keydata={}",
|
||||
rawkey()
|
||||
RAWKEY
|
||||
);
|
||||
assert!(raw.parse::<Aheader>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_good_headers() {
|
||||
let fixed_header = "addr=a@b.example.org; prefer-encrypt=mutual; keydata=xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJgWL7O3y/g 4HdDW/dN2z26Y6W13IMzx9gLInn1KQZChtqWAcr/ReUucXcymwcfg1mdkBGk3TSLeLihN6CJx8Wsv8 ig+kgAzte4f5rqEEAJVQ9WZHuti7UiYs6oRzqTo06CRe9owVXxzdMf0VDQtf7ZFm9dpzKKbhH7Lu88 80iiotQ9/yRCkDGp9fNThsrLdZiK6OIAcIBAqi2rI89aS1dAmnRbktQieCx5izzyYkR1KvVL3gTTll HOzfKVEC2asmtWu2e4se/+O4WMIS1eGrn7GeWVb0Vwc5ABEBAAHNETxhQEBiLmV4YW1wbGUuZGU+ws CJBBABCAAzAhkBBQJcxt5FAhsDBAsJCAcGFQgJCgsCAxYCARYhBI4xxYKBgH3ANh5cufaKrc9mtiML AAoJEPaKrc9mtiML938H/18F+3Wf9/JaAy/8hCO1v4S2PVBhxaKCokaNFtkfaMRne2l087LscCFPiF Nyb4mv6Z3YeK8Xpxlp2sI0ecvdiqLUOGfnxS6tQrj+83EjtIrZ/hXOk1h121QFWH9Zg2VNHtODXjAg dLDC0NWUrclR0ZOqEDQHeo0ibTILdokVfXFN25wakPmGaYJP2y729cb1ve7RzvIvwn+Dddfxo3ao72 rBfLi7l4NQ4S0KsY4cw+/6l5bRCKYCP77wZtvCwUvfVVosLdT43agtSiBI49+ayqvZ8OCvSJa61i+v 81brTiEy9GBod4eAp45Ibsuemkw+gon4ZOvUXHTjwFB+h63MrozOwE0EXMbePQEIAL/vauf1zK8JgC u3V+G+SOX0iWw5xUlCPX+ERpBbWfwu3uAqn4wYXD3JDE/fVAF668xiV4eTPtlSUd5h0mn+G7uXMMOt kb+20SoEt50f8zw8TrL9t+ZsV11GKZWJpCar5AhXWsn6EEi8I2hLL5vn55ZZmHuGgN4jjmkRl3ToKC LhaXwTBjCJem7N5EH7F75wErEITa55v4Lb4Nfca7vnvtYrI1OA446xa8gHra0SINelTD09/JM/Fw4s WVPBaRZmJK/Tnu79N23No9XBUubmFPv1pNexZsQclicnTpt/BEWhiun7d6lfGB63K1aoHRTR1pcrWv BuALuuz0gqar2zlI0AEQEAAcLAdgQYAQgAIAUCXMbeRQIbDBYhBI4xxYKBgH3ANh5cufaKrc9mtiML AAoJEPaKrc9mtiMLKSEIAIyLCRO2OyZ0IYRvRPpMn4p7E+7Pfcz/0mSkOy+1hshgJnqivXurm8zwGr wdMqeV4eslKR9H1RUdWGUQJNbtwmmjrt5DHpIhYHl5t3FpCBaGbV20Omo00Q38lBl9MtrmZkZw+ktE k6X+0xCKssMF+2MADkSOIufbR5HrDVB89VZOHCO9DeXvCUUAw2hyJiL/LHmLzJ40zYoTmb+F//f0k0 j+tRdbkefyRoCmwG7YGiT+2hnCdgcezswnzah5J3ZKlrg7jOGo1LxtbvNUzxNBbC6S/aNgwm6qxo7x egRhmEl5uZ16zwyj4qz+xkjGy25Of5mWfUDoNw7OT7sjUbHOOMc=";
|
||||
let fixed_header = concat!(
|
||||
"addr=a@b.example.org; prefer-encrypt=mutual; ",
|
||||
"keydata=xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJg",
|
||||
" WL7O3y/g4HdDW/dN2z26Y6W13IMzx9gLInn1KQZChtqWAcr/ReUucXcymwcfg1mdkBGk3TSLeLihN6",
|
||||
" CJx8Wsv8ig+kgAzte4f5rqEEAJVQ9WZHuti7UiYs6oRzqTo06CRe9owVXxzdMf0VDQtf7ZFm9dpzKK",
|
||||
" bhH7Lu8880iiotQ9/yRCkDGp9fNThsrLdZiK6OIAcIBAqi2rI89aS1dAmnRbktQieCx5izzyYkR1Kv",
|
||||
" VL3gTTllHOzfKVEC2asmtWu2e4se/+O4WMIS1eGrn7GeWVb0Vwc5ABEBAAHNETxhQEBiLmV4YW1wbG",
|
||||
" UuZGU+wsCJBBABCAAzAhkBBQJcxt5FAhsDBAsJCAcGFQgJCgsCAxYCARYhBI4xxYKBgH3ANh5cufaK",
|
||||
" rc9mtiMLAAoJEPaKrc9mtiML938H/18F+3Wf9/JaAy/8hCO1v4S2PVBhxaKCokaNFtkfaMRne2l087",
|
||||
" LscCFPiFNyb4mv6Z3YeK8Xpxlp2sI0ecvdiqLUOGfnxS6tQrj+83EjtIrZ/hXOk1h121QFWH9Zg2VN",
|
||||
" HtODXjAgdLDC0NWUrclR0ZOqEDQHeo0ibTILdokVfXFN25wakPmGaYJP2y729cb1ve7RzvIvwn+Ddd",
|
||||
" fxo3ao72rBfLi7l4NQ4S0KsY4cw+/6l5bRCKYCP77wZtvCwUvfVVosLdT43agtSiBI49+ayqvZ8OCv",
|
||||
" SJa61i+v81brTiEy9GBod4eAp45Ibsuemkw+gon4ZOvUXHTjwFB+h63MrozOwE0EXMbePQEIAL/vau",
|
||||
" f1zK8JgCu3V+G+SOX0iWw5xUlCPX+ERpBbWfwu3uAqn4wYXD3JDE/fVAF668xiV4eTPtlSUd5h0mn+",
|
||||
" G7uXMMOtkb+20SoEt50f8zw8TrL9t+ZsV11GKZWJpCar5AhXWsn6EEi8I2hLL5vn55ZZmHuGgN4jjm",
|
||||
" kRl3ToKCLhaXwTBjCJem7N5EH7F75wErEITa55v4Lb4Nfca7vnvtYrI1OA446xa8gHra0SINelTD09",
|
||||
" /JM/Fw4sWVPBaRZmJK/Tnu79N23No9XBUubmFPv1pNexZsQclicnTpt/BEWhiun7d6lfGB63K1aoHR",
|
||||
" TR1pcrWvBuALuuz0gqar2zlI0AEQEAAcLAdgQYAQgAIAUCXMbeRQIbDBYhBI4xxYKBgH3ANh5cufaK",
|
||||
" rc9mtiMLAAoJEPaKrc9mtiMLKSEIAIyLCRO2OyZ0IYRvRPpMn4p7E+7Pfcz/0mSkOy+1hshgJnqivX",
|
||||
" urm8zwGrwdMqeV4eslKR9H1RUdWGUQJNbtwmmjrt5DHpIhYHl5t3FpCBaGbV20Omo00Q38lBl9Mtrm",
|
||||
" ZkZw+ktEk6X+0xCKssMF+2MADkSOIufbR5HrDVB89VZOHCO9DeXvCUUAw2hyJiL/LHmLzJ40zYoTmb",
|
||||
" +F//f0k0j+tRdbkefyRoCmwG7YGiT+2hnCdgcezswnzah5J3ZKlrg7jOGo1LxtbvNUzxNBbC6S/aNg",
|
||||
" wm6qxo7xegRhmEl5uZ16zwyj4qz+xkjGy25Of5mWfUDoNw7OT7sjUbHOOMc="
|
||||
);
|
||||
|
||||
let ah = Aheader::from_str(fixed_header).expect("failed to parse");
|
||||
assert_eq!(ah.addr, "a@b.example.org");
|
||||
assert_eq!(ah.prefer_encrypt, EncryptPreference::Mutual);
|
||||
assert_eq!(format!("{}", ah), fixed_header);
|
||||
|
||||
let rendered = ah.to_string();
|
||||
assert_eq!(rendered, fixed_header);
|
||||
|
||||
let ah = Aheader::from_str(&format!(" _foo; __FOO=BAR ;;; addr = a@b.example.org ;\r\n prefer-encrypt = mutual ; keydata = {}", rawkey())).expect("failed to parse");
|
||||
let ah = Aheader::from_str(&format!(" _foo; __FOO=BAR ;;; addr = a@b.example.org ;\r\n prefer-encrypt = mutual ; keydata = {}", RAWKEY)).expect("failed to parse");
|
||||
assert_eq!(ah.addr, "a@b.example.org");
|
||||
assert_eq!(ah.prefer_encrypt, EncryptPreference::Mutual);
|
||||
|
||||
Aheader::from_str(&format!(
|
||||
"addr=a@b.example.org; prefer-encrypt=ignoreUnknownValues; keydata={}",
|
||||
rawkey()
|
||||
RAWKEY
|
||||
))
|
||||
.expect("failed to parse");
|
||||
|
||||
Aheader::from_str(&format!("addr=a@b.example.org; keydata={}", rawkey()))
|
||||
Aheader::from_str(&format!("addr=a@b.example.org; keydata={}", RAWKEY))
|
||||
.expect("failed to parse");
|
||||
}
|
||||
|
||||
@@ -261,4 +285,30 @@ mod tests {
|
||||
assert!(Aheader::from_str(" ;;").is_err());
|
||||
assert!(Aheader::from_str("addr=a@t.de; unknwon=1; keydata=jau").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_aheader() {
|
||||
assert!(format!(
|
||||
"{}",
|
||||
Aheader::new(
|
||||
"test@example.com".to_string(),
|
||||
Key::from_base64(RAWKEY, KeyType::Public).unwrap(),
|
||||
EncryptPreference::Mutual
|
||||
)
|
||||
)
|
||||
.contains("prefer-encrypt=mutual;"));
|
||||
|
||||
// According to Autocrypt Level 1 specification,
|
||||
// only "prefer-encrypt=mutual;" can be used.
|
||||
// If the setting is nopreference, the whole attribute is omitted.
|
||||
assert!(!format!(
|
||||
"{}",
|
||||
Aheader::new(
|
||||
"test@example.com".to_string(),
|
||||
Key::from_base64(RAWKEY, KeyType::Public).unwrap(),
|
||||
EncryptPreference::NoPreference
|
||||
)
|
||||
)
|
||||
.contains("prefer-encrypt"));
|
||||
}
|
||||
}
|
||||
|
||||
331
src/blob.rs
@@ -6,9 +6,13 @@ use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use self::image::GenericImageView;
|
||||
use crate::constants::AVATAR_SIZE;
|
||||
use crate::context::Context;
|
||||
use crate::events::Event;
|
||||
|
||||
extern crate image;
|
||||
|
||||
/// Represents a file in the blob directory.
|
||||
///
|
||||
/// The object has a name, which will always be valid UTF-8. Having a
|
||||
@@ -32,11 +36,11 @@ impl<'a> BlobObject<'a> {
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// [BlobErrorKind::CreateFailure] is used when the file could not
|
||||
/// [BlobError::CreateFailure] is used when the file could not
|
||||
/// be created. You can expect [BlobError.cause] to contain an
|
||||
/// underlying error.
|
||||
///
|
||||
/// [BlobErrorKind::WriteFailure] is used when the file could not
|
||||
/// [BlobError::WriteFailure] is used when the file could not
|
||||
/// be written to. You can expect [BlobError.cause] to contain an
|
||||
/// underlying error.
|
||||
pub fn create(
|
||||
@@ -48,7 +52,12 @@ impl<'a> BlobObject<'a> {
|
||||
let (stem, ext) = BlobObject::sanitise_name(suggested_name.as_ref());
|
||||
let (name, mut file) = BlobObject::create_new_file(&blobdir, &stem, &ext)?;
|
||||
file.write_all(data)
|
||||
.map_err(|err| BlobError::new_write_failure(blobdir, &name, err))?;
|
||||
.map_err(|err| BlobError::WriteFailure {
|
||||
blobdir: blobdir.to_path_buf(),
|
||||
blobname: name.clone(),
|
||||
cause: err,
|
||||
backtrace: failure::Backtrace::new(),
|
||||
})?;
|
||||
let blob = BlobObject {
|
||||
blobdir,
|
||||
name: format!("$BLOBDIR/{}", name),
|
||||
@@ -71,18 +80,25 @@ impl<'a> BlobObject<'a> {
|
||||
Ok(file) => return Ok((name, file)),
|
||||
Err(err) => {
|
||||
if attempt == max_attempt {
|
||||
return Err(BlobError::new_create_failure(dir, &name, err));
|
||||
return Err(BlobError::CreateFailure {
|
||||
blobdir: dir.to_path_buf(),
|
||||
blobname: name,
|
||||
cause: err,
|
||||
backtrace: failure::Backtrace::new(),
|
||||
});
|
||||
} else {
|
||||
name = format!("{}-{}{}", stem, rand::random::<u32>(), ext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(BlobError::new_create_failure(
|
||||
dir,
|
||||
&name,
|
||||
format_err!("Unreachable code - supposedly"),
|
||||
))
|
||||
// This is supposed to be unreachable, but the compiler doesn't know.
|
||||
Err(BlobError::CreateFailure {
|
||||
blobdir: dir.to_path_buf(),
|
||||
blobname: name,
|
||||
cause: std::io::Error::new(std::io::ErrorKind::Other, "supposedly unreachable"),
|
||||
backtrace: failure::Backtrace::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new blob object with unique name by copying an existing file.
|
||||
@@ -95,24 +111,35 @@ impl<'a> BlobObject<'a> {
|
||||
/// # Errors
|
||||
///
|
||||
/// In addition to the errors in [BlobObject::create] the
|
||||
/// [BlobErrorKind::CopyFailure] is used when the data can not be
|
||||
/// [BlobError::CopyFailure] is used when the data can not be
|
||||
/// copied.
|
||||
pub fn create_and_copy(
|
||||
context: &'a Context,
|
||||
src: impl AsRef<Path>,
|
||||
) -> std::result::Result<BlobObject<'a>, BlobError> {
|
||||
let mut src_file = fs::File::open(src.as_ref()).map_err(|err| {
|
||||
BlobError::new_copy_failure(context.get_blobdir(), "", src.as_ref(), err)
|
||||
let mut src_file = fs::File::open(src.as_ref()).map_err(|err| BlobError::CopyFailure {
|
||||
blobdir: context.get_blobdir().to_path_buf(),
|
||||
blobname: String::from(""),
|
||||
src: src.as_ref().to_path_buf(),
|
||||
cause: err,
|
||||
backtrace: failure::Backtrace::new(),
|
||||
})?;
|
||||
let (stem, ext) = BlobObject::sanitise_name(&src.as_ref().to_string_lossy());
|
||||
let (name, mut dst_file) = BlobObject::create_new_file(context.get_blobdir(), &stem, &ext)?;
|
||||
let name_for_err = name.clone();
|
||||
std::io::copy(&mut src_file, &mut dst_file).map_err(|err| {
|
||||
{
|
||||
// Attempt to remove the failed file, swallow errors resulting from that.
|
||||
let path = context.get_blobdir().join(&name);
|
||||
let path = context.get_blobdir().join(&name_for_err);
|
||||
fs::remove_file(path).ok();
|
||||
}
|
||||
BlobError::new_copy_failure(context.get_blobdir(), &name, src.as_ref(), err)
|
||||
BlobError::CopyFailure {
|
||||
blobdir: context.get_blobdir().to_path_buf(),
|
||||
blobname: name_for_err,
|
||||
src: src.as_ref().to_path_buf(),
|
||||
cause: err,
|
||||
backtrace: failure::Backtrace::new(),
|
||||
}
|
||||
})?;
|
||||
let blob = BlobObject {
|
||||
blobdir: context.get_blobdir(),
|
||||
@@ -136,7 +163,7 @@ impl<'a> BlobObject<'a> {
|
||||
/// This merely delegates to the [BlobObject::create_and_copy] and
|
||||
/// the [BlobObject::from_path] methods. See those for possible
|
||||
/// errors.
|
||||
pub fn create_from_path(
|
||||
pub fn new_from_path(
|
||||
context: &Context,
|
||||
src: impl AsRef<Path>,
|
||||
) -> std::result::Result<BlobObject, BlobError> {
|
||||
@@ -156,10 +183,10 @@ impl<'a> BlobObject<'a> {
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// [BlobErrorKind::WrongBlobdir] is used if the path is not in
|
||||
/// [BlobError::WrongBlobdir] is used if the path is not in
|
||||
/// the blob directory.
|
||||
///
|
||||
/// [BlobErrorKind::WrongName] is used if the file name does not
|
||||
/// [BlobError::WrongName] is used if the file name does not
|
||||
/// remain identical after sanitisation.
|
||||
pub fn from_path(
|
||||
context: &Context,
|
||||
@@ -168,13 +195,21 @@ impl<'a> BlobObject<'a> {
|
||||
let rel_path = path
|
||||
.as_ref()
|
||||
.strip_prefix(context.get_blobdir())
|
||||
.map_err(|_| BlobError::new_wrong_blobdir(context.get_blobdir(), path.as_ref()))?;
|
||||
.map_err(|_| BlobError::WrongBlobdir {
|
||||
blobdir: context.get_blobdir().to_path_buf(),
|
||||
src: path.as_ref().to_path_buf(),
|
||||
backtrace: failure::Backtrace::new(),
|
||||
})?;
|
||||
if !BlobObject::is_acceptible_blob_name(&rel_path) {
|
||||
return Err(BlobError::new_wrong_name(path.as_ref()));
|
||||
return Err(BlobError::WrongName {
|
||||
blobname: path.as_ref().to_path_buf(),
|
||||
backtrace: failure::Backtrace::new(),
|
||||
});
|
||||
}
|
||||
let name = rel_path
|
||||
.to_str()
|
||||
.ok_or_else(|| BlobError::new_wrong_name(path.as_ref()))?;
|
||||
let name = rel_path.to_str().ok_or_else(|| BlobError::WrongName {
|
||||
blobname: path.as_ref().to_path_buf(),
|
||||
backtrace: failure::Backtrace::new(),
|
||||
})?;
|
||||
BlobObject::from_name(context, name.to_string())
|
||||
}
|
||||
|
||||
@@ -187,7 +222,7 @@ impl<'a> BlobObject<'a> {
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// [BlobErrorKind::WrongName] is used if the name is not a valid
|
||||
/// [BlobError::WrongName] is used if the name is not a valid
|
||||
/// blobname, i.e. if [BlobObject::sanitise_name] does modify the
|
||||
/// provided name.
|
||||
pub fn from_name(
|
||||
@@ -199,7 +234,10 @@ impl<'a> BlobObject<'a> {
|
||||
false => name,
|
||||
};
|
||||
if !BlobObject::is_acceptible_blob_name(&name) {
|
||||
return Err(BlobError::new_wrong_name(name));
|
||||
return Err(BlobError::WrongName {
|
||||
blobname: PathBuf::from(name),
|
||||
backtrace: failure::Backtrace::new(),
|
||||
});
|
||||
}
|
||||
Ok(BlobObject {
|
||||
blobdir: context.get_blobdir(),
|
||||
@@ -315,6 +353,31 @@ impl<'a> BlobObject<'a> {
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn recode_to_avatar_size(&self, context: &Context) -> Result<(), BlobError> {
|
||||
let blob_abs = self.to_abs_path();
|
||||
let img = image::open(&blob_abs).map_err(|err| BlobError::RecodeFailure {
|
||||
blobdir: context.get_blobdir().to_path_buf(),
|
||||
blobname: blob_abs.to_str().unwrap_or_default().to_string(),
|
||||
cause: err,
|
||||
backtrace: failure::Backtrace::new(),
|
||||
})?;
|
||||
|
||||
if img.width() <= AVATAR_SIZE && img.height() <= AVATAR_SIZE {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let img = img.thumbnail(AVATAR_SIZE, AVATAR_SIZE);
|
||||
|
||||
img.save(&blob_abs).map_err(|err| BlobError::WriteFailure {
|
||||
blobdir: context.get_blobdir().to_path_buf(),
|
||||
blobname: blob_abs.to_str().unwrap_or_default().to_string(),
|
||||
cause: err,
|
||||
backtrace: failure::Backtrace::new(),
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for BlobObject<'a> {
|
||||
@@ -324,171 +387,57 @@ impl<'a> fmt::Display for BlobObject<'a> {
|
||||
}
|
||||
|
||||
/// Errors for the [BlobObject].
|
||||
///
|
||||
/// To keep the return type small and thus the happy path fast this
|
||||
/// stores everything on the heap.
|
||||
#[derive(Debug)]
|
||||
pub struct BlobError {
|
||||
inner: Box<BlobErrorInner>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BlobErrorInner {
|
||||
kind: BlobErrorKind,
|
||||
data: BlobErrorData,
|
||||
backtrace: failure::Backtrace,
|
||||
}
|
||||
|
||||
/// Error kind for [BlobError].
|
||||
///
|
||||
/// Each error kind has associated data in the [BlobErrorData].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BlobErrorKind {
|
||||
/// Failed to create the blob.
|
||||
CreateFailure,
|
||||
/// Failed to write data to blob.
|
||||
WriteFailure,
|
||||
/// Failed to copy data to blob.
|
||||
CopyFailure,
|
||||
/// Blob is not in the blobdir.
|
||||
WrongBlobdir,
|
||||
/// Blob has a bad name.
|
||||
///
|
||||
/// E.g. the name is not sanitised correctly or contains a
|
||||
/// sub-directory.
|
||||
WrongName,
|
||||
}
|
||||
|
||||
/// Associated data for each [BlobError] error kind.
|
||||
///
|
||||
/// This is not stored directly on the [BlobErrorKind] so that the
|
||||
/// kind can stay trivially Copy and Eq. It is however possible to
|
||||
/// create a [BlobError] with mismatching [BlobErrorKind] and
|
||||
/// [BlobErrorData], don't do that.
|
||||
///
|
||||
/// Any blobname stored here is the bare name, without the `$BLOBDIR`
|
||||
/// prefix. All data is owned so that errors do not need to be tied
|
||||
/// to any lifetimes.
|
||||
#[derive(Debug)]
|
||||
enum BlobErrorData {
|
||||
#[derive(Fail, Debug)]
|
||||
pub enum BlobError {
|
||||
CreateFailure {
|
||||
blobdir: PathBuf,
|
||||
blobname: String,
|
||||
cause: failure::Error,
|
||||
#[cause]
|
||||
cause: std::io::Error,
|
||||
backtrace: failure::Backtrace,
|
||||
},
|
||||
WriteFailure {
|
||||
blobdir: PathBuf,
|
||||
blobname: String,
|
||||
cause: failure::Error,
|
||||
#[cause]
|
||||
cause: std::io::Error,
|
||||
backtrace: failure::Backtrace,
|
||||
},
|
||||
CopyFailure {
|
||||
blobdir: PathBuf,
|
||||
blobname: String,
|
||||
src: PathBuf,
|
||||
cause: failure::Error,
|
||||
#[cause]
|
||||
cause: std::io::Error,
|
||||
backtrace: failure::Backtrace,
|
||||
},
|
||||
RecodeFailure {
|
||||
blobdir: PathBuf,
|
||||
blobname: String,
|
||||
#[cause]
|
||||
cause: image::ImageError,
|
||||
backtrace: failure::Backtrace,
|
||||
},
|
||||
WrongBlobdir {
|
||||
blobdir: PathBuf,
|
||||
src: PathBuf,
|
||||
backtrace: failure::Backtrace,
|
||||
},
|
||||
WrongName {
|
||||
blobname: PathBuf,
|
||||
backtrace: failure::Backtrace,
|
||||
},
|
||||
}
|
||||
|
||||
impl BlobError {
|
||||
pub fn kind(&self) -> BlobErrorKind {
|
||||
self.inner.kind
|
||||
}
|
||||
|
||||
fn new_create_failure(
|
||||
blobdir: impl Into<PathBuf>,
|
||||
blobname: impl Into<String>,
|
||||
cause: impl Into<failure::Error>,
|
||||
) -> BlobError {
|
||||
BlobError {
|
||||
inner: Box::new(BlobErrorInner {
|
||||
kind: BlobErrorKind::CreateFailure,
|
||||
data: BlobErrorData::CreateFailure {
|
||||
blobdir: blobdir.into(),
|
||||
blobname: blobname.into(),
|
||||
cause: cause.into(),
|
||||
},
|
||||
backtrace: failure::Backtrace::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_write_failure(
|
||||
blobdir: impl Into<PathBuf>,
|
||||
blobname: impl Into<String>,
|
||||
cause: impl Into<failure::Error>,
|
||||
) -> BlobError {
|
||||
BlobError {
|
||||
inner: Box::new(BlobErrorInner {
|
||||
kind: BlobErrorKind::WriteFailure,
|
||||
data: BlobErrorData::WriteFailure {
|
||||
blobdir: blobdir.into(),
|
||||
blobname: blobname.into(),
|
||||
cause: cause.into(),
|
||||
},
|
||||
backtrace: failure::Backtrace::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_copy_failure(
|
||||
blobdir: impl Into<PathBuf>,
|
||||
blobname: impl Into<String>,
|
||||
src: impl Into<PathBuf>,
|
||||
cause: impl Into<failure::Error>,
|
||||
) -> BlobError {
|
||||
BlobError {
|
||||
inner: Box::new(BlobErrorInner {
|
||||
kind: BlobErrorKind::CopyFailure,
|
||||
data: BlobErrorData::CopyFailure {
|
||||
blobdir: blobdir.into(),
|
||||
blobname: blobname.into(),
|
||||
src: src.into(),
|
||||
cause: cause.into(),
|
||||
},
|
||||
backtrace: failure::Backtrace::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_wrong_blobdir(blobdir: impl Into<PathBuf>, src: impl Into<PathBuf>) -> BlobError {
|
||||
BlobError {
|
||||
inner: Box::new(BlobErrorInner {
|
||||
kind: BlobErrorKind::WrongBlobdir,
|
||||
data: BlobErrorData::WrongBlobdir {
|
||||
blobdir: blobdir.into(),
|
||||
src: src.into(),
|
||||
},
|
||||
backtrace: failure::Backtrace::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_wrong_name(blobname: impl Into<PathBuf>) -> BlobError {
|
||||
BlobError {
|
||||
inner: Box::new(BlobErrorInner {
|
||||
kind: BlobErrorKind::WrongName,
|
||||
data: BlobErrorData::WrongName {
|
||||
blobname: blobname.into(),
|
||||
},
|
||||
backtrace: failure::Backtrace::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implementing Display is done by hand because the failure
|
||||
// #[fail(display = "...")] syntax does not allow using
|
||||
// `blobdir.display()`.
|
||||
impl fmt::Display for BlobError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// Match on the data rather than kind, they are equivalent for
|
||||
// identifying purposes but contain the actual data we need.
|
||||
match &self.inner.data {
|
||||
BlobErrorData::CreateFailure {
|
||||
match &self {
|
||||
BlobError::CreateFailure {
|
||||
blobdir, blobname, ..
|
||||
} => write!(
|
||||
f,
|
||||
@@ -496,7 +445,7 @@ impl fmt::Display for BlobError {
|
||||
blobname,
|
||||
blobdir.display()
|
||||
),
|
||||
BlobErrorData::WriteFailure {
|
||||
BlobError::WriteFailure {
|
||||
blobdir, blobname, ..
|
||||
} => write!(
|
||||
f,
|
||||
@@ -504,7 +453,7 @@ impl fmt::Display for BlobError {
|
||||
blobname,
|
||||
blobdir.display()
|
||||
),
|
||||
BlobErrorData::CopyFailure {
|
||||
BlobError::CopyFailure {
|
||||
blobdir,
|
||||
blobname,
|
||||
src,
|
||||
@@ -516,34 +465,22 @@ impl fmt::Display for BlobError {
|
||||
blobname,
|
||||
blobdir.display(),
|
||||
),
|
||||
BlobErrorData::WrongBlobdir { blobdir, src } => write!(
|
||||
BlobError::RecodeFailure {
|
||||
blobdir, blobname, ..
|
||||
} => write!(f, "Failed to recode {} in {}", blobname, blobdir.display(),),
|
||||
BlobError::WrongBlobdir { blobdir, src, .. } => write!(
|
||||
f,
|
||||
"File path {} is not in blobdir {}",
|
||||
src.display(),
|
||||
blobdir.display(),
|
||||
),
|
||||
BlobErrorData::WrongName { blobname } => {
|
||||
BlobError::WrongName { blobname, .. } => {
|
||||
write!(f, "Blob has a bad name: {}", blobname.display(),)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl failure::Fail for BlobError {
|
||||
fn cause(&self) -> Option<&dyn failure::Fail> {
|
||||
match &self.inner.data {
|
||||
BlobErrorData::CreateFailure { cause, .. }
|
||||
| BlobErrorData::WriteFailure { cause, .. }
|
||||
| BlobErrorData::CopyFailure { cause, .. } => Some(cause.as_fail()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn backtrace(&self) -> Option<&failure::Backtrace> {
|
||||
Some(&self.inner.backtrace)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -585,23 +522,23 @@ mod tests {
|
||||
#[test]
|
||||
fn test_suffix() {
|
||||
let t = dummy_context();
|
||||
let foo = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
|
||||
assert_eq!(foo.suffix(), Some("txt"));
|
||||
let bar = BlobObject::create(&t.ctx, "bar", b"world").unwrap();
|
||||
assert_eq!(bar.suffix(), None);
|
||||
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
|
||||
assert_eq!(blob.suffix(), Some("txt"));
|
||||
let blob = BlobObject::create(&t.ctx, "bar", b"world").unwrap();
|
||||
assert_eq!(blob.suffix(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_dup() {
|
||||
let t = dummy_context();
|
||||
BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
|
||||
let foo = t.ctx.get_blobdir().join("foo.txt");
|
||||
assert!(foo.exists());
|
||||
let foo_path = t.ctx.get_blobdir().join("foo.txt");
|
||||
assert!(foo_path.exists());
|
||||
BlobObject::create(&t.ctx, "foo.txt", b"world").unwrap();
|
||||
for dirent in fs::read_dir(t.ctx.get_blobdir()).unwrap() {
|
||||
let fname = dirent.unwrap().file_name();
|
||||
if fname == foo.file_name().unwrap() {
|
||||
assert_eq!(fs::read(&foo).unwrap(), b"hello");
|
||||
if fname == foo_path.file_name().unwrap() {
|
||||
assert_eq!(fs::read(&foo_path).unwrap(), b"hello");
|
||||
} else {
|
||||
let name = fname.to_str().unwrap();
|
||||
assert!(name.starts_with("foo"));
|
||||
@@ -614,13 +551,13 @@ mod tests {
|
||||
fn test_double_ext_preserved() {
|
||||
let t = dummy_context();
|
||||
BlobObject::create(&t.ctx, "foo.tar.gz", b"hello").unwrap();
|
||||
let foo = t.ctx.get_blobdir().join("foo.tar.gz");
|
||||
assert!(foo.exists());
|
||||
let foo_path = t.ctx.get_blobdir().join("foo.tar.gz");
|
||||
assert!(foo_path.exists());
|
||||
BlobObject::create(&t.ctx, "foo.tar.gz", b"world").unwrap();
|
||||
for dirent in fs::read_dir(t.ctx.get_blobdir()).unwrap() {
|
||||
let fname = dirent.unwrap().file_name();
|
||||
if fname == foo.file_name().unwrap() {
|
||||
assert_eq!(fs::read(&foo).unwrap(), b"hello");
|
||||
if fname == foo_path.file_name().unwrap() {
|
||||
assert_eq!(fs::read(&foo_path).unwrap(), b"hello");
|
||||
} else {
|
||||
let name = fname.to_str().unwrap();
|
||||
println!("{}", name);
|
||||
@@ -661,14 +598,14 @@ mod tests {
|
||||
|
||||
let src_ext = t.dir.path().join("external");
|
||||
fs::write(&src_ext, b"boo").unwrap();
|
||||
let blob = BlobObject::create_from_path(&t.ctx, &src_ext).unwrap();
|
||||
let blob = BlobObject::new_from_path(&t.ctx, &src_ext).unwrap();
|
||||
assert_eq!(blob.as_name(), "$BLOBDIR/external");
|
||||
let data = fs::read(blob.to_abs_path()).unwrap();
|
||||
assert_eq!(data, b"boo");
|
||||
|
||||
let src_int = t.ctx.get_blobdir().join("internal");
|
||||
fs::write(&src_int, b"boo").unwrap();
|
||||
let blob = BlobObject::create_from_path(&t.ctx, &src_int).unwrap();
|
||||
let blob = BlobObject::new_from_path(&t.ctx, &src_int).unwrap();
|
||||
assert_eq!(blob.as_name(), "$BLOBDIR/internal");
|
||||
let data = fs::read(blob.to_abs_path()).unwrap();
|
||||
assert_eq!(data, b"boo");
|
||||
@@ -678,7 +615,7 @@ mod tests {
|
||||
let t = dummy_context();
|
||||
let src_ext = t.dir.path().join("autocrypt-setup-message-4137848473.html");
|
||||
fs::write(&src_ext, b"boo").unwrap();
|
||||
let blob = BlobObject::create_from_path(&t.ctx, &src_ext).unwrap();
|
||||
let blob = BlobObject::new_from_path(&t.ctx, &src_ext).unwrap();
|
||||
assert_eq!(
|
||||
blob.as_name(),
|
||||
"$BLOBDIR/autocrypt-setup-message-4137848473.html"
|
||||
|
||||
438
src/chat.rs
@@ -4,19 +4,20 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use itertools::Itertools;
|
||||
use num_traits::FromPrimitive;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::blob::{BlobErrorKind, BlobObject};
|
||||
use crate::blob::{BlobError, BlobObject};
|
||||
use crate::chatlist::*;
|
||||
use crate::config::*;
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_mimeparser::SystemMessage;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
use crate::job::*;
|
||||
use crate::message::{self, InvalidMsgId, Message, MessageState, MsgId};
|
||||
use crate::mimeparser::SystemMessage;
|
||||
use crate::param::*;
|
||||
use crate::sql::{self, Sql};
|
||||
use crate::stock::StockMessage;
|
||||
@@ -34,7 +35,6 @@ pub struct Chat {
|
||||
pub grpid: String,
|
||||
blocked: Blocked,
|
||||
pub param: Params,
|
||||
pub gossiped_timestamp: i64,
|
||||
is_sending_locations: bool,
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ impl Chat {
|
||||
pub fn load_from_db(context: &Context, chat_id: u32) -> Result<Self, Error> {
|
||||
let res = context.sql.query_row(
|
||||
"SELECT c.id,c.type,c.name, c.grpid,c.param,c.archived, \
|
||||
c.blocked, c.gossiped_timestamp, c.locations_send_until \
|
||||
c.blocked, c.locations_send_until \
|
||||
FROM chats c WHERE c.id=?;",
|
||||
params![chat_id as i32],
|
||||
|row| {
|
||||
@@ -55,8 +55,7 @@ impl Chat {
|
||||
param: row.get::<_, String>(4)?.parse().unwrap_or_default(),
|
||||
archived: row.get(5)?,
|
||||
blocked: row.get::<_, Option<_>>(6)?.unwrap_or_default(),
|
||||
gossiped_timestamp: row.get(7)?,
|
||||
is_sending_locations: row.get(8)?,
|
||||
is_sending_locations: row.get(7)?,
|
||||
};
|
||||
|
||||
Ok(c)
|
||||
@@ -64,13 +63,15 @@ impl Chat {
|
||||
);
|
||||
|
||||
match res {
|
||||
Err(err @ crate::error::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => Err(err),
|
||||
Err(err @ crate::sql::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => {
|
||||
Err(err.into())
|
||||
}
|
||||
Err(err) => {
|
||||
error!(
|
||||
context,
|
||||
"chat: failed to load from db {}: {:?}", chat_id, err
|
||||
);
|
||||
Err(err)
|
||||
Err(err.into())
|
||||
}
|
||||
Ok(mut chat) => {
|
||||
match chat.id {
|
||||
@@ -115,10 +116,12 @@ impl Chat {
|
||||
self.param.exists(Param::Selftalk)
|
||||
}
|
||||
|
||||
/// Returns true if chat is a device chat.
|
||||
pub fn is_device_talk(&self) -> bool {
|
||||
self.param.exists(Param::Devicetalk)
|
||||
}
|
||||
|
||||
/// Returns true if user can send messages to this chat.
|
||||
pub fn can_send(&self) -> bool {
|
||||
self.id > DC_CHAT_ID_LAST_SPECIAL && !self.is_device_talk()
|
||||
}
|
||||
@@ -129,17 +132,21 @@ impl Chat {
|
||||
&context.sql,
|
||||
"UPDATE chats SET param=? WHERE id=?",
|
||||
params![self.param.to_string(), self.id as i32],
|
||||
)
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns chat ID.
|
||||
pub fn get_id(&self) -> u32 {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Returns chat type.
|
||||
pub fn get_type(&self) -> Chattype {
|
||||
self.typ
|
||||
}
|
||||
|
||||
/// Returns chat name.
|
||||
pub fn get_name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
@@ -165,7 +172,7 @@ impl Chat {
|
||||
}
|
||||
|
||||
if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup {
|
||||
if self.id == 1 {
|
||||
if self.id == DC_CHAT_ID_DEADDROP {
|
||||
return context.stock_str(StockMessage::DeadDrop).into();
|
||||
}
|
||||
let cnt = get_chat_contact_cnt(context, self.id);
|
||||
@@ -212,6 +219,10 @@ impl Chat {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_gossiped_timestamp(&self, context: &Context) -> i64 {
|
||||
get_gossiped_timestamp(context, self.id)
|
||||
}
|
||||
|
||||
pub fn get_color(&self, context: &Context) -> u32 {
|
||||
let mut color = 0;
|
||||
|
||||
@@ -242,10 +253,12 @@ impl Chat {
|
||||
!self.is_unpromoted()
|
||||
}
|
||||
|
||||
/// Returns true if chat is a verified group chat.
|
||||
pub fn is_verified(&self) -> bool {
|
||||
(self.typ == Chattype::VerifiedGroup)
|
||||
self.typ == Chattype::VerifiedGroup
|
||||
}
|
||||
|
||||
/// Returns true if location streaming is enabled in the chat.
|
||||
pub fn is_sending_locations(&self) -> bool {
|
||||
self.is_sending_locations
|
||||
}
|
||||
@@ -256,8 +269,6 @@ impl Chat {
|
||||
msg: &mut Message,
|
||||
timestamp: i64,
|
||||
) -> Result<MsgId, Error> {
|
||||
let mut do_guarantee_e2ee: bool;
|
||||
let e2ee_enabled: bool;
|
||||
let mut new_references = "".into();
|
||||
let mut new_in_reply_to = "".into();
|
||||
let mut msg_id = 0;
|
||||
@@ -308,24 +319,20 @@ impl Chat {
|
||||
self.id
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup {
|
||||
if self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
|
||||
self.param.remove(Param::Unpromoted);
|
||||
self.update_param(context)?;
|
||||
}
|
||||
}
|
||||
} else if (self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup)
|
||||
&& self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1
|
||||
{
|
||||
msg.param.set_int(Param::AttachGroupImage, 1);
|
||||
self.param.remove(Param::Unpromoted);
|
||||
self.update_param(context)?;
|
||||
}
|
||||
|
||||
/* check if we can guarantee E2EE for this message.
|
||||
if we guarantee E2EE, and circumstances change
|
||||
/* check if we want to encrypt this message. If yes and circumstances change
|
||||
so that E2EE is no longer available at a later point (reset, changed settings),
|
||||
we do not send the message out at all */
|
||||
do_guarantee_e2ee = false;
|
||||
e2ee_enabled = context.get_config_bool(Config::E2eeEnabled);
|
||||
if e2ee_enabled && msg.param.get_int(Param::ForcePlaintext).unwrap_or_default() == 0 {
|
||||
we might not send the message out at all */
|
||||
if msg.param.get_int(Param::ForcePlaintext).unwrap_or_default() == 0 {
|
||||
let mut can_encrypt = true;
|
||||
let mut all_mutual = true;
|
||||
let mut all_mutual = context.get_config_bool(Config::E2eeEnabled);
|
||||
|
||||
// take care that this statement returns NULL rows
|
||||
// if there is no peerstates for a chat member!
|
||||
@@ -372,18 +379,13 @@ impl Chat {
|
||||
}
|
||||
}
|
||||
|
||||
if can_encrypt {
|
||||
if all_mutual {
|
||||
do_guarantee_e2ee = true;
|
||||
} else if last_msg_in_chat_encrypted(context, &context.sql, self.id) {
|
||||
do_guarantee_e2ee = true;
|
||||
}
|
||||
if can_encrypt
|
||||
&& (all_mutual || last_msg_in_chat_encrypted(context, &context.sql, self.id))
|
||||
{
|
||||
msg.param.set_int(Param::GuaranteeE2ee, 1);
|
||||
}
|
||||
}
|
||||
if do_guarantee_e2ee {
|
||||
msg.param.set_int(Param::GuaranteeE2ee, 1);
|
||||
}
|
||||
// reset eg. for forwarding
|
||||
// reset encrypt error state eg. for forwarding
|
||||
msg.param.remove(Param::ErroneousE2ee);
|
||||
|
||||
// set "In-Reply-To:" to identify the message to which the composed message is a reply;
|
||||
@@ -416,15 +418,15 @@ impl Chat {
|
||||
} else if !parent_in_reply_to.is_empty() && !parent_rfc724_mid.is_empty() {
|
||||
new_references = format!("{} {}", parent_in_reply_to, parent_rfc724_mid);
|
||||
} else if !parent_in_reply_to.is_empty() {
|
||||
new_references = parent_in_reply_to.clone();
|
||||
new_references = parent_in_reply_to;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add independent location to database
|
||||
|
||||
if msg.param.exists(Param::SetLatitude) {
|
||||
if sql::execute(
|
||||
if msg.param.exists(Param::SetLatitude)
|
||||
&& sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO locations \
|
||||
@@ -439,17 +441,16 @@ impl Chat {
|
||||
],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
location_id = sql::get_rowid2(
|
||||
context,
|
||||
&context.sql,
|
||||
"locations",
|
||||
"timestamp",
|
||||
timestamp,
|
||||
"from_id",
|
||||
DC_CONTACT_ID_SELF as i32,
|
||||
);
|
||||
}
|
||||
{
|
||||
location_id = sql::get_rowid2(
|
||||
context,
|
||||
&context.sql,
|
||||
"locations",
|
||||
"timestamp",
|
||||
timestamp,
|
||||
"from_id",
|
||||
DC_CONTACT_ID_SELF as i32,
|
||||
);
|
||||
}
|
||||
|
||||
// add message to the database
|
||||
@@ -506,7 +507,7 @@ impl Chat {
|
||||
/// chat messages, use dc_get_chat_msgs().
|
||||
///
|
||||
/// If the user is asked before creation, he should be
|
||||
/// asked whether he wants to chat with the _contact_ belonging to the message;
|
||||
/// asked whether he wants to chat with the *contact* belonging to the message;
|
||||
/// the group names may be really weird when taken from the subject of implicit
|
||||
/// groups and this may look confusing.
|
||||
///
|
||||
@@ -588,6 +589,10 @@ pub fn unblock(context: &Context, chat_id: u32) {
|
||||
}
|
||||
|
||||
pub fn set_blocking(context: &Context, chat_id: u32, new_blocking: Blocked) -> bool {
|
||||
if chat_id == 0 {
|
||||
warn!(context, "ignoring setting of Block-status for chat_id=0");
|
||||
return false;
|
||||
}
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
@@ -597,12 +602,6 @@ pub fn set_blocking(context: &Context, chat_id: u32, new_blocking: Blocked) -> b
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
fn copy_device_icon_to_blobs(context: &Context) -> Result<String, Error> {
|
||||
let icon = include_bytes!("../assets/icon-device.png");
|
||||
let blob = BlobObject::create(context, "icon-device.png".to_string(), icon)?;
|
||||
Ok(blob.as_name().to_string())
|
||||
}
|
||||
|
||||
pub fn update_saved_messages_icon(context: &Context) -> Result<(), Error> {
|
||||
// if there is no saved-messages chat, there is nothing to update. this is no error.
|
||||
if let Ok((chat_id, _)) = lookup_by_contact_id(context, DC_CONTACT_ID_SELF) {
|
||||
@@ -617,6 +616,24 @@ pub fn update_saved_messages_icon(context: &Context) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_device_icon(context: &Context) -> Result<(), Error> {
|
||||
// if there is no device-chat, there is nothing to update. this is no error.
|
||||
if let Ok((chat_id, _)) = lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE) {
|
||||
let icon = include_bytes!("../assets/icon-device.png");
|
||||
let blob = BlobObject::create(context, "icon-device.png".to_string(), icon)?;
|
||||
let icon = blob.as_name().to_string();
|
||||
|
||||
let mut chat = Chat::load_from_db(context, chat_id)?;
|
||||
chat.param.set(Param::ProfileImage, &icon);
|
||||
chat.update_param(context)?;
|
||||
|
||||
let mut contact = Contact::load_from_db(context, DC_CONTACT_ID_DEVICE)?;
|
||||
contact.param.set(Param::ProfileImage, icon);
|
||||
contact.update_param(context)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_or_lookup_by_contact_id(
|
||||
context: &Context,
|
||||
contact_id: u32,
|
||||
@@ -636,22 +653,19 @@ pub fn create_or_lookup_by_contact_id(
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
format!(
|
||||
"INSERT INTO chats (type, name, param, blocked, grpid) VALUES({}, '{}', '{}', {}, '{}')",
|
||||
"INSERT INTO chats (type, name, param, blocked, grpid, created_timestamp) VALUES(?, ?, ?, ?, ?, ?)",
|
||||
params![
|
||||
100,
|
||||
chat_name,
|
||||
match contact_id {
|
||||
DC_CONTACT_ID_SELF => "K=1".to_string(), // K = Param::Selftalk
|
||||
DC_CONTACT_ID_DEVICE => {
|
||||
let icon = copy_device_icon_to_blobs(context)?;
|
||||
format!("D=1\ni={}", icon) // D = Param::Devicetalk, i = Param::ProfileImage
|
||||
},
|
||||
DC_CONTACT_ID_DEVICE => "D=1".to_string(), // D = Param::Devicetalk
|
||||
_ => "".to_string()
|
||||
},
|
||||
create_blocked as u8,
|
||||
contact.get_addr(),
|
||||
),
|
||||
params![],
|
||||
time(),
|
||||
]
|
||||
)?;
|
||||
|
||||
let chat_id = sql::get_rowid(context, &context.sql, "chats", "grpid", contact.get_addr());
|
||||
@@ -659,15 +673,14 @@ pub fn create_or_lookup_by_contact_id(
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
format!(
|
||||
"INSERT INTO chats_contacts (chat_id, contact_id) VALUES({}, {})",
|
||||
chat_id, contact_id
|
||||
),
|
||||
params![],
|
||||
"INSERT INTO chats_contacts (chat_id, contact_id) VALUES(?, ?)",
|
||||
params![chat_id, contact_id],
|
||||
)?;
|
||||
|
||||
if contact_id == DC_CONTACT_ID_SELF {
|
||||
update_saved_messages_icon(context)?;
|
||||
} else if contact_id == DC_CONTACT_ID_DEVICE {
|
||||
update_device_icon(context)?;
|
||||
}
|
||||
|
||||
Ok((chat_id, create_blocked))
|
||||
@@ -680,7 +693,7 @@ pub fn lookup_by_contact_id(context: &Context, contact_id: u32) -> Result<(u32,
|
||||
"SELECT c.id, c.blocked FROM chats c INNER JOIN chats_contacts j ON c.id=j.chat_id WHERE c.type=100 AND c.id>9 AND j.contact_id=?;",
|
||||
params![contact_id as i32],
|
||||
|row| Ok((row.get(0)?, row.get::<_, Option<_>>(1)?.unwrap_or_default())),
|
||||
)
|
||||
).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn get_by_contact_id(context: &Context, contact_id: u32) -> Result<u32, Error> {
|
||||
@@ -733,6 +746,7 @@ fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> {
|
||||
.get_blob(Param::File, context, !msg.is_increation())?
|
||||
.ok_or_else(|| format_err!("Attachment missing for message of type #{}", msg.type_0))?;
|
||||
msg.param.set(Param::File, blob.as_name());
|
||||
|
||||
if msg.type_0 == Viewtype::File || msg.type_0 == Viewtype::Image {
|
||||
// Correct the type, take care not to correct already very special
|
||||
// formats as GIF or VOICE.
|
||||
@@ -807,6 +821,7 @@ fn last_msg_in_chat_encrypted(context: &Context, sql: &Sql, chat_id: u32) -> boo
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether a contact is in a chat or not.
|
||||
pub fn is_contact_in_chat(context: &Context, chat_id: u32, contact_id: u32) -> bool {
|
||||
/* this function works for group and for normal chats, however, it is more useful for group chats.
|
||||
DC_CONTACT_ID_SELF may be used to check, if the user itself is in a group chat (DC_CONTACT_ID_SELF is not added to normal chats) */
|
||||
@@ -828,7 +843,8 @@ pub fn unarchive(context: &Context, chat_id: u32) -> Result<(), Error> {
|
||||
&context.sql,
|
||||
"UPDATE chats SET archived=0 WHERE id=?",
|
||||
params![chat_id as i32],
|
||||
)
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a message defined by a dc_msg_t object to a chat.
|
||||
@@ -869,17 +885,14 @@ pub fn send_msg(context: &Context, chat_id: u32, msg: &mut Message) -> Result<Ms
|
||||
let forwards = msg.param.get(Param::PrepForwards);
|
||||
if let Some(forwards) = forwards {
|
||||
for forward in forwards.split(' ') {
|
||||
match forward
|
||||
if let Ok(msg_id) = forward
|
||||
.parse::<u32>()
|
||||
.map_err(|_| InvalidMsgId)
|
||||
.map(|id| MsgId::new(id))
|
||||
.map(MsgId::new)
|
||||
{
|
||||
Ok(msg_id) => {
|
||||
if let Ok(mut msg) = Message::load_from_db(context, msg_id) {
|
||||
send_msg(context, 0, &mut msg)?;
|
||||
};
|
||||
}
|
||||
Err(_) => (),
|
||||
if let Ok(mut msg) = Message::load_from_db(context, msg_id) {
|
||||
send_msg(context, 0, &mut msg)?;
|
||||
};
|
||||
}
|
||||
}
|
||||
msg.param.remove(Param::PrepForwards);
|
||||
@@ -975,7 +988,8 @@ fn do_set_draft(context: &Context, chat_id: u32, msg: &mut Message) -> Result<()
|
||||
msg.param.to_string(),
|
||||
1,
|
||||
],
|
||||
)
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// similar to as dc_set_draft() but does not emit an event
|
||||
@@ -1098,6 +1112,7 @@ pub fn get_chat_msgs(
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns number of messages in a chat.
|
||||
pub fn get_msg_cnt(context: &Context, chat_id: u32) -> usize {
|
||||
context
|
||||
.sql
|
||||
@@ -1268,6 +1283,7 @@ pub fn get_next_media(
|
||||
ret
|
||||
}
|
||||
|
||||
/// Archives or unarchives a chat.
|
||||
pub fn archive(context: &Context, chat_id: u32, archive: bool) -> Result<(), Error> {
|
||||
ensure!(
|
||||
chat_id > DC_CHAT_ID_LAST_SPECIAL,
|
||||
@@ -1303,6 +1319,7 @@ pub fn archive(context: &Context, chat_id: u32, archive: bool) -> Result<(), Err
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deletes a chat.
|
||||
pub fn delete(context: &Context, chat_id: u32) -> Result<(), Error> {
|
||||
ensure!(
|
||||
chat_id > DC_CHAT_ID_LAST_SPECIAL,
|
||||
@@ -1388,7 +1405,7 @@ pub fn create_group_chat(
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO chats (type, name, grpid, param) VALUES(?, ?, ?, \'U=1\');",
|
||||
"INSERT INTO chats (type, name, grpid, param, created_timestamp) VALUES(?, ?, ?, \'U=1\', ?);",
|
||||
params![
|
||||
if verified != VerifiedStatus::Unverified {
|
||||
Chattype::VerifiedGroup
|
||||
@@ -1396,7 +1413,8 @@ pub fn create_group_chat(
|
||||
Chattype::Group
|
||||
},
|
||||
chat_name.as_ref(),
|
||||
grpid
|
||||
grpid,
|
||||
time(),
|
||||
],
|
||||
)?;
|
||||
|
||||
@@ -1432,6 +1450,7 @@ pub fn add_to_chat_contacts_table(context: &Context, chat_id: u32, contact_id: u
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Adds a contact to the chat.
|
||||
pub fn add_contact_to_chat(context: &Context, chat_id: u32, contact_id: u32) -> bool {
|
||||
match add_contact_to_chat_ex(context, chat_id, contact_id, false) {
|
||||
Ok(res) => res,
|
||||
@@ -1456,7 +1475,7 @@ pub(crate) fn add_contact_to_chat_ex(
|
||||
let contact = Contact::get_by_id(context, contact_id)?;
|
||||
let mut msg = Message::default();
|
||||
|
||||
reset_gossiped_timestamp(context, chat_id);
|
||||
reset_gossiped_timestamp(context, chat_id)?;
|
||||
|
||||
/*this also makes sure, not contacts are added to special or normal chats*/
|
||||
let mut chat = Chat::load_from_db(context, chat_id)?;
|
||||
@@ -1486,7 +1505,7 @@ pub(crate) fn add_contact_to_chat_ex(
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_default();
|
||||
if contact.get_addr() == &self_addr {
|
||||
if addr_cmp(contact.get_addr(), &self_addr) {
|
||||
// ourself is added using DC_CONTACT_ID_SELF, do not add this address explicitly.
|
||||
// if SELF is not in the group, members cannot be added at all.
|
||||
warn!(
|
||||
@@ -1554,12 +1573,28 @@ fn real_group_exists(context: &Context, chat_id: u32) -> bool {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn reset_gossiped_timestamp(context: &Context, chat_id: u32) {
|
||||
set_gossiped_timestamp(context, chat_id, 0);
|
||||
pub fn reset_gossiped_timestamp(context: &Context, chat_id: u32) -> crate::sql::Result<()> {
|
||||
set_gossiped_timestamp(context, chat_id, 0)
|
||||
}
|
||||
|
||||
// Should return Result
|
||||
pub fn set_gossiped_timestamp(context: &Context, chat_id: u32, timestamp: i64) {
|
||||
/// Get timestamp of the last gossip sent in the chat.
|
||||
/// Zero return value means that gossip was never sent.
|
||||
pub fn get_gossiped_timestamp(context: &Context, chat_id: u32) -> i64 {
|
||||
context
|
||||
.sql
|
||||
.query_get_value::<_, i64>(
|
||||
context,
|
||||
"SELECT gossiped_timestamp FROM chats WHERE id=?;",
|
||||
params![chat_id as i32],
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn set_gossiped_timestamp(
|
||||
context: &Context,
|
||||
chat_id: u32,
|
||||
timestamp: i64,
|
||||
) -> crate::sql::Result<()> {
|
||||
if 0 != chat_id {
|
||||
info!(
|
||||
context,
|
||||
@@ -1572,7 +1607,6 @@ pub fn set_gossiped_timestamp(context: &Context, chat_id: u32, timestamp: i64) {
|
||||
"UPDATE chats SET gossiped_timestamp=? WHERE id=?;",
|
||||
params![timestamp, chat_id as i32],
|
||||
)
|
||||
.ok();
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
@@ -1584,10 +1618,58 @@ pub fn set_gossiped_timestamp(context: &Context, chat_id: u32, timestamp: i64) {
|
||||
"UPDATE chats SET gossiped_timestamp=?;",
|
||||
params![timestamp],
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shall_attach_selfavatar(context: &Context, chat_id: u32) -> Result<bool, Error> {
|
||||
// versions before 12/2019 already allowed to set selfavatar, however, it was never sent to others.
|
||||
// to avoid sending out previously set selfavatars unexpectedly we added this additional check.
|
||||
// it can be removed after some time.
|
||||
if !context
|
||||
.sql
|
||||
.get_raw_config_bool(context, "attach_selfavatar")
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let timestamp_some_days_ago = time() - DC_RESEND_USER_AVATAR_DAYS * 24 * 60 * 60;
|
||||
let needs_attach = context.sql.query_map(
|
||||
"SELECT c.selfavatar_sent
|
||||
FROM chats_contacts cc
|
||||
LEFT JOIN contacts c ON c.id=cc.contact_id
|
||||
WHERE cc.chat_id=? AND cc.contact_id!=?;",
|
||||
params![chat_id, DC_CONTACT_ID_SELF],
|
||||
|row| Ok(row.get::<_, i64>(0)),
|
||||
|rows| {
|
||||
let mut needs_attach = false;
|
||||
for row in rows {
|
||||
if let Ok(selfavatar_sent) = row {
|
||||
let selfavatar_sent = selfavatar_sent?;
|
||||
if selfavatar_sent < timestamp_some_days_ago {
|
||||
needs_attach = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(needs_attach)
|
||||
},
|
||||
)?;
|
||||
Ok(needs_attach)
|
||||
}
|
||||
|
||||
pub fn set_selfavatar_timestamp(
|
||||
context: &Context,
|
||||
chat_id: u32,
|
||||
timestamp: i64,
|
||||
) -> Result<(), Error> {
|
||||
context.sql.execute(
|
||||
"UPDATE contacts
|
||||
SET selfavatar_sent=?
|
||||
WHERE id IN(SELECT contact_id FROM chats_contacts WHERE chat_id=?);",
|
||||
params![timestamp, chat_id],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_contact_from_chat(
|
||||
context: &Context,
|
||||
chat_id: u32,
|
||||
@@ -1683,10 +1765,13 @@ fn set_group_explicitly_left(context: &Context, grpid: impl AsRef<str>) -> Resul
|
||||
}
|
||||
|
||||
pub fn is_group_explicitly_left(context: &Context, grpid: impl AsRef<str>) -> Result<bool, Error> {
|
||||
context.sql.exists(
|
||||
"SELECT id FROM leftgrps WHERE grpid=?;",
|
||||
params![grpid.as_ref()],
|
||||
)
|
||||
context
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT id FROM leftgrps WHERE grpid=?;",
|
||||
params![grpid.as_ref()],
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn set_chat_name(
|
||||
@@ -1716,12 +1801,8 @@ pub fn set_chat_name(
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
format!(
|
||||
"UPDATE chats SET name='{}' WHERE id={};",
|
||||
new_name.as_ref(),
|
||||
chat_id as i32
|
||||
),
|
||||
params![],
|
||||
"UPDATE chats SET name=? WHERE id=?;",
|
||||
params![new_name.as_ref(), chat_id as i32],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
@@ -1795,13 +1876,14 @@ pub fn set_chat_profile_image(
|
||||
));
|
||||
} else {
|
||||
let image_blob = BlobObject::from_path(context, Path::new(new_image.as_ref())).or_else(
|
||||
|err| match err.kind() {
|
||||
BlobErrorKind::WrongBlobdir => {
|
||||
|err| match err {
|
||||
BlobError::WrongBlobdir { .. } => {
|
||||
BlobObject::create_and_copy(context, Path::new(new_image.as_ref()))
|
||||
}
|
||||
_ => Err(err),
|
||||
},
|
||||
)?;
|
||||
image_blob.recode_to_avatar_size(context)?;
|
||||
chat.param.set(Param::ProfileImage, image_blob.as_name());
|
||||
msg.param.set(Param::Arg, image_blob.as_name());
|
||||
msg.text = Some(context.stock_system_msg(
|
||||
@@ -1839,6 +1921,7 @@ pub fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: u32) -> Resul
|
||||
|
||||
unarchive(context, chat_id)?;
|
||||
if let Ok(mut chat) = Chat::load_from_db(context, chat_id) {
|
||||
ensure!(chat.can_send(), "cannot send to chat #{}", chat_id);
|
||||
curr_timestamp = dc_create_smeared_timestamps(context, msg_ids.len());
|
||||
let ids = context.sql.query_map(
|
||||
format!(
|
||||
@@ -1907,6 +1990,54 @@ pub fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: u32) -> Resul
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_info_json(context: &Context, chat_id: u32) -> Result<String, Error> {
|
||||
let chat = Chat::load_from_db(context, chat_id).unwrap();
|
||||
|
||||
// ToDo:
|
||||
// - [x] id
|
||||
// - [x] type
|
||||
// - [x] name
|
||||
// - [x] archived
|
||||
// - [x] color
|
||||
// - [x] profileImage
|
||||
// - [x] subtitle
|
||||
// - [x] draft,
|
||||
// - [ ] deaddrop,
|
||||
// - [ ] summary,
|
||||
// - [ ] lastUpdated,
|
||||
// - [ ] freshMessageCounter,
|
||||
// - [ ] email
|
||||
|
||||
let profile_image = match chat.get_profile_image(context) {
|
||||
Some(path) => path.into_os_string().into_string().unwrap(),
|
||||
None => "".to_string(),
|
||||
};
|
||||
|
||||
let draft = match get_draft(context, chat_id) {
|
||||
Ok(message) => match message {
|
||||
Some(m) => m.text.unwrap_or_else(|| "".to_string()),
|
||||
None => "".to_string(),
|
||||
},
|
||||
Err(_) => "".to_string(),
|
||||
};
|
||||
|
||||
let s = json!({
|
||||
"id": chat.id,
|
||||
"type": chat.typ as u32,
|
||||
"name": chat.name,
|
||||
"archived": chat.archived,
|
||||
"param": chat.param.to_string(),
|
||||
"gossiped_timestamp": chat.get_gossiped_timestamp(context),
|
||||
"is_sending_locations": chat.is_sending_locations,
|
||||
"color": chat.get_color(context),
|
||||
"profile_image": profile_image,
|
||||
"subtitle": chat.get_subtitle(context),
|
||||
"draft": draft
|
||||
});
|
||||
|
||||
Ok(s.to_string())
|
||||
}
|
||||
|
||||
pub fn get_chat_contact_cnt(context: &Context, chat_id: u32) -> usize {
|
||||
context
|
||||
.sql
|
||||
@@ -1951,6 +2082,9 @@ pub fn get_chat_id_by_grpid(context: &Context, grpid: impl AsRef<str>) -> (u32,
|
||||
.unwrap_or((0, false, Blocked::Not))
|
||||
}
|
||||
|
||||
/// Adds a message to device chat.
|
||||
///
|
||||
/// Optional `label` can be provided to ensure that message is added only once.
|
||||
pub fn add_device_msg(
|
||||
context: &Context,
|
||||
label: Option<&str>,
|
||||
@@ -1960,8 +2094,7 @@ pub fn add_device_msg(
|
||||
label.is_some() || msg.is_some(),
|
||||
"device-messages need label, msg or both"
|
||||
);
|
||||
let (chat_id, _blocked) =
|
||||
create_or_lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE, Blocked::Not)?;
|
||||
let mut chat_id = 0;
|
||||
let mut msg_id = MsgId::new_unset();
|
||||
|
||||
if let Some(label) = label {
|
||||
@@ -1972,7 +2105,10 @@ pub fn add_device_msg(
|
||||
}
|
||||
|
||||
if let Some(msg) = msg {
|
||||
chat_id = create_or_lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE, Blocked::Not)?.0;
|
||||
|
||||
let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device");
|
||||
msg.try_calc_and_set_dimensions(context).ok();
|
||||
prepare_msg_blob(context, msg)?;
|
||||
unarchive(context, chat_id)?;
|
||||
|
||||
@@ -2023,6 +2159,9 @@ pub fn was_device_msg_ever_added(context: &Context, label: &str) -> Result<bool,
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Adds an informational message to chat.
|
||||
///
|
||||
/// For example, it can be a message showing that a member was added to a group.
|
||||
pub fn add_info_msg(context: &Context, chat_id: u32, text: impl AsRef<str>) {
|
||||
let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device");
|
||||
|
||||
@@ -2200,6 +2339,7 @@ mod tests {
|
||||
let chat = Chat::load_from_db(&t.ctx, chat_id);
|
||||
assert!(chat.is_ok());
|
||||
let chat = chat.unwrap();
|
||||
assert_eq!(chat.get_type(), Chattype::Single);
|
||||
assert!(chat.is_device_talk());
|
||||
assert!(!chat.is_self_talk());
|
||||
assert!(!chat.can_send());
|
||||
@@ -2251,6 +2391,38 @@ mod tests {
|
||||
assert!(was_device_msg_ever_added(&t.ctx, "").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_device_chat() {
|
||||
let t = test_context(Some(Box::new(logging_cb)));
|
||||
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some("message text".to_string());
|
||||
add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).ok();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
|
||||
assert_eq!(chats.len(), 1);
|
||||
|
||||
// after the device-chat and all messages are deleted, a re-adding should do nothing
|
||||
delete(&t.ctx, chats.get_chat_id(0)).ok();
|
||||
add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).ok();
|
||||
assert_eq!(chatlist_len(&t.ctx, 0), 0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_device_chat_cannot_sent() {
|
||||
let t = test_context(Some(Box::new(logging_cb)));
|
||||
t.ctx.update_device_chats().unwrap();
|
||||
let (device_chat_id, _) =
|
||||
create_or_lookup_by_contact_id(&t.ctx, DC_CONTACT_ID_DEVICE, Blocked::Not).unwrap();
|
||||
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some("message text".to_string());
|
||||
assert!(send_msg(&t.ctx, device_chat_id, &mut msg).is_err());
|
||||
assert!(prepare_msg(&t.ctx, device_chat_id, &mut msg).is_err());
|
||||
|
||||
let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)).unwrap();
|
||||
assert!(forward_msgs(&t.ctx, &[msg_id], device_chat_id).is_err());
|
||||
}
|
||||
|
||||
fn chatlist_len(ctx: &Context, listflags: usize) -> usize {
|
||||
Chatlist::try_load(ctx, listflags, None, None)
|
||||
.unwrap()
|
||||
@@ -2306,4 +2478,58 @@ mod tests {
|
||||
assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 1);
|
||||
assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_chat_name() {
|
||||
let t = dummy_context();
|
||||
let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap();
|
||||
assert_eq!(
|
||||
Chat::load_from_db(&t.ctx, chat_id).unwrap().get_name(),
|
||||
"foo"
|
||||
);
|
||||
|
||||
set_chat_name(&t.ctx, chat_id, "bar").unwrap();
|
||||
assert_eq!(
|
||||
Chat::load_from_db(&t.ctx, chat_id).unwrap().get_name(),
|
||||
"bar"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_same_chat_twice() {
|
||||
let context = dummy_context();
|
||||
let contact1 = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
|
||||
assert_ne!(contact1, 0);
|
||||
|
||||
let chat_id = create_by_contact_id(&context.ctx, contact1).unwrap();
|
||||
assert!(
|
||||
chat_id > DC_CHAT_ID_LAST_SPECIAL,
|
||||
"chat_id too small {}",
|
||||
chat_id
|
||||
);
|
||||
let chat = Chat::load_from_db(&context.ctx, chat_id).unwrap();
|
||||
|
||||
let chat2_id = create_by_contact_id(&context.ctx, contact1).unwrap();
|
||||
assert_eq!(chat2_id, chat_id);
|
||||
let chat2 = Chat::load_from_db(&context.ctx, chat2_id).unwrap();
|
||||
|
||||
assert_eq!(chat2.name, chat.name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shall_attach_selfavatar() {
|
||||
let t = dummy_context();
|
||||
let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap();
|
||||
assert!(!shall_attach_selfavatar(&t.ctx, chat_id).unwrap());
|
||||
|
||||
let (contact_id, _) =
|
||||
Contact::add_or_lookup(&t.ctx, "", "foo@bar.org", Origin::IncomingUnknownTo).unwrap();
|
||||
add_contact_to_chat(&t.ctx, chat_id, contact_id);
|
||||
assert!(!shall_attach_selfavatar(&t.ctx, chat_id).unwrap());
|
||||
t.ctx.set_config(Config::Selfavatar, None).unwrap(); // setting to None also forces re-sending
|
||||
assert!(shall_attach_selfavatar(&t.ctx, chat_id).unwrap());
|
||||
|
||||
assert!(set_selfavatar_timestamp(&t.ctx, chat_id, time()).is_ok());
|
||||
assert!(!shall_attach_selfavatar(&t.ctx, chat_id).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
189
src/chatlist.rs
@@ -6,7 +6,7 @@ use crate::contact::*;
|
||||
use crate::context::*;
|
||||
use crate::error::Result;
|
||||
use crate::lot::Lot;
|
||||
use crate::message::{Message, MsgId};
|
||||
use crate::message::{Message, MessageState, MsgId};
|
||||
use crate::stock::StockMessage;
|
||||
|
||||
/// An object representing a single chatlist in memory.
|
||||
@@ -60,7 +60,7 @@ impl Chatlist {
|
||||
/// or "Not now".
|
||||
/// The UI can also offer a "Close" button that calls dc_marknoticed_contact() then.
|
||||
/// - DC_CHAT_ID_ARCHIVED_LINK (6) - this special chat is present if the user has
|
||||
/// archived _any_ chat using dc_archive_chat(). The UI should show a link as
|
||||
/// archived *any* chat using dc_archive_chat(). The UI should show a link as
|
||||
/// "Show archived chats", if the user clicks this item, the UI should show a
|
||||
/// list of all archived chats that can be created by this function hen using
|
||||
/// the DC_GCL_ARCHIVED_ONLY flag.
|
||||
@@ -71,7 +71,7 @@ impl Chatlist {
|
||||
/// The `listflags` is a combination of flags:
|
||||
/// - if the flag DC_GCL_ARCHIVED_ONLY is set, only archived chats are returned.
|
||||
/// if DC_GCL_ARCHIVED_ONLY is not set, only unarchived chats are returned and
|
||||
/// the pseudo-chat DC_CHAT_ID_ARCHIVED_LINK is added if there are _any_ archived
|
||||
/// the pseudo-chat DC_CHAT_ID_ARCHIVED_LINK is added if there are *any* archived
|
||||
/// chats
|
||||
/// - if the flag DC_GCL_NO_SPECIALS is set, deaddrop and archive link are not added
|
||||
/// to the list (may be used eg. for selecting chats on forwarding, the flag is
|
||||
@@ -119,46 +119,42 @@ impl Chatlist {
|
||||
let mut ids = if let Some(query_contact_id) = query_contact_id {
|
||||
// show chats shared with a given contact
|
||||
context.sql.query_map(
|
||||
concat!(
|
||||
"SELECT c.id, m.id",
|
||||
" FROM chats c",
|
||||
" LEFT JOIN msgs m",
|
||||
" ON c.id=m.chat_id",
|
||||
" AND m.timestamp=(",
|
||||
" SELECT MAX(timestamp)",
|
||||
" FROM msgs",
|
||||
" WHERE chat_id=c.id",
|
||||
" AND hidden=0)",
|
||||
" WHERE c.id>9",
|
||||
" AND c.blocked=0",
|
||||
" AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
|
||||
" GROUP BY c.id",
|
||||
" ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;"
|
||||
),
|
||||
params![query_contact_id as i32],
|
||||
"SELECT c.id, m.id
|
||||
FROM chats c
|
||||
LEFT JOIN msgs m
|
||||
ON c.id=m.chat_id
|
||||
AND m.timestamp=(
|
||||
SELECT MAX(timestamp)
|
||||
FROM msgs
|
||||
WHERE chat_id=c.id
|
||||
AND (hidden=0 OR state=?))
|
||||
WHERE c.id>9
|
||||
AND c.blocked=0
|
||||
AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)
|
||||
GROUP BY c.id
|
||||
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
||||
params![MessageState::OutDraft, query_contact_id as i32],
|
||||
process_row,
|
||||
process_rows,
|
||||
)?
|
||||
} else if 0 != listflags & DC_GCL_ARCHIVED_ONLY {
|
||||
// show archived chats
|
||||
context.sql.query_map(
|
||||
concat!(
|
||||
"SELECT c.id, m.id",
|
||||
" FROM chats c",
|
||||
" LEFT JOIN msgs m",
|
||||
" ON c.id=m.chat_id",
|
||||
" AND m.timestamp=(",
|
||||
" SELECT MAX(timestamp)",
|
||||
" FROM msgs",
|
||||
" WHERE chat_id=c.id",
|
||||
" AND hidden=0)",
|
||||
" WHERE c.id>9",
|
||||
" AND c.blocked=0",
|
||||
" AND c.archived=1",
|
||||
" GROUP BY c.id",
|
||||
" ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;"
|
||||
),
|
||||
params![],
|
||||
"SELECT c.id, m.id
|
||||
FROM chats c
|
||||
LEFT JOIN msgs m
|
||||
ON c.id=m.chat_id
|
||||
AND m.timestamp=(
|
||||
SELECT MAX(timestamp)
|
||||
FROM msgs
|
||||
WHERE chat_id=c.id
|
||||
AND (hidden=0 OR state=?))
|
||||
WHERE c.id>9
|
||||
AND c.blocked=0
|
||||
AND c.archived=1
|
||||
GROUP BY c.id
|
||||
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
||||
params![MessageState::OutDraft],
|
||||
process_row,
|
||||
process_rows,
|
||||
)?
|
||||
@@ -168,46 +164,42 @@ impl Chatlist {
|
||||
|
||||
let str_like_cmd = format!("%{}%", query);
|
||||
context.sql.query_map(
|
||||
concat!(
|
||||
"SELECT c.id, m.id",
|
||||
" FROM chats c",
|
||||
" LEFT JOIN msgs m",
|
||||
" ON c.id=m.chat_id",
|
||||
" AND m.timestamp=(",
|
||||
" SELECT MAX(timestamp)",
|
||||
" FROM msgs",
|
||||
" WHERE chat_id=c.id",
|
||||
" AND hidden=0)",
|
||||
" WHERE c.id>9",
|
||||
" AND c.blocked=0",
|
||||
" AND c.name LIKE ?",
|
||||
" GROUP BY c.id",
|
||||
" ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;"
|
||||
),
|
||||
params![str_like_cmd],
|
||||
"SELECT c.id, m.id
|
||||
FROM chats c
|
||||
LEFT JOIN msgs m
|
||||
ON c.id=m.chat_id
|
||||
AND m.timestamp=(
|
||||
SELECT MAX(timestamp)
|
||||
FROM msgs
|
||||
WHERE chat_id=c.id
|
||||
AND (hidden=0 OR state=?))
|
||||
WHERE c.id>9
|
||||
AND c.blocked=0
|
||||
AND c.name LIKE ?
|
||||
GROUP BY c.id
|
||||
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
||||
params![MessageState::OutDraft, str_like_cmd],
|
||||
process_row,
|
||||
process_rows,
|
||||
)?
|
||||
} else {
|
||||
// show normal chatlist
|
||||
let mut ids = context.sql.query_map(
|
||||
concat!(
|
||||
"SELECT c.id, m.id",
|
||||
" FROM chats c",
|
||||
" LEFT JOIN msgs m",
|
||||
" ON c.id=m.chat_id",
|
||||
" AND m.timestamp=(",
|
||||
" SELECT MAX(timestamp)",
|
||||
" FROM msgs",
|
||||
" WHERE chat_id=c.id",
|
||||
" AND hidden=0)",
|
||||
" WHERE c.id>9",
|
||||
" AND c.blocked=0",
|
||||
" AND c.archived=0",
|
||||
" GROUP BY c.id",
|
||||
" ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;"
|
||||
),
|
||||
params![],
|
||||
"SELECT c.id, m.id
|
||||
FROM chats c
|
||||
LEFT JOIN msgs m
|
||||
ON c.id=m.chat_id
|
||||
AND m.timestamp=(
|
||||
SELECT MAX(timestamp)
|
||||
FROM msgs
|
||||
WHERE chat_id=c.id
|
||||
AND (hidden=0 OR state=?))
|
||||
WHERE c.id>9
|
||||
AND c.blocked=0
|
||||
AND c.archived=0
|
||||
GROUP BY c.id
|
||||
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
||||
params![MessageState::OutDraft],
|
||||
process_row,
|
||||
process_rows,
|
||||
)?;
|
||||
@@ -297,7 +289,7 @@ impl Chatlist {
|
||||
let lastmsg_id = self.ids[index].1;
|
||||
let mut lastcontact = None;
|
||||
|
||||
let mut lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
|
||||
let lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
|
||||
if lastmsg.from_id != DC_CONTACT_ID_SELF
|
||||
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
|
||||
{
|
||||
@@ -309,16 +301,6 @@ impl Chatlist {
|
||||
None
|
||||
};
|
||||
|
||||
if let Ok(draft) = get_draft(context, chat.id) {
|
||||
if draft.is_some()
|
||||
&& (lastmsg.is_none()
|
||||
|| draft.as_ref().unwrap().timestamp_sort
|
||||
> lastmsg.as_ref().unwrap().timestamp_sort)
|
||||
{
|
||||
lastmsg = draft;
|
||||
}
|
||||
}
|
||||
|
||||
if chat.id == DC_CHAT_ID_ARCHIVED_LINK {
|
||||
ret.text2 = None;
|
||||
} else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED
|
||||
@@ -332,7 +314,7 @@ impl Chatlist {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the number of archived chats
|
||||
/// Returns the number of archived chats
|
||||
pub fn dc_get_archived_cnt(context: &Context) -> u32 {
|
||||
context
|
||||
.sql
|
||||
@@ -362,3 +344,44 @@ fn get_last_deaddrop_fresh_msg(context: &Context) -> Option<MsgId> {
|
||||
params![],
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::chat;
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn test_try_load() {
|
||||
let t = dummy_context();
|
||||
let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat").unwrap();
|
||||
let chat_id2 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "b chat").unwrap();
|
||||
let chat_id3 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "c chat").unwrap();
|
||||
|
||||
// check that the chatlist starts with the most recent message
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
|
||||
assert_eq!(chats.len(), 3);
|
||||
assert_eq!(chats.get_chat_id(0), chat_id3);
|
||||
assert_eq!(chats.get_chat_id(1), chat_id2);
|
||||
assert_eq!(chats.get_chat_id(2), chat_id1);
|
||||
|
||||
// drafts are sorted to the top
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.set_text(Some("hello".to_string()));
|
||||
set_draft(&t.ctx, chat_id2, Some(&mut msg));
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
|
||||
assert_eq!(chats.get_chat_id(0), chat_id2);
|
||||
|
||||
// check chatlist query and archive functionality
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, Some("b"), None).unwrap();
|
||||
assert_eq!(chats.len(), 1);
|
||||
|
||||
let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap();
|
||||
assert_eq!(chats.len(), 0);
|
||||
|
||||
chat::archive(&t.ctx, chat_id1, true).ok();
|
||||
let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap();
|
||||
assert_eq!(chats.len(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
use strum::{EnumProperty, IntoEnumIterator};
|
||||
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
|
||||
|
||||
use crate::blob::BlobObject;
|
||||
use crate::constants::DC_VERSION_STR;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Error;
|
||||
use crate::job::*;
|
||||
use crate::stock::StockMessage;
|
||||
use rusqlite::NO_PARAMS;
|
||||
|
||||
/// The available configuration keys.
|
||||
#[derive(
|
||||
@@ -28,27 +29,38 @@ pub enum Config {
|
||||
SendPort,
|
||||
SmtpCertificateChecks,
|
||||
ServerFlags,
|
||||
|
||||
#[strum(props(default = "INBOX"))]
|
||||
ImapFolder,
|
||||
|
||||
Displayname,
|
||||
Selfstatus,
|
||||
Selfavatar,
|
||||
|
||||
#[strum(props(default = "0"))]
|
||||
BccSelf,
|
||||
|
||||
#[strum(props(default = "1"))]
|
||||
E2eeEnabled,
|
||||
|
||||
#[strum(props(default = "1"))]
|
||||
MdnsEnabled,
|
||||
|
||||
#[strum(props(default = "1"))]
|
||||
InboxWatch,
|
||||
|
||||
#[strum(props(default = "1"))]
|
||||
SentboxWatch,
|
||||
|
||||
#[strum(props(default = "1"))]
|
||||
MvboxWatch,
|
||||
|
||||
#[strum(props(default = "1"))]
|
||||
MvboxMove,
|
||||
|
||||
#[strum(props(default = "0"))] // also change ShowEmails.default() on changes
|
||||
ShowEmails,
|
||||
|
||||
SaveMimeHeaders,
|
||||
ConfiguredAddr,
|
||||
ConfiguredMailServer,
|
||||
@@ -66,11 +78,13 @@ pub enum Config {
|
||||
ConfiguredSendSecurity,
|
||||
ConfiguredE2EEEnabled,
|
||||
Configured,
|
||||
// Deprecated
|
||||
|
||||
#[strum(serialize = "sys.version")]
|
||||
SysVersion,
|
||||
|
||||
#[strum(serialize = "sys.msgsize_max_recommended")]
|
||||
SysMsgsizeMaxRecommended,
|
||||
|
||||
#[strum(serialize = "sys.config_keys")]
|
||||
SysConfigKeys,
|
||||
}
|
||||
@@ -112,16 +126,25 @@ impl Context {
|
||||
|
||||
/// Set the given config key.
|
||||
/// If `None` is passed as a value the value is cleared and set to the default if there is one.
|
||||
pub fn set_config(&self, key: Config, value: Option<&str>) -> Result<(), Error> {
|
||||
pub fn set_config(&self, key: Config, value: Option<&str>) -> crate::sql::Result<()> {
|
||||
match key {
|
||||
Config::Selfavatar if value.is_some() => {
|
||||
let rel_path = std::fs::canonicalize(value.unwrap())?;
|
||||
Config::Selfavatar => {
|
||||
self.sql
|
||||
.set_raw_config(self, key, Some(&rel_path.to_string_lossy()))
|
||||
.execute("UPDATE contacts SET selfavatar_sent=0;", NO_PARAMS)?;
|
||||
self.sql
|
||||
.set_raw_config_bool(self, "attach_selfavatar", true)?;
|
||||
match value {
|
||||
Some(value) => {
|
||||
let blob = BlobObject::new_from_path(&self, value)?;
|
||||
blob.recode_to_avatar_size(self)?;
|
||||
self.sql.set_raw_config(self, key, Some(blob.as_name()))
|
||||
}
|
||||
None => self.sql.set_raw_config(self, key, None),
|
||||
}
|
||||
}
|
||||
Config::InboxWatch => {
|
||||
let ret = self.sql.set_raw_config(self, key, value);
|
||||
interrupt_inbox_idle(self, true);
|
||||
interrupt_inbox_idle(self);
|
||||
ret
|
||||
}
|
||||
Config::SentboxWatch => {
|
||||
@@ -167,6 +190,10 @@ mod tests {
|
||||
use std::str::FromStr;
|
||||
use std::string::ToString;
|
||||
|
||||
use crate::test_utils::*;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
#[test]
|
||||
fn test_to_string() {
|
||||
assert_eq!(Config::MailServer.to_string(), "mail_server");
|
||||
@@ -183,4 +210,34 @@ mod tests {
|
||||
fn test_default_prop() {
|
||||
assert_eq!(Config::ImapFolder.get_str("default"), Some("INBOX"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_selfavatar_outside_blobdir() -> failure::Fallible<()> {
|
||||
let t = dummy_context();
|
||||
let avatar_src = t.dir.path().join("avatar.jpg");
|
||||
let avatar_bytes = include_bytes!("../test-data/image/avatar1000x1000.jpg");
|
||||
File::create(&avatar_src)?.write_all(avatar_bytes)?;
|
||||
let avatar_blob = t.ctx.get_blobdir().join("avatar.jpg");
|
||||
assert!(!avatar_blob.exists());
|
||||
t.ctx
|
||||
.set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap()))?;
|
||||
assert!(avatar_blob.exists());
|
||||
assert!(std::fs::metadata(&avatar_blob).unwrap().len() < avatar_bytes.len() as u64);
|
||||
let avatar_cfg = t.ctx.get_config(Config::Selfavatar);
|
||||
assert_eq!(avatar_cfg, avatar_blob.to_str().map(|s| s.to_string()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_selfavatar_in_blobdir() -> failure::Fallible<()> {
|
||||
let t = dummy_context();
|
||||
let avatar_src = t.ctx.get_blobdir().join("avatar.jpg");
|
||||
let avatar_bytes = include_bytes!("../test-data/image/avatar1000x1000.jpg");
|
||||
File::create(&avatar_src)?.write_all(avatar_bytes)?;
|
||||
t.ctx
|
||||
.set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap()))?;
|
||||
let avatar_cfg = t.ctx.get_config(Config::Selfavatar);
|
||||
assert_eq!(avatar_cfg, avatar_src.to_str().map(|s| s.to_string()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,43 @@
|
||||
//! Thunderbird's Autoconfiguration implementation
|
||||
//! # Thunderbird's Autoconfiguration implementation
|
||||
//!
|
||||
//! Documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
|
||||
use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
use crate::login_param::LoginParam;
|
||||
|
||||
use super::read_autoconf_file;
|
||||
/* ******************************************************************************
|
||||
* Thunderbird's Autoconfigure
|
||||
******************************************************************************/
|
||||
/* documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
|
||||
use super::read_url::read_url;
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Error {
|
||||
#[fail(display = "Invalid email address: {:?}", _0)]
|
||||
InvalidEmailAddress(String),
|
||||
|
||||
#[fail(display = "XML error at position {}", position)]
|
||||
InvalidXml {
|
||||
position: usize,
|
||||
#[cause]
|
||||
error: quick_xml::Error,
|
||||
},
|
||||
|
||||
#[fail(display = "Bad or incomplete autoconfig")]
|
||||
IncompleteAutoconfig(LoginParam),
|
||||
|
||||
#[fail(display = "Failed to get URL {}", _0)]
|
||||
ReadUrlError(#[cause] super::read_url::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl From<super::read_url::Error> for Error {
|
||||
fn from(err: super::read_url::Error) -> Error {
|
||||
Error::ReadUrlError(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MozAutoconfigure<'a> {
|
||||
pub in_emailaddr: &'a str,
|
||||
pub in_emaildomain: &'a str,
|
||||
@@ -23,13 +49,14 @@ struct MozAutoconfigure<'a> {
|
||||
pub tag_config: MozConfigTag,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum MozServer {
|
||||
Undefined,
|
||||
Imap,
|
||||
Smtp,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum MozConfigTag {
|
||||
Undefined,
|
||||
Hostname,
|
||||
@@ -38,15 +65,14 @@ enum MozConfigTag {
|
||||
Username,
|
||||
}
|
||||
|
||||
pub fn moz_parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Error> {
|
||||
fn parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam> {
|
||||
let mut reader = quick_xml::Reader::from_str(xml_raw);
|
||||
reader.trim_text(true);
|
||||
|
||||
// Split address into local part and domain part.
|
||||
let p = match in_emailaddr.find('@') {
|
||||
Some(i) => i,
|
||||
None => bail!("Email address {} does not contain @", in_emailaddr),
|
||||
};
|
||||
let p = in_emailaddr
|
||||
.find('@')
|
||||
.ok_or_else(|| Error::InvalidEmailAddress(in_emailaddr.to_string()))?;
|
||||
let (in_emaillocalpart, in_emaildomain) = in_emailaddr.split_at(p);
|
||||
let in_emaildomain = &in_emaildomain[1..];
|
||||
|
||||
@@ -63,22 +89,22 @@ pub fn moz_parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Er
|
||||
|
||||
let mut buf = Vec::new();
|
||||
loop {
|
||||
match reader.read_event(&mut buf) {
|
||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||
let event = reader
|
||||
.read_event(&mut buf)
|
||||
.map_err(|error| Error::InvalidXml {
|
||||
position: reader.buffer_position(),
|
||||
error,
|
||||
})?;
|
||||
|
||||
match event {
|
||||
quick_xml::events::Event::Start(ref e) => {
|
||||
moz_autoconfigure_starttag_cb(e, &mut moz_ac, &reader)
|
||||
}
|
||||
Ok(quick_xml::events::Event::End(ref e)) => moz_autoconfigure_endtag_cb(e, &mut moz_ac),
|
||||
Ok(quick_xml::events::Event::Text(ref e)) => {
|
||||
quick_xml::events::Event::End(ref e) => moz_autoconfigure_endtag_cb(e, &mut moz_ac),
|
||||
quick_xml::events::Event::Text(ref e) => {
|
||||
moz_autoconfigure_text_cb(e, &mut moz_ac, &reader)
|
||||
}
|
||||
Err(e) => {
|
||||
bail!(
|
||||
"Configure xml: Error at position {}: {:?}",
|
||||
reader.buffer_position(),
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(quick_xml::events::Event::Eof) => break,
|
||||
quick_xml::events::Event::Eof => break,
|
||||
_ => (),
|
||||
}
|
||||
buf.clear();
|
||||
@@ -89,27 +115,27 @@ pub fn moz_parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Er
|
||||
|| moz_ac.out.send_server.is_empty()
|
||||
|| moz_ac.out.send_port == 0
|
||||
{
|
||||
let r = moz_ac.out.to_string();
|
||||
bail!("Bad or incomplete autoconfig: {}", r,);
|
||||
Err(Error::IncompleteAutoconfig(moz_ac.out))
|
||||
} else {
|
||||
Ok(moz_ac.out)
|
||||
}
|
||||
|
||||
Ok(moz_ac.out)
|
||||
}
|
||||
|
||||
pub fn moz_autoconfigure(
|
||||
context: &Context,
|
||||
url: &str,
|
||||
param_in: &LoginParam,
|
||||
) -> Option<LoginParam> {
|
||||
let xml_raw = read_autoconf_file(context, url)?;
|
||||
) -> Result<LoginParam> {
|
||||
let xml_raw = read_url(context, url)?;
|
||||
|
||||
match moz_parse_xml(¶m_in.addr, &xml_raw) {
|
||||
Err(err) => {
|
||||
warn!(context, "{}", err);
|
||||
None
|
||||
}
|
||||
Ok(lp) => Some(lp),
|
||||
let res = parse_xml(¶m_in.addr, &xml_raw);
|
||||
if let Err(err) = &res {
|
||||
warn!(
|
||||
context,
|
||||
"Failed to parse Thunderbird autoconfiguration XML: {}", err
|
||||
);
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
||||
@@ -314,7 +340,7 @@ mod tests {
|
||||
</loginPageInfo>
|
||||
</webMail>
|
||||
</clientConfig>";
|
||||
let res = moz_parse_xml("example@outlook.com", xml_raw).expect("XML parsing failed");
|
||||
let res = parse_xml("example@outlook.com", xml_raw).expect("XML parsing failed");
|
||||
assert_eq!(res.mail_server, "outlook.office365.com");
|
||||
assert_eq!(res.mail_port, 993);
|
||||
assert_eq!(res.send_server, "smtp.office365.com");
|
||||
|
||||
@@ -1,14 +1,41 @@
|
||||
//! Outlook's Autodiscover
|
||||
|
||||
use quick_xml;
|
||||
use quick_xml::events::BytesEnd;
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
use crate::login_param::LoginParam;
|
||||
|
||||
use super::read_autoconf_file;
|
||||
use super::read_url::read_url;
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Error {
|
||||
#[fail(display = "XML error at position {}", position)]
|
||||
InvalidXml {
|
||||
position: usize,
|
||||
#[cause]
|
||||
error: quick_xml::Error,
|
||||
},
|
||||
|
||||
#[fail(display = "Bad or incomplete autoconfig")]
|
||||
IncompleteAutoconfig(LoginParam),
|
||||
|
||||
#[fail(display = "Failed to get URL {}", _0)]
|
||||
ReadUrlError(#[cause] super::read_url::Error),
|
||||
|
||||
#[fail(display = "Number of redirection is exceeded")]
|
||||
RedirectionError,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl From<super::read_url::Error> for Error {
|
||||
fn from(err: super::read_url::Error) -> Error {
|
||||
Error::ReadUrlError(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Outlook's Autodiscover
|
||||
struct OutlookAutodiscover {
|
||||
pub out: LoginParam,
|
||||
pub out_imap_set: bool,
|
||||
@@ -25,7 +52,7 @@ enum ParsingResult {
|
||||
RedirectUrl(String),
|
||||
}
|
||||
|
||||
fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
|
||||
fn parse_xml(xml_raw: &str) -> Result<ParsingResult> {
|
||||
let mut outlk_ad = OutlookAutodiscover {
|
||||
out: LoginParam::new(),
|
||||
out_imap_set: false,
|
||||
@@ -45,8 +72,15 @@ fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
|
||||
let mut current_tag: Option<String> = None;
|
||||
|
||||
loop {
|
||||
match reader.read_event(&mut buf) {
|
||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||
let event = reader
|
||||
.read_event(&mut buf)
|
||||
.map_err(|error| Error::InvalidXml {
|
||||
position: reader.buffer_position(),
|
||||
error,
|
||||
})?;
|
||||
|
||||
match event {
|
||||
quick_xml::events::Event::Start(ref e) => {
|
||||
let tag = String::from_utf8_lossy(e.name()).trim().to_lowercase();
|
||||
|
||||
if tag == "protocol" {
|
||||
@@ -61,11 +95,11 @@ fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
|
||||
current_tag = Some(tag);
|
||||
}
|
||||
}
|
||||
Ok(quick_xml::events::Event::End(ref e)) => {
|
||||
quick_xml::events::Event::End(ref e) => {
|
||||
outlk_autodiscover_endtag_cb(e, &mut outlk_ad);
|
||||
current_tag = None;
|
||||
}
|
||||
Ok(quick_xml::events::Event::Text(ref e)) => {
|
||||
quick_xml::events::Event::Text(ref e) => {
|
||||
let val = e.unescape_and_decode(&reader).unwrap_or_default();
|
||||
|
||||
if let Some(ref tag) = current_tag {
|
||||
@@ -81,21 +115,14 @@ fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
|
||||
};
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
bail!(
|
||||
"Configure xml: Error at position {}: {:?}",
|
||||
reader.buffer_position(),
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(quick_xml::events::Event::Eof) => break,
|
||||
quick_xml::events::Event::Eof => break,
|
||||
_ => (),
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
// XML redirect via redirecturl
|
||||
if outlk_ad.config_redirecturl.is_none()
|
||||
let res = if outlk_ad.config_redirecturl.is_none()
|
||||
|| outlk_ad.config_redirecturl.as_ref().unwrap().is_empty()
|
||||
{
|
||||
if outlk_ad.out.mail_server.is_empty()
|
||||
@@ -103,41 +130,34 @@ fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
|
||||
|| outlk_ad.out.send_server.is_empty()
|
||||
|| outlk_ad.out.send_port == 0
|
||||
{
|
||||
let r = outlk_ad.out.to_string();
|
||||
bail!("Bad or incomplete autoconfig: {}", r,);
|
||||
return Err(Error::IncompleteAutoconfig(outlk_ad.out));
|
||||
}
|
||||
Ok(ParsingResult::LoginParam(outlk_ad.out))
|
||||
ParsingResult::LoginParam(outlk_ad.out)
|
||||
} else {
|
||||
Ok(ParsingResult::RedirectUrl(
|
||||
outlk_ad.config_redirecturl.unwrap(),
|
||||
))
|
||||
}
|
||||
ParsingResult::RedirectUrl(outlk_ad.config_redirecturl.unwrap())
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn outlk_autodiscover(
|
||||
context: &Context,
|
||||
url: &str,
|
||||
_param_in: &LoginParam,
|
||||
) -> Option<LoginParam> {
|
||||
) -> Result<LoginParam> {
|
||||
let mut url = url.to_string();
|
||||
/* Follow up to 10 xml-redirects (http-redirects are followed in read_autoconf_file() */
|
||||
/* Follow up to 10 xml-redirects (http-redirects are followed in read_url() */
|
||||
for _i in 0..10 {
|
||||
if let Some(xml_raw) = read_autoconf_file(context, &url) {
|
||||
match outlk_parse_xml(&xml_raw) {
|
||||
Err(err) => {
|
||||
warn!(context, "{}", err);
|
||||
return None;
|
||||
}
|
||||
Ok(res) => match res {
|
||||
ParsingResult::RedirectUrl(redirect_url) => url = redirect_url,
|
||||
ParsingResult::LoginParam(login_param) => return Some(login_param),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
let xml_raw = read_url(context, &url)?;
|
||||
let res = parse_xml(&xml_raw);
|
||||
if let Err(err) = &res {
|
||||
warn!(context, "{}", err);
|
||||
}
|
||||
match res? {
|
||||
ParsingResult::RedirectUrl(redirect_url) => url = redirect_url,
|
||||
ParsingResult::LoginParam(login_param) => return Ok(login_param),
|
||||
}
|
||||
}
|
||||
None
|
||||
Err(Error::RedirectionError)
|
||||
}
|
||||
|
||||
fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut OutlookAutodiscover) {
|
||||
@@ -179,7 +199,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parse_redirect() {
|
||||
let res = outlk_parse_xml("
|
||||
let res = parse_xml("
|
||||
<?xml version=\"1.0\" encoding=\"utf-8\"?>
|
||||
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
|
||||
<Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">
|
||||
@@ -206,7 +226,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parse_loginparam() {
|
||||
let res = outlk_parse_xml(
|
||||
let res = parse_xml(
|
||||
"\
|
||||
<?xml version=\"1.0\" encoding=\"utf-8\"?>
|
||||
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
//! Email accounts autoconfiguration process module
|
||||
|
||||
mod auto_mozilla;
|
||||
mod auto_outlook;
|
||||
mod read_url;
|
||||
|
||||
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
||||
|
||||
use async_std::task;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::e2ee;
|
||||
use crate::job::*;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::login_param::{CertificateChecks, LoginParam};
|
||||
use crate::oauth2::*;
|
||||
use crate::param::Params;
|
||||
|
||||
mod auto_outlook;
|
||||
use auto_outlook::outlk_autodiscover;
|
||||
mod auto_mozilla;
|
||||
use auto_mozilla::moz_autoconfigure;
|
||||
use auto_outlook::outlk_autodiscover;
|
||||
|
||||
macro_rules! progress {
|
||||
($context:tt, $progress:expr) => {
|
||||
@@ -45,9 +49,8 @@ pub fn dc_is_configured(context: &Context) -> bool {
|
||||
/*******************************************************************************
|
||||
* 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 fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
pub fn JobConfigureImap(context: &Context) {
|
||||
if !context.sql.is_open() {
|
||||
error!(context, "Cannot configure, database not opened.",);
|
||||
progress!(context, 0);
|
||||
@@ -91,9 +94,11 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
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;
|
||||
let mut keep_flags = 0;
|
||||
|
||||
const STEP_12_USE_AUTOCONFIG: u8 = 12;
|
||||
const STEP_13_AFTER_AUTOCONFIG: u8 = 13;
|
||||
|
||||
const STEP_3_INDEX: u8 = 13;
|
||||
let mut step_counter: u8 = 0;
|
||||
while !context.shall_stop_ongoing() {
|
||||
step_counter += 1;
|
||||
@@ -109,7 +114,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
}
|
||||
// Step 1: Load the parameters and check email-address and password
|
||||
2 => {
|
||||
if 0 != param.server_flags & 0x2 {
|
||||
if 0 != param.server_flags & DC_LP_AUTH_OAUTH2 {
|
||||
// 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.
|
||||
@@ -144,6 +149,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
// 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 */
|
||||
@@ -151,12 +157,18 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
&& 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
|
||||
&& (param.server_flags & !DC_LP_AUTH_OAUTH2) == 0
|
||||
{
|
||||
keep_flags = param.server_flags & 0x2;
|
||||
// no advanced parameters entered by the user: query provider-database or do Autoconfig
|
||||
keep_flags = param.server_flags & DC_LP_AUTH_OAUTH2;
|
||||
if let Some(new_param) = get_offline_autoconfig(context, ¶m) {
|
||||
// got parameters from our provider-database, skip Autoconfig, preserve the OAuth2 setting
|
||||
param_autoconfig = Some(new_param);
|
||||
step_counter = STEP_12_USE_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop
|
||||
}
|
||||
} else {
|
||||
// Autoconfig is not needed so skip it.
|
||||
step_counter = STEP_3_INDEX - 1;
|
||||
// advanced parameters entered by the user: skip Autoconfig
|
||||
step_counter = STEP_13_AFTER_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -167,7 +179,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
"https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
|
||||
param_domain, param_addr_urlencoded
|
||||
);
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok();
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -179,7 +191,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
"https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}",
|
||||
param_domain, param_addr_urlencoded
|
||||
);
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok();
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -192,7 +204,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
"https://{}{}/autodiscover/autodiscover.xml",
|
||||
"", param_domain
|
||||
);
|
||||
param_autoconfig = outlk_autodiscover(context, &url, ¶m);
|
||||
param_autoconfig = outlk_autodiscover(context, &url, ¶m).ok();
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -203,7 +215,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
"https://{}{}/autodiscover/autodiscover.xml",
|
||||
"autodiscover.", param_domain
|
||||
);
|
||||
param_autoconfig = outlk_autodiscover(context, &url, ¶m);
|
||||
param_autoconfig = outlk_autodiscover(context, &url, ¶m).ok();
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -215,7 +227,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
"http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
|
||||
param_domain, param_addr_urlencoded
|
||||
);
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok();
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -227,7 +239,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
"http://{}/.well-known/autoconfig/mail/config-v1.1.xml",
|
||||
param_domain
|
||||
);
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok();
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -237,12 +249,14 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
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);
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok();
|
||||
}
|
||||
true
|
||||
}
|
||||
/* C. Do we have any result? */
|
||||
12 => {
|
||||
/* C. Do we have any autoconfig result?
|
||||
If you change the match-number here, also update STEP_12_COPY_AUTOCONFIG above
|
||||
*/
|
||||
STEP_12_USE_AUTOCONFIG => {
|
||||
progress!(context, 500);
|
||||
if let Some(ref cfg) = param_autoconfig {
|
||||
info!(context, "Got autoconfig: {}", &cfg);
|
||||
@@ -255,15 +269,15 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
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 */
|
||||
/* although param_autoconfig's data are no longer needed from,
|
||||
it is used to later to prevent trying variations of port/server/logins */
|
||||
}
|
||||
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 you change the match-number here, also update STEP_13_AFTER_AUTOCONFIG above
|
||||
STEP_13_AFTER_AUTOCONFIG => {
|
||||
if param.mail_server.is_empty() {
|
||||
param.mail_server = format!("imap.{}", param_domain,)
|
||||
}
|
||||
@@ -354,20 +368,21 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
}
|
||||
16 => {
|
||||
progress!(context, 900);
|
||||
let flags: libc::c_int = if context.get_config_bool(Config::MvboxWatch)
|
||||
|| context.get_config_bool(Config::MvboxMove)
|
||||
{
|
||||
DC_CREATE_MVBOX as i32
|
||||
let create_mvbox = context.get_config_bool(Config::MvboxWatch)
|
||||
|| context.get_config_bool(Config::MvboxMove);
|
||||
let imap = &context.inbox_thread.read().unwrap().imap;
|
||||
if let Err(err) = imap.ensure_configured_folders(context, create_mvbox) {
|
||||
warn!(context, "configuring folders failed: {:?}", err);
|
||||
false
|
||||
} else {
|
||||
0
|
||||
};
|
||||
context
|
||||
.inbox_thread
|
||||
.read()
|
||||
.unwrap()
|
||||
.imap
|
||||
.configure_folders(context, flags);
|
||||
true
|
||||
let res = imap.select_with_uidvalidity(context, "INBOX");
|
||||
if let Err(err) = res {
|
||||
error!(context, "could not read INBOX status: {:?}", err);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
17 => {
|
||||
progress!(context, 910);
|
||||
@@ -389,11 +404,10 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
// (~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.");
|
||||
info!(context, "key generation completed");
|
||||
progress!(context, 940);
|
||||
break; // We are done here
|
||||
}
|
||||
|
||||
_ => {
|
||||
error!(context, "Internal error: step counter out of bound",);
|
||||
break;
|
||||
@@ -416,24 +430,6 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
context.smtp.clone().lock().unwrap().disconnect();
|
||||
}
|
||||
|
||||
/*
|
||||
if !success {
|
||||
// disconnect if configure did not succeed
|
||||
if imap_connected_here {
|
||||
// context.inbox.read().unwrap().disconnect(context);
|
||||
}
|
||||
if smtp_connected_here {
|
||||
// context.smtp.clone().lock().unwrap().disconnect();
|
||||
}
|
||||
} else {
|
||||
assert!(imap_connected_here && smtp_connected_here);
|
||||
info!(
|
||||
context,
|
||||
0, "Keeping IMAP/SMTP connections open after successful configuration"
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
// remember the entered parameters on success
|
||||
// and restore to last-entered on failure.
|
||||
// this way, the parameters visible to the ui are always in-sync with the current configuration.
|
||||
@@ -447,6 +443,42 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
progress!(context, if success { 1000 } else { 0 });
|
||||
}
|
||||
|
||||
fn get_offline_autoconfig(context: &Context, param: &LoginParam) -> Option<LoginParam> {
|
||||
// XXX we don't have https://github.com/deltachat/provider-db APIs
|
||||
// integrated yet but we'll already add nauta as a first use case, also
|
||||
// showing what we need from provider-db in the future.
|
||||
info!(
|
||||
context,
|
||||
"checking internal provider-info for offline autoconfig"
|
||||
);
|
||||
|
||||
if param.addr.ends_with("@nauta.cu") {
|
||||
let mut p = LoginParam::new();
|
||||
|
||||
p.addr = param.addr.clone();
|
||||
p.mail_server = "imap.nauta.cu".to_string();
|
||||
p.mail_user = param.addr.clone();
|
||||
p.mail_pw = param.mail_pw.clone();
|
||||
p.mail_port = 143;
|
||||
p.imap_certificate_checks = CertificateChecks::AcceptInvalidCertificates;
|
||||
|
||||
p.send_server = "smtp.nauta.cu".to_string();
|
||||
p.send_user = param.addr.clone();
|
||||
p.send_pw = param.mail_pw.clone();
|
||||
p.send_port = 25;
|
||||
p.smtp_certificate_checks = CertificateChecks::AcceptInvalidCertificates;
|
||||
p.server_flags = DC_LP_AUTH_NORMAL as i32
|
||||
| DC_LP_IMAP_SOCKET_STARTTLS as i32
|
||||
| DC_LP_SMTP_SOCKET_STARTTLS as i32;
|
||||
|
||||
info!(context, "found offline autoconfig: {}", p);
|
||||
Some(p)
|
||||
} else {
|
||||
info!(context, "no offline autoconfig found");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn try_imap_connections(
|
||||
context: &Context,
|
||||
mut param: &mut LoginParam,
|
||||
@@ -502,17 +534,22 @@ fn try_imap_connection(
|
||||
|
||||
fn try_imap_one_param(context: &Context, param: &LoginParam) -> Option<bool> {
|
||||
let inf = format!(
|
||||
"imap: {}@{}:{} flags=0x{:x}",
|
||||
param.mail_user, param.mail_server, param.mail_port, param.server_flags
|
||||
"imap: {}@{}:{} flags=0x{:x} certificate_checks={}",
|
||||
param.mail_user,
|
||||
param.mail_server,
|
||||
param.mail_port,
|
||||
param.server_flags,
|
||||
param.imap_certificate_checks
|
||||
);
|
||||
info!(context, "Trying: {}", inf);
|
||||
if context
|
||||
.inbox_thread
|
||||
.read()
|
||||
.unwrap()
|
||||
.imap
|
||||
.connect(context, ¶m)
|
||||
{
|
||||
if task::block_on(
|
||||
context
|
||||
.inbox_thread
|
||||
.read()
|
||||
.unwrap()
|
||||
.imap
|
||||
.connect(context, ¶m),
|
||||
) {
|
||||
info!(context, "success: {}", inf);
|
||||
return Some(true);
|
||||
}
|
||||
@@ -581,32 +618,12 @@ fn try_smtp_one_param(context: &Context, param: &LoginParam) -> Option<bool> {
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Configure a Context
|
||||
******************************************************************************/
|
||||
|
||||
pub fn read_autoconf_file(context: &Context, url: &str) -> Option<String> {
|
||||
info!(context, "Testing {} ...", url);
|
||||
|
||||
match reqwest::Client::new()
|
||||
.get(url)
|
||||
.send()
|
||||
.and_then(|mut res| res.text())
|
||||
{
|
||||
Ok(res) => Some(res),
|
||||
Err(_err) => {
|
||||
info!(context, "Can\'t read file.",);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::config::*;
|
||||
use crate::configure::dc_job_do_DC_JOB_CONFIGURE_IMAP;
|
||||
use crate::configure::JobConfigureImap;
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
@@ -616,6 +633,21 @@ mod tests {
|
||||
.set_config(Config::Addr, Some("probably@unexistant.addr"))
|
||||
.unwrap();
|
||||
t.ctx.set_config(Config::MailPw, Some("123456")).unwrap();
|
||||
dc_job_do_DC_JOB_CONFIGURE_IMAP(&t.ctx);
|
||||
JobConfigureImap(&t.ctx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_offline_autoconfig() {
|
||||
let context = dummy_context().ctx;
|
||||
|
||||
let mut params = LoginParam::new();
|
||||
params.addr = "someone123@example.org".to_string();
|
||||
assert!(get_offline_autoconfig(&context, ¶ms).is_none());
|
||||
|
||||
let mut params = LoginParam::new();
|
||||
params.addr = "someone123@nauta.cu".to_string();
|
||||
let found_params = get_offline_autoconfig(&context, ¶ms).unwrap();
|
||||
assert_eq!(found_params.mail_server, "imap.nauta.cu".to_string());
|
||||
assert_eq!(found_params.send_server, "smtp.nauta.cu".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
26
src/configure/read_url.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use crate::context::Context;
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Error {
|
||||
#[fail(display = "URL request error")]
|
||||
GetError(#[cause] reqwest::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
pub fn read_url(context: &Context, url: &str) -> Result<String> {
|
||||
info!(context, "Requesting URL {}", url);
|
||||
|
||||
match reqwest::Client::new()
|
||||
.get(url)
|
||||
.send()
|
||||
.and_then(|mut res| res.text())
|
||||
{
|
||||
Ok(res) => Ok(res),
|
||||
Err(err) => {
|
||||
info!(context, "Can\'t read URL {}", url);
|
||||
|
||||
Err(Error::GetError(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,21 +8,6 @@ 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;
|
||||
const DC_INBOX_WATCH_DEFAULT: i32 = 1;
|
||||
@@ -73,6 +58,9 @@ pub const DC_GCM_ADDDAYMARKER: u32 = 0x01;
|
||||
pub const DC_GCL_VERIFIED_ONLY: usize = 0x01;
|
||||
pub const DC_GCL_ADD_SELF: usize = 0x02;
|
||||
|
||||
// unchanged user avatars are resent to the recipients every some days
|
||||
pub const DC_RESEND_USER_AVATAR_DAYS: i64 = 14;
|
||||
|
||||
// values for DC_PARAM_FORCE_PLAINTEXT
|
||||
pub(crate) const DC_FP_NO_AUTOCRYPT_HEADER: i32 = 2;
|
||||
pub(crate) const DC_FP_ADD_AUTOCRYPT_HEADER: i32 = 1;
|
||||
@@ -126,7 +114,7 @@ pub const DC_MSG_ID_LAST_SPECIAL: u32 = 9;
|
||||
/// approx. max. length returned by dc_msg_get_text()
|
||||
const DC_MAX_GET_TEXT_LEN: usize = 30000;
|
||||
/// approx. max. length returned by dc_get_msg_info()
|
||||
const DC_MAX_GET_INFO_LEN: usize = 100000;
|
||||
const DC_MAX_GET_INFO_LEN: usize = 100_000;
|
||||
|
||||
pub const DC_CONTACT_ID_UNDEFINED: u32 = 0;
|
||||
pub const DC_CONTACT_ID_SELF: u32 = 1;
|
||||
@@ -134,7 +122,9 @@ pub const DC_CONTACT_ID_INFO: u32 = 2;
|
||||
pub const DC_CONTACT_ID_DEVICE: u32 = 5;
|
||||
pub const DC_CONTACT_ID_LAST_SPECIAL: u32 = 9;
|
||||
|
||||
pub const DC_CREATE_MVBOX: usize = 1;
|
||||
// decorative address that is used for DC_CONTACT_ID_DEVICE
|
||||
// when an api that returns an email is called.
|
||||
pub const DC_CONTACT_ID_DEVICE_ADDR: &str = "device@localhost";
|
||||
|
||||
// Flags for empty server job
|
||||
|
||||
@@ -194,6 +184,9 @@ pub const DC_VC_CONTACT_CONFIRM: i32 = 6;
|
||||
pub const DC_BOB_ERROR: i32 = 0;
|
||||
pub const DC_BOB_SUCCESS: i32 = 1;
|
||||
|
||||
// max. width/height of an avatar
|
||||
pub const AVATAR_SIZE: u32 = 192;
|
||||
|
||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
|
||||
#[repr(i32)]
|
||||
pub enum Viewtype {
|
||||
|
||||
239
src/contact.rs
@@ -1,3 +1,5 @@
|
||||
//! Contacts module
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use deltachat_derive::*;
|
||||
@@ -10,11 +12,13 @@ use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::e2ee;
|
||||
use crate::error::Result;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::events::Event;
|
||||
use crate::key::*;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::message::{MessageState, MsgId};
|
||||
use crate::mimeparser::AvatarAction;
|
||||
use crate::param::*;
|
||||
use crate::peerstate::*;
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
@@ -23,15 +27,17 @@ use crate::stock::StockMessage;
|
||||
const DC_ORIGIN_MIN_CONTACT_LIST: i32 = 0x100;
|
||||
|
||||
/// An object representing a single contact in memory.
|
||||
///
|
||||
/// The contact object is not updated.
|
||||
/// If you want an update, you have to recreate the object.
|
||||
///
|
||||
/// The library makes sure
|
||||
/// only to use names _authorized_ by the contact in `To:` or `Cc:`.
|
||||
/// _Given-names _as "Daddy" or "Honey" are not used there.
|
||||
/// *Given-names* as "Daddy" or "Honey" are not used there.
|
||||
/// For this purpose, internally, two names are tracked -
|
||||
/// authorized-name and given-name.
|
||||
/// authorized name and given name.
|
||||
/// By default, these names are equal, but functions working with contact names
|
||||
/// only affect the given name.
|
||||
#[derive(Debug)]
|
||||
pub struct Contact {
|
||||
/// The contact ID.
|
||||
@@ -56,6 +62,8 @@ pub struct Contact {
|
||||
blocked: bool,
|
||||
/// The origin/source of the contact.
|
||||
pub origin: Origin,
|
||||
/// Parameters as Param::ProfileImage
|
||||
pub param: Params,
|
||||
}
|
||||
|
||||
/// Possible origins of a contact.
|
||||
@@ -92,11 +100,11 @@ pub enum Origin {
|
||||
/// address is in our address book
|
||||
AdressBook = 0x80000,
|
||||
/// set on Alice's side for contacts like Bob that have scanned the QR code offered by her. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling dc_contact_is_verified() !
|
||||
SecurejoinInvited = 0x1000000,
|
||||
SecurejoinInvited = 0x0100_0000,
|
||||
/// set on Bob's side for contacts scanned and verified from a QR code. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling dc_contact_is_verified() !
|
||||
SecurejoinJoined = 0x2000000,
|
||||
SecurejoinJoined = 0x0200_0000,
|
||||
/// contact added mannually by dc_create_contact(), this should be the largets origin as otherwise the user cannot modify the names
|
||||
ManuallyCreated = 0x4000000,
|
||||
ManuallyCreated = 0x0400_0000,
|
||||
}
|
||||
|
||||
impl Default for Origin {
|
||||
@@ -106,19 +114,11 @@ impl Default for Origin {
|
||||
}
|
||||
|
||||
impl Origin {
|
||||
/// Contacts that start a new "normal" chat, defaults to off.
|
||||
pub fn is_start_new_chat(self) -> bool {
|
||||
self as i32 >= 0x7FFFFFFF
|
||||
}
|
||||
|
||||
/// Contacts that are verified and known not to be spam.
|
||||
pub fn is_verified(self) -> bool {
|
||||
self as i32 >= 0x100
|
||||
}
|
||||
|
||||
/// Contacts that are shown in the contact list.
|
||||
pub fn include_in_contactlist(self) -> bool {
|
||||
self as i32 >= DC_ORIGIN_MIN_CONTACT_LIST
|
||||
/// Contacts that are known, i. e. they came in via accepted contacts or
|
||||
/// themselves an accepted contact. Known contacts are shown in the
|
||||
/// contact list when one creates a chat and wants to add members etc.
|
||||
pub fn is_known(self) -> bool {
|
||||
self >= Origin::IncomingReplyTo
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,33 +141,11 @@ pub enum VerifiedStatus {
|
||||
}
|
||||
|
||||
impl Contact {
|
||||
pub fn load_from_db(context: &Context, contact_id: u32) -> Result<Self> {
|
||||
if contact_id == DC_CONTACT_ID_SELF {
|
||||
let contact = Contact {
|
||||
id: contact_id,
|
||||
name: context.stock_str(StockMessage::SelfMsg).into(),
|
||||
authname: "".into(),
|
||||
addr: context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_default(),
|
||||
blocked: false,
|
||||
origin: Origin::Unknown,
|
||||
};
|
||||
return Ok(contact);
|
||||
} else if contact_id == DC_CONTACT_ID_DEVICE {
|
||||
let contact = Contact {
|
||||
id: contact_id,
|
||||
name: context.stock_str(StockMessage::DeviceMessages).into(),
|
||||
authname: "".into(),
|
||||
addr: "device@localhost".into(),
|
||||
blocked: false,
|
||||
origin: Origin::Unknown,
|
||||
};
|
||||
return Ok(contact);
|
||||
}
|
||||
|
||||
context.sql.query_row(
|
||||
"SELECT c.name, c.addr, c.origin, c.blocked, c.authname FROM contacts c WHERE c.id=?;",
|
||||
pub fn load_from_db(context: &Context, contact_id: u32) -> crate::sql::Result<Self> {
|
||||
let mut res = context.sql.query_row(
|
||||
"SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param
|
||||
FROM contacts c
|
||||
WHERE c.id=?;",
|
||||
params![contact_id as i32],
|
||||
|row| {
|
||||
let contact = Self {
|
||||
@@ -177,10 +155,21 @@ impl Contact {
|
||||
addr: row.get::<_, String>(1)?,
|
||||
blocked: row.get::<_, Option<i32>>(3)?.unwrap_or_default() != 0,
|
||||
origin: row.get(2)?,
|
||||
param: row.get::<_, String>(5)?.parse().unwrap_or_default(),
|
||||
};
|
||||
Ok(contact)
|
||||
}
|
||||
)
|
||||
},
|
||||
)?;
|
||||
if contact_id == DC_CONTACT_ID_SELF {
|
||||
res.name = context.stock_str(StockMessage::SelfMsg).to_string();
|
||||
res.addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_default();
|
||||
} else if contact_id == DC_CONTACT_ID_DEVICE {
|
||||
res.name = context.stock_str(StockMessage::DeviceMessages).to_string();
|
||||
res.addr = DC_CONTACT_ID_DEVICE_ADDR.to_string();
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Returns `true` if this contact is blocked.
|
||||
@@ -208,7 +197,7 @@ impl Contact {
|
||||
/// Add a single contact as a result of an _explicit_ user action.
|
||||
///
|
||||
/// We assume, the contact name, if any, is entered by the user and is used "as is" therefore,
|
||||
/// normalize() is _not_ called for the name. If the contact is blocked, it is unblocked.
|
||||
/// normalize() is *not* called for the name. If the contact is blocked, it is unblocked.
|
||||
///
|
||||
/// To add a number of contacts, see `dc_add_address_book()` which is much faster for adding
|
||||
/// a bunch of addresses.
|
||||
@@ -238,7 +227,7 @@ impl Contact {
|
||||
}
|
||||
|
||||
/// Mark all messages sent by the given contact
|
||||
/// as _noticed_. See also dc_marknoticed_chat() and dc_markseen_msgs()
|
||||
/// as *noticed*. See also dc_marknoticed_chat() and dc_markseen_msgs()
|
||||
///
|
||||
/// Calling this function usually results in the event `#DC_EVENT_MSGS_CHANGED`.
|
||||
pub fn mark_noticed(context: &Context, id: u32) {
|
||||
@@ -272,7 +261,7 @@ impl Contact {
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_default();
|
||||
|
||||
if addr_normalized == addr_self {
|
||||
if addr_cmp(addr_normalized, addr_self) {
|
||||
return DC_CONTACT_ID_SELF;
|
||||
}
|
||||
|
||||
@@ -309,7 +298,7 @@ impl Contact {
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_default();
|
||||
|
||||
if addr == addr_self {
|
||||
if addr_cmp(addr, addr_self) {
|
||||
return Ok((DC_CONTACT_ID_SELF, sth_modified));
|
||||
}
|
||||
|
||||
@@ -409,6 +398,7 @@ impl Contact {
|
||||
{
|
||||
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
|
||||
sth_modified = Modifier::Created;
|
||||
info!(context, "added contact id={} addr={}", row_id, addr);
|
||||
} else {
|
||||
error!(context, "Cannot add contact.");
|
||||
}
|
||||
@@ -428,7 +418,7 @@ impl Contact {
|
||||
/// the event `DC_EVENT_CONTACTS_CHANGED` is sent.
|
||||
///
|
||||
/// To add a single contact entered by the user, you should prefer `Contact::create`,
|
||||
/// however, for adding a bunch of addresses, this function is _much_ faster.
|
||||
/// however, for adding a bunch of addresses, this function is much faster.
|
||||
///
|
||||
/// The `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
|
||||
///
|
||||
@@ -494,7 +484,7 @@ impl Contact {
|
||||
params![
|
||||
self_addr,
|
||||
DC_CONTACT_ID_LAST_SPECIAL as i32,
|
||||
0x100,
|
||||
Origin::IncomingReplyTo,
|
||||
&s3str_like_cmd,
|
||||
&s3str_like_cmd,
|
||||
if flag_verified_only { 0 } else { 1 },
|
||||
@@ -585,7 +575,12 @@ impl Contact {
|
||||
|
||||
let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql);
|
||||
|
||||
if peerstate.is_some() && peerstate.as_ref().and_then(|p| p.peek_key(0)).is_some() {
|
||||
if peerstate.is_some()
|
||||
&& peerstate
|
||||
.as_ref()
|
||||
.and_then(|p| p.peek_key(PeerstateVerifiedStatus::Unverified))
|
||||
.is_some()
|
||||
{
|
||||
let peerstate = peerstate.as_ref().unwrap();
|
||||
let p =
|
||||
context.stock_str(if peerstate.prefer_encrypt == EncryptPreference::Mutual {
|
||||
@@ -605,25 +600,25 @@ impl Contact {
|
||||
.map(|k| k.formatted_fingerprint())
|
||||
.unwrap_or_default();
|
||||
let fingerprint_other_verified = peerstate
|
||||
.peek_key(2)
|
||||
.peek_key(PeerstateVerifiedStatus::BidirectVerified)
|
||||
.map(|k| k.formatted_fingerprint())
|
||||
.unwrap_or_default();
|
||||
let fingerprint_other_unverified = peerstate
|
||||
.peek_key(0)
|
||||
.peek_key(PeerstateVerifiedStatus::Unverified)
|
||||
.map(|k| k.formatted_fingerprint())
|
||||
.unwrap_or_default();
|
||||
if peerstate.addr.is_some() && &loginparam.addr < peerstate.addr.as_ref().unwrap() {
|
||||
if loginparam.addr < peerstate.addr {
|
||||
cat_fingerprint(&mut ret, &loginparam.addr, &fingerprint_self, "");
|
||||
cat_fingerprint(
|
||||
&mut ret,
|
||||
peerstate.addr.as_ref().unwrap(),
|
||||
peerstate.addr.clone(),
|
||||
&fingerprint_other_verified,
|
||||
&fingerprint_other_unverified,
|
||||
);
|
||||
} else {
|
||||
cat_fingerprint(
|
||||
&mut ret,
|
||||
peerstate.addr.as_ref().unwrap(),
|
||||
peerstate.addr.clone(),
|
||||
&fingerprint_other_verified,
|
||||
&fingerprint_other_unverified,
|
||||
);
|
||||
@@ -686,7 +681,7 @@ impl Contact {
|
||||
}
|
||||
Err(err) => {
|
||||
error!(context, "delete_contact {} failed ({})", contact_id, err);
|
||||
return Err(err);
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -704,7 +699,17 @@ impl Contact {
|
||||
/// like "Me" in the selected language and the email address
|
||||
/// defined by dc_set_config().
|
||||
pub fn get_by_id(context: &Context, contact_id: u32) -> Result<Contact> {
|
||||
Contact::load_from_db(context, contact_id)
|
||||
Ok(Contact::load_from_db(context, contact_id)?)
|
||||
}
|
||||
|
||||
pub fn update_param(&mut self, context: &Context) -> Result<()> {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE contacts SET param=? WHERE id=?",
|
||||
params![self.param.to_string(), self.id as i32],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the ID of the contact.
|
||||
@@ -740,6 +745,9 @@ impl Contact {
|
||||
if !self.name.is_empty() {
|
||||
return &self.name;
|
||||
}
|
||||
if !self.authname.is_empty() {
|
||||
return &self.authname;
|
||||
}
|
||||
&self.addr
|
||||
}
|
||||
|
||||
@@ -775,8 +783,11 @@ impl Contact {
|
||||
if let Some(p) = context.get_config(Config::Selfavatar) {
|
||||
return Some(PathBuf::from(p));
|
||||
}
|
||||
} else if let Some(image_rel) = self.param.get(Param::ProfileImage) {
|
||||
if !image_rel.is_empty() {
|
||||
return Some(dc_get_abs_path(context, image_rel));
|
||||
}
|
||||
}
|
||||
// TODO: else get image_abs from contact param
|
||||
None
|
||||
}
|
||||
|
||||
@@ -859,14 +870,14 @@ impl Contact {
|
||||
.unwrap_or_default() as usize
|
||||
}
|
||||
|
||||
pub fn get_origin_by_id(context: &Context, contact_id: u32, ret_blocked: &mut i32) -> Origin {
|
||||
pub fn get_origin_by_id(context: &Context, contact_id: u32, ret_blocked: &mut bool) -> Origin {
|
||||
let mut ret = Origin::Unknown;
|
||||
*ret_blocked = 0;
|
||||
*ret_blocked = false;
|
||||
|
||||
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
|
||||
/* we could optimize this by loading only the needed fields */
|
||||
if contact.blocked {
|
||||
*ret_blocked = 1;
|
||||
*ret_blocked = true;
|
||||
} else {
|
||||
ret = contact.origin;
|
||||
}
|
||||
@@ -901,7 +912,7 @@ impl Contact {
|
||||
}
|
||||
|
||||
/// Extracts first name from full name.
|
||||
fn get_first_name<'a>(full_name: &'a str) -> &'a str {
|
||||
fn get_first_name(full_name: &str) -> &str {
|
||||
full_name.splitn(2, ' ').next().unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -928,21 +939,21 @@ fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
|
||||
}
|
||||
|
||||
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
|
||||
if contact.blocked != new_blocking {
|
||||
if sql::execute(
|
||||
if contact.blocked != new_blocking
|
||||
&& sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE contacts SET blocked=? WHERE id=?;",
|
||||
params![new_blocking as i32, contact_id as i32],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
// also (un)block all chats with _only_ this contact - we do not delete them to allow a
|
||||
// non-destructive blocking->unblocking.
|
||||
// (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
|
||||
// However, I'm not sure about this point; it may be confusing if the user wants to add other people;
|
||||
// this would result in recreating the same group...)
|
||||
if sql::execute(
|
||||
{
|
||||
// also (un)block all chats with _only_ this contact - we do not delete them to allow a
|
||||
// non-destructive blocking->unblocking.
|
||||
// (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
|
||||
// However, I'm not sure about this point; it may be confusing if the user wants to add other people;
|
||||
// this would result in recreating the same group...)
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats SET blocked=? WHERE type=? AND id IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
|
||||
@@ -951,11 +962,36 @@ fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
|
||||
Contact::mark_noticed(context, contact_id);
|
||||
context.call_cb(Event::ContactsChanged(None));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_profile_image(
|
||||
context: &Context,
|
||||
contact_id: u32,
|
||||
profile_image: &AvatarAction,
|
||||
) -> Result<()> {
|
||||
// the given profile image is expected to be already in the blob directory
|
||||
// as profile images can be set only by receiving messages, this should be always the case, however.
|
||||
let mut contact = Contact::load_from_db(context, contact_id)?;
|
||||
let changed = match profile_image {
|
||||
AvatarAction::Change(profile_image) => {
|
||||
contact.param.set(Param::ProfileImage, profile_image);
|
||||
true
|
||||
}
|
||||
AvatarAction::Delete => {
|
||||
contact.param.remove(Param::ProfileImage);
|
||||
true
|
||||
}
|
||||
AvatarAction::None => false,
|
||||
};
|
||||
if changed {
|
||||
contact.update_param(context)?;
|
||||
context.call_cb(Event::ContactsChanged(Some(contact_id)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Normalize a name.
|
||||
///
|
||||
/// - Remove quotes (come from some bad MUA implementations)
|
||||
@@ -1020,21 +1056,23 @@ fn cat_fingerprint(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addr_cmp(addr1: impl AsRef<str>, addr2: impl AsRef<str>) -> bool {
|
||||
let norm1 = addr_normalize(addr1.as_ref());
|
||||
let norm2 = addr_normalize(addr2.as_ref());
|
||||
impl Context {
|
||||
/// determine whether the specified addr maps to the/a self addr
|
||||
pub fn is_self_addr(&self, addr: &str) -> Result<bool> {
|
||||
let self_addr = match self.get_config(Config::ConfiguredAddr) {
|
||||
Some(s) => s,
|
||||
None => return Err(Error::NotConfigured),
|
||||
};
|
||||
|
||||
norm1 == norm2
|
||||
Ok(addr_cmp(self_addr, addr))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addr_equals_self(context: &Context, addr: impl AsRef<str>) -> bool {
|
||||
if !addr.as_ref().is_empty() {
|
||||
let normalized_addr = addr_normalize(addr.as_ref());
|
||||
if let Some(self_addr) = context.get_config(Config::ConfiguredAddr) {
|
||||
return normalized_addr == self_addr;
|
||||
}
|
||||
}
|
||||
false
|
||||
pub fn addr_cmp(addr1: impl AsRef<str>, addr2: impl AsRef<str>) -> bool {
|
||||
let norm1 = addr_normalize(addr1.as_ref()).to_lowercase();
|
||||
let norm2 = addr_normalize(addr2.as_ref()).to_lowercase();
|
||||
|
||||
norm1 == norm2
|
||||
}
|
||||
|
||||
fn split_address_book(book: &str) -> Vec<(&str, &str)> {
|
||||
@@ -1083,6 +1121,10 @@ mod tests {
|
||||
fn test_normalize_addr() {
|
||||
assert_eq!(addr_normalize("mailto:john@doe.com"), "john@doe.com");
|
||||
assert_eq!(addr_normalize(" hello@world.com "), "hello@world.com");
|
||||
|
||||
// normalisation preserves case to allow user-defined spelling.
|
||||
// however, case is ignored on addr_cmp()
|
||||
assert_ne!(addr_normalize("John@Doe.com"), "john@doe.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1116,6 +1158,18 @@ mod tests {
|
||||
assert_eq!(contacts.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_self_addr() -> Result<()> {
|
||||
let t = test_context(None);
|
||||
assert!(t.ctx.is_self_addr("me@me.org").is_err());
|
||||
|
||||
let addr = configure_alice_keypair(&t.ctx);
|
||||
assert_eq!(t.ctx.is_self_addr("me@me.org")?, false);
|
||||
assert_eq!(t.ctx.is_self_addr(&addr)?, true);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_or_lookup() {
|
||||
// add some contacts, this also tests add_address_book()
|
||||
@@ -1204,4 +1258,11 @@ mod tests {
|
||||
assert_eq!(contact.get_addr(), ""); // we're not configured
|
||||
assert!(!contact.is_blocked());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addr_cmp() {
|
||||
assert!(addr_cmp("AA@AA.ORG", "aa@aa.ORG"));
|
||||
assert!(addr_cmp(" aa@aa.ORG ", "AA@AA.ORG"));
|
||||
assert!(addr_cmp(" mailto:AA@AA.ORG", "Aa@Aa.orG"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Context module
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -19,7 +21,7 @@ use crate::login_param::LoginParam;
|
||||
use crate::lot::Lot;
|
||||
use crate::message::{self, Message, MsgId};
|
||||
use crate::param::Params;
|
||||
use crate::smtp::*;
|
||||
use crate::smtp::Smtp;
|
||||
use crate::sql::Sql;
|
||||
|
||||
/// Callback function type for [Context]
|
||||
@@ -86,7 +88,7 @@ pub fn get_info() -> HashMap<&'static str, String> {
|
||||
);
|
||||
res.insert(
|
||||
"arch",
|
||||
(::std::mem::size_of::<*mut libc::c_void>())
|
||||
(std::mem::size_of::<*mut libc::c_void>())
|
||||
.wrapping_mul(8)
|
||||
.to_string(),
|
||||
);
|
||||
@@ -296,6 +298,11 @@ impl Context {
|
||||
res.insert("database_version", dbversion.to_string());
|
||||
res.insert("blobdir", self.get_blobdir().display().to_string());
|
||||
res.insert("display_name", displayname.unwrap_or_else(|| unset.into()));
|
||||
res.insert(
|
||||
"selfavatar",
|
||||
self.get_config(Config::Selfavatar)
|
||||
.unwrap_or_else(|| "<unset>".to_string()),
|
||||
);
|
||||
res.insert("is_configured", is_configured.to_string());
|
||||
res.insert("entered_account_settings", l.to_string());
|
||||
res.insert("used_account_settings", l2.to_string());
|
||||
@@ -435,10 +442,9 @@ impl Context {
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.is_inbox(folder) && !self.is_sentbox(folder) {
|
||||
if self.is_mvbox(folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(msg) = Message::load_from_db(self, msg_id) {
|
||||
if msg.is_setupmessage() {
|
||||
// do not move setup messages;
|
||||
@@ -446,10 +452,6 @@ impl Context {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.is_mvbox(folder) {
|
||||
message::update_msg_move_state(self, &msg.rfc724_mid, MoveState::Stay);
|
||||
}
|
||||
|
||||
// 1 = dc message, 2 = reply to dc message
|
||||
if 0 != msg.is_dc_message {
|
||||
job_add(
|
||||
@@ -459,7 +461,6 @@ impl Context {
|
||||
Params::new(),
|
||||
0,
|
||||
);
|
||||
message::update_msg_move_state(self, &msg.rfc724_mid, MoveState::Moving);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -495,12 +496,25 @@ pub struct BobStatus {
|
||||
pub qr_scan: Option<Lot>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum PerformJobsNeeded {
|
||||
Not,
|
||||
AtOnce,
|
||||
AvoidDos,
|
||||
}
|
||||
|
||||
impl Default for PerformJobsNeeded {
|
||||
fn default() -> Self {
|
||||
Self::Not
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct SmtpState {
|
||||
pub idle: bool,
|
||||
pub suspended: bool,
|
||||
pub doing_jobs: bool,
|
||||
pub perform_jobs_needed: i32,
|
||||
pub perform_jobs_needed: PerformJobsNeeded,
|
||||
pub probe_network: bool,
|
||||
}
|
||||
|
||||
|
||||
1312
src/dc_mimeparser.rs
@@ -1,175 +1,169 @@
|
||||
use crate::dc_dehtml::*;
|
||||
use crate::dehtml::*;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Simplify {
|
||||
pub is_forwarded: bool,
|
||||
}
|
||||
|
||||
/// Return index of footer line in vector of message lines, or vector length if
|
||||
/// no footer is found.
|
||||
///
|
||||
/// Also return whether not-standard (rfc3676, §4.3) footer is found.
|
||||
fn find_message_footer(lines: &[&str]) -> (usize, bool) {
|
||||
/// Remove standard (RFC 3676, §4.3) footer if it is found.
|
||||
fn remove_message_footer<'a>(lines: &'a [&str]) -> &'a [&'a str] {
|
||||
for (ix, &line) in lines.iter().enumerate() {
|
||||
// quoted-printable may encode `-- ` to `-- =20` which is converted
|
||||
// back to `-- `
|
||||
match line {
|
||||
"-- " | "-- " => return (ix, false),
|
||||
"--" | "---" | "----" => return (ix, true),
|
||||
"-- " | "-- " => return &lines[..ix],
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
(lines.len(), false)
|
||||
lines
|
||||
}
|
||||
|
||||
impl Simplify {
|
||||
pub fn new() -> Self {
|
||||
Simplify {
|
||||
is_forwarded: false,
|
||||
/// Remove nonstandard footer and a boolean indicating whether such
|
||||
/// footer was removed.
|
||||
fn remove_nonstandard_footer<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
|
||||
for (ix, &line) in lines.iter().enumerate() {
|
||||
if line == "--"
|
||||
|| line == "---"
|
||||
|| line == "----"
|
||||
|| line.starts_with("-----")
|
||||
|| line.starts_with("_____")
|
||||
|| line.starts_with("=====")
|
||||
|| line.starts_with("*****")
|
||||
|| line.starts_with("~~~~~")
|
||||
{
|
||||
return (&lines[..ix], true);
|
||||
}
|
||||
}
|
||||
(lines, false)
|
||||
}
|
||||
|
||||
/// Simplify and normalise text: Remove quotes, signatures, unnecessary
|
||||
/// lineends etc.
|
||||
/// The data returned from simplify() must be free()'d when no longer used.
|
||||
pub fn simplify(&mut self, input: &str, is_html: bool, is_msgrmsg: bool) -> String {
|
||||
let mut out = if is_html {
|
||||
dc_dehtml(input)
|
||||
} else {
|
||||
input.to_string()
|
||||
};
|
||||
fn split_lines(buf: &str) -> Vec<&str> {
|
||||
buf.split('\n').collect()
|
||||
}
|
||||
|
||||
out.retain(|c| c != '\r');
|
||||
out = self.simplify_plain_text(&out, is_msgrmsg);
|
||||
out.retain(|c| c != '\r');
|
||||
/// Simplify message text for chat display.
|
||||
/// Remove quotes, signatures, trailing empty lines etc.
|
||||
pub fn simplify(input: &str, is_html: bool, is_chat_message: bool) -> (String, bool) {
|
||||
let mut out = if is_html {
|
||||
dehtml(input)
|
||||
} else {
|
||||
input.to_string()
|
||||
};
|
||||
|
||||
out
|
||||
out.retain(|c| c != '\r');
|
||||
let lines = split_lines(&out);
|
||||
let (lines, is_forwarded) = skip_forward_header(&lines);
|
||||
|
||||
let lines = remove_message_footer(lines);
|
||||
let (lines, has_nonstandard_footer) = remove_nonstandard_footer(lines);
|
||||
let (lines, has_bottom_quote) = if !is_chat_message {
|
||||
remove_bottom_quote(lines)
|
||||
} else {
|
||||
(lines, false)
|
||||
};
|
||||
let (lines, has_top_quote) = if !is_chat_message {
|
||||
remove_top_quote(lines)
|
||||
} else {
|
||||
(lines, false)
|
||||
};
|
||||
|
||||
// re-create buffer from the remaining lines
|
||||
let text = render_message(
|
||||
lines,
|
||||
has_top_quote,
|
||||
has_nonstandard_footer || has_bottom_quote,
|
||||
);
|
||||
(text, is_forwarded)
|
||||
}
|
||||
|
||||
/// Skips "forwarded message" header.
|
||||
/// Returns message body lines and a boolean indicating whether
|
||||
/// a message is forwarded or not.
|
||||
fn skip_forward_header<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
|
||||
if lines.len() >= 3
|
||||
&& lines[0] == "---------- Forwarded message ----------"
|
||||
&& lines[1].starts_with("From: ")
|
||||
&& lines[2].is_empty()
|
||||
{
|
||||
(&lines[3..], true)
|
||||
} else {
|
||||
(lines, false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplify Plain Text
|
||||
*/
|
||||
#[allow(non_snake_case)]
|
||||
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: Vec<_> = buf_terminated.split('\n').collect();
|
||||
let mut l_first: usize = 0;
|
||||
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 = lines[l_first];
|
||||
let line1 = lines[l_first + 1];
|
||||
let line2 = lines[l_first + 2];
|
||||
if line0 == "---------- Forwarded message ----------"
|
||||
&& line1.starts_with("From: ")
|
||||
&& line2.is_empty()
|
||||
{
|
||||
self.is_forwarded = true;
|
||||
l_first += 3
|
||||
fn remove_bottom_quote<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
|
||||
let mut last_quoted_line = None;
|
||||
for (l, line) in lines.iter().enumerate().rev() {
|
||||
if is_plain_quote(line) {
|
||||
last_quoted_line = Some(l)
|
||||
} else if !is_empty_line(line) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(mut l_last) = last_quoted_line {
|
||||
if l_last > 1 && is_empty_line(lines[l_last - 1]) {
|
||||
l_last -= 1
|
||||
}
|
||||
if l_last > 1 {
|
||||
let line = lines[l_last - 1];
|
||||
if is_quoted_headline(line) {
|
||||
l_last -= 1
|
||||
}
|
||||
}
|
||||
for l in l_first..l_last {
|
||||
let line = lines[l];
|
||||
if line == "-----"
|
||||
|| line == "_____"
|
||||
|| line == "====="
|
||||
|| line == "*****"
|
||||
|| line == "~~~~~"
|
||||
{
|
||||
l_last = l;
|
||||
is_cut_at_end = true;
|
||||
/* done */
|
||||
(&lines[..l_last], true)
|
||||
} else {
|
||||
(lines, false)
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_top_quote<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
|
||||
let mut last_quoted_line = None;
|
||||
let mut has_quoted_headline = false;
|
||||
for (l, line) in lines.iter().enumerate() {
|
||||
if is_plain_quote(line) {
|
||||
last_quoted_line = Some(l)
|
||||
} else if !is_empty_line(line) {
|
||||
if is_quoted_headline(line) && !has_quoted_headline && last_quoted_line.is_none() {
|
||||
has_quoted_headline = true
|
||||
} else {
|
||||
/* non-quoting line found */
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !is_msgrmsg {
|
||||
let mut l_lastQuotedLine = None;
|
||||
for l in (l_first..l_last).rev() {
|
||||
let line = lines[l];
|
||||
if is_plain_quote(line) {
|
||||
l_lastQuotedLine = Some(l)
|
||||
} else if !is_empty_line(line) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(last_quoted_line) = l_lastQuotedLine {
|
||||
l_last = last_quoted_line;
|
||||
is_cut_at_end = true;
|
||||
if l_last > 1 && is_empty_line(lines[l_last - 1]) {
|
||||
l_last -= 1
|
||||
}
|
||||
if l_last > 1 {
|
||||
let line = lines[l_last - 1];
|
||||
if is_quoted_headline(line) {
|
||||
l_last -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !is_msgrmsg {
|
||||
let mut l_lastQuotedLine_0 = None;
|
||||
let mut hasQuotedHeadline = 0;
|
||||
for l in l_first..l_last {
|
||||
let line = lines[l];
|
||||
if is_plain_quote(line) {
|
||||
l_lastQuotedLine_0 = Some(l)
|
||||
} else if !is_empty_line(line) {
|
||||
if is_quoted_headline(line)
|
||||
&& 0 == hasQuotedHeadline
|
||||
&& l_lastQuotedLine_0.is_none()
|
||||
{
|
||||
hasQuotedHeadline = 1i32
|
||||
} else {
|
||||
/* non-quoting line found */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(last_quoted_line) = l_lastQuotedLine_0 {
|
||||
l_first = last_quoted_line + 1;
|
||||
is_cut_at_begin = true
|
||||
}
|
||||
}
|
||||
/* re-create buffer from the remaining lines */
|
||||
let mut ret = String::new();
|
||||
if is_cut_at_begin {
|
||||
ret += "[...]";
|
||||
}
|
||||
/* we write empty lines only in case and non-empty line follows */
|
||||
let mut pending_linebreaks = 0;
|
||||
let mut content_lines_added = 0;
|
||||
for l in l_first..l_last {
|
||||
let line = lines[l];
|
||||
if is_empty_line(line) {
|
||||
pending_linebreaks += 1
|
||||
} else {
|
||||
if 0 != content_lines_added {
|
||||
if pending_linebreaks > 2i32 {
|
||||
pending_linebreaks = 2i32
|
||||
}
|
||||
while 0 != pending_linebreaks {
|
||||
ret += "\n";
|
||||
pending_linebreaks -= 1
|
||||
}
|
||||
}
|
||||
// the incoming message might contain invalid UTF8
|
||||
ret += line;
|
||||
content_lines_added += 1;
|
||||
pending_linebreaks = 1i32
|
||||
}
|
||||
}
|
||||
if is_cut_at_end && (!is_cut_at_begin || 0 != content_lines_added) {
|
||||
ret += " [...]";
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
if let Some(last_quoted_line) = last_quoted_line {
|
||||
(&lines[last_quoted_line + 1..], true)
|
||||
} else {
|
||||
(lines, false)
|
||||
}
|
||||
}
|
||||
|
||||
fn render_message(lines: &[&str], is_cut_at_begin: bool, is_cut_at_end: bool) -> String {
|
||||
let mut ret = String::new();
|
||||
if is_cut_at_begin {
|
||||
ret += "[...]";
|
||||
}
|
||||
/* we write empty lines only in case and non-empty line follows */
|
||||
let mut pending_linebreaks = 0;
|
||||
let mut empty_body = true;
|
||||
for line in lines {
|
||||
if is_empty_line(line) {
|
||||
pending_linebreaks += 1
|
||||
} else {
|
||||
if !empty_body {
|
||||
if pending_linebreaks > 2 {
|
||||
pending_linebreaks = 2
|
||||
}
|
||||
while 0 != pending_linebreaks {
|
||||
ret += "\n";
|
||||
pending_linebreaks -= 1
|
||||
}
|
||||
}
|
||||
// the incoming message might contain invalid UTF8
|
||||
ret += line;
|
||||
empty_body = false;
|
||||
pending_linebreaks = 1
|
||||
}
|
||||
}
|
||||
if is_cut_at_end && (!is_cut_at_begin || !empty_body) {
|
||||
ret += " [...]";
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,50 +207,59 @@ mod tests {
|
||||
#[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);
|
||||
let (output, _is_forwarded) = simplify(&input, false, true);
|
||||
assert!(output.split('\n').all(|s| s != "-- "));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_trim() {
|
||||
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);
|
||||
let (plain, is_forwarded) = simplify(html, true, false);
|
||||
|
||||
assert_eq!(plain, "line1\nline2");
|
||||
assert!(!is_forwarded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_parse_href() {
|
||||
let mut simplify = Simplify::new();
|
||||
let html = "<a href=url>text</a";
|
||||
let plain = simplify.simplify(html, true, false);
|
||||
let (plain, is_forwarded) = simplify(html, true, false);
|
||||
|
||||
assert_eq!(plain, "[text](url)");
|
||||
assert!(!is_forwarded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_bold_text() {
|
||||
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);
|
||||
let (plain, is_forwarded) = simplify(html, true, false);
|
||||
|
||||
assert_eq!(plain, "text *bold*<>");
|
||||
assert!(!is_forwarded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_forwarded_message() {
|
||||
let text = "---------- Forwarded message ----------\r\nFrom: test@example.com\r\n\r\nForwarded message\r\n-- \r\nSignature goes here";
|
||||
let (plain, is_forwarded) = simplify(text, false, false);
|
||||
|
||||
assert_eq!(plain, "Forwarded message");
|
||||
assert!(is_forwarded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_html_encoded() {
|
||||
let mut simplify = Simplify::new();
|
||||
let html =
|
||||
"<>"'& äÄöÖüÜß fooÆçÇ ♦‎‏‌&noent;‍";
|
||||
|
||||
let plain = simplify.simplify(html, true, false);
|
||||
let (plain, is_forwarded) = simplify(html, true, false);
|
||||
|
||||
assert_eq!(
|
||||
plain,
|
||||
"<>\"\'& äÄöÖüÜß fooÆçÇ \u{2666}\u{200e}\u{200f}\u{200c}&noent;\u{200d}"
|
||||
);
|
||||
assert!(!is_forwarded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -270,4 +273,19 @@ mod tests {
|
||||
assert!(!is_plain_quote("Life is pain"));
|
||||
assert!(!is_plain_quote(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_top_quote() {
|
||||
let (lines, has_top_quote) = remove_top_quote(&["> first", "> second"]);
|
||||
assert!(lines.is_empty());
|
||||
assert!(has_top_quote);
|
||||
|
||||
let (lines, has_top_quote) = remove_top_quote(&["> first", "> second", "not a quote"]);
|
||||
assert_eq!(lines, &["not a quote"]);
|
||||
assert!(has_top_quote);
|
||||
|
||||
let (lines, has_top_quote) = remove_top_quote(&["not a quote", "> first", "> second"]);
|
||||
assert_eq!(lines, &["not a quote", "> first", "> second"]);
|
||||
assert!(!has_top_quote);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,251 +0,0 @@
|
||||
use itertools::Itertools;
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
use charset::Charset;
|
||||
use libc::free;
|
||||
use mmime::mailmime::decode::mailmime_encoded_phrase_parse;
|
||||
use mmime::other::*;
|
||||
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
|
||||
|
||||
use crate::dc_tools::*;
|
||||
|
||||
/**
|
||||
* Encode non-ascii-strings as `=?UTF-8?Q?Bj=c3=b6rn_Petersen?=`.
|
||||
* Belongs to RFC 2047: https://tools.ietf.org/html/rfc2047
|
||||
*
|
||||
* We do not fold at position 72; this would result in empty words as `=?utf-8?Q??=` which are correct,
|
||||
* but cannot be displayed by some mail programs (eg. Android Stock Mail).
|
||||
* however, this is not needed, as long as _one_ word is not longer than 72 characters.
|
||||
* _if_ it is, the display may get weird. This affects the subject only.
|
||||
* the best solution wor all this would be if libetpan encodes the line as only libetpan knowns when a header line is full.
|
||||
*
|
||||
* @param to_encode Null-terminated UTF-8-string to encode.
|
||||
* @return Returns the encoded string which must be free()'d when no longed needed.
|
||||
* On errors, NULL is returned.
|
||||
*/
|
||||
pub fn dc_encode_header_words(input: impl AsRef<str>) -> String {
|
||||
let mut result = String::default();
|
||||
for (_, group) in &input.as_ref().chars().group_by(|c| c.is_whitespace()) {
|
||||
let word: String = group.collect();
|
||||
result.push_str("e_word(&word.as_bytes()));
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn must_encode(byte: u8) -> bool {
|
||||
static SPECIALS: &[u8] = b",:!\"#$@[\\]^`{|}~=?_";
|
||||
|
||||
SPECIALS.iter().any(|b| *b == byte)
|
||||
}
|
||||
|
||||
fn quote_word(word: &[u8]) -> String {
|
||||
let mut result = String::default();
|
||||
let mut encoded = false;
|
||||
|
||||
for byte in word {
|
||||
let byte = *byte;
|
||||
if byte >= 128 || must_encode(byte) {
|
||||
result.push_str(&format!("={:2X}", byte));
|
||||
encoded = true;
|
||||
} else if byte == b' ' {
|
||||
result.push('_');
|
||||
encoded = true;
|
||||
} else {
|
||||
result.push(byte as _);
|
||||
}
|
||||
}
|
||||
|
||||
if encoded {
|
||||
result = format!("=?utf-8?Q?{}?=", &result);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/* ******************************************************************************
|
||||
* Encode/decode header words, RFC 2047
|
||||
******************************************************************************/
|
||||
|
||||
pub(crate) fn dc_decode_header_words(input: &str) -> String {
|
||||
static FROM_ENCODING: &[u8] = b"iso-8859-1\x00";
|
||||
static TO_ENCODING: &[u8] = b"utf-8\x00";
|
||||
let mut out = ptr::null_mut();
|
||||
let mut cur_token = 0;
|
||||
let input_c = CString::yolo(input);
|
||||
unsafe {
|
||||
let r = mailmime_encoded_phrase_parse(
|
||||
FROM_ENCODING.as_ptr().cast(),
|
||||
input_c.as_ptr(),
|
||||
input.len(),
|
||||
&mut cur_token,
|
||||
TO_ENCODING.as_ptr().cast(),
|
||||
&mut out,
|
||||
);
|
||||
if r as u32 != MAILIMF_NO_ERROR || out.is_null() {
|
||||
input.to_string()
|
||||
} else {
|
||||
let res = to_string_lossy(out);
|
||||
free(out.cast());
|
||||
res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_needs_ext_header(to_check: impl AsRef<str>) -> bool {
|
||||
let to_check = to_check.as_ref();
|
||||
|
||||
if to_check.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
to_check.chars().any(|c| {
|
||||
!(c.is_ascii_alphanumeric()
|
||||
|| c == '-'
|
||||
|| c == '_'
|
||||
|| c == '_'
|
||||
|| c == '.'
|
||||
|| c == '~'
|
||||
|| c == '%')
|
||||
})
|
||||
}
|
||||
|
||||
const EXT_ASCII_ST: &AsciiSet = &CONTROLS
|
||||
.add(b' ')
|
||||
.add(b'-')
|
||||
.add(b'_')
|
||||
.add(b'.')
|
||||
.add(b'~')
|
||||
.add(b'%');
|
||||
|
||||
/// Encode an UTF-8 string to the extended header format.
|
||||
pub fn dc_encode_ext_header(to_encode: impl AsRef<str>) -> String {
|
||||
let encoded = utf8_percent_encode(to_encode.as_ref(), &EXT_ASCII_ST);
|
||||
format!("utf-8''{}", encoded)
|
||||
}
|
||||
|
||||
/// Decode an extended-header-format strings to UTF-8.
|
||||
pub fn dc_decode_ext_header(to_decode: &[u8]) -> Cow<str> {
|
||||
if let Some(index) = bytes!(b'\'').find(to_decode) {
|
||||
let (charset, rest) = to_decode.split_at(index);
|
||||
if !charset.is_empty() {
|
||||
// skip language
|
||||
if let Some(index2) = bytes!(b'\'').find(&rest[1..]) {
|
||||
let decoded = percent_decode(&rest[index2 + 2..]);
|
||||
|
||||
if charset != b"utf-8" && charset != b"UTF-8" {
|
||||
if let Some(encoding) = Charset::for_label(charset) {
|
||||
let bytes = decoded.collect::<Vec<u8>>();
|
||||
let (res, _, _) = encoding.decode(&bytes);
|
||||
return Cow::Owned(res.into_owned());
|
||||
} else {
|
||||
return decoded.decode_utf8_lossy();
|
||||
}
|
||||
} else {
|
||||
return decoded.decode_utf8_lossy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String::from_utf8_lossy(to_decode)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_dc_decode_header_words() {
|
||||
assert_eq!(
|
||||
dc_decode_header_words("=?utf-8?B?dGVzdMOkw7bDvC50eHQ=?="),
|
||||
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
|
||||
);
|
||||
|
||||
assert_eq!(dc_decode_header_words("just ascii test"), "just ascii test");
|
||||
|
||||
assert_eq!(dc_encode_header_words("abcdef"), "abcdef");
|
||||
|
||||
let r = dc_encode_header_words(
|
||||
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
|
||||
);
|
||||
assert!(r.starts_with("=?utf-8"));
|
||||
|
||||
assert_eq!(
|
||||
dc_decode_header_words(&r),
|
||||
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
dc_decode_header_words("=?ISO-8859-1?Q?attachment=3B=0D=0A_filename=3D?= =?ISO-8859-1?Q?=22test=E4=F6=FC=2Etxt=22=3B=0D=0A_size=3D39?="),
|
||||
std::string::String::from_utf8(b"attachment;\r\n filename=\"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\";\r\n size=39".to_vec()).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_encode_ext_header() {
|
||||
let buf1 = dc_encode_ext_header("Björn Petersen");
|
||||
assert_eq!(&buf1, "utf-8\'\'Bj%C3%B6rn%20Petersen");
|
||||
let buf2 = dc_decode_ext_header(buf1.as_bytes());
|
||||
assert_eq!(&buf2, "Björn Petersen",);
|
||||
|
||||
let buf1 = dc_decode_ext_header(b"iso-8859-1\'en\'%A3%20rates");
|
||||
assert_eq!(buf1, "£ rates",);
|
||||
|
||||
let buf1 = dc_decode_ext_header(b"wrong\'format");
|
||||
assert_eq!(buf1, "wrong\'format",);
|
||||
|
||||
let buf1 = dc_decode_ext_header(b"\'\'");
|
||||
assert_eq!(buf1, "\'\'");
|
||||
|
||||
let buf1 = dc_decode_ext_header(b"x\'\'");
|
||||
assert_eq!(buf1, "");
|
||||
|
||||
let buf1 = dc_decode_ext_header(b"\'");
|
||||
assert_eq!(buf1, "\'");
|
||||
|
||||
let buf1 = dc_decode_ext_header(b"");
|
||||
assert_eq!(buf1, "");
|
||||
|
||||
// regressions
|
||||
assert_eq!(
|
||||
dc_decode_ext_header(dc_encode_ext_header("%0A").as_bytes()),
|
||||
"%0A"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_needs_ext_header() {
|
||||
assert_eq!(dc_needs_ext_header("Björn"), true);
|
||||
assert_eq!(dc_needs_ext_header("Bjoern"), false);
|
||||
assert_eq!(dc_needs_ext_header(""), false);
|
||||
assert_eq!(dc_needs_ext_header(" "), true);
|
||||
assert_eq!(dc_needs_ext_header("a b"), true);
|
||||
}
|
||||
|
||||
use proptest::prelude::*;
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn test_ext_header_roundtrip(buf: String) {
|
||||
let encoded = dc_encode_ext_header(&buf);
|
||||
let decoded = dc_decode_ext_header(encoded.as_bytes());
|
||||
assert_eq!(buf, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ext_header_decode_anything(buf: Vec<u8>) {
|
||||
// make sure this never panics
|
||||
let _decoded = dc_decode_ext_header(&buf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_header_roundtrip(input: String) {
|
||||
let encoded = dc_encode_header_words(&input);
|
||||
let decoded = dc_decode_header_words(&encoded);
|
||||
|
||||
assert_eq!(input, decoded);
|
||||
}
|
||||
}
|
||||
}
|
||||
628
src/dc_tools.rs
@@ -3,54 +3,22 @@
|
||||
|
||||
use core::cmp::{max, min};
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::time::SystemTime;
|
||||
use std::{fmt, fs, ptr};
|
||||
use std::{fmt, fs};
|
||||
|
||||
use chrono::{Local, TimeZone};
|
||||
use libc::{memcpy, strlen};
|
||||
use mmime::clist::*;
|
||||
use mmime::mailimf::types::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
|
||||
pub(crate) fn dc_exactly_one_bit_set(v: libc::c_int) -> bool {
|
||||
pub(crate) fn dc_exactly_one_bit_set(v: i32) -> bool {
|
||||
0 != v && 0 == v & (v - 1)
|
||||
}
|
||||
|
||||
/// Duplicates a string
|
||||
///
|
||||
/// returns an empty string if NULL is given, never returns NULL (exits on errors)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use deltachat::dc_tools::{dc_strdup, to_string_lossy};
|
||||
/// unsafe {
|
||||
/// let str_a = b"foobar\x00" as *const u8 as *const libc::c_char;
|
||||
/// let str_a_copy = dc_strdup(str_a);
|
||||
/// assert_eq!(to_string_lossy(str_a_copy), "foobar");
|
||||
/// assert_ne!(str_a, str_a_copy);
|
||||
/// }
|
||||
/// ```
|
||||
pub unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
|
||||
let ret: *mut libc::c_char;
|
||||
if !s.is_null() {
|
||||
ret = strdup(s);
|
||||
assert!(!ret.is_null());
|
||||
} else {
|
||||
ret = libc::calloc(1, 1) as *mut libc::c_char;
|
||||
assert!(!ret.is_null());
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
/// Shortens a string to a specified length and adds "..." or "[...]" to the end of
|
||||
/// the shortened string.
|
||||
pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Cow<str> {
|
||||
@@ -74,39 +42,15 @@ pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Co
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn dc_str_from_clist(list: *const clist, delimiter: &str) -> String {
|
||||
let mut res = String::new();
|
||||
|
||||
if !list.is_null() {
|
||||
for rfc724_mid in unsafe { (*list).into_iter() } {
|
||||
if !res.is_empty() {
|
||||
res += delimiter;
|
||||
}
|
||||
res += &to_string_lossy(rfc724_mid as *const libc::c_char);
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
pub(crate) fn dc_str_to_clist(str: &str, delimiter: &str) -> *mut clist {
|
||||
unsafe {
|
||||
let list: *mut clist = clist_new();
|
||||
for cur in str.split(&delimiter) {
|
||||
clist_insert_after(list, (*list).last, cur.strdup().cast());
|
||||
}
|
||||
list
|
||||
}
|
||||
}
|
||||
|
||||
/* the colors must fulfill some criterions as:
|
||||
- contrast to black and to white
|
||||
- work as a text-color
|
||||
- being noticeable on a typical map
|
||||
- harmonize together while being different enough
|
||||
(therefore, we cannot just use random rgb colors :) */
|
||||
/// the colors must fulfill some criterions as:
|
||||
/// - contrast to black and to white
|
||||
/// - work as a text-color
|
||||
/// - being noticeable on a typical map
|
||||
/// - harmonize together while being different enough
|
||||
/// (therefore, we cannot just use random rgb colors :)
|
||||
const COLORS: [u32; 16] = [
|
||||
0xe56555, 0xf28c48, 0x8e85ee, 0x76c84d, 0x5bb6cc, 0x549cdd, 0xd25c99, 0xb37800, 0xf23030,
|
||||
0x39b249, 0xbb243b, 0x964078, 0x66874f, 0x308ab9, 0x127ed0, 0xbe450c,
|
||||
0xe5_65_55, 0xf2_8c_48, 0x8e_85_ee, 0x76_c8_4d, 0x5b_b6_cc, 0x54_9c_dd, 0xd2_5c_99, 0xb3_78_00,
|
||||
0xf2_30_30, 0x39_b2_49, 0xbb_24_3b, 0x96_40_78, 0x66_87_4f, 0x30_8a_b9, 0x12_7e_d0, 0xbe_45_0c,
|
||||
];
|
||||
|
||||
pub(crate) fn dc_str_to_color(s: impl AsRef<str>) -> u32 {
|
||||
@@ -115,40 +59,13 @@ pub(crate) fn dc_str_to_color(s: impl AsRef<str>) -> u32 {
|
||||
let bytes = str_lower.as_bytes();
|
||||
for (i, byte) in bytes.iter().enumerate() {
|
||||
checksum += (i + 1) * *byte as usize;
|
||||
checksum %= 0xffffff;
|
||||
checksum %= 0x00ff_ffff;
|
||||
}
|
||||
let color_index = checksum % COLORS.len();
|
||||
|
||||
COLORS[color_index]
|
||||
}
|
||||
|
||||
/* date/time tools */
|
||||
/* the result is UTC or DC_INVALID_TIMESTAMP */
|
||||
pub(crate) fn dc_timestamp_from_date(date_time: *mut mailimf_date_time) -> i64 {
|
||||
assert!(!date_time.is_null());
|
||||
let dt = unsafe { *date_time };
|
||||
|
||||
let sec = dt.dt_sec;
|
||||
let min = dt.dt_min;
|
||||
let hour = dt.dt_hour;
|
||||
let day = dt.dt_day;
|
||||
let month = dt.dt_month;
|
||||
let year = dt.dt_year;
|
||||
|
||||
let ts = chrono::NaiveDateTime::new(
|
||||
chrono::NaiveDate::from_ymd(year, month as u32, day as u32),
|
||||
chrono::NaiveTime::from_hms(hour as u32, min as u32, sec as u32),
|
||||
);
|
||||
|
||||
let (zone_hour, zone_min) = if dt.dt_zone >= 0 {
|
||||
(dt.dt_zone / 100, dt.dt_zone % 100)
|
||||
} else {
|
||||
(-(-dt.dt_zone / 100), -(-dt.dt_zone % 100))
|
||||
};
|
||||
|
||||
ts.timestamp() - (zone_hour * 3600 + zone_min * 60) as i64
|
||||
}
|
||||
|
||||
/* ******************************************************************************
|
||||
* date/time tools
|
||||
******************************************************************************/
|
||||
@@ -265,22 +182,6 @@ fn encode_66bits_as_base64(v1: u32, v2: u32, fill: u32) -> String {
|
||||
String::from_utf8(wrapped_writer).unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn dc_create_incoming_rfc724_mid(
|
||||
message_timestamp: i64,
|
||||
contact_id_from: u32,
|
||||
contact_ids_to: &[u32],
|
||||
) -> Option<String> {
|
||||
/* create a deterministic rfc724_mid from input such that
|
||||
repeatedly calling it with the same input results in the same Message-id */
|
||||
|
||||
let largest_id_to = contact_ids_to.iter().max().copied().unwrap_or_default();
|
||||
let result = format!(
|
||||
"{}-{}-{}@stub",
|
||||
message_timestamp, contact_id_from, largest_id_to
|
||||
);
|
||||
Some(result)
|
||||
}
|
||||
|
||||
/// Function generates a Message-ID that can be used for a new outgoing message.
|
||||
/// - this function is called for all outgoing messages.
|
||||
/// - the message ID should be globally unique
|
||||
@@ -300,8 +201,11 @@ pub(crate) fn dc_create_outgoing_rfc724_mid(grpid: Option<&str>, from_addr: &str
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `mid` - A string that holds the message id
|
||||
/// * `mid` - A string that holds the message id. Leading/Trailing <>
|
||||
/// characters are automatically stripped.
|
||||
pub(crate) fn dc_extract_grpid_from_rfc724_mid(mid: &str) -> Option<&str> {
|
||||
let mid = mid.trim_start_matches('<').trim_end_matches('>');
|
||||
|
||||
if mid.len() < 9 || !mid.starts_with("Gr.") {
|
||||
return None;
|
||||
}
|
||||
@@ -318,29 +222,6 @@ pub(crate) fn dc_extract_grpid_from_rfc724_mid(mid: &str) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn dc_extract_grpid_from_rfc724_mid_list(list: *const clist) -> *mut libc::c_char {
|
||||
if !list.is_null() {
|
||||
unsafe {
|
||||
for cur in (*list).into_iter() {
|
||||
let mid = to_string_lossy(cur as *const libc::c_char);
|
||||
|
||||
if let Some(grpid) = dc_extract_grpid_from_rfc724_mid(&mid) {
|
||||
return grpid.strdup();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
pub(crate) fn dc_ensure_no_slash_safe(path: &str) -> &str {
|
||||
if path.ends_with('/') || path.ends_with('\\') {
|
||||
return &path[..path.len() - 1];
|
||||
}
|
||||
path
|
||||
}
|
||||
|
||||
// Function returns a sanitized basename that does not contain
|
||||
// win/linux path separators and also not any non-ascii chars
|
||||
fn get_safe_basename(filename: &str) -> String {
|
||||
@@ -381,11 +262,9 @@ pub fn dc_derive_safe_stem_ext(filename: &str) -> (String, String) {
|
||||
// the returned suffix is lower-case
|
||||
#[allow(non_snake_case)]
|
||||
pub fn dc_get_filesuffix_lc(path_filename: impl AsRef<str>) -> Option<String> {
|
||||
if let Some(p) = Path::new(path_filename.as_ref()).extension() {
|
||||
Some(p.to_string_lossy().to_lowercase())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
Path::new(path_filename.as_ref())
|
||||
.extension()
|
||||
.map(|p| p.to_string_lossy().to_lowercase())
|
||||
}
|
||||
|
||||
/// Returns the `(width, height)` of the given image buffer.
|
||||
@@ -502,11 +381,14 @@ pub(crate) fn dc_copy_file(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn dc_create_folder(context: &Context, path: impl AsRef<std::path::Path>) -> bool {
|
||||
pub(crate) fn dc_create_folder(
|
||||
context: &Context,
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> Result<(), std::io::Error> {
|
||||
let path_abs = dc_get_abs_path(context, &path);
|
||||
if !path_abs.exists() {
|
||||
match fs::create_dir_all(path_abs) {
|
||||
Ok(_) => true,
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
@@ -514,18 +396,22 @@ pub(crate) fn dc_create_folder(context: &Context, path: impl AsRef<std::path::Pa
|
||||
path.as_ref().display(),
|
||||
err
|
||||
);
|
||||
false
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
true
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Write a the given content to provied file path.
|
||||
pub(crate) fn dc_write_file(context: &Context, path: impl AsRef<Path>, buf: &[u8]) -> bool {
|
||||
pub(crate) fn dc_write_file(
|
||||
context: &Context,
|
||||
path: impl AsRef<Path>,
|
||||
buf: &[u8],
|
||||
) -> Result<(), std::io::Error> {
|
||||
let path_abs = dc_get_abs_path(context, &path);
|
||||
if let Err(err) = fs::write(&path_abs, buf) {
|
||||
fs::write(&path_abs, buf).map_err(|err| {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot write {} bytes to \"{}\": {}",
|
||||
@@ -533,10 +419,8 @@ pub(crate) fn dc_write_file(context: &Context, path: impl AsRef<Path>, buf: &[u8
|
||||
path.as_ref().display(),
|
||||
err
|
||||
);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
err
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dc_read_file<P: AsRef<std::path::Path>>(
|
||||
@@ -599,212 +483,6 @@ pub(crate) fn dc_get_next_backup_path(
|
||||
bail!("could not create backup file, disk full?");
|
||||
}
|
||||
|
||||
/// Error type for the [OsStrExt] trait
|
||||
#[derive(Debug, Fail, PartialEq)]
|
||||
pub enum CStringError {
|
||||
/// The string contains an interior null byte
|
||||
#[fail(display = "String contains an interior null byte")]
|
||||
InteriorNullByte,
|
||||
/// The string is not valid Unicode
|
||||
#[fail(display = "String is not valid unicode")]
|
||||
NotUnicode,
|
||||
}
|
||||
|
||||
/// Extra convenience methods on [std::ffi::OsStr] to work with `*libc::c_char`.
|
||||
///
|
||||
/// The primary function of this trait is to more easily convert
|
||||
/// [OsStr], [OsString] or [Path] into pointers to C strings. This always
|
||||
/// allocates a new string since it is very common for the source
|
||||
/// string not to have the required terminal null byte.
|
||||
///
|
||||
/// It is implemented for `AsRef<std::ffi::OsStr>>` trait, which
|
||||
/// allows any type which implements this trait to transparently use
|
||||
/// this. This is how the conversion for [Path] works.
|
||||
///
|
||||
/// [OsStr]: std::ffi::OsStr
|
||||
/// [OsString]: std::ffi::OsString
|
||||
/// [Path]: std::path::Path
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use deltachat::dc_tools::{dc_strdup, OsStrExt};
|
||||
/// let path = std::path::Path::new("/some/path");
|
||||
/// let path_c = path.to_c_string().unwrap();
|
||||
/// unsafe {
|
||||
/// let mut c_ptr: *mut libc::c_char = dc_strdup(path_c.as_ptr());
|
||||
/// }
|
||||
/// ```
|
||||
pub trait OsStrExt {
|
||||
/// Convert a [std::ffi::OsStr] to an [std::ffi::CString]
|
||||
///
|
||||
/// This is useful to convert e.g. a [std::path::Path] to
|
||||
/// [*libc::c_char] by using
|
||||
/// [Path::as_os_str()](std::path::Path::as_os_str) and
|
||||
/// [CStr::as_ptr()](std::ffi::CStr::as_ptr).
|
||||
///
|
||||
/// This returns [CString] and not [&CStr] because not all [OsStr]
|
||||
/// slices end with a null byte, particularly those coming from
|
||||
/// [Path] do not have a null byte and having to handle this as
|
||||
/// the caller would defeat the point of this function.
|
||||
///
|
||||
/// On Windows this requires that the [OsStr] contains valid
|
||||
/// unicode, which should normally be the case for a [Path].
|
||||
///
|
||||
/// [CString]: std::ffi::CString
|
||||
/// [CStr]: std::ffi::CStr
|
||||
/// [OsStr]: std::ffi::OsStr
|
||||
/// [Path]: std::path::Path
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Since a C `*char` is terminated by a NULL byte this conversion
|
||||
/// will fail, when the [OsStr] has an interior null byte. The
|
||||
/// function will return
|
||||
/// `[Err]([CStringError::InteriorNullByte])`. When converting
|
||||
/// from a [Path] it should be safe to
|
||||
/// [`.unwrap()`](std::result::Result::unwrap) this anyway since a
|
||||
/// [Path] should not contain interior null bytes.
|
||||
///
|
||||
/// On windows when the string contains invalid Unicode
|
||||
/// `[Err]([CStringError::NotUnicode])` is returned.
|
||||
fn to_c_string(&self) -> Result<CString, CStringError>;
|
||||
}
|
||||
|
||||
impl<T: AsRef<std::ffi::OsStr>> OsStrExt for T {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn to_c_string(&self) -> Result<CString, CStringError> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
CString::new(self.as_ref().as_bytes()).map_err(|err| match err {
|
||||
std::ffi::NulError { .. } => CStringError::InteriorNullByte,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn to_c_string(&self) -> Result<CString, CStringError> {
|
||||
os_str_to_c_string_unicode(&self)
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation for os_str_to_c_string on windows.
|
||||
#[allow(dead_code)]
|
||||
fn os_str_to_c_string_unicode(
|
||||
os_str: &dyn AsRef<std::ffi::OsStr>,
|
||||
) -> Result<CString, CStringError> {
|
||||
match os_str.as_ref().to_str() {
|
||||
Some(val) => CString::new(val.as_bytes()).map_err(|err| match err {
|
||||
std::ffi::NulError { .. } => CStringError::InteriorNullByte,
|
||||
}),
|
||||
None => Err(CStringError::NotUnicode),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience methods/associated functions for working with [CString]
|
||||
///
|
||||
/// This is helps transitioning from unsafe code.
|
||||
pub trait CStringExt {
|
||||
/// Create a new [CString], yolo style
|
||||
///
|
||||
/// This unwrap the result, panicking when there are embedded NULL
|
||||
/// bytes.
|
||||
fn yolo<T: Into<Vec<u8>>>(t: T) -> CString {
|
||||
CString::new(t).expect("String contains null byte, can not be CString")
|
||||
}
|
||||
}
|
||||
|
||||
impl CStringExt for CString {}
|
||||
|
||||
/// Convenience methods to make transitioning from raw C strings easier.
|
||||
///
|
||||
/// To interact with (legacy) C APIs we often need to convert from
|
||||
/// Rust strings to raw C strings. This can be clumsy to do correctly
|
||||
/// and the compiler sometimes allows it in an unsafe way. These
|
||||
/// methods make it more succinct and help you get it right.
|
||||
pub trait StrExt {
|
||||
/// Allocate a new raw C `*char` version of this string.
|
||||
///
|
||||
/// This allocates a new raw C string which must be freed using
|
||||
/// `free`. It takes care of some common pitfalls with using
|
||||
/// [CString.as_ptr].
|
||||
///
|
||||
/// [CString.as_ptr]: std::ffi::CString.as_ptr
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic when the original string contains an
|
||||
/// interior null byte as this can not be represented in raw C
|
||||
/// strings.
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char;
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> StrExt for T {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||
let tmp = CString::yolo(self.as_ref());
|
||||
dc_strdup(tmp.as_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_string_lossy(s: *const libc::c_char) -> String {
|
||||
if s.is_null() {
|
||||
return "".into();
|
||||
}
|
||||
|
||||
let cstr = unsafe { CStr::from_ptr(s) };
|
||||
|
||||
cstr.to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
pub fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
|
||||
if s.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(to_string_lossy(s))
|
||||
}
|
||||
|
||||
/// Convert a C `*char` pointer to a [std::path::Path] slice.
|
||||
///
|
||||
/// This converts a `*libc::c_char` pointer to a [Path] slice. This
|
||||
/// essentially has to convert the pointer to [std::ffi::OsStr] to do
|
||||
/// so and thus is the inverse of [OsStrExt::to_c_string]. Just like
|
||||
/// [OsStrExt::to_c_string] requires valid Unicode on Windows, this
|
||||
/// requires that the pointer contains valid UTF-8 on Windows.
|
||||
///
|
||||
/// Because this returns a reference the [Path] silce can not outlive
|
||||
/// the original pointer.
|
||||
///
|
||||
/// [Path]: std::path::Path
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
assert!(!s.is_null(), "cannot be used on null pointers");
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
unsafe {
|
||||
let c_str = std::ffi::CStr::from_ptr(s).to_bytes();
|
||||
let os_str = std::ffi::OsStr::from_bytes(c_str);
|
||||
std::path::Path::new(os_str)
|
||||
}
|
||||
}
|
||||
|
||||
// as_path() implementation for windows, documented above.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
as_path_unicode(s)
|
||||
}
|
||||
|
||||
// Implementation for as_path() on Windows.
|
||||
//
|
||||
// Having this as a separate function means it can be tested on unix
|
||||
// too.
|
||||
#[allow(dead_code)]
|
||||
fn as_path_unicode<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
assert!(!s.is_null(), "cannot be used on null pointers");
|
||||
|
||||
let cstr = unsafe { CStr::from_ptr(s) };
|
||||
let str = cstr.to_str().unwrap_or_else(|err| panic!("{}", err));
|
||||
|
||||
std::path::Path::new(str)
|
||||
}
|
||||
|
||||
pub(crate) fn time() -> i64 {
|
||||
SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
@@ -882,71 +560,14 @@ pub(crate) fn listflags_has(listflags: u32, bitindex: usize) -> bool {
|
||||
(listflags & bitindex) == bitindex
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn strdup(s: *const libc::c_char) -> *mut libc::c_char {
|
||||
if s.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
let slen = strlen(s);
|
||||
let result = libc::malloc(slen + 1);
|
||||
if result.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
memcpy(result, s as *const _, slen + 1);
|
||||
result as *mut _
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn strcasecmp(s1: *const libc::c_char, s2: *const libc::c_char) -> libc::c_int {
|
||||
let s1 = std::ffi::CStr::from_ptr(s1)
|
||||
.to_string_lossy()
|
||||
.to_lowercase();
|
||||
let s2 = std::ffi::CStr::from_ptr(s2)
|
||||
.to_string_lossy()
|
||||
.to_lowercase();
|
||||
if s1 == s2 {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use libc::{free, strcmp};
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::CStr;
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn test_dc_strdup() {
|
||||
unsafe {
|
||||
let str_a = b"foobar\x00" as *const u8 as *const libc::c_char;
|
||||
let str_a_copy = dc_strdup(str_a);
|
||||
|
||||
// Value of str_a_copy should equal foobar
|
||||
assert_eq!(
|
||||
CStr::from_ptr(str_a_copy),
|
||||
CString::new("foobar").unwrap().as_c_str()
|
||||
);
|
||||
// Address of str_a should be different from str_a_copy
|
||||
assert_ne!(str_a, str_a_copy);
|
||||
|
||||
let str_a = std::ptr::null() as *const libc::c_char;
|
||||
let str_a_copy = dc_strdup(str_a);
|
||||
// Value of str_a_copy should equal ""
|
||||
assert_eq!(
|
||||
CStr::from_ptr(str_a_copy),
|
||||
CString::new("").unwrap().as_c_str()
|
||||
);
|
||||
assert_ne!(str_a, str_a_copy);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rust_ftoa() {
|
||||
assert_eq!("1.22", format!("{}", 1.22));
|
||||
@@ -1007,44 +628,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/* calls free() for each item content */
|
||||
unsafe fn clist_free_content(haystack: *const clist) {
|
||||
let mut iter = (*haystack).first;
|
||||
|
||||
while !iter.is_null() {
|
||||
free((*iter).data);
|
||||
(*iter).data = ptr::null_mut();
|
||||
iter = if !iter.is_null() {
|
||||
(*iter).next
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_str_to_clist_1() {
|
||||
unsafe {
|
||||
let list = dc_str_to_clist("", " ");
|
||||
assert_eq!((*list).count, 1);
|
||||
clist_free_content(list);
|
||||
clist_free(list);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_str_to_clist_4() {
|
||||
unsafe {
|
||||
let list: *mut clist = dc_str_to_clist("foo bar test", " ");
|
||||
assert_eq!((*list).count, 3);
|
||||
let str = dc_str_from_clist(list, " ");
|
||||
assert_eq!(str, "foo bar test");
|
||||
|
||||
clist_free_content(list);
|
||||
clist_free(list);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_create_id() {
|
||||
let buf = dc_create_id();
|
||||
@@ -1072,113 +655,6 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_os_str_to_c_string_cwd() {
|
||||
let some_dir = std::env::current_dir().unwrap();
|
||||
some_dir.as_os_str().to_c_string().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_os_str_to_c_string_unicode() {
|
||||
let some_str = String::from("/some/valid/utf8");
|
||||
let some_dir = std::path::Path::new(&some_str);
|
||||
assert_eq!(
|
||||
some_dir.as_os_str().to_c_string().unwrap(),
|
||||
CString::new("/some/valid/utf8").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_os_str_to_c_string_nul() {
|
||||
let some_str = std::ffi::OsString::from("foo\x00bar");
|
||||
assert_eq!(
|
||||
some_str.to_c_string().err().unwrap(),
|
||||
CStringError::InteriorNullByte
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_to_c_string_cwd() {
|
||||
let some_dir = std::env::current_dir().unwrap();
|
||||
some_dir.to_c_string().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_to_c_string_unicode() {
|
||||
let some_str = String::from("/some/valid/utf8");
|
||||
let some_dir = std::path::Path::new(&some_str);
|
||||
assert_eq!(
|
||||
some_dir.as_os_str().to_c_string().unwrap(),
|
||||
CString::new("/some/valid/utf8").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_os_str_to_c_string_unicode_fn() {
|
||||
let some_str = std::ffi::OsString::from("foo");
|
||||
assert_eq!(
|
||||
os_str_to_c_string_unicode(&some_str).unwrap(),
|
||||
CString::new("foo").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_to_c_string_unicode_fn() {
|
||||
let some_str = String::from("/some/path");
|
||||
let some_path = std::path::Path::new(&some_str);
|
||||
assert_eq!(
|
||||
os_str_to_c_string_unicode(&some_path).unwrap(),
|
||||
CString::new("/some/path").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_os_str_to_c_string_unicode_fn_nul() {
|
||||
let some_str = std::ffi::OsString::from("fooz\x00bar");
|
||||
assert_eq!(
|
||||
os_str_to_c_string_unicode(&some_str).err().unwrap(),
|
||||
CStringError::InteriorNullByte
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_as_path() {
|
||||
let some_path = CString::new("/some/path").unwrap();
|
||||
let ptr = some_path.as_ptr();
|
||||
assert_eq!(as_path(ptr), std::ffi::OsString::from("/some/path"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_as_path_unicode_fn() {
|
||||
let some_path = CString::new("/some/path").unwrap();
|
||||
let ptr = some_path.as_ptr();
|
||||
assert_eq!(as_path_unicode(ptr), std::ffi::OsString::from("/some/path"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cstring_yolo() {
|
||||
assert_eq!(CString::new("hello").unwrap(), CString::yolo("hello"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strdup_str() {
|
||||
unsafe {
|
||||
let s = "hello".strdup();
|
||||
let cmp = strcmp(s, b"hello\x00" as *const u8 as *const libc::c_char);
|
||||
free(s as *mut libc::c_void);
|
||||
assert_eq!(cmp, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strdup_string() {
|
||||
unsafe {
|
||||
let s = String::from("hello").strdup();
|
||||
let cmp = strcmp(s, b"hello\x00" as *const u8 as *const libc::c_char);
|
||||
free(s as *mut libc::c_void);
|
||||
assert_eq!(cmp, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_extract_grpid_from_rfc724_mid() {
|
||||
// Should return None if we pass invalid mid
|
||||
@@ -1200,6 +676,16 @@ mod tests {
|
||||
let mid = "Gr.1234567890123456.morerandom@domain.de";
|
||||
let grpid = dc_extract_grpid_from_rfc724_mid(mid);
|
||||
assert_eq!(grpid, Some("1234567890123456"));
|
||||
|
||||
// Should return extracted grpid for grpid with length of 11
|
||||
let mid = "<Gr.12345678901.morerandom@domain.de>";
|
||||
let grpid = dc_extract_grpid_from_rfc724_mid(mid);
|
||||
assert_eq!(grpid, Some("12345678901"));
|
||||
|
||||
// Should return extracted grpid for grpid with length of 11
|
||||
let mid = "<Gr.1234567890123456.morerandom@domain.de>";
|
||||
let grpid = dc_extract_grpid_from_rfc724_mid(mid);
|
||||
assert_eq!(grpid, Some("1234567890123456"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1281,14 +767,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_create_incoming_rfc724_mid() {
|
||||
let res = dc_create_incoming_rfc724_mid(123, 45, &vec![6, 7]);
|
||||
assert_eq!(res, Some("123-45-7@stub".into()));
|
||||
let res = dc_create_incoming_rfc724_mid(123, 45, &vec![]);
|
||||
assert_eq!(res, Some("123-45-0@stub".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_get_safe_basename() {
|
||||
assert_eq!(get_safe_basename("12312/hello"), "hello");
|
||||
@@ -1323,7 +801,7 @@ mod tests {
|
||||
dc_delete_file(context, "$BLOBDIR/foobar.dadada");
|
||||
dc_delete_file(context, "$BLOBDIR/foobar-folder");
|
||||
}
|
||||
assert!(dc_write_file(context, "$BLOBDIR/foobar", b"content"));
|
||||
assert!(dc_write_file(context, "$BLOBDIR/foobar", b"content").is_ok());
|
||||
assert!(dc_file_exist(context, "$BLOBDIR/foobar",));
|
||||
assert!(!dc_file_exist(context, "$BLOBDIR/foobarx"));
|
||||
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/foobar"), 7);
|
||||
@@ -1350,12 +828,12 @@ mod tests {
|
||||
|
||||
assert!(dc_delete_file(context, "$BLOBDIR/foobar"));
|
||||
assert!(dc_delete_file(context, "$BLOBDIR/dada"));
|
||||
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder"));
|
||||
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder").is_ok());
|
||||
assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",));
|
||||
assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder"));
|
||||
|
||||
let fn0 = "$BLOBDIR/data.data";
|
||||
assert!(dc_write_file(context, &fn0, b"content"));
|
||||
assert!(dc_write_file(context, &fn0, b"content").is_ok());
|
||||
|
||||
assert!(dc_delete_file(context, &fn0));
|
||||
assert!(!dc_file_exist(context, &fn0));
|
||||
@@ -1364,15 +842,15 @@ mod tests {
|
||||
#[test]
|
||||
fn test_listflags_has() {
|
||||
let listflags: u32 = 0x1101;
|
||||
assert!(listflags_has(listflags, 0x1) == true);
|
||||
assert!(listflags_has(listflags, 0x10) == false);
|
||||
assert!(listflags_has(listflags, 0x100) == true);
|
||||
assert!(listflags_has(listflags, 0x1000) == true);
|
||||
assert!(listflags_has(listflags, 0x1));
|
||||
assert!(!listflags_has(listflags, 0x10));
|
||||
assert!(listflags_has(listflags, 0x100));
|
||||
assert!(listflags_has(listflags, 0x1000));
|
||||
let listflags: u32 = (DC_GCL_ADD_SELF | DC_GCL_VERIFIED_ONLY).try_into().unwrap();
|
||||
assert!(listflags_has(listflags, DC_GCL_VERIFIED_ONLY) == true);
|
||||
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == true);
|
||||
assert!(listflags_has(listflags, DC_GCL_VERIFIED_ONLY));
|
||||
assert!(listflags_has(listflags, DC_GCL_ADD_SELF));
|
||||
let listflags: u32 = DC_GCL_VERIFIED_ONLY.try_into().unwrap();
|
||||
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == false);
|
||||
assert!(!listflags_has(listflags, DC_GCL_ADD_SELF));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
//! De-HTML
|
||||
//!
|
||||
//! A module to remove HTML tags from the email text
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
@@ -19,22 +23,18 @@ enum AddText {
|
||||
YesPreserveLineEnds,
|
||||
}
|
||||
|
||||
// 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 fn dc_dehtml(buf_terminated: &str) -> String {
|
||||
let buf_terminated = buf_terminated.trim();
|
||||
|
||||
if buf_terminated.is_empty() {
|
||||
return "".into();
|
||||
}
|
||||
// dehtml() returns way too many newlines; however, an optimisation on this issue is not needed as
|
||||
// the newlines are typically removed in further processing by the caller
|
||||
pub fn dehtml(buf: &str) -> String {
|
||||
let buf = buf.trim();
|
||||
|
||||
let mut dehtml = Dehtml {
|
||||
strbuilder: String::with_capacity(buf_terminated.len()),
|
||||
strbuilder: String::with_capacity(buf.len()),
|
||||
add_text: AddText::YesRemoveLineEnds,
|
||||
last_href: None,
|
||||
};
|
||||
|
||||
let mut reader = quick_xml::Reader::from_str(buf_terminated);
|
||||
let mut reader = quick_xml::Reader::from_str(buf);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
|
||||
@@ -139,13 +139,12 @@ fn dehtml_starttag_cb<B: std::io::BufRead>(
|
||||
dehtml.add_text = AddText::YesPreserveLineEnds;
|
||||
}
|
||||
"a" => {
|
||||
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()
|
||||
}) {
|
||||
if let Some(href) = event
|
||||
.html_attributes()
|
||||
.filter_map(|attr| attr.ok())
|
||||
.find(|attr| String::from_utf8_lossy(attr.key).trim().to_lowercase() == "href")
|
||||
{
|
||||
let href = href
|
||||
.unwrap()
|
||||
.unescape_and_decode_value(reader)
|
||||
.unwrap_or_default()
|
||||
.to_lowercase();
|
||||
@@ -171,7 +170,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_dc_dehtml() {
|
||||
fn test_dehtml() {
|
||||
let cases = vec![
|
||||
(
|
||||
"<a href='https://example.com'> Foo </a>",
|
||||
@@ -186,7 +185,7 @@ mod tests {
|
||||
("", ""),
|
||||
];
|
||||
for (input, output) in cases {
|
||||
assert_eq!(dc_dehtml(input), output);
|
||||
assert_eq!(dehtml(input), output);
|
||||
}
|
||||
}
|
||||
}
|
||||
605
src/e2ee.rs
@@ -1,41 +1,19 @@
|
||||
//! End-to-end encryption support.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::ptr;
|
||||
use std::str::FromStr;
|
||||
|
||||
use libc::strlen;
|
||||
use mmime::clist::*;
|
||||
use mmime::mailimf::types::*;
|
||||
use mmime::mailimf::types_helper::*;
|
||||
use mmime::mailimf::*;
|
||||
use mmime::mailmime::content::*;
|
||||
use mmime::mailmime::types::*;
|
||||
use mmime::mailmime::types_helper::*;
|
||||
use mmime::mailmime::write_mem::*;
|
||||
use mmime::mailmime::*;
|
||||
use mmime::mailprivacy_prepare_mime;
|
||||
use mmime::mmapstring::*;
|
||||
use mmime::{mailmime_substitute, MAILIMF_NO_ERROR, MAIL_NO_ERROR};
|
||||
use mailparse::{MailHeaderMap, ParsedMail};
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
use crate::aheader::*;
|
||||
use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::*;
|
||||
use crate::key::*;
|
||||
use crate::keyring::*;
|
||||
use crate::mimefactory::MimeFactory;
|
||||
use crate::peerstate::*;
|
||||
use crate::pgp;
|
||||
use crate::securejoin::handle_degrade_event;
|
||||
use crate::wrapmime;
|
||||
use crate::wrapmime::*;
|
||||
|
||||
// standard mime-version header aka b"Version: 1\r\n\x00"
|
||||
static mut VERSION_CONTENT: [libc::c_char; 13] =
|
||||
[86, 101, 114, 115, 105, 111, 110, 58, 32, 49, 13, 10, 0];
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EncryptHelper {
|
||||
@@ -71,39 +49,27 @@ impl EncryptHelper {
|
||||
Aheader::new(addr, pk, self.prefer_encrypt)
|
||||
}
|
||||
|
||||
pub fn try_encrypt(
|
||||
&mut self,
|
||||
factory: &mut MimeFactory,
|
||||
/// Determines if we can and should encrypt.
|
||||
pub fn should_encrypt(
|
||||
&self,
|
||||
context: &Context,
|
||||
e2ee_guaranteed: bool,
|
||||
min_verified: libc::c_int,
|
||||
do_gossip: bool,
|
||||
mut in_out_message: *mut Mailmime,
|
||||
imffields_unprotected: *mut mailimf_fields,
|
||||
peerstates: &[(Option<Peerstate>, &str)],
|
||||
) -> Result<bool> {
|
||||
// libEtPan's pgp_encrypt_mime() takes the parent as the new root.
|
||||
// We just expect the root as being given to this function.
|
||||
ensure!(
|
||||
!in_out_message.is_null() && unsafe { (*in_out_message).mm_parent.is_null() },
|
||||
"corrupted inputs"
|
||||
);
|
||||
|
||||
if !(self.prefer_encrypt == EncryptPreference::Mutual || e2ee_guaranteed) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let context = &factory.context;
|
||||
let mut keyring = Keyring::default();
|
||||
let mut gossip_headers: Vec<String> = Vec::with_capacity(factory.recipients_addr.len());
|
||||
|
||||
// determine if we can and should encrypt
|
||||
for recipient_addr in factory.recipients_addr.iter() {
|
||||
if recipient_addr == &self.addr {
|
||||
continue;
|
||||
}
|
||||
let peerstate = match Peerstate::from_addr(context, &context.sql, recipient_addr) {
|
||||
Some(peerstate) => peerstate,
|
||||
for (peerstate, addr) in peerstates {
|
||||
match peerstate {
|
||||
Some(peerstate) => {
|
||||
if peerstate.prefer_encrypt != EncryptPreference::Mutual && !e2ee_guaranteed {
|
||||
info!(context, "peerstate for {:?} is no-encrypt", addr);
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let msg = format!("peerstate for {} missing, cannot encrypt", recipient_addr);
|
||||
let msg = format!("peerstate for {:?} missing, cannot encrypt", addr);
|
||||
if e2ee_guaranteed {
|
||||
return Err(format_err!("{}", msg));
|
||||
} else {
|
||||
@@ -111,169 +77,59 @@ impl EncryptHelper {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if peerstate.prefer_encrypt != EncryptPreference::Mutual && !e2ee_guaranteed {
|
||||
info!(context, "peerstate for {} is no-encrypt", recipient_addr);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if let Some(key) = peerstate.peek_key(min_verified as usize) {
|
||||
keyring.add_owned(key.clone());
|
||||
if do_gossip {
|
||||
if let Some(header) = peerstate.render_gossip_header(min_verified as usize) {
|
||||
gossip_headers.push(header.to_string());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bail!(
|
||||
"proper enc-key for {} missing, cannot encrypt",
|
||||
recipient_addr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let sign_key = {
|
||||
keyring.add_ref(&self.public_key);
|
||||
let key = Key::from_self_private(context, self.addr.clone(), &context.sql);
|
||||
ensure!(key.is_some(), "no own private key found");
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
key
|
||||
};
|
||||
/// Tries to encrypt the passed in `mail`.
|
||||
pub fn encrypt(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
min_verified: PeerstateVerifiedStatus,
|
||||
mail_to_encrypt: lettre_email::PartBuilder,
|
||||
peerstates: &[(Option<Peerstate>, &str)],
|
||||
) -> Result<String> {
|
||||
let mut keyring = Keyring::default();
|
||||
|
||||
// encrypt message
|
||||
unsafe {
|
||||
mailprivacy_prepare_mime(in_out_message);
|
||||
let mut part_to_encrypt = (*in_out_message).mm_data.mm_message.mm_msg_mime;
|
||||
(*part_to_encrypt).mm_parent = ptr::null_mut();
|
||||
let imffields_encrypted = mailimf_fields_new_empty();
|
||||
|
||||
// mailmime_new_message_data() calls mailmime_fields_new_with_version()
|
||||
// which would add the unwanted MIME-Version:-header
|
||||
let message_to_encrypt = mailmime_new_simple(
|
||||
MAILMIME_MESSAGE as libc::c_int,
|
||||
mailmime_fields_new_empty(),
|
||||
mailmime_get_content_message(),
|
||||
imffields_encrypted,
|
||||
part_to_encrypt,
|
||||
);
|
||||
|
||||
for header in &gossip_headers {
|
||||
wrapmime::new_custom_field(imffields_encrypted, "Autocrypt-Gossip", &header)
|
||||
}
|
||||
|
||||
// memoryhole headers: move some headers into encrypted part
|
||||
// XXX note we can't use clist's into_iter() because the loop body also removes items
|
||||
let mut cur = (*(*imffields_unprotected).fld_list).first;
|
||||
while !cur.is_null() {
|
||||
let field = (*cur).data as *mut mailimf_field;
|
||||
let mut move_to_encrypted = false;
|
||||
|
||||
if !field.is_null() {
|
||||
if (*field).fld_type == MAILIMF_FIELD_SUBJECT as libc::c_int {
|
||||
move_to_encrypted = true;
|
||||
} else if (*field).fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
|
||||
let opt_field = (*field).fld_data.fld_optional_field;
|
||||
if !opt_field.is_null() && !(*opt_field).fld_name.is_null() {
|
||||
let fld_name = to_string_lossy((*opt_field).fld_name);
|
||||
if fld_name.starts_with("Secure-Join")
|
||||
|| (fld_name.starts_with("Chat-") && fld_name != "Chat-Version")
|
||||
{
|
||||
move_to_encrypted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if move_to_encrypted {
|
||||
mailimf_fields_add(imffields_encrypted, field);
|
||||
cur = clist_delete((*imffields_unprotected).fld_list, cur);
|
||||
} else {
|
||||
cur = (*cur).next;
|
||||
}
|
||||
}
|
||||
|
||||
let subject = mailimf_subject_new("...".strdup());
|
||||
mailimf_fields_add(imffields_unprotected, mailimf_field_new_subject(subject));
|
||||
|
||||
wrapmime::append_ct_param(
|
||||
(*part_to_encrypt).mm_content_type,
|
||||
"protected-headers",
|
||||
"v1",
|
||||
)?;
|
||||
let plain = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
|
||||
let mut col = 0;
|
||||
mailmime_write_mem(plain, &mut col, message_to_encrypt);
|
||||
mailmime_free(message_to_encrypt);
|
||||
|
||||
ensure!(
|
||||
!(*plain).str_0.is_null() && (*plain).len > 0,
|
||||
"could not write/allocate"
|
||||
);
|
||||
|
||||
let ctext = pgp::pk_encrypt(
|
||||
std::slice::from_raw_parts((*plain).str_0 as *const u8, (*plain).len),
|
||||
&keyring,
|
||||
sign_key.as_ref(),
|
||||
);
|
||||
mmap_string_free(plain);
|
||||
|
||||
let ctext_v = ctext?;
|
||||
|
||||
// create MIME-structure that will contain the encrypted text
|
||||
let mut encrypted_part = new_data_part(
|
||||
ptr::null_mut(),
|
||||
0 as libc::size_t,
|
||||
"multipart/encrypted",
|
||||
MAILMIME_MECHANISM_BASE64,
|
||||
)?;
|
||||
let content = (*encrypted_part).mm_content_type;
|
||||
wrapmime::append_ct_param(content, "protocol", "application/pgp-encrypted")?;
|
||||
|
||||
let version_mime = new_data_part(
|
||||
VERSION_CONTENT.as_mut_ptr() as *mut libc::c_void,
|
||||
strlen(VERSION_CONTENT.as_mut_ptr()),
|
||||
"application/pgp-encrypted",
|
||||
MAILMIME_MECHANISM_7BIT,
|
||||
)?;
|
||||
mailmime_smart_add_part(encrypted_part, version_mime);
|
||||
|
||||
// we assume that ctext_v is not dropped until the end
|
||||
// of this if-scope
|
||||
let ctext_part = new_data_part(
|
||||
ctext_v.as_ptr() as *mut libc::c_void,
|
||||
ctext_v.len(),
|
||||
"application/octet-stream",
|
||||
MAILMIME_MECHANISM_7BIT,
|
||||
)?;
|
||||
|
||||
mailmime_smart_add_part(encrypted_part, ctext_part);
|
||||
(*in_out_message).mm_data.mm_message.mm_msg_mime = encrypted_part;
|
||||
(*encrypted_part).mm_parent = in_out_message;
|
||||
let gossiped = !&gossip_headers.is_empty();
|
||||
factory.finalize_mime_message(in_out_message, true, gossiped)?;
|
||||
|
||||
Ok(true)
|
||||
for (peerstate, addr) in peerstates
|
||||
.iter()
|
||||
.filter_map(|(state, addr)| state.as_ref().map(|s| (s, addr)))
|
||||
{
|
||||
let key = peerstate.peek_key(min_verified).ok_or_else(|| {
|
||||
format_err!("proper enc-key for {} missing, cannot encrypt", addr)
|
||||
})?;
|
||||
keyring.add_ref(key);
|
||||
}
|
||||
|
||||
keyring.add_ref(&self.public_key);
|
||||
let sign_key = Key::from_self_private(context, self.addr.clone(), &context.sql)
|
||||
.ok_or_else(|| format_err!("missing own private key"))?;
|
||||
|
||||
let raw_message = mail_to_encrypt.build().as_string().into_bytes();
|
||||
|
||||
let ctext = pgp::pk_encrypt(&raw_message, &keyring, Some(&sign_key))?;
|
||||
|
||||
Ok(ctext)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_decrypt(
|
||||
context: &Context,
|
||||
in_out_message: *mut Mailmime,
|
||||
) -> Result<(bool, HashSet<String>, HashSet<String>)> {
|
||||
// just a pointer into mailmime structure, must not be freed
|
||||
let imffields = mailmime_find_mailimf_fields(in_out_message);
|
||||
ensure!(
|
||||
!in_out_message.is_null() && !imffields.is_null(),
|
||||
"corrupt invalid mime inputs"
|
||||
);
|
||||
|
||||
let from = wrapmime::get_field_from(imffields)?;
|
||||
let message_time = wrapmime::get_field_date(imffields)?;
|
||||
mail: &ParsedMail<'_>,
|
||||
message_time: i64,
|
||||
) -> Result<(Option<Vec<u8>>, HashSet<String>)> {
|
||||
let from = mail
|
||||
.headers
|
||||
.get_first_value("From")?
|
||||
.and_then(|from_addr| mailparse::addrparse(&from_addr).ok())
|
||||
.and_then(|from| from.extract_single_info())
|
||||
.map(|from| from.addr)
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut peerstate = None;
|
||||
let autocryptheader = Aheader::from_imffields(&from, imffields);
|
||||
let autocryptheader = Aheader::from_headers(context, &from, &mail.headers);
|
||||
|
||||
if message_time > 0 {
|
||||
peerstate = Peerstate::from_addr(context, &context.sql, &from);
|
||||
@@ -282,9 +138,7 @@ pub fn try_decrypt(
|
||||
if let Some(ref header) = autocryptheader {
|
||||
peerstate.apply_header(&header, message_time);
|
||||
peerstate.save_to_db(&context.sql, false)?;
|
||||
} else if message_time > peerstate.last_seen_autocrypt
|
||||
&& !contains_report(in_out_message)
|
||||
{
|
||||
} else if message_time > peerstate.last_seen_autocrypt && !contains_report(mail) {
|
||||
peerstate.degrade_encryption(message_time);
|
||||
peerstate.save_to_db(&context.sql, false)?;
|
||||
}
|
||||
@@ -294,13 +148,12 @@ pub fn try_decrypt(
|
||||
peerstate = Some(p);
|
||||
}
|
||||
}
|
||||
|
||||
/* possibly perform decryption */
|
||||
let mut private_keyring = Keyring::default();
|
||||
let mut public_keyring_for_validate = Keyring::default();
|
||||
let mut encrypted = false;
|
||||
let mut out_mail = None;
|
||||
let mut signatures = HashSet::default();
|
||||
let mut gossipped_addr = HashSet::default();
|
||||
|
||||
let self_addr = context.get_config(Config::ConfiguredAddr);
|
||||
|
||||
if let Some(self_addr) = self_addr {
|
||||
@@ -320,60 +173,16 @@ pub fn try_decrypt(
|
||||
}
|
||||
}
|
||||
|
||||
let mut gossip_headers = ptr::null_mut();
|
||||
encrypted = decrypt_if_autocrypt_message(
|
||||
out_mail = decrypt_if_autocrypt_message(
|
||||
context,
|
||||
in_out_message,
|
||||
mail,
|
||||
&private_keyring,
|
||||
&public_keyring_for_validate,
|
||||
&mut signatures,
|
||||
&mut gossip_headers,
|
||||
)?;
|
||||
if !gossip_headers.is_null() {
|
||||
gossipped_addr =
|
||||
update_gossip_peerstates(context, message_time, imffields, gossip_headers)?;
|
||||
unsafe { mailimf_fields_free(gossip_headers) };
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((encrypted, signatures, gossipped_addr))
|
||||
}
|
||||
|
||||
fn new_data_part(
|
||||
data: *mut libc::c_void,
|
||||
data_bytes: libc::size_t,
|
||||
content_type: &str,
|
||||
default_encoding: u32,
|
||||
) -> Result<*mut Mailmime> {
|
||||
let content = new_content_type(&content_type)?;
|
||||
let mut encoding = ptr::null_mut();
|
||||
if wrapmime::content_type_needs_encoding(content) {
|
||||
encoding = unsafe { mailmime_mechanism_new(default_encoding as i32, ptr::null_mut()) };
|
||||
ensure!(!encoding.is_null(), "failed to create encoding");
|
||||
}
|
||||
let mime_fields = {
|
||||
unsafe {
|
||||
mailmime_fields_new_with_data(
|
||||
encoding,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
}
|
||||
};
|
||||
ensure!(!mime_fields.is_null(), "internal mime error");
|
||||
|
||||
let mime = unsafe { mailmime_new_empty(content, mime_fields) };
|
||||
ensure!(!mime.is_null(), "internal mime error");
|
||||
|
||||
if unsafe { (*mime).mm_type } == MAILMIME_SINGLE as libc::c_int {
|
||||
if !data.is_null() && data_bytes > 0 {
|
||||
unsafe { mailmime_set_body_text(mime, data as *mut libc::c_char, data_bytes) };
|
||||
}
|
||||
}
|
||||
|
||||
Ok(mime)
|
||||
Ok((out_mail, signatures))
|
||||
}
|
||||
|
||||
/// Load public key from database or generate a new one.
|
||||
@@ -400,7 +209,7 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
|
||||
);
|
||||
match pgp::create_keypair(&self_addr) {
|
||||
Some((public_key, private_key)) => {
|
||||
match dc_key_save_self_keypair(
|
||||
if dc_key_save_self_keypair(
|
||||
context,
|
||||
&public_key,
|
||||
&private_key,
|
||||
@@ -408,219 +217,105 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
|
||||
true,
|
||||
&context.sql,
|
||||
) {
|
||||
true => {
|
||||
info!(
|
||||
context,
|
||||
"Keypair generated in {:.3}s.",
|
||||
start.elapsed().as_secs()
|
||||
);
|
||||
Ok(public_key)
|
||||
}
|
||||
false => Err(format_err!("Failed to save keypair")),
|
||||
info!(
|
||||
context,
|
||||
"Keypair generated in {:.3}s.",
|
||||
start.elapsed().as_secs()
|
||||
);
|
||||
Ok(public_key)
|
||||
} else {
|
||||
Err(format_err!("Failed to save keypair"))
|
||||
}
|
||||
}
|
||||
None => Err(format_err!("Failed to generate keypair")),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_gossip_peerstates(
|
||||
context: &Context,
|
||||
message_time: i64,
|
||||
imffields: *mut mailimf_fields,
|
||||
gossip_headers: *const mailimf_fields,
|
||||
) -> Result<HashSet<String>> {
|
||||
// XXX split the parsing from the modification part
|
||||
let mut recipients: Option<HashSet<String>> = None;
|
||||
let mut gossipped_addr: HashSet<String> = Default::default();
|
||||
/// Returns a reference to the encrypted payload and validates the autocrypt structure.
|
||||
fn get_autocrypt_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Result<&'a ParsedMail<'b>> {
|
||||
ensure!(
|
||||
mail.ctype.mimetype == "multipart/encrypted",
|
||||
"Not a multipart/encrypted message: {}",
|
||||
mail.ctype.mimetype
|
||||
);
|
||||
ensure!(
|
||||
mail.subparts.len() == 2,
|
||||
"Invalid Autocrypt Level 1 Mime Parts"
|
||||
);
|
||||
|
||||
for cur_data in unsafe { (*(*gossip_headers).fld_list).into_iter() } {
|
||||
let field = cur_data as *mut mailimf_field;
|
||||
if field.is_null() {
|
||||
continue;
|
||||
}
|
||||
ensure!(
|
||||
mail.subparts[0].ctype.mimetype == "application/pgp-encrypted",
|
||||
"Invalid Autocrypt Level 1 version part: {:?}",
|
||||
mail.subparts[0].ctype,
|
||||
);
|
||||
|
||||
let field = unsafe { *field };
|
||||
ensure!(
|
||||
mail.subparts[1].ctype.mimetype == "application/octet-stream",
|
||||
"Invalid Autocrypt Level 1 encrypted part: {:?}",
|
||||
mail.subparts[1].ctype
|
||||
);
|
||||
|
||||
if field.fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
|
||||
let optional_field = unsafe { field.fld_data.fld_optional_field };
|
||||
if optional_field.is_null() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let optional_field = unsafe { *optional_field };
|
||||
if !optional_field.fld_name.is_null()
|
||||
&& to_string_lossy(optional_field.fld_name) == "Autocrypt-Gossip"
|
||||
{
|
||||
let value = to_string_lossy(optional_field.fld_value);
|
||||
let gossip_header = Aheader::from_str(&value);
|
||||
|
||||
if let Ok(ref header) = gossip_header {
|
||||
if recipients.is_none() {
|
||||
recipients = Some(mailimf_get_recipients(imffields));
|
||||
}
|
||||
if recipients.as_ref().unwrap().contains(&header.addr) {
|
||||
let mut peerstate =
|
||||
Peerstate::from_addr(context, &context.sql, &header.addr);
|
||||
if let Some(ref mut peerstate) = peerstate {
|
||||
peerstate.apply_gossip(header, message_time);
|
||||
peerstate.save_to_db(&context.sql, false)?;
|
||||
} else {
|
||||
let p = Peerstate::from_gossip(context, header, message_time);
|
||||
p.save_to_db(&context.sql, true)?;
|
||||
peerstate = Some(p);
|
||||
}
|
||||
if let Some(peerstate) = peerstate {
|
||||
if peerstate.degrade_event.is_some() {
|
||||
handle_degrade_event(context, &peerstate)?;
|
||||
}
|
||||
}
|
||||
|
||||
gossipped_addr.insert(header.addr.clone());
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"Ignoring gossipped \"{}\" as the address is not in To/Cc list.",
|
||||
&header.addr,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(gossipped_addr)
|
||||
Ok(&mail.subparts[1])
|
||||
}
|
||||
|
||||
fn decrypt_if_autocrypt_message(
|
||||
fn decrypt_if_autocrypt_message<'a>(
|
||||
context: &Context,
|
||||
mime_undetermined: *mut Mailmime,
|
||||
mail: &ParsedMail<'a>,
|
||||
private_keyring: &Keyring,
|
||||
public_keyring_for_validate: &Keyring,
|
||||
ret_valid_signatures: &mut HashSet<String>,
|
||||
ret_gossip_headers: *mut *mut mailimf_fields,
|
||||
) -> Result<bool> {
|
||||
/* The returned bool is true if we detected an Autocrypt-encrypted
|
||||
message and successfully decrypted it. Decryption then modifies the
|
||||
passed in mime structure in place. The returned bool is false
|
||||
if it was not an Autocrypt message.
|
||||
) -> Result<Option<Vec<u8>>> {
|
||||
// The returned bool is true if we detected an Autocrypt-encrypted
|
||||
// message and successfully decrypted it. Decryption then modifies the
|
||||
// passed in mime structure in place. The returned bool is false
|
||||
// if it was not an Autocrypt message.
|
||||
//
|
||||
// Errors are returned for failures related to decryption of AC-messages.
|
||||
|
||||
Errors are returned for failures related to decryption of AC-messages.
|
||||
*/
|
||||
ensure!(!mime_undetermined.is_null(), "Invalid mime reference");
|
||||
|
||||
let (mime, encrypted_data_part) = match wrapmime::get_autocrypt_mime(mime_undetermined) {
|
||||
let encrypted_data_part = match get_autocrypt_mime(mail) {
|
||||
Err(_) => {
|
||||
// not a proper autocrypt message, abort and ignore
|
||||
return Ok(false);
|
||||
// not an autocrypt mime message, abort and ignore
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(res) => res,
|
||||
};
|
||||
info!(context, "Detected Autocrypt-mime message");
|
||||
|
||||
let decrypted_mime = decrypt_part(
|
||||
decrypt_part(
|
||||
context,
|
||||
encrypted_data_part,
|
||||
private_keyring,
|
||||
public_keyring_for_validate,
|
||||
ret_valid_signatures,
|
||||
)?;
|
||||
// decrypted_mime is a dangling pointer which we now put into mailmime's Ownership
|
||||
unsafe {
|
||||
mailmime_substitute(mime, decrypted_mime);
|
||||
mailmime_free(mime);
|
||||
}
|
||||
|
||||
// finally, let's also return gossip headers
|
||||
// XXX better return parsed headers so that upstream
|
||||
// does not need to dive into mmime-stuff again.
|
||||
unsafe {
|
||||
if (*ret_gossip_headers).is_null() && !ret_valid_signatures.is_empty() {
|
||||
let mut dummy: libc::size_t = 0;
|
||||
let mut test: *mut mailimf_fields = ptr::null_mut();
|
||||
if mailimf_envelope_and_optional_fields_parse(
|
||||
(*decrypted_mime).mm_mime_start,
|
||||
(*decrypted_mime).mm_length,
|
||||
&mut dummy,
|
||||
&mut test,
|
||||
) == MAILIMF_NO_ERROR as libc::c_int
|
||||
&& !test.is_null()
|
||||
{
|
||||
*ret_gossip_headers = test;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns Ok(None) if nothing encrypted was found.
|
||||
fn decrypt_part(
|
||||
_context: &Context,
|
||||
mime: *mut Mailmime,
|
||||
mail: &ParsedMail<'_>,
|
||||
private_keyring: &Keyring,
|
||||
public_keyring_for_validate: &Keyring,
|
||||
ret_valid_signatures: &mut HashSet<String>,
|
||||
) -> Result<*mut Mailmime> {
|
||||
let mime_data: *mut mailmime_data;
|
||||
let mut mime_transfer_encoding = MAILMIME_MECHANISM_BINARY as libc::c_int;
|
||||
|
||||
unsafe {
|
||||
mime_data = (*mime).mm_data.mm_single;
|
||||
}
|
||||
if !wrapmime::has_decryptable_data(mime_data) {
|
||||
return Ok(ptr::null_mut());
|
||||
}
|
||||
|
||||
if let Some(enc) = wrapmime::get_mime_transfer_encoding(mime) {
|
||||
mime_transfer_encoding = enc;
|
||||
}
|
||||
|
||||
let data: Vec<u8> = wrapmime::decode_dt_data(mime_data, mime_transfer_encoding)?;
|
||||
|
||||
let mut ret_decrypted_mime = ptr::null_mut();
|
||||
) -> Result<Option<Vec<u8>>> {
|
||||
let data = mail.get_body_raw()?;
|
||||
|
||||
if has_decrypted_pgp_armor(&data) {
|
||||
// we should only have one decryption happening
|
||||
ensure!(ret_valid_signatures.is_empty(), "corrupt signatures");
|
||||
|
||||
let plain = match pgp::pk_decrypt(
|
||||
let plain = pgp::pk_decrypt(
|
||||
&data,
|
||||
&private_keyring,
|
||||
&public_keyring_for_validate,
|
||||
Some(ret_valid_signatures),
|
||||
) {
|
||||
Ok(plain) => {
|
||||
ensure!(!ret_valid_signatures.is_empty(), "no valid signatures");
|
||||
plain
|
||||
}
|
||||
Err(err) => bail!("could not decrypt: {}", err),
|
||||
};
|
||||
let plain_bytes = plain.len();
|
||||
let plain_buf = plain.as_ptr() as *const libc::c_char;
|
||||
)?;
|
||||
|
||||
let mut index = 0;
|
||||
let mut decrypted_mime = ptr::null_mut();
|
||||
if unsafe {
|
||||
mailmime_parse(
|
||||
plain_buf as *const _,
|
||||
plain_bytes,
|
||||
&mut index,
|
||||
&mut decrypted_mime,
|
||||
)
|
||||
} != MAIL_NO_ERROR as libc::c_int
|
||||
|| decrypted_mime.is_null()
|
||||
{
|
||||
if !decrypted_mime.is_null() {
|
||||
unsafe { mailmime_free(decrypted_mime) };
|
||||
}
|
||||
} else {
|
||||
// decrypted_mime points into `plain`.
|
||||
// FIXME(@dignifiedquire): this still leaks memory I believe, as mailmime_free
|
||||
// does not free the underlying buffer. But for now we have to live with it
|
||||
std::mem::forget(plain);
|
||||
ret_decrypted_mime = decrypted_mime;
|
||||
}
|
||||
ensure!(!ret_valid_signatures.is_empty(), "no valid signatures");
|
||||
return Ok(Some(plain));
|
||||
}
|
||||
|
||||
Ok(ret_decrypted_mime)
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn has_decrypted_pgp_armor(input: &[u8]) -> bool {
|
||||
@@ -644,36 +339,8 @@ fn has_decrypted_pgp_armor(input: &[u8]) -> bool {
|
||||
/// However, Delta Chat itself has no problem with encrypted multipart/report
|
||||
/// parts and MUAs should be encouraged to encrpyt multipart/reports as well so
|
||||
/// that we could use the normal Autocrypt processing.
|
||||
fn contains_report(mime: *mut Mailmime) -> bool {
|
||||
assert!(!mime.is_null());
|
||||
let mime = unsafe { *mime };
|
||||
|
||||
if mime.mm_type == MAILMIME_MULTIPLE as libc::c_int {
|
||||
let tp_type = unsafe { (*(*mime.mm_content_type).ct_type).tp_type };
|
||||
let ct_type =
|
||||
unsafe { (*(*(*mime.mm_content_type).ct_type).tp_data.tp_composite_type).ct_type };
|
||||
|
||||
if tp_type == MAILMIME_TYPE_COMPOSITE_TYPE as libc::c_int
|
||||
&& ct_type == MAILMIME_COMPOSITE_TYPE_MULTIPART as libc::c_int
|
||||
&& to_string_lossy(unsafe { (*mime.mm_content_type).ct_subtype }) == "report"
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for cur_data in unsafe { (*(*mime.mm_mime_fields).fld_list).into_iter() } {
|
||||
if contains_report(cur_data as *mut Mailmime) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if mime.mm_type == MAILMIME_MESSAGE as libc::c_int {
|
||||
let m = unsafe { mime.mm_data.mm_message.mm_msg_mime };
|
||||
|
||||
if contains_report(m) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
fn contains_report(mail: &ParsedMail<'_>) -> bool {
|
||||
mail.ctype.mimetype == "multipart/report"
|
||||
}
|
||||
|
||||
/// Ensures a private key exists for the configured user.
|
||||
@@ -699,7 +366,6 @@ pub fn ensure_secret_key_exists(context: &Context) -> Result<String> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use libc::free;
|
||||
|
||||
use crate::test_utils::*;
|
||||
|
||||
@@ -722,7 +388,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_mailmime_parse() {
|
||||
let plain = b"Chat-Disposition-Notification-To: holger@deltachat.de
|
||||
let plain = b"Chat-Disposition-Notification-To: hello@world.de
|
||||
Chat-Group-ID: CovhGgau8M-
|
||||
Chat-Group-Name: Delta Chat Dev
|
||||
Subject: =?utf-8?Q?Chat=3A?= Delta Chat =?utf-8?Q?Dev=3A?= sidenote for
|
||||
@@ -730,35 +396,18 @@ Subject: =?utf-8?Q?Chat=3A?= Delta Chat =?utf-8?Q?Dev=3A?= sidenote for
|
||||
Content-Type: text/plain; charset=\"utf-8\"; protected-headers=\"v1\"
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
sidenote for all: rust core master is broken currently ... so dont recomm=
|
||||
end to try to run with desktop or ios unless you are ready to hunt bugs
|
||||
sidenote for all: things are trick atm recomm=
|
||||
end not to try to run with desktop or ios unless you are ready to hunt bugs
|
||||
|
||||
-- =20
|
||||
Sent with my Delta Chat Messenger: https://delta.chat";
|
||||
let plain_bytes = plain.len();
|
||||
let plain_buf = plain.as_ptr() as *const libc::c_char;
|
||||
let mail = mailparse::parse_mail(plain).expect("failed to parse valid message");
|
||||
|
||||
let mut index = 0;
|
||||
let mut decrypted_mime = std::ptr::null_mut();
|
||||
|
||||
let res = unsafe {
|
||||
mailmime_parse(
|
||||
plain_buf as *const _,
|
||||
plain_bytes,
|
||||
&mut index,
|
||||
&mut decrypted_mime,
|
||||
)
|
||||
};
|
||||
unsafe {
|
||||
let msg1 = (*decrypted_mime).mm_data.mm_message.mm_msg_mime;
|
||||
let data = mailmime_transfer_decode(msg1).unwrap();
|
||||
println!("{:?}", String::from_utf8_lossy(&data));
|
||||
}
|
||||
|
||||
assert_eq!(res, 0);
|
||||
assert!(!decrypted_mime.is_null());
|
||||
|
||||
unsafe { free(decrypted_mime as *mut _) };
|
||||
assert_eq!(mail.headers.len(), 6);
|
||||
assert!(
|
||||
mail.get_body().unwrap().starts_with(
|
||||
"sidenote for all: things are trick atm recommend not to try to run with desktop or ios unless you are ready to hunt bugs")
|
||||
);
|
||||
}
|
||||
|
||||
mod load_or_generate_self_public_key {
|
||||
|
||||
80
src/error.rs
@@ -1,19 +1,13 @@
|
||||
use failure::Fail;
|
||||
//! # Error handling
|
||||
|
||||
use lettre_email::mime;
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Error {
|
||||
#[fail(display = "Sqlite Error: {:?}", _0)]
|
||||
Sql(rusqlite::Error),
|
||||
#[fail(display = "Sqlite Connection Pool Error: {:?}", _0)]
|
||||
ConnectionPool(r2d2::Error),
|
||||
#[fail(display = "{:?}", _0)]
|
||||
Failure(failure::Error),
|
||||
#[fail(display = "Sqlite: Connection closed")]
|
||||
SqlNoConnection,
|
||||
#[fail(display = "Sqlite: Already open")]
|
||||
SqlAlreadyOpen,
|
||||
#[fail(display = "Sqlite: Failed to open")]
|
||||
SqlFailedToOpen,
|
||||
#[fail(display = "SQL error: {:?}", _0)]
|
||||
SqlError(#[cause] crate::sql::Error),
|
||||
#[fail(display = "{:?}", _0)]
|
||||
Io(std::io::Error),
|
||||
#[fail(display = "{:?}", _0)]
|
||||
@@ -22,8 +16,6 @@ pub enum Error {
|
||||
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),
|
||||
#[fail(display = "Base64Decode: {:?}", _0)]
|
||||
@@ -36,31 +28,21 @@ pub enum Error {
|
||||
InvalidMsgId,
|
||||
#[fail(display = "Watch folder not found {:?}", _0)]
|
||||
WatchFolderNotFound(String),
|
||||
#[fail(display = "Connection Failed params: {}", _0)]
|
||||
ImapConnectionFailed(String),
|
||||
#[fail(display = "Could not get OAUTH token")]
|
||||
ImapOauthError,
|
||||
#[fail(display = "Could not login as {}", _0)]
|
||||
ImapLoginFailed(String),
|
||||
#[fail(display = "Cannot idle")]
|
||||
ImapMissesIdle,
|
||||
#[fail(display = "Imap IDLE protocol failed to init/complete")]
|
||||
ImapIdleProtocolFailed(String),
|
||||
#[fail(display = "Imap IDLE failed to select folder {:?}", _0)]
|
||||
ImapSelectFailed(String),
|
||||
#[fail(display = "Connect without configured params")]
|
||||
ConnectWithoutConfigure,
|
||||
#[fail(display = "imap operation attempted while imap is torn down")]
|
||||
ImapInTeardown,
|
||||
#[fail(display = "No IMAP Connection established")]
|
||||
ImapNoConnection,
|
||||
#[fail(display = "Invalid Email: {:?}", _0)]
|
||||
MailParseError(#[cause] mailparse::MailParseError),
|
||||
#[fail(display = "Building invalid Email: {:?}", _0)]
|
||||
LettreError(#[cause] lettre_email::error::Error),
|
||||
#[fail(display = "FromStr error: {:?}", _0)]
|
||||
FromStr(#[cause] mime::FromStrError),
|
||||
#[fail(display = "Not Configured")]
|
||||
NotConfigured,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl From<rusqlite::Error> for Error {
|
||||
fn from(err: rusqlite::Error) -> Error {
|
||||
Error::Sql(err)
|
||||
impl From<crate::sql::Error> for Error {
|
||||
fn from(err: crate::sql::Error) -> Error {
|
||||
Error::SqlError(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,12 +58,6 @@ impl From<failure::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<r2d2::Error> for Error {
|
||||
fn from(err: r2d2::Error) -> Error {
|
||||
Error::ConnectionPool(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(err: std::io::Error) -> Error {
|
||||
Error::Io(err)
|
||||
@@ -100,12 +76,6 @@ impl From<image_meta::ImageError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::dc_tools::CStringError> for Error {
|
||||
fn from(err: crate::dc_tools::CStringError) -> Error {
|
||||
Error::CStringError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<pgp::errors::Error> for Error {
|
||||
fn from(err: pgp::errors::Error) -> Error {
|
||||
Error::Pgp(err)
|
||||
@@ -130,6 +100,24 @@ impl From<crate::message::InvalidMsgId> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mailparse::MailParseError> for Error {
|
||||
fn from(err: mailparse::MailParseError) -> Error {
|
||||
Error::MailParseError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<lettre_email::error::Error> for Error {
|
||||
fn from(err: lettre_email::error::Error) -> Error {
|
||||
Error::LettreError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mime::FromStrError> for Error {
|
||||
fn from(err: mime::FromStrError) -> Error {
|
||||
Error::FromStr(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! bail {
|
||||
($e:expr) => {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! # Events specification
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use strum::EnumProperty;
|
||||
@@ -90,7 +92,7 @@ pub enum Event {
|
||||
/// 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
|
||||
/// failed (returned false). It should be sufficient to report only the *last* error
|
||||
/// in a messasge box then.
|
||||
///
|
||||
/// @return
|
||||
@@ -243,4 +245,11 @@ pub enum Event {
|
||||
/// @return 0
|
||||
#[strum(props(id = "2061"))]
|
||||
SecurejoinJoinerProgress { contact_id: u32, progress: usize },
|
||||
|
||||
/// This event is sent out to the inviter when a joiner successfully joined a group.
|
||||
/// @param data1 (int) chat_id
|
||||
/// @param data2 (int) contact_id
|
||||
/// @return 0
|
||||
#[strum(props(id = "2062"))]
|
||||
SecurejoinMemberAdded { chat_id: u32, contact_id: u32 },
|
||||
}
|
||||
|
||||
58
src/headerdef.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
#[derive(Debug, Display, Clone, PartialEq, Eq, EnumVariantNames)]
|
||||
#[strum(serialize_all = "kebab_case")]
|
||||
#[allow(dead_code)]
|
||||
pub enum HeaderDef {
|
||||
MessageId,
|
||||
Subject,
|
||||
Date,
|
||||
From_,
|
||||
To,
|
||||
Cc,
|
||||
Disposition,
|
||||
OriginalMessageId,
|
||||
ListId,
|
||||
References,
|
||||
InReplyTo,
|
||||
Precedence,
|
||||
ChatVersion,
|
||||
ChatGroupId,
|
||||
ChatGroupName,
|
||||
ChatGroupNameChanged,
|
||||
ChatVerified,
|
||||
ChatGroupImage, // deprecated
|
||||
ChatGroupAvatar,
|
||||
ChatUserAvatar,
|
||||
ChatVoiceMessage,
|
||||
ChatGroupMemberRemoved,
|
||||
ChatGroupMemberAdded,
|
||||
ChatContent,
|
||||
ChatDuration,
|
||||
ChatDispositionNotificationTo,
|
||||
AutocryptSetupMessage,
|
||||
SecureJoin,
|
||||
SecureJoinGroup,
|
||||
SecureJoinFingerprint,
|
||||
SecureJoinInvitenumber,
|
||||
SecureJoinAuth,
|
||||
_TestHeader,
|
||||
}
|
||||
|
||||
impl HeaderDef {
|
||||
/// Returns the corresponding Event id.
|
||||
pub fn get_headername(&self) -> String {
|
||||
self.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
/// Test that kebab_case serialization works as expected
|
||||
fn kebab_test() {
|
||||
assert_eq!(HeaderDef::From_.to_string(), "from");
|
||||
|
||||
assert_eq!(HeaderDef::_TestHeader.to_string(), "test-header");
|
||||
}
|
||||
}
|
||||
1440
src/imap.rs
273
src/imap/idle.rs
Normal file
@@ -0,0 +1,273 @@
|
||||
use super::Imap;
|
||||
|
||||
use async_imap::extensions::idle::IdleResponse;
|
||||
use async_std::prelude::*;
|
||||
use async_std::task;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::imap_client::*;
|
||||
|
||||
use super::select_folder;
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Error {
|
||||
#[fail(display = "IMAP IDLE protocol failed to init/complete")]
|
||||
IdleProtocolFailed(#[cause] async_imap::error::Error),
|
||||
|
||||
#[fail(display = "IMAP IDLE protocol timed out")]
|
||||
IdleTimeout(#[cause] async_std::future::TimeoutError),
|
||||
|
||||
#[fail(display = "IMAP server does not have IDLE capability")]
|
||||
IdleAbilityMissing,
|
||||
|
||||
#[fail(display = "IMAP select folder error")]
|
||||
SelectFolderError(#[cause] select_folder::Error),
|
||||
|
||||
#[fail(display = "IMAP error")]
|
||||
ImapError(#[cause] async_imap::error::Error),
|
||||
|
||||
#[fail(display = "Setup handle error")]
|
||||
SetupHandleError(#[cause] super::Error),
|
||||
}
|
||||
|
||||
impl From<select_folder::Error> for Error {
|
||||
fn from(err: select_folder::Error) -> Error {
|
||||
Error::SelectFolderError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl Imap {
|
||||
pub fn can_idle(&self) -> bool {
|
||||
task::block_on(async move { self.config.read().await.can_idle })
|
||||
}
|
||||
|
||||
pub fn idle(&self, context: &Context, watch_folder: Option<String>) -> Result<()> {
|
||||
task::block_on(async move {
|
||||
if !self.can_idle() {
|
||||
return Err(Error::IdleAbilityMissing);
|
||||
}
|
||||
|
||||
self.setup_handle_if_needed(context)
|
||||
.await
|
||||
.map_err(Error::SetupHandleError)?;
|
||||
|
||||
self.select_folder(context, watch_folder.clone()).await?;
|
||||
|
||||
let session = self.session.lock().await.take();
|
||||
let timeout = Duration::from_secs(23 * 60);
|
||||
if let Some(session) = session {
|
||||
match session.idle() {
|
||||
// BEWARE: If you change the Secure branch you
|
||||
// typically also need to change the Insecure branch.
|
||||
IdleHandle::Secure(mut handle) => {
|
||||
if let Err(err) = handle.init().await {
|
||||
return Err(Error::IdleProtocolFailed(err));
|
||||
}
|
||||
|
||||
let (idle_wait, interrupt) = handle.wait_with_timeout(timeout);
|
||||
*self.interrupt.lock().await = Some(interrupt);
|
||||
|
||||
if self.skip_next_idle_wait.load(Ordering::SeqCst) {
|
||||
// interrupt_idle has happened before we
|
||||
// provided self.interrupt
|
||||
self.skip_next_idle_wait.store(false, Ordering::SeqCst);
|
||||
std::mem::drop(idle_wait);
|
||||
info!(context, "Idle wait was skipped");
|
||||
} else {
|
||||
info!(context, "Idle entering wait-on-remote state");
|
||||
match idle_wait.await {
|
||||
IdleResponse::NewData(_) => {
|
||||
info!(context, "Idle has NewData");
|
||||
}
|
||||
// TODO: idle_wait does not distinguish manual interrupts
|
||||
// from Timeouts if we would know it's a Timeout we could bail
|
||||
// directly and reconnect .
|
||||
IdleResponse::Timeout => {
|
||||
info!(context, "Idle-wait timeout or interruption");
|
||||
}
|
||||
IdleResponse::ManualInterrupt => {
|
||||
info!(context, "Idle wait was interrupted");
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we can't properly terminate the idle
|
||||
// protocol let's break the connection.
|
||||
let res =
|
||||
async_std::future::timeout(Duration::from_secs(15), handle.done())
|
||||
.await
|
||||
.map_err(|err| {
|
||||
self.trigger_reconnect();
|
||||
Error::IdleTimeout(err)
|
||||
})?;
|
||||
|
||||
match res {
|
||||
Ok(session) => {
|
||||
*self.session.lock().await = Some(Session::Secure(session));
|
||||
}
|
||||
Err(err) => {
|
||||
// if we cannot terminate IDLE it probably
|
||||
// means that we waited long (with idle_wait)
|
||||
// but the network went away/changed
|
||||
self.trigger_reconnect();
|
||||
return Err(Error::IdleProtocolFailed(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
IdleHandle::Insecure(mut handle) => {
|
||||
if let Err(err) = handle.init().await {
|
||||
return Err(Error::IdleProtocolFailed(err));
|
||||
}
|
||||
|
||||
let (idle_wait, interrupt) = handle.wait_with_timeout(timeout);
|
||||
*self.interrupt.lock().await = Some(interrupt);
|
||||
|
||||
if self.skip_next_idle_wait.load(Ordering::SeqCst) {
|
||||
// interrupt_idle has happened before we
|
||||
// provided self.interrupt
|
||||
self.skip_next_idle_wait.store(false, Ordering::SeqCst);
|
||||
std::mem::drop(idle_wait);
|
||||
info!(context, "Idle wait was skipped");
|
||||
} else {
|
||||
info!(context, "Idle entering wait-on-remote state");
|
||||
match idle_wait.await {
|
||||
IdleResponse::NewData(_) => {
|
||||
info!(context, "Idle has NewData");
|
||||
}
|
||||
// TODO: idle_wait does not distinguish manual interrupts
|
||||
// from Timeouts if we would know it's a Timeout we could bail
|
||||
// directly and reconnect .
|
||||
IdleResponse::Timeout => {
|
||||
info!(context, "Idle-wait timeout or interruption");
|
||||
}
|
||||
IdleResponse::ManualInterrupt => {
|
||||
info!(context, "Idle wait was interrupted");
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we can't properly terminate the idle
|
||||
// protocol let's break the connection.
|
||||
let res =
|
||||
async_std::future::timeout(Duration::from_secs(15), handle.done())
|
||||
.await
|
||||
.map_err(|err| {
|
||||
self.trigger_reconnect();
|
||||
Error::IdleTimeout(err)
|
||||
})?;
|
||||
|
||||
match res {
|
||||
Ok(session) => {
|
||||
*self.session.lock().await = Some(Session::Insecure(session));
|
||||
}
|
||||
Err(err) => {
|
||||
// if we cannot terminate IDLE it probably
|
||||
// means that we waited long (with idle_wait)
|
||||
// but the network went away/changed
|
||||
self.trigger_reconnect();
|
||||
return Err(Error::IdleProtocolFailed(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn fake_idle(&self, context: &Context, watch_folder: Option<String>) {
|
||||
// Idle using polling. This is also needed if we're not yet configured -
|
||||
// in this case, we're waiting for a configure job (and an interrupt).
|
||||
task::block_on(async move {
|
||||
let fake_idle_start_time = SystemTime::now();
|
||||
|
||||
info!(context, "IMAP-fake-IDLEing...");
|
||||
|
||||
let interrupt = stop_token::StopSource::new();
|
||||
|
||||
// check every minute if there are new messages
|
||||
// TODO: grow sleep durations / make them more flexible
|
||||
let interval = async_std::stream::interval(Duration::from_secs(60));
|
||||
let mut interrupt_interval = interrupt.stop_token().stop_stream(interval);
|
||||
*self.interrupt.lock().await = Some(interrupt);
|
||||
if self.skip_next_idle_wait.load(Ordering::SeqCst) {
|
||||
// interrupt_idle has happened before we
|
||||
// provided self.interrupt
|
||||
self.skip_next_idle_wait.store(false, Ordering::SeqCst);
|
||||
info!(context, "fake-idle wait was skipped");
|
||||
} else {
|
||||
// loop until we are interrupted or if we fetched something
|
||||
while let Some(_) = interrupt_interval.next().await {
|
||||
// try to connect with proper login params
|
||||
// (setup_handle_if_needed might not know about them if we
|
||||
// never successfully connected)
|
||||
if let Err(err) = self.connect_configured(context) {
|
||||
warn!(context, "fake_idle: could not connect: {}", err);
|
||||
continue;
|
||||
}
|
||||
if self.config.read().await.can_idle {
|
||||
// we only fake-idled because network was gone during IDLE, probably
|
||||
break;
|
||||
}
|
||||
info!(context, "fake_idle is connected");
|
||||
// we are connected, let's see if fetching messages results
|
||||
// in anything. If so, we behave as if IDLE had data but
|
||||
// will have already fetched the messages so perform_*_fetch
|
||||
// will not find any new.
|
||||
|
||||
if let Some(ref watch_folder) = watch_folder {
|
||||
match self.fetch_new_messages(context, watch_folder).await {
|
||||
Ok(res) => {
|
||||
info!(context, "fetch_new_messages returned {:?}", res);
|
||||
if res {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!(context, "could not fetch from folder: {}", err);
|
||||
self.trigger_reconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.interrupt.lock().await.take();
|
||||
|
||||
info!(
|
||||
context,
|
||||
"IMAP-fake-IDLE done after {:.4}s",
|
||||
SystemTime::now()
|
||||
.duration_since(fake_idle_start_time)
|
||||
.unwrap_or_default()
|
||||
.as_millis() as f64
|
||||
/ 1000.,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn interrupt_idle(&self, context: &Context) {
|
||||
task::block_on(async move {
|
||||
let mut interrupt: Option<stop_token::StopSource> = self.interrupt.lock().await.take();
|
||||
if interrupt.is_none() {
|
||||
// idle wait is not running, signal it needs to skip
|
||||
self.skip_next_idle_wait.store(true, Ordering::SeqCst);
|
||||
|
||||
// meanwhile idle-wait may have produced the StopSource
|
||||
interrupt = self.interrupt.lock().await.take();
|
||||
}
|
||||
// let's manually drop the StopSource
|
||||
if interrupt.is_some() {
|
||||
// the imap thread provided us a stop token but might
|
||||
// not have entered idle_wait yet, give it some time
|
||||
// for that to happen. XXX handle this without extra wait
|
||||
// https://github.com/deltachat/deltachat-core-rust/issues/925
|
||||
std::thread::sleep(Duration::from_millis(200));
|
||||
info!(context, "low-level: dropping stop-source to interrupt idle");
|
||||
std::mem::drop(interrupt)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
1273
src/imap/mod.rs
Normal file
113
src/imap/select_folder.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use super::Imap;
|
||||
|
||||
use crate::context::Context;
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Error {
|
||||
#[fail(display = "IMAP Could not obtain imap-session object.")]
|
||||
NoSession,
|
||||
|
||||
#[fail(display = "IMAP Connection Lost or no connection established")]
|
||||
ConnectionLost,
|
||||
|
||||
#[fail(display = "IMAP Folder name invalid: {:?}", _0)]
|
||||
BadFolderName(String),
|
||||
|
||||
#[fail(display = "IMAP close/expunge failed: {}", _0)]
|
||||
CloseExpungeFailed(#[cause] async_imap::error::Error),
|
||||
|
||||
#[fail(display = "IMAP other error: {:?}", _0)]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl Imap {
|
||||
/// select a folder, possibly update uid_validity and, if needed,
|
||||
/// expunge the folder to remove delete-marked messages.
|
||||
pub(super) async fn select_folder<S: AsRef<str>>(
|
||||
&self,
|
||||
context: &Context,
|
||||
folder: Option<S>,
|
||||
) -> Result<()> {
|
||||
if self.session.lock().await.is_none() {
|
||||
let mut cfg = self.config.write().await;
|
||||
cfg.selected_folder = None;
|
||||
cfg.selected_folder_needs_expunge = false;
|
||||
self.trigger_reconnect();
|
||||
return Err(Error::NoSession);
|
||||
}
|
||||
|
||||
// if there is a new folder and the new folder is equal to the selected one, there's nothing to do.
|
||||
// if there is _no_ new folder, we continue as we might want to expunge below.
|
||||
if let Some(ref folder) = folder {
|
||||
if let Some(ref selected_folder) = self.config.read().await.selected_folder {
|
||||
if folder.as_ref() == selected_folder {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deselect existing folder, if needed (it's also done implicitly by SELECT, however, without EXPUNGE then)
|
||||
let needs_expunge = { self.config.read().await.selected_folder_needs_expunge };
|
||||
if needs_expunge {
|
||||
if let Some(ref folder) = self.config.read().await.selected_folder {
|
||||
info!(context, "Expunge messages in \"{}\".", folder);
|
||||
|
||||
// A CLOSE-SELECT is considerably faster than an EXPUNGE-SELECT, see
|
||||
// https://tools.ietf.org/html/rfc3501#section-6.4.2
|
||||
if let Some(ref mut session) = &mut *self.session.lock().await {
|
||||
match session.close().await {
|
||||
Ok(_) => {
|
||||
info!(context, "close/expunge succeeded");
|
||||
}
|
||||
Err(err) => {
|
||||
self.trigger_reconnect();
|
||||
return Err(Error::CloseExpungeFailed(err));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(Error::NoSession);
|
||||
}
|
||||
}
|
||||
self.config.write().await.selected_folder_needs_expunge = false;
|
||||
}
|
||||
|
||||
// select new folder
|
||||
if let Some(ref folder) = folder {
|
||||
if let Some(ref mut session) = &mut *self.session.lock().await {
|
||||
let res = session.select(folder).await;
|
||||
|
||||
// https://tools.ietf.org/html/rfc3501#section-6.3.1
|
||||
// says that if the server reports select failure we are in
|
||||
// authenticated (not-select) state.
|
||||
|
||||
match res {
|
||||
Ok(mailbox) => {
|
||||
let mut config = self.config.write().await;
|
||||
config.selected_folder = Some(folder.as_ref().to_string());
|
||||
config.selected_mailbox = Some(mailbox);
|
||||
Ok(())
|
||||
}
|
||||
Err(async_imap::error::Error::ConnectionLost) => {
|
||||
self.trigger_reconnect();
|
||||
self.config.write().await.selected_folder = None;
|
||||
Err(Error::ConnectionLost)
|
||||
}
|
||||
Err(async_imap::error::Error::Validate(_)) => {
|
||||
Err(Error::BadFolderName(folder.as_ref().to_string()))
|
||||
}
|
||||
Err(err) => {
|
||||
self.config.write().await.selected_folder = None;
|
||||
self.trigger_reconnect();
|
||||
Err(Error::Other(err.to_string()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(Error::NoSession)
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,11 @@ use async_imap::{
|
||||
types::{Capabilities, Fetch, Mailbox, Name},
|
||||
Client as ImapClient, Session as ImapSession,
|
||||
};
|
||||
use async_native_tls::TlsStream;
|
||||
use async_std::net::{self, TcpStream};
|
||||
use async_std::prelude::*;
|
||||
use async_std::sync::Arc;
|
||||
use async_tls::client::TlsStream;
|
||||
|
||||
use crate::login_param::{dc_build_tls_config, CertificateChecks};
|
||||
|
||||
const DCC_IMAP_DEBUG: &str = "DCC_IMAP_DEBUG";
|
||||
use crate::login_param::{dc_build_tls, CertificateChecks};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Client {
|
||||
@@ -38,18 +35,18 @@ impl Client {
|
||||
certificate_checks: CertificateChecks,
|
||||
) -> ImapResult<Self> {
|
||||
let stream = TcpStream::connect(addr).await?;
|
||||
let tls_config = dc_build_tls_config(certificate_checks);
|
||||
let tls_connector: async_tls::TlsConnector = Arc::new(tls_config).into();
|
||||
let tls_stream = tls_connector.connect(domain.as_ref(), stream)?.await?;
|
||||
let tls = dc_build_tls(certificate_checks)?;
|
||||
let tls_connector: async_native_tls::TlsConnector = tls.into();
|
||||
let tls_stream = tls_connector.connect(domain.as_ref(), stream).await?;
|
||||
let mut client = ImapClient::new(tls_stream);
|
||||
if std::env::var(DCC_IMAP_DEBUG).is_ok() {
|
||||
if std::env::var(crate::DCC_IMAP_DEBUG).is_ok() {
|
||||
client.debug = true;
|
||||
}
|
||||
|
||||
let _greeting = client
|
||||
.read_response()
|
||||
.await
|
||||
.expect("failed to read greeting");
|
||||
.ok_or_else(|| ImapError::Bad("failed to read greeting".to_string()))?;
|
||||
|
||||
Ok(Client::Secure(client))
|
||||
}
|
||||
@@ -58,13 +55,13 @@ impl Client {
|
||||
let stream = TcpStream::connect(addr).await?;
|
||||
|
||||
let mut client = ImapClient::new(stream);
|
||||
if std::env::var(DCC_IMAP_DEBUG).is_ok() {
|
||||
if std::env::var(crate::DCC_IMAP_DEBUG).is_ok() {
|
||||
client.debug = true;
|
||||
}
|
||||
let _greeting = client
|
||||
.read_response()
|
||||
.await
|
||||
.expect("failed to read greeting");
|
||||
.ok_or_else(|| ImapError::Bad("failed to read greeting".to_string()))?;
|
||||
|
||||
Ok(Client::Insecure(client))
|
||||
}
|
||||
@@ -72,13 +69,14 @@ impl Client {
|
||||
pub async fn secure<S: AsRef<str>>(
|
||||
self,
|
||||
domain: S,
|
||||
_certificate_checks: CertificateChecks,
|
||||
certificate_checks: CertificateChecks,
|
||||
) -> ImapResult<Client> {
|
||||
match self {
|
||||
Client::Insecure(client) => {
|
||||
let tls = async_tls::TlsConnector::new();
|
||||
let tls = dc_build_tls(certificate_checks)?;
|
||||
let tls_stream = tls.into();
|
||||
|
||||
let client_sec = client.secure(domain, &tls).await?;
|
||||
let client_sec = client.secure(domain, &tls_stream).await?;
|
||||
|
||||
Ok(Client::Secure(client_sec))
|
||||
}
|
||||
|
||||
111
src/imex.rs
@@ -12,7 +12,6 @@ use crate::config::Config;
|
||||
use crate::configure::*;
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_mimeparser::SystemMessage;
|
||||
use crate::dc_tools::*;
|
||||
use crate::e2ee;
|
||||
use crate::error::*;
|
||||
@@ -20,6 +19,7 @@ use crate::events::Event;
|
||||
use crate::job::*;
|
||||
use crate::key::*;
|
||||
use crate::message::{Message, MsgId};
|
||||
use crate::mimeparser::SystemMessage;
|
||||
use crate::param::*;
|
||||
use crate::pgp;
|
||||
use crate::sql::{self, Sql};
|
||||
@@ -53,7 +53,7 @@ pub enum ImexMode {
|
||||
/// For this purpose, the function creates a job that is executed in the IMAP-thread then;
|
||||
/// this requires to call dc_perform_inbox_jobs() regularly.
|
||||
///
|
||||
/// What to do is defined by the _what_ parameter.
|
||||
/// What to do is defined by the *what* parameter.
|
||||
///
|
||||
/// While dc_imex() returns immediately, the started job may take a while,
|
||||
/// you can stop it using dc_stop_ongoing_process(). During execution of the job,
|
||||
@@ -84,27 +84,24 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<Strin
|
||||
let mut newest_backup_time = 0;
|
||||
let mut newest_backup_path: Option<std::path::PathBuf> = None;
|
||||
for dirent in dir_iter {
|
||||
match dirent {
|
||||
Ok(dirent) => {
|
||||
let path = dirent.path();
|
||||
let name = dirent.file_name();
|
||||
let name = name.to_string_lossy();
|
||||
if name.starts_with("delta-chat") && name.ends_with(".bak") {
|
||||
let sql = Sql::new();
|
||||
if sql.open(context, &path, true) {
|
||||
let curr_backup_time = sql
|
||||
.get_raw_config_int(context, "backup_time")
|
||||
.unwrap_or_default();
|
||||
if curr_backup_time > newest_backup_time {
|
||||
newest_backup_path = Some(path);
|
||||
newest_backup_time = curr_backup_time;
|
||||
}
|
||||
info!(context, "backup_time of {} is {}", name, curr_backup_time);
|
||||
sql.close(&context);
|
||||
if let Ok(dirent) = dirent {
|
||||
let path = dirent.path();
|
||||
let name = dirent.file_name();
|
||||
let name = name.to_string_lossy();
|
||||
if name.starts_with("delta-chat") && name.ends_with(".bak") {
|
||||
let sql = Sql::new();
|
||||
if sql.open(context, &path, true) {
|
||||
let curr_backup_time = sql
|
||||
.get_raw_config_int(context, "backup_time")
|
||||
.unwrap_or_default();
|
||||
if curr_backup_time > newest_backup_time {
|
||||
newest_backup_path = Some(path);
|
||||
newest_backup_time = curr_backup_time;
|
||||
}
|
||||
info!(context, "backup_time of {} is {}", name, curr_backup_time);
|
||||
sql.close(&context);
|
||||
}
|
||||
}
|
||||
Err(_) => (),
|
||||
}
|
||||
}
|
||||
match newest_backup_path {
|
||||
@@ -175,7 +172,7 @@ pub fn render_setup_file(context: &Context, passphrase: &str) -> Result<String>
|
||||
);
|
||||
let self_addr = e2ee::ensure_secret_key_exists(context)?;
|
||||
let private_key = Key::from_self_private(context, self_addr, &context.sql)
|
||||
.ok_or(format_err!("Failed to get private key."))?;
|
||||
.ok_or_else(|| format_err!("Failed to get private key."))?;
|
||||
let ac_headers = match context.get_config_bool(Config::E2eeEnabled) {
|
||||
false => None,
|
||||
true => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
|
||||
@@ -222,7 +219,7 @@ pub fn create_setup_code(_context: &Context) -> String {
|
||||
for i in 0..9 {
|
||||
loop {
|
||||
random_val = rng.gen();
|
||||
if !(random_val as usize > 60000) {
|
||||
if random_val as usize <= 60000 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -367,7 +364,7 @@ pub fn normalize_setup_code(s: &str) -> String {
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) -> Result<()> {
|
||||
pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> {
|
||||
ensure!(context.alloc_ongoing(), "could not allocate ongoing");
|
||||
let what: Option<ImexMode> = job.param.get_int(Param::Cmd).and_then(ImexMode::from_i32);
|
||||
let param = job.param.get(Param::Arg).unwrap_or_default();
|
||||
@@ -383,7 +380,7 @@ pub fn job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) -> Result<()> {
|
||||
context.free_ongoing();
|
||||
bail!("Cannot create private key or private key not available.");
|
||||
} else {
|
||||
dc_create_folder(context, ¶m);
|
||||
dc_create_folder(context, ¶m)?;
|
||||
}
|
||||
}
|
||||
let path = Path::new(param);
|
||||
@@ -462,7 +459,9 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
|
||||
|files| {
|
||||
for (processed_files_cnt, file) in files.enumerate() {
|
||||
let (file_name, file_blob) = file?;
|
||||
ensure!(!context.shall_stop_ongoing(), "received stop signal");
|
||||
if context.shall_stop_ongoing() {
|
||||
return Ok(false);
|
||||
}
|
||||
let mut permille = processed_files_cnt * 1000 / total_files_cnt;
|
||||
if permille < 10 {
|
||||
permille = 10
|
||||
@@ -476,25 +475,25 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
|
||||
}
|
||||
|
||||
let path_filename = context.get_blobdir().join(file_name);
|
||||
if dc_write_file(context, &path_filename, &file_blob) {
|
||||
continue;
|
||||
}
|
||||
bail!(
|
||||
"Storage full? Cannot write file {} with {} bytes.",
|
||||
path_filename.display(),
|
||||
file_blob.len(),
|
||||
);
|
||||
dc_write_file(context, &path_filename, &file_blob)?;
|
||||
}
|
||||
Ok(())
|
||||
Ok(true)
|
||||
},
|
||||
);
|
||||
|
||||
res.and_then(|_| {
|
||||
// only delete backup_blobs if all files were successfully extracted
|
||||
sql::execute(context, &context.sql, "DROP TABLE backup_blobs;", params![])?;
|
||||
sql::try_execute(context, &context.sql, "VACUUM;").ok();
|
||||
Ok(())
|
||||
})
|
||||
match res {
|
||||
Ok(all_files_extracted) => {
|
||||
if all_files_extracted {
|
||||
// only delete backup_blobs if all files were successfully extracted
|
||||
sql::execute(context, &context.sql, "DROP TABLE backup_blobs;", params![])?;
|
||||
sql::try_execute(context, &context.sql, "VACUUM;").ok();
|
||||
Ok(())
|
||||
} else {
|
||||
bail!("received stop signal");
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -546,13 +545,13 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
}
|
||||
Ok(()) => {
|
||||
dest_sql.set_raw_config_int(context, "backup_time", now as i32)?;
|
||||
context.call_cb(Event::ImexFileWritten(dest_path_filename.clone()));
|
||||
context.call_cb(Event::ImexFileWritten(dest_path_filename));
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
dest_sql.close(context);
|
||||
|
||||
res
|
||||
Ok(res?)
|
||||
}
|
||||
|
||||
fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
|
||||
@@ -575,16 +574,15 @@ fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
|
||||
info!(context, "EXPORT: total_files_cnt={}", total_files_cnt);
|
||||
// scan directory, pass 2: copy files
|
||||
let dir_handle = std::fs::read_dir(&dir)?;
|
||||
sql.prepare(
|
||||
let exported_all_files = sql.prepare(
|
||||
"INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);",
|
||||
|mut stmt, _| {
|
||||
let mut processed_files_cnt = 0;
|
||||
for entry in dir_handle {
|
||||
let entry = entry?;
|
||||
ensure!(
|
||||
!context.shall_stop_ongoing(),
|
||||
"canceled during export-files"
|
||||
);
|
||||
if context.shall_stop_ongoing() {
|
||||
return Ok(false);
|
||||
}
|
||||
processed_files_cnt += 1;
|
||||
let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10);
|
||||
context.call_cb(Event::ImexProgress(permille));
|
||||
@@ -604,9 +602,10 @@ fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
|
||||
stmt.execute(params![name, buf])?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
Ok(true)
|
||||
},
|
||||
)?;
|
||||
ensure!(exported_all_files, "canceled during export-files");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -686,14 +685,14 @@ fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
let (id, public_key, private_key, is_default) = key_pair?;
|
||||
let id = Some(id).filter(|_| is_default != 0);
|
||||
if let Some(key) = public_key {
|
||||
if !export_key_to_asc_file(context, &dir, id, &key) {
|
||||
if export_key_to_asc_file(context, &dir, id, &key).is_err() {
|
||||
export_errors += 1;
|
||||
}
|
||||
} else {
|
||||
export_errors += 1;
|
||||
}
|
||||
if let Some(key) = private_key {
|
||||
if !export_key_to_asc_file(context, &dir, id, &key) {
|
||||
if export_key_to_asc_file(context, &dir, id, &key).is_err() {
|
||||
export_errors += 1;
|
||||
}
|
||||
} else {
|
||||
@@ -717,8 +716,7 @@ fn export_key_to_asc_file(
|
||||
dir: impl AsRef<Path>,
|
||||
id: Option<i64>,
|
||||
key: &Key,
|
||||
) -> bool {
|
||||
let mut success = false;
|
||||
) -> std::io::Result<()> {
|
||||
let file_name = {
|
||||
let kind = if key.is_public() { "public" } else { "private" };
|
||||
let id = id.map_or("default".into(), |i| i.to_string());
|
||||
@@ -728,14 +726,13 @@ fn export_key_to_asc_file(
|
||||
info!(context, "Exporting key {}", file_name.display());
|
||||
dc_delete_file(context, &file_name);
|
||||
|
||||
if !key.write_asc_to_file(&file_name, context) {
|
||||
let res = key.write_asc_to_file(&file_name, context);
|
||||
if res.is_err() {
|
||||
error!(context, "Cannot write key to {}", file_name.display());
|
||||
} else {
|
||||
context.call_cb(Event::ImexFileWritten(file_name));
|
||||
success = true;
|
||||
}
|
||||
|
||||
success
|
||||
res
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -799,7 +796,7 @@ mod tests {
|
||||
let base64 = include_str!("../test-data/key/public.asc");
|
||||
let key = Key::from_base64(base64, KeyType::Public).unwrap();
|
||||
let blobdir = "$BLOBDIR";
|
||||
assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key));
|
||||
assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key).is_ok());
|
||||
let blobdir = context.ctx.get_blobdir().to_str().unwrap();
|
||||
let filename = format!("{}/public-key-default.asc", blobdir);
|
||||
let bytes = std::fs::read(&filename).unwrap();
|
||||
|
||||
606
src/job.rs
@@ -1,14 +1,21 @@
|
||||
//! # Job module
|
||||
//!
|
||||
//! This module implements a job queue maintained in the SQLite database
|
||||
//! and job types.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
use async_std::task;
|
||||
|
||||
use crate::blob::BlobObject;
|
||||
use crate::chat;
|
||||
use crate::config::Config;
|
||||
use crate::configure::*;
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::context::{Context, PerformJobsNeeded};
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
@@ -18,10 +25,13 @@ use crate::location;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::message::MsgId;
|
||||
use crate::message::{self, Message, MessageState};
|
||||
use crate::mimefactory::{vec_contains_lowercase, Loaded, MimeFactory};
|
||||
use crate::mimefactory::{vec_contains_lowercase, MimeFactory, RenderedEmail};
|
||||
use crate::param::*;
|
||||
use crate::sql;
|
||||
|
||||
// results in ~3 weeks for the last backoff timespan
|
||||
const JOB_RETRIES: u32 = 17;
|
||||
|
||||
/// Thread IDs
|
||||
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
|
||||
#[repr(i32)]
|
||||
@@ -31,6 +41,13 @@ enum Thread {
|
||||
Smtp = 5000,
|
||||
}
|
||||
|
||||
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
||||
enum TryAgain {
|
||||
Dont,
|
||||
AtOnce,
|
||||
StandardDelay,
|
||||
}
|
||||
|
||||
impl Default for Thread {
|
||||
fn default() -> Self {
|
||||
Thread::Unknown
|
||||
@@ -100,13 +117,14 @@ pub struct Job {
|
||||
pub foreign_id: u32,
|
||||
pub desired_timestamp: i64,
|
||||
pub added_timestamp: i64,
|
||||
pub tries: i32,
|
||||
pub tries: u32,
|
||||
pub param: Params,
|
||||
pub try_again: i32,
|
||||
try_again: TryAgain,
|
||||
pub pending_error: Option<String>,
|
||||
}
|
||||
|
||||
impl Job {
|
||||
/// Deletes the job from the database.
|
||||
fn delete(&self, context: &Context) -> bool {
|
||||
context
|
||||
.sql
|
||||
@@ -114,6 +132,9 @@ impl Job {
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Updates the job already stored in the database.
|
||||
///
|
||||
/// To add a new job, use [job_add].
|
||||
fn update(&self, context: &Context) -> bool {
|
||||
sql::execute(
|
||||
context,
|
||||
@@ -130,13 +151,13 @@ impl Job {
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn do_DC_JOB_SEND(&mut self, context: &Context) {
|
||||
fn SendMsgToSmtp(&mut self, context: &Context) {
|
||||
/* connect to SMTP server, if not yet done */
|
||||
if !context.smtp.lock().unwrap().is_connected() {
|
||||
let loginparam = LoginParam::from_database(context, "configured_");
|
||||
let connected = context.smtp.lock().unwrap().connect(context, &loginparam);
|
||||
if connected.is_err() {
|
||||
self.try_again_later(3, None);
|
||||
self.try_again_later(TryAgain::StandardDelay, None);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -146,13 +167,15 @@ impl Job {
|
||||
if let Some(recipients) = self.param.get(Param::Recipients) {
|
||||
let recipients_list = recipients
|
||||
.split('\x1e')
|
||||
.filter_map(|addr| match lettre::EmailAddress::new(addr.to_string()) {
|
||||
Ok(addr) => Some(addr),
|
||||
Err(err) => {
|
||||
warn!(context, "invalid recipient: {} {:?}", addr, err);
|
||||
None
|
||||
}
|
||||
})
|
||||
.filter_map(
|
||||
|addr| match async_smtp::EmailAddress::new(addr.to_string()) {
|
||||
Ok(addr) => Some(addr),
|
||||
Err(err) => {
|
||||
warn!(context, "invalid recipient: {} {:?}", addr, err);
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
/* if there is a msg-id and it does not exist in the db, cancel sending.
|
||||
@@ -173,11 +196,34 @@ impl Job {
|
||||
// was sent we need to mark it in the database ASAP as we
|
||||
// otherwise might send it twice.
|
||||
let mut smtp = context.smtp.lock().unwrap();
|
||||
match smtp.send(context, recipients_list, body, self.job_id) {
|
||||
Err(err) => {
|
||||
if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
|
||||
info!(context, "smtp-sending out mime message:");
|
||||
println!("{}", String::from_utf8_lossy(&body));
|
||||
}
|
||||
match task::block_on(smtp.send(context, recipients_list, body, self.job_id)) {
|
||||
Err(crate::smtp::send::Error::SendTimeout(err)) => {
|
||||
warn!(context, "SMTP send timed out {:?}", err);
|
||||
smtp.disconnect();
|
||||
warn!(context, "smtp failed: {}", err);
|
||||
self.try_again_later(-1, Some(err.to_string()));
|
||||
self.try_again_later(
|
||||
TryAgain::AtOnce,
|
||||
Some("send-timeout".to_string()),
|
||||
);
|
||||
}
|
||||
Err(crate::smtp::send::Error::SendError(err)) => {
|
||||
// Remote error, retry later.
|
||||
warn!(context, "SMTP failed to send: {}", err);
|
||||
smtp.disconnect();
|
||||
self.try_again_later(TryAgain::AtOnce, Some(err.to_string()));
|
||||
}
|
||||
Err(crate::smtp::send::Error::EnvelopeError(err)) => {
|
||||
// Local error, job is invalid, do not retry.
|
||||
smtp.disconnect();
|
||||
warn!(context, "SMTP job is invalid: {}", err);
|
||||
}
|
||||
Err(crate::smtp::send::Error::NoTransport) => {
|
||||
// Should never happen.
|
||||
// It does not even make sense to disconnect here.
|
||||
error!(context, "SMTP job failed because SMTP has no transport");
|
||||
}
|
||||
Ok(()) => {
|
||||
// smtp success, update db ASAP, then delete smtp file
|
||||
@@ -196,23 +242,20 @@ impl Job {
|
||||
}
|
||||
|
||||
// this value does not increase the number of tries
|
||||
fn try_again_later(&mut self, try_again: i32, pending_error: Option<String>) {
|
||||
fn try_again_later(&mut self, try_again: TryAgain, pending_error: Option<String>) {
|
||||
self.try_again = try_again;
|
||||
self.pending_error = pending_error;
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn do_DC_JOB_MOVE_MSG(&mut self, context: &Context) {
|
||||
fn MoveMsg(&mut self, context: &Context) {
|
||||
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
|
||||
|
||||
if let Ok(msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
|
||||
if context
|
||||
.sql
|
||||
.get_raw_config_int(context, "folders_configured")
|
||||
.unwrap_or_default()
|
||||
< 3
|
||||
{
|
||||
imap_inbox.configure_folders(context, 0x1i32);
|
||||
if let Err(err) = imap_inbox.ensure_configured_folders(context, true) {
|
||||
self.try_again_later(TryAgain::StandardDelay, None);
|
||||
warn!(context, "could not configure folders: {:?}", err);
|
||||
return;
|
||||
}
|
||||
let dest_folder = context
|
||||
.sql
|
||||
@@ -230,7 +273,7 @@ impl Job {
|
||||
&mut dest_uid,
|
||||
) {
|
||||
ImapActionResult::RetryLater => {
|
||||
self.try_again_later(3i32, None);
|
||||
self.try_again_later(TryAgain::StandardDelay, None);
|
||||
}
|
||||
ImapActionResult::Success => {
|
||||
message::update_server_uid(
|
||||
@@ -247,7 +290,7 @@ impl Job {
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn do_DC_JOB_DELETE_MSG_ON_IMAP(&mut self, context: &Context) {
|
||||
fn DeleteMsgOnImap(&mut self, context: &Context) {
|
||||
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
|
||||
|
||||
if let Ok(mut msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
|
||||
@@ -266,7 +309,7 @@ impl Job {
|
||||
let res =
|
||||
imap_inbox.delete_msg(context, &mid, server_folder, &mut msg.server_uid);
|
||||
if res == ImapActionResult::RetryLater {
|
||||
self.try_again_later(-1i32, None);
|
||||
self.try_again_later(TryAgain::AtOnce, None);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -276,7 +319,7 @@ impl Job {
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn do_DC_JOB_EMPTY_SERVER(&mut self, context: &Context) {
|
||||
fn EmptyServer(&mut self, context: &Context) {
|
||||
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
|
||||
if self.foreign_id & DC_EMPTY_MVBOX > 0 {
|
||||
if let Some(mvbox_folder) = context
|
||||
@@ -292,14 +335,14 @@ impl Job {
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn do_DC_JOB_MARKSEEN_MSG_ON_IMAP(&mut self, context: &Context) {
|
||||
fn MarkseenMsgOnImap(&mut self, context: &Context) {
|
||||
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
|
||||
|
||||
if let Ok(msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
|
||||
let folder = msg.server_folder.as_ref().unwrap();
|
||||
match imap_inbox.set_seen(context, folder, msg.server_uid) {
|
||||
ImapActionResult::RetryLater => {
|
||||
self.try_again_later(3i32, None);
|
||||
self.try_again_later(TryAgain::StandardDelay, None);
|
||||
}
|
||||
ImapActionResult::AlreadyDone => {}
|
||||
ImapActionResult::Success | ImapActionResult::Failed => {
|
||||
@@ -320,7 +363,7 @@ impl Job {
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn do_DC_JOB_MARKSEEN_MDN_ON_IMAP(&mut self, context: &Context) {
|
||||
fn MarkseenMdnOnImap(&mut self, context: &Context) {
|
||||
let folder = self
|
||||
.param
|
||||
.get(Param::ServerFolder)
|
||||
@@ -329,17 +372,14 @@ impl Job {
|
||||
let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32;
|
||||
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
|
||||
if imap_inbox.set_seen(context, &folder, uid) == ImapActionResult::RetryLater {
|
||||
self.try_again_later(3i32, None);
|
||||
self.try_again_later(TryAgain::StandardDelay, None);
|
||||
return;
|
||||
}
|
||||
if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() {
|
||||
if context
|
||||
.sql
|
||||
.get_raw_config_int(context, "folders_configured")
|
||||
.unwrap_or_default()
|
||||
< 3
|
||||
{
|
||||
imap_inbox.configure_folders(context, 0x1i32);
|
||||
if let Err(err) = imap_inbox.ensure_configured_folders(context, true) {
|
||||
self.try_again_later(TryAgain::StandardDelay, None);
|
||||
warn!(context, "configuring folders failed: {:?}", err);
|
||||
return;
|
||||
}
|
||||
let dest_folder = context
|
||||
.sql
|
||||
@@ -349,7 +389,7 @@ impl Job {
|
||||
if ImapActionResult::RetryLater
|
||||
== imap_inbox.mv(context, &folder, uid, &dest_folder, &mut dest_uid)
|
||||
{
|
||||
self.try_again_later(3, None);
|
||||
self.try_again_later(TryAgain::StandardDelay, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -370,31 +410,37 @@ pub fn job_kill_action(context: &Context, action: Action) -> bool {
|
||||
pub fn perform_inbox_fetch(context: &Context) {
|
||||
let use_network = context.get_config_bool(Config::InboxWatch);
|
||||
|
||||
context
|
||||
.inbox_thread
|
||||
.write()
|
||||
.unwrap()
|
||||
.fetch(context, use_network);
|
||||
task::block_on(
|
||||
context
|
||||
.inbox_thread
|
||||
.write()
|
||||
.unwrap()
|
||||
.fetch(context, use_network),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn perform_mvbox_fetch(context: &Context) {
|
||||
let use_network = context.get_config_bool(Config::MvboxWatch);
|
||||
|
||||
context
|
||||
.mvbox_thread
|
||||
.write()
|
||||
.unwrap()
|
||||
.fetch(context, use_network);
|
||||
task::block_on(
|
||||
context
|
||||
.mvbox_thread
|
||||
.write()
|
||||
.unwrap()
|
||||
.fetch(context, use_network),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn perform_sentbox_fetch(context: &Context) {
|
||||
let use_network = context.get_config_bool(Config::SentboxWatch);
|
||||
|
||||
context
|
||||
.sentbox_thread
|
||||
.write()
|
||||
.unwrap()
|
||||
.fetch(context, use_network);
|
||||
task::block_on(
|
||||
context
|
||||
.sentbox_thread
|
||||
.write()
|
||||
.unwrap()
|
||||
.fetch(context, use_network),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn perform_inbox_idle(context: &Context) {
|
||||
@@ -434,19 +480,19 @@ pub fn perform_sentbox_idle(context: &Context) {
|
||||
.idle(context, use_network);
|
||||
}
|
||||
|
||||
pub fn interrupt_inbox_idle(context: &Context, block: bool) {
|
||||
info!(context, "interrupt_inbox_idle called blocking={}", block);
|
||||
if block {
|
||||
context.inbox_thread.read().unwrap().interrupt_idle(context);
|
||||
} else {
|
||||
match context.inbox_thread.try_read() {
|
||||
Ok(inbox_thread) => {
|
||||
inbox_thread.interrupt_idle(context);
|
||||
}
|
||||
Err(err) => {
|
||||
*context.perform_inbox_jobs_needed.write().unwrap() = true;
|
||||
warn!(context, "could not interrupt idle: {}", err);
|
||||
}
|
||||
pub fn interrupt_inbox_idle(context: &Context) {
|
||||
info!(context, "interrupt_inbox_idle called");
|
||||
// we do not block on trying to obtain the thread lock
|
||||
// because we don't know in which state the thread is.
|
||||
// If it's currently fetching then we can not get the lock
|
||||
// but we flag it for checking jobs so that idle will be skipped.
|
||||
match context.inbox_thread.try_read() {
|
||||
Ok(inbox_thread) => {
|
||||
inbox_thread.interrupt_idle(context);
|
||||
}
|
||||
Err(err) => {
|
||||
*context.perform_inbox_jobs_needed.write().unwrap() = true;
|
||||
warn!(context, "could not interrupt idle: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -470,7 +516,7 @@ pub fn perform_smtp_jobs(context: &Context) {
|
||||
|
||||
let probe_smtp_network = state.probe_network;
|
||||
state.probe_network = false;
|
||||
state.perform_jobs_needed = 0;
|
||||
state.perform_jobs_needed = PerformJobsNeeded::Not;
|
||||
|
||||
if state.suspended {
|
||||
info!(context, "SMTP-jobs suspended.",);
|
||||
@@ -498,24 +544,27 @@ pub fn perform_smtp_idle(context: &Context) {
|
||||
let &(ref lock, ref cvar) = &*context.smtp_state.clone();
|
||||
let mut state = lock.lock().unwrap();
|
||||
|
||||
if state.perform_jobs_needed == 1 {
|
||||
info!(
|
||||
context,
|
||||
"SMTP-idle will not be started because of waiting jobs.",
|
||||
);
|
||||
} else {
|
||||
let dur = get_next_wakeup_time(context, Thread::Smtp);
|
||||
|
||||
loop {
|
||||
let res = cvar.wait_timeout(state, dur).unwrap();
|
||||
state = res.0;
|
||||
|
||||
if state.idle || res.1.timed_out() {
|
||||
// We received the notification and the value has been updated, we can leave.
|
||||
break;
|
||||
}
|
||||
match state.perform_jobs_needed {
|
||||
PerformJobsNeeded::AtOnce => {
|
||||
info!(
|
||||
context,
|
||||
"SMTP-idle will not be started because of waiting jobs.",
|
||||
);
|
||||
}
|
||||
PerformJobsNeeded::Not | PerformJobsNeeded::AvoidDos => {
|
||||
let dur = get_next_wakeup_time(context, Thread::Smtp);
|
||||
|
||||
loop {
|
||||
let res = cvar.wait_timeout(state, dur).unwrap();
|
||||
state = res.0;
|
||||
|
||||
if state.idle || res.1.timed_out() {
|
||||
// We received the notification and the value has been updated, we can leave.
|
||||
break;
|
||||
}
|
||||
}
|
||||
state.idle = false;
|
||||
}
|
||||
state.idle = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,7 +604,7 @@ pub fn maybe_network(context: &Context) {
|
||||
}
|
||||
|
||||
interrupt_smtp_idle(context);
|
||||
interrupt_inbox_idle(context, true);
|
||||
interrupt_inbox_idle(context);
|
||||
interrupt_mvbox_idle(context);
|
||||
interrupt_sentbox_idle(context);
|
||||
}
|
||||
@@ -579,49 +628,34 @@ fn set_delivered(context: &Context, msg_id: MsgId) {
|
||||
.unwrap_or_default();
|
||||
context.call_cb(Event::MsgDelivered {
|
||||
chat_id: chat_id as u32,
|
||||
msg_id: msg_id,
|
||||
msg_id,
|
||||
});
|
||||
}
|
||||
|
||||
/* special case for DC_JOB_SEND_MSG_TO_SMTP */
|
||||
#[allow(non_snake_case)]
|
||||
pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<(), Error> {
|
||||
let mut mimefactory = MimeFactory::load_msg(context, msg_id)?;
|
||||
|
||||
if chat::msgtype_has_file(mimefactory.msg.type_0) {
|
||||
let file_param = mimefactory.msg.param.get_path(Param::File, context)?;
|
||||
if let Some(pathNfilename) = file_param {
|
||||
if (mimefactory.msg.type_0 == Viewtype::Image
|
||||
|| mimefactory.msg.type_0 == Viewtype::Gif)
|
||||
&& !mimefactory.msg.param.exists(Param::Width)
|
||||
{
|
||||
mimefactory.msg.param.set_int(Param::Width, 0);
|
||||
mimefactory.msg.param.set_int(Param::Height, 0);
|
||||
|
||||
if let Ok(buf) = dc_read_file(context, pathNfilename) {
|
||||
if let Ok((width, height)) = dc_get_filemeta(&buf) {
|
||||
mimefactory.msg.param.set_int(Param::Width, width as i32);
|
||||
mimefactory.msg.param.set_int(Param::Height, height as i32);
|
||||
}
|
||||
}
|
||||
mimefactory.msg.save_param_to_disk(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut msg = Message::load_from_db(context, msg_id)?;
|
||||
msg.try_calc_and_set_dimensions(context).ok();
|
||||
|
||||
/* create message */
|
||||
if let Err(msg) = unsafe { mimefactory.render() } {
|
||||
let e = msg.to_string();
|
||||
message::set_msg_failed(context, msg_id, Some(e));
|
||||
return Err(msg);
|
||||
}
|
||||
if 0 != mimefactory
|
||||
.msg
|
||||
.param
|
||||
.get_int(Param::GuaranteeE2ee)
|
||||
.unwrap_or_default()
|
||||
&& !mimefactory.out_encrypted
|
||||
{
|
||||
let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default();
|
||||
|
||||
let attach_selfavatar = match chat::shall_attach_selfavatar(context, msg.chat_id) {
|
||||
Ok(attach_selfavatar) => attach_selfavatar,
|
||||
Err(err) => {
|
||||
warn!(context, "job: cannot get selfavatar-state: {}", err);
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
let mimefactory = MimeFactory::from_msg(context, &msg, attach_selfavatar)?;
|
||||
let mut rendered_msg = mimefactory.render().map_err(|err| {
|
||||
message::set_msg_failed(context, msg_id, Some(err.to_string()));
|
||||
err
|
||||
})?;
|
||||
|
||||
if needs_encryption && !rendered_msg.is_encrypted {
|
||||
/* unrecoverable */
|
||||
message::set_msg_failed(
|
||||
context,
|
||||
@@ -631,19 +665,17 @@ pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<(), Error> {
|
||||
bail!(
|
||||
"e2e encryption unavailable {} - {:?}",
|
||||
msg_id,
|
||||
mimefactory.msg.param.get_int(Param::GuaranteeE2ee),
|
||||
needs_encryption
|
||||
);
|
||||
}
|
||||
|
||||
if context.get_config_bool(Config::BccSelf)
|
||||
&& !vec_contains_lowercase(&mimefactory.recipients_addr, &mimefactory.from_addr)
|
||||
&& !vec_contains_lowercase(&rendered_msg.recipients, &rendered_msg.from)
|
||||
{
|
||||
mimefactory.recipients_names.push("".to_string());
|
||||
mimefactory
|
||||
.recipients_addr
|
||||
.push(mimefactory.from_addr.to_string());
|
||||
rendered_msg.recipients.push(rendered_msg.from.clone());
|
||||
}
|
||||
|
||||
if mimefactory.recipients_addr.is_empty() {
|
||||
if rendered_msg.recipients.is_empty() {
|
||||
// may happen eg. for groups with only SELF and bcc_self disabled
|
||||
info!(
|
||||
context,
|
||||
@@ -653,36 +685,35 @@ pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<(), Error> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if mimefactory.out_gossiped {
|
||||
chat::set_gossiped_timestamp(context, mimefactory.msg.chat_id, time());
|
||||
if rendered_msg.is_gossiped {
|
||||
chat::set_gossiped_timestamp(context, msg.chat_id, time())?;
|
||||
}
|
||||
if 0 != mimefactory.out_last_added_location_id {
|
||||
if let Err(err) = location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
|
||||
{
|
||||
|
||||
if 0 != rendered_msg.last_added_location_id {
|
||||
if let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, time()) {
|
||||
error!(context, "Failed to set kml sent_timestamp: {:?}", err);
|
||||
}
|
||||
if !mimefactory.msg.hidden {
|
||||
if let Err(err) = location::set_msg_location_id(
|
||||
context,
|
||||
mimefactory.msg.id,
|
||||
mimefactory.out_last_added_location_id,
|
||||
) {
|
||||
if !msg.hidden {
|
||||
if let Err(err) =
|
||||
location::set_msg_location_id(context, msg.id, rendered_msg.last_added_location_id)
|
||||
{
|
||||
error!(context, "Failed to set msg_location_id: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
if mimefactory.out_encrypted
|
||||
&& mimefactory
|
||||
.msg
|
||||
.param
|
||||
.get_int(Param::GuaranteeE2ee)
|
||||
.unwrap_or_default()
|
||||
== 0
|
||||
{
|
||||
mimefactory.msg.param.set_int(Param::GuaranteeE2ee, 1);
|
||||
mimefactory.msg.save_param_to_disk(context);
|
||||
|
||||
if attach_selfavatar {
|
||||
if let Err(err) = chat::set_selfavatar_timestamp(context, msg.chat_id, time()) {
|
||||
error!(context, "Failed to set selfavatar timestamp: {:?}", err);
|
||||
}
|
||||
}
|
||||
add_smtp_job(context, Action::SendMsgToSmtp, &mut mimefactory)?;
|
||||
|
||||
if rendered_msg.is_encrypted && !needs_encryption {
|
||||
msg.param.set_int(Param::GuaranteeE2ee, 1);
|
||||
msg.save_param_to_disk(context);
|
||||
}
|
||||
|
||||
add_smtp_job(context, Action::SendMsgToSmtp, &rendered_msg)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -707,54 +738,9 @@ pub fn perform_sentbox_jobs(context: &Context) {
|
||||
}
|
||||
|
||||
fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
let query = if !probe_network {
|
||||
// processing for first-try and after backoff-timeouts:
|
||||
// process jobs in the order they were added.
|
||||
"SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries \
|
||||
FROM jobs WHERE thread=? AND desired_timestamp<=? ORDER BY action DESC, added_timestamp;"
|
||||
} else {
|
||||
// processing after call to dc_maybe_network():
|
||||
// process _all_ pending jobs that failed before
|
||||
// in the order of their backoff-times.
|
||||
"SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries \
|
||||
FROM jobs WHERE thread=? AND tries>0 ORDER BY desired_timestamp, action DESC;"
|
||||
};
|
||||
let jobs: Vec<Job> = load_jobs(context, thread, probe_network);
|
||||
|
||||
let params_no_probe = params![thread as i64, time()];
|
||||
let params_probe = params![thread as i64];
|
||||
let params: &[&dyn rusqlite::ToSql] = if !probe_network {
|
||||
params_no_probe
|
||||
} else {
|
||||
params_probe
|
||||
};
|
||||
|
||||
let jobs: Result<Vec<Job>, _> = context
|
||||
.sql
|
||||
.query_map(
|
||||
query,
|
||||
params,
|
||||
|row| {
|
||||
let job = Job {
|
||||
job_id: row.get(0)?,
|
||||
action: row.get(1)?,
|
||||
foreign_id: row.get(2)?,
|
||||
desired_timestamp: row.get(5)?,
|
||||
added_timestamp: row.get(4)?,
|
||||
tries: row.get(6)?,
|
||||
param: row.get::<_, String>(3)?.parse().unwrap_or_default(),
|
||||
try_again: 0,
|
||||
pending_error: None,
|
||||
};
|
||||
|
||||
Ok(job)
|
||||
},
|
||||
|jobs| jobs.collect::<Result<Vec<Job>, _>>().map_err(Into::into),
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!(context, "query failed: {:?}", err);
|
||||
});
|
||||
|
||||
for mut job in jobs.unwrap_or_default() {
|
||||
for mut job in jobs {
|
||||
info!(
|
||||
context,
|
||||
"{}-job #{}, action {} started...",
|
||||
@@ -788,43 +774,39 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
suspend_smtp_thread(context, true);
|
||||
}
|
||||
|
||||
let mut tries = 0;
|
||||
while tries <= 1 {
|
||||
for _tries in 0..2 {
|
||||
// this can be modified by a job using dc_job_try_again_later()
|
||||
job.try_again = 0;
|
||||
job.try_again = TryAgain::Dont;
|
||||
|
||||
match job.action {
|
||||
Action::Unknown => {
|
||||
warn!(context, "Unknown job id found");
|
||||
info!(context, "Unknown job id found");
|
||||
}
|
||||
Action::SendMsgToSmtp => job.do_DC_JOB_SEND(context),
|
||||
Action::EmptyServer => job.do_DC_JOB_EMPTY_SERVER(context),
|
||||
Action::DeleteMsgOnImap => job.do_DC_JOB_DELETE_MSG_ON_IMAP(context),
|
||||
Action::MarkseenMsgOnImap => job.do_DC_JOB_MARKSEEN_MSG_ON_IMAP(context),
|
||||
Action::MarkseenMdnOnImap => job.do_DC_JOB_MARKSEEN_MDN_ON_IMAP(context),
|
||||
Action::MoveMsg => job.do_DC_JOB_MOVE_MSG(context),
|
||||
Action::SendMdn => job.do_DC_JOB_SEND(context),
|
||||
Action::ConfigureImap => dc_job_do_DC_JOB_CONFIGURE_IMAP(context),
|
||||
Action::ImexImap => match job_do_DC_JOB_IMEX_IMAP(context, &job) {
|
||||
Action::SendMsgToSmtp => job.SendMsgToSmtp(context),
|
||||
Action::EmptyServer => job.EmptyServer(context),
|
||||
Action::DeleteMsgOnImap => job.DeleteMsgOnImap(context),
|
||||
Action::MarkseenMsgOnImap => job.MarkseenMsgOnImap(context),
|
||||
Action::MarkseenMdnOnImap => job.MarkseenMdnOnImap(context),
|
||||
Action::MoveMsg => job.MoveMsg(context),
|
||||
Action::SendMdn => job.SendMsgToSmtp(context),
|
||||
Action::ConfigureImap => JobConfigureImap(context),
|
||||
Action::ImexImap => match JobImexImap(context, &job) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
error!(context, "{}", err);
|
||||
}
|
||||
},
|
||||
Action::MaybeSendLocations => {
|
||||
location::job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context, &job)
|
||||
}
|
||||
Action::MaybeSendLocations => location::JobMaybeSendLocations(context, &job),
|
||||
Action::MaybeSendLocationsEnded => {
|
||||
location::job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context, &mut job)
|
||||
location::JobMaybeSendLocationsEnded(context, &mut job)
|
||||
}
|
||||
Action::Housekeeping => sql::housekeeping(context),
|
||||
Action::SendMdnOld => {}
|
||||
Action::SendMsgToSmtpOld => {}
|
||||
}
|
||||
if job.try_again != -1 {
|
||||
if job.try_again != TryAgain::AtOnce {
|
||||
break;
|
||||
}
|
||||
tries += 1
|
||||
}
|
||||
if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
|
||||
context
|
||||
@@ -841,28 +823,16 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
.unsuspend(context);
|
||||
suspend_smtp_thread(context, false);
|
||||
break;
|
||||
} else if job.try_again == 2 {
|
||||
// just try over next loop unconditionally, the ui typically interrupts idle when the file (video) is ready
|
||||
info!(
|
||||
context,
|
||||
"{}-job #{} not yet ready and will be delayed.",
|
||||
if thread == Thread::Imap {
|
||||
"INBOX"
|
||||
} else {
|
||||
"SMTP"
|
||||
},
|
||||
job.job_id
|
||||
);
|
||||
} else if job.try_again == -1 || job.try_again == 3 {
|
||||
} else if job.try_again == TryAgain::AtOnce || job.try_again == TryAgain::StandardDelay {
|
||||
let tries = job.tries + 1;
|
||||
if tries < 17 {
|
||||
if tries < JOB_RETRIES {
|
||||
job.tries = tries;
|
||||
let time_offset = get_backoff_time_offset(tries);
|
||||
job.desired_timestamp = job.added_timestamp + time_offset;
|
||||
job.desired_timestamp = time() + time_offset;
|
||||
job.update(context);
|
||||
info!(
|
||||
context,
|
||||
"{}-job #{} not succeeded on try #{}, retry in ADD_TIME+{} (in {} seconds).",
|
||||
"{}-job #{} not succeeded on try #{}, retry in {} seconds.",
|
||||
if thread == Thread::Imap {
|
||||
"INBOX"
|
||||
} else {
|
||||
@@ -870,17 +840,16 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
},
|
||||
job.job_id as u32,
|
||||
tries,
|
||||
time_offset,
|
||||
job.added_timestamp + time_offset - time()
|
||||
time_offset
|
||||
);
|
||||
if thread == Thread::Smtp && tries < 17 - 1 {
|
||||
if thread == Thread::Smtp && tries < JOB_RETRIES - 1 {
|
||||
context
|
||||
.smtp_state
|
||||
.clone()
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.perform_jobs_needed = 2;
|
||||
.perform_jobs_needed = PerformJobsNeeded::AvoidDos;
|
||||
}
|
||||
} else {
|
||||
if job.action == Action::SendMsgToSmtp {
|
||||
@@ -906,13 +875,11 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn get_backoff_time_offset(c_tries: libc::c_int) -> i64 {
|
||||
// results in ~3 weeks for the last backoff timespan
|
||||
let N = 2_i32.pow((c_tries - 1) as u32) * 60;
|
||||
fn get_backoff_time_offset(tries: u32) -> i64 {
|
||||
let n = 2_i32.pow(tries - 1) * 60;
|
||||
let mut rng = thread_rng();
|
||||
let n: i32 = rng.gen();
|
||||
let mut seconds = n % (N + 1);
|
||||
let r: i32 = rng.gen();
|
||||
let mut seconds = r % (n + 1);
|
||||
if seconds < 1 {
|
||||
seconds = 1;
|
||||
}
|
||||
@@ -932,38 +899,40 @@ fn suspend_smtp_thread(context: &Context, suspend: bool) {
|
||||
}
|
||||
|
||||
fn send_mdn(context: &Context, msg_id: MsgId) -> Result<(), Error> {
|
||||
let mut mimefactory = MimeFactory::load_mdn(context, msg_id)?;
|
||||
unsafe { mimefactory.render()? };
|
||||
add_smtp_job(context, Action::SendMdn, &mut mimefactory)?;
|
||||
let msg = Message::load_from_db(context, msg_id)?;
|
||||
let mimefactory = MimeFactory::from_mdn(context, &msg)?;
|
||||
let rendered_msg = mimefactory.render()?;
|
||||
|
||||
add_smtp_job(context, Action::SendMdn, &rendered_msg)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn add_smtp_job(context: &Context, action: Action, mimefactory: &MimeFactory) -> Result<(), Error> {
|
||||
fn add_smtp_job(
|
||||
context: &Context,
|
||||
action: Action,
|
||||
rendered_msg: &RenderedEmail,
|
||||
) -> Result<(), Error> {
|
||||
ensure!(
|
||||
!mimefactory.recipients_addr.is_empty(),
|
||||
!rendered_msg.recipients.is_empty(),
|
||||
"no recipients for smtp job set"
|
||||
);
|
||||
let mut param = Params::new();
|
||||
let bytes = unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
(*mimefactory.out).str_0 as *const u8,
|
||||
(*mimefactory.out).len,
|
||||
)
|
||||
};
|
||||
let blob = BlobObject::create(context, &mimefactory.rfc724_mid, bytes)?;
|
||||
let recipients = mimefactory.recipients_addr.join("\x1e");
|
||||
let bytes = &rendered_msg.message;
|
||||
let blob = BlobObject::create(context, &rendered_msg.rfc724_mid, bytes)?;
|
||||
|
||||
let recipients = rendered_msg.recipients.join("\x1e");
|
||||
param.set(Param::File, blob.as_name());
|
||||
param.set(Param::Recipients, &recipients);
|
||||
|
||||
job_add(
|
||||
context,
|
||||
action,
|
||||
(if mimefactory.loaded == Loaded::Message {
|
||||
mimefactory.msg.id.to_u32() as i32
|
||||
} else {
|
||||
0
|
||||
}) as libc::c_int,
|
||||
rendered_msg
|
||||
.foreign_id
|
||||
.map(|v| v.to_u32() as i32)
|
||||
.unwrap_or_default(),
|
||||
param,
|
||||
0,
|
||||
);
|
||||
@@ -971,10 +940,12 @@ fn add_smtp_job(context: &Context, action: Action, mimefactory: &MimeFactory) ->
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds a job to the database, scheduling it `delay_seconds`
|
||||
/// after the current time.
|
||||
pub fn job_add(
|
||||
context: &Context,
|
||||
action: Action,
|
||||
foreign_id: libc::c_int,
|
||||
foreign_id: i32,
|
||||
param: Params,
|
||||
delay_seconds: i64,
|
||||
) {
|
||||
@@ -1001,7 +972,7 @@ pub fn job_add(
|
||||
).ok();
|
||||
|
||||
match thread {
|
||||
Thread::Imap => interrupt_inbox_idle(context, false),
|
||||
Thread::Imap => interrupt_inbox_idle(context),
|
||||
Thread::Smtp => interrupt_smtp_idle(context),
|
||||
Thread::Unknown => {}
|
||||
}
|
||||
@@ -1013,7 +984,110 @@ pub fn interrupt_smtp_idle(context: &Context) {
|
||||
let &(ref lock, ref cvar) = &*context.smtp_state.clone();
|
||||
let mut state = lock.lock().unwrap();
|
||||
|
||||
state.perform_jobs_needed = 1;
|
||||
state.perform_jobs_needed = PerformJobsNeeded::AtOnce;
|
||||
state.idle = true;
|
||||
cvar.notify_one();
|
||||
info!(context, "Interrupting SMTP-idle... ended",);
|
||||
}
|
||||
|
||||
/// Load jobs from the database.
|
||||
///
|
||||
/// Load jobs for this "[Thread]", i.e. either load SMTP jobs or load
|
||||
/// IMAP jobs. The `probe_network` parameter decides how to query
|
||||
/// jobs, this is tricky and probably wrong currently. Look at the
|
||||
/// SQL queries for details.
|
||||
fn load_jobs(context: &Context, thread: Thread, probe_network: bool) -> Vec<Job> {
|
||||
let query = if !probe_network {
|
||||
// processing for first-try and after backoff-timeouts:
|
||||
// process jobs in the order they were added.
|
||||
"SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries \
|
||||
FROM jobs WHERE thread=? AND desired_timestamp<=? ORDER BY action DESC, added_timestamp;"
|
||||
} else {
|
||||
// processing after call to dc_maybe_network():
|
||||
// process _all_ pending jobs that failed before
|
||||
// in the order of their backoff-times.
|
||||
"SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries \
|
||||
FROM jobs WHERE thread=? AND tries>0 ORDER BY desired_timestamp, action DESC;"
|
||||
};
|
||||
|
||||
let params_no_probe = params![thread as i64, time()];
|
||||
let params_probe = params![thread as i64];
|
||||
let params: &[&dyn rusqlite::ToSql] = if !probe_network {
|
||||
params_no_probe
|
||||
} else {
|
||||
params_probe
|
||||
};
|
||||
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
query,
|
||||
params,
|
||||
|row| {
|
||||
let job = Job {
|
||||
job_id: row.get(0)?,
|
||||
action: row.get(1)?,
|
||||
foreign_id: row.get(2)?,
|
||||
desired_timestamp: row.get(5)?,
|
||||
added_timestamp: row.get(4)?,
|
||||
tries: row.get(6)?,
|
||||
param: row.get::<_, String>(3)?.parse().unwrap_or_default(),
|
||||
try_again: TryAgain::Dont,
|
||||
pending_error: None,
|
||||
};
|
||||
|
||||
Ok(job)
|
||||
},
|
||||
|jobs| {
|
||||
let mut ret: Vec<Job> = Vec::new();
|
||||
for job in jobs {
|
||||
match job {
|
||||
Ok(j) => ret.push(j),
|
||||
Err(e) => warn!(context, "Bad job from the database: {}", e),
|
||||
}
|
||||
}
|
||||
Ok(ret)
|
||||
},
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::test_utils::*;
|
||||
|
||||
fn insert_job(context: &Context, foreign_id: i64) {
|
||||
let now = time();
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"INSERT INTO jobs
|
||||
(added_timestamp, thread, action, foreign_id, param, desired_timestamp)
|
||||
VALUES (?, ?, ?, ?, ?, ?);",
|
||||
params![
|
||||
now,
|
||||
Thread::from(Action::MoveMsg),
|
||||
Action::MoveMsg,
|
||||
foreign_id,
|
||||
Params::new().to_string(),
|
||||
now
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_jobs() {
|
||||
// We want to ensure that loading jobs skips over jobs which
|
||||
// fails to load from the database instead of failing to load
|
||||
// all jobs.
|
||||
let t = dummy_context();
|
||||
insert_job(&t.ctx, 0);
|
||||
insert_job(&t.ctx, -1); // This can not be loaded into Job struct.
|
||||
insert_job(&t.ctx, 1);
|
||||
let jobs = load_jobs(&t.ctx, Thread::from(Action::MoveMsg), false);
|
||||
assert_eq!(jobs.len(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,16 +63,17 @@ impl JobThread {
|
||||
|
||||
info!(context, "Interrupting {}-IDLE...", self.name);
|
||||
|
||||
self.imap.interrupt_idle();
|
||||
self.imap.interrupt_idle(context);
|
||||
|
||||
let &(ref lock, ref cvar) = &*self.state.clone();
|
||||
let mut state = lock.lock().unwrap();
|
||||
|
||||
state.idle = true;
|
||||
cvar.notify_one();
|
||||
info!(context, "Interrupting {}-IDLE... finished", self.name);
|
||||
}
|
||||
|
||||
pub fn fetch(&mut self, context: &Context, use_network: bool) {
|
||||
pub async fn fetch(&mut self, context: &Context, use_network: bool) {
|
||||
{
|
||||
let &(ref lock, _) = &*self.state.clone();
|
||||
let mut state = lock.lock().unwrap();
|
||||
@@ -85,10 +86,10 @@ impl JobThread {
|
||||
}
|
||||
|
||||
if use_network {
|
||||
if let Err(err) = self.connect_and_fetch(context) {
|
||||
if let Err(err) = self.connect_and_fetch(context).await {
|
||||
warn!(context, "connect+fetch failed: {}, reconnect & retry", err);
|
||||
self.imap.trigger_reconnect();
|
||||
if let Err(err) = self.connect_and_fetch(context) {
|
||||
if let Err(err) = self.connect_and_fetch(context).await {
|
||||
warn!(context, "connect+fetch failed: {}", err);
|
||||
}
|
||||
}
|
||||
@@ -96,14 +97,18 @@ impl JobThread {
|
||||
self.state.0.lock().unwrap().using_handle = false;
|
||||
}
|
||||
|
||||
fn connect_and_fetch(&mut self, context: &Context) -> Result<()> {
|
||||
async fn connect_and_fetch(&mut self, context: &Context) -> Result<()> {
|
||||
let prefix = format!("{}-fetch", self.name);
|
||||
match self.imap.connect_configured(context) {
|
||||
Ok(()) => {
|
||||
if let Some(watch_folder) = self.get_watch_folder(context) {
|
||||
let start = std::time::Instant::now();
|
||||
info!(context, "{} started...", prefix);
|
||||
let res = self.imap.fetch(context, &watch_folder);
|
||||
let res = self
|
||||
.imap
|
||||
.fetch(context, &watch_folder)
|
||||
.await
|
||||
.map_err(Into::into);
|
||||
let elapsed = start.elapsed().as_millis();
|
||||
info!(context, "{} done in {:.3} ms.", prefix, elapsed);
|
||||
|
||||
@@ -112,7 +117,7 @@ impl JobThread {
|
||||
Err(Error::WatchFolderNotFound("not-set".to_string()))
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
Err(err) => Err(crate::error::Error::Message(err.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,20 +174,19 @@ impl JobThread {
|
||||
let prefix = format!("{}-IDLE", self.name);
|
||||
let do_fake_idle = match self.imap.connect_configured(context) {
|
||||
Ok(()) => {
|
||||
info!(context, "{} started...", prefix);
|
||||
let watch_folder = self.get_watch_folder(context);
|
||||
let res = self.imap.idle(context, watch_folder);
|
||||
info!(context, "{} ended...", prefix);
|
||||
match res {
|
||||
Ok(()) => false,
|
||||
Err(Error::ImapMissesIdle) => true, // we have to do fake_idle
|
||||
Err(err) => {
|
||||
if !self.imap.can_idle() {
|
||||
true // we have to do fake_idle
|
||||
} else {
|
||||
let watch_folder = self.get_watch_folder(context);
|
||||
info!(context, "{} started...", prefix);
|
||||
let res = self.imap.idle(context, watch_folder);
|
||||
info!(context, "{} ended...", prefix);
|
||||
if let Err(err) = res {
|
||||
warn!(context, "{} failed: {} -> reconnecting", prefix, err);
|
||||
// something is borked, let's start afresh on the next occassion
|
||||
self.imap.disconnect(context);
|
||||
|
||||
false
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
|
||||
29
src/key.rs
@@ -178,21 +178,9 @@ impl Key {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_base64(&self, break_every: usize) -> String {
|
||||
pub fn to_base64(&self) -> String {
|
||||
let buf = self.to_bytes();
|
||||
|
||||
let encoded = base64::encode(&buf);
|
||||
encoded
|
||||
.chars()
|
||||
.enumerate()
|
||||
.fold(String::new(), |mut res, (i, c)| {
|
||||
if i > 0 && i % break_every == 0 {
|
||||
res.push(' ')
|
||||
}
|
||||
res.push(c);
|
||||
res
|
||||
})
|
||||
.to_string()
|
||||
base64::encode(&buf)
|
||||
}
|
||||
|
||||
pub fn to_armored_string(
|
||||
@@ -217,15 +205,18 @@ impl Key {
|
||||
.expect("failed to serialize key")
|
||||
}
|
||||
|
||||
pub fn write_asc_to_file(&self, file: impl AsRef<Path>, context: &Context) -> bool {
|
||||
pub fn write_asc_to_file(
|
||||
&self,
|
||||
file: impl AsRef<Path>,
|
||||
context: &Context,
|
||||
) -> std::io::Result<()> {
|
||||
let file_content = self.to_asc(None).into_bytes();
|
||||
|
||||
if dc_write_file(context, &file, &file_content) {
|
||||
true
|
||||
} else {
|
||||
let res = dc_write_file(context, &file, &file_content);
|
||||
if res.is_err() {
|
||||
error!(context, "Cannot write key to {}", file.as_ref().display());
|
||||
false
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
pub fn fingerprint(&self) -> String {
|
||||
|
||||
29
src/lib.rs
@@ -1,9 +1,9 @@
|
||||
#![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)]
|
||||
#![deny(clippy::correctness, missing_debug_implementations, clippy::all)]
|
||||
// for now we hide warnings to not clutter/hide errors during "cargo clippy"
|
||||
#![allow(clippy::cognitive_complexity, clippy::too_many_arguments)]
|
||||
#![allow(clippy::match_bool)]
|
||||
#![feature(ptr_wrapping_offset_from)]
|
||||
#![feature(drain_filter)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate failure_derive;
|
||||
@@ -17,15 +17,15 @@ 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;
|
||||
pub mod log;
|
||||
#[macro_use]
|
||||
pub mod error;
|
||||
|
||||
pub mod headerdef;
|
||||
|
||||
pub(crate) mod events;
|
||||
pub use events::*;
|
||||
|
||||
@@ -51,6 +51,7 @@ mod login_param;
|
||||
pub mod lot;
|
||||
pub mod message;
|
||||
mod mimefactory;
|
||||
pub mod mimeparser;
|
||||
pub mod oauth2;
|
||||
mod param;
|
||||
pub mod peerstate;
|
||||
@@ -62,15 +63,17 @@ pub mod sql;
|
||||
pub mod stock;
|
||||
mod token;
|
||||
#[macro_use]
|
||||
mod wrapmime;
|
||||
mod dehtml;
|
||||
|
||||
pub mod dc_array;
|
||||
mod dc_dehtml;
|
||||
pub mod dc_mimeparser;
|
||||
pub mod dc_receive_imf;
|
||||
mod dc_simplify;
|
||||
mod dc_strencode;
|
||||
pub mod dc_tools;
|
||||
|
||||
/// if set imap/incoming and smtp/outgoing MIME messages will be printed
|
||||
pub const DCC_MIME_DEBUG: &str = "DCC_MIME_DEBUG";
|
||||
|
||||
/// if set IMAP protocol commands and responses will be printed
|
||||
pub const DCC_IMAP_DEBUG: &str = "DCC_IMAP_DEBUG";
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_utils;
|
||||
|
||||
@@ -8,12 +8,12 @@ use crate::chat;
|
||||
use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
use crate::context::*;
|
||||
use crate::dc_mimeparser::SystemMessage;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
use crate::job::*;
|
||||
use crate::message::{Message, MsgId};
|
||||
use crate::mimeparser::SystemMessage;
|
||||
use crate::param::*;
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
@@ -229,7 +229,7 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
|
||||
job_add(
|
||||
context,
|
||||
Action::MaybeSendLocationsEnded,
|
||||
chat_id as libc::c_int,
|
||||
chat_id as i32,
|
||||
Params::new(),
|
||||
seconds + 1,
|
||||
);
|
||||
@@ -495,50 +495,54 @@ pub fn save(
|
||||
independent: bool,
|
||||
) -> Result<u32, Error> {
|
||||
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "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;
|
||||
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])?;
|
||||
for location in locations {
|
||||
let exists =
|
||||
stmt_test.exists(params![location.timestamp, contact_id as i32])?;
|
||||
|
||||
if 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",
|
||||
if independent || !exists {
|
||||
stmt_insert.execute(params![
|
||||
location.timestamp,
|
||||
"from_id",
|
||||
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)
|
||||
},
|
||||
)
|
||||
Ok(newest_location_id)
|
||||
},
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
|
||||
pub fn JobMaybeSendLocations(context: &Context, _job: &Job) {
|
||||
let now = time();
|
||||
let mut continue_streaming = false;
|
||||
info!(
|
||||
@@ -628,7 +632,7 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut Job) {
|
||||
pub fn JobMaybeSendLocationsEnded(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.
|
||||
|
||||
28
src/log.rs
@@ -5,10 +5,16 @@ macro_rules! info {
|
||||
($ctx:expr, $msg:expr) => {
|
||||
info!($ctx, $msg,)
|
||||
};
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
|
||||
let formatted = format!($msg, $($args),*);
|
||||
emit_event!($ctx, $crate::Event::Info(formatted));
|
||||
};
|
||||
let thread = ::std::thread::current();
|
||||
let full = format!("{thid:?} {file}:{line}: {msg}",
|
||||
thid = thread.id(),
|
||||
file = file!(),
|
||||
line = line!(),
|
||||
msg = &formatted);
|
||||
emit_event!($ctx, $crate::Event::Info(full));
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
@@ -16,10 +22,16 @@ macro_rules! warn {
|
||||
($ctx:expr, $msg:expr) => {
|
||||
warn!($ctx, $msg,)
|
||||
};
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
|
||||
let formatted = format!($msg, $($args),*);
|
||||
emit_event!($ctx, $crate::Event::Warning(formatted));
|
||||
};
|
||||
let thread = ::std::thread::current();
|
||||
let full = format!("{thid:?} {file}:{line}: {msg}",
|
||||
thid = thread.id(),
|
||||
file = file!(),
|
||||
line = line!(),
|
||||
msg = &formatted);
|
||||
emit_event!($ctx, $crate::Event::Warning(full));
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
@@ -27,10 +39,10 @@ macro_rules! error {
|
||||
($ctx:expr, $msg:expr) => {
|
||||
error!($ctx, $msg,)
|
||||
};
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
|
||||
let formatted = format!($msg, $($args),*);
|
||||
emit_event!($ctx, $crate::Event::Error(formatted));
|
||||
};
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
|
||||