Compare commits

..

3 Commits

Author SHA1 Message Date
Floris Bruynooghe
0eab93257c Change selfavatar type to BlobObject
This changes the type of ConfigItem::Selfavatar to a BlobObject.  This
is what also happened on master because there was a bug with how
selfavatar was not being correctly handled as a blob.

As a side-effect this also adds a lifetime to the ConfigItem object.
This resulted in some strum derives no longer working which in itself
resulted in some simplifications between ConfigKey and ConfigItem:

ConfigKey::to_key_string & ConfigKey::from_key_string are used to
create the SQL keynames.  The ConfigItem is converted to its
ConfigKey discriminant in the SQL write path which avoids the
duplicate source for SQL keyname.

FFI-level tests are added for testing the copy behaviour since that is
now effectively a problem of the FFI, in Rust this is impossible to
have thanks to the types.
2019-11-25 21:07:02 +01:00
Floris Bruynooghe
e6d3bd284b Convert InboxWatch config to a bool
This introduces InboxWatch as a bool rather than the old string type.
Introducing the bool handling in the config machinery and updating all
the users to use the get_config_item/set_config_item API.
2019-11-25 21:07:02 +01:00
Floris Bruynooghe
f6cdc7b498 Introduce enum API for configuration
This introduces a new Context::get_config_item() and
Context::set_config_item() API which operates on two enums: ConfigKey
and ConfigItem.  The latter has variats which hold the actual
configuration as data.  This allows to introduce stronger typing of
the configuration items.

This commit however does not yet do this, it adapts the existing APIs
to use the new API leaving the stronger typing for future commits.
This should make the changes more digestible and reviewable.
2019-11-25 21:02:37 +01:00
108 changed files with 30154 additions and 6892 deletions

2
.gitignore vendored
View File

@@ -23,5 +23,3 @@ python/liveconfig*
# ignore doxgen generated files
deltachat-ffi/html
deltachat-ffi/xml
.rsynclist

View File

@@ -1,67 +1,5 @@
# Changelog
## next (pending)
- restructured (but not change) imap idle handling into own file. cc @link2xt
## 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

574
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,13 @@
[package]
name = "deltachat"
version = "1.0.0-beta.12"
version = "1.0.0-beta.8"
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 }
@@ -17,12 +18,11 @@ smallvec = "0.6.9"
reqwest = { version = "0.9.15", default-features = false, features = ["rustls-tls"] }
num-derive = "0.2.5"
num-traits = "0.2.6"
lettre = { git = "https://github.com/deltachat/lettre", branch = "feat/mail" }
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "feat/mail" }
lettre = { git = "https://github.com/deltachat/lettre", branch = "feat/rustls" }
async-imap = { git = "https://github.com/async-email/async-imap", branch="master" }
async-tls = "0.6"
async-std = { version = "1.0", features = ["unstable"] }
base64 = "0.11"
base64 = "0.10"
charset = "0.1"
percent-encoding = "2.0"
serde = { version = "1.0", features = ["derive"] }
@@ -47,14 +47,13 @@ image-meta = "0.1.0"
quick-xml = "0.15.0"
escaper = "0.1.0"
bitflags = "1.1.0"
jetscii = "0.4.4"
debug_stub_derive = "0.3.0"
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" }
[dev-dependencies]
tempfile = "3.0"
@@ -66,6 +65,7 @@ proptest = "0.9.4"
members = [
"deltachat-ffi",
"deltachat_derive",
"mmime",
]
[[example]]

View File

@@ -87,15 +87,6 @@ $ 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -5,44 +5,66 @@
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="409.60001"
inkscape:export-xdpi="409.60001"
inkscape:export-filename="/Users/bpetersen/projects/deltachat-core-rust/assets/icon-device.png"
version="1.0"
width="60"
height="60"
viewBox="0 0 45 45"
preserveAspectRatio="xMidYMid meet"
id="svg4344"
inkscape:version="1.0beta1 (32d4812, 2019-09-19)"
sodipodi:docname="icon-device.svg"
inkscape:version="1.0beta1 (32d4812, 2019-09-19)">
id="svg4344"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 45 45"
height="60"
width="60"
version="1.0"
inkscape:export-filename="/Users/bpetersen/Documents/deltachat/icons/device4.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96">
<defs
id="defs4348" />
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>
<sodipodi:namedview
inkscape:snap-global="false"
pagecolor="#ffffff"
bordercolor="#666666"
inkscape:document-rotation="0"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1600"
inkscape:window-height="1035"
id="namedview4346"
showgrid="false"
units="px"
inkscape:zoom="3.959798"
inkscape:cx="28.322498"
inkscape:cy="24.898474"
inkscape:window-x="45"
inkscape:window-y="23"
inkscape:current-layer="svg4344"
inkscape:window-maximized="0"
inkscape:current-layer="svg4344" />
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"
bordercolor="#666666"
pagecolor="#ffffff" />
<metadata
id="metadata4336">
Created by potrace 1.15, written by Peter Selinger 2001-2017
@@ -56,28 +78,33 @@ Created by potrace 1.15, written by Peter Selinger 2001-2017
</cc:Work>
</rdf:RDF>
</metadata>
<rect
y="-4.4408921e-16"
x="0"
height="45"
width="45"
id="rect860"
style="opacity:1;fill:#76868b;fill-opacity:1;stroke-width:0.819271" />
<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" />
<g
fill="#000000"
id="g4342"
stroke="none"
style="fill:#ffffff;fill-opacity:1"
fill="#000000"
transform="matrix(0.00255113,0,0,-0.00255113,5.586152,38.200477)"
id="g4342">
style="fill:#ffffff;fill-opacity:1">
<path
style="fill:#ffffff;fill-opacity:1"
d="m 8175,12765 c -703,-114 -1248,-608 -1387,-1258 -17,-82 -21,-136 -22,-277 0,-202 15,-307 70,-470 149,-446 499,-733 1009,-828 142,-26 465,-23 619,6 691,131 1201,609 1328,1244 31,158 31,417 0,565 -114,533 -482,889 -1038,1004 -133,27 -448,35 -579,14 z"
inkscape:connector-curvature="0"
id="path4338"
inkscape:connector-curvature="0" />
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" />
<path
style="fill:#ffffff;fill-opacity:1"
d="m 7070,9203 c -212,-20 -275,-27 -397,-48 -691,-117 -1400,-444 -2038,-940 -182,-142 -328,-270 -585,-517 -595,-571 -911,-974 -927,-1181 -6,-76 11,-120 69,-184 75,-80 159,-108 245,-79 109,37 263,181 632,595 539,606 774,826 1035,969 135,75 231,105 341,106 82,1 94,-2 138,-27 116,-68 161,-209 122,-376 -9,-36 -349,-868 -757,-1850 -407,-982 -785,-1892 -838,-2021 -287,-694 -513,-1389 -615,-1889 -70,-342 -90,-683 -52,-874 88,-440 381,-703 882,-792 124,-23 401,-30 562,-16 783,69 1674,461 2561,1125 796,596 1492,1354 1607,1751 43,146 -33,308 -168,360 -61,23 -100,15 -173,-36 -105,-74 -202,-170 -539,-529 -515,-551 -762,-783 -982,-927 -251,-164 -437,-186 -543,-65 -56,64 -74,131 -67,247 13,179 91,434 249,815 135,324 1588,4102 1646,4280 106,325 151,561 159,826 9,281 -22,463 -112,652 -58,122 -114,199 -211,292 -245,233 -582,343 -1044,338 -91,-1 -181,-3 -200,-5 z"
inkscape:connector-curvature="0"
id="path4340"
inkscape:connector-curvature="0" />
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" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -5,21 +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:export-ydpi="409.60001"
inkscape:export-xdpi="409.60001"
inkscape:export-filename="/home/kerle/test-icon.png"
inkscape:export-ydpi="204.8"
inkscape:export-xdpi="204.8"
inkscape:export-filename="/Users/bpetersen/Documents/deltachat/icons/saved-messages.png"
version="1.0"
width="60"
height="60"
viewBox="0 0 45 45"
preserveAspectRatio="xMidYMid meet"
id="svg4344"
sodipodi:docname="icon-saved-messages.svg"
sodipodi:docname="saved-messages.svg"
inkscape:version="1.0beta1 (32d4812, 2019-09-19)">
<defs
id="defs4348" />
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>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
@@ -30,16 +53,16 @@
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1395"
inkscape:window-height="855"
inkscape:window-width="1600"
inkscape:window-height="1035"
id="namedview4346"
showgrid="false"
units="px"
inkscape:zoom="4"
inkscape:cx="29.308676"
inkscape:cy="49.03624"
inkscape:window-x="89"
inkscape:window-y="108"
inkscape:zoom="2.8"
inkscape:cx="48.214285"
inkscape:cy="55"
inkscape:window-x="45"
inkscape:window-y="23"
inkscape:window-maximized="0"
inkscape:current-layer="svg4344"
inkscape:lockguides="false" />
@@ -56,16 +79,21 @@ Created by potrace 1.15, written by Peter Selinger 2001-2017
</cc:Work>
</rdf:RDF>
</metadata>
<rect
y="0"
x="0"
height="45"
width="45"
id="rect1420"
style="fill:#87aade;fill-opacity:1;stroke:none;stroke-width:0.968078" />
<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" />
<path
id="rect846"
style="fill:#ffffff;stroke-width:0.58409804"
d="M 13.5,7.5 V 39 h 0.08654 L 22.533801,29.370239 31.482419,39 h 0.01758 V 7.5 Z m 9.004056,4.108698 1.879508,4.876388 5.039514,0.359779 -3.879358,3.363728 1.227764,5.095749 -4.276893,-2.796643 -4.280949,2.788618 1.237229,-5.093073 -3.873949,-3.371754 5.040866,-0.350417 z"
inkscape:connector-curvature="0" />
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)" />
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

View File

@@ -2,7 +2,7 @@
export BRANCH=${CIRCLE_BRANCH:?branch to build}
export REPONAME=${CIRCLE_PROJECT_REPONAME:?repository name}
export SSHTARGET=${SSHTARGET-ci@b1.delta.chat}
export SSHTARGET=ci@b1.delta.chat
# we construct the BUILDDIR such that we can easily share the
# CARGO_TARGET_DIR between runs ("..")

View File

@@ -2,7 +2,7 @@
export BRANCH=${CIRCLE_BRANCH:?branch to build}
export REPONAME=${CIRCLE_PROJECT_REPONAME:?repository name}
export SSHTARGET=${SSHTARGET-ci@b1.delta.chat}
export SSHTARGET=ci@b1.delta.chat
# we construct the BUILDDIR such that we can easily share the
# CARGO_TARGET_DIR between runs ("..")

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.0.0-beta.12"
version = "1.0.0-beta.8"
description = "Deltachat FFI"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
@@ -20,7 +20,6 @@ deltachat-provider-database = "0.2.1"
libc = "0.2"
human-panic = "1.0.1"
num-traits = "0.2.6"
failure = "0.1.6"
[features]
default = ["vendored", "nightly", "ringbuf"]

View File

@@ -994,20 +994,11 @@ 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, NULL);
* dc_msg_set_file(msg, "/file/to/send.mp4", NULL);
* dc_prepare_msg(context, chat_id, msg);
*
* // ... create the file ...
*
* // ... after /file/to/send.mp4 is ready:
* 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
@@ -1033,11 +1024,8 @@ 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
@@ -1146,32 +1134,11 @@ 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().
@@ -1962,7 +1929,7 @@ void dc_imex (dc_context_t* context, int what, c
* }
* while (!configure_succeeded())
* }
* dc_str_unref(file);
* free(file);
* }
* ~~~
*
@@ -2617,25 +2584,6 @@ dc_lot_t* dc_chatlist_get_summary (const dc_chatlist_t* chatlist, siz
dc_context_t* dc_chatlist_get_context (dc_chatlist_t* chatlist);
/**
* 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
*
@@ -4439,16 +4387,6 @@ 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
/**
* @}
*/

View File

@@ -25,13 +25,13 @@ 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 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,15 +175,6 @@ 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,
@@ -346,12 +337,16 @@ pub unsafe extern "C" fn dc_get_config(
return "".strdup();
}
let ffi_context = &*context;
match config::Config::from_str(&to_string_lossy(key)) {
match config::Config::from_key_str(&to_string_lossy(key)) {
Ok(key) => ffi_context
.with_inner(|ctx| ctx.get_config(key).unwrap_or_default().strdup())
.unwrap_or_else(|_| "".strdup()),
Err(_) => {
ffi_context.error("dc_get_config(): invalid key");
Err(err) => {
ffi_context.error(&format!(
"dc_get_config(): invalid key {} ({})",
&to_string_lossy(key),
err
));
"".strdup()
}
}
@@ -851,21 +846,6 @@ 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,
@@ -2377,27 +2357,6 @@ pub unsafe extern "C" fn dc_chat_is_sending_locations(chat: *mut dc_chat_t) -> l
ffi_chat.chat.is_sending_locations() as libc::c_int
}
#[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]

View File

@@ -2,7 +2,7 @@ extern crate deltachat_provider_database;
use std::ptr;
use crate::string::{to_string_lossy, StrExt};
use deltachat::dc_tools::{to_string_lossy, StrExt};
use deltachat_provider_database::StatusState;
#[no_mangle]

View File

@@ -1,350 +0,0 @@
use failure::Fail;
use std::ffi::{CStr, CString};
/// Duplicates a string
///
/// returns an empty string if NULL is given, never returns NULL (exits on errors)
///
/// # Examples
///
/// ```rust,norun
/// use deltachat::dc_tools::{dc_strdup, to_string_lossy};
/// unsafe {
/// let str_a = b"foobar\x00" as *const u8 as *const libc::c_char;
/// let str_a_copy = dc_strdup(str_a);
/// assert_eq!(to_string_lossy(str_a_copy), "foobar");
/// assert_ne!(str_a, str_a_copy);
/// }
/// ```
pub unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
let ret: *mut libc::c_char;
if !s.is_null() {
ret = libc::strdup(s);
assert!(!ret.is_null());
} else {
ret = libc::calloc(1, 1) as *mut libc::c_char;
assert!(!ret.is_null());
}
ret
}
/// Error type for the [OsStrExt] trait
#[derive(Debug, Fail, PartialEq)]
pub enum CStringError {
/// The string contains an interior null byte
#[fail(display = "String contains an interior null byte")]
InteriorNullByte,
/// The string is not valid Unicode
#[fail(display = "String is not valid unicode")]
NotUnicode,
}
/// Extra convenience methods on [std::ffi::OsStr] to work with `*libc::c_char`.
///
/// The primary function of this trait is to more easily convert
/// [OsStr], [OsString] or [Path] into pointers to C strings. This always
/// allocates a new string since it is very common for the source
/// string not to have the required terminal null byte.
///
/// It is implemented for `AsRef<std::ffi::OsStr>>` trait, which
/// allows any type which implements this trait to transparently use
/// this. This is how the conversion for [Path] works.
///
/// [OsStr]: std::ffi::OsStr
/// [OsString]: std::ffi::OsString
/// [Path]: std::path::Path
///
/// # Example
///
/// ```
/// use deltachat::dc_tools::{dc_strdup, OsStrExt};
/// let path = std::path::Path::new("/some/path");
/// let path_c = path.to_c_string().unwrap();
/// unsafe {
/// let mut c_ptr: *mut libc::c_char = dc_strdup(path_c.as_ptr());
/// }
/// ```
pub trait OsStrExt {
/// Convert a [std::ffi::OsStr] to an [std::ffi::CString]
///
/// This is useful to convert e.g. a [std::path::Path] to
/// [*libc::c_char] by using
/// [Path::as_os_str()](std::path::Path::as_os_str) and
/// [CStr::as_ptr()](std::ffi::CStr::as_ptr).
///
/// This returns [CString] and not [&CStr] because not all [OsStr]
/// slices end with a null byte, particularly those coming from
/// [Path] do not have a null byte and having to handle this as
/// the caller would defeat the point of this function.
///
/// On Windows this requires that the [OsStr] contains valid
/// unicode, which should normally be the case for a [Path].
///
/// [CString]: std::ffi::CString
/// [CStr]: std::ffi::CStr
/// [OsStr]: std::ffi::OsStr
/// [Path]: std::path::Path
///
/// # Errors
///
/// Since a C `*char` is terminated by a NULL byte this conversion
/// will fail, when the [OsStr] has an interior null byte. The
/// function will return
/// `[Err]([CStringError::InteriorNullByte])`. When converting
/// from a [Path] it should be safe to
/// [`.unwrap()`](std::result::Result::unwrap) this anyway since a
/// [Path] should not contain interior null bytes.
///
/// On windows when the string contains invalid Unicode
/// `[Err]([CStringError::NotUnicode])` is returned.
fn to_c_string(&self) -> Result<CString, CStringError>;
}
impl<T: AsRef<std::ffi::OsStr>> OsStrExt for T {
#[cfg(not(target_os = "windows"))]
fn to_c_string(&self) -> Result<CString, CStringError> {
use std::os::unix::ffi::OsStrExt;
CString::new(self.as_ref().as_bytes()).map_err(|err| match err {
std::ffi::NulError { .. } => CStringError::InteriorNullByte,
})
}
#[cfg(target_os = "windows")]
fn to_c_string(&self) -> Result<CString, CStringError> {
os_str_to_c_string_unicode(&self)
}
}
// Implementation for os_str_to_c_string on windows.
#[allow(dead_code)]
fn os_str_to_c_string_unicode(
os_str: &dyn AsRef<std::ffi::OsStr>,
) -> Result<CString, CStringError> {
match os_str.as_ref().to_str() {
Some(val) => CString::new(val.as_bytes()).map_err(|err| match err {
std::ffi::NulError { .. } => CStringError::InteriorNullByte,
}),
None => Err(CStringError::NotUnicode),
}
}
/// Convenience methods/associated functions for working with [CString]
///
/// This is helps transitioning from unsafe code.
pub trait CStringExt {
/// Create a new [CString], yolo style
///
/// This unwrap the result, panicking when there are embedded NULL
/// bytes.
fn yolo<T: Into<Vec<u8>>>(t: T) -> CString {
CString::new(t).expect("String contains null byte, can not be CString")
}
}
impl CStringExt for CString {}
/// Convenience methods to make transitioning from raw C strings easier.
///
/// To interact with (legacy) C APIs we often need to convert from
/// Rust strings to raw C strings. This can be clumsy to do correctly
/// and the compiler sometimes allows it in an unsafe way. These
/// methods make it more succinct and help you get it right.
pub trait StrExt {
/// Allocate a new raw C `*char` version of this string.
///
/// This allocates a new raw C string which must be freed using
/// `free`. It takes care of some common pitfalls with using
/// [CString.as_ptr].
///
/// [CString.as_ptr]: std::ffi::CString.as_ptr
///
/// # Panics
///
/// This function will panic when the original string contains an
/// interior null byte as this can not be represented in raw C
/// strings.
unsafe fn strdup(&self) -> *mut libc::c_char;
}
impl<T: AsRef<str>> StrExt for T {
unsafe fn strdup(&self) -> *mut libc::c_char {
let tmp = CString::yolo(self.as_ref());
dc_strdup(tmp.as_ptr())
}
}
pub fn to_string_lossy(s: *const libc::c_char) -> String {
if s.is_null() {
return "".into();
}
let cstr = unsafe { CStr::from_ptr(s) };
cstr.to_string_lossy().to_string()
}
pub fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
if s.is_null() {
return None;
}
Some(to_string_lossy(s))
}
/// Convert a C `*char` pointer to a [std::path::Path] slice.
///
/// This converts a `*libc::c_char` pointer to a [Path] slice. This
/// essentially has to convert the pointer to [std::ffi::OsStr] to do
/// so and thus is the inverse of [OsStrExt::to_c_string]. Just like
/// [OsStrExt::to_c_string] requires valid Unicode on Windows, this
/// requires that the pointer contains valid UTF-8 on Windows.
///
/// Because this returns a reference the [Path] silce can not outlive
/// the original pointer.
///
/// [Path]: std::path::Path
#[cfg(not(target_os = "windows"))]
pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
assert!(!s.is_null(), "cannot be used on null pointers");
use std::os::unix::ffi::OsStrExt;
unsafe {
let c_str = std::ffi::CStr::from_ptr(s).to_bytes();
let os_str = std::ffi::OsStr::from_bytes(c_str);
std::path::Path::new(os_str)
}
}
// as_path() implementation for windows, documented above.
#[cfg(target_os = "windows")]
pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
as_path_unicode(s)
}
// Implementation for as_path() on Windows.
//
// Having this as a separate function means it can be tested on unix
// too.
#[allow(dead_code)]
fn as_path_unicode<'a>(s: *const libc::c_char) -> &'a std::path::Path {
assert!(!s.is_null(), "cannot be used on null pointers");
let cstr = unsafe { CStr::from_ptr(s) };
let str = cstr.to_str().unwrap_or_else(|err| panic!("{}", err));
std::path::Path::new(str)
}
#[cfg(test)]
mod tests {
use super::*;
use libc::{free, strcmp};
#[test]
fn test_os_str_to_c_string_cwd() {
let some_dir = std::env::current_dir().unwrap();
some_dir.as_os_str().to_c_string().unwrap();
}
#[test]
fn test_os_str_to_c_string_unicode() {
let some_str = String::from("/some/valid/utf8");
let some_dir = std::path::Path::new(&some_str);
assert_eq!(
some_dir.as_os_str().to_c_string().unwrap(),
CString::new("/some/valid/utf8").unwrap()
);
}
#[test]
fn test_os_str_to_c_string_nul() {
let some_str = std::ffi::OsString::from("foo\x00bar");
assert_eq!(
some_str.to_c_string().err().unwrap(),
CStringError::InteriorNullByte
)
}
#[test]
fn test_path_to_c_string_cwd() {
let some_dir = std::env::current_dir().unwrap();
some_dir.to_c_string().unwrap();
}
#[test]
fn test_path_to_c_string_unicode() {
let some_str = String::from("/some/valid/utf8");
let some_dir = std::path::Path::new(&some_str);
assert_eq!(
some_dir.as_os_str().to_c_string().unwrap(),
CString::new("/some/valid/utf8").unwrap()
);
}
#[test]
fn test_os_str_to_c_string_unicode_fn() {
let some_str = std::ffi::OsString::from("foo");
assert_eq!(
os_str_to_c_string_unicode(&some_str).unwrap(),
CString::new("foo").unwrap()
);
}
#[test]
fn test_path_to_c_string_unicode_fn() {
let some_str = String::from("/some/path");
let some_path = std::path::Path::new(&some_str);
assert_eq!(
os_str_to_c_string_unicode(&some_path).unwrap(),
CString::new("/some/path").unwrap()
);
}
#[test]
fn test_os_str_to_c_string_unicode_fn_nul() {
let some_str = std::ffi::OsString::from("fooz\x00bar");
assert_eq!(
os_str_to_c_string_unicode(&some_str).err().unwrap(),
CStringError::InteriorNullByte
);
}
#[test]
fn test_as_path() {
let some_path = CString::new("/some/path").unwrap();
let ptr = some_path.as_ptr();
assert_eq!(as_path(ptr), std::ffi::OsString::from("/some/path"))
}
#[test]
fn test_as_path_unicode_fn() {
let some_path = CString::new("/some/path").unwrap();
let ptr = some_path.as_ptr();
assert_eq!(as_path_unicode(ptr), std::ffi::OsString::from("/some/path"));
}
#[test]
fn test_cstring_yolo() {
assert_eq!(CString::new("hello").unwrap(), CString::yolo("hello"));
}
#[test]
fn test_strdup_str() {
unsafe {
let s = "hello".strdup();
let cmp = strcmp(s, b"hello\x00" as *const u8 as *const libc::c_char);
free(s as *mut libc::c_void);
assert_eq!(cmp, 0);
}
}
#[test]
fn test_strdup_string() {
unsafe {
let s = String::from("hello").strdup();
let cmp = strcmp(s, b"hello\x00" as *const u8 as *const libc::c_char);
free(s as *mut libc::c_void);
assert_eq!(cmp, 0);
}
}
}

View File

@@ -19,11 +19,12 @@ 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 fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
pub unsafe fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
info!(context, "Resetting tables ({})...", bits);
if 0 != bits & 1 {
sql::execute(context, &context.sql, "DELETE FROM jobs;", params![]).unwrap();
@@ -94,7 +95,7 @@ pub fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
fn dc_poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<(), Error> {
let data = dc_read_file(context, filename)?;
dc_receive_imf(context, &data, "import", 0, 0);
unsafe { dc_receive_imf(context, &data, "import", 0, 0) };
Ok(())
}
@@ -106,19 +107,18 @@ fn dc_poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<(),
/// @param context The context as created by dc_context_new().
/// @param spec The file or directory to import. NULL for the last command.
/// @return 1=success, 0=error.
fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int {
fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int {
if !context.sql.is_open() {
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 let Some(spec) = spec {
real_spec = spec.to_string();
if !spec.is_null() {
real_spec = to_string_lossy(spec);
context
.sql
.set_raw_config(context, "import_spec", Some(&real_spec))
@@ -132,8 +132,10 @@ fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int {
real_spec = rs.unwrap();
}
if let Some(suffix) = dc_get_filesuffix_lc(&real_spec) {
if suffix == "eml" && dc_poke_eml_file(context, &real_spec).is_ok() {
read_cnt += 1
if suffix == "eml" {
if dc_poke_eml_file(context, &real_spec).is_ok() {
read_cnt += 1
}
}
} else {
/* import a directory */
@@ -174,7 +176,7 @@ fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int {
1
}
fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
let contact = Contact::get_by_id(context, msg.get_from_id()).expect("invalid contact");
let contact_name = contact.get_name();
let contact_id = contact.get_id();
@@ -219,7 +221,7 @@ fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
);
}
fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Error> {
unsafe fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Error> {
let mut lines_out = 0;
for &msg_id in msglist {
if msg_id.is_daymarker() {
@@ -250,7 +252,7 @@ fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Error> {
Ok(())
}
fn log_contactlist(context: &Context, contacts: &Vec<u32>) {
unsafe fn log_contactlist(context: &Context, contacts: &Vec<u32>) {
let mut contacts = contacts.clone();
if !contacts.contains(&1) {
contacts.push(1);
@@ -302,7 +304,7 @@ fn chat_prefix(chat: &Chat) -> &'static str {
chat.typ.into()
}
pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
let chat_id = *context.cmdline_sel_chat_id.read().unwrap();
let mut sel_chat = if chat_id > 0 {
Chat::load_from_db(context, chat_id).ok()
@@ -313,6 +315,11 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
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();
@@ -462,7 +469,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
);
}
"poke" => {
ensure!(0 != poke_spec(context, Some(arg1)), "Poke failed");
ensure!(0 != poke_spec(context, arg1_c), "Poke failed");
}
"reset" => {
ensure!(!arg1.is_empty(), "Argument <bits> missing: 1=jobs, 2=peerstates, 4=private keys, 8=rest but server config");
@@ -581,7 +588,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
let members = chat::get_chat_contacts(context, sel_chat.id);
let subtitle = if sel_chat.is_device_talk() {
"device-talk".to_string()
} else if sel_chat.get_type() == Chattype::Single && !members.is_empty() {
} else if sel_chat.get_type() == Chattype::Single && members.len() >= 1 {
let contact = Contact::get_by_id(context, members[0])?;
contact.get_addr().to_string()
} else {
@@ -832,9 +839,6 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
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.");
@@ -858,7 +862,11 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
"archive" | "unarchive" => {
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
let chat_id = arg1.parse()?;
chat::archive(context, chat_id, arg0 == "archive")?;
chat::archive(
context,
chat_id,
if arg0 == "archive" { true } else { false },
)?;
}
"delchat" => {
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
@@ -879,7 +887,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
}
"forward" => {
ensure!(
!arg1.is_empty() && !arg2.is_empty(),
!arg1.is_empty() && arg2.is_empty(),
"Arguments <msg-id> <chat-id> expected"
);
@@ -1003,5 +1011,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
_ => bail!("Unknown command: \"{}\" type ? for help.", arg0),
}
free(arg1_c as *mut _);
Ok(())
}

View File

@@ -235,7 +235,7 @@ impl Completer for DcHelper {
}
}
const IMEX_COMMANDS: [&str; 12] = [
const IMEX_COMMANDS: [&'static str; 12] = [
"initiate-key-transfer",
"get-setupcodebegin",
"continue-key-transfer",
@@ -250,7 +250,7 @@ const IMEX_COMMANDS: [&str; 12] = [
"stop",
];
const DB_COMMANDS: [&str; 11] = [
const DB_COMMANDS: [&'static str; 11] = [
"info",
"open",
"close",
@@ -264,7 +264,7 @@ const DB_COMMANDS: [&str; 11] = [
"housekeeping",
];
const CHAT_COMMANDS: [&str; 24] = [
const CHAT_COMMANDS: [&'static str; 24] = [
"listchats",
"listarchived",
"chat",
@@ -290,7 +290,7 @@ const CHAT_COMMANDS: [&str; 24] = [
"unarchive",
"delchat",
];
const MESSAGE_COMMANDS: [&str; 8] = [
const MESSAGE_COMMANDS: [&'static str; 8] = [
"listmsgs",
"msginfo",
"listfresh",
@@ -300,7 +300,7 @@ const MESSAGE_COMMANDS: [&str; 8] = [
"unstar",
"delmsg",
];
const CONTACT_COMMANDS: [&str; 6] = [
const CONTACT_COMMANDS: [&'static str; 6] = [
"listcontacts",
"listverified",
"addcontact",
@@ -308,7 +308,7 @@ const CONTACT_COMMANDS: [&str; 6] = [
"delcontact",
"cleanupcontacts",
];
const MISC_COMMANDS: [&str; 9] = [
const MISC_COMMANDS: [&'static str; 9] = [
"getqr", "getbadqr", "checkqr", "event", "fileinfo", "clear", "exit", "quit", "help",
];
@@ -334,8 +334,8 @@ impl Hinter for DcHelper {
}
}
static COLORED_PROMPT: &str = "\x1b[1;32m> \x1b[0m";
static PROMPT: &str = "> ";
static COLORED_PROMPT: &'static str = "\x1b[1;32m> \x1b[0m";
static PROMPT: &'static 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 handle_cmd(line.trim(), ctx) {
match unsafe { handle_cmd(line.trim(), ctx) } {
Ok(ExitResult::Continue) => {}
Ok(ExitResult::Exit) => break,
Err(err) => println!("Error: {}", err),
@@ -434,7 +434,7 @@ enum ExitResult {
Exit,
}
fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult, failure::Error> {
unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult, failure::Error> {
let mut args = line.splitn(2, ' ');
let arg0 = args.next().unwrap_or_default();
let arg1 = args.next().unwrap_or_default();

View File

@@ -101,9 +101,19 @@ 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.write().unwrap() = false;
*running.clone().write().unwrap() = false;
deltachat::job::interrupt_inbox_idle(&ctx, true);
deltachat::job::interrupt_smtp_idle(&ctx);

View File

@@ -0,0 +1,42 @@
# copied from http://koushiro.me/2019/04/30/Building-and-Testing-Rust-projects-on-CircleCI/
version: 2.1
jobs:
build:
docker:
- image: ubuntu:18.04
working_directory: ~/deltachat-core-rust
steps:
- checkout
- run:
name: Setup build environment
command: |
apt update
apt install -y curl build-essential autoconf libtool git python pkg-config
# this will pick default toolchain from `rust-toolchain` file
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --no-modify-path --default-toolchain none -y;
source $HOME/.cargo/env
no_output_timeout: 1800s
- run:
name: Format
command: |
export PATH=~/.cargo/bin:$PATH
rustup component add rustfmt
cargo fmt -- --check
- run:
name: Test
command: |
export PATH=~/.cargo/bin:$PATH
export RUST_BACKTRACE=1
cargo test
workflows:
version: 2.1
build:
jobs:
- build

23
mmime/Cargo.toml Normal file
View File

@@ -0,0 +1,23 @@
[package]
name = "mmime"
version = "0.1.2"
authors = ["dignifiedquire <dignifiedquire@users.noreply.github.com>"]
edition = "2018"
license = "MIT OR Apache-2.0"
homepage = "https://github.com/deltachat/deltachat-core-rust"
repository = "https://github.com/deltachat/deltachat-core-rust"
readme = "README.md"
description = "Mime parsing for email"
keywords = ["mail", "mim", "email", "imap", "smtp"]
categories = ["std", "email"]
[dependencies]
libc = "0.2.54"
charset = "0.1.2"
memmap = "0.7.0"
lazy_static = "1.3.0"
rand = "0.6.5"
chrono = "0.4.6"
hex = "0.4.0"

201
mmime/LICENSE-APACHE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

23
mmime/LICENSE-MIT Normal file
View File

@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

4
mmime/LICENSE.md Normal file
View File

@@ -0,0 +1,4 @@
This library is primarly distributed under the terms of both the MIT license and
the Apache License (Version 2.0).
See LICENSE-MIT and LICENSE-APACHE for details.

16
mmime/README.md Normal file
View File

@@ -0,0 +1,16 @@
# mmime
[![CircleCI build status][circle-shield]][circle] [![Appveyor build status][appveyor-shield]][appveyor] [![License][license-shield]][license]
> mmmmmmime parsing
Base code was compiled using c2rust from libetpan.
[circle-shield]: https://img.shields.io/circleci/project/github/dignifiedquire/mmime/master.svg?style=flat-square
[circle]: https://circleci.com/gh/dignifiedquire/mmime/
[appveyor-shield]: https://ci.appveyor.com/api/projects/status/l26co5rba32knrlu/branch/master?style=flat-square
[appveyor]: https://ci.appveyor.com/project/dignifiedquire/mmime/branch/master
[license-shield]: https://img.shields.io/badge/License-MIT%2FApache2.0-green.svg?style=flat-square
[license]: https://github.com/rpgp/rpgp/blob/master/LICENSE.md

32
mmime/src/charconv.rs Normal file
View File

@@ -0,0 +1,32 @@
use crate::other::*;
use libc;
use std::ffi::{CStr, CString};
pub const MAIL_CHARCONV_ERROR_CONV: libc::c_uint = 3;
pub const MAIL_CHARCONV_ERROR_MEMORY: libc::c_uint = 2;
pub const MAIL_CHARCONV_ERROR_UNKNOWN_CHARSET: libc::c_uint = 1;
pub const MAIL_CHARCONV_NO_ERROR: libc::c_uint = 0;
pub unsafe fn charconv(
tocode: *const libc::c_char,
fromcode: *const libc::c_char,
s: *const libc::c_char,
length: size_t,
result: *mut *mut libc::c_char,
) -> libc::c_int {
assert!(!fromcode.is_null(), "invalid fromcode");
assert!(!s.is_null(), "invalid input string");
if let Some(encoding) =
charset::Charset::for_label(CStr::from_ptr(fromcode).to_string_lossy().as_bytes())
{
let data = std::slice::from_raw_parts(s as *const u8, strlen(s));
let (res, _, _) = encoding.decode(data);
let res_c = CString::new(res.as_bytes()).unwrap_or_default();
*result = strdup(res_c.as_ptr()) as *mut _;
MAIL_CHARCONV_NO_ERROR as libc::c_int
} else {
MAIL_CHARCONV_ERROR_UNKNOWN_CHARSET as libc::c_int
}
}

427
mmime/src/chash.rs Normal file
View File

@@ -0,0 +1,427 @@
use libc;
use crate::other::*;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct chashdatum {
pub data: *mut libc::c_void,
pub len: libc::c_uint,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct chash {
pub size: libc::c_uint,
pub count: libc::c_uint,
pub copyvalue: libc::c_int,
pub copykey: libc::c_int,
pub cells: *mut *mut chashcell,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct chashcell {
pub func: libc::c_uint,
pub key: chashdatum,
pub value: chashdatum,
pub next: *mut chashcell,
}
pub type chashiter = chashcell;
/* Allocates a new (empty) hash using this initial size and the given flags,
specifying which data should be copied in the hash.
CHASH_COPYNONE : Keys/Values are not copied.
CHASH_COPYKEY : Keys are dupped and freed as needed in the hash.
CHASH_COPYVALUE : Values are dupped and freed as needed in the hash.
CHASH_COPYALL : Both keys and values are dupped in the hash.
*/
pub unsafe fn chash_new(mut size: libc::c_uint, mut flags: libc::c_int) -> *mut chash {
let mut h: *mut chash = 0 as *mut chash;
h = malloc(::std::mem::size_of::<chash>() as libc::size_t) as *mut chash;
if h.is_null() {
return 0 as *mut chash;
}
if size < 13i32 as libc::c_uint {
size = 13i32 as libc::c_uint
}
(*h).count = 0i32 as libc::c_uint;
(*h).cells = calloc(
size as libc::size_t,
::std::mem::size_of::<*mut chashcell>() as libc::size_t,
) as *mut *mut chashcell;
if (*h).cells.is_null() {
free(h as *mut libc::c_void);
return 0 as *mut chash;
}
(*h).size = size;
(*h).copykey = flags & 1i32;
(*h).copyvalue = flags & 2i32;
return h;
}
/* Frees a hash */
pub unsafe fn chash_free(mut hash: *mut chash) {
let mut indx: libc::c_uint = 0;
let mut iter: *mut chashiter = 0 as *mut chashiter;
let mut next: *mut chashiter = 0 as *mut chashiter;
indx = 0i32 as libc::c_uint;
while indx < (*hash).size {
iter = *(*hash).cells.offset(indx as isize);
while !iter.is_null() {
next = (*iter).next;
if 0 != (*hash).copykey {
free((*iter).key.data);
}
if 0 != (*hash).copyvalue {
free((*iter).value.data);
}
free(iter as *mut libc::c_void);
iter = next
}
indx = indx.wrapping_add(1)
}
free((*hash).cells as *mut libc::c_void);
free(hash as *mut libc::c_void);
}
/* Removes all elements from a hash */
pub unsafe fn chash_clear(mut hash: *mut chash) {
let mut indx: libc::c_uint = 0;
let mut iter: *mut chashiter = 0 as *mut chashiter;
let mut next: *mut chashiter = 0 as *mut chashiter;
indx = 0i32 as libc::c_uint;
while indx < (*hash).size {
iter = *(*hash).cells.offset(indx as isize);
while !iter.is_null() {
next = (*iter).next;
if 0 != (*hash).copykey {
free((*iter).key.data);
}
if 0 != (*hash).copyvalue {
free((*iter).value.data);
}
free(iter as *mut libc::c_void);
iter = next
}
indx = indx.wrapping_add(1)
}
memset(
(*hash).cells as *mut libc::c_void,
0i32,
((*hash).size as libc::size_t)
.wrapping_mul(::std::mem::size_of::<*mut chashcell>() as libc::size_t),
);
(*hash).count = 0i32 as libc::c_uint;
}
/* Adds an entry in the hash table.
Length can be 0 if key/value are strings.
If an entry already exists for this key, it is replaced, and its value
is returned. Otherwise, the data pointer will be NULL and the length
field be set to TRUE or FALSe to indicate success or failure. */
pub unsafe fn chash_set(
mut hash: *mut chash,
mut key: *mut chashdatum,
mut value: *mut chashdatum,
mut oldvalue: *mut chashdatum,
) -> libc::c_int {
let mut current_block: u64;
let mut func: libc::c_uint = 0;
let mut indx: libc::c_uint = 0;
let mut iter: *mut chashiter = 0 as *mut chashiter;
let mut cell: *mut chashiter = 0 as *mut chashiter;
let mut r: libc::c_int = 0;
if (*hash).count > (*hash).size.wrapping_mul(3i32 as libc::c_uint) {
r = chash_resize(
hash,
(*hash)
.count
.wrapping_div(3i32 as libc::c_uint)
.wrapping_mul(2i32 as libc::c_uint)
.wrapping_add(1i32 as libc::c_uint),
);
if r < 0i32 {
current_block = 17701753836843438419;
} else {
current_block = 7095457783677275021;
}
} else {
current_block = 7095457783677275021;
}
match current_block {
7095457783677275021 => {
func = chash_func((*key).data as *const libc::c_char, (*key).len);
indx = func.wrapping_rem((*hash).size);
iter = *(*hash).cells.offset(indx as isize);
loop {
if iter.is_null() {
current_block = 17788412896529399552;
break;
}
if (*iter).key.len == (*key).len
&& (*iter).func == func
&& 0 == memcmp((*iter).key.data, (*key).data, (*key).len as libc::size_t)
{
/* found, replacing entry */
if 0 != (*hash).copyvalue {
let mut data: *mut libc::c_char = 0 as *mut libc::c_char;
data = chash_dup((*value).data, (*value).len);
if data.is_null() {
current_block = 17701753836843438419;
break;
}
free((*iter).value.data);
(*iter).value.data = data as *mut libc::c_void;
(*iter).value.len = (*value).len
} else {
if !oldvalue.is_null() {
(*oldvalue).data = (*iter).value.data;
(*oldvalue).len = (*iter).value.len
}
(*iter).value.data = (*value).data;
(*iter).value.len = (*value).len
}
if 0 == (*hash).copykey {
(*iter).key.data = (*key).data
}
if !oldvalue.is_null() {
(*oldvalue).data = (*value).data;
(*oldvalue).len = (*value).len
}
return 0i32;
} else {
iter = (*iter).next
}
}
match current_block {
17701753836843438419 => {}
_ => {
if !oldvalue.is_null() {
(*oldvalue).data = 0 as *mut libc::c_void;
(*oldvalue).len = 0i32 as libc::c_uint
}
cell = malloc(::std::mem::size_of::<chashcell>() as libc::size_t)
as *mut chashcell;
if !cell.is_null() {
if 0 != (*hash).copykey {
(*cell).key.data =
chash_dup((*key).data, (*key).len) as *mut libc::c_void;
if (*cell).key.data.is_null() {
current_block = 4267898785354516004;
} else {
current_block = 7226443171521532240;
}
} else {
(*cell).key.data = (*key).data;
current_block = 7226443171521532240;
}
match current_block {
7226443171521532240 => {
(*cell).key.len = (*key).len;
if 0 != (*hash).copyvalue {
(*cell).value.data =
chash_dup((*value).data, (*value).len) as *mut libc::c_void;
if (*cell).value.data.is_null() {
if 0 != (*hash).copykey {
free((*cell).key.data);
}
current_block = 4267898785354516004;
} else {
current_block = 6717214610478484138;
}
} else {
(*cell).value.data = (*value).data;
current_block = 6717214610478484138;
}
match current_block {
4267898785354516004 => {}
_ => {
(*cell).value.len = (*value).len;
(*cell).func = func;
(*cell).next = *(*hash).cells.offset(indx as isize);
let ref mut fresh0 = *(*hash).cells.offset(indx as isize);
*fresh0 = cell;
(*hash).count = (*hash).count.wrapping_add(1);
return 0i32;
}
}
}
_ => {}
}
free(cell as *mut libc::c_void);
}
}
}
}
_ => {}
}
return -1i32;
}
#[inline]
unsafe fn chash_dup(mut data: *const libc::c_void, mut len: libc::c_uint) -> *mut libc::c_char {
let mut r: *mut libc::c_void = 0 as *mut libc::c_void;
r = malloc(len as libc::size_t) as *mut libc::c_char as *mut libc::c_void;
if r.is_null() {
return 0 as *mut libc::c_char;
}
memcpy(r, data, len as libc::size_t);
return r as *mut libc::c_char;
}
#[inline]
unsafe fn chash_func(mut key: *const libc::c_char, mut len: libc::c_uint) -> libc::c_uint {
let mut c: libc::c_uint = 5381i32 as libc::c_uint;
let mut k: *const libc::c_char = key;
loop {
let fresh1 = len;
len = len.wrapping_sub(1);
if !(0 != fresh1) {
break;
}
let fresh2 = k;
k = k.offset(1);
c = (c << 5i32)
.wrapping_add(c)
.wrapping_add(*fresh2 as libc::c_uint)
}
return c;
}
/* Resizes the hash table to the passed size. */
pub unsafe fn chash_resize(mut hash: *mut chash, mut size: libc::c_uint) -> libc::c_int {
let mut cells: *mut *mut chashcell = 0 as *mut *mut chashcell;
let mut indx: libc::c_uint = 0;
let mut nindx: libc::c_uint = 0;
let mut iter: *mut chashiter = 0 as *mut chashiter;
let mut next: *mut chashiter = 0 as *mut chashiter;
if (*hash).size == size {
return 0i32;
}
cells = calloc(
size as libc::size_t,
::std::mem::size_of::<*mut chashcell>() as libc::size_t,
) as *mut *mut chashcell;
if cells.is_null() {
return -1i32;
}
indx = 0i32 as libc::c_uint;
while indx < (*hash).size {
iter = *(*hash).cells.offset(indx as isize);
while !iter.is_null() {
next = (*iter).next;
nindx = (*iter).func.wrapping_rem(size);
(*iter).next = *cells.offset(nindx as isize);
let ref mut fresh3 = *cells.offset(nindx as isize);
*fresh3 = iter;
iter = next
}
indx = indx.wrapping_add(1)
}
free((*hash).cells as *mut libc::c_void);
(*hash).size = size;
(*hash).cells = cells;
return 0i32;
}
/* Retrieves the data associated to the key if it is found in the hash table.
The data pointer and the length will be NULL if not found*/
pub unsafe fn chash_get(
mut hash: *mut chash,
mut key: *mut chashdatum,
mut result: *mut chashdatum,
) -> libc::c_int {
let mut func: libc::c_uint = 0;
let mut iter: *mut chashiter = 0 as *mut chashiter;
func = chash_func((*key).data as *const libc::c_char, (*key).len);
iter = *(*hash)
.cells
.offset(func.wrapping_rem((*hash).size) as isize);
while !iter.is_null() {
if (*iter).key.len == (*key).len
&& (*iter).func == func
&& 0 == memcmp((*iter).key.data, (*key).data, (*key).len as libc::size_t)
{
*result = (*iter).value;
return 0i32;
}
iter = (*iter).next
}
return -1i32;
}
/* Removes the entry associated to this key if it is found in the hash table,
and returns its contents if not dupped (otherwise, pointer will be NULL
and len TRUE). If entry is not found both pointer and len will be NULL. */
pub unsafe fn chash_delete(
mut hash: *mut chash,
mut key: *mut chashdatum,
mut oldvalue: *mut chashdatum,
) -> libc::c_int {
/* chashdatum result = { NULL, TRUE }; */
let mut func: libc::c_uint = 0;
let mut indx: libc::c_uint = 0;
let mut iter: *mut chashiter = 0 as *mut chashiter;
let mut old: *mut chashiter = 0 as *mut chashiter;
func = chash_func((*key).data as *const libc::c_char, (*key).len);
indx = func.wrapping_rem((*hash).size);
old = 0 as *mut chashiter;
iter = *(*hash).cells.offset(indx as isize);
while !iter.is_null() {
if (*iter).key.len == (*key).len
&& (*iter).func == func
&& 0 == memcmp((*iter).key.data, (*key).data, (*key).len as libc::size_t)
{
if !old.is_null() {
(*old).next = (*iter).next
} else {
let ref mut fresh4 = *(*hash).cells.offset(indx as isize);
*fresh4 = (*iter).next
}
if 0 != (*hash).copykey {
free((*iter).key.data);
}
if 0 != (*hash).copyvalue {
free((*iter).value.data);
} else if !oldvalue.is_null() {
(*oldvalue).data = (*iter).value.data;
(*oldvalue).len = (*iter).value.len
}
free(iter as *mut libc::c_void);
(*hash).count = (*hash).count.wrapping_sub(1);
return 0i32;
}
old = iter;
iter = (*iter).next
}
return -1i32;
}
/* Returns an iterator to the first non-empty entry of the hash table */
pub unsafe fn chash_begin(mut hash: *mut chash) -> *mut chashiter {
let mut iter: *mut chashiter = 0 as *mut chashiter;
let mut indx: libc::c_uint = 0i32 as libc::c_uint;
iter = *(*hash).cells.offset(0isize);
while iter.is_null() {
indx = indx.wrapping_add(1);
if indx >= (*hash).size {
return 0 as *mut chashiter;
}
iter = *(*hash).cells.offset(indx as isize)
}
return iter;
}
/* Returns the next non-empty entry of the hash table */
pub unsafe fn chash_next(mut hash: *mut chash, mut iter: *mut chashiter) -> *mut chashiter {
let mut indx: libc::c_uint = 0;
if iter.is_null() {
return 0 as *mut chashiter;
}
indx = (*iter).func.wrapping_rem((*hash).size);
iter = (*iter).next;
while iter.is_null() {
indx = indx.wrapping_add(1);
if indx >= (*hash).size {
return 0 as *mut chashiter;
}
iter = *(*hash).cells.offset(indx as isize)
}
return iter;
}

202
mmime/src/clist.rs Normal file
View File

@@ -0,0 +1,202 @@
use libc;
use crate::other::*;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct clistcell {
pub data: *mut libc::c_void,
pub previous: *mut clistcell,
pub next: *mut clistcell,
}
#[derive(Clone)]
#[repr(C)]
pub struct clist {
pub first: *mut clistcell,
pub last: *mut clistcell,
pub count: libc::c_int,
}
impl Default for clist {
fn default() -> Self {
Self {
first: std::ptr::null_mut(),
last: std::ptr::null_mut(),
count: 0,
}
}
}
impl Drop for clist {
fn drop(&mut self) {
unsafe {
let mut l1 = self.first;
while !l1.is_null() {
let l2 = (*l1).next;
free(l1 as *mut libc::c_void);
l1 = l2
}
}
}
}
pub type clistiter = clistcell;
pub struct CListIterator {
cur: *mut clistiter,
}
impl Iterator for CListIterator {
type Item = *mut libc::c_void;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
if self.cur.is_null() {
None
} else {
let data = (*self.cur).data;
self.cur = (*self.cur).next;
Some(data)
}
}
}
}
impl IntoIterator for &clist {
type Item = *mut libc::c_void;
type IntoIter = CListIterator;
fn into_iter(self) -> Self::IntoIter {
return CListIterator { cur: self.first };
}
}
pub type clist_func =
Option<unsafe extern "C" fn(_: *mut libc::c_void, _: *mut libc::c_void) -> ()>;
/* Allocate a new pointer list */
pub fn clist_new() -> *mut clist {
Box::into_raw(Box::new(Default::default()))
}
/* Destroys a list. Data pointed by data pointers is NOT freed. */
pub unsafe fn clist_free(mut lst: *mut clist) {
Box::from_raw(lst);
}
/* Inserts this data pointer after the element pointed by the iterator */
pub unsafe fn clist_insert_after(
mut lst: *mut clist,
mut iter: *mut clistiter,
mut data: *mut libc::c_void,
) -> libc::c_int {
let mut c: *mut clistcell = 0 as *mut clistcell;
c = malloc(::std::mem::size_of::<clistcell>() as libc::size_t) as *mut clistcell;
if c.is_null() {
return -1i32;
}
(*c).data = data;
(*lst).count += 1;
if (*lst).first == (*lst).last && (*lst).last.is_null() {
(*c).next = 0 as *mut clistcell;
(*c).previous = (*c).next;
(*lst).last = c;
(*lst).first = (*lst).last;
return 0i32;
}
if iter.is_null() {
(*c).previous = (*lst).last;
(*(*c).previous).next = c;
(*c).next = 0 as *mut clistcell;
(*lst).last = c;
return 0i32;
}
(*c).previous = iter;
(*c).next = (*iter).next;
if !(*c).next.is_null() {
(*(*c).next).previous = c
} else {
(*lst).last = c
}
(*(*c).previous).next = c;
return 0i32;
}
/* Deletes the element pointed by the iterator.
Returns an iterator to the next element. */
pub unsafe fn clist_delete(mut lst: *mut clist, mut iter: *mut clistiter) -> *mut clistiter {
let mut ret: *mut clistiter = 0 as *mut clistiter;
if iter.is_null() {
return 0 as *mut clistiter;
}
if !(*iter).previous.is_null() {
(*(*iter).previous).next = (*iter).next
} else {
(*lst).first = (*iter).next
}
if !(*iter).next.is_null() {
(*(*iter).next).previous = (*iter).previous;
ret = (*iter).next
} else {
(*lst).last = (*iter).previous;
ret = 0 as *mut clistiter
}
free(iter as *mut libc::c_void);
(*lst).count -= 1;
return ret;
}
pub unsafe fn clist_foreach(
mut lst: *mut clist,
mut func: clist_func,
mut data: *mut libc::c_void,
) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = (*lst).first;
while !cur.is_null() {
func.expect("non-null function pointer")((*cur).data, data);
cur = (*cur).next
}
}
pub unsafe fn clist_nth_data(mut lst: *mut clist, mut indx: libc::c_int) -> *mut libc::c_void {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = internal_clist_nth(lst, indx);
if cur.is_null() {
return 0 as *mut libc::c_void;
}
return (*cur).data;
}
#[inline]
unsafe fn internal_clist_nth(mut lst: *mut clist, mut indx: libc::c_int) -> *mut clistiter {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = (*lst).first;
while indx > 0i32 && !cur.is_null() {
cur = (*cur).next;
indx -= 1
}
if cur.is_null() {
return 0 as *mut clistiter;
}
return cur;
}
pub unsafe fn clist_nth(mut lst: *mut clist, mut indx: libc::c_int) -> *mut clistiter {
return internal_clist_nth(lst, indx);
}
#[cfg(test)]
mod tests {
use super::*;
use std::ptr;
#[test]
fn test_clist_iterator() {
unsafe {
let mut c = clist_new();
assert!(!c.is_null());
clist_insert_after(c, ptr::null_mut(), clist_nth as _);
assert_eq!((*c).count, 1);
/* Only one iteration */
for data in &*c {
assert_eq!(data, clist_nth as _);
}
assert_eq!((*c).count, 1);
clist_free(c);
}
}
}

71
mmime/src/constants.rs Normal file
View File

@@ -0,0 +1,71 @@
pub const MAIL_ERROR_SSL: libc::c_uint = 58;
pub const MAIL_ERROR_FOLDER: libc::c_uint = 57;
pub const MAIL_ERROR_UNABLE: libc::c_uint = 56;
pub const MAIL_ERROR_SYSTEM: libc::c_uint = 55;
pub const MAIL_ERROR_COMMAND: libc::c_uint = 54;
pub const MAIL_ERROR_SEND: libc::c_uint = 53;
pub const MAIL_ERROR_CHAR_ENCODING_FAILED: libc::c_uint = 52;
pub const MAIL_ERROR_SUBJECT_NOT_FOUND: libc::c_uint = 51;
/* 50 */
pub const MAIL_ERROR_PROGRAM_ERROR: libc::c_uint = 50;
pub const MAIL_ERROR_NO_PERMISSION: libc::c_uint = 49;
pub const MAIL_ERROR_COMMAND_NOT_SUPPORTED: libc::c_uint = 48;
pub const MAIL_ERROR_NO_APOP: libc::c_uint = 47;
pub const MAIL_ERROR_READONLY: libc::c_uint = 46;
pub const MAIL_ERROR_FATAL: libc::c_uint = 45;
pub const MAIL_ERROR_CLOSE: libc::c_uint = 44;
pub const MAIL_ERROR_CAPABILITY: libc::c_uint = 43;
pub const MAIL_ERROR_PROTOCOL: libc::c_uint = 42;
/* misc errors */
pub const MAIL_ERROR_MISC: libc::c_uint = 41;
/* 40 */
pub const MAIL_ERROR_EXPUNGE: libc::c_uint = 40;
pub const MAIL_ERROR_NO_TLS: libc::c_uint = 39;
pub const MAIL_ERROR_CACHE_MISS: libc::c_uint = 38;
pub const MAIL_ERROR_STARTTLS: libc::c_uint = 37;
pub const MAIL_ERROR_MOVE: libc::c_uint = 36;
pub const MAIL_ERROR_FOLDER_NOT_FOUND: libc::c_uint = 35;
pub const MAIL_ERROR_REMOVE: libc::c_uint = 34;
pub const MAIL_ERROR_PART_NOT_FOUND: libc::c_uint = 33;
pub const MAIL_ERROR_INVAL: libc::c_uint = 32;
pub const MAIL_ERROR_PARSE: libc::c_uint = 31;
/* 30 */
pub const MAIL_ERROR_MSG_NOT_FOUND: libc::c_uint = 30;
pub const MAIL_ERROR_DISKSPACE: libc::c_uint = 29;
pub const MAIL_ERROR_SEARCH: libc::c_uint = 28;
pub const MAIL_ERROR_STORE: libc::c_uint = 27;
pub const MAIL_ERROR_FETCH: libc::c_uint = 26;
pub const MAIL_ERROR_COPY: libc::c_uint = 25;
pub const MAIL_ERROR_APPEND: libc::c_uint = 24;
pub const MAIL_ERROR_LSUB: libc::c_uint = 23;
pub const MAIL_ERROR_LIST: libc::c_uint = 22;
pub const MAIL_ERROR_UNSUBSCRIBE: libc::c_uint = 21;
/* 20 */
pub const MAIL_ERROR_SUBSCRIBE: libc::c_uint = 20;
pub const MAIL_ERROR_STATUS: libc::c_uint = 19;
pub const MAIL_ERROR_MEMORY: libc::c_uint = 18;
pub const MAIL_ERROR_SELECT: libc::c_uint = 17;
pub const MAIL_ERROR_EXAMINE: libc::c_uint = 16;
pub const MAIL_ERROR_CHECK: libc::c_uint = 15;
pub const MAIL_ERROR_RENAME: libc::c_uint = 14;
pub const MAIL_ERROR_NOOP: libc::c_uint = 13;
pub const MAIL_ERROR_LOGOUT: libc::c_uint = 12;
pub const MAIL_ERROR_DELETE: libc::c_uint = 11;
/* 10 */
pub const MAIL_ERROR_CREATE: libc::c_uint = 10;
pub const MAIL_ERROR_LOGIN: libc::c_uint = 9;
pub const MAIL_ERROR_STREAM: libc::c_uint = 8;
pub const MAIL_ERROR_FILE: libc::c_uint = 7;
pub const MAIL_ERROR_BAD_STATE: libc::c_uint = 6;
pub const MAIL_ERROR_CONNECT: libc::c_uint = 5;
pub const MAIL_ERROR_UNKNOWN: libc::c_uint = 4;
pub const MAIL_ERROR_NOT_IMPLEMENTED: libc::c_uint = 3;
pub const MAIL_NO_ERROR_NON_AUTHENTICATED: libc::c_uint = 2;
pub const MAIL_NO_ERROR_AUTHENTICATED: libc::c_uint = 1;
pub const MAIL_NO_ERROR: libc::c_uint = 0;
pub const MAILIMF_ERROR_FILE: libc::c_uint = 4;
pub const MAILIMF_ERROR_INVAL: libc::c_uint = 3;
pub const MAILIMF_ERROR_MEMORY: libc::c_uint = 2;
pub const MAILIMF_ERROR_PARSE: libc::c_uint = 1;
pub const MAILIMF_NO_ERROR: libc::c_uint = 0;

386
mmime/src/display.rs Normal file
View File

@@ -0,0 +1,386 @@
use crate::clist::*;
use crate::mailimf::types::*;
use crate::mailmime::types::*;
use std::ffi::CStr;
pub unsafe fn display_mime(mut mime: *mut Mailmime) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
println!("{}", (*mime).mm_type);
match (*mime).mm_type as u32 {
MAILMIME_SINGLE => {
println!("single part");
}
MAILMIME_MULTIPLE => {
println!("multipart");
}
MAILMIME_MESSAGE => println!("message"),
_ => {}
}
if !(*mime).mm_mime_fields.is_null() {
if !(*(*(*mime).mm_mime_fields).fld_list).first.is_null() {
print!("MIME headers begin");
display_mime_fields((*mime).mm_mime_fields);
println!("MIME headers end");
}
}
display_mime_content((*mime).mm_content_type);
match (*mime).mm_type as u32 {
MAILMIME_SINGLE => {
display_mime_data((*mime).mm_data.mm_single);
}
MAILMIME_MULTIPLE => {
cur = (*(*mime).mm_data.mm_multipart.mm_mp_list).first;
while !cur.is_null() {
display_mime(
(if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut Mailmime,
);
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
}
MAILMIME_MESSAGE => {
if !(*mime).mm_data.mm_message.mm_fields.is_null() {
if !(*(*(*mime).mm_data.mm_message.mm_fields).fld_list)
.first
.is_null()
{
println!("headers begin");
display_fields((*mime).mm_data.mm_message.mm_fields);
println!("headers end");
}
if !(*mime).mm_data.mm_message.mm_msg_mime.is_null() {
display_mime((*mime).mm_data.mm_message.mm_msg_mime);
}
}
}
_ => {}
};
}
unsafe fn display_mime_content(mut content_type: *mut mailmime_content) {
print!("type: ");
display_mime_type((*content_type).ct_type);
println!(
"/{}",
CStr::from_ptr((*content_type).ct_subtype).to_str().unwrap()
);
}
unsafe fn display_mime_type(mut type_0: *mut mailmime_type) {
match (*type_0).tp_type {
1 => {
display_mime_discrete_type((*type_0).tp_data.tp_discrete_type);
}
2 => {
display_mime_composite_type((*type_0).tp_data.tp_composite_type);
}
_ => {}
};
}
unsafe fn display_mime_composite_type(mut ct: *mut mailmime_composite_type) {
match (*ct).ct_type {
1 => {
print!("message");
}
2 => {
print!("multipart");
}
3 => {
print!("{}", CStr::from_ptr((*ct).ct_token).to_str().unwrap());
}
_ => {}
};
}
unsafe fn display_mime_discrete_type(mut discrete_type: *mut mailmime_discrete_type) {
match (*discrete_type).dt_type {
1 => {
print!("text");
}
2 => {
print!("image");
}
3 => {
print!("audio");
}
4 => {
print!("video");
}
5 => {
print!("application");
}
6 => {
print!("{}", (*discrete_type).dt_extension as u8 as char);
}
_ => {}
};
}
pub unsafe fn display_mime_data(mut data: *mut mailmime_data) {
match (*data).dt_type {
0 => {
println!(
"data : {} bytes",
(*data).dt_data.dt_text.dt_length as libc::c_uint,
);
}
1 => {
println!(
"data (file) : {}",
CStr::from_ptr((*data).dt_data.dt_filename)
.to_str()
.unwrap()
);
}
_ => {}
};
}
unsafe fn display_mime_dsp_parm(mut param: *mut mailmime_disposition_parm) {
match (*param).pa_type {
0 => {
println!(
"filename: {}",
CStr::from_ptr((*param).pa_data.pa_filename)
.to_str()
.unwrap()
);
}
_ => {}
};
}
unsafe fn display_mime_disposition(mut disposition: *mut mailmime_disposition) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = (*(*disposition).dsp_parms).first;
while !cur.is_null() {
let mut param: *mut mailmime_disposition_parm = 0 as *mut mailmime_disposition_parm;
param = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut mailmime_disposition_parm;
display_mime_dsp_parm(param);
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
}
unsafe fn display_mime_field(mut field: *mut mailmime_field) {
match (*field).fld_type {
1 => {
print!("content-type: ");
display_mime_content((*field).fld_data.fld_content);
println!("");
}
6 => {
display_mime_disposition((*field).fld_data.fld_disposition);
}
_ => {}
};
}
unsafe fn display_mime_fields(mut fields: *mut mailmime_fields) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = (*(*fields).fld_list).first;
while !cur.is_null() {
let mut field: *mut mailmime_field = 0 as *mut mailmime_field;
field = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut mailmime_field;
display_mime_field(field);
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
}
unsafe fn display_date_time(mut d: *mut mailimf_date_time) {
print!(
"{:02}/{:02}/{:02} {:02}:{:02}:{:02} +{:04}",
(*d).dt_day,
(*d).dt_month,
(*d).dt_year,
(*d).dt_hour,
(*d).dt_min,
(*d).dt_sec,
(*d).dt_zone,
);
}
unsafe fn display_orig_date(mut orig_date: *mut mailimf_orig_date) {
display_date_time((*orig_date).dt_date_time);
}
unsafe fn display_mailbox(mut mb: *mut mailimf_mailbox) {
if !(*mb).mb_display_name.is_null() {
print!(
"{}",
CStr::from_ptr((*mb).mb_display_name).to_str().unwrap()
);
}
print!("<{}>", CStr::from_ptr((*mb).mb_addr_spec).to_str().unwrap());
}
unsafe fn display_mailbox_list(mut mb_list: *mut mailimf_mailbox_list) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = (*(*mb_list).mb_list).first;
while !cur.is_null() {
let mut mb: *mut mailimf_mailbox = 0 as *mut mailimf_mailbox;
mb = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut mailimf_mailbox;
display_mailbox(mb);
if !if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
.is_null()
{
print!(", ");
}
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
}
unsafe fn display_group(mut group: *mut mailimf_group) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
print!(
"{}: ",
CStr::from_ptr((*group).grp_display_name).to_str().unwrap()
);
cur = (*(*(*group).grp_mb_list).mb_list).first;
while !cur.is_null() {
let mut mb: *mut mailimf_mailbox = 0 as *mut mailimf_mailbox;
mb = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut mailimf_mailbox;
display_mailbox(mb);
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
print!("; ");
}
unsafe fn display_address(mut a: *mut mailimf_address) {
match (*a).ad_type {
2 => {
display_group((*a).ad_data.ad_group);
}
1 => {
display_mailbox((*a).ad_data.ad_mailbox);
}
_ => {}
};
}
unsafe fn display_address_list(mut addr_list: *mut mailimf_address_list) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = (*(*addr_list).ad_list).first;
while !cur.is_null() {
let mut addr: *mut mailimf_address = 0 as *mut mailimf_address;
addr = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut mailimf_address;
display_address(addr);
if !if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
.is_null()
{
print!(", ");
}
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
}
unsafe fn display_from(mut from: *mut mailimf_from) {
display_mailbox_list((*from).frm_mb_list);
}
unsafe fn display_to(mut to: *mut mailimf_to) {
display_address_list((*to).to_addr_list);
}
unsafe fn display_cc(mut cc: *mut mailimf_cc) {
display_address_list((*cc).cc_addr_list);
}
unsafe fn display_subject(mut subject: *mut mailimf_subject) {
print!("{}", CStr::from_ptr((*subject).sbj_value).to_str().unwrap());
}
unsafe fn display_field(mut field: *mut mailimf_field) {
match (*field).fld_type {
9 => {
print!("Date: ");
display_orig_date((*field).fld_data.fld_orig_date);
println!("");
}
10 => {
print!("From: ");
display_from((*field).fld_data.fld_from);
println!("");
}
13 => {
print!("To: ");
display_to((*field).fld_data.fld_to);
println!("");
}
14 => {
print!("Cc: ");
display_cc((*field).fld_data.fld_cc);
println!("");
}
19 => {
print!("Subject: ");
display_subject((*field).fld_data.fld_subject);
println!("");
}
16 => {
println!(
"Message-ID: {}",
CStr::from_ptr((*(*field).fld_data.fld_message_id).mid_value)
.to_str()
.unwrap(),
);
}
_ => {}
};
}
unsafe fn display_fields(mut fields: *mut mailimf_fields) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = (*(*fields).fld_list).first;
while !cur.is_null() {
let mut f: *mut mailimf_field = 0 as *mut mailimf_field;
f = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut mailimf_field;
display_field(f);
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
}

78
mmime/src/lib.rs Normal file
View File

@@ -0,0 +1,78 @@
#![deny(clippy::correctness)]
// TODO: make all of these errors, such that clippy actually passes.
#![warn(clippy::all, clippy::perf, clippy::not_unsafe_ptr_arg_deref)]
// This is nice, but for now just annoying.
#![allow(clippy::unreadable_literal)]
#![feature(ptr_wrapping_offset_from)]
#![allow(unused_attributes)]
#![allow(unused_variables)]
#![allow(mutable_transmutes)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(unused_assignments)]
#![allow(unused_mut)]
#![allow(unused_must_use)]
#![feature(extern_types)]
#![feature(const_raw_ptr_to_usize_cast)]
pub mod charconv;
pub mod chash;
pub mod clist;
pub mod display;
pub mod mailimf;
pub mod mailmime;
pub mod mmapstring;
pub mod other;
pub use self::charconv::*;
pub use self::chash::*;
pub use self::clist::*;
pub use self::display::*;
pub use self::mailimf::*;
pub use self::mailmime::*;
pub use self::mmapstring::*;
pub use self::other::*;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mailmime_parse_test() {
unsafe {
let data = "MIME-Version: 1.0\
Content-Type: multipart/mixed; boundary=frontier\
\
This is a message with multiple parts in MIME format.\
--frontier\
Content-Type: text/plain\
\
This is the body of the message.\
--frontier\
Content-Type: application/octet-stream\
Content-Transfer-Encoding: base64\
\
PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\
Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==\
--frontier--";
let c_data = std::ffi::CString::new(data).unwrap();
let mut current_index = 0;
let mut mime = std::ptr::null_mut();
let res = crate::mailmime::content::mailmime_parse(
c_data.as_ptr(),
data.len() as usize,
&mut current_index,
&mut mime,
);
assert_eq!(res, MAIL_NO_ERROR as libc::c_int);
assert!(!mime.is_null());
display_mime(mime);
mailmime::types::mailmime_free(mime);
}
}
}

5921
mmime/src/mailimf/mod.rs Normal file

File diff suppressed because it is too large Load Diff

1196
mmime/src/mailimf/types.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,89 @@
use crate::clist::*;
use crate::mailimf::types::*;
use crate::other::*;
/*
this function creates a new mailimf_fields structure with no fields
*/
pub unsafe fn mailimf_fields_new_empty() -> *mut mailimf_fields {
let mut list: *mut clist = 0 as *mut clist;
let mut fields_list: *mut mailimf_fields = 0 as *mut mailimf_fields;
list = clist_new();
if list.is_null() {
return 0 as *mut mailimf_fields;
}
fields_list = mailimf_fields_new(list);
if fields_list.is_null() {
return 0 as *mut mailimf_fields;
}
return fields_list;
}
/*
this function adds a field to the mailimf_fields structure
@return MAILIMF_NO_ERROR will be returned on success,
other code will be returned otherwise
*/
pub unsafe fn mailimf_fields_add(
mut fields: *mut mailimf_fields,
mut field: *mut mailimf_field,
) -> libc::c_int {
let mut r: libc::c_int = 0;
r = clist_insert_after(
(*fields).fld_list,
(*(*fields).fld_list).last,
field as *mut libc::c_void,
);
if r < 0i32 {
return MAILIMF_ERROR_MEMORY as libc::c_int;
}
return MAILIMF_NO_ERROR as libc::c_int;
}
/*
mailimf_field_new_custom creates a new field of type optional
@param name should be allocated with malloc()
@param value should be allocated with malloc()
*/
pub unsafe fn mailimf_field_new_custom(
mut name: *mut libc::c_char,
mut value: *mut libc::c_char,
) -> *mut mailimf_field {
let mut opt_field: *mut mailimf_optional_field = 0 as *mut mailimf_optional_field;
let mut field: *mut mailimf_field = 0 as *mut mailimf_field;
opt_field = mailimf_optional_field_new(name, value);
if !opt_field.is_null() {
field = mailimf_field_new(
MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int,
0 as *mut mailimf_return,
0 as *mut mailimf_orig_date,
0 as *mut mailimf_from,
0 as *mut mailimf_sender,
0 as *mut mailimf_to,
0 as *mut mailimf_cc,
0 as *mut mailimf_bcc,
0 as *mut mailimf_message_id,
0 as *mut mailimf_orig_date,
0 as *mut mailimf_from,
0 as *mut mailimf_sender,
0 as *mut mailimf_reply_to,
0 as *mut mailimf_to,
0 as *mut mailimf_cc,
0 as *mut mailimf_bcc,
0 as *mut mailimf_message_id,
0 as *mut mailimf_in_reply_to,
0 as *mut mailimf_references,
0 as *mut mailimf_subject,
0 as *mut mailimf_comments,
0 as *mut mailimf_keywords,
opt_field,
);
if field.is_null() {
mailimf_optional_field_free(opt_field);
} else {
return field;
}
}
return 0 as *mut mailimf_field;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,860 @@
use libc;
use libc::toupper;
use crate::charconv::*;
use crate::mailimf::*;
use crate::mailmime::content::*;
use crate::mailmime::types::*;
use crate::mmapstring::*;
use crate::other::*;
pub const TYPE_WORD: libc::c_uint = 1;
pub const TYPE_ENCODED_WORD: libc::c_uint = 2;
pub const MAILMIME_ENCODING_Q: libc::c_uint = 1;
pub const MAILMIME_ENCODING_B: libc::c_uint = 0;
pub const TYPE_ERROR: libc::c_uint = 0;
pub unsafe fn mailmime_encoded_phrase_parse(
mut default_fromcode: *const libc::c_char,
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut tocode: *const libc::c_char,
mut result: *mut *mut libc::c_char,
) -> libc::c_int {
let mut current_block: u64;
let mut gphrase: *mut MMAPString = 0 as *mut MMAPString;
let mut word: *mut mailmime_encoded_word = 0 as *mut mailmime_encoded_word;
let mut first: libc::c_int = 0;
let mut cur_token: size_t = 0;
let mut r: libc::c_int = 0;
let mut res: libc::c_int = 0;
let mut str: *mut libc::c_char = 0 as *mut libc::c_char;
let mut wordutf8: *mut libc::c_char = 0 as *mut libc::c_char;
let mut type_0: libc::c_int = 0;
let mut missing_closing_quote: libc::c_int = 0;
cur_token = *indx;
gphrase = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
if gphrase.is_null() {
res = MAILIMF_ERROR_MEMORY as libc::c_int
} else {
first = 1i32;
type_0 = TYPE_ERROR as libc::c_int;
loop {
let mut has_fwd: libc::c_int = 0;
word = 0 as *mut mailmime_encoded_word;
r = mailmime_encoded_word_parse(
message,
length,
&mut cur_token,
&mut word,
&mut has_fwd,
&mut missing_closing_quote,
);
if r == MAILIMF_NO_ERROR as libc::c_int {
if 0 == first && 0 != has_fwd {
if type_0 != TYPE_ENCODED_WORD as libc::c_int {
if mmap_string_append_c(gphrase, ' ' as i32 as libc::c_char).is_null() {
mailmime_encoded_word_free(word);
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 13246848547199022064;
break;
}
}
}
type_0 = TYPE_ENCODED_WORD as libc::c_int;
wordutf8 = 0 as *mut libc::c_char;
r = charconv(
tocode,
(*word).wd_charset,
(*word).wd_text,
strlen((*word).wd_text),
&mut wordutf8,
);
match r {
2 => {
mailmime_encoded_word_free(word);
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 13246848547199022064;
break;
}
1 => {
r = charconv(
tocode,
b"iso-8859-1\x00" as *const u8 as *const libc::c_char,
(*word).wd_text,
strlen((*word).wd_text),
&mut wordutf8,
)
}
3 => {
mailmime_encoded_word_free(word);
res = MAILIMF_ERROR_PARSE as libc::c_int;
current_block = 13246848547199022064;
break;
}
_ => {}
}
match r {
2 => {
mailmime_encoded_word_free(word);
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 13246848547199022064;
break;
}
3 => {
mailmime_encoded_word_free(word);
res = MAILIMF_ERROR_PARSE as libc::c_int;
current_block = 13246848547199022064;
break;
}
_ => {
if !wordutf8.is_null() {
if mmap_string_append(gphrase, wordutf8).is_null() {
mailmime_encoded_word_free(word);
free(wordutf8 as *mut libc::c_void);
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 13246848547199022064;
break;
} else {
free(wordutf8 as *mut libc::c_void);
}
}
mailmime_encoded_word_free(word);
first = 0i32
}
}
} else if !(r == MAILIMF_ERROR_PARSE as libc::c_int) {
/* do nothing */
res = r;
current_block = 13246848547199022064;
break;
}
if !(r == MAILIMF_ERROR_PARSE as libc::c_int) {
continue;
}
let mut raw_word: *mut libc::c_char = 0 as *mut libc::c_char;
raw_word = 0 as *mut libc::c_char;
r = mailmime_non_encoded_word_parse(
message,
length,
&mut cur_token,
&mut raw_word,
&mut has_fwd,
);
if r == MAILIMF_NO_ERROR as libc::c_int {
if 0 == first && 0 != has_fwd {
if mmap_string_append_c(gphrase, ' ' as i32 as libc::c_char).is_null() {
free(raw_word as *mut libc::c_void);
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 13246848547199022064;
break;
}
}
type_0 = TYPE_WORD as libc::c_int;
wordutf8 = 0 as *mut libc::c_char;
r = charconv(
tocode,
default_fromcode,
raw_word,
strlen(raw_word),
&mut wordutf8,
);
match r {
2 => {
free(raw_word as *mut libc::c_void);
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 13246848547199022064;
break;
}
1 | 3 => {
free(raw_word as *mut libc::c_void);
res = MAILIMF_ERROR_PARSE as libc::c_int;
current_block = 13246848547199022064;
break;
}
_ => {
if mmap_string_append(gphrase, wordutf8).is_null() {
free(wordutf8 as *mut libc::c_void);
free(raw_word as *mut libc::c_void);
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 13246848547199022064;
break;
} else {
free(wordutf8 as *mut libc::c_void);
free(raw_word as *mut libc::c_void);
first = 0i32
}
}
}
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
r = mailimf_fws_parse(message, length, &mut cur_token);
if r != MAILIMF_NO_ERROR as libc::c_int {
current_block = 5005389895767293342;
break;
}
if mmap_string_append_c(gphrase, ' ' as i32 as libc::c_char).is_null() {
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 13246848547199022064;
break;
} else {
first = 0i32;
current_block = 5005389895767293342;
break;
}
} else {
res = r;
current_block = 13246848547199022064;
break;
}
}
match current_block {
5005389895767293342 => {
if 0 != first {
if cur_token != length {
res = MAILIMF_ERROR_PARSE as libc::c_int;
current_block = 13246848547199022064;
} else {
current_block = 7072655752890836508;
}
} else {
current_block = 7072655752890836508;
}
match current_block {
13246848547199022064 => {}
_ => {
str = strdup((*gphrase).str_0);
if str.is_null() {
res = MAILIMF_ERROR_MEMORY as libc::c_int
} else {
mmap_string_free(gphrase);
*result = str;
*indx = cur_token;
return MAILIMF_NO_ERROR as libc::c_int;
}
}
}
}
_ => {}
}
mmap_string_free(gphrase);
}
return res;
}
unsafe fn mailmime_non_encoded_word_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut libc::c_char,
mut p_has_fwd: *mut libc::c_int,
) -> libc::c_int {
let mut end: libc::c_int = 0;
let mut cur_token: size_t = 0;
let mut res: libc::c_int = 0;
let mut text: *mut libc::c_char = 0 as *mut libc::c_char;
let mut r: libc::c_int = 0;
let mut begin: size_t = 0;
let mut state: libc::c_int = 0;
let mut has_fwd: libc::c_int = 0;
cur_token = *indx;
has_fwd = 0i32;
r = mailimf_fws_parse(message, length, &mut cur_token);
if r == MAILIMF_NO_ERROR as libc::c_int {
has_fwd = 1i32
}
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
res = r
} else {
begin = cur_token;
state = 0i32;
end = 0i32;
while !(cur_token >= length) {
let mut current_block_17: u64;
match *message.offset(cur_token as isize) as libc::c_int {
32 | 9 | 13 | 10 => {
state = 0i32;
end = 1i32;
current_block_17 = 16924917904204750491;
}
61 => {
state = 1i32;
current_block_17 = 16924917904204750491;
}
63 => {
if state == 1i32 {
cur_token = cur_token.wrapping_sub(1);
end = 1i32
}
current_block_17 = 10192508258555769664;
}
_ => {
current_block_17 = 10192508258555769664;
}
}
match current_block_17 {
10192508258555769664 => state = 0i32,
_ => {}
}
if 0 != end {
break;
}
cur_token = cur_token.wrapping_add(1)
}
if cur_token.wrapping_sub(begin) == 0i32 as libc::size_t {
res = MAILIMF_ERROR_PARSE as libc::c_int
} else {
text = malloc(
cur_token
.wrapping_sub(begin)
.wrapping_add(1i32 as libc::size_t),
) as *mut libc::c_char;
if text.is_null() {
res = MAILIMF_ERROR_MEMORY as libc::c_int
} else {
memcpy(
text as *mut libc::c_void,
message.offset(begin as isize) as *const libc::c_void,
cur_token.wrapping_sub(begin),
);
*text.offset(cur_token.wrapping_sub(begin) as isize) =
'\u{0}' as i32 as libc::c_char;
*indx = cur_token;
*result = text;
*p_has_fwd = has_fwd;
return MAILIMF_NO_ERROR as libc::c_int;
}
}
}
return res;
}
pub unsafe fn mailmime_encoded_word_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut mailmime_encoded_word,
mut p_has_fwd: *mut libc::c_int,
mut p_missing_closing_quote: *mut libc::c_int,
) -> libc::c_int {
let mut current_block: u64;
/*
Parse the following, when a unicode character encoding is split.
=?UTF-8?B?4Lij4Liw4LmA4Lia4Li04LiU4LiE4Lin4Liy4Lih4Lih4Lix4LiZ4Liq4LmM?=
=?UTF-8?B?4LmA4LiV4LmH4Lih4Lie4Li04LiB4Lix4LiUIFRSQU5TRk9STUVSUyA0IOC4?=
=?UTF-8?B?oeC4seC4meC4quC5jOC4hOC4o+C4muC4l+C4uOC4geC4o+C4sOC4muC4miDg?=
=?UTF-8?B?uJfguLXguYjguYDguJTguLXguKLguKfguYPguJnguYDguKHguLfguK3guIfg?=
=?UTF-8?B?uYTguJfguKI=?=
Expected result:
ระเบิดความมันส์เต็มพิกัด TRANSFORMERS 4 มันส์ครบทุกระบบ ที่เดียวในเมืองไทย
libetpan result:
ระเบิดความมันส์เต็มพิกัด TRANSFORMERS 4 ?ันส์ครบทุกระบบ ??ี่เดียวในเมือง??ทย
See https://github.com/dinhviethoa/libetpan/pull/211
*/
let mut cur_token: size_t = 0;
let mut charset: *mut libc::c_char = 0 as *mut libc::c_char;
let mut encoding: libc::c_int = 0;
let mut body: *mut libc::c_char = 0 as *mut libc::c_char;
let mut old_body_len: size_t = 0;
let mut text: *mut libc::c_char = 0 as *mut libc::c_char;
let mut end_encoding: size_t = 0;
let mut lookfwd_cur_token: size_t = 0;
let mut lookfwd_charset: *mut libc::c_char = 0 as *mut libc::c_char;
let mut lookfwd_encoding: libc::c_int = 0;
let mut copy_len: size_t = 0;
let mut decoded_token: size_t = 0;
let mut decoded: *mut libc::c_char = 0 as *mut libc::c_char;
let mut decoded_len: size_t = 0;
let mut ew: *mut mailmime_encoded_word = 0 as *mut mailmime_encoded_word;
let mut r: libc::c_int = 0;
let mut res: libc::c_int = 0;
let mut opening_quote: libc::c_int = 0;
let mut end: libc::c_int = 0;
let mut has_fwd: libc::c_int = 0;
let mut missing_closing_quote: libc::c_int = 0;
cur_token = *indx;
text = 0 as *mut libc::c_char;
lookfwd_charset = 0 as *mut libc::c_char;
missing_closing_quote = 0i32;
has_fwd = 0i32;
r = mailimf_fws_parse(message, length, &mut cur_token);
if r == MAILIMF_NO_ERROR as libc::c_int {
has_fwd = 1i32
}
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
res = r
} else {
opening_quote = 0i32;
r = mailimf_char_parse(message, length, &mut cur_token, '\"' as i32 as libc::c_char);
if r == MAILIMF_NO_ERROR as libc::c_int {
opening_quote = 1i32;
current_block = 17788412896529399552;
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
current_block = 17788412896529399552;
} else {
/* do nothing */
res = r;
current_block = 7995813543095296079;
}
match current_block {
7995813543095296079 => {}
_ => {
r = mailimf_token_case_insensitive_len_parse(
message,
length,
&mut cur_token,
b"=?\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
strlen(b"=?\x00" as *const u8 as *const libc::c_char),
);
if r != MAILIMF_NO_ERROR as libc::c_int {
res = r
} else {
r = mailmime_charset_parse(message, length, &mut cur_token, &mut charset);
if r != MAILIMF_NO_ERROR as libc::c_int {
res = r
} else {
r = mailimf_char_parse(
message,
length,
&mut cur_token,
'?' as i32 as libc::c_char,
);
if r != MAILIMF_NO_ERROR as libc::c_int {
res = r
} else {
r = mailmime_encoding_parse(
message,
length,
&mut cur_token,
&mut encoding,
);
if r != MAILIMF_NO_ERROR as libc::c_int {
res = r
} else {
r = mailimf_char_parse(
message,
length,
&mut cur_token,
'?' as i32 as libc::c_char,
);
if r != MAILIMF_NO_ERROR as libc::c_int {
res = r
} else {
lookfwd_cur_token = cur_token;
body = 0 as *mut libc::c_char;
old_body_len = 0i32 as size_t;
loop {
let mut has_base64_padding: libc::c_int = 0;
end = 0i32;
has_base64_padding = 0i32;
end_encoding = cur_token;
while !(end_encoding >= length) {
if end_encoding.wrapping_add(1i32 as libc::size_t)
< length
{
if *message.offset(end_encoding as isize)
as libc::c_int
== '?' as i32
&& *message.offset(
end_encoding
.wrapping_add(1i32 as libc::size_t)
as isize,
)
as libc::c_int
== '=' as i32
{
end = 1i32
}
}
if 0 != end {
break;
}
end_encoding = end_encoding.wrapping_add(1)
}
copy_len = end_encoding.wrapping_sub(lookfwd_cur_token);
if copy_len > 0i32 as libc::size_t {
if encoding == MAILMIME_ENCODING_B as libc::c_int {
if end_encoding >= 1i32 as libc::size_t {
if *message.offset(
end_encoding
.wrapping_sub(1i32 as libc::size_t)
as isize,
)
as libc::c_int
== '=' as i32
{
has_base64_padding = 1i32
}
}
}
body = realloc(
body as *mut libc::c_void,
old_body_len
.wrapping_add(copy_len)
.wrapping_add(1i32 as libc::size_t),
)
as *mut libc::c_char;
if body.is_null() {
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 13900684162107791171;
break;
} else {
memcpy(
body.offset(old_body_len as isize)
as *mut libc::c_void,
&*message.offset(cur_token as isize)
as *const libc::c_char
as *const libc::c_void,
copy_len,
);
*body
.offset(old_body_len.wrapping_add(copy_len)
as isize) = '\u{0}' as i32 as libc::c_char;
old_body_len = (old_body_len as libc::size_t)
.wrapping_add(copy_len)
as size_t
as size_t
}
}
cur_token = end_encoding;
r = mailimf_token_case_insensitive_len_parse(
message,
length,
&mut cur_token,
b"?=\x00" as *const u8 as *const libc::c_char
as *mut libc::c_char,
strlen(b"?=\x00" as *const u8 as *const libc::c_char),
);
if r != MAILIMF_NO_ERROR as libc::c_int {
current_block = 2652804691515851435;
break;
}
if 0 != has_base64_padding {
current_block = 2652804691515851435;
break;
}
lookfwd_cur_token = cur_token;
r = mailimf_fws_parse(
message,
length,
&mut lookfwd_cur_token,
);
if r != MAILIMF_NO_ERROR as libc::c_int
&& r != MAILIMF_ERROR_PARSE as libc::c_int
{
current_block = 2652804691515851435;
break;
}
r = mailimf_token_case_insensitive_len_parse(
message,
length,
&mut lookfwd_cur_token,
b"=?\x00" as *const u8 as *const libc::c_char
as *mut libc::c_char,
strlen(b"=?\x00" as *const u8 as *const libc::c_char),
);
if r != MAILIMF_NO_ERROR as libc::c_int {
current_block = 2652804691515851435;
break;
}
r = mailmime_charset_parse(
message,
length,
&mut lookfwd_cur_token,
&mut lookfwd_charset,
);
if r != MAILIMF_NO_ERROR as libc::c_int {
current_block = 2652804691515851435;
break;
}
r = mailimf_char_parse(
message,
length,
&mut lookfwd_cur_token,
'?' as i32 as libc::c_char,
);
if r != MAILIMF_NO_ERROR as libc::c_int {
current_block = 2652804691515851435;
break;
}
r = mailmime_encoding_parse(
message,
length,
&mut lookfwd_cur_token,
&mut lookfwd_encoding,
);
if r != MAILIMF_NO_ERROR as libc::c_int {
current_block = 2652804691515851435;
break;
}
r = mailimf_char_parse(
message,
length,
&mut lookfwd_cur_token,
'?' as i32 as libc::c_char,
);
if r != MAILIMF_NO_ERROR as libc::c_int {
current_block = 2652804691515851435;
break;
}
if strcasecmp(charset, lookfwd_charset) == 0i32
&& encoding == lookfwd_encoding
{
cur_token = lookfwd_cur_token;
mailmime_charset_free(lookfwd_charset);
lookfwd_charset = 0 as *mut libc::c_char
} else {
/* the next charset is not matched with the current one,
therefore exit the loop to decode the body appended so far */
current_block = 2652804691515851435;
break;
}
}
match current_block {
2652804691515851435 => {
if !lookfwd_charset.is_null() {
mailmime_charset_free(lookfwd_charset);
lookfwd_charset = 0 as *mut libc::c_char
}
if body.is_null() {
body = strdup(
b"\x00" as *const u8 as *const libc::c_char,
);
if body.is_null() {
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 13900684162107791171;
} else {
current_block = 16778110326724371720;
}
} else {
current_block = 16778110326724371720;
}
match current_block {
13900684162107791171 => {}
_ => {
decoded_token = 0i32 as size_t;
decoded_len = 0i32 as size_t;
decoded = 0 as *mut libc::c_char;
match encoding {
0 => {
r = mailmime_base64_body_parse(
body,
strlen(body),
&mut decoded_token,
&mut decoded,
&mut decoded_len,
);
if r != MAILIMF_NO_ERROR as libc::c_int
{
res = r;
current_block =
13900684162107791171;
} else {
current_block = 7337917895049117968;
}
}
1 => {
r =
mailmime_quoted_printable_body_parse(body,
strlen(body),
&mut decoded_token,
&mut decoded,
&mut decoded_len,
1i32);
if r != MAILIMF_NO_ERROR as libc::c_int
{
res = r;
current_block =
13900684162107791171;
} else {
current_block = 7337917895049117968;
}
}
_ => {
current_block = 7337917895049117968;
}
}
match current_block {
13900684162107791171 => {}
_ => {
text =
malloc(decoded_len.wrapping_add(
1i32 as libc::size_t,
))
as *mut libc::c_char;
if text.is_null() {
res = MAILIMF_ERROR_MEMORY
as libc::c_int
} else {
if decoded_len
> 0i32 as libc::size_t
{
memcpy(
text as *mut libc::c_void,
decoded
as *const libc::c_void,
decoded_len,
);
}
*text
.offset(decoded_len as isize) =
'\u{0}' as i32 as libc::c_char;
if 0 != opening_quote {
r = mailimf_char_parse(
message,
length,
&mut cur_token,
'\"' as i32 as libc::c_char,
);
if r == MAILIMF_ERROR_PARSE
as libc::c_int
{
missing_closing_quote = 1i32
}
}
if strcasecmp(
charset,
b"utf8\x00" as *const u8
as *const libc::c_char,
) == 0i32
{
free(
charset
as *mut libc::c_void,
);
charset = strdup(
b"utf-8\x00" as *const u8
as *const libc::c_char,
)
}
ew = mailmime_encoded_word_new(
charset, text,
);
if ew.is_null() {
res = MAILIMF_ERROR_MEMORY
as libc::c_int
} else {
*result = ew;
*indx = cur_token;
*p_has_fwd = has_fwd;
*p_missing_closing_quote =
missing_closing_quote;
mailmime_decoded_part_free(
decoded,
);
free(body as *mut libc::c_void);
return MAILIMF_NO_ERROR
as libc::c_int;
}
}
mailmime_decoded_part_free(decoded);
}
}
}
}
}
_ => {}
}
free(body as *mut libc::c_void);
mailmime_encoded_text_free(text);
}
}
}
mailmime_charset_free(charset);
}
}
}
}
}
return res;
}
unsafe fn mailmime_encoding_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut libc::c_int,
) -> libc::c_int {
let mut cur_token: size_t = 0;
let mut encoding: libc::c_int = 0;
cur_token = *indx;
if cur_token >= length {
return MAILIMF_ERROR_PARSE as libc::c_int;
}
match toupper(*message.offset(cur_token as isize) as libc::c_uchar as libc::c_int)
as libc::c_char as libc::c_int
{
81 => encoding = MAILMIME_ENCODING_Q as libc::c_int,
66 => encoding = MAILMIME_ENCODING_B as libc::c_int,
_ => return MAILIMF_ERROR_INVAL as libc::c_int,
}
cur_token = cur_token.wrapping_add(1);
*result = encoding;
*indx = cur_token;
return MAILIMF_NO_ERROR as libc::c_int;
}
/*
* libEtPan! -- a mail stuff library
*
* Copyright (C) 2001, 2005 - DINH Viet Hoa
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the libEtPan! project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* $Id: mailmime_decode.c,v 1.37 2010/11/16 20:52:28 hoa Exp $
*/
/*
RFC 2047 : MIME (Multipurpose Internet Mail Extensions) Part Three:
Message Header Extensions for Non-ASCII Text
*/
unsafe fn mailmime_charset_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut charset: *mut *mut libc::c_char,
) -> libc::c_int {
return mailmime_etoken_parse(message, length, indx, charset);
}
unsafe fn mailmime_etoken_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut libc::c_char,
) -> libc::c_int {
return mailimf_custom_string_parse(message, length, indx, result, Some(is_etoken_char));
}
unsafe fn is_etoken_char(mut ch: libc::c_char) -> libc::c_int {
let mut uch: libc::c_uchar = ch as libc::c_uchar;
if (uch as libc::c_int) < 31i32 {
return 0i32;
}
match uch as libc::c_int {
32 | 40 | 41 | 60 | 62 | 64 | 44 | 59 | 58 | 34 | 47 | 91 | 93 | 63 | 61 => return 0i32,
_ => {}
}
return 1i32;
}

View File

@@ -0,0 +1,583 @@
use libc::{self, toupper};
use crate::clist::*;
use crate::mailimf::*;
use crate::mailmime::types::*;
use crate::mailmime::*;
use crate::other::*;
pub const MAILMIME_DISPOSITION_TYPE_EXTENSION: libc::c_uint = 3;
pub const MAILMIME_DISPOSITION_TYPE_ATTACHMENT: libc::c_uint = 2;
pub const MAILMIME_DISPOSITION_TYPE_INLINE: libc::c_uint = 1;
pub const MAILMIME_DISPOSITION_TYPE_ERROR: libc::c_uint = 0;
pub unsafe fn mailmime_disposition_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut mailmime_disposition,
) -> libc::c_int {
let mut current_block: u64;
let mut final_token: size_t = 0;
let mut cur_token: size_t = 0;
let mut dsp_type: *mut mailmime_disposition_type = 0 as *mut mailmime_disposition_type;
let mut list: *mut clist = 0 as *mut clist;
let mut dsp: *mut mailmime_disposition = 0 as *mut mailmime_disposition;
let mut r: libc::c_int = 0;
let mut res: libc::c_int = 0;
cur_token = *indx;
r = mailmime_disposition_type_parse(message, length, &mut cur_token, &mut dsp_type);
if r != MAILIMF_NO_ERROR as libc::c_int {
res = r
} else {
list = clist_new();
if list.is_null() {
res = MAILIMF_ERROR_MEMORY as libc::c_int
} else {
loop {
let mut param: *mut mailmime_disposition_parm = 0 as *mut mailmime_disposition_parm;
final_token = cur_token;
r = mailimf_unstrict_char_parse(
message,
length,
&mut cur_token,
';' as i32 as libc::c_char,
);
if r == MAILIMF_NO_ERROR as libc::c_int {
param = 0 as *mut mailmime_disposition_parm;
r = mailmime_disposition_parm_parse(
message,
length,
&mut cur_token,
&mut param,
);
if r == MAILIMF_NO_ERROR as libc::c_int {
r = clist_insert_after(list, (*list).last, param as *mut libc::c_void);
if !(r < 0i32) {
continue;
}
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 18290070879695007868;
break;
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
cur_token = final_token;
current_block = 652864300344834934;
break;
} else {
res = r;
current_block = 18290070879695007868;
break;
}
} else {
/* do nothing */
if r == MAILIMF_ERROR_PARSE as libc::c_int {
current_block = 652864300344834934;
break;
}
res = r;
current_block = 18290070879695007868;
break;
}
}
match current_block {
652864300344834934 => {
dsp = mailmime_disposition_new(dsp_type, list);
if dsp.is_null() {
res = MAILIMF_ERROR_MEMORY as libc::c_int
} else {
*result = dsp;
*indx = cur_token;
return MAILIMF_NO_ERROR as libc::c_int;
}
}
_ => {}
}
clist_foreach(
list,
::std::mem::transmute::<
Option<unsafe fn(_: *mut mailmime_disposition_parm) -> ()>,
clist_func,
>(Some(mailmime_disposition_parm_free)),
0 as *mut libc::c_void,
);
clist_free(list);
}
mailmime_disposition_type_free(dsp_type);
}
return res;
}
/*
* libEtPan! -- a mail stuff library
*
* Copyright (C) 2001, 2005 - DINH Viet Hoa
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the libEtPan! project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* $Id: mailmime_disposition.c,v 1.17 2011/05/03 16:30:22 hoa Exp $
*/
unsafe fn mailmime_disposition_parm_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut mailmime_disposition_parm,
) -> libc::c_int {
let mut current_block: u64;
let mut filename: *mut libc::c_char = 0 as *mut libc::c_char;
let mut creation_date: *mut libc::c_char = 0 as *mut libc::c_char;
let mut modification_date: *mut libc::c_char = 0 as *mut libc::c_char;
let mut read_date: *mut libc::c_char = 0 as *mut libc::c_char;
let mut size: size_t = 0;
let mut parameter: *mut mailmime_parameter = 0 as *mut mailmime_parameter;
let mut cur_token: size_t = 0;
let mut dsp_parm: *mut mailmime_disposition_parm = 0 as *mut mailmime_disposition_parm;
let mut type_0: libc::c_int = 0;
let mut guessed_type: libc::c_int = 0;
let mut r: libc::c_int = 0;
let mut res: libc::c_int = 0;
cur_token = *indx;
filename = 0 as *mut libc::c_char;
creation_date = 0 as *mut libc::c_char;
modification_date = 0 as *mut libc::c_char;
read_date = 0 as *mut libc::c_char;
size = 0i32 as size_t;
parameter = 0 as *mut mailmime_parameter;
r = mailimf_cfws_parse(message, length, &mut cur_token);
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
res = r
} else {
guessed_type = mailmime_disposition_guess_type(message, length, cur_token);
type_0 = MAILMIME_DISPOSITION_PARM_PARAMETER as libc::c_int;
match guessed_type {
0 => {
r = mailmime_filename_parm_parse(message, length, &mut cur_token, &mut filename);
if r == MAILIMF_NO_ERROR as libc::c_int {
type_0 = guessed_type;
current_block = 13826291924415791078;
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
current_block = 13826291924415791078;
} else {
/* do nothing */
res = r;
current_block = 9120900589700563584;
}
}
1 => {
r = mailmime_creation_date_parm_parse(
message,
length,
&mut cur_token,
&mut creation_date,
);
if r == MAILIMF_NO_ERROR as libc::c_int {
type_0 = guessed_type;
current_block = 13826291924415791078;
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
current_block = 13826291924415791078;
} else {
/* do nothing */
res = r;
current_block = 9120900589700563584;
}
}
2 => {
r = mailmime_modification_date_parm_parse(
message,
length,
&mut cur_token,
&mut modification_date,
);
if r == MAILIMF_NO_ERROR as libc::c_int {
type_0 = guessed_type;
current_block = 13826291924415791078;
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
current_block = 13826291924415791078;
} else {
/* do nothing */
res = r;
current_block = 9120900589700563584;
}
}
3 => {
r = mailmime_read_date_parm_parse(message, length, &mut cur_token, &mut read_date);
if r == MAILIMF_NO_ERROR as libc::c_int {
type_0 = guessed_type;
current_block = 13826291924415791078;
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
current_block = 13826291924415791078;
} else {
/* do nothing */
res = r;
current_block = 9120900589700563584;
}
}
4 => {
r = mailmime_size_parm_parse(message, length, &mut cur_token, &mut size);
if r == MAILIMF_NO_ERROR as libc::c_int {
type_0 = guessed_type;
current_block = 13826291924415791078;
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
current_block = 13826291924415791078;
} else {
/* do nothing */
res = r;
current_block = 9120900589700563584;
}
}
_ => {
current_block = 13826291924415791078;
}
}
match current_block {
9120900589700563584 => {}
_ => {
if type_0 == MAILMIME_DISPOSITION_PARM_PARAMETER as libc::c_int {
r = mailmime_parameter_parse(message, length, &mut cur_token, &mut parameter);
if r != MAILIMF_NO_ERROR as libc::c_int {
type_0 = guessed_type;
res = r;
current_block = 9120900589700563584;
} else {
current_block = 6721012065216013753;
}
} else {
current_block = 6721012065216013753;
}
match current_block {
9120900589700563584 => {}
_ => {
dsp_parm = mailmime_disposition_parm_new(
type_0,
filename,
creation_date,
modification_date,
read_date,
size,
parameter,
);
if dsp_parm.is_null() {
res = MAILIMF_ERROR_MEMORY as libc::c_int;
if !filename.is_null() {
mailmime_filename_parm_free(filename);
}
if !creation_date.is_null() {
mailmime_creation_date_parm_free(creation_date);
}
if !modification_date.is_null() {
mailmime_modification_date_parm_free(modification_date);
}
if !read_date.is_null() {
mailmime_read_date_parm_free(read_date);
}
if !parameter.is_null() {
mailmime_parameter_free(parameter);
}
} else {
*result = dsp_parm;
*indx = cur_token;
return MAILIMF_NO_ERROR as libc::c_int;
}
}
}
}
}
}
return res;
}
unsafe fn mailmime_size_parm_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut size_t,
) -> libc::c_int {
let mut value: uint32_t = 0;
let mut cur_token: size_t = 0;
let mut r: libc::c_int = 0;
cur_token = *indx;
r = mailimf_token_case_insensitive_len_parse(
message,
length,
&mut cur_token,
b"size\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
strlen(b"size\x00" as *const u8 as *const libc::c_char),
);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
r = mailimf_unstrict_char_parse(message, length, &mut cur_token, '=' as i32 as libc::c_char);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
r = mailimf_cfws_parse(message, length, &mut cur_token);
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
return r;
}
r = mailimf_number_parse(message, length, &mut cur_token, &mut value);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
*indx = cur_token;
*result = value as size_t;
return MAILIMF_NO_ERROR as libc::c_int;
}
unsafe fn mailmime_read_date_parm_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut libc::c_char,
) -> libc::c_int {
let mut value: *mut libc::c_char = 0 as *mut libc::c_char;
let mut cur_token: size_t = 0;
let mut r: libc::c_int = 0;
cur_token = *indx;
r = mailimf_token_case_insensitive_len_parse(
message,
length,
&mut cur_token,
b"read-date\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
strlen(b"read-date\x00" as *const u8 as *const libc::c_char),
);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
r = mailimf_unstrict_char_parse(message, length, &mut cur_token, '=' as i32 as libc::c_char);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
r = mailimf_cfws_parse(message, length, &mut cur_token);
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
return r;
}
r = mailmime_quoted_date_time_parse(message, length, &mut cur_token, &mut value);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
*indx = cur_token;
*result = value;
return MAILIMF_NO_ERROR as libc::c_int;
}
unsafe fn mailmime_quoted_date_time_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut libc::c_char,
) -> libc::c_int {
return mailimf_quoted_string_parse(message, length, indx, result);
}
unsafe fn mailmime_modification_date_parm_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut libc::c_char,
) -> libc::c_int {
let mut value: *mut libc::c_char = 0 as *mut libc::c_char;
let mut cur_token: size_t = 0;
let mut r: libc::c_int = 0;
cur_token = *indx;
r = mailimf_token_case_insensitive_len_parse(
message,
length,
&mut cur_token,
b"modification-date\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
strlen(b"modification-date\x00" as *const u8 as *const libc::c_char),
);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
r = mailimf_unstrict_char_parse(message, length, &mut cur_token, '=' as i32 as libc::c_char);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
r = mailimf_cfws_parse(message, length, &mut cur_token);
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
return r;
}
r = mailmime_quoted_date_time_parse(message, length, &mut cur_token, &mut value);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
*indx = cur_token;
*result = value;
return MAILIMF_NO_ERROR as libc::c_int;
}
unsafe fn mailmime_creation_date_parm_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut libc::c_char,
) -> libc::c_int {
let mut value: *mut libc::c_char = 0 as *mut libc::c_char;
let mut r: libc::c_int = 0;
let mut cur_token: size_t = 0;
cur_token = *indx;
r = mailimf_token_case_insensitive_len_parse(
message,
length,
&mut cur_token,
b"creation-date\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
strlen(b"creation-date\x00" as *const u8 as *const libc::c_char),
);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
r = mailimf_unstrict_char_parse(message, length, &mut cur_token, '=' as i32 as libc::c_char);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
r = mailimf_cfws_parse(message, length, &mut cur_token);
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
return r;
}
r = mailmime_quoted_date_time_parse(message, length, &mut cur_token, &mut value);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
*indx = cur_token;
*result = value;
return MAILIMF_NO_ERROR as libc::c_int;
}
unsafe fn mailmime_filename_parm_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut libc::c_char,
) -> libc::c_int {
let mut value: *mut libc::c_char = 0 as *mut libc::c_char;
let mut r: libc::c_int = 0;
let mut cur_token: size_t = 0;
cur_token = *indx;
r = mailimf_token_case_insensitive_len_parse(
message,
length,
&mut cur_token,
b"filename\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
strlen(b"filename\x00" as *const u8 as *const libc::c_char),
);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
r = mailimf_unstrict_char_parse(message, length, &mut cur_token, '=' as i32 as libc::c_char);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
r = mailimf_cfws_parse(message, length, &mut cur_token);
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
return r;
}
r = mailmime_value_parse(message, length, &mut cur_token, &mut value);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
*indx = cur_token;
*result = value;
return MAILIMF_NO_ERROR as libc::c_int;
}
pub unsafe fn mailmime_disposition_guess_type(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: size_t,
) -> libc::c_int {
if indx >= length {
return MAILMIME_DISPOSITION_PARM_PARAMETER as libc::c_int;
}
match toupper(*message.offset(indx as isize) as libc::c_uchar as libc::c_int) as libc::c_char
as libc::c_int
{
70 => return MAILMIME_DISPOSITION_PARM_FILENAME as libc::c_int,
67 => return MAILMIME_DISPOSITION_PARM_CREATION_DATE as libc::c_int,
77 => return MAILMIME_DISPOSITION_PARM_MODIFICATION_DATE as libc::c_int,
82 => return MAILMIME_DISPOSITION_PARM_READ_DATE as libc::c_int,
83 => return MAILMIME_DISPOSITION_PARM_SIZE as libc::c_int,
_ => return MAILMIME_DISPOSITION_PARM_PARAMETER as libc::c_int,
};
}
pub unsafe fn mailmime_disposition_type_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut mailmime_disposition_type,
) -> libc::c_int {
let mut cur_token: size_t = 0;
let mut type_0: libc::c_int = 0;
let mut extension: *mut libc::c_char = 0 as *mut libc::c_char;
let mut dsp_type: *mut mailmime_disposition_type = 0 as *mut mailmime_disposition_type;
let mut r: libc::c_int = 0;
let mut res: libc::c_int = 0;
cur_token = *indx;
r = mailimf_cfws_parse(message, length, &mut cur_token);
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
res = r
} else {
type_0 = MAILMIME_DISPOSITION_TYPE_ERROR as libc::c_int;
extension = 0 as *mut libc::c_char;
r = mailimf_token_case_insensitive_len_parse(
message,
length,
&mut cur_token,
b"inline\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
strlen(b"inline\x00" as *const u8 as *const libc::c_char),
);
if r == MAILIMF_NO_ERROR as libc::c_int {
type_0 = MAILMIME_DISPOSITION_TYPE_INLINE as libc::c_int
}
if r == MAILIMF_ERROR_PARSE as libc::c_int {
r = mailimf_token_case_insensitive_len_parse(
message,
length,
&mut cur_token,
b"attachment\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
strlen(b"attachment\x00" as *const u8 as *const libc::c_char),
);
if r == MAILIMF_NO_ERROR as libc::c_int {
type_0 = MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int
}
}
if r == MAILIMF_ERROR_PARSE as libc::c_int {
r = mailmime_extension_token_parse(message, length, &mut cur_token, &mut extension);
if r == MAILIMF_NO_ERROR as libc::c_int {
type_0 = MAILMIME_DISPOSITION_TYPE_EXTENSION as libc::c_int
}
}
if r != MAILIMF_NO_ERROR as libc::c_int {
res = r
} else {
dsp_type = mailmime_disposition_type_new(type_0, extension);
if dsp_type.is_null() {
res = MAILIMF_ERROR_MEMORY as libc::c_int;
if !extension.is_null() {
free(extension as *mut libc::c_void);
}
} else {
*result = dsp_type;
*indx = cur_token;
return MAILIMF_NO_ERROR as libc::c_int;
}
}
}
return res;
}

1143
mmime/src/mailmime/mod.rs Normal file

File diff suppressed because it is too large Load Diff

891
mmime/src/mailmime/types.rs Normal file
View File

@@ -0,0 +1,891 @@
use crate::clist::*;
use crate::mailimf::types::*;
use crate::mmapstring::*;
use crate::other::*;
pub const MAILMIME_MECHANISM_TOKEN: libc::c_uint = 6;
pub const MAILMIME_MECHANISM_BASE64: libc::c_uint = 5;
pub const MAILMIME_MECHANISM_QUOTED_PRINTABLE: libc::c_uint = 4;
pub const MAILMIME_MECHANISM_BINARY: libc::c_uint = 3;
pub const MAILMIME_MECHANISM_8BIT: libc::c_uint = 2;
pub const MAILMIME_MECHANISM_7BIT: libc::c_uint = 1;
pub const MAILMIME_MECHANISM_ERROR: libc::c_uint = 0;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_composite_type {
pub ct_type: libc::c_int,
pub ct_token: *mut libc::c_char,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_content {
pub ct_type: *mut mailmime_type,
pub ct_subtype: *mut libc::c_char,
pub ct_parameters: *mut clist,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_type {
pub tp_type: libc::c_int,
pub tp_data: unnamed,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union unnamed {
pub tp_discrete_type: *mut mailmime_discrete_type,
pub tp_composite_type: *mut mailmime_composite_type,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_discrete_type {
pub dt_type: libc::c_int,
pub dt_extension: *mut libc::c_char,
}
pub type unnamed_0 = libc::c_uint;
pub const MAILMIME_FIELD_LOCATION: unnamed_0 = 8;
pub const MAILMIME_FIELD_LANGUAGE: unnamed_0 = 7;
pub const MAILMIME_FIELD_DISPOSITION: unnamed_0 = 6;
pub const MAILMIME_FIELD_VERSION: unnamed_0 = 5;
pub const MAILMIME_FIELD_DESCRIPTION: unnamed_0 = 4;
pub const MAILMIME_FIELD_ID: unnamed_0 = 3;
pub const MAILMIME_FIELD_TRANSFER_ENCODING: unnamed_0 = 2;
pub const MAILMIME_FIELD_TYPE: unnamed_0 = 1;
pub const MAILMIME_FIELD_NONE: unnamed_0 = 0;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_field {
pub fld_type: libc::c_int,
pub fld_data: unnamed_1,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union unnamed_1 {
pub fld_content: *mut mailmime_content,
pub fld_encoding: *mut mailmime_mechanism,
pub fld_id: *mut libc::c_char,
pub fld_description: *mut libc::c_char,
pub fld_version: uint32_t,
pub fld_disposition: *mut mailmime_disposition,
pub fld_language: *mut mailmime_language,
pub fld_location: *mut libc::c_char,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_language {
pub lg_list: *mut clist,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_disposition {
pub dsp_type: *mut mailmime_disposition_type,
pub dsp_parms: *mut clist,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_disposition_type {
pub dsp_type: libc::c_int,
pub dsp_extension: *mut libc::c_char,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_mechanism {
pub enc_type: libc::c_int,
pub enc_token: *mut libc::c_char,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_fields {
pub fld_list: *mut clist,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_parameter {
pub pa_name: *mut libc::c_char,
pub pa_value: *mut libc::c_char,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_disposition_parm {
pub pa_type: libc::c_int,
pub pa_data: unnamed_3,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union unnamed_3 {
pub pa_filename: *mut libc::c_char,
pub pa_creation_date: *mut libc::c_char,
pub pa_modification_date: *mut libc::c_char,
pub pa_read_date: *mut libc::c_char,
pub pa_size: size_t,
pub pa_parameter: *mut mailmime_parameter,
}
pub const MAILMIME_DISPOSITION_PARM_PARAMETER: unnamed_11 = 5;
pub const MAILMIME_DISPOSITION_PARM_READ_DATE: unnamed_11 = 3;
pub const MAILMIME_DISPOSITION_PARM_MODIFICATION_DATE: unnamed_11 = 2;
pub const MAILMIME_DISPOSITION_PARM_CREATION_DATE: unnamed_11 = 1;
pub const MAILMIME_DISPOSITION_PARM_FILENAME: unnamed_11 = 0;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_multipart_body {
pub bd_list: *mut clist,
}
pub type unnamed_4 = libc::c_uint;
pub const MAILMIME_DATA_FILE: unnamed_4 = 1;
pub const MAILMIME_DATA_TEXT: unnamed_4 = 0;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_data {
pub dt_type: libc::c_int,
pub dt_encoding: libc::c_int,
pub dt_encoded: libc::c_int,
pub dt_data: unnamed_5,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union unnamed_5 {
pub dt_text: unnamed_6,
pub dt_filename: *mut libc::c_char,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct unnamed_6 {
pub dt_data: *const libc::c_char,
pub dt_length: size_t,
}
pub type unnamed_7 = libc::c_uint;
pub const MAILMIME_MESSAGE: unnamed_7 = 3;
pub const MAILMIME_MULTIPLE: unnamed_7 = 2;
pub const MAILMIME_SINGLE: unnamed_7 = 1;
pub const MAILMIME_NONE: unnamed_7 = 0;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct Mailmime {
pub mm_parent_type: libc::c_int,
pub mm_parent: *mut Mailmime,
pub mm_multipart_pos: *mut clistiter,
pub mm_type: libc::c_int,
pub mm_mime_start: *const libc::c_char,
pub mm_length: size_t,
pub mm_mime_fields: *mut mailmime_fields,
pub mm_content_type: *mut mailmime_content,
pub mm_body: *mut mailmime_data,
pub mm_data: unnamed_8,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union unnamed_8 {
pub mm_single: *mut mailmime_data,
pub mm_multipart: unnamed_10,
pub mm_message: unnamed_9,
}
/* message */
#[derive(Copy, Clone)]
#[repr(C)]
pub struct unnamed_9 {
pub mm_fields: *mut mailimf_fields,
pub mm_msg_mime: *mut Mailmime,
}
/* multi-part */
#[derive(Copy, Clone)]
#[repr(C)]
pub struct unnamed_10 {
pub mm_preamble: *mut mailmime_data,
pub mm_epilogue: *mut mailmime_data,
pub mm_mp_list: *mut clist,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_encoded_word {
pub wd_charset: *mut libc::c_char,
pub wd_text: *mut libc::c_char,
}
pub type unnamed_11 = libc::c_uint;
pub const MAILMIME_DISPOSITION_PARM_SIZE: unnamed_11 = 4;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_section {
pub sec_list: *mut clist,
}
pub unsafe fn mailmime_attribute_free(mut attribute: *mut libc::c_char) {
mailmime_token_free(attribute);
}
pub unsafe fn mailmime_token_free(mut token: *mut libc::c_char) {
free(token as *mut libc::c_void);
}
pub unsafe fn mailmime_composite_type_new(
mut ct_type: libc::c_int,
mut ct_token: *mut libc::c_char,
) -> *mut mailmime_composite_type {
let mut ct: *mut mailmime_composite_type = 0 as *mut mailmime_composite_type;
ct = malloc(::std::mem::size_of::<mailmime_composite_type>() as libc::size_t)
as *mut mailmime_composite_type;
if ct.is_null() {
return 0 as *mut mailmime_composite_type;
}
(*ct).ct_type = ct_type;
(*ct).ct_token = ct_token;
return ct;
}
pub unsafe fn mailmime_composite_type_free(mut ct: *mut mailmime_composite_type) {
if !(*ct).ct_token.is_null() {
mailmime_extension_token_free((*ct).ct_token);
}
free(ct as *mut libc::c_void);
}
pub unsafe fn mailmime_extension_token_free(mut extension: *mut libc::c_char) {
mailmime_token_free(extension);
}
pub unsafe fn mailmime_content_new(
mut ct_type: *mut mailmime_type,
mut ct_subtype: *mut libc::c_char,
mut ct_parameters: *mut clist,
) -> *mut mailmime_content {
let mut content: *mut mailmime_content = 0 as *mut mailmime_content;
content =
malloc(::std::mem::size_of::<mailmime_content>() as libc::size_t) as *mut mailmime_content;
if content.is_null() {
return 0 as *mut mailmime_content;
}
(*content).ct_type = ct_type;
(*content).ct_subtype = ct_subtype;
(*content).ct_parameters = ct_parameters;
return content;
}
pub unsafe fn mailmime_content_free(mut content: *mut mailmime_content) {
mailmime_type_free((*content).ct_type);
mailmime_subtype_free((*content).ct_subtype);
if !(*content).ct_parameters.is_null() {
clist_foreach(
(*content).ct_parameters,
::std::mem::transmute::<Option<unsafe fn(_: *mut mailmime_parameter) -> ()>, clist_func>(
Some(mailmime_parameter_free),
),
0 as *mut libc::c_void,
);
clist_free((*content).ct_parameters);
}
free(content as *mut libc::c_void);
}
pub unsafe fn mailmime_parameter_free(mut parameter: *mut mailmime_parameter) {
mailmime_attribute_free((*parameter).pa_name);
mailmime_value_free((*parameter).pa_value);
free(parameter as *mut libc::c_void);
}
pub unsafe fn mailmime_value_free(mut value: *mut libc::c_char) {
free(value as *mut libc::c_void);
}
pub unsafe fn mailmime_subtype_free(mut subtype: *mut libc::c_char) {
mailmime_extension_token_free(subtype);
}
pub unsafe fn mailmime_type_free(mut type_0: *mut mailmime_type) {
match (*type_0).tp_type {
1 => {
mailmime_discrete_type_free((*type_0).tp_data.tp_discrete_type);
}
2 => {
mailmime_composite_type_free((*type_0).tp_data.tp_composite_type);
}
_ => {}
}
free(type_0 as *mut libc::c_void);
}
pub unsafe fn mailmime_discrete_type_free(mut discrete_type: *mut mailmime_discrete_type) {
if !(*discrete_type).dt_extension.is_null() {
mailmime_extension_token_free((*discrete_type).dt_extension);
}
free(discrete_type as *mut libc::c_void);
}
pub unsafe fn mailmime_description_free(mut description: *mut libc::c_char) {
free(description as *mut libc::c_void);
}
pub unsafe fn mailmime_location_free(mut location: *mut libc::c_char) {
free(location as *mut libc::c_void);
}
pub unsafe fn mailmime_discrete_type_new(
mut dt_type: libc::c_int,
mut dt_extension: *mut libc::c_char,
) -> *mut mailmime_discrete_type {
let mut discrete_type: *mut mailmime_discrete_type = 0 as *mut mailmime_discrete_type;
discrete_type = malloc(::std::mem::size_of::<mailmime_discrete_type>() as libc::size_t)
as *mut mailmime_discrete_type;
if discrete_type.is_null() {
return 0 as *mut mailmime_discrete_type;
}
(*discrete_type).dt_type = dt_type;
(*discrete_type).dt_extension = dt_extension;
return discrete_type;
}
pub unsafe fn mailmime_encoding_free(mut encoding: *mut mailmime_mechanism) {
mailmime_mechanism_free(encoding);
}
pub unsafe fn mailmime_mechanism_free(mut mechanism: *mut mailmime_mechanism) {
if !(*mechanism).enc_token.is_null() {
mailmime_token_free((*mechanism).enc_token);
}
free(mechanism as *mut libc::c_void);
}
pub unsafe fn mailmime_id_free(mut id: *mut libc::c_char) {
mailimf_msg_id_free(id);
}
pub unsafe fn mailmime_mechanism_new(
mut enc_type: libc::c_int,
mut enc_token: *mut libc::c_char,
) -> *mut mailmime_mechanism {
let mut mechanism: *mut mailmime_mechanism = 0 as *mut mailmime_mechanism;
mechanism = malloc(::std::mem::size_of::<mailmime_mechanism>() as libc::size_t)
as *mut mailmime_mechanism;
if mechanism.is_null() {
return 0 as *mut mailmime_mechanism;
}
(*mechanism).enc_type = enc_type;
(*mechanism).enc_token = enc_token;
return mechanism;
}
pub unsafe fn mailmime_parameter_new(
mut pa_name: *mut libc::c_char,
mut pa_value: *mut libc::c_char,
) -> *mut mailmime_parameter {
let mut parameter: *mut mailmime_parameter = 0 as *mut mailmime_parameter;
parameter = malloc(::std::mem::size_of::<mailmime_parameter>() as libc::size_t)
as *mut mailmime_parameter;
if parameter.is_null() {
return 0 as *mut mailmime_parameter;
}
(*parameter).pa_name = pa_name;
(*parameter).pa_value = pa_value;
return parameter;
}
pub unsafe fn mailmime_type_new(
mut tp_type: libc::c_int,
mut tp_discrete_type: *mut mailmime_discrete_type,
mut tp_composite_type: *mut mailmime_composite_type,
) -> *mut mailmime_type {
let mut mime_type: *mut mailmime_type = 0 as *mut mailmime_type;
mime_type =
malloc(::std::mem::size_of::<mailmime_type>() as libc::size_t) as *mut mailmime_type;
if mime_type.is_null() {
return 0 as *mut mailmime_type;
}
(*mime_type).tp_type = tp_type;
match tp_type {
1 => (*mime_type).tp_data.tp_discrete_type = tp_discrete_type,
2 => (*mime_type).tp_data.tp_composite_type = tp_composite_type,
_ => {}
}
return mime_type;
}
pub unsafe fn mailmime_language_new(mut lg_list: *mut clist) -> *mut mailmime_language {
let mut lang: *mut mailmime_language = 0 as *mut mailmime_language;
lang = malloc(::std::mem::size_of::<mailmime_language>() as libc::size_t)
as *mut mailmime_language;
if lang.is_null() {
return 0 as *mut mailmime_language;
}
(*lang).lg_list = lg_list;
return lang;
}
pub unsafe fn mailmime_language_free(mut lang: *mut mailmime_language) {
clist_foreach(
(*lang).lg_list,
::std::mem::transmute::<Option<unsafe fn(_: *mut libc::c_char) -> ()>, clist_func>(Some(
mailimf_atom_free,
)),
0 as *mut libc::c_void,
);
clist_free((*lang).lg_list);
free(lang as *mut libc::c_void);
}
/*
void mailmime_x_token_free(gchar * x_token);
*/
pub unsafe fn mailmime_field_new(
mut fld_type: libc::c_int,
mut fld_content: *mut mailmime_content,
mut fld_encoding: *mut mailmime_mechanism,
mut fld_id: *mut libc::c_char,
mut fld_description: *mut libc::c_char,
mut fld_version: uint32_t,
mut fld_disposition: *mut mailmime_disposition,
mut fld_language: *mut mailmime_language,
mut fld_location: *mut libc::c_char,
) -> *mut mailmime_field {
let mut field: *mut mailmime_field = 0 as *mut mailmime_field;
field = malloc(::std::mem::size_of::<mailmime_field>() as libc::size_t) as *mut mailmime_field;
if field.is_null() {
return 0 as *mut mailmime_field;
}
(*field).fld_type = fld_type;
match fld_type {
1 => (*field).fld_data.fld_content = fld_content,
2 => (*field).fld_data.fld_encoding = fld_encoding,
3 => (*field).fld_data.fld_id = fld_id,
4 => (*field).fld_data.fld_description = fld_description,
5 => (*field).fld_data.fld_version = fld_version,
6 => (*field).fld_data.fld_disposition = fld_disposition,
7 => (*field).fld_data.fld_language = fld_language,
8 => (*field).fld_data.fld_location = fld_location,
_ => {}
}
return field;
}
pub unsafe fn mailmime_field_free(mut field: *mut mailmime_field) {
match (*field).fld_type {
1 => {
if !(*field).fld_data.fld_content.is_null() {
mailmime_content_free((*field).fld_data.fld_content);
}
}
2 => {
if !(*field).fld_data.fld_encoding.is_null() {
mailmime_encoding_free((*field).fld_data.fld_encoding);
}
}
3 => {
if !(*field).fld_data.fld_id.is_null() {
mailmime_id_free((*field).fld_data.fld_id);
}
}
4 => {
if !(*field).fld_data.fld_description.is_null() {
mailmime_description_free((*field).fld_data.fld_description);
}
}
6 => {
if !(*field).fld_data.fld_disposition.is_null() {
mailmime_disposition_free((*field).fld_data.fld_disposition);
}
}
7 => {
if !(*field).fld_data.fld_language.is_null() {
mailmime_language_free((*field).fld_data.fld_language);
}
}
8 => {
if !(*field).fld_data.fld_location.is_null() {
mailmime_location_free((*field).fld_data.fld_location);
}
}
_ => {}
}
free(field as *mut libc::c_void);
}
pub unsafe fn mailmime_disposition_free(mut dsp: *mut mailmime_disposition) {
mailmime_disposition_type_free((*dsp).dsp_type);
clist_foreach(
(*dsp).dsp_parms,
::std::mem::transmute::<
Option<unsafe fn(_: *mut mailmime_disposition_parm) -> ()>,
clist_func,
>(Some(mailmime_disposition_parm_free)),
0 as *mut libc::c_void,
);
clist_free((*dsp).dsp_parms);
free(dsp as *mut libc::c_void);
}
pub unsafe fn mailmime_disposition_parm_free(mut dsp_parm: *mut mailmime_disposition_parm) {
match (*dsp_parm).pa_type {
0 => {
mailmime_filename_parm_free((*dsp_parm).pa_data.pa_filename);
}
1 => {
mailmime_creation_date_parm_free((*dsp_parm).pa_data.pa_creation_date);
}
2 => {
mailmime_modification_date_parm_free((*dsp_parm).pa_data.pa_modification_date);
}
3 => {
mailmime_read_date_parm_free((*dsp_parm).pa_data.pa_read_date);
}
5 => {
mailmime_parameter_free((*dsp_parm).pa_data.pa_parameter);
}
_ => {}
}
free(dsp_parm as *mut libc::c_void);
}
pub unsafe fn mailmime_read_date_parm_free(mut date: *mut libc::c_char) {
mailmime_quoted_date_time_free(date);
}
pub unsafe fn mailmime_quoted_date_time_free(mut date: *mut libc::c_char) {
mailimf_quoted_string_free(date);
}
pub unsafe fn mailmime_modification_date_parm_free(mut date: *mut libc::c_char) {
mailmime_quoted_date_time_free(date);
}
pub unsafe fn mailmime_creation_date_parm_free(mut date: *mut libc::c_char) {
mailmime_quoted_date_time_free(date);
}
pub unsafe fn mailmime_filename_parm_free(mut filename: *mut libc::c_char) {
mailmime_value_free(filename);
}
pub unsafe fn mailmime_disposition_type_free(mut dsp_type: *mut mailmime_disposition_type) {
if !(*dsp_type).dsp_extension.is_null() {
free((*dsp_type).dsp_extension as *mut libc::c_void);
}
free(dsp_type as *mut libc::c_void);
}
pub unsafe fn mailmime_fields_new(mut fld_list: *mut clist) -> *mut mailmime_fields {
let mut fields: *mut mailmime_fields = 0 as *mut mailmime_fields;
fields =
malloc(::std::mem::size_of::<mailmime_fields>() as libc::size_t) as *mut mailmime_fields;
if fields.is_null() {
return 0 as *mut mailmime_fields;
}
(*fields).fld_list = fld_list;
return fields;
}
pub unsafe fn mailmime_fields_free(mut fields: *mut mailmime_fields) {
clist_foreach(
(*fields).fld_list,
::std::mem::transmute::<Option<unsafe fn(_: *mut mailmime_field) -> ()>, clist_func>(Some(
mailmime_field_free,
)),
0 as *mut libc::c_void,
);
clist_free((*fields).fld_list);
free(fields as *mut libc::c_void);
}
pub unsafe fn mailmime_multipart_body_new(mut bd_list: *mut clist) -> *mut mailmime_multipart_body {
let mut mp_body: *mut mailmime_multipart_body = 0 as *mut mailmime_multipart_body;
mp_body = malloc(::std::mem::size_of::<mailmime_multipart_body>() as libc::size_t)
as *mut mailmime_multipart_body;
if mp_body.is_null() {
return 0 as *mut mailmime_multipart_body;
}
(*mp_body).bd_list = bd_list;
return mp_body;
}
pub unsafe fn mailmime_multipart_body_free(mut mp_body: *mut mailmime_multipart_body) {
clist_foreach(
(*mp_body).bd_list,
::std::mem::transmute::<Option<unsafe fn(_: *mut mailimf_body) -> ()>, clist_func>(Some(
mailimf_body_free,
)),
0 as *mut libc::c_void,
);
clist_free((*mp_body).bd_list);
free(mp_body as *mut libc::c_void);
}
pub unsafe fn mailmime_data_new(
mut dt_type: libc::c_int,
mut dt_encoding: libc::c_int,
mut dt_encoded: libc::c_int,
mut dt_data: *const libc::c_char,
mut dt_length: size_t,
mut dt_filename: *mut libc::c_char,
) -> *mut mailmime_data {
let mut mime_data: *mut mailmime_data = 0 as *mut mailmime_data;
mime_data =
malloc(::std::mem::size_of::<mailmime_data>() as libc::size_t) as *mut mailmime_data;
if mime_data.is_null() {
return 0 as *mut mailmime_data;
}
(*mime_data).dt_type = dt_type;
(*mime_data).dt_encoding = dt_encoding;
(*mime_data).dt_encoded = dt_encoded;
match dt_type {
0 => {
(*mime_data).dt_data.dt_text.dt_data = dt_data;
(*mime_data).dt_data.dt_text.dt_length = dt_length
}
1 => (*mime_data).dt_data.dt_filename = dt_filename,
_ => {}
}
return mime_data;
}
pub unsafe fn mailmime_data_free(mut mime_data: *mut mailmime_data) {
match (*mime_data).dt_type {
1 => {
free((*mime_data).dt_data.dt_filename as *mut libc::c_void);
}
_ => {}
}
free(mime_data as *mut libc::c_void);
}
pub unsafe fn mailmime_new(
mut mm_type: libc::c_int,
mut mm_mime_start: *const libc::c_char,
mut mm_length: size_t,
mut mm_mime_fields: *mut mailmime_fields,
mut mm_content_type: *mut mailmime_content,
mut mm_body: *mut mailmime_data,
mut mm_preamble: *mut mailmime_data,
mut mm_epilogue: *mut mailmime_data,
mut mm_mp_list: *mut clist,
mut mm_fields: *mut mailimf_fields,
mut mm_msg_mime: *mut Mailmime,
) -> *mut Mailmime {
let mut mime: *mut Mailmime = 0 as *mut Mailmime;
let mut cur: *mut clistiter = 0 as *mut clistiter;
mime = malloc(::std::mem::size_of::<Mailmime>() as libc::size_t) as *mut Mailmime;
if mime.is_null() {
return 0 as *mut Mailmime;
}
(*mime).mm_parent = 0 as *mut Mailmime;
(*mime).mm_parent_type = MAILMIME_NONE as libc::c_int;
(*mime).mm_multipart_pos = 0 as *mut clistiter;
(*mime).mm_type = mm_type;
(*mime).mm_mime_start = mm_mime_start;
(*mime).mm_length = mm_length;
(*mime).mm_mime_fields = mm_mime_fields;
(*mime).mm_content_type = mm_content_type;
(*mime).mm_body = mm_body;
match mm_type {
1 => (*mime).mm_data.mm_single = mm_body,
2 => {
(*mime).mm_data.mm_multipart.mm_preamble = mm_preamble;
(*mime).mm_data.mm_multipart.mm_epilogue = mm_epilogue;
(*mime).mm_data.mm_multipart.mm_mp_list = mm_mp_list;
cur = (*mm_mp_list).first;
while !cur.is_null() {
let mut submime: *mut Mailmime = 0 as *mut Mailmime;
submime = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut Mailmime;
(*submime).mm_parent = mime;
(*submime).mm_parent_type = MAILMIME_MULTIPLE as libc::c_int;
(*submime).mm_multipart_pos = cur;
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
}
3 => {
(*mime).mm_data.mm_message.mm_fields = mm_fields;
(*mime).mm_data.mm_message.mm_msg_mime = mm_msg_mime;
if !mm_msg_mime.is_null() {
(*mm_msg_mime).mm_parent = mime;
(*mm_msg_mime).mm_parent_type = MAILMIME_MESSAGE as libc::c_int
}
}
_ => {}
}
return mime;
}
pub unsafe fn mailmime_new_simple(
mut mm_type: libc::c_int,
mut mm_mime_fields: *mut mailmime_fields,
mut mm_content_type: *mut mailmime_content,
mut mm_fields: *mut mailimf_fields,
mut mm_msg_mime: *mut Mailmime,
) -> *mut Mailmime {
mailmime_new(
mm_type,
std::ptr::null(),
0,
mm_mime_fields,
mm_content_type,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
mm_fields,
mm_msg_mime,
)
}
pub unsafe fn mailmime_free(mut mime: *mut Mailmime) {
match (*mime).mm_type {
1 => {
if (*mime).mm_body.is_null() && !(*mime).mm_data.mm_single.is_null() {
mailmime_data_free((*mime).mm_data.mm_single);
}
}
2 => {
/* do nothing */
if !(*mime).mm_data.mm_multipart.mm_preamble.is_null() {
mailmime_data_free((*mime).mm_data.mm_multipart.mm_preamble);
}
if !(*mime).mm_data.mm_multipart.mm_epilogue.is_null() {
mailmime_data_free((*mime).mm_data.mm_multipart.mm_epilogue);
}
clist_foreach(
(*mime).mm_data.mm_multipart.mm_mp_list,
::std::mem::transmute::<Option<unsafe fn(_: *mut Mailmime) -> ()>, clist_func>(
Some(mailmime_free),
),
0 as *mut libc::c_void,
);
clist_free((*mime).mm_data.mm_multipart.mm_mp_list);
}
3 => {
if !(*mime).mm_data.mm_message.mm_fields.is_null() {
mailimf_fields_free((*mime).mm_data.mm_message.mm_fields);
}
if !(*mime).mm_data.mm_message.mm_msg_mime.is_null() {
mailmime_free((*mime).mm_data.mm_message.mm_msg_mime);
}
}
_ => {}
}
if !(*mime).mm_body.is_null() {
mailmime_data_free((*mime).mm_body);
}
if !(*mime).mm_mime_fields.is_null() {
mailmime_fields_free((*mime).mm_mime_fields);
}
if !(*mime).mm_content_type.is_null() {
mailmime_content_free((*mime).mm_content_type);
}
free(mime as *mut libc::c_void);
}
pub unsafe fn mailmime_encoded_word_new(
mut wd_charset: *mut libc::c_char,
mut wd_text: *mut libc::c_char,
) -> *mut mailmime_encoded_word {
let mut ew: *mut mailmime_encoded_word = 0 as *mut mailmime_encoded_word;
ew = malloc(::std::mem::size_of::<mailmime_encoded_word>() as libc::size_t)
as *mut mailmime_encoded_word;
if ew.is_null() {
return 0 as *mut mailmime_encoded_word;
}
(*ew).wd_charset = wd_charset;
(*ew).wd_text = wd_text;
return ew;
}
pub unsafe fn mailmime_encoded_word_free(mut ew: *mut mailmime_encoded_word) {
mailmime_charset_free((*ew).wd_charset);
mailmime_encoded_text_free((*ew).wd_text);
free(ew as *mut libc::c_void);
}
pub unsafe fn mailmime_encoded_text_free(mut text: *mut libc::c_char) {
free(text as *mut libc::c_void);
}
pub unsafe fn mailmime_charset_free(mut charset: *mut libc::c_char) {
free(charset as *mut libc::c_void);
}
pub unsafe fn mailmime_disposition_new(
mut dsp_type: *mut mailmime_disposition_type,
mut dsp_parms: *mut clist,
) -> *mut mailmime_disposition {
let mut dsp: *mut mailmime_disposition = 0 as *mut mailmime_disposition;
dsp = malloc(::std::mem::size_of::<mailmime_disposition>() as libc::size_t)
as *mut mailmime_disposition;
if dsp.is_null() {
return 0 as *mut mailmime_disposition;
}
(*dsp).dsp_type = dsp_type;
(*dsp).dsp_parms = dsp_parms;
return dsp;
}
pub unsafe fn mailmime_disposition_type_new(
mut dsp_type: libc::c_int,
mut dsp_extension: *mut libc::c_char,
) -> *mut mailmime_disposition_type {
let mut m_dsp_type: *mut mailmime_disposition_type = 0 as *mut mailmime_disposition_type;
m_dsp_type = malloc(::std::mem::size_of::<mailmime_disposition_type>() as libc::size_t)
as *mut mailmime_disposition_type;
if m_dsp_type.is_null() {
return 0 as *mut mailmime_disposition_type;
}
(*m_dsp_type).dsp_type = dsp_type;
(*m_dsp_type).dsp_extension = dsp_extension;
return m_dsp_type;
}
pub unsafe fn mailmime_disposition_parm_new(
mut pa_type: libc::c_int,
mut pa_filename: *mut libc::c_char,
mut pa_creation_date: *mut libc::c_char,
mut pa_modification_date: *mut libc::c_char,
mut pa_read_date: *mut libc::c_char,
mut pa_size: size_t,
mut pa_parameter: *mut mailmime_parameter,
) -> *mut mailmime_disposition_parm {
let mut dsp_parm: *mut mailmime_disposition_parm = 0 as *mut mailmime_disposition_parm;
dsp_parm = malloc(::std::mem::size_of::<mailmime_disposition_parm>() as libc::size_t)
as *mut mailmime_disposition_parm;
if dsp_parm.is_null() {
return 0 as *mut mailmime_disposition_parm;
}
(*dsp_parm).pa_type = pa_type;
match pa_type {
0 => (*dsp_parm).pa_data.pa_filename = pa_filename,
1 => (*dsp_parm).pa_data.pa_creation_date = pa_creation_date,
2 => (*dsp_parm).pa_data.pa_modification_date = pa_modification_date,
3 => (*dsp_parm).pa_data.pa_read_date = pa_read_date,
4 => (*dsp_parm).pa_data.pa_size = pa_size,
5 => (*dsp_parm).pa_data.pa_parameter = pa_parameter,
_ => {}
}
return dsp_parm;
}
pub unsafe fn mailmime_section_new(mut sec_list: *mut clist) -> *mut mailmime_section {
let mut section: *mut mailmime_section = 0 as *mut mailmime_section;
section =
malloc(::std::mem::size_of::<mailmime_section>() as libc::size_t) as *mut mailmime_section;
if section.is_null() {
return 0 as *mut mailmime_section;
}
(*section).sec_list = sec_list;
return section;
}
pub unsafe fn mailmime_section_free(mut section: *mut mailmime_section) {
clist_foreach(
(*section).sec_list,
::std::mem::transmute::<Option<unsafe extern "C" fn(_: *mut libc::c_void) -> ()>, clist_func>(
Some(free),
),
0 as *mut libc::c_void,
);
clist_free((*section).sec_list);
free(section as *mut libc::c_void);
}
pub unsafe fn mailmime_decoded_part_free(mut part: *mut libc::c_char) {
mmap_string_unref(part);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,82 @@
use crate::mailmime::types::*;
use crate::mailmime::write_generic::*;
use crate::mmapstring::*;
use crate::other::*;
unsafe fn do_write(
mut data: *mut libc::c_void,
mut str: *const libc::c_char,
mut length: size_t,
) -> libc::c_int {
let mut f: *mut MMAPString = 0 as *mut MMAPString;
f = data as *mut MMAPString;
if mmap_string_append_len(f, str, length).is_null() {
return 0i32;
} else {
return length as libc::c_int;
};
}
pub unsafe fn mailmime_content_write_mem(
mut f: *mut MMAPString,
mut col: *mut libc::c_int,
mut content: *mut mailmime_content,
) -> libc::c_int {
return mailmime_content_write_driver(Some(do_write), f as *mut libc::c_void, col, content);
}
pub unsafe fn mailmime_content_type_write_mem(
mut f: *mut MMAPString,
mut col: *mut libc::c_int,
mut content: *mut mailmime_content,
) -> libc::c_int {
return mailmime_content_type_write_driver(
Some(do_write),
f as *mut libc::c_void,
col,
content,
);
}
pub unsafe fn mailmime_write_mem(
mut f: *mut MMAPString,
mut col: *mut libc::c_int,
mut build_info: *mut Mailmime,
) -> libc::c_int {
return mailmime_write_driver(Some(do_write), f as *mut libc::c_void, col, build_info);
}
pub unsafe fn mailmime_quoted_printable_write_mem(
mut f: *mut MMAPString,
mut col: *mut libc::c_int,
mut istext: libc::c_int,
mut text: *const libc::c_char,
mut size: size_t,
) -> libc::c_int {
return mailmime_quoted_printable_write_driver(
Some(do_write),
f as *mut libc::c_void,
col,
istext,
text,
size,
);
}
pub unsafe fn mailmime_base64_write_mem(
mut f: *mut MMAPString,
mut col: *mut libc::c_int,
mut text: *const libc::c_char,
mut size: size_t,
) -> libc::c_int {
return mailmime_base64_write_driver(Some(do_write), f as *mut libc::c_void, col, text, size);
}
pub unsafe fn mailmime_data_write_mem(
mut f: *mut MMAPString,
mut col: *mut libc::c_int,
mut data: *mut mailmime_data,
mut istext: libc::c_int,
) -> libc::c_int {
return mailmime_data_write_driver(Some(do_write), f as *mut libc::c_void, col, data, istext);
}

397
mmime/src/mmapstring.rs Normal file
View File

@@ -0,0 +1,397 @@
use std::sync::Mutex;
use lazy_static::lazy_static;
use libc;
use crate::chash::*;
use crate::other::*;
lazy_static! {
static ref mmapstring_lock: Mutex<()> = Mutex::new(());
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct MMAPString {
pub str_0: *mut libc::c_char,
pub len: size_t,
pub allocated_len: size_t,
pub fd: libc::c_int,
pub mmapped_size: size_t,
}
pub const TMPDIR: &'static str = "/tmp";
pub unsafe fn mmap_string_new(mut init: *const libc::c_char) -> *mut MMAPString {
let mut string: *mut MMAPString = 0 as *mut MMAPString;
string = mmap_string_sized_new(if !init.is_null() {
strlen(init).wrapping_add(2i32 as libc::size_t)
} else {
2i32 as libc::size_t
});
if string.is_null() {
return 0 as *mut MMAPString;
}
if !init.is_null() {
mmap_string_append(string, init);
}
return string;
}
pub unsafe fn mmap_string_append(
mut string: *mut MMAPString,
mut val: *const libc::c_char,
) -> *mut MMAPString {
return mmap_string_insert_len(string, (*string).len, val, strlen(val));
}
pub unsafe fn mmap_string_insert_len(
mut string: *mut MMAPString,
mut pos: size_t,
mut val: *const libc::c_char,
mut len: size_t,
) -> *mut MMAPString {
if mmap_string_maybe_expand(string, len).is_null() {
return 0 as *mut MMAPString;
}
if pos < (*string).len {
memmove(
(*string).str_0.offset(pos as isize).offset(len as isize) as *mut libc::c_void,
(*string).str_0.offset(pos as isize) as *const libc::c_void,
(*string).len.wrapping_sub(pos),
);
}
memmove(
(*string).str_0.offset(pos as isize) as *mut libc::c_void,
val as *const libc::c_void,
len,
);
(*string).len = ((*string).len as libc::size_t).wrapping_add(len) as size_t as size_t;
*(*string).str_0.offset((*string).len as isize) = 0i32 as libc::c_char;
return string;
}
unsafe fn mmap_string_maybe_expand(
mut string: *mut MMAPString,
mut len: size_t,
) -> *mut MMAPString {
if (*string).len.wrapping_add(len) >= (*string).allocated_len {
let mut old_size: size_t = 0;
let mut newstring: *mut MMAPString = 0 as *mut MMAPString;
old_size = (*string).allocated_len;
(*string).allocated_len = nearest_power(
1i32 as size_t,
(*string)
.len
.wrapping_add(len)
.wrapping_add(1i32 as libc::size_t),
);
newstring = mmap_string_realloc_memory(string);
if newstring.is_null() {
(*string).allocated_len = old_size
}
return newstring;
}
return string;
}
/* Strings.
*/
/* SEB */
unsafe fn mmap_string_realloc_memory(mut string: *mut MMAPString) -> *mut MMAPString {
let mut tmp: *mut libc::c_char = 0 as *mut libc::c_char;
tmp = realloc(
(*string).str_0 as *mut libc::c_void,
(*string).allocated_len,
) as *mut libc::c_char;
if tmp.is_null() {
string = 0 as *mut MMAPString
} else {
(*string).str_0 = tmp
}
return string;
}
/* MMAPString */
#[inline]
unsafe fn nearest_power(mut base: size_t, mut num: size_t) -> size_t {
if num > (-1i32 as size_t).wrapping_div(2i32 as libc::size_t) {
return -1i32 as size_t;
} else {
let mut n: size_t = base;
while n < num {
n <<= 1i32
}
return n;
};
}
pub unsafe fn mmap_string_sized_new(mut dfl_size: size_t) -> *mut MMAPString {
let mut string: *mut MMAPString = 0 as *mut MMAPString;
string = malloc(::std::mem::size_of::<MMAPString>() as libc::size_t) as *mut MMAPString;
if string.is_null() {
return 0 as *mut MMAPString;
}
(*string).allocated_len = 0i32 as size_t;
(*string).len = 0i32 as size_t;
(*string).str_0 = 0 as *mut libc::c_char;
(*string).fd = -1i32;
(*string).mmapped_size = 0i32 as size_t;
if mmap_string_maybe_expand(
string,
if dfl_size > 2i32 as libc::size_t {
dfl_size
} else {
2i32 as libc::size_t
},
)
.is_null()
{
free(string as *mut libc::c_void);
return 0 as *mut MMAPString;
}
*(*string).str_0.offset(0isize) = 0i32 as libc::c_char;
return string;
}
pub unsafe fn mmap_string_new_len(
mut init: *const libc::c_char,
mut len: size_t,
) -> *mut MMAPString {
let mut string: *mut MMAPString = 0 as *mut MMAPString;
if len <= 0i32 as libc::size_t {
return mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
} else {
string = mmap_string_sized_new(len);
if string.is_null() {
return string;
}
if !init.is_null() {
mmap_string_append_len(string, init, len);
}
return string;
};
}
pub unsafe fn mmap_string_append_len(
mut string: *mut MMAPString,
mut val: *const libc::c_char,
mut len: size_t,
) -> *mut MMAPString {
return mmap_string_insert_len(string, (*string).len, val, len);
}
pub unsafe fn mmap_string_free(mut string: *mut MMAPString) {
if string.is_null() {
return;
}
free((*string).str_0 as *mut libc::c_void);
free(string as *mut libc::c_void);
}
pub unsafe fn mmap_string_assign(
mut string: *mut MMAPString,
mut rval: *const libc::c_char,
) -> *mut MMAPString {
mmap_string_truncate(string, 0i32 as size_t);
if mmap_string_append(string, rval).is_null() {
return 0 as *mut MMAPString;
}
return string;
}
pub unsafe fn mmap_string_truncate(
mut string: *mut MMAPString,
mut len: size_t,
) -> *mut MMAPString {
(*string).len = if len < (*string).len {
len
} else {
(*string).len
};
*(*string).str_0.offset((*string).len as isize) = 0i32 as libc::c_char;
return string;
}
pub unsafe fn mmap_string_set_size(
mut string: *mut MMAPString,
mut len: size_t,
) -> *mut MMAPString {
if len >= (*string).allocated_len {
if mmap_string_maybe_expand(string, len.wrapping_sub((*string).len)).is_null() {
return 0 as *mut MMAPString;
}
}
(*string).len = len;
*(*string).str_0.offset(len as isize) = 0i32 as libc::c_char;
return string;
}
pub unsafe fn mmap_string_append_c(
mut string: *mut MMAPString,
mut c: libc::c_char,
) -> *mut MMAPString {
return mmap_string_insert_c(string, (*string).len, c);
}
pub unsafe fn mmap_string_insert_c(
mut string: *mut MMAPString,
mut pos: size_t,
mut c: libc::c_char,
) -> *mut MMAPString {
if mmap_string_maybe_expand(string, 1i32 as size_t).is_null() {
return 0 as *mut MMAPString;
}
if pos < (*string).len {
memmove(
(*string).str_0.offset(pos as isize).offset(1isize) as *mut libc::c_void,
(*string).str_0.offset(pos as isize) as *const libc::c_void,
(*string).len.wrapping_sub(pos),
);
}
*(*string).str_0.offset(pos as isize) = c;
(*string).len =
((*string).len as libc::size_t).wrapping_add(1i32 as libc::size_t) as size_t as size_t;
*(*string).str_0.offset((*string).len as isize) = 0i32 as libc::c_char;
return string;
}
pub unsafe fn mmap_string_prepend(
mut string: *mut MMAPString,
mut val: *const libc::c_char,
) -> *mut MMAPString {
return mmap_string_insert_len(string, 0i32 as size_t, val, strlen(val));
}
pub unsafe fn mmap_string_prepend_c(
mut string: *mut MMAPString,
mut c: libc::c_char,
) -> *mut MMAPString {
return mmap_string_insert_c(string, 0i32 as size_t, c);
}
pub unsafe fn mmap_string_prepend_len(
mut string: *mut MMAPString,
mut val: *const libc::c_char,
mut len: size_t,
) -> *mut MMAPString {
return mmap_string_insert_len(string, 0i32 as size_t, val, len);
}
pub unsafe fn mmap_string_insert(
mut string: *mut MMAPString,
mut pos: size_t,
mut val: *const libc::c_char,
) -> *mut MMAPString {
return mmap_string_insert_len(string, pos, val, strlen(val));
}
pub unsafe fn mmap_string_erase(
mut string: *mut MMAPString,
mut pos: size_t,
mut len: size_t,
) -> *mut MMAPString {
if pos.wrapping_add(len) < (*string).len {
memmove(
(*string).str_0.offset(pos as isize) as *mut libc::c_void,
(*string).str_0.offset(pos as isize).offset(len as isize) as *const libc::c_void,
(*string).len.wrapping_sub(pos.wrapping_add(len)),
);
}
(*string).len = ((*string).len as libc::size_t).wrapping_sub(len) as size_t as size_t;
*(*string).str_0.offset((*string).len as isize) = 0i32 as libc::c_char;
return string;
}
pub unsafe fn mmap_string_set_ceil(mut ceil: size_t) {
mmap_string_ceil = ceil;
}
static mut mmap_string_ceil: size_t = (8i32 * 1024i32 * 1024i32) as size_t;
pub unsafe fn mmap_string_ref(mut string: *mut MMAPString) -> libc::c_int {
let mut ht: *mut chash = 0 as *mut chash;
let mut r: libc::c_int = 0;
let mut key: chashdatum = chashdatum {
data: 0 as *mut libc::c_void,
len: 0,
};
let mut data: chashdatum = chashdatum {
data: 0 as *mut libc::c_void,
len: 0,
};
mmapstring_lock.lock().unwrap();
if mmapstring_hashtable.is_null() {
mmapstring_hashtable_init();
}
ht = mmapstring_hashtable;
if ht.is_null() {
return -1i32;
}
key.data = &mut (*string).str_0 as *mut *mut libc::c_char as *mut libc::c_void;
key.len = ::std::mem::size_of::<*mut libc::c_char>() as libc::size_t as libc::c_uint;
data.data = string as *mut libc::c_void;
data.len = 0i32 as libc::c_uint;
r = chash_set(
mmapstring_hashtable,
&mut key,
&mut data,
0 as *mut chashdatum,
);
if r < 0i32 {
return r;
}
return 0i32;
}
static mut mmapstring_hashtable: *mut chash = 0 as *const chash as *mut chash;
unsafe fn mmapstring_hashtable_init() {
mmapstring_hashtable = chash_new(13i32 as libc::c_uint, 1i32);
}
pub unsafe fn mmap_string_unref(mut str: *mut libc::c_char) -> libc::c_int {
let mut string: *mut MMAPString = 0 as *mut MMAPString;
let mut ht: *mut chash = 0 as *mut chash;
let mut key: chashdatum = chashdatum {
data: 0 as *mut libc::c_void,
len: 0,
};
let mut data: chashdatum = chashdatum {
data: 0 as *mut libc::c_void,
len: 0,
};
let mut r: libc::c_int = 0;
if str.is_null() {
return -1i32;
}
mmapstring_lock.lock().unwrap();
ht = mmapstring_hashtable;
if ht.is_null() {
return -1i32;
}
key.data = &mut str as *mut *mut libc::c_char as *mut libc::c_void;
key.len = ::std::mem::size_of::<*mut libc::c_char>() as libc::size_t as libc::c_uint;
r = chash_get(ht, &mut key, &mut data);
if r < 0i32 {
string = 0 as *mut MMAPString
} else {
string = data.data as *mut MMAPString
}
if !string.is_null() {
chash_delete(ht, &mut key, 0 as *mut chashdatum);
if chash_count(ht) == 0i32 as libc::c_uint {
chash_free(ht);
mmapstring_hashtable = 0 as *mut chash
}
}
if !string.is_null() {
mmap_string_free(string);
return 0i32;
} else {
return -1i32;
};
}
#[inline]
unsafe fn chash_count(mut hash: *mut chash) -> libc::c_uint {
return (*hash).count;
}
pub unsafe fn mmapstring_init_lock() {}
pub unsafe fn mmapstring_uninit_lock() {}

1728
mmime/src/other.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,6 @@
import mimetypes
import calendar
import json
from datetime import datetime
import os
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
@@ -243,12 +242,6 @@ 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):
@@ -331,18 +324,6 @@ 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):
@@ -351,12 +332,6 @@ 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.

View File

@@ -98,7 +98,6 @@ 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
@@ -151,8 +150,7 @@ DC_STR_MSGLOCATIONENABLED = 64
DC_STR_MSGLOCATIONDISABLED = 65
DC_STR_LOCATION = 66
DC_STR_STICKER = 67
DC_STR_DEVICE_MESSAGES = 68
DC_STR_COUNT = 68
DC_STR_COUNT = 67
# end const generated

View File

@@ -1,6 +1,7 @@
""" The Message object. """
import os
import shutil
from . import props
from .cutil import from_dc_charpointer, as_dc_charpointer
from .capi import lib, ffi
@@ -57,6 +58,8 @@ 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
@@ -69,6 +72,19 @@ 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
@@ -146,7 +162,7 @@ class Message(object):
if mime_headers:
s = ffi.string(ffi.gc(mime_headers, lib.dc_str_unref))
if isinstance(s, bytes):
return email.message_from_bytes(s)
s = s.decode("ascii")
return email.message_from_string(s)
@property

View File

@@ -155,18 +155,6 @@ 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()
@@ -442,65 +430,6 @@ 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)
message = chat.prepare_message(msg1)
assert message.is_out_preparing()
assert message.text == "withfile"
chat.send_prepared(message)
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)
@@ -532,46 +461,27 @@ class TestOnlineAccount:
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
ev = ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
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):
def test_forward_messages(self, acfactory):
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")
lp.sec("ac2: wait for receive")
# wait for other account to receive
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
assert ev[2] == msg_out.id
msg_in = ac2.get_message_by_id(msg_out.id)
assert msg_in.text == "message2"
lp.sec("ac2: check that the message arrive in deaddrop")
# check the message arrived in contact-requests/deaddrop
chat2 = msg_in.chat
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]
@@ -705,71 +615,6 @@ 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()
@@ -923,7 +768,6 @@ 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()
@@ -935,7 +779,6 @@ 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()

View File

@@ -57,13 +57,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)
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)

View File

@@ -1,4 +1,7 @@
from __future__ import print_function
import pytest
from deltachat import capi, cutil, const, set_context_callback, clear_context_callback
from deltachat.capi import ffi
from deltachat.capi import lib
@@ -155,3 +158,41 @@ def test_is_open_actually_open(tmpdir):
db_fname = tmpdir.join("test.db")
lib.dc_open(ctx, db_fname.strpath.encode("ascii"), ffi.NULL)
assert lib.dc_is_open(ctx) == 1
class TestConfig:
@pytest.fixture
def ctx(self, tmpdir):
ctx = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
db_fname = tmpdir.join("test.db")
lib.dc_open(ctx, db_fname.strpath.encode("ascii"), ffi.NULL)
assert lib.dc_is_open(ctx) == 1
return ctx
def test_selfavatar_copy(self, ctx, tmpdir):
# Setting an avatar outside of the blobdir should copy it into
# the blobdir.
avatar_src = tmpdir.join("avatar.jpg")
avatar_src.write("avatar")
lib.dc_set_config(ctx, b"selfavatar",
cutil.as_dc_charpointer(avatar_src.strpath))
avatar_blob = tmpdir.join("test.db-blobs/avatar.jpg")
assert avatar_blob.exists()
assert avatar_blob.read() == "avatar"
avatar_cfg = cutil.from_dc_charpointer(
lib.dc_get_config(ctx, b"selfavatar"))
assert avatar_cfg == avatar_blob
def test_selfavatar_nocopy(self, ctx, tmpdir):
# Setting an avatar already in the blobdir should not copy it.
avatar_src = tmpdir.join("test.db-blobs/avatar.jpg")
avatar_src.write("avatar")
lib.dc_set_config(ctx, b"selfavatar",
cutil.as_dc_charpointer(avatar_src.strpath))
avatar_cfg = cutil.from_dc_charpointer(
lib.dc_get_config(ctx, b"selfavatar"))
assert avatar_src == avatar_cfg

View File

@@ -7,7 +7,11 @@ envlist =
[testenv]
commands =
pytest -n6 --reruns 2 --reruns-delay 5 -v -rsXx {posargs:tests}
# (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}
# python tests/package_wheels.py {toxworkdir}/wheelhouse
passenv =
TRAVIS
@@ -32,7 +36,7 @@ commands =
[testenv:lint]
skipsdist = True
skip_install = True
usedevelop = True
deps =
flake8
# pygments required by rst-lint
@@ -49,8 +53,7 @@ deps =
sphinx==2.2.0
breathe
commands =
sphinx-build -Q -w toxdoc-warnings.log -b html . _build/html
sphinx-build -w toxdoc-warnings.log -b html . _build/html
[testenv:lintdoc]
skipsdist = True
@@ -62,14 +65,12 @@ commands =
{[testenv:lint]commands}
{[testenv:doc]commands}
[pytest]
addopts = -v -rs
python_files = tests/test_*.py
norecursedirs = .tox
addopts = -v -ra
python_files = tests/test_*.py
norecursedirs = .tox
xfail_strict=true
timeout = 60
timeout = 60
timeout_method = thread
[flake8]

View File

@@ -33,8 +33,6 @@ 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:

View File

@@ -3,12 +3,14 @@
//! 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
@@ -42,8 +44,8 @@ impl str::FromStr for EncryptPreference {
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"mutual" => Ok(EncryptPreference::Mutual),
"nopreference" => Ok(EncryptPreference::NoPreference),
_ => Err(()),
"reset" => Ok(EncryptPreference::Reset),
_ => Ok(EncryptPreference::NoPreference),
}
}
}
@@ -66,54 +68,60 @@ impl Aheader {
}
}
pub fn from_headers(
context: &Context,
wanted_from: &str,
headers: &[mailparse::MailHeader<'_>],
) -> Option<Self> {
use mailparse::MailHeaderMap;
if let Ok(Some(value)) = headers.get_first_value("Autocrypt") {
match Self::from_str(&value) {
Ok(header) => {
if addr_cmp(&header.addr, wanted_from) {
return Some(header);
}
}
Err(err) => {
warn!(
context,
"found invalid autocrypt header {}: {:?}", value, err
);
}
}
pub fn from_imffields(wanted_from: &str, header: *const mailimf_fields) -> Option<Self> {
if header.is_null() {
return None;
}
None
let mut fine_header = None;
let mut cur = unsafe { (*(*header).fld_list).first };
while !cur.is_null() {
let field = unsafe { (*cur).data as *mut mailimf_field };
if !field.is_null()
&& unsafe { (*field).fld_type } == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int
{
let optional_field = unsafe { (*field).fld_data.fld_optional_field };
if !optional_field.is_null()
&& unsafe { !(*optional_field).fld_name.is_null() }
&& unsafe { CStr::from_ptr((*optional_field).fld_name).to_string_lossy() }
== "Autocrypt"
{
let value =
unsafe { CStr::from_ptr((*optional_field).fld_value).to_string_lossy() };
if let Ok(test) = Self::from_str(&value) {
if addr_cmp(&test.addr, wanted_from) {
if fine_header.is_none() {
fine_header = Some(test);
} else {
// TODO: figure out what kind of error case this is
return None;
}
}
}
}
}
cur = unsafe { (*cur).next };
}
fine_header
}
}
impl fmt::Display for Aheader {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
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
// TODO replace 78 with enum /rtn
// adds a whitespace every 78 characters, this allows libEtPan to
// wrap the lines according to RFC 5322
// (which may insert a linebreak before every whitespace)
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)
let keydata = self.public_key.to_base64(78);
write!(
fmt,
"addr={}; prefer-encrypt={}; keydata={}",
self.addr, self.prefer_encrypt, keydata
)
}
}
@@ -159,10 +167,13 @@ impl str::FromStr for Aheader {
}
};
let prefer_encrypt = attributes
let prefer_encrypt = match attributes
.remove("prefer-encrypt")
.and_then(|raw| raw.parse().ok())
.unwrap_or_default();
{
Some(pref) => pref,
None => EncryptPreference::NoPreference,
};
// Autocrypt-Level0: unknown attributes starting with an underscore can be safely ignored
// Autocrypt-Level0: unknown attribute, treat the header as invalid
@@ -182,13 +193,15 @@ impl str::FromStr for Aheader {
mod tests {
use super::*;
const RAWKEY: &str = "xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJgWL7O3y/g4HdDW/dN2z26Y6W13IMzx9gLInn1KQZChtqWAcr/ReUucXcymwcfg1mdkBGk3TSLeLihN6CJx8Wsv8ig+kgAzte4f5rqEEAJVQ9WZHuti7UiYs6oRzqTo06CRe9owVXxzdMf0VDQtf7ZFm9dpzKKbhH7Lu8880iiotQ9/yRCkDGp9fNThsrLdZiK6OIAcIBAqi2rI89aS1dAmnRbktQieCx5izzyYkR1KvVL3gTTllHOzfKVEC2asmtWu2e4se/+O4WMIS1eGrn7GeWVb0Vwc5ABEBAAHNETxhQEBiLmV4YW1wbGUuZGU+wsCJBBABCAAzAhkBBQJcxt5FAhsDBAsJCAcGFQgJCgsCAxYCARYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiML938H/18F+3Wf9/JaAy/8hCO1v4S2PVBhxaKCokaNFtkfaMRne2l087LscCFPiFNyb4mv6Z3YeK8Xpxlp2sI0ecvdiqLUOGfnxS6tQrj+83EjtIrZ/hXOk1h121QFWH9Zg2VNHtODXjAgdLDC0NWUrclR0ZOqEDQHeo0ibTILdokVfXFN25wakPmGaYJP2y729cb1ve7RzvIvwn+Dddfxo3ao72rBfLi7l4NQ4S0KsY4cw+/6l5bRCKYCP77wZtvCwUvfVVosLdT43agtSiBI49+ayqvZ8OCvSJa61i+v81brTiEy9GBod4eAp45Ibsuemkw+gon4ZOvUXHTjwFB+h63MrozOwE0EXMbePQEIAL/vauf1zK8JgCu3V+G+SOX0iWw5xUlCPX+ERpBbWfwu3uAqn4wYXD3JDE/fVAF668xiV4eTPtlSUd5h0mn+G7uXMMOtkb+20SoEt50f8zw8TrL9t+ZsV11GKZWJpCar5AhXWsn6EEi8I2hLL5vn55ZZmHuGgN4jjmkRl3ToKCLhaXwTBjCJem7N5EH7F75wErEITa55v4Lb4Nfca7vnvtYrI1OA446xa8gHra0SINelTD09/JM/Fw4sWVPBaRZmJK/Tnu79N23No9XBUubmFPv1pNexZsQclicnTpt/BEWhiun7d6lfGB63K1aoHRTR1pcrWvBuALuuz0gqar2zlI0AEQEAAcLAdgQYAQgAIAUCXMbeRQIbDBYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiMLKSEIAIyLCRO2OyZ0IYRvRPpMn4p7E+7Pfcz/0mSkOy+1hshgJnqivXurm8zwGrwdMqeV4eslKR9H1RUdWGUQJNbtwmmjrt5DHpIhYHl5t3FpCBaGbV20Omo00Q38lBl9MtrmZkZw+ktEk6X+0xCKssMF+2MADkSOIufbR5HrDVB89VZOHCO9DeXvCUUAw2hyJiL/LHmLzJ40zYoTmb+F//f0k0j+tRdbkefyRoCmwG7YGiT+2hnCdgcezswnzah5J3ZKlrg7jOGo1LxtbvNUzxNBbC6S/aNgwm6qxo7xegRhmEl5uZ16zwyj4qz+xkjGy25Of5mWfUDoNw7OT7sjUbHOOMc=";
fn rawkey() -> String {
"xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJgWL7O3y/g4HdDW/dN2z26Y6W13IMzx9gLInn1KQZChtqWAcr/ReUucXcymwcfg1mdkBGk3TSLeLihN6CJx8Wsv8ig+kgAzte4f5rqEEAJVQ9WZHuti7UiYs6oRzqTo06CRe9owVXxzdMf0VDQtf7ZFm9dpzKKbhH7Lu8880iiotQ9/yRCkDGp9fNThsrLdZiK6OIAcIBAqi2rI89aS1dAmnRbktQieCx5izzyYkR1KvVL3gTTllHOzfKVEC2asmtWu2e4se/+O4WMIS1eGrn7GeWVb0Vwc5ABEBAAHNETxhQEBiLmV4YW1wbGUuZGU+wsCJBBABCAAzAhkBBQJcxt5FAhsDBAsJCAcGFQgJCgsCAxYCARYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiML938H/18F+3Wf9/JaAy/8hCO1v4S2PVBhxaKCokaNFtkfaMRne2l087LscCFPiFNyb4mv6Z3YeK8Xpxlp2sI0ecvdiqLUOGfnxS6tQrj+83EjtIrZ/hXOk1h121QFWH9Zg2VNHtODXjAgdLDC0NWUrclR0ZOqEDQHeo0ibTILdokVfXFN25wakPmGaYJP2y729cb1ve7RzvIvwn+Dddfxo3ao72rBfLi7l4NQ4S0KsY4cw+/6l5bRCKYCP77wZtvCwUvfVVosLdT43agtSiBI49+ayqvZ8OCvSJa61i+v81brTiEy9GBod4eAp45Ibsuemkw+gon4ZOvUXHTjwFB+h63MrozOwE0EXMbePQEIAL/vauf1zK8JgCu3V+G+SOX0iWw5xUlCPX+ERpBbWfwu3uAqn4wYXD3JDE/fVAF668xiV4eTPtlSUd5h0mn+G7uXMMOtkb+20SoEt50f8zw8TrL9t+ZsV11GKZWJpCar5AhXWsn6EEi8I2hLL5vn55ZZmHuGgN4jjmkRl3ToKCLhaXwTBjCJem7N5EH7F75wErEITa55v4Lb4Nfca7vnvtYrI1OA446xa8gHra0SINelTD09/JM/Fw4sWVPBaRZmJK/Tnu79N23No9XBUubmFPv1pNexZsQclicnTpt/BEWhiun7d6lfGB63K1aoHRTR1pcrWvBuALuuz0gqar2zlI0AEQEAAcLAdgQYAQgAIAUCXMbeRQIbDBYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiMLKSEIAIyLCRO2OyZ0IYRvRPpMn4p7E+7Pfcz/0mSkOy+1hshgJnqivXurm8zwGrwdMqeV4eslKR9H1RUdWGUQJNbtwmmjrt5DHpIhYHl5t3FpCBaGbV20Omo00Q38lBl9MtrmZkZw+ktEk6X+0xCKssMF+2MADkSOIufbR5HrDVB89VZOHCO9DeXvCUUAw2hyJiL/LHmLzJ40zYoTmb+F//f0k0j+tRdbkefyRoCmwG7YGiT+2hnCdgcezswnzah5J3ZKlrg7jOGo1LxtbvNUzxNBbC6S/aNgwm6qxo7xegRhmEl5uZ16zwyj4qz+xkjGy25Of5mWfUDoNw7OT7sjUbHOOMc=".into()
}
#[test]
fn test_from_str() {
let h: Aheader = format!(
"addr=me@mail.com; prefer-encrypt=mutual; keydata={}",
RAWKEY
rawkey()
)
.parse()
.expect("failed to parse");
@@ -197,22 +210,9 @@ 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,57 +223,33 @@ 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 = 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 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 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");
}
@@ -285,30 +261,4 @@ 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"));
}
}

View File

@@ -32,11 +32,11 @@ impl<'a> BlobObject<'a> {
///
/// # Errors
///
/// [BlobError::CreateFailure] is used when the file could not
/// [BlobErrorKind::CreateFailure] is used when the file could not
/// be created. You can expect [BlobError.cause] to contain an
/// underlying error.
///
/// [BlobError::WriteFailure] is used when the file could not
/// [BlobErrorKind::WriteFailure] is used when the file could not
/// be written to. You can expect [BlobError.cause] to contain an
/// underlying error.
pub fn create(
@@ -48,12 +48,7 @@ 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::WriteFailure {
blobdir: blobdir.to_path_buf(),
blobname: name.clone(),
cause: err,
backtrace: failure::Backtrace::new(),
})?;
.map_err(|err| BlobError::new_write_failure(blobdir, &name, err))?;
let blob = BlobObject {
blobdir,
name: format!("$BLOBDIR/{}", name),
@@ -76,25 +71,18 @@ impl<'a> BlobObject<'a> {
Ok(file) => return Ok((name, file)),
Err(err) => {
if attempt == max_attempt {
return Err(BlobError::CreateFailure {
blobdir: dir.to_path_buf(),
blobname: name,
cause: err,
backtrace: failure::Backtrace::new(),
});
return Err(BlobError::new_create_failure(dir, &name, err));
} else {
name = format!("{}-{}{}", stem, rand::random::<u32>(), ext);
}
}
}
}
// 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(),
})
Err(BlobError::new_create_failure(
dir,
&name,
format_err!("Unreachable code - supposedly"),
))
}
/// Creates a new blob object with unique name by copying an existing file.
@@ -107,35 +95,24 @@ impl<'a> BlobObject<'a> {
/// # Errors
///
/// In addition to the errors in [BlobObject::create] the
/// [BlobError::CopyFailure] is used when the data can not be
/// [BlobErrorKind::CopyFailure] is used when the data can not be
/// copied.
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::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 mut src_file = fs::File::open(src.as_ref()).map_err(|err| {
BlobError::new_copy_failure(context.get_blobdir(), "", src.as_ref(), err)
})?;
let (stem, ext) = BlobObject::sanitise_name(&src.as_ref().to_string_lossy());
let (name, mut dst_file) = BlobObject::create_new_file(context.get_blobdir(), &stem, &ext)?;
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_for_err);
let path = context.get_blobdir().join(&name);
fs::remove_file(path).ok();
}
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(),
}
BlobError::new_copy_failure(context.get_blobdir(), &name, src.as_ref(), err)
})?;
let blob = BlobObject {
blobdir: context.get_blobdir(),
@@ -179,10 +156,10 @@ impl<'a> BlobObject<'a> {
///
/// # Errors
///
/// [BlobError::WrongBlobdir] is used if the path is not in
/// [BlobErrorKind::WrongBlobdir] is used if the path is not in
/// the blob directory.
///
/// [BlobError::WrongName] is used if the file name does not
/// [BlobErrorKind::WrongName] is used if the file name does not
/// remain identical after sanitisation.
pub fn from_path(
context: &Context,
@@ -191,21 +168,13 @@ impl<'a> BlobObject<'a> {
let rel_path = path
.as_ref()
.strip_prefix(context.get_blobdir())
.map_err(|_| BlobError::WrongBlobdir {
blobdir: context.get_blobdir().to_path_buf(),
src: path.as_ref().to_path_buf(),
backtrace: failure::Backtrace::new(),
})?;
.map_err(|_| BlobError::new_wrong_blobdir(context.get_blobdir(), path.as_ref()))?;
if !BlobObject::is_acceptible_blob_name(&rel_path) {
return Err(BlobError::WrongName {
blobname: path.as_ref().to_path_buf(),
backtrace: failure::Backtrace::new(),
});
return Err(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(),
})?;
let name = rel_path
.to_str()
.ok_or_else(|| BlobError::new_wrong_name(path.as_ref()))?;
BlobObject::from_name(context, name.to_string())
}
@@ -218,7 +187,7 @@ impl<'a> BlobObject<'a> {
///
/// # Errors
///
/// [BlobError::WrongName] is used if the name is not a valid
/// [BlobErrorKind::WrongName] is used if the name is not a valid
/// blobname, i.e. if [BlobObject::sanitise_name] does modify the
/// provided name.
pub fn from_name(
@@ -230,10 +199,7 @@ impl<'a> BlobObject<'a> {
false => name,
};
if !BlobObject::is_acceptible_blob_name(&name) {
return Err(BlobError::WrongName {
blobname: PathBuf::from(name),
backtrace: failure::Backtrace::new(),
});
return Err(BlobError::new_wrong_name(name));
}
Ok(BlobObject {
blobdir: context.get_blobdir(),
@@ -358,50 +324,171 @@ impl<'a> fmt::Display for BlobObject<'a> {
}
/// Errors for the [BlobObject].
#[derive(Fail, Debug)]
pub enum BlobError {
///
/// To keep the return type small and thus the happy path fast this
/// stores everything on the heap.
#[derive(Debug)]
pub struct BlobError {
inner: Box<BlobErrorInner>,
}
#[derive(Debug)]
struct BlobErrorInner {
kind: BlobErrorKind,
data: BlobErrorData,
backtrace: failure::Backtrace,
}
/// Error kind for [BlobError].
///
/// Each error kind has associated data in the [BlobErrorData].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlobErrorKind {
/// Failed to create the blob.
CreateFailure,
/// Failed to write data to blob.
WriteFailure,
/// Failed to copy data to blob.
CopyFailure,
/// Blob is not in the blobdir.
WrongBlobdir,
/// Blob has a bad name.
///
/// E.g. the name is not sanitised correctly or contains a
/// sub-directory.
WrongName,
}
/// Associated data for each [BlobError] error kind.
///
/// This is not stored directly on the [BlobErrorKind] so that the
/// kind can stay trivially Copy and Eq. It is however possible to
/// create a [BlobError] with mismatching [BlobErrorKind] and
/// [BlobErrorData], don't do that.
///
/// Any blobname stored here is the bare name, without the `$BLOBDIR`
/// prefix. All data is owned so that errors do not need to be tied
/// to any lifetimes.
#[derive(Debug)]
enum BlobErrorData {
CreateFailure {
blobdir: PathBuf,
blobname: String,
#[cause]
cause: std::io::Error,
backtrace: failure::Backtrace,
cause: failure::Error,
},
WriteFailure {
blobdir: PathBuf,
blobname: String,
#[cause]
cause: std::io::Error,
backtrace: failure::Backtrace,
cause: failure::Error,
},
CopyFailure {
blobdir: PathBuf,
blobname: String,
src: PathBuf,
#[cause]
cause: std::io::Error,
backtrace: failure::Backtrace,
cause: failure::Error,
},
WrongBlobdir {
blobdir: PathBuf,
src: PathBuf,
backtrace: failure::Backtrace,
},
WrongName {
blobname: PathBuf,
backtrace: failure::Backtrace,
},
}
// Implementing Display is done by hand because the failure
// #[fail(display = "...")] syntax does not allow using
// `blobdir.display()`.
impl BlobError {
pub fn kind(&self) -> BlobErrorKind {
self.inner.kind
}
fn new_create_failure(
blobdir: impl Into<PathBuf>,
blobname: impl Into<String>,
cause: impl Into<failure::Error>,
) -> BlobError {
BlobError {
inner: Box::new(BlobErrorInner {
kind: BlobErrorKind::CreateFailure,
data: BlobErrorData::CreateFailure {
blobdir: blobdir.into(),
blobname: blobname.into(),
cause: cause.into(),
},
backtrace: failure::Backtrace::new(),
}),
}
}
fn new_write_failure(
blobdir: impl Into<PathBuf>,
blobname: impl Into<String>,
cause: impl Into<failure::Error>,
) -> BlobError {
BlobError {
inner: Box::new(BlobErrorInner {
kind: BlobErrorKind::WriteFailure,
data: BlobErrorData::WriteFailure {
blobdir: blobdir.into(),
blobname: blobname.into(),
cause: cause.into(),
},
backtrace: failure::Backtrace::new(),
}),
}
}
fn new_copy_failure(
blobdir: impl Into<PathBuf>,
blobname: impl Into<String>,
src: impl Into<PathBuf>,
cause: impl Into<failure::Error>,
) -> BlobError {
BlobError {
inner: Box::new(BlobErrorInner {
kind: BlobErrorKind::CopyFailure,
data: BlobErrorData::CopyFailure {
blobdir: blobdir.into(),
blobname: blobname.into(),
src: src.into(),
cause: cause.into(),
},
backtrace: failure::Backtrace::new(),
}),
}
}
fn new_wrong_blobdir(blobdir: impl Into<PathBuf>, src: impl Into<PathBuf>) -> BlobError {
BlobError {
inner: Box::new(BlobErrorInner {
kind: BlobErrorKind::WrongBlobdir,
data: BlobErrorData::WrongBlobdir {
blobdir: blobdir.into(),
src: src.into(),
},
backtrace: failure::Backtrace::new(),
}),
}
}
fn new_wrong_name(blobname: impl Into<PathBuf>) -> BlobError {
BlobError {
inner: Box::new(BlobErrorInner {
kind: BlobErrorKind::WrongName,
data: BlobErrorData::WrongName {
blobname: blobname.into(),
},
backtrace: failure::Backtrace::new(),
}),
}
}
}
impl fmt::Display for BlobError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Match on the data rather than kind, they are equivalent for
// identifying purposes but contain the actual data we need.
match &self {
BlobError::CreateFailure {
match &self.inner.data {
BlobErrorData::CreateFailure {
blobdir, blobname, ..
} => write!(
f,
@@ -409,7 +496,7 @@ impl fmt::Display for BlobError {
blobname,
blobdir.display()
),
BlobError::WriteFailure {
BlobErrorData::WriteFailure {
blobdir, blobname, ..
} => write!(
f,
@@ -417,7 +504,7 @@ impl fmt::Display for BlobError {
blobname,
blobdir.display()
),
BlobError::CopyFailure {
BlobErrorData::CopyFailure {
blobdir,
blobname,
src,
@@ -429,19 +516,34 @@ impl fmt::Display for BlobError {
blobname,
blobdir.display(),
),
BlobError::WrongBlobdir { blobdir, src, .. } => write!(
BlobErrorData::WrongBlobdir { blobdir, src } => write!(
f,
"File path {} is not in blobdir {}",
src.display(),
blobdir.display(),
),
BlobError::WrongName { blobname, .. } => {
BlobErrorData::WrongName { blobname } => {
write!(f, "Blob has a bad name: {}", blobname.display(),)
}
}
}
}
impl failure::Fail for BlobError {
fn cause(&self) -> Option<&dyn failure::Fail> {
match &self.inner.data {
BlobErrorData::CreateFailure { cause, .. }
| BlobErrorData::WriteFailure { cause, .. }
| BlobErrorData::CopyFailure { cause, .. } => Some(cause.as_fail()),
_ => None,
}
}
fn backtrace(&self) -> Option<&failure::Backtrace> {
Some(&self.inner.backtrace)
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -483,23 +585,23 @@ mod tests {
#[test]
fn test_suffix() {
let t = dummy_context();
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);
let foo = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
assert_eq!(foo.suffix(), Some("txt"));
let bar = BlobObject::create(&t.ctx, "bar", b"world").unwrap();
assert_eq!(bar.suffix(), None);
}
#[test]
fn test_create_dup() {
let t = dummy_context();
BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
let foo_path = t.ctx.get_blobdir().join("foo.txt");
assert!(foo_path.exists());
let foo = t.ctx.get_blobdir().join("foo.txt");
assert!(foo.exists());
BlobObject::create(&t.ctx, "foo.txt", b"world").unwrap();
for dirent in fs::read_dir(t.ctx.get_blobdir()).unwrap() {
let fname = dirent.unwrap().file_name();
if fname == foo_path.file_name().unwrap() {
assert_eq!(fs::read(&foo_path).unwrap(), b"hello");
if fname == foo.file_name().unwrap() {
assert_eq!(fs::read(&foo).unwrap(), b"hello");
} else {
let name = fname.to_str().unwrap();
assert!(name.starts_with("foo"));
@@ -512,13 +614,13 @@ mod tests {
fn test_double_ext_preserved() {
let t = dummy_context();
BlobObject::create(&t.ctx, "foo.tar.gz", b"hello").unwrap();
let foo_path = t.ctx.get_blobdir().join("foo.tar.gz");
assert!(foo_path.exists());
let foo = t.ctx.get_blobdir().join("foo.tar.gz");
assert!(foo.exists());
BlobObject::create(&t.ctx, "foo.tar.gz", b"world").unwrap();
for dirent in fs::read_dir(t.ctx.get_blobdir()).unwrap() {
let fname = dirent.unwrap().file_name();
if fname == foo_path.file_name().unwrap() {
assert_eq!(fs::read(&foo_path).unwrap(), b"hello");
if fname == foo.file_name().unwrap() {
assert_eq!(fs::read(&foo).unwrap(), b"hello");
} else {
let name = fname.to_str().unwrap();
println!("{}", name);

View File

@@ -4,20 +4,19 @@ use std::path::{Path, PathBuf};
use itertools::Itertools;
use num_traits::FromPrimitive;
use serde_json::json;
use crate::blob::{BlobError, BlobObject};
use crate::blob::{BlobErrorKind, 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;
@@ -65,15 +64,13 @@ impl Chat {
);
match res {
Err(err @ crate::sql::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => {
Err(err.into())
}
Err(err @ crate::error::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => Err(err),
Err(err) => {
error!(
context,
"chat: failed to load from db {}: {:?}", chat_id, err
);
Err(err.into())
Err(err)
}
Ok(mut chat) => {
match chat.id {
@@ -132,8 +129,7 @@ impl Chat {
&context.sql,
"UPDATE chats SET param=? WHERE id=?",
params![self.param.to_string(), self.id as i32],
)?;
Ok(())
)
}
pub fn get_id(&self) -> u32 {
@@ -260,6 +256,8 @@ 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;
@@ -310,20 +308,24 @@ impl Chat {
self.id
);
}
} else if self.typ == Chattype::Group
|| self.typ == Chattype::VerifiedGroup
&& 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 {
if self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
self.param.remove(Param::Unpromoted);
self.update_param(context)?;
}
}
}
/* check if we want to encrypt this message. If yes and circumstances change
/* check if we can guarantee E2EE for this message.
if we guarantee E2EE, and circumstances change
so that E2EE is no longer available at a later point (reset, changed settings),
we might not send the message out at all */
if msg.param.get_int(Param::ForcePlaintext).unwrap_or_default() == 0 {
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 {
let mut can_encrypt = true;
let mut all_mutual = context.get_config_bool(Config::E2eeEnabled);
let mut all_mutual = true;
// take care that this statement returns NULL rows
// if there is no peerstates for a chat member!
@@ -370,13 +372,18 @@ impl Chat {
}
}
if can_encrypt
&& (all_mutual || last_msg_in_chat_encrypted(context, &context.sql, self.id))
{
msg.param.set_int(Param::GuaranteeE2ee, 1);
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;
}
}
}
// reset encrypt error state eg. for forwarding
if do_guarantee_e2ee {
msg.param.set_int(Param::GuaranteeE2ee, 1);
}
// reset eg. for forwarding
msg.param.remove(Param::ErroneousE2ee);
// set "In-Reply-To:" to identify the message to which the composed message is a reply;
@@ -409,15 +416,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;
new_references = parent_in_reply_to.clone();
}
}
}
// add independent location to database
if msg.param.exists(Param::SetLatitude)
&& sql::execute(
if msg.param.exists(Param::SetLatitude) {
if sql::execute(
context,
&context.sql,
"INSERT INTO locations \
@@ -432,16 +439,17 @@ 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
@@ -498,7 +506,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.
///
@@ -580,10 +588,6 @@ 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,
@@ -633,7 +637,7 @@ pub fn create_or_lookup_by_contact_id(
context,
&context.sql,
format!(
"INSERT INTO chats (type, name, param, blocked, grpid, created_timestamp) VALUES({}, '{}', '{}', {}, '{}', {})",
"INSERT INTO chats (type, name, param, blocked, grpid) VALUES({}, '{}', '{}', {}, '{}')",
100,
chat_name,
match contact_id {
@@ -646,7 +650,6 @@ pub fn create_or_lookup_by_contact_id(
},
create_blocked as u8,
contact.get_addr(),
time(),
),
params![],
)?;
@@ -677,7 +680,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> {
@@ -725,21 +728,11 @@ fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> {
if msg.type_0 == Viewtype::Text {
// the caller should check if the message text is empty
} else if msgtype_has_file(msg.type_0) {
let blob = if let Some(f) = msg.param.get_file(Param::File, context)? {
match f {
ParamsFile::Blob(blob) => blob,
ParamsFile::FsPath(path) => {
// path is outside the blobdir, let's copy
let blob = BlobObject::create_and_copy(context, path)?;
msg.param.set(Param::File, blob.as_name());
blob
}
}
} else {
bail!("Attachment missing for message of type #{}", msg.type_0);
};
let blob = msg
.param
.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.
@@ -835,8 +828,7 @@ 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.
@@ -877,14 +869,17 @@ 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(' ') {
if let Ok(msg_id) = forward
match forward
.parse::<u32>()
.map_err(|_| InvalidMsgId)
.map(MsgId::new)
.map(|id| MsgId::new(id))
{
if let Ok(mut msg) = Message::load_from_db(context, msg_id) {
send_msg(context, 0, &mut msg)?;
};
Ok(msg_id) => {
if let Ok(mut msg) = Message::load_from_db(context, msg_id) {
send_msg(context, 0, &mut msg)?;
};
}
Err(_) => (),
}
}
msg.param.remove(Param::PrepForwards);
@@ -980,8 +975,7 @@ 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
@@ -1394,7 +1388,7 @@ pub fn create_group_chat(
sql::execute(
context,
&context.sql,
"INSERT INTO chats (type, name, grpid, param, created_timestamp) VALUES(?, ?, ?, \'U=1\', ?);",
"INSERT INTO chats (type, name, grpid, param) VALUES(?, ?, ?, \'U=1\');",
params![
if verified != VerifiedStatus::Unverified {
Chattype::VerifiedGroup
@@ -1402,8 +1396,7 @@ pub fn create_group_chat(
Chattype::Group
},
chat_name.as_ref(),
grpid,
time(),
grpid
],
)?;
@@ -1493,7 +1486,7 @@ pub(crate) fn add_contact_to_chat_ex(
let self_addr = context
.get_config(Config::ConfiguredAddr)
.unwrap_or_default();
if addr_cmp(contact.get_addr(), &self_addr) {
if 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!(
@@ -1690,13 +1683,10 @@ 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()],
)
.map_err(Into::into)
context.sql.exists(
"SELECT id FROM leftgrps WHERE grpid=?;",
params![grpid.as_ref()],
)
}
pub fn set_chat_name(
@@ -1726,8 +1716,12 @@ pub fn set_chat_name(
if sql::execute(
context,
&context.sql,
"UPDATE chats SET name=? WHERE id=?;",
params![new_name.as_ref(), chat_id as i32],
format!(
"UPDATE chats SET name='{}' WHERE id={};",
new_name.as_ref(),
chat_id as i32
),
params![],
)
.is_ok()
{
@@ -1801,8 +1795,8 @@ 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 {
BlobError::WrongBlobdir { .. } => {
|err| match err.kind() {
BlobErrorKind::WrongBlobdir => {
BlobObject::create_and_copy(context, Path::new(new_image.as_ref()))
}
_ => Err(err),
@@ -1845,7 +1839,6 @@ 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!(
@@ -1914,54 +1907,6 @@ 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.gossiped_timestamp,
"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
@@ -2015,7 +1960,8 @@ pub fn add_device_msg(
label.is_some() || msg.is_some(),
"device-messages need label, msg or both"
);
let mut chat_id = 0;
let (chat_id, _blocked) =
create_or_lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE, Blocked::Not)?;
let mut msg_id = MsgId::new_unset();
if let Some(label) = label {
@@ -2026,10 +1972,7 @@ 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)?;
@@ -2257,7 +2200,6 @@ 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());
@@ -2309,38 +2251,6 @@ 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()
@@ -2396,20 +2306,4 @@ 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"
);
}
}

View File

@@ -6,7 +6,7 @@ use crate::contact::*;
use crate::context::*;
use crate::error::Result;
use crate::lot::Lot;
use crate::message::{Message, MessageState, MsgId};
use crate::message::{Message, 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,42 +119,46 @@ 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(
"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],
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],
process_row,
process_rows,
)?
} else if 0 != listflags & DC_GCL_ARCHIVED_ONLY {
// show archived chats
context.sql.query_map(
"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],
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![],
process_row,
process_rows,
)?
@@ -164,42 +168,46 @@ impl Chatlist {
let str_like_cmd = format!("%{}%", query);
context.sql.query_map(
"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],
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],
process_row,
process_rows,
)?
} else {
// show normal chatlist
let mut ids = context.sql.query_map(
"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],
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![],
process_row,
process_rows,
)?;
@@ -289,7 +297,7 @@ impl Chatlist {
let lastmsg_id = self.ids[index].1;
let mut lastcontact = None;
let lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
let mut lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
if lastmsg.from_id != DC_CONTACT_ID_SELF
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
{
@@ -301,6 +309,16 @@ 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
@@ -344,44 +362,3 @@ 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);
}
}

View File

@@ -1,174 +1,575 @@
//! # Key-value configuration management
use strum::{EnumProperty, IntoEnumIterator};
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
use std::str::FromStr;
use strum::IntoEnumIterator;
use strum_macros::{AsRefStr, Display, EnumDiscriminants, EnumIter, 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;
/// The available configuration keys.
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Display, EnumString, AsRefStr, EnumIter, EnumProperty,
)]
/// The available configuration items.
///
/// There is also an enum called `ConfigKey` which has the same enum
/// variants but does not carry any data.
#[derive(Debug, Clone, Display, PartialEq, Eq, EnumDiscriminants, EnumProperty, AsRefStr)]
#[strum(serialize_all = "snake_case")]
pub enum Config {
Addr,
MailServer,
MailUser,
MailPw,
MailPort,
ImapCertificateChecks,
SendServer,
SendUser,
SendPw,
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,
ConfiguredMailUser,
ConfiguredMailPw,
ConfiguredMailPort,
ConfiguredMailSecurity,
ConfiguredImapCertificateChecks,
ConfiguredSendServer,
ConfiguredSendUser,
ConfiguredSendPw,
ConfiguredSendPort,
ConfiguredSmtpCertificateChecks,
ConfiguredServerFlags,
ConfiguredSendSecurity,
ConfiguredE2EEEnabled,
Configured,
#[strum(serialize = "sys.version")]
SysVersion,
#[strum(serialize = "sys.msgsize_max_recommended")]
SysMsgsizeMaxRecommended,
#[strum(serialize = "sys.config_keys")]
SysConfigKeys,
#[strum_discriminants(
name(ConfigKey),
derive(Display, EnumString, EnumIter),
strum(serialize_all = "snake_case")
)]
pub enum ConfigItem<'a> {
Addr(String),
BccSelf(String),
Configured(String),
ConfiguredAddr(String),
ConfiguredE2EEEnabled(String),
ConfiguredImapCertificateChecks(String),
ConfiguredMailPort(String),
ConfiguredMailPw(String),
ConfiguredMailSecurity(String),
ConfiguredMailServer(String),
ConfiguredMailUser(String),
ConfiguredSendPort(String),
ConfiguredSendPw(String),
ConfiguredSendSecurity(String),
ConfiguredSendServer(String),
ConfiguredSendUser(String),
ConfiguredServerFlags(String),
ConfiguredSmtpCertificateChecks(String),
Displayname(String),
E2eeEnabled(String),
ImapCertificateChecks(String),
ImapFolder(String),
InboxWatch(bool),
MailPort(String),
MailPw(String),
MailServer(String),
MailUser(String),
MdnsEnabled(String),
MvboxMove(String),
MvboxWatch(String),
SaveMimeHeaders(String),
Selfavatar(BlobObject<'a>),
Selfstatus(String),
SendPort(String),
SendPw(String),
SendServer(String),
SendUser(String),
SentboxWatch(String),
ServerFlags(String),
ShowEmails(String),
SmtpCertificateChecks(String),
SysConfigKeys(String),
SysMsgsizeMaxRecommended(String),
SysVersion(String),
}
impl Context {
/// Get a configuration key. Returns `None` if no value is set, and no default value found.
pub fn get_config(&self, key: Config) -> Option<String> {
let value = match key {
Config::Selfavatar => {
let rel_path = self.sql.get_raw_config(self, key);
rel_path.map(|p| dc_get_abs_path(self, &p).to_string_lossy().into_owned())
}
Config::SysVersion => Some((&*DC_VERSION_STR).clone()),
Config::SysMsgsizeMaxRecommended => Some(format!("{}", 24 * 1024 * 1024 / 4 * 3)),
Config::SysConfigKeys => Some(get_config_keys_string()),
_ => self.sql.get_raw_config(self, key),
};
// Transitional: support the old name for ConfigKey.
pub type Config = ConfigKey;
if value.is_some() {
return value;
}
// Default values
match key {
Config::Selfstatus => Some(self.stock_str(StockMessage::StatusLine).into_owned()),
_ => key.get_str("default").map(|s| s.to_string()),
impl ConfigKey {
/// Creates a [ConfigKey] from a string.
///
/// Use this rather than [ConfigKey::from_str].
pub fn from_key_str(s: &str) -> Result<ConfigKey, strum::ParseError> {
match s {
"sys.config_keys" => Ok(ConfigKey::SysConfigKeys),
"sys.msgsize_max_recommended" => Ok(ConfigKey::SysMsgsizeMaxRecommended),
"sys.version" => Ok(ConfigKey::SysVersion),
_ => ConfigKey::from_str(s),
}
}
/// Returns a string version of the [ConfigKey].
///
/// Use this rather than [ConfigKey::to_string].
pub fn to_key_string(&self) -> String {
let mut s = self.to_string();
if s.starts_with("sys_") {
s = s.replacen("sys_", "sys.", 1);
}
s
}
/// Default values for configuration options.
///
/// These are returned in case there is no value stored in the
/// database.
fn default_item<'a>(&self, context: &Context) -> Option<ConfigItem<'a>> {
match self {
Self::BccSelf => Some(ConfigItem::BccSelf(String::from("0"))),
Self::E2eeEnabled => Some(ConfigItem::E2eeEnabled(String::from("1"))),
Self::ImapFolder => Some(ConfigItem::ImapFolder(String::from("INBOX"))),
Self::InboxWatch => Some(ConfigItem::InboxWatch(true)),
Self::MdnsEnabled => Some(ConfigItem::MdnsEnabled(String::from("1"))),
Self::MvboxMove => Some(ConfigItem::MvboxMove(String::from("1"))),
Self::MvboxWatch => Some(ConfigItem::MvboxWatch(String::from("1"))),
Self::Selfstatus => Some(ConfigItem::Selfstatus(
context.stock_str(StockMessage::StatusLine).into_owned(),
)),
Self::SentboxWatch => Some(ConfigItem::SentboxWatch(String::from("1"))),
Self::ShowEmails => Some(ConfigItem::ShowEmails(String::from("0"))),
Self::SysConfigKeys => Some(ConfigItem::SysConfigKeys(get_config_keys_string())),
Self::SysMsgsizeMaxRecommended => Some(ConfigItem::SysMsgsizeMaxRecommended(format!(
"{}",
24 * 1024 * 1024 / 4 * 3
))),
Self::SysVersion => Some(ConfigItem::SysVersion((&*DC_VERSION_STR).clone())),
_ => None,
}
}
}
impl<'a> rusqlite::types::ToSql for ConfigItem<'a> {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
let sql_obj = match self {
ConfigItem::Selfavatar(blob) => {
rusqlite::types::Value::Text(blob.as_name().to_string())
}
ConfigItem::InboxWatch(value) => rusqlite::types::Value::Integer(*value as i64),
ConfigItem::Addr(value)
| ConfigItem::BccSelf(value)
| ConfigItem::Configured(value)
| ConfigItem::ConfiguredAddr(value)
| ConfigItem::ConfiguredE2EEEnabled(value)
| ConfigItem::ConfiguredImapCertificateChecks(value)
| ConfigItem::ConfiguredMailPort(value)
| ConfigItem::ConfiguredMailPw(value)
| ConfigItem::ConfiguredMailSecurity(value)
| ConfigItem::ConfiguredMailServer(value)
| ConfigItem::ConfiguredMailUser(value)
| ConfigItem::ConfiguredSendPort(value)
| ConfigItem::ConfiguredSendPw(value)
| ConfigItem::ConfiguredSendSecurity(value)
| ConfigItem::ConfiguredSendServer(value)
| ConfigItem::ConfiguredSendUser(value)
| ConfigItem::ConfiguredServerFlags(value)
| ConfigItem::ConfiguredSmtpCertificateChecks(value)
| ConfigItem::Displayname(value)
| ConfigItem::E2eeEnabled(value)
| ConfigItem::ImapCertificateChecks(value)
| ConfigItem::ImapFolder(value)
| ConfigItem::MailPort(value)
| ConfigItem::MailPw(value)
| ConfigItem::MailServer(value)
| ConfigItem::MailUser(value)
| ConfigItem::MdnsEnabled(value)
| ConfigItem::MvboxMove(value)
| ConfigItem::MvboxWatch(value)
| ConfigItem::SaveMimeHeaders(value)
| ConfigItem::Selfstatus(value)
| ConfigItem::SendPort(value)
| ConfigItem::SendPw(value)
| ConfigItem::SendServer(value)
| ConfigItem::SendUser(value)
| ConfigItem::SentboxWatch(value)
| ConfigItem::ServerFlags(value)
| ConfigItem::ShowEmails(value)
| ConfigItem::SmtpCertificateChecks(value)
| ConfigItem::SysConfigKeys(value)
| ConfigItem::SysMsgsizeMaxRecommended(value)
| ConfigItem::SysVersion(value) => rusqlite::types::Value::Text(value.to_string()),
};
let out = rusqlite::types::ToSqlOutput::Owned(sql_obj);
Ok(out)
}
}
impl Context {
/// Gets a configuration option.
///
/// If there is no value set in the database returns the default
/// value from [ConfigKey::default_item]. Any `Sys*` key should
/// always end up with the default.
pub fn get_config_item(&self, key: ConfigKey) -> Option<ConfigItem> {
let res = self.sql.query_row(
"SELECT value FROM config WHERE keyname=?;",
params![key.to_key_string()],
|row| {
row.get_raw_checked(0)
.map(|valref| self.sql_raw_to_config_item(key, valref))
},
);
match res {
Ok(item) => item,
Err(Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => key.default_item(self),
Err(err) => {
warn!(self, "get_config: Failed SQL query: {}", err);
None
}
}
}
// This is effectively the FromSql impl for ConfigItem, but we
// need to know the ConfigKey which the trait does not give us.
fn sql_raw_to_config_item(
&self,
key: ConfigKey,
raw: rusqlite::types::ValueRef,
) -> Option<ConfigItem> {
let to_string = |raw: rusqlite::types::ValueRef| -> Option<String> {
raw.as_str()
.map_err(|err| {
warn!(self, "ConfigItem {}; not a string: {}", key, err);
})
.map(|s| s.to_string())
.ok()
};
let to_blob = |raw: rusqlite::types::ValueRef| -> Option<BlobObject> {
// from_path() case because there was a time when
// rust-core inserted absolute filenames into the database
// and we want to gracefully recover from this.
to_string(raw).and_then(|s| {
BlobObject::from_name(&self, s.clone())
.or_else(|_| BlobObject::from_path(&self, s))
.ok()
})
};
let to_int = |raw: rusqlite::types::ValueRef| -> Option<i64> {
match raw {
// Current way this is stored.
rusqlite::types::ValueRef::Integer(val) => Some(val),
// Backward compatibility.
rusqlite::types::ValueRef::Text(val) => std::str::from_utf8(val)
.map_err(|e| {
warn!(self, "ConfigItem {}; not UTF-8: {}", key, e);
})
.ok()
.and_then(|v| match v.parse::<i64>() {
Ok(i) => Some(i),
Err(e) => {
warn!(self, "ConfigItem {}; not parsed as int: {}", key, e);
None
}
}),
_ => {
warn!(self, "ConfigItem {}; bad SQLite type: {:?}", key, raw);
None
}
}
};
let to_bool = |raw: rusqlite::types::ValueRef| -> Option<bool> {
to_int(raw).and_then(|i| match i {
0 => Some(false),
1 => Some(true),
v => {
warn!(self, "ConfigItem {}; bad bool value: {}", key, v);
None
}
})
};
match key {
ConfigKey::Addr => to_string(raw).map(|val| ConfigItem::Addr(val)),
ConfigKey::BccSelf => to_string(raw).map(|val| ConfigItem::BccSelf(val)),
ConfigKey::Configured => to_string(raw).map(|val| ConfigItem::Configured(val)),
ConfigKey::ConfiguredAddr => to_string(raw).map(|val| ConfigItem::ConfiguredAddr(val)),
ConfigKey::ConfiguredE2EEEnabled => {
to_string(raw).map(|val| ConfigItem::ConfiguredE2EEEnabled(val))
}
ConfigKey::ConfiguredImapCertificateChecks => {
to_string(raw).map(|val| ConfigItem::ConfiguredImapCertificateChecks(val))
}
ConfigKey::ConfiguredMailPort => {
to_string(raw).map(|val| ConfigItem::ConfiguredMailPort(val))
}
ConfigKey::ConfiguredMailPw => {
to_string(raw).map(|val| ConfigItem::ConfiguredMailPw(val))
}
ConfigKey::ConfiguredMailSecurity => {
to_string(raw).map(|val| ConfigItem::ConfiguredMailSecurity(val))
}
ConfigKey::ConfiguredMailServer => {
to_string(raw).map(|val| ConfigItem::ConfiguredMailServer(val))
}
ConfigKey::ConfiguredMailUser => {
to_string(raw).map(|val| ConfigItem::ConfiguredMailUser(val))
}
ConfigKey::ConfiguredSendPort => {
to_string(raw).map(|val| ConfigItem::ConfiguredSendPort(val))
}
ConfigKey::ConfiguredSendPw => {
to_string(raw).map(|val| ConfigItem::ConfiguredSendPw(val))
}
ConfigKey::ConfiguredSendSecurity => {
to_string(raw).map(|val| ConfigItem::ConfiguredSendSecurity(val))
}
ConfigKey::ConfiguredSendServer => {
to_string(raw).map(|val| ConfigItem::ConfiguredSendServer(val))
}
ConfigKey::ConfiguredSendUser => {
to_string(raw).map(|val| ConfigItem::ConfiguredSendUser(val))
}
ConfigKey::ConfiguredServerFlags => {
to_string(raw).map(|val| ConfigItem::ConfiguredServerFlags(val))
}
ConfigKey::ConfiguredSmtpCertificateChecks => {
to_string(raw).map(|val| ConfigItem::ConfiguredSmtpCertificateChecks(val))
}
ConfigKey::Displayname => to_string(raw).map(|val| ConfigItem::Displayname(val)),
ConfigKey::E2eeEnabled => to_string(raw).map(|val| ConfigItem::E2eeEnabled(val)),
ConfigKey::ImapCertificateChecks => {
to_string(raw).map(|val| ConfigItem::ImapCertificateChecks(val))
}
ConfigKey::ImapFolder => to_string(raw).map(|val| ConfigItem::ImapFolder(val)),
ConfigKey::InboxWatch => to_bool(raw).map(|val| ConfigItem::InboxWatch(val)),
ConfigKey::MailPort => to_string(raw).map(|val| ConfigItem::MailPort(val)),
ConfigKey::MailPw => to_string(raw).map(|val| ConfigItem::MailPw(val)),
ConfigKey::MailServer => to_string(raw).map(|val| ConfigItem::MailServer(val)),
ConfigKey::MailUser => to_string(raw).map(|val| ConfigItem::MailUser(val)),
ConfigKey::MdnsEnabled => to_string(raw).map(|val| ConfigItem::MdnsEnabled(val)),
ConfigKey::MvboxMove => to_string(raw).map(|val| ConfigItem::MvboxMove(val)),
ConfigKey::MvboxWatch => to_string(raw).map(|val| ConfigItem::MvboxWatch(val)),
ConfigKey::SaveMimeHeaders => {
to_string(raw).map(|val| ConfigItem::SaveMimeHeaders(val))
}
ConfigKey::Selfavatar => to_blob(raw).map(|val| ConfigItem::Selfavatar(val)),
ConfigKey::Selfstatus => to_string(raw).map(|val| ConfigItem::Selfstatus(val)),
ConfigKey::SendPort => to_string(raw).map(|val| ConfigItem::SendPort(val)),
ConfigKey::SendPw => to_string(raw).map(|val| ConfigItem::SendPw(val)),
ConfigKey::SendServer => to_string(raw).map(|val| ConfigItem::SendServer(val)),
ConfigKey::SendUser => to_string(raw).map(|val| ConfigItem::SendUser(val)),
ConfigKey::SentboxWatch => to_string(raw).map(|val| ConfigItem::SentboxWatch(val)),
ConfigKey::ServerFlags => to_string(raw).map(|val| ConfigItem::ServerFlags(val)),
ConfigKey::ShowEmails => to_string(raw).map(|val| ConfigItem::ShowEmails(val)),
ConfigKey::SmtpCertificateChecks => {
to_string(raw).map(|val| ConfigItem::SmtpCertificateChecks(val))
}
ConfigKey::SysConfigKeys => to_string(raw).map(|val| ConfigItem::SysConfigKeys(val)),
ConfigKey::SysMsgsizeMaxRecommended => {
to_string(raw).map(|val| ConfigItem::SysMsgsizeMaxRecommended(val))
}
ConfigKey::SysVersion => to_string(raw).map(|val| ConfigItem::SysVersion(val)),
}
}
/// Stores a configuration item in the database.
///
/// # Errors
///
/// You can not store any of the [ConfigItem::SysVersion],
/// [ConfigItem::SysMsgsizemaxrecommended] or
/// [ConfigItem::SysConfigkeys] variants.
pub fn set_config_item<'a>(&self, item: ConfigItem<'a>) -> Result<ConfigItem<'a>, Error> {
match item {
ConfigItem::SysConfigKeys(_)
| ConfigItem::SysMsgsizeMaxRecommended(_)
| ConfigItem::SysVersion(_) => bail!("Can not set config item {}", item),
_ => (),
}
let keyname = ConfigKey::from(&item).to_key_string();
// Would prefer to use INSERT OR REPLACE but this needs a
// uniqueness constraint on the keyname column which does not
// yet exist.
if self.sql.exists(
"SELECT value FROM config WHERE keyname=?;",
params![keyname],
)? {
self.sql.execute(
"UPDATE config SET value=? WHERE keyname=?",
params![item, keyname],
)?;
} else {
self.sql.execute(
"INSERT INTO config (keyname, value) VALUES (?, ?)",
params![keyname, item],
)?;
}
match item {
ConfigItem::InboxWatch(_) => interrupt_inbox_idle(self, true),
ConfigItem::SentboxWatch(_) => interrupt_sentbox_idle(self),
ConfigItem::MvboxWatch(_) => interrupt_mvbox_idle(self),
_ => (),
};
Ok(item)
}
/// Deletes a configuration option.
///
/// Returns `true` if the option was deleted, `false` if it wasn't
/// present in the first place.
pub fn del_config_item(&self, key: ConfigKey) -> Result<bool, Error> {
match self.sql.execute(
"DELETE FROM config WHERE keyname=?;",
params![key.to_string()],
) {
Ok(0) => Ok(false),
Ok(_) => Ok(true),
Err(err) => Err(err),
}
}
/// Transitional: migrate to get_config_item.
///
/// This will migrate to the FFI once nothing in the core uses
/// this anymore.
pub fn get_config(&self, key: Config) -> Option<String> {
if let Some(item) = self.get_config_item(key) {
let value = match item {
// Bool values.
ConfigItem::InboxWatch(value) => format!("{}", value as u32),
// String values.
ConfigItem::Addr(value)
| ConfigItem::BccSelf(value)
| ConfigItem::Configured(value)
| ConfigItem::ConfiguredAddr(value)
| ConfigItem::ConfiguredE2EEEnabled(value)
| ConfigItem::ConfiguredImapCertificateChecks(value)
| ConfigItem::ConfiguredMailPort(value)
| ConfigItem::ConfiguredMailPw(value)
| ConfigItem::ConfiguredMailSecurity(value)
| ConfigItem::ConfiguredMailServer(value)
| ConfigItem::ConfiguredMailUser(value)
| ConfigItem::ConfiguredSendPort(value)
| ConfigItem::ConfiguredSendPw(value)
| ConfigItem::ConfiguredSendSecurity(value)
| ConfigItem::ConfiguredSendServer(value)
| ConfigItem::ConfiguredSendUser(value)
| ConfigItem::ConfiguredServerFlags(value)
| ConfigItem::ConfiguredSmtpCertificateChecks(value)
| ConfigItem::Displayname(value)
| ConfigItem::E2eeEnabled(value)
| ConfigItem::ImapCertificateChecks(value)
| ConfigItem::ImapFolder(value)
| ConfigItem::MailPort(value)
| ConfigItem::MailPw(value)
| ConfigItem::MailServer(value)
| ConfigItem::MailUser(value)
| ConfigItem::MdnsEnabled(value)
| ConfigItem::MvboxMove(value)
| ConfigItem::MvboxWatch(value)
| ConfigItem::SaveMimeHeaders(value)
| ConfigItem::Selfstatus(value)
| ConfigItem::SendPort(value)
| ConfigItem::SendPw(value)
| ConfigItem::SendServer(value)
| ConfigItem::SendUser(value)
| ConfigItem::SentboxWatch(value)
| ConfigItem::ServerFlags(value)
| ConfigItem::ShowEmails(value)
| ConfigItem::SmtpCertificateChecks(value)
| ConfigItem::SysConfigKeys(value)
| ConfigItem::SysMsgsizeMaxRecommended(value)
| ConfigItem::SysVersion(value) => value,
// Blob values
ConfigItem::Selfavatar(blob) => blob.to_abs_path().to_string_lossy().into_owned(),
};
Some(value)
} else {
None
}
}
/// Transitional: migrate to get_config_item.
pub fn get_config_int(&self, key: Config) -> i32 {
self.get_config(key)
.and_then(|s| s.parse().ok())
.unwrap_or_default()
}
/// Transitional: migrate to get_config_item.
pub fn get_config_bool(&self, key: Config) -> bool {
self.get_config_int(key) != 0
}
/// Set the given config key.
/// If `None` is passed as a value the value is cleared and set to the default if there is one.
pub fn set_config(&self, key: Config, value: Option<&str>) -> crate::sql::Result<()> {
match key {
Config::Selfavatar if value.is_some() => {
let blob = BlobObject::create_from_path(&self, value.unwrap())?;
self.sql.set_raw_config(self, key, Some(blob.as_name()))
}
Config::InboxWatch => {
let ret = self.sql.set_raw_config(self, key, value);
interrupt_inbox_idle(self, true);
ret
}
Config::SentboxWatch => {
let ret = self.sql.set_raw_config(self, key, value);
interrupt_sentbox_idle(self);
ret
}
Config::MvboxWatch => {
let ret = self.sql.set_raw_config(self, key, value);
interrupt_mvbox_idle(self);
ret
}
///
/// Transitional: migrate to set_config_item/del_config_item.
///
/// 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> {
let maybe_val = match key {
Config::Selfstatus => {
let def = self.stock_str(StockMessage::StatusLine);
let val = if value.is_none() || value.unwrap() == def {
if value.is_none() || value.unwrap() == def {
None
} else {
value
};
self.sql.set_raw_config(self, key, val)
}
}
_ => self.sql.set_raw_config(self, key, value),
_ => value,
};
if let Some(val) = maybe_val {
let v = val.to_string();
let item = match key {
ConfigKey::Addr => ConfigItem::Addr(v),
ConfigKey::BccSelf => ConfigItem::BccSelf(v),
ConfigKey::Configured => ConfigItem::Configured(v),
ConfigKey::ConfiguredAddr => ConfigItem::ConfiguredAddr(v),
ConfigKey::ConfiguredE2EEEnabled => ConfigItem::ConfiguredE2EEEnabled(v),
ConfigKey::ConfiguredImapCertificateChecks => {
ConfigItem::ConfiguredImapCertificateChecks(v)
}
ConfigKey::ConfiguredMailPort => ConfigItem::ConfiguredMailPort(v),
ConfigKey::ConfiguredMailPw => ConfigItem::ConfiguredMailPw(v),
ConfigKey::ConfiguredMailSecurity => ConfigItem::ConfiguredMailSecurity(v),
ConfigKey::ConfiguredMailServer => ConfigItem::ConfiguredMailServer(v),
ConfigKey::ConfiguredMailUser => ConfigItem::ConfiguredMailUser(v),
ConfigKey::ConfiguredSendPort => ConfigItem::ConfiguredSendPort(v),
ConfigKey::ConfiguredSendPw => ConfigItem::ConfiguredSendPw(v),
ConfigKey::ConfiguredSendSecurity => ConfigItem::ConfiguredSendSecurity(v),
ConfigKey::ConfiguredSendServer => ConfigItem::ConfiguredSendServer(v),
ConfigKey::ConfiguredSendUser => ConfigItem::ConfiguredSendUser(v),
ConfigKey::ConfiguredServerFlags => ConfigItem::ConfiguredServerFlags(v),
ConfigKey::ConfiguredSmtpCertificateChecks => {
ConfigItem::ConfiguredSmtpCertificateChecks(v)
}
ConfigKey::Displayname => ConfigItem::Displayname(v),
ConfigKey::E2eeEnabled => ConfigItem::E2eeEnabled(v),
ConfigKey::ImapCertificateChecks => ConfigItem::ImapCertificateChecks(v),
ConfigKey::ImapFolder => ConfigItem::ImapFolder(v),
ConfigKey::InboxWatch => {
let val = match v.parse::<u32>() {
Ok(0) => false,
Ok(1) => true,
_ => bail!("set_config for {}: not a bool: {}", key, v),
};
ConfigItem::InboxWatch(val)
}
ConfigKey::MailPort => ConfigItem::MailPort(v),
ConfigKey::MailPw => ConfigItem::MailPw(v),
ConfigKey::MailServer => ConfigItem::MailServer(v),
ConfigKey::MailUser => ConfigItem::MailUser(v),
ConfigKey::MdnsEnabled => ConfigItem::MdnsEnabled(v),
ConfigKey::MvboxMove => ConfigItem::MvboxMove(v),
ConfigKey::MvboxWatch => ConfigItem::MvboxWatch(v),
ConfigKey::SaveMimeHeaders => ConfigItem::SaveMimeHeaders(v),
ConfigKey::Selfavatar => {
ConfigItem::Selfavatar(BlobObject::create_from_path(self, v)?)
}
ConfigKey::Selfstatus => ConfigItem::Selfstatus(v),
ConfigKey::SendPort => ConfigItem::SendPort(v),
ConfigKey::SendPw => ConfigItem::SendPw(v),
ConfigKey::SendServer => ConfigItem::SendServer(v),
ConfigKey::SendUser => ConfigItem::SendUser(v),
ConfigKey::SentboxWatch => ConfigItem::SentboxWatch(v),
ConfigKey::ServerFlags => ConfigItem::ServerFlags(v),
ConfigKey::ShowEmails => ConfigItem::ShowEmails(v),
ConfigKey::SmtpCertificateChecks => ConfigItem::SmtpCertificateChecks(v),
ConfigKey::SysConfigKeys => ConfigItem::SysConfigKeys(v),
ConfigKey::SysMsgsizeMaxRecommended => ConfigItem::SysMsgsizeMaxRecommended(v),
ConfigKey::SysVersion => ConfigItem::SysVersion(v),
};
self.set_config_item(item).map(|_| ())
} else {
self.del_config_item(key).map(|_| ())
}
}
}
/// Returns all available configuration keys concated together.
fn get_config_keys_string() -> String {
let keys = Config::iter().fold(String::new(), |mut acc, key| {
acc += key.as_ref();
let keys = ConfigKey::iter().fold(String::new(), |mut acc, key| {
acc += &key.to_key_string();
acc += " ";
acc
});
format!(" {} ", keys)
}
@@ -176,26 +577,78 @@ fn get_config_keys_string() -> String {
mod tests {
use super::*;
use std::str::FromStr;
use std::string::ToString;
use crate::test_utils::*;
#[test]
fn test_to_string() {
assert_eq!(Config::MailServer.to_string(), "mail_server");
assert_eq!(Config::from_str("mail_server"), Ok(Config::MailServer));
assert_eq!(Config::SysConfigKeys.to_string(), "sys.config_keys");
fn test_string_conversions() {
assert_eq!(ConfigKey::MailServer.to_key_string(), "mail_server");
assert_eq!(
Config::from_str("sys.config_keys"),
Ok(Config::SysConfigKeys)
ConfigKey::from_key_str("mail_server"),
Ok(ConfigKey::MailServer)
);
assert_eq!(ConfigKey::SysConfigKeys.to_key_string(), "sys.config_keys");
assert_eq!(
ConfigKey::from_key_str("sys.config_keys"),
Ok(ConfigKey::SysConfigKeys)
);
}
#[test]
fn test_default_prop() {
assert_eq!(Config::ImapFolder.get_str("default"), Some("INBOX"));
fn test_sys_config_keys() {
let t = dummy_context();
if let Some(ConfigItem::SysConfigKeys(s)) = t.ctx.get_config_item(ConfigKey::SysConfigKeys)
{
assert!(s.contains(" sys.msgsize_max_recommended "));
assert!(s.contains(" sys.version "));
assert!(s.contains(" imap_folder "));
}
}
#[test]
fn test_config_item() {
let t = test_context(Some(Box::new(logging_cb)));
// An item which has a default.
let opt = t.ctx.get_config_item(ConfigKey::ImapFolder);
assert_eq!(opt, Some(ConfigItem::ImapFolder("INBOX".into())));
// Set a different value.
t.ctx
.set_config_item(ConfigItem::ImapFolder("DeltaChat".into()))
.unwrap();
let opt = t.ctx.get_config_item(ConfigKey::ImapFolder);
assert_eq!(opt, Some(ConfigItem::ImapFolder("DeltaChat".into())));
// Set another value, testing update.
t.ctx
.set_config_item(ConfigItem::ImapFolder("Chat".into()))
.unwrap();
let opt = t.ctx.get_config_item(ConfigKey::ImapFolder);
assert_eq!(opt, Some(ConfigItem::ImapFolder("Chat".into())));
// Reset to the default.
t.ctx.del_config_item(ConfigKey::ImapFolder).unwrap();
let opt = t.ctx.get_config_item(ConfigKey::ImapFolder);
assert_eq!(opt, Some(ConfigItem::ImapFolder("INBOX".into())));
// An item without default.
let opt = t.ctx.get_config_item(ConfigKey::Addr);
assert!(opt.is_none());
// Set the item.
t.ctx
.set_config_item(ConfigItem::Addr("me@example.com".into()))
.unwrap();
let opt = t.ctx.get_config_item(ConfigKey::Addr);
assert_eq!(opt, Some(ConfigItem::Addr("me@example.com".into())));
// Delete the item.
t.ctx.del_config_item(ConfigKey::Addr).unwrap();
let opt = t.ctx.get_config_item(ConfigKey::Addr);
assert!(opt.is_none());
}
#[test]
@@ -225,4 +678,48 @@ mod tests {
assert_eq!(avatar_cfg, avatar_src.to_str().map(|s| s.to_string()));
Ok(())
}
#[test]
fn test_config_item_bool() {
let t = test_context(Some(Box::new(logging_cb)));
// Backwards compatible value.
t.ctx
.sql
.execute(
"INSERT INTO config (keyname, value) VALUES (?, ?)",
params![ConfigKey::InboxWatch.to_string(), "0"],
)
.unwrap();
let item = t.ctx.get_config_item(ConfigKey::InboxWatch).unwrap();
assert_eq!(item, ConfigItem::InboxWatch(false));
t.ctx
.sql
.execute(
"UPDATE config SET value=? WHERE keyname=?",
params!["1", ConfigKey::InboxWatch.to_string()],
)
.unwrap();
let item = t.ctx.get_config_item(ConfigKey::InboxWatch).unwrap();
assert_eq!(item, ConfigItem::InboxWatch(true));
t.ctx
.sql
.execute(
"UPDATE config SET value=? WHERE keyname=?",
params!["bad", ConfigKey::InboxWatch.to_string()],
)
.unwrap();
let item = t.ctx.get_config_item(ConfigKey::InboxWatch);
assert!(item.is_none());
// Normal value.
t.ctx.set_config_item(ConfigItem::InboxWatch(true)).unwrap();
let item = t.ctx.get_config_item(ConfigKey::InboxWatch).unwrap();
assert_eq!(item, ConfigItem::InboxWatch(true));
t.ctx
.set_config_item(ConfigItem::InboxWatch(false))
.unwrap();
let item = t.ctx.get_config_item(ConfigKey::InboxWatch).unwrap();
assert_eq!(item, ConfigItem::InboxWatch(false));
}
}

View File

@@ -1,43 +1,17 @@
//! # Thunderbird's Autoconfiguration implementation
//!
//! Documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
//! Thunderbird's Autoconfiguration implementation
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_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)]
use super::read_autoconf_file;
/* ******************************************************************************
* Thunderbird's Autoconfigure
******************************************************************************/
/* documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
struct MozAutoconfigure<'a> {
pub in_emailaddr: &'a str,
pub in_emaildomain: &'a str,
@@ -49,14 +23,13 @@ struct MozAutoconfigure<'a> {
pub tag_config: MozConfigTag,
}
#[derive(Debug, PartialEq)]
#[derive(PartialEq)]
enum MozServer {
Undefined,
Imap,
Smtp,
}
#[derive(Debug)]
enum MozConfigTag {
Undefined,
Hostname,
@@ -65,14 +38,15 @@ enum MozConfigTag {
Username,
}
fn parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam> {
pub fn moz_parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Error> {
let mut reader = quick_xml::Reader::from_str(xml_raw);
reader.trim_text(true);
// Split address into local part and domain part.
let p = in_emailaddr
.find('@')
.ok_or_else(|| Error::InvalidEmailAddress(in_emailaddr.to_string()))?;
let p = match in_emailaddr.find('@') {
Some(i) => i,
None => bail!("Email address {} does not contain @", in_emailaddr),
};
let (in_emaillocalpart, in_emaildomain) = in_emailaddr.split_at(p);
let in_emaildomain = &in_emaildomain[1..];
@@ -89,22 +63,22 @@ fn parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam> {
let mut buf = Vec::new();
loop {
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) => {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
moz_autoconfigure_starttag_cb(e, &mut moz_ac, &reader)
}
quick_xml::events::Event::End(ref e) => moz_autoconfigure_endtag_cb(e, &mut moz_ac),
quick_xml::events::Event::Text(ref e) => {
Ok(quick_xml::events::Event::End(ref e)) => moz_autoconfigure_endtag_cb(e, &mut moz_ac),
Ok(quick_xml::events::Event::Text(ref e)) => {
moz_autoconfigure_text_cb(e, &mut moz_ac, &reader)
}
quick_xml::events::Event::Eof => break,
Err(e) => {
bail!(
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
);
}
Ok(quick_xml::events::Event::Eof) => break,
_ => (),
}
buf.clear();
@@ -115,27 +89,27 @@ fn parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam> {
|| moz_ac.out.send_server.is_empty()
|| moz_ac.out.send_port == 0
{
Err(Error::IncompleteAutoconfig(moz_ac.out))
} else {
Ok(moz_ac.out)
let r = moz_ac.out.to_string();
bail!("Bad or incomplete autoconfig: {}", r,);
}
Ok(moz_ac.out)
}
pub fn moz_autoconfigure(
context: &Context,
url: &str,
param_in: &LoginParam,
) -> Result<LoginParam> {
let xml_raw = read_url(context, url)?;
) -> Option<LoginParam> {
let xml_raw = read_autoconf_file(context, url)?;
let res = parse_xml(&param_in.addr, &xml_raw);
if let Err(err) = &res {
warn!(
context,
"Failed to parse Thunderbird autoconfiguration XML: {}", err
);
match moz_parse_xml(&param_in.addr, &xml_raw) {
Err(err) => {
warn!(context, "{}", err);
None
}
Ok(lp) => Some(lp),
}
res
}
fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
@@ -340,7 +314,7 @@ mod tests {
</loginPageInfo>
</webMail>
</clientConfig>";
let res = parse_xml("example@outlook.com", xml_raw).expect("XML parsing failed");
let res = moz_parse_xml("example@outlook.com", xml_raw).expect("XML parsing failed");
assert_eq!(res.mail_server, "outlook.office365.com");
assert_eq!(res.mail_port, 993);
assert_eq!(res.send_server, "smtp.office365.com");

View File

@@ -1,41 +1,14 @@
//! 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_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)
}
}
use super::read_autoconf_file;
/// Outlook's Autodiscover
struct OutlookAutodiscover {
pub out: LoginParam,
pub out_imap_set: bool,
@@ -52,7 +25,7 @@ enum ParsingResult {
RedirectUrl(String),
}
fn parse_xml(xml_raw: &str) -> Result<ParsingResult> {
fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
let mut outlk_ad = OutlookAutodiscover {
out: LoginParam::new(),
out_imap_set: false,
@@ -72,15 +45,8 @@ fn parse_xml(xml_raw: &str) -> Result<ParsingResult> {
let mut current_tag: Option<String> = None;
loop {
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) => {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
let tag = String::from_utf8_lossy(e.name()).trim().to_lowercase();
if tag == "protocol" {
@@ -95,11 +61,11 @@ fn parse_xml(xml_raw: &str) -> Result<ParsingResult> {
current_tag = Some(tag);
}
}
quick_xml::events::Event::End(ref e) => {
Ok(quick_xml::events::Event::End(ref e)) => {
outlk_autodiscover_endtag_cb(e, &mut outlk_ad);
current_tag = None;
}
quick_xml::events::Event::Text(ref e) => {
Ok(quick_xml::events::Event::Text(ref e)) => {
let val = e.unescape_and_decode(&reader).unwrap_or_default();
if let Some(ref tag) = current_tag {
@@ -115,14 +81,21 @@ fn parse_xml(xml_raw: &str) -> Result<ParsingResult> {
};
}
}
quick_xml::events::Event::Eof => break,
Err(e) => {
bail!(
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
);
}
Ok(quick_xml::events::Event::Eof) => break,
_ => (),
}
buf.clear();
}
// XML redirect via redirecturl
let res = if outlk_ad.config_redirecturl.is_none()
if outlk_ad.config_redirecturl.is_none()
|| outlk_ad.config_redirecturl.as_ref().unwrap().is_empty()
{
if outlk_ad.out.mail_server.is_empty()
@@ -130,34 +103,41 @@ fn parse_xml(xml_raw: &str) -> Result<ParsingResult> {
|| outlk_ad.out.send_server.is_empty()
|| outlk_ad.out.send_port == 0
{
return Err(Error::IncompleteAutoconfig(outlk_ad.out));
let r = outlk_ad.out.to_string();
bail!("Bad or incomplete autoconfig: {}", r,);
}
ParsingResult::LoginParam(outlk_ad.out)
Ok(ParsingResult::LoginParam(outlk_ad.out))
} else {
ParsingResult::RedirectUrl(outlk_ad.config_redirecturl.unwrap())
};
Ok(res)
Ok(ParsingResult::RedirectUrl(
outlk_ad.config_redirecturl.unwrap(),
))
}
}
pub fn outlk_autodiscover(
context: &Context,
url: &str,
_param_in: &LoginParam,
) -> Result<LoginParam> {
) -> Option<LoginParam> {
let mut url = url.to_string();
/* Follow up to 10 xml-redirects (http-redirects are followed in read_url() */
/* Follow up to 10 xml-redirects (http-redirects are followed in read_autoconf_file() */
for _i in 0..10 {
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),
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;
}
}
Err(Error::RedirectionError)
None
}
fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut OutlookAutodiscover) {
@@ -199,7 +179,7 @@ mod tests {
#[test]
fn test_parse_redirect() {
let res = parse_xml("
let res = outlk_parse_xml("
<?xml version=\"1.0\" encoding=\"utf-8\"?>
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
<Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">
@@ -226,7 +206,7 @@ mod tests {
#[test]
fn test_parse_loginparam() {
let res = parse_xml(
let res = outlk_parse_xml(
"\
<?xml version=\"1.0\" encoding=\"utf-8\"?>
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">

View File

@@ -1,9 +1,5 @@
//! Email accounts autoconfiguration process module
mod auto_mozilla;
mod auto_outlook;
mod read_url;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use crate::config::Config;
@@ -16,8 +12,10 @@ use crate::login_param::LoginParam;
use crate::oauth2::*;
use crate::param::Params;
use auto_mozilla::moz_autoconfigure;
mod auto_outlook;
use auto_outlook::outlk_autodiscover;
mod auto_mozilla;
use auto_mozilla::moz_autoconfigure;
macro_rules! progress {
($context:tt, $progress:expr) => {
@@ -47,8 +45,9 @@ 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 JobConfigureImap(context: &Context) {
pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
if !context.sql.is_open() {
error!(context, "Cannot configure, database not opened.",);
progress!(context, 0);
@@ -168,7 +167,7 @@ pub fn JobConfigureImap(context: &Context) {
"https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
param_domain, param_addr_urlencoded
);
param_autoconfig = moz_autoconfigure(context, &url, &param).ok();
param_autoconfig = moz_autoconfigure(context, &url, &param);
}
true
}
@@ -180,7 +179,7 @@ pub fn JobConfigureImap(context: &Context) {
"https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}",
param_domain, param_addr_urlencoded
);
param_autoconfig = moz_autoconfigure(context, &url, &param).ok();
param_autoconfig = moz_autoconfigure(context, &url, &param);
}
true
}
@@ -193,7 +192,7 @@ pub fn JobConfigureImap(context: &Context) {
"https://{}{}/autodiscover/autodiscover.xml",
"", param_domain
);
param_autoconfig = outlk_autodiscover(context, &url, &param).ok();
param_autoconfig = outlk_autodiscover(context, &url, &param);
}
true
}
@@ -204,7 +203,7 @@ pub fn JobConfigureImap(context: &Context) {
"https://{}{}/autodiscover/autodiscover.xml",
"autodiscover.", param_domain
);
param_autoconfig = outlk_autodiscover(context, &url, &param).ok();
param_autoconfig = outlk_autodiscover(context, &url, &param);
}
true
}
@@ -216,7 +215,7 @@ pub fn JobConfigureImap(context: &Context) {
"http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
param_domain, param_addr_urlencoded
);
param_autoconfig = moz_autoconfigure(context, &url, &param).ok();
param_autoconfig = moz_autoconfigure(context, &url, &param);
}
true
}
@@ -228,7 +227,7 @@ pub fn JobConfigureImap(context: &Context) {
"http://{}/.well-known/autoconfig/mail/config-v1.1.xml",
param_domain
);
param_autoconfig = moz_autoconfigure(context, &url, &param).ok();
param_autoconfig = moz_autoconfigure(context, &url, &param);
}
true
}
@@ -238,7 +237,7 @@ pub fn JobConfigureImap(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, &param).ok();
param_autoconfig = moz_autoconfigure(context, &url, &param);
}
true
}
@@ -355,21 +354,20 @@ pub fn JobConfigureImap(context: &Context) {
}
16 => {
progress!(context, 900);
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
let flags: libc::c_int = if context.get_config_bool(Config::MvboxWatch)
|| context.get_config_bool(Config::MvboxMove)
{
DC_CREATE_MVBOX as i32
} else {
let res = imap.select_with_uidvalidity(context, "INBOX");
if let Err(err) = res {
error!(context, "could not read INBOX status: {:?}", err);
false
} else {
true
}
}
0
};
context
.inbox_thread
.read()
.unwrap()
.imap
.configure_folders(context, flags);
true
}
17 => {
progress!(context, 910);
@@ -391,10 +389,11 @@ pub fn JobConfigureImap(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, "key generation completed");
info!(context, "Configure completed.");
progress!(context, 940);
break; // We are done here
}
_ => {
error!(context, "Internal error: step counter out of bound",);
break;
@@ -417,6 +416,24 @@ pub fn JobConfigureImap(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.
@@ -564,11 +581,32 @@ 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 crate::config::*;
use crate::configure::JobConfigureImap;
use crate::configure::dc_job_do_DC_JOB_CONFIGURE_IMAP;
use crate::test_utils::*;
#[test]
@@ -578,6 +616,6 @@ mod tests {
.set_config(Config::Addr, Some("probably@unexistant.addr"))
.unwrap();
t.ctx.set_config(Config::MailPw, Some("123456")).unwrap();
JobConfigureImap(&t.ctx);
dc_job_do_DC_JOB_CONFIGURE_IMAP(&t.ctx);
}
}

View File

@@ -1,26 +0,0 @@
use crate::context::Context;
#[derive(Debug, Fail)]
pub enum Error {
#[fail(display = "URL request error")]
GetError(#[cause] reqwest::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
pub fn read_url(context: &Context, url: &str) -> Result<String> {
info!(context, "Requesting URL {}", url);
match reqwest::Client::new()
.get(url)
.send()
.and_then(|mut res| res.text())
{
Ok(res) => Ok(res),
Err(err) => {
info!(context, "Can\'t read URL {}", url);
Err(Error::GetError(err))
}
}
}

View File

@@ -8,6 +8,21 @@ 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;
@@ -119,6 +134,8 @@ 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;
// Flags for empty server job
pub const DC_EMPTY_MVBOX: u32 = 0x01;

View File

@@ -23,17 +23,15 @@ 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.
@@ -108,6 +106,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
@@ -138,7 +141,7 @@ pub enum VerifiedStatus {
}
impl Contact {
pub fn load_from_db(context: &Context, contact_id: u32) -> crate::sql::Result<Self> {
pub fn load_from_db(context: &Context, contact_id: u32) -> Result<Self> {
if contact_id == DC_CONTACT_ID_SELF {
let contact = Contact {
id: contact_id,
@@ -205,7 +208,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.
@@ -235,7 +238,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) {
@@ -269,7 +272,7 @@ impl Contact {
.get_config(Config::ConfiguredAddr)
.unwrap_or_default();
if addr_cmp(addr_normalized, addr_self) {
if addr_normalized == addr_self {
return DC_CONTACT_ID_SELF;
}
@@ -306,7 +309,7 @@ impl Contact {
.get_config(Config::ConfiguredAddr)
.unwrap_or_default();
if addr_cmp(addr, addr_self) {
if addr == addr_self {
return Ok((DC_CONTACT_ID_SELF, sth_modified));
}
@@ -425,7 +428,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`.
///
@@ -582,12 +585,7 @@ 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(PeerstateVerifiedStatus::Unverified))
.is_some()
{
if peerstate.is_some() && peerstate.as_ref().and_then(|p| p.peek_key(0)).is_some() {
let peerstate = peerstate.as_ref().unwrap();
let p =
context.stock_str(if peerstate.prefer_encrypt == EncryptPreference::Mutual {
@@ -607,25 +605,25 @@ impl Contact {
.map(|k| k.formatted_fingerprint())
.unwrap_or_default();
let fingerprint_other_verified = peerstate
.peek_key(PeerstateVerifiedStatus::BidirectVerified)
.peek_key(2)
.map(|k| k.formatted_fingerprint())
.unwrap_or_default();
let fingerprint_other_unverified = peerstate
.peek_key(PeerstateVerifiedStatus::Unverified)
.peek_key(0)
.map(|k| k.formatted_fingerprint())
.unwrap_or_default();
if loginparam.addr < peerstate.addr {
if peerstate.addr.is_some() && &loginparam.addr < peerstate.addr.as_ref().unwrap() {
cat_fingerprint(&mut ret, &loginparam.addr, &fingerprint_self, "");
cat_fingerprint(
&mut ret,
peerstate.addr.clone(),
peerstate.addr.as_ref().unwrap(),
&fingerprint_other_verified,
&fingerprint_other_unverified,
);
} else {
cat_fingerprint(
&mut ret,
peerstate.addr.clone(),
peerstate.addr.as_ref().unwrap(),
&fingerprint_other_verified,
&fingerprint_other_unverified,
);
@@ -688,7 +686,7 @@ impl Contact {
}
Err(err) => {
error!(context, "delete_contact {} failed ({})", contact_id, err);
return Err(err.into());
return Err(err);
}
}
}
@@ -706,7 +704,7 @@ 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> {
Ok(Contact::load_from_db(context, contact_id)?)
Contact::load_from_db(context, contact_id)
}
/// Get the ID of the contact.
@@ -903,7 +901,7 @@ impl Contact {
}
/// Extracts first name from full name.
fn get_first_name(full_name: &str) -> &str {
fn get_first_name<'a>(full_name: &'a str) -> &'a str {
full_name.splitn(2, ' ').next().unwrap_or_default()
}
@@ -930,21 +928,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
&& sql::execute(
if contact.blocked != new_blocking {
if 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=?);",
@@ -953,6 +951,7 @@ fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
Contact::mark_noticed(context, contact_id);
context.call_cb(Event::ContactsChanged(None));
}
}
}
}
}
@@ -1022,16 +1021,17 @@ fn cat_fingerprint(
}
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();
let norm1 = addr_normalize(addr1.as_ref());
let norm2 = addr_normalize(addr2.as_ref());
norm1 == norm2
}
pub fn addr_equals_self(context: &Context, addr: impl AsRef<str>) -> bool {
if !addr.as_ref().is_empty() {
let normalized_addr = addr_normalize(addr.as_ref());
if let Some(self_addr) = context.get_config(Config::ConfiguredAddr) {
return addr_cmp(addr, self_addr);
return normalized_addr == self_addr;
}
}
false
@@ -1083,10 +1083,6 @@ 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]
@@ -1208,11 +1204,4 @@ 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"));
}
}

View File

@@ -1,5 +1,3 @@
//! Contacts module
use std::collections::HashMap;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
@@ -8,7 +6,7 @@ use std::sync::{Arc, Condvar, Mutex, RwLock};
use libc::uintptr_t;
use crate::chat::*;
use crate::config::Config;
use crate::config::{Config, ConfigItem, ConfigKey};
use crate::constants::*;
use crate::contact::*;
use crate::error::*;
@@ -21,7 +19,7 @@ use crate::login_param::LoginParam;
use crate::lot::Lot;
use crate::message::{self, Message, MsgId};
use crate::param::Params;
use crate::smtp::Smtp;
use crate::smtp::*;
use crate::sql::Sql;
/// Callback function type for [Context]
@@ -88,7 +86,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(),
);
@@ -271,7 +269,6 @@ impl Context {
"<Not yet calculated>".into()
};
let inbox_watch = self.get_config_int(Config::InboxWatch);
let sentbox_watch = self.get_config_int(Config::SentboxWatch);
let mvbox_watch = self.get_config_int(Config::MvboxWatch);
let mvbox_move = self.get_config_int(Config::MvboxMove);
@@ -301,7 +298,9 @@ impl Context {
res.insert("is_configured", is_configured.to_string());
res.insert("entered_account_settings", l.to_string());
res.insert("used_account_settings", l2.to_string());
res.insert("inbox_watch", inbox_watch.to_string());
if let Some(ConfigItem::InboxWatch(val)) = self.get_config_item(ConfigKey::InboxWatch) {
res.insert("inbox_watch", val.to_string());
}
res.insert("sentbox_watch", sentbox_watch.to_string());
res.insert("mvbox_watch", mvbox_watch.to_string());
res.insert("mvbox_move", mvbox_move.to_string());
@@ -437,9 +436,10 @@ impl Context {
return;
}
if self.is_mvbox(folder) {
if !self.is_inbox(folder) && !self.is_sentbox(folder) {
return;
}
if let Ok(msg) = Message::load_from_db(self, msg_id) {
if msg.is_setupmessage() {
// do not move setup messages;
@@ -447,6 +447,10 @@ 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(
@@ -456,6 +460,7 @@ impl Context {
Params::new(),
0,
);
message::update_msg_move_state(self, &msg.rfc724_mid, MoveState::Moving);
}
}
}
@@ -491,25 +496,12 @@ 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: PerformJobsNeeded,
pub perform_jobs_needed: i32,
pub probe_network: bool,
}

View File

@@ -1,7 +1,3 @@
//! 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};
@@ -23,18 +19,22 @@ enum AddText {
YesPreserveLineEnds,
}
// 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();
// 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();
}
let mut dehtml = Dehtml {
strbuilder: String::with_capacity(buf.len()),
strbuilder: String::with_capacity(buf_terminated.len()),
add_text: AddText::YesRemoveLineEnds,
last_href: None,
};
let mut reader = quick_xml::Reader::from_str(buf);
let mut reader = quick_xml::Reader::from_str(buf_terminated);
let mut buf = Vec::new();
@@ -171,7 +171,7 @@ mod tests {
use super::*;
#[test]
fn test_dehtml() {
fn test_dc_dehtml() {
let cases = vec![
(
"<a href='https://example.com'> Foo </a>",
@@ -186,7 +186,7 @@ mod tests {
("", ""),
];
for (input, output) in cases {
assert_eq!(dehtml(input), output);
assert_eq!(dc_dehtml(input), output);
}
}
}

1312
src/dc_mimeparser.rs Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
use crate::dehtml::*;
use crate::dc_dehtml::*;
#[derive(Copy, Clone)]
pub struct Simplify {
@@ -34,7 +34,7 @@ impl Simplify {
/// 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 {
dehtml(input)
dc_dehtml(input)
} else {
input.to_string()
};
@@ -49,7 +49,7 @@ impl Simplify {
/**
* Simplify Plain Text
*/
#[allow(non_snake_case, clippy::mut_range_bound)]
#[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)

251
src/dc_strencode.rs Normal file
View File

@@ -0,0 +1,251 @@
use itertools::Itertools;
use std::borrow::Cow;
use std::ffi::CString;
use std::ptr;
use charset::Charset;
use libc::free;
use mmime::mailmime::decode::mailmime_encoded_phrase_parse;
use mmime::other::*;
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
use crate::dc_tools::*;
/**
* Encode non-ascii-strings as `=?UTF-8?Q?Bj=c3=b6rn_Petersen?=`.
* Belongs to RFC 2047: https://tools.ietf.org/html/rfc2047
*
* We do not fold at position 72; this would result in empty words as `=?utf-8?Q??=` which are correct,
* but cannot be displayed by some mail programs (eg. Android Stock Mail).
* however, this is not needed, as long as _one_ word is not longer than 72 characters.
* _if_ it is, the display may get weird. This affects the subject only.
* the best solution wor all this would be if libetpan encodes the line as only libetpan knowns when a header line is full.
*
* @param to_encode Null-terminated UTF-8-string to encode.
* @return Returns the encoded string which must be free()'d when no longed needed.
* On errors, NULL is returned.
*/
pub fn dc_encode_header_words(input: impl AsRef<str>) -> String {
let mut result = String::default();
for (_, group) in &input.as_ref().chars().group_by(|c| c.is_whitespace()) {
let word: String = group.collect();
result.push_str(&quote_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);
}
}
}

View File

@@ -3,22 +3,54 @@
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};
use std::{fmt, fs, ptr};
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: i32) -> bool {
pub(crate) fn dc_exactly_one_bit_set(v: libc::c_int) -> 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> {
@@ -42,12 +74,36 @@ pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Co
}
}
/// the colors must fulfill some criterions as:
/// - 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 :)
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 :) */
const COLORS: [u32; 16] = [
0xe56555, 0xf28c48, 0x8e85ee, 0x76c84d, 0x5bb6cc, 0x549cdd, 0xd25c99, 0xb37800, 0xf23030,
0x39b249, 0xbb243b, 0x964078, 0x66874f, 0x308ab9, 0x127ed0, 0xbe450c,
@@ -66,6 +122,33 @@ pub(crate) fn dc_str_to_color(s: impl AsRef<str>) -> u32 {
COLORS[color_index]
}
/* 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
******************************************************************************/
@@ -217,11 +300,8 @@ pub(crate) fn dc_create_outgoing_rfc724_mid(grpid: Option<&str>, from_addr: &str
///
/// # Arguments
///
/// * `mid` - A string that holds the message id. Leading/Trailing <>
/// characters are automatically stripped.
/// * `mid` - A string that holds the message id
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;
}
@@ -238,6 +318,29 @@ 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 {
@@ -278,9 +381,11 @@ 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> {
Path::new(path_filename.as_ref())
.extension()
.map(|p| p.to_string_lossy().to_lowercase())
if let Some(p) = Path::new(path_filename.as_ref()).extension() {
Some(p.to_string_lossy().to_lowercase())
} else {
None
}
}
/// Returns the `(width, height)` of the given image buffer.
@@ -418,13 +523,9 @@ pub(crate) fn dc_create_folder(context: &Context, path: impl AsRef<std::path::Pa
}
/// Write a the given content to provied file path.
pub(crate) fn dc_write_file(
context: &Context,
path: impl AsRef<Path>,
buf: &[u8],
) -> Result<(), std::io::Error> {
pub(crate) fn dc_write_file(context: &Context, path: impl AsRef<Path>, buf: &[u8]) -> bool {
let path_abs = dc_get_abs_path(context, &path);
fs::write(&path_abs, buf).map_err(|err| {
if let Err(err) = fs::write(&path_abs, buf) {
warn!(
context,
"Cannot write {} bytes to \"{}\": {}",
@@ -432,8 +533,10 @@ pub(crate) fn dc_write_file(
path.as_ref().display(),
err
);
err
})
false
} else {
true
}
}
pub fn dc_read_file<P: AsRef<std::path::Path>>(
@@ -496,6 +599,212 @@ 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)
@@ -573,14 +882,71 @@ 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));
@@ -641,6 +1007,44 @@ mod tests {
);
}
/* calls free() for each item content */
unsafe fn clist_free_content(haystack: *const clist) {
let mut iter = (*haystack).first;
while !iter.is_null() {
free((*iter).data);
(*iter).data = ptr::null_mut();
iter = if !iter.is_null() {
(*iter).next
} else {
ptr::null_mut()
}
}
}
#[test]
fn test_dc_str_to_clist_1() {
unsafe {
let list = dc_str_to_clist("", " ");
assert_eq!((*list).count, 1);
clist_free_content(list);
clist_free(list);
}
}
#[test]
fn test_dc_str_to_clist_4() {
unsafe {
let list: *mut clist = dc_str_to_clist("foo bar test", " ");
assert_eq!((*list).count, 3);
let str = dc_str_from_clist(list, " ");
assert_eq!(str, "foo bar test");
clist_free_content(list);
clist_free(list);
}
}
#[test]
fn test_dc_create_id() {
let buf = dc_create_id();
@@ -668,6 +1072,113 @@ 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
@@ -689,16 +1200,6 @@ 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]
@@ -782,9 +1283,9 @@ mod tests {
#[test]
fn test_dc_create_incoming_rfc724_mid() {
let res = dc_create_incoming_rfc724_mid(123, 45, &[6, 7]);
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, &[]);
let res = dc_create_incoming_rfc724_mid(123, 45, &vec![]);
assert_eq!(res, Some("123-45-0@stub".into()));
}
@@ -822,7 +1323,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").is_ok());
assert!(dc_write_file(context, "$BLOBDIR/foobar", b"content"));
assert!(dc_file_exist(context, "$BLOBDIR/foobar",));
assert!(!dc_file_exist(context, "$BLOBDIR/foobarx"));
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/foobar"), 7);
@@ -854,7 +1355,7 @@ mod tests {
assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder"));
let fn0 = "$BLOBDIR/data.data";
assert!(dc_write_file(context, &fn0, b"content").is_ok());
assert!(dc_write_file(context, &fn0, b"content"));
assert!(dc_delete_file(context, &fn0));
assert!(!dc_file_exist(context, &fn0));
@@ -863,15 +1364,15 @@ mod tests {
#[test]
fn test_listflags_has() {
let listflags: u32 = 0x1101;
assert!(listflags_has(listflags, 0x1));
assert!(!listflags_has(listflags, 0x10));
assert!(listflags_has(listflags, 0x100));
assert!(listflags_has(listflags, 0x1000));
assert!(listflags_has(listflags, 0x1) == true);
assert!(listflags_has(listflags, 0x10) == false);
assert!(listflags_has(listflags, 0x100) == true);
assert!(listflags_has(listflags, 0x1000) == true);
let listflags: u32 = (DC_GCL_ADD_SELF | DC_GCL_VERIFIED_ONLY).try_into().unwrap();
assert!(listflags_has(listflags, DC_GCL_VERIFIED_ONLY));
assert!(listflags_has(listflags, DC_GCL_ADD_SELF));
assert!(listflags_has(listflags, DC_GCL_VERIFIED_ONLY) == true);
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == true);
let listflags: u32 = DC_GCL_VERIFIED_ONLY.try_into().unwrap();
assert!(!listflags_has(listflags, DC_GCL_ADD_SELF));
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == false);
}
#[test]

View File

@@ -1,20 +1,41 @@
//! End-to-end encryption support.
use std::collections::HashSet;
use std::ptr;
use std::str::FromStr;
use mailparse::MailHeaderMap;
use libc::strlen;
use mmime::clist::*;
use mmime::mailimf::types::*;
use mmime::mailimf::types_helper::*;
use mmime::mailimf::*;
use mmime::mailmime::content::*;
use mmime::mailmime::types::*;
use mmime::mailmime::types_helper::*;
use mmime::mailmime::write_mem::*;
use mmime::mailmime::*;
use mmime::mailprivacy_prepare_mime;
use mmime::mmapstring::*;
use mmime::{mailmime_substitute, MAILIMF_NO_ERROR, MAIL_NO_ERROR};
use num_traits::FromPrimitive;
use 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 {
@@ -50,27 +71,39 @@ impl EncryptHelper {
Aheader::new(addr, pk, self.prefer_encrypt)
}
/// Determines if we can and should encrypt.
pub fn should_encrypt(
&self,
context: &Context,
pub fn try_encrypt(
&mut self,
factory: &mut MimeFactory,
e2ee_guaranteed: bool,
peerstates: &[(Option<Peerstate>, &str)],
min_verified: libc::c_int,
do_gossip: bool,
mut in_out_message: *mut Mailmime,
imffields_unprotected: *mut mailimf_fields,
) -> Result<bool> {
// 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);
}
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);
}
}
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,
None => {
let msg = format!("peerstate for {:?} missing, cannot encrypt", addr);
let msg = format!("peerstate for {} missing, cannot encrypt", recipient_addr);
if e2ee_guaranteed {
return Err(format_err!("{}", msg));
} else {
@@ -78,59 +111,169 @@ 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
);
}
}
Ok(true)
}
let sign_key = {
keyring.add_ref(&self.public_key);
let key = Key::from_self_private(context, self.addr.clone(), &context.sql);
ensure!(key.is_some(), "no own private key found");
/// Tries to encrypt the passed in `mail`.
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();
key
};
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);
// 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)
}
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,
mail: &mailparse::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();
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)?;
let mut peerstate = None;
let autocryptheader = Aheader::from_headers(context, &from, &mail.headers);
let autocryptheader = Aheader::from_imffields(&from, imffields);
if message_time > 0 {
peerstate = Peerstate::from_addr(context, &context.sql, &from);
@@ -139,7 +282,9 @@ 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(mail) {
} else if message_time > peerstate.last_seen_autocrypt
&& !contains_report(in_out_message)
{
peerstate.degrade_encryption(message_time);
peerstate.save_to_db(&context.sql, false)?;
}
@@ -149,12 +294,13 @@ 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 out_mail = None;
let mut encrypted = false;
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 {
@@ -174,16 +320,60 @@ pub fn try_decrypt(
}
}
out_mail = decrypt_if_autocrypt_message(
let mut gossip_headers = ptr::null_mut();
encrypted = decrypt_if_autocrypt_message(
context,
mail,
in_out_message,
&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((out_mail, signatures))
Ok((encrypted, signatures, gossipped_addr))
}
fn new_data_part(
data: *mut libc::c_void,
data_bytes: libc::size_t,
content_type: &str,
default_encoding: u32,
) -> Result<*mut Mailmime> {
let content = new_content_type(&content_type)?;
let mut encoding = ptr::null_mut();
if wrapmime::content_type_needs_encoding(content) {
encoding = unsafe { mailmime_mechanism_new(default_encoding as i32, ptr::null_mut()) };
ensure!(!encoding.is_null(), "failed to create encoding");
}
let mime_fields = {
unsafe {
mailmime_fields_new_with_data(
encoding,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
)
}
};
ensure!(!mime_fields.is_null(), "internal mime error");
let mime = unsafe { mailmime_new_empty(content, mime_fields) };
ensure!(!mime.is_null(), "internal mime error");
if unsafe { (*mime).mm_type } == MAILMIME_SINGLE as libc::c_int {
if !data.is_null() && data_bytes > 0 {
unsafe { mailmime_set_body_text(mime, data as *mut libc::c_char, data_bytes) };
}
}
Ok(mime)
}
/// Load public key from database or generate a new one.
@@ -210,7 +400,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)) => {
if dc_key_save_self_keypair(
match dc_key_save_self_keypair(
context,
&public_key,
&private_key,
@@ -218,78 +408,219 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
true,
&context.sql,
) {
info!(
context,
"Keypair generated in {:.3}s.",
start.elapsed().as_secs()
);
Ok(public_key)
} else {
Err(format_err!("Failed to save keypair"))
true => {
info!(
context,
"Keypair generated in {:.3}s.",
start.elapsed().as_secs()
);
Ok(public_key)
}
false => Err(format_err!("Failed to save keypair")),
}
}
None => Err(format_err!("Failed to generate keypair")),
}
}
fn decrypt_if_autocrypt_message<'a>(
fn update_gossip_peerstates(
context: &Context,
mail: &mailparse::ParsedMail<'a>,
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();
for cur_data in unsafe { (*(*gossip_headers).fld_list).into_iter() } {
let field = cur_data as *mut mailimf_field;
if field.is_null() {
continue;
}
let field = unsafe { *field };
if field.fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
let optional_field = unsafe { field.fld_data.fld_optional_field };
if optional_field.is_null() {
continue;
}
let optional_field = unsafe { *optional_field };
if !optional_field.fld_name.is_null()
&& to_string_lossy(optional_field.fld_name) == "Autocrypt-Gossip"
{
let value = to_string_lossy(optional_field.fld_value);
let gossip_header = Aheader::from_str(&value);
if let Ok(ref header) = gossip_header {
if recipients.is_none() {
recipients = Some(mailimf_get_recipients(imffields));
}
if recipients.as_ref().unwrap().contains(&header.addr) {
let mut peerstate =
Peerstate::from_addr(context, &context.sql, &header.addr);
if let Some(ref mut peerstate) = peerstate {
peerstate.apply_gossip(header, message_time);
peerstate.save_to_db(&context.sql, false)?;
} else {
let p = Peerstate::from_gossip(context, header, message_time);
p.save_to_db(&context.sql, true)?;
peerstate = Some(p);
}
if let Some(peerstate) = peerstate {
if peerstate.degrade_event.is_some() {
handle_degrade_event(context, &peerstate)?;
}
}
gossipped_addr.insert(header.addr.clone());
} else {
info!(
context,
"Ignoring gossipped \"{}\" as the address is not in To/Cc list.",
&header.addr,
);
}
}
}
}
}
Ok(gossipped_addr)
}
fn decrypt_if_autocrypt_message(
context: &Context,
mime_undetermined: *mut Mailmime,
private_keyring: &Keyring,
public_keyring_for_validate: &Keyring,
ret_valid_signatures: &mut HashSet<String>,
) -> 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.
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.
let encrypted_data_part = match wrapmime::get_autocrypt_mime(mail) {
Errors are returned for failures related to decryption of AC-messages.
*/
ensure!(!mime_undetermined.is_null(), "Invalid mime reference");
let (mime, encrypted_data_part) = match wrapmime::get_autocrypt_mime(mime_undetermined) {
Err(_) => {
// not an autocrypt mime message, abort and ignore
return Ok(None);
// not a proper autocrypt message, abort and ignore
return Ok(false);
}
Ok(res) => res,
};
info!(context, "Detected Autocrypt-mime message");
decrypt_part(
let decrypted_mime = 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,
mail: &mailparse::ParsedMail<'_>,
mime: *mut Mailmime,
private_keyring: &Keyring,
public_keyring_for_validate: &Keyring,
ret_valid_signatures: &mut HashSet<String>,
) -> Result<Option<Vec<u8>>> {
let data = mail.get_body_raw()?;
) -> 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();
if has_decrypted_pgp_armor(&data) {
// we should only have one decryption happening
ensure!(ret_valid_signatures.is_empty(), "corrupt signatures");
let plain = pgp::pk_decrypt(
let plain = match 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;
ensure!(!ret_valid_signatures.is_empty(), "no valid signatures");
return Ok(Some(plain));
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;
}
}
Ok(None)
Ok(ret_decrypted_mime)
}
fn has_decrypted_pgp_armor(input: &[u8]) -> bool {
@@ -313,8 +644,36 @@ 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(mail: &mailparse::ParsedMail<'_>) -> bool {
mail.ctype.mimetype == "multipart/report"
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
}
/// Ensures a private key exists for the configured user.
@@ -340,6 +699,7 @@ pub fn ensure_secret_key_exists(context: &Context) -> Result<String> {
#[cfg(test)]
mod tests {
use super::*;
use libc::free;
use crate::test_utils::*;
@@ -362,7 +722,7 @@ mod tests {
#[test]
fn test_mailmime_parse() {
let plain = b"Chat-Disposition-Notification-To: hello@world.de
let plain = b"Chat-Disposition-Notification-To: holger@deltachat.de
Chat-Group-ID: CovhGgau8M-
Chat-Group-Name: Delta Chat Dev
Subject: =?utf-8?Q?Chat=3A?= Delta Chat =?utf-8?Q?Dev=3A?= sidenote for
@@ -370,18 +730,35 @@ Subject: =?utf-8?Q?Chat=3A?= Delta Chat =?utf-8?Q?Dev=3A?= sidenote for
Content-Type: text/plain; charset=\"utf-8\"; protected-headers=\"v1\"
Content-Transfer-Encoding: quoted-printable
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
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
-- =20
Sent with my Delta Chat Messenger: https://delta.chat";
let mail = mailparse::parse_mail(plain).expect("failed to parse valid message");
let plain_bytes = plain.len();
let plain_buf = plain.as_ptr() as *const libc::c_char;
assert_eq!(mail.headers.len(), 6);
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")
);
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 _) };
}
mod load_or_generate_self_public_key {

View File

@@ -1,13 +1,19 @@
//! # Error handling
use lettre_email::mime;
use failure::Fail;
#[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 = "SQL error: {:?}", _0)]
SqlError(#[cause] crate::sql::Error),
#[fail(display = "Sqlite: Connection closed")]
SqlNoConnection,
#[fail(display = "Sqlite: Already open")]
SqlAlreadyOpen,
#[fail(display = "Sqlite: Failed to open")]
SqlFailedToOpen,
#[fail(display = "{:?}", _0)]
Io(std::io::Error),
#[fail(display = "{:?}", _0)]
@@ -16,6 +22,8 @@ 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)]
@@ -28,19 +36,31 @@ pub enum Error {
InvalidMsgId,
#[fail(display = "Watch folder not found {:?}", _0)]
WatchFolderNotFound(String),
#[fail(display = "Inalid 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 = "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,
}
pub type Result<T> = std::result::Result<T, Error>;
impl From<crate::sql::Error> for Error {
fn from(err: crate::sql::Error) -> Error {
Error::SqlError(err)
impl From<rusqlite::Error> for Error {
fn from(err: rusqlite::Error) -> Error {
Error::Sql(err)
}
}
@@ -56,6 +76,12 @@ impl From<failure::Error> for Error {
}
}
impl From<r2d2::Error> for Error {
fn from(err: r2d2::Error) -> Error {
Error::ConnectionPool(err)
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Error {
Error::Io(err)
@@ -74,6 +100,12 @@ impl From<image_meta::ImageError> for Error {
}
}
impl From<crate::dc_tools::CStringError> for Error {
fn from(err: crate::dc_tools::CStringError) -> Error {
Error::CStringError(err)
}
}
impl From<pgp::errors::Error> for Error {
fn from(err: pgp::errors::Error) -> Error {
Error::Pgp(err)
@@ -98,24 +130,6 @@ impl From<crate::message::InvalidMsgId> for Error {
}
}
impl From<mailparse::MailParseError> for Error {
fn from(err: mailparse::MailParseError) -> Error {
Error::MailParseError(err)
}
}
impl From<lettre_email::error::Error> for Error {
fn from(err: lettre_email::error::Error) -> Error {
Error::LettreError(err)
}
}
impl From<mime::FromStrError> for Error {
fn from(err: mime::FromStrError) -> Error {
Error::FromStr(err)
}
}
#[macro_export]
macro_rules! bail {
($e:expr) => {

View File

@@ -1,5 +1,3 @@
//! # Events specification
use std::path::PathBuf;
use strum::EnumProperty;
@@ -92,7 +90,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
@@ -245,11 +243,4 @@ 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 },
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,250 +0,0 @@
use super::Imap;
use async_imap::extensions::idle::IdleResponse;
use async_std::prelude::*;
use async_std::task;
use std::sync::atomic::Ordering;
use std::time::{Duration, SystemTime};
use crate::context::Context;
use crate::imap_client::*;
use super::select_folder;
type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Fail)]
pub enum Error {
#[fail(display = "IMAP IDLE protocol failed to init/complete")]
IdleProtocolFailed(#[cause] async_imap::error::Error),
#[fail(display = "IMAP 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");
}
}
}
match handle.done().await {
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");
}
}
}
match handle.done().await {
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()
.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)
}
});
}
}

View File

@@ -1,113 +0,0 @@
use super::Imap;
use crate::context::Context;
type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Fail)]
pub enum Error {
#[fail(display = "IMAP Could not obtain imap-session object.")]
NoSession,
#[fail(display = "IMAP Connection Lost or no connection established")]
ConnectionLost,
#[fail(display = "IMAP Folder name invalid: {:?}", _0)]
BadFolderName(String),
#[fail(display = "IMAP close/expunge failed: {}", _0)]
CloseExpungeFailed(#[cause] async_imap::error::Error),
#[fail(display = "IMAP other error: {:?}", _0)]
Other(String),
}
impl Imap {
/// select a folder, possibly update uid_validity and, if needed,
/// expunge the folder to remove delete-marked messages.
pub(super) async fn select_folder<S: AsRef<str>>(
&self,
context: &Context,
folder: Option<S>,
) -> Result<()> {
if self.session.lock().await.is_none() {
let mut cfg = self.config.write().await;
cfg.selected_folder = None;
cfg.selected_folder_needs_expunge = false;
self.trigger_reconnect();
return Err(Error::NoSession);
}
// if there is a new folder and the new folder is equal to the selected one, there's nothing to do.
// if there is _no_ new folder, we continue as we might want to expunge below.
if let Some(ref folder) = folder {
if let Some(ref selected_folder) = self.config.read().await.selected_folder {
if folder.as_ref() == selected_folder {
return Ok(());
}
}
}
// deselect existing folder, if needed (it's also done implicitly by SELECT, however, without EXPUNGE then)
let needs_expunge = { self.config.read().await.selected_folder_needs_expunge };
if needs_expunge {
if let Some(ref folder) = self.config.read().await.selected_folder {
info!(context, "Expunge messages in \"{}\".", folder);
// A CLOSE-SELECT is considerably faster than an EXPUNGE-SELECT, see
// https://tools.ietf.org/html/rfc3501#section-6.4.2
if let Some(ref mut session) = &mut *self.session.lock().await {
match session.close().await {
Ok(_) => {
info!(context, "close/expunge succeeded");
}
Err(err) => {
self.trigger_reconnect();
return Err(Error::CloseExpungeFailed(err));
}
}
} else {
return Err(Error::NoSession);
}
}
self.config.write().await.selected_folder_needs_expunge = false;
}
// select new folder
if let Some(ref folder) = folder {
if let Some(ref mut session) = &mut *self.session.lock().await {
let res = session.select(folder).await;
// https://tools.ietf.org/html/rfc3501#section-6.3.1
// says that if the server reports select failure we are in
// authenticated (not-select) state.
match res {
Ok(mailbox) => {
let mut config = self.config.write().await;
config.selected_folder = Some(folder.as_ref().to_string());
config.selected_mailbox = Some(mailbox);
Ok(())
}
Err(async_imap::error::Error::ConnectionLost) => {
self.trigger_reconnect();
self.config.write().await.selected_folder = None;
Err(Error::ConnectionLost)
}
Err(async_imap::error::Error::Validate(_)) => {
Err(Error::BadFolderName(folder.as_ref().to_string()))
}
Err(err) => {
self.config.write().await.selected_folder = None;
self.trigger_reconnect();
Err(Error::Other(err.to_string()))
}
}
} else {
Err(Error::NoSession)
}
} else {
Ok(())
}
}
}

View File

@@ -11,6 +11,8 @@ use async_tls::client::TlsStream;
use crate::login_param::{dc_build_tls_config, CertificateChecks};
const DCC_IMAP_DEBUG: &str = "DCC_IMAP_DEBUG";
#[derive(Debug)]
pub(crate) enum Client {
Secure(ImapClient<TlsStream<TcpStream>>),
@@ -40,7 +42,7 @@ impl Client {
let tls_connector: async_tls::TlsConnector = Arc::new(tls_config).into();
let tls_stream = tls_connector.connect(domain.as_ref(), stream)?.await?;
let mut client = ImapClient::new(tls_stream);
if std::env::var(crate::DCC_IMAP_DEBUG).is_ok() {
if std::env::var(DCC_IMAP_DEBUG).is_ok() {
client.debug = true;
}
@@ -56,7 +58,7 @@ impl Client {
let stream = TcpStream::connect(addr).await?;
let mut client = ImapClient::new(stream);
if std::env::var(crate::DCC_IMAP_DEBUG).is_ok() {
if std::env::var(DCC_IMAP_DEBUG).is_ok() {
client.debug = true;
}
let _greeting = client
@@ -70,12 +72,11 @@ 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_config = dc_build_tls_config(certificate_checks);
let tls: async_tls::TlsConnector = Arc::new(tls_config).into();
let tls = async_tls::TlsConnector::new();
let client_sec = client.secure(domain, &tls).await?;

View File

@@ -12,6 +12,7 @@ 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::*;
@@ -19,7 +20,6 @@ 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,24 +84,27 @@ 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 {
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;
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);
}
info!(context, "backup_time of {} is {}", name, curr_backup_time);
sql.close(&context);
}
}
Err(_) => (),
}
}
match newest_backup_path {
@@ -172,7 +175,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_else(|| format_err!("Failed to get private key."))?;
.ok_or(format_err!("Failed to get private key."))?;
let ac_headers = match context.get_config_bool(Config::E2eeEnabled) {
false => None,
true => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
@@ -219,7 +222,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;
}
}
@@ -364,7 +367,7 @@ pub fn normalize_setup_code(s: &str) -> String {
}
#[allow(non_snake_case)]
pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> {
pub fn job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) -> Result<()> {
ensure!(context.alloc_ongoing(), "could not allocate ongoing");
let what: Option<ImexMode> = job.param.get_int(Param::Cmd).and_then(ImexMode::from_i32);
let param = job.param.get(Param::Arg).unwrap_or_default();
@@ -459,9 +462,7 @@ 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?;
if context.shall_stop_ongoing() {
return Ok(false);
}
ensure!(!context.shall_stop_ongoing(), "received stop signal");
let mut permille = processed_files_cnt * 1000 / total_files_cnt;
if permille < 10 {
permille = 10
@@ -475,25 +476,25 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
}
let path_filename = context.get_blobdir().join(file_name);
dc_write_file(context, &path_filename, &file_blob)?;
if dc_write_file(context, &path_filename, &file_blob) {
continue;
}
bail!(
"Storage full? Cannot write file {} with {} bytes.",
path_filename.display(),
file_blob.len(),
);
}
Ok(true)
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()),
}
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(())
})
}
/*******************************************************************************
@@ -545,13 +546,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));
context.call_cb(Event::ImexFileWritten(dest_path_filename.clone()));
Ok(())
}
};
dest_sql.close(context);
Ok(res?)
res
}
fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
@@ -574,15 +575,16 @@ 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)?;
let exported_all_files = sql.prepare(
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?;
if context.shall_stop_ongoing() {
return Ok(false);
}
ensure!(
!context.shall_stop_ongoing(),
"canceled during export-files"
);
processed_files_cnt += 1;
let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10);
context.call_cb(Event::ImexProgress(permille));
@@ -602,10 +604,9 @@ fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
stmt.execute(params![name, buf])?;
}
}
Ok(true)
Ok(())
},
)?;
ensure!(exported_all_files, "canceled during export-files");
Ok(())
}
@@ -685,14 +686,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).is_err() {
if !export_key_to_asc_file(context, &dir, id, &key) {
export_errors += 1;
}
} else {
export_errors += 1;
}
if let Some(key) = private_key {
if export_key_to_asc_file(context, &dir, id, &key).is_err() {
if !export_key_to_asc_file(context, &dir, id, &key) {
export_errors += 1;
}
} else {
@@ -716,7 +717,8 @@ fn export_key_to_asc_file(
dir: impl AsRef<Path>,
id: Option<i64>,
key: &Key,
) -> std::io::Result<()> {
) -> bool {
let mut success = false;
let file_name = {
let kind = if key.is_public() { "public" } else { "private" };
let id = id.map_or("default".into(), |i| i.to_string());
@@ -726,13 +728,14 @@ fn export_key_to_asc_file(
info!(context, "Exporting key {}", file_name.display());
dc_delete_file(context, &file_name);
let res = key.write_asc_to_file(&file_name, context);
if res.is_err() {
if !key.write_asc_to_file(&file_name, context) {
error!(context, "Cannot write key to {}", file_name.display());
} else {
context.call_cb(Event::ImexFileWritten(file_name));
success = true;
}
res
success
}
#[cfg(test)]
@@ -796,7 +799,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).is_ok());
assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key));
let blobdir = context.ctx.get_blobdir().to_str().unwrap();
let filename = format!("{}/public-key-default.asc", blobdir);
let bytes = std::fs::read(&filename).unwrap();

View File

@@ -1,21 +1,14 @@
//! # 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::config::{Config, ConfigItem, ConfigKey};
use crate::configure::*;
use crate::constants::*;
use crate::context::{Context, PerformJobsNeeded};
use crate::context::Context;
use crate::dc_tools::*;
use crate::error::Error;
use crate::events::Event;
@@ -25,13 +18,10 @@ use crate::location;
use crate::login_param::LoginParam;
use crate::message::MsgId;
use crate::message::{self, Message, MessageState};
use crate::mimefactory::{vec_contains_lowercase, MimeFactory, RenderedEmail};
use crate::mimefactory::{vec_contains_lowercase, Loaded, MimeFactory};
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)]
@@ -41,13 +31,6 @@ 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
@@ -117,14 +100,13 @@ pub struct Job {
pub foreign_id: u32,
pub desired_timestamp: i64,
pub added_timestamp: i64,
pub tries: u32,
pub tries: i32,
pub param: Params,
try_again: TryAgain,
pub try_again: i32,
pub pending_error: Option<String>,
}
impl Job {
/// Deletes the job from the database.
fn delete(&self, context: &Context) -> bool {
context
.sql
@@ -132,9 +114,6 @@ 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,
@@ -151,13 +130,13 @@ impl Job {
}
#[allow(non_snake_case)]
fn SendMsgToSmtp(&mut self, context: &Context) {
fn do_DC_JOB_SEND(&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(TryAgain::StandardDelay, None);
self.try_again_later(3, None);
return;
}
}
@@ -194,26 +173,11 @@ 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();
if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
info!(context, "smtp-sending out mime message:");
println!("{}", String::from_utf8_lossy(&body));
}
match smtp.send(context, recipients_list, body, self.job_id) {
Err(crate::smtp::send::Error::SendError(err)) => {
// Remote error, retry later.
Err(err) => {
smtp.disconnect();
info!(context, "SMTP failed to send: {}", err);
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");
warn!(context, "smtp failed: {}", err);
self.try_again_later(-1, Some(err.to_string()));
}
Ok(()) => {
// smtp success, update db ASAP, then delete smtp file
@@ -232,20 +196,23 @@ impl Job {
}
// this value does not increase the number of tries
fn try_again_later(&mut self, try_again: TryAgain, pending_error: Option<String>) {
fn try_again_later(&mut self, try_again: i32, pending_error: Option<String>) {
self.try_again = try_again;
self.pending_error = pending_error;
}
#[allow(non_snake_case)]
fn MoveMsg(&mut self, context: &Context) {
fn do_DC_JOB_MOVE_MSG(&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 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;
if context
.sql
.get_raw_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
imap_inbox.configure_folders(context, 0x1i32);
}
let dest_folder = context
.sql
@@ -263,7 +230,7 @@ impl Job {
&mut dest_uid,
) {
ImapActionResult::RetryLater => {
self.try_again_later(TryAgain::StandardDelay, None);
self.try_again_later(3i32, None);
}
ImapActionResult::Success => {
message::update_server_uid(
@@ -280,7 +247,7 @@ impl Job {
}
#[allow(non_snake_case)]
fn DeleteMsgOnImap(&mut self, context: &Context) {
fn do_DC_JOB_DELETE_MSG_ON_IMAP(&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)) {
@@ -299,7 +266,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(TryAgain::AtOnce, None);
self.try_again_later(-1i32, None);
return;
}
}
@@ -309,7 +276,7 @@ impl Job {
}
#[allow(non_snake_case)]
fn EmptyServer(&mut self, context: &Context) {
fn do_DC_JOB_EMPTY_SERVER(&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
@@ -325,14 +292,14 @@ impl Job {
}
#[allow(non_snake_case)]
fn MarkseenMsgOnImap(&mut self, context: &Context) {
fn do_DC_JOB_MARKSEEN_MSG_ON_IMAP(&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(TryAgain::StandardDelay, None);
self.try_again_later(3i32, None);
}
ImapActionResult::AlreadyDone => {}
ImapActionResult::Success | ImapActionResult::Failed => {
@@ -353,7 +320,7 @@ impl Job {
}
#[allow(non_snake_case)]
fn MarkseenMdnOnImap(&mut self, context: &Context) {
fn do_DC_JOB_MARKSEEN_MDN_ON_IMAP(&mut self, context: &Context) {
let folder = self
.param
.get(Param::ServerFolder)
@@ -362,14 +329,17 @@ 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(TryAgain::StandardDelay, None);
self.try_again_later(3i32, None);
return;
}
if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() {
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;
if context
.sql
.get_raw_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
imap_inbox.configure_folders(context, 0x1i32);
}
let dest_folder = context
.sql
@@ -379,7 +349,7 @@ impl Job {
if ImapActionResult::RetryLater
== imap_inbox.mv(context, &folder, uid, &dest_folder, &mut dest_uid)
{
self.try_again_later(TryAgain::StandardDelay, None);
self.try_again_later(3, None);
}
}
}
@@ -398,39 +368,38 @@ 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);
let use_network =
if let Some(ConfigItem::InboxWatch(val)) = context.get_config_item(ConfigKey::InboxWatch) {
val
} else {
false
};
task::block_on(
context
.inbox_thread
.write()
.unwrap()
.fetch(context, use_network),
);
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);
task::block_on(
context
.mvbox_thread
.write()
.unwrap()
.fetch(context, use_network),
);
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);
task::block_on(
context
.sentbox_thread
.write()
.unwrap()
.fetch(context, use_network),
);
context
.sentbox_thread
.write()
.unwrap()
.fetch(context, use_network);
}
pub fn perform_inbox_idle(context: &Context) {
@@ -441,7 +410,12 @@ pub fn perform_inbox_idle(context: &Context) {
);
return;
}
let use_network = context.get_config_bool(Config::InboxWatch);
let use_network =
if let Some(ConfigItem::InboxWatch(val)) = context.get_config_item(Config::InboxWatch) {
val
} else {
false
};
context
.inbox_thread
@@ -506,7 +480,7 @@ pub fn perform_smtp_jobs(context: &Context) {
let probe_smtp_network = state.probe_network;
state.probe_network = false;
state.perform_jobs_needed = PerformJobsNeeded::Not;
state.perform_jobs_needed = 0;
if state.suspended {
info!(context, "SMTP-jobs suspended.",);
@@ -534,27 +508,24 @@ pub fn perform_smtp_idle(context: &Context) {
let &(ref lock, ref cvar) = &*context.smtp_state.clone();
let mut state = lock.lock().unwrap();
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);
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;
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;
}
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;
}
}
@@ -618,26 +589,49 @@ 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 msg = Message::load_from_db(context, msg_id)?;
msg.try_calc_and_set_dimensions(context).ok();
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);
}
}
}
/* create message */
let needs_encryption = msg.param.get_int(Param::GuaranteeE2ee).unwrap_or_default();
let mimefactory = MimeFactory::from_msg(context, &msg)?;
let mut rendered_msg = mimefactory.render().map_err(|err| {
message::set_msg_failed(context, msg_id, Some(err.to_string()));
err
})?;
if 0 != needs_encryption && !rendered_msg.is_encrypted {
if let Err(msg) = unsafe { mimefactory.render() } {
let e = msg.to_string();
message::set_msg_failed(context, msg_id, Some(e));
return Err(msg);
}
if 0 != mimefactory
.msg
.param
.get_int(Param::GuaranteeE2ee)
.unwrap_or_default()
&& !mimefactory.out_encrypted
{
/* unrecoverable */
message::set_msg_failed(
context,
@@ -647,17 +641,19 @@ pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<(), Error> {
bail!(
"e2e encryption unavailable {} - {:?}",
msg_id,
needs_encryption
mimefactory.msg.param.get_int(Param::GuaranteeE2ee),
);
}
if context.get_config_bool(Config::BccSelf)
&& !vec_contains_lowercase(&rendered_msg.recipients, &rendered_msg.from)
&& !vec_contains_lowercase(&mimefactory.recipients_addr, &mimefactory.from_addr)
{
rendered_msg.recipients.push(rendered_msg.from.clone());
mimefactory.recipients_names.push("".to_string());
mimefactory
.recipients_addr
.push(mimefactory.from_addr.to_string());
}
if rendered_msg.recipients.is_empty() {
if mimefactory.recipients_addr.is_empty() {
// may happen eg. for groups with only SELF and bcc_self disabled
info!(
context,
@@ -667,27 +663,36 @@ pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<(), Error> {
return Ok(());
}
if rendered_msg.is_gossiped {
chat::set_gossiped_timestamp(context, msg.chat_id, time());
if mimefactory.out_gossiped {
chat::set_gossiped_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()) {
if 0 != mimefactory.out_last_added_location_id {
if let Err(err) = location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
{
error!(context, "Failed to set kml sent_timestamp: {:?}", err);
}
if !msg.hidden {
if let Err(err) =
location::set_msg_location_id(context, msg.id, rendered_msg.last_added_location_id)
{
if !mimefactory.msg.hidden {
if let Err(err) = location::set_msg_location_id(
context,
mimefactory.msg.id,
mimefactory.out_last_added_location_id,
) {
error!(context, "Failed to set msg_location_id: {:?}", err);
}
}
}
if rendered_msg.is_encrypted && needs_encryption == 0 {
msg.param.set_int(Param::GuaranteeE2ee, 1);
msg.save_param_to_disk(context);
if mimefactory.out_encrypted
&& mimefactory
.msg
.param
.get_int(Param::GuaranteeE2ee)
.unwrap_or_default()
== 0
{
mimefactory.msg.param.set_int(Param::GuaranteeE2ee, 1);
mimefactory.msg.save_param_to_disk(context);
}
add_smtp_job(context, Action::SendMsgToSmtp, &rendered_msg)?;
add_smtp_job(context, Action::SendMsgToSmtp, &mut mimefactory)?;
Ok(())
}
@@ -712,9 +717,54 @@ pub fn perform_sentbox_jobs(context: &Context) {
}
fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
let jobs: Vec<Job> = load_jobs(context, thread, probe_network);
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;"
};
for mut job in jobs {
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() {
info!(
context,
"{}-job #{}, action {} started...",
@@ -748,39 +798,43 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
suspend_smtp_thread(context, true);
}
for _tries in 0..2 {
let mut tries = 0;
while tries <= 1 {
// this can be modified by a job using dc_job_try_again_later()
job.try_again = TryAgain::Dont;
job.try_again = 0;
match job.action {
Action::Unknown => {
info!(context, "Unknown job id found");
warn!(context, "Unknown job id found");
}
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) {
Action::SendMsgToSmtp => job.do_DC_JOB_SEND(context),
Action::EmptyServer => job.do_DC_JOB_EMPTY_SERVER(context),
Action::DeleteMsgOnImap => job.do_DC_JOB_DELETE_MSG_ON_IMAP(context),
Action::MarkseenMsgOnImap => job.do_DC_JOB_MARKSEEN_MSG_ON_IMAP(context),
Action::MarkseenMdnOnImap => job.do_DC_JOB_MARKSEEN_MDN_ON_IMAP(context),
Action::MoveMsg => job.do_DC_JOB_MOVE_MSG(context),
Action::SendMdn => job.do_DC_JOB_SEND(context),
Action::ConfigureImap => dc_job_do_DC_JOB_CONFIGURE_IMAP(context),
Action::ImexImap => match job_do_DC_JOB_IMEX_IMAP(context, &job) {
Ok(()) => {}
Err(err) => {
error!(context, "{}", err);
}
},
Action::MaybeSendLocations => location::JobMaybeSendLocations(context, &job),
Action::MaybeSendLocations => {
location::job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context, &job)
}
Action::MaybeSendLocationsEnded => {
location::JobMaybeSendLocationsEnded(context, &mut job)
location::job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context, &mut job)
}
Action::Housekeeping => sql::housekeeping(context),
Action::SendMdnOld => {}
Action::SendMsgToSmtpOld => {}
}
if job.try_again != TryAgain::AtOnce {
if job.try_again != -1 {
break;
}
tries += 1
}
if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
context
@@ -797,16 +851,28 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
.unsuspend(context);
suspend_smtp_thread(context, false);
break;
} else if job.try_again == TryAgain::AtOnce || job.try_again == TryAgain::StandardDelay {
} 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 {
let tries = job.tries + 1;
if tries < JOB_RETRIES {
if tries < 17 {
job.tries = tries;
let time_offset = get_backoff_time_offset(tries);
job.desired_timestamp = time() + time_offset;
job.desired_timestamp = job.added_timestamp + time_offset;
job.update(context);
info!(
context,
"{}-job #{} not succeeded on try #{}, retry in {} seconds.",
"{}-job #{} not succeeded on try #{}, retry in ADD_TIME+{} (in {} seconds).",
if thread == Thread::Imap {
"INBOX"
} else {
@@ -814,16 +880,17 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
},
job.job_id as u32,
tries,
time_offset
time_offset,
job.added_timestamp + time_offset - time()
);
if thread == Thread::Smtp && tries < JOB_RETRIES - 1 {
if thread == Thread::Smtp && tries < 17 - 1 {
context
.smtp_state
.clone()
.0
.lock()
.unwrap()
.perform_jobs_needed = PerformJobsNeeded::AvoidDos;
.perform_jobs_needed = 2;
}
} else {
if job.action == Action::SendMsgToSmtp {
@@ -849,11 +916,13 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
}
}
fn get_backoff_time_offset(tries: u32) -> i64 {
let n = 2_i32.pow(tries - 1) * 60;
#[allow(non_snake_case)]
fn get_backoff_time_offset(c_tries: libc::c_int) -> i64 {
// results in ~3 weeks for the last backoff timespan
let N = 2_i32.pow((c_tries - 1) as u32) * 60;
let mut rng = thread_rng();
let r: i32 = rng.gen();
let mut seconds = r % (n + 1);
let n: i32 = rng.gen();
let mut seconds = n % (N + 1);
if seconds < 1 {
seconds = 1;
}
@@ -873,40 +942,38 @@ fn suspend_smtp_thread(context: &Context, suspend: bool) {
}
fn send_mdn(context: &Context, msg_id: MsgId) -> Result<(), Error> {
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)?;
let mut mimefactory = MimeFactory::load_mdn(context, msg_id)?;
unsafe { mimefactory.render()? };
add_smtp_job(context, Action::SendMdn, &mut mimefactory)?;
Ok(())
}
#[allow(non_snake_case)]
fn add_smtp_job(
context: &Context,
action: Action,
rendered_msg: &RenderedEmail,
) -> Result<(), Error> {
fn add_smtp_job(context: &Context, action: Action, mimefactory: &MimeFactory) -> Result<(), Error> {
ensure!(
!rendered_msg.recipients.is_empty(),
!mimefactory.recipients_addr.is_empty(),
"no recipients for smtp job set"
);
let mut param = Params::new();
let bytes = &rendered_msg.message;
let blob = BlobObject::create(context, &rendered_msg.rfc724_mid, bytes)?;
let recipients = rendered_msg.recipients.join("\x1e");
let bytes = unsafe {
std::slice::from_raw_parts(
(*mimefactory.out).str_0 as *const u8,
(*mimefactory.out).len,
)
};
let blob = BlobObject::create(context, &mimefactory.rfc724_mid, bytes)?;
let recipients = mimefactory.recipients_addr.join("\x1e");
param.set(Param::File, blob.as_name());
param.set(Param::Recipients, &recipients);
job_add(
context,
action,
rendered_msg
.foreign_id
.map(|v| v.to_u32() as i32)
.unwrap_or_default(),
(if mimefactory.loaded == Loaded::Message {
mimefactory.msg.id.to_u32() as i32
} else {
0
}) as libc::c_int,
param,
0,
);
@@ -914,12 +981,10 @@ fn add_smtp_job(
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: i32,
foreign_id: libc::c_int,
param: Params,
delay_seconds: i64,
) {
@@ -958,110 +1023,7 @@ 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 = PerformJobsNeeded::AtOnce;
state.perform_jobs_needed = 1;
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);
}
}

View File

@@ -63,17 +63,16 @@ impl JobThread {
info!(context, "Interrupting {}-IDLE...", self.name);
self.imap.interrupt_idle(context);
self.imap.interrupt_idle();
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 async fn fetch(&mut self, context: &Context, use_network: bool) {
pub fn fetch(&mut self, context: &Context, use_network: bool) {
{
let &(ref lock, _) = &*self.state.clone();
let mut state = lock.lock().unwrap();
@@ -86,10 +85,10 @@ impl JobThread {
}
if use_network {
if let Err(err) = self.connect_and_fetch(context).await {
if let Err(err) = self.connect_and_fetch(context) {
warn!(context, "connect+fetch failed: {}, reconnect & retry", err);
self.imap.trigger_reconnect();
if let Err(err) = self.connect_and_fetch(context).await {
if let Err(err) = self.connect_and_fetch(context) {
warn!(context, "connect+fetch failed: {}", err);
}
}
@@ -97,18 +96,14 @@ impl JobThread {
self.state.0.lock().unwrap().using_handle = false;
}
async fn connect_and_fetch(&mut self, context: &Context) -> Result<()> {
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)
.await
.map_err(Into::into);
let res = self.imap.fetch(context, &watch_folder);
let elapsed = start.elapsed().as_millis();
info!(context, "{} done in {:.3} ms.", prefix, elapsed);
@@ -117,7 +112,7 @@ impl JobThread {
Err(Error::WatchFolderNotFound("not-set".to_string()))
}
}
Err(err) => Err(crate::error::Error::Message(err.to_string())),
Err(err) => Err(err),
}
}
@@ -174,19 +169,20 @@ impl JobThread {
let prefix = format!("{}-IDLE", self.name);
let do_fake_idle = match self.imap.connect_configured(context) {
Ok(()) => {
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 {
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) => {
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) => {

View File

@@ -178,9 +178,21 @@ impl Key {
}
}
pub fn to_base64(&self) -> String {
pub fn to_base64(&self, break_every: usize) -> String {
let buf = self.to_bytes();
base64::encode(&buf)
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()
}
pub fn to_armored_string(
@@ -205,18 +217,15 @@ impl Key {
.expect("failed to serialize key")
}
pub fn write_asc_to_file(
&self,
file: impl AsRef<Path>,
context: &Context,
) -> std::io::Result<()> {
pub fn write_asc_to_file(&self, file: impl AsRef<Path>, context: &Context) -> bool {
let file_content = self.to_asc(None).into_bytes();
let res = dc_write_file(context, &file, &file_content);
if res.is_err() {
if dc_write_file(context, &file, &file_content) {
true
} else {
error!(context, "Cannot write key to {}", file.as_ref().display());
false
}
res
}
pub fn fingerprint(&self) -> String {

View File

@@ -1,17 +1,8 @@
#![deny(clippy::correctness, missing_debug_implementations, clippy::all)]
// for now we hide warnings to not clutter/hide errors during "cargo clippy"
#![allow(
clippy::type_complexity,
clippy::cognitive_complexity,
clippy::too_many_arguments,
clippy::block_in_if_condition_stmt,
clippy::large_enum_variant
)]
#![allow(
clippy::unreadable_literal,
clippy::needless_range_loop,
clippy::match_bool
)]
#![deny(clippy::correctness, missing_debug_implementations)]
// TODO: make all of these errors, such that clippy actually passes.
#![warn(clippy::all, clippy::perf, clippy::not_unsafe_ptr_arg_deref)]
// This is nice, but for now just annoying.
#![allow(clippy::unreadable_literal)]
#![feature(ptr_wrapping_offset_from)]
#[macro_use]
@@ -26,6 +17,8 @@ extern crate strum;
#[macro_use]
extern crate strum_macros;
#[macro_use]
extern crate jetscii;
#[macro_use]
extern crate debug_stub_derive;
#[macro_use]
@@ -58,7 +51,6 @@ mod login_param;
pub mod lot;
pub mod message;
mod mimefactory;
pub mod mimeparser;
pub mod oauth2;
mod param;
pub mod peerstate;
@@ -71,18 +63,14 @@ 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;

View File

@@ -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 i32,
chat_id as libc::c_int,
Params::new(),
seconds + 1,
);
@@ -495,54 +495,50 @@ 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![
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",
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)
},
)
.map_err(Into::into)
}
Ok(newest_location_id)
},
)
}
#[allow(non_snake_case)]
pub fn JobMaybeSendLocations(context: &Context, _job: &Job) {
pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
let now = time();
let mut continue_streaming = false;
info!(
@@ -632,7 +628,7 @@ pub fn JobMaybeSendLocations(context: &Context, _job: &Job) {
}
#[allow(non_snake_case)]
pub fn JobMaybeSendLocationsEnded(context: &Context, job: &mut Job) {
pub fn job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut Job) {
// this function is called when location-streaming _might_ have ended for a chat.
// the function checks, if location-streaming is really ended;
// if so, a device-message is added if not yet done.

View File

@@ -5,10 +5,10 @@ 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));
}};
};
}
#[macro_export]
@@ -16,10 +16,10 @@ 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));
}};
};
}
#[macro_export]
@@ -27,10 +27,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]

View File

@@ -1,5 +1,3 @@
//! # Login parameters
use std::borrow::Cow;
use std::fmt;
@@ -106,7 +104,7 @@ impl LoginParam {
let server_flags = sql.get_raw_config_int(context, key).unwrap_or_default();
LoginParam {
addr,
addr: addr.to_string(),
mail_server,
mail_user,
mail_pw,
@@ -200,7 +198,6 @@ impl fmt::Display for LoginParam {
}
}
#[allow(clippy::ptr_arg)]
fn unset_empty(s: &String) -> Cow<String> {
if s.is_empty() {
Cow::Owned("unset".to_string())
@@ -209,45 +206,44 @@ fn unset_empty(s: &String) -> Cow<String> {
}
}
#[allow(clippy::useless_let_if_seq)]
fn get_readable_flags(flags: i32) -> String {
let mut res = String::new();
for bit in 0..31 {
if 0 != flags & 1 << bit {
let mut flag_added = false;
let mut flag_added = 0;
if 1 << bit == 0x2 {
res += "OAUTH2 ";
flag_added = true;
flag_added = 1;
}
if 1 << bit == 0x4 {
res += "AUTH_NORMAL ";
flag_added = true;
flag_added = 1;
}
if 1 << bit == 0x100 {
res += "IMAP_STARTTLS ";
flag_added = true;
flag_added = 1;
}
if 1 << bit == 0x200 {
res += "IMAP_SSL ";
flag_added = true;
flag_added = 1;
}
if 1 << bit == 0x400 {
res += "IMAP_PLAIN ";
flag_added = true;
flag_added = 1;
}
if 1 << bit == 0x10000 {
res += "SMTP_STARTTLS ";
flag_added = true;
flag_added = 1
}
if 1 << bit == 0x20000 {
res += "SMTP_SSL ";
flag_added = true;
flag_added = 1
}
if 1 << bit == 0x40000 {
res += "SMTP_PLAIN ";
flag_added = true;
flag_added = 1
}
if flag_added {
if 0 == flag_added {
res += &format!("{:#0x}", 1 << bit);
}
}

View File

@@ -5,7 +5,7 @@ use deltachat_derive::{FromSql, ToSql};
/// Lot objects are created
/// eg. by chatlist.get_summary() or dc_msg_get_summary().
///
/// *Lot* is used in the meaning *heap* here.
/// _Lot_ is used in the meaning _heap_ here.
#[derive(Default, Debug, Clone)]
pub struct Lot {
pub(crate) text1_meaning: Meaning,

View File

@@ -1,5 +1,3 @@
//! # Messages and their identifiers
use std::path::{Path, PathBuf};
use deltachat_derive::{FromSql, ToSql};
@@ -9,12 +7,12 @@ use crate::chat::{self, Chat};
use crate::constants::*;
use crate::contact::*;
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::lot::{Lot, LotState, Meaning};
use crate::mimeparser::SystemMessage;
use crate::param::*;
use crate::pgp::*;
use crate::sql;
@@ -46,7 +44,7 @@ impl MsgId {
/// Whether the message ID signifies a special message.
///
/// This kind of message ID can not be used for real messages.
pub fn is_special(self) -> bool {
pub fn is_special(&self) -> bool {
match self.0 {
0..=DC_MSG_ID_LAST_SPECIAL => true,
_ => false,
@@ -62,21 +60,21 @@ impl MsgId {
///
/// When this is `true`, [MsgId::is_special] will also always be
/// `true`.
pub fn is_unset(self) -> bool {
pub fn is_unset(&self) -> bool {
self.0 == 0
}
/// Whether the message ID is the special marker1 marker.
///
/// See the docs of the `dc_get_chat_msgs` C API for details.
pub fn is_marker1(self) -> bool {
pub fn is_marker1(&self) -> bool {
self.0 == DC_MSG_ID_MARKER1
}
/// Whether the message ID is the special day marker.
///
/// See the docs of the `dc_get_chat_msgs` C API for details.
pub fn is_daymarker(self) -> bool {
pub fn is_daymarker(&self) -> bool {
self.0 == DC_MSG_ID_DAYMARKER
}
@@ -84,7 +82,7 @@ impl MsgId {
///
/// Avoid using this, eventually types should be cleaned up enough
/// that it is no longer necessary.
pub fn to_u32(self) -> u32 {
pub fn to_u32(&self) -> u32 {
self.0
}
}
@@ -161,6 +159,7 @@ pub struct Message {
pub(crate) from_id: u32,
pub(crate) to_id: u32,
pub(crate) chat_id: u32,
pub(crate) move_state: MoveState,
pub(crate) type_0: Viewtype,
pub(crate) state: MessageState,
pub(crate) hidden: bool,
@@ -193,85 +192,84 @@ impl Message {
!id.is_special(),
"Can not load special message IDs from DB."
);
context
.sql
.query_row(
concat!(
"SELECT",
" m.id AS id,",
" rfc724_mid AS rfc724mid,",
" m.mime_in_reply_to AS mime_in_reply_to,",
" m.server_folder AS server_folder,",
" m.server_uid AS server_uid,",
" m.chat_id AS chat_id,",
" m.from_id AS from_id,",
" m.to_id AS to_id,",
" m.timestamp AS timestamp,",
" m.timestamp_sent AS timestamp_sent,",
" m.timestamp_rcvd AS timestamp_rcvd,",
" m.type AS type,",
" m.state AS state,",
" m.msgrmsg AS msgrmsg,",
" m.txt AS txt,",
" m.param AS param,",
" m.starred AS starred,",
" m.hidden AS hidden,",
" m.location_id AS location,",
" c.blocked AS blocked",
" FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id",
" WHERE m.id=?;"
),
params![id],
|row| {
let mut msg = Message::default();
// msg.id = row.get::<_, AnyMsgId>("id")?;
msg.id = row.get("id")?;
msg.rfc724_mid = row.get::<_, String>("rfc724mid")?;
msg.in_reply_to = row.get::<_, Option<String>>("mime_in_reply_to")?;
msg.server_folder = row.get::<_, Option<String>>("server_folder")?;
msg.server_uid = row.get("server_uid")?;
msg.chat_id = row.get("chat_id")?;
msg.from_id = row.get("from_id")?;
msg.to_id = row.get("to_id")?;
msg.timestamp_sort = row.get("timestamp")?;
msg.timestamp_sent = row.get("timestamp_sent")?;
msg.timestamp_rcvd = row.get("timestamp_rcvd")?;
msg.type_0 = row.get("type")?;
msg.state = row.get("state")?;
msg.is_dc_message = row.get("msgrmsg")?;
context.sql.query_row(
concat!(
"SELECT",
" m.id AS id,",
" rfc724_mid AS rfc724mid,",
" m.mime_in_reply_to AS mime_in_reply_to,",
" m.server_folder AS server_folder,",
" m.server_uid AS server_uid,",
" m.move_state as move_state,",
" m.chat_id AS chat_id,",
" m.from_id AS from_id,",
" m.to_id AS to_id,",
" m.timestamp AS timestamp,",
" m.timestamp_sent AS timestamp_sent,",
" m.timestamp_rcvd AS timestamp_rcvd,",
" m.type AS type,",
" m.state AS state,",
" m.msgrmsg AS msgrmsg,",
" m.txt AS txt,",
" m.param AS param,",
" m.starred AS starred,",
" m.hidden AS hidden,",
" m.location_id AS location,",
" c.blocked AS blocked",
" FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id",
" WHERE m.id=?;"
),
params![id],
|row| {
let mut msg = Message::default();
// msg.id = row.get::<_, AnyMsgId>("id")?;
msg.id = row.get("id")?;
msg.rfc724_mid = row.get::<_, String>("rfc724mid")?;
msg.in_reply_to = row.get::<_, Option<String>>("mime_in_reply_to")?;
msg.server_folder = row.get::<_, Option<String>>("server_folder")?;
msg.server_uid = row.get("server_uid")?;
msg.move_state = row.get("move_state")?;
msg.chat_id = row.get("chat_id")?;
msg.from_id = row.get("from_id")?;
msg.to_id = row.get("to_id")?;
msg.timestamp_sort = row.get("timestamp")?;
msg.timestamp_sent = row.get("timestamp_sent")?;
msg.timestamp_rcvd = row.get("timestamp_rcvd")?;
msg.type_0 = row.get("type")?;
msg.state = row.get("state")?;
msg.is_dc_message = row.get("msgrmsg")?;
let text;
if let rusqlite::types::ValueRef::Text(buf) = row.get_raw("txt") {
if let Ok(t) = String::from_utf8(buf.to_vec()) {
text = t;
} else {
warn!(
context,
concat!(
"dc_msg_load_from_db: could not get ",
"text column as non-lossy utf8 id {}"
),
id
);
text = String::from_utf8_lossy(buf).into_owned();
}
let text;
if let rusqlite::types::ValueRef::Text(buf) = row.get_raw("txt") {
if let Ok(t) = String::from_utf8(buf.to_vec()) {
text = t;
} else {
text = "".to_string();
warn!(
context,
concat!(
"dc_msg_load_from_db: could not get ",
"text column as non-lossy utf8 id {}"
),
id
);
text = String::from_utf8_lossy(buf).into_owned();
}
msg.text = Some(text);
} else {
text = "".to_string();
}
msg.text = Some(text);
msg.param = row.get::<_, String>("param")?.parse().unwrap_or_default();
msg.starred = row.get("starred")?;
msg.hidden = row.get("hidden")?;
msg.location_id = row.get("location")?;
msg.chat_blocked = row
.get::<_, Option<Blocked>>("blocked")?
.unwrap_or_default();
msg.param = row.get::<_, String>("param")?.parse().unwrap_or_default();
msg.starred = row.get("starred")?;
msg.hidden = row.get("hidden")?;
msg.location_id = row.get("location")?;
msg.chat_blocked = row
.get::<_, Option<Blocked>>("blocked")?
.unwrap_or_default();
Ok(msg)
},
)
.map_err(Into::into)
Ok(msg)
},
)
}
pub fn delete_from_db(context: &Context, msg_id: MsgId) {
@@ -311,32 +309,6 @@ impl Message {
self.param.get_path(Param::File, context).unwrap_or(None)
}
pub fn try_calc_and_set_dimensions(&mut self, context: &Context) -> Result<(), Error> {
if chat::msgtype_has_file(self.type_0) {
let file_param = self.param.get_path(Param::File, context)?;
if let Some(path_and_filename) = file_param {
if (self.type_0 == Viewtype::Image || self.type_0 == Viewtype::Gif)
&& !self.param.exists(Param::Width)
{
self.param.set_int(Param::Width, 0);
self.param.set_int(Param::Height, 0);
if let Ok(buf) = dc_read_file(context, path_and_filename) {
if let Ok((width, height)) = dc_get_filemeta(&buf) {
self.param.set_int(Param::Width, width as i32);
self.param.set_int(Param::Height, height as i32);
}
}
if !self.id.is_unset() {
self.save_param_to_disk(context);
}
}
}
}
Ok(())
}
/// Check if a message has a location bound to it.
/// These messages are also returned by dc_get_locations()
/// and the UI may decide to display a special icon beside such messages,
@@ -459,8 +431,8 @@ impl Message {
return ret;
};
let contact = if self.from_id != DC_CONTACT_ID_SELF as u32
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
let contact = if self.from_id != DC_CONTACT_ID_SELF as libc::c_uint
&& ((*chat).typ == Chattype::Group || (*chat).typ == Chattype::VerifiedGroup)
{
Contact::get_by_id(context, self.from_id).ok()
} else {
@@ -472,11 +444,11 @@ impl Message {
ret
}
pub fn get_summarytext(&self, context: &Context, approx_characters: usize) -> String {
pub fn get_summarytext(&mut self, context: &Context, approx_characters: usize) -> String {
get_summarytext_by_raw(
self.type_0,
self.text.as_ref(),
&self.param,
&mut self.param,
approx_characters,
context,
)
@@ -504,8 +476,8 @@ impl Message {
pub fn is_info(&self) -> bool {
let cmd = self.param.get_cmd();
self.from_id == DC_CONTACT_ID_INFO as u32
|| self.to_id == DC_CONTACT_ID_INFO as u32
self.from_id == DC_CONTACT_ID_INFO as libc::c_uint
|| self.to_id == DC_CONTACT_ID_INFO as libc::c_uint
|| cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
}
@@ -689,7 +661,7 @@ impl Lot {
self.text2 = Some(get_summarytext_by_raw(
msg.type_0,
msg.text.as_ref(),
&msg.param,
&mut msg.param,
SUMMARY_CHARACTERS,
context,
));
@@ -732,7 +704,7 @@ pub fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
ret += &format!(" by {}", name);
ret += "\n";
if msg.from_id != DC_CONTACT_ID_SELF as u32 {
if msg.from_id != DC_CONTACT_ID_SELF as libc::c_uint {
let s = dc_timestamp_to_str(if 0 != msg.timestamp_rcvd {
msg.timestamp_rcvd
} else {
@@ -1015,7 +987,7 @@ pub fn star_msgs(context: &Context, msg_ids: &[MsgId], star: bool) -> bool {
pub fn get_summarytext_by_raw(
viewtype: Viewtype,
text: Option<impl AsRef<str>>,
param: &Params,
param: &mut Params,
approx_characters: usize,
context: &Context,
) -> String {
@@ -1102,6 +1074,18 @@ pub fn exists(context: &Context, msg_id: MsgId) -> bool {
}
}
pub fn update_msg_move_state(context: &Context, rfc724_mid: &str, state: MoveState) -> bool {
// we update the move_state for all messages belonging to a given Message-ID
// so that the state stay intact when parts are deleted
sql::execute(
context,
&context.sql,
"UPDATE msgs SET move_state=? WHERE rfc724_mid=?;",
params![state as i32, rfc724_mid],
)
.is_ok()
}
pub fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option<impl AsRef<str>>) {
if let Ok(mut msg) = Message::load_from_db(context, msg_id) {
if msg.state.can_fail() {
@@ -1139,7 +1123,7 @@ pub fn mdn_from_ext(
return None;
}
let res = context.sql.query_row(
if let Ok((msg_id, chat_id, chat_type, msg_state)) = context.sql.query_row(
concat!(
"SELECT",
" m.id AS msg_id,",
@@ -1159,12 +1143,7 @@ pub fn mdn_from_ext(
row.get::<_, MessageState>("state")?,
))
},
);
if let Err(ref err) = res {
info!(context, "Failed to select MDN {:?}", err);
}
if let Ok((msg_id, chat_id, chat_type, msg_state)) = res {
) {
let mut read_by_all = false;
// if already marked as MDNS_RCVD msgstate_can_fail() returns false.
@@ -1220,17 +1199,16 @@ pub fn mdn_from_ext(
} // else wait for more receipts
}
}
return if read_by_all {
Some((chat_id, msg_id))
} else {
None
return match read_by_all {
true => Some((chat_id, msg_id)),
false => None,
};
}
None
}
/// The number of messages assigned to real chat (!=deaddrop, !=trash)
pub fn get_real_msg_cnt(context: &Context) -> i32 {
pub fn get_real_msg_cnt(context: &Context) -> libc::c_int {
match context.sql.query_row(
"SELECT COUNT(*) \
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
@@ -1246,7 +1224,7 @@ pub fn get_real_msg_cnt(context: &Context) -> i32 {
}
}
pub fn get_deaddrop_msg_cnt(context: &Context) -> usize {
pub fn get_deaddrop_msg_cnt(context: &Context) -> libc::size_t {
match context.sql.query_row(
"SELECT COUNT(*) \
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
@@ -1254,7 +1232,7 @@ pub fn get_deaddrop_msg_cnt(context: &Context) -> usize {
rusqlite::NO_PARAMS,
|row| row.get::<_, isize>(0),
) {
Ok(res) => res as usize,
Ok(res) => res as libc::size_t,
Err(err) => {
error!(context, "dc_get_deaddrop_msg_cnt() failed. {}", err);
0
@@ -1262,7 +1240,7 @@ pub fn get_deaddrop_msg_cnt(context: &Context) -> usize {
}
}
pub fn rfc724_mid_cnt(context: &Context, rfc724_mid: &str) -> i32 {
pub fn rfc724_mid_cnt(context: &Context, rfc724_mid: &str) -> libc::c_int {
// check the number of messages with the same rfc724_mid
match context.sql.query_row(
"SELECT COUNT(*) FROM msgs WHERE rfc724_mid=?;",
@@ -1283,20 +1261,17 @@ pub(crate) fn rfc724_mid_exists(
) -> Result<(String, u32, MsgId), Error> {
ensure!(!rfc724_mid.is_empty(), "empty rfc724_mid");
context
.sql
.query_row(
"SELECT server_folder, server_uid, id FROM msgs WHERE rfc724_mid=?",
&[rfc724_mid],
|row| {
let server_folder = row.get::<_, Option<String>>(0)?.unwrap_or_default();
let server_uid = row.get(1)?;
let msg_id: MsgId = row.get(2)?;
context.sql.query_row(
"SELECT server_folder, server_uid, id FROM msgs WHERE rfc724_mid=?",
&[rfc724_mid],
|row| {
let server_folder = row.get::<_, Option<String>>(0)?.unwrap_or_default();
let server_uid = row.get(1)?;
let msg_id: MsgId = row.get(2)?;
Ok((server_folder, server_uid, msg_id))
},
)
.map_err(Into::into)
Ok((server_folder, server_uid, msg_id))
},
)
}
pub fn update_server_uid(
@@ -1371,32 +1346,50 @@ mod tests {
some_file.set(Param::File, "foo.bar");
assert_eq!(
get_summarytext_by_raw(Viewtype::Text, some_text.as_ref(), &Params::new(), 50, &ctx),
get_summarytext_by_raw(
Viewtype::Text,
some_text.as_ref(),
&mut Params::new(),
50,
&ctx
),
"bla bla" // for simple text, the type is not added to the summary
);
assert_eq!(
get_summarytext_by_raw(Viewtype::Image, no_text.as_ref(), &some_file, 50, &ctx,),
get_summarytext_by_raw(Viewtype::Image, no_text.as_ref(), &mut some_file, 50, &ctx,),
"Image" // file names are not added for images
);
assert_eq!(
get_summarytext_by_raw(Viewtype::Video, no_text.as_ref(), &some_file, 50, &ctx,),
get_summarytext_by_raw(Viewtype::Video, no_text.as_ref(), &mut some_file, 50, &ctx,),
"Video" // file names are not added for videos
);
assert_eq!(
get_summarytext_by_raw(Viewtype::Gif, no_text.as_ref(), &some_file, 50, &ctx,),
get_summarytext_by_raw(Viewtype::Gif, no_text.as_ref(), &mut some_file, 50, &ctx,),
"GIF" // file names are not added for GIFs
);
assert_eq!(
get_summarytext_by_raw(Viewtype::Sticker, no_text.as_ref(), &some_file, 50, &ctx,),
get_summarytext_by_raw(
Viewtype::Sticker,
no_text.as_ref(),
&mut some_file,
50,
&ctx,
),
"Sticker" // file names are not added for stickers
);
assert_eq!(
get_summarytext_by_raw(Viewtype::Voice, empty_text.as_ref(), &some_file, 50, &ctx,),
get_summarytext_by_raw(
Viewtype::Voice,
empty_text.as_ref(),
&mut some_file,
50,
&ctx,
),
"Voice message" // file names are not added for voice messages, empty text is skipped
);
@@ -1406,7 +1399,13 @@ mod tests {
);
assert_eq!(
get_summarytext_by_raw(Viewtype::Voice, some_text.as_ref(), &some_file, 50, &ctx),
get_summarytext_by_raw(
Viewtype::Voice,
some_text.as_ref(),
&mut some_file,
50,
&ctx
),
"Voice message \u{2013} bla bla" // `\u{2013}` explicitly checks for "EN DASH"
);
@@ -1416,12 +1415,24 @@ mod tests {
);
assert_eq!(
get_summarytext_by_raw(Viewtype::Audio, empty_text.as_ref(), &some_file, 50, &ctx,),
get_summarytext_by_raw(
Viewtype::Audio,
empty_text.as_ref(),
&mut some_file,
50,
&ctx,
),
"Audio \u{2013} foo.bar" // file name is added for audio, empty text is not added
);
assert_eq!(
get_summarytext_by_raw(Viewtype::Audio, some_text.as_ref(), &some_file, 50, &ctx),
get_summarytext_by_raw(
Viewtype::Audio,
some_text.as_ref(),
&mut some_file,
50,
&ctx
),
"Audio \u{2013} foo.bar \u{2013} bla bla" // file name and text added for audio
);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -355,8 +355,6 @@ fn normalize_addr(addr: &str) -> &str {
mod tests {
use super::*;
use crate::test_utils::*;
#[test]
fn test_normalize_addr() {
assert_eq!(normalize_addr(" hello@mail.de "), "hello@mail.de");
@@ -386,34 +384,4 @@ mod tests {
assert_eq!(Oauth2::from_address("hello@web.de"), None);
}
#[test]
fn test_dc_get_oauth2_addr() {
let ctx = dummy_context();
let addr = "dignifiedquire@gmail.com";
let code = "fail";
let res = dc_get_oauth2_addr(&ctx.ctx, addr, code);
// this should fail as it is an invalid password
assert_eq!(res, None);
}
#[test]
fn test_dc_get_oauth2_url() {
let ctx = dummy_context();
let addr = "dignifiedquire@gmail.com";
let redirect_uri = "chat.delta:/com.b44t.messenger";
let res = dc_get_oauth2_url(&ctx.ctx, addr, redirect_uri);
assert_eq!(res, Some("https://accounts.google.com/o/oauth2/auth?client_id=959970109878%2D4mvtgf6feshskf7695nfln6002mom908%2Eapps%2Egoogleusercontent%2Ecom&redirect_uri=chat%2Edelta%3A%2Fcom%2Eb44t%2Emessenger&response_type=code&scope=https%3A%2F%2Fmail.google.com%2F%20email&access_type=offline".into()));
}
#[test]
fn test_dc_get_oauth2_token() {
let ctx = dummy_context();
let addr = "dignifiedquire@gmail.com";
let code = "fail";
let res = dc_get_oauth2_access_token(&ctx.ctx, addr, code, false);
// this should fail as it is an invalid password
assert_eq!(res, None);
}
}

View File

@@ -7,8 +7,8 @@ use num_traits::FromPrimitive;
use crate::blob::{BlobError, BlobObject};
use crate::context::Context;
use crate::dc_mimeparser::SystemMessage;
use crate::error;
use crate::mimeparser::SystemMessage;
/// Available param keys.
#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash, PartialOrd, Ord, FromPrimitive)]
@@ -50,8 +50,8 @@ pub enum Param {
Error = b'L',
/// For Messages: space-separated list of messaged IDs of forwarded copies.
///
/// This is used when a [crate::message::Message] is in the
/// [crate::message::MessageState::OutPending] state but is already forwarded.
/// This is used when a [Message] is in the
/// [MessageState::OutPending] state but is already forwarded.
/// In this case the forwarded messages are written to the
/// database and their message IDs are added to this parameter of
/// the original message, which is also saved in the database.
@@ -222,7 +222,7 @@ impl Params {
Some(val) => val,
None => return Ok(None),
};
ParamsFile::from_param(context, val).map(Some)
ParamsFile::from_param(context, val).map(|file| Some(file))
}
/// Gets the parameter and returns a [BlobObject] for it.
@@ -321,6 +321,7 @@ mod tests {
use std::fs;
use std::path::Path;
use crate::blob::BlobErrorKind;
use crate::test_utils::*;
#[test]
@@ -373,7 +374,7 @@ mod tests {
if let ParamsFile::FsPath(p) = ParamsFile::from_param(&t.ctx, "/foo/bar/baz").unwrap() {
assert_eq!(p, Path::new("/foo/bar/baz"));
} else {
panic!("Wrong enum variant");
assert!(false, "Wrong enum variant");
}
}
@@ -383,7 +384,7 @@ mod tests {
if let ParamsFile::Blob(b) = ParamsFile::from_param(&t.ctx, "$BLOBDIR/foo").unwrap() {
assert_eq!(b.as_name(), "$BLOBDIR/foo");
} else {
panic!("Wrong enum variant");
assert!(false, "Wrong enum variant");
}
}
@@ -403,10 +404,7 @@ mod tests {
// Blob does not exist yet, expect BlobError.
let err = p.get_blob(Param::File, &t.ctx, false).unwrap_err();
match err {
BlobError::WrongBlobdir { .. } => (),
_ => panic!("wrong error type/variant: {:?}", err),
}
assert_eq!(err.kind(), BlobErrorKind::WrongBlobdir);
fs::write(fname, b"boo").unwrap();
let blob = p.get_blob(Param::File, &t.ctx, true).unwrap().unwrap();

View File

@@ -8,27 +8,14 @@ use crate::aheader::*;
use crate::chat::*;
use crate::constants::*;
use crate::context::Context;
use crate::error::*;
use crate::key::*;
use crate::sql::{self, Sql};
#[derive(Debug)]
pub enum PeerstateKeyType {
GossipKey,
PublicKey,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, FromPrimitive)]
#[repr(u8)]
pub enum PeerstateVerifiedStatus {
Unverified = 0,
//Verified = 1, // not used
BidirectVerified = 2,
}
/// Peerstate represents the state of an Autocrypt peer.
pub struct Peerstate<'a> {
pub context: &'a Context,
pub addr: String,
pub addr: Option<String>,
pub last_seen: i64,
pub last_seen_autocrypt: i64,
pub prefer_encrypt: EncryptPreference,
@@ -100,10 +87,10 @@ pub enum DegradeEvent {
}
impl<'a> Peerstate<'a> {
pub fn new(context: &'a Context, addr: String) -> Self {
pub fn new(context: &'a Context) -> Self {
Peerstate {
context,
addr,
addr: None,
last_seen: 0,
last_seen_autocrypt: 0,
prefer_encrypt: Default::default(),
@@ -120,8 +107,9 @@ impl<'a> Peerstate<'a> {
}
pub fn from_header(context: &'a Context, header: &Aheader, message_time: i64) -> Self {
let mut res = Self::new(context, header.addr.clone());
let mut res = Self::new(context);
res.addr = Some(header.addr.clone());
res.last_seen = message_time;
res.last_seen_autocrypt = message_time;
res.to_save = Some(ToSave::All);
@@ -133,8 +121,9 @@ impl<'a> Peerstate<'a> {
}
pub fn from_gossip(context: &'a Context, gossip_header: &Aheader, message_time: i64) -> Self {
let mut res = Self::new(context, gossip_header.addr.clone());
let mut res = Self::new(context);
res.addr = Some(gossip_header.addr.clone());
res.gossip_timestamp = message_time;
res.to_save = Some(ToSave::All);
res.gossip_key = Some(gossip_header.public_key.clone());
@@ -177,8 +166,9 @@ impl<'a> Peerstate<'a> {
public_key, gossip_timestamp, gossip_key, public_key_fingerprint,
gossip_key_fingerprint, verified_key, verified_key_fingerprint
*/
let mut res = Self::new(context, row.get(0)?);
let mut res = Self::new(context);
res.addr = Some(row.get(0)?);
res.last_seen = row.get(1)?;
res.last_seen_autocrypt = row.get(2)?;
res.prefer_encrypt = EncryptPreference::from_i32(row.get(3)?).unwrap_or_default();
@@ -272,7 +262,9 @@ impl<'a> Peerstate<'a> {
}
pub fn apply_header(&mut self, header: &Aheader, message_time: i64) {
if self.addr.to_lowercase() != header.addr.to_lowercase() {
if self.addr.is_none()
|| self.addr.as_ref().unwrap().to_lowercase() != header.addr.to_lowercase()
{
return;
}
@@ -302,7 +294,9 @@ impl<'a> Peerstate<'a> {
}
pub fn apply_gossip(&mut self, gossip_header: &Aheader, message_time: i64) {
if self.addr.to_lowercase() != gossip_header.addr.to_lowercase() {
if self.addr.is_none()
|| self.addr.as_ref().unwrap().to_lowercase() != gossip_header.addr.to_lowercase()
{
return;
}
@@ -317,26 +311,28 @@ impl<'a> Peerstate<'a> {
};
}
pub fn render_gossip_header(&self, min_verified: PeerstateVerifiedStatus) -> Option<String> {
if let Some(key) = self.peek_key(min_verified) {
// TODO: avoid cloning
let header = Aheader::new(
self.addr.clone(),
key.clone(),
EncryptPreference::NoPreference,
);
Some(header.to_string())
} else {
None
pub fn render_gossip_header(&self, min_verified: usize) -> Option<String> {
if let Some(ref addr) = self.addr {
if let Some(key) = self.peek_key(min_verified) {
// TODO: avoid cloning
let header = Aheader::new(
addr.to_string(),
key.clone(),
EncryptPreference::NoPreference,
);
return Some(header.to_string());
}
}
None
}
pub fn peek_key(&self, min_verified: PeerstateVerifiedStatus) -> Option<&Key> {
pub fn peek_key(&self, min_verified: usize) -> Option<&Key> {
if self.public_key.is_none() && self.gossip_key.is_none() && self.verified_key.is_none() {
return None;
}
if min_verified != PeerstateVerifiedStatus::Unverified {
if 0 != min_verified {
return self.verified_key.as_ref();
}
if self.public_key.is_some() {
@@ -346,51 +342,40 @@ impl<'a> Peerstate<'a> {
self.gossip_key.as_ref()
}
pub fn set_verified(
&mut self,
which_key: PeerstateKeyType,
fingerprint: &str,
verified: PeerstateVerifiedStatus,
) -> bool {
if verified == PeerstateVerifiedStatus::BidirectVerified {
match which_key {
PeerstateKeyType::PublicKey => {
if self.public_key_fingerprint.is_some()
&& self.public_key_fingerprint.as_ref().unwrap() == fingerprint
{
self.to_save = Some(ToSave::All);
self.verified_key = self.public_key.clone();
self.verified_key_fingerprint = self.public_key_fingerprint.clone();
true
} else {
false
}
}
PeerstateKeyType::GossipKey => {
if self.gossip_key_fingerprint.is_some()
&& self.gossip_key_fingerprint.as_ref().unwrap() == fingerprint
{
self.to_save = Some(ToSave::All);
self.verified_key = self.gossip_key.clone();
self.verified_key_fingerprint = self.gossip_key_fingerprint.clone();
true
} else {
false
}
}
pub fn set_verified(&mut self, which_key: usize, fingerprint: &str, verified: usize) -> bool {
let mut success = false;
if !(which_key != 0 && which_key != 1 || verified != 2) {
if which_key == 1
&& self.public_key_fingerprint.is_some()
&& self.public_key_fingerprint.as_ref().unwrap() == fingerprint
{
self.to_save = Some(ToSave::All);
self.verified_key = self.public_key.clone();
self.verified_key_fingerprint = self.public_key_fingerprint.clone();
success = true;
}
if which_key == 0
&& self.gossip_key_fingerprint.is_some()
&& self.gossip_key_fingerprint.as_ref().unwrap() == fingerprint
{
self.to_save = Some(ToSave::All);
self.verified_key = self.gossip_key.clone();
self.verified_key_fingerprint = self.gossip_key_fingerprint.clone();
success = true;
}
} else {
false
}
success
}
pub fn save_to_db(&self, sql: &Sql, create: bool) -> crate::sql::Result<()> {
pub fn save_to_db(&self, sql: &Sql, create: bool) -> Result<()> {
ensure!(!self.addr.is_none(), "self.addr is not configured");
if create {
sql::execute(
self.context,
sql,
"INSERT INTO acpeerstates (addr) VALUES(?);",
params![self.addr],
params![self.addr.as_ref().unwrap()],
)?;
}
@@ -468,7 +453,7 @@ mod tests {
let mut peerstate = Peerstate {
context: &ctx.ctx,
addr: addr.into(),
addr: Some(addr.into()),
last_seen: 10,
last_seen_autocrypt: 11,
prefer_encrypt: EncryptPreference::Mutual,
@@ -513,7 +498,7 @@ mod tests {
let peerstate = Peerstate {
context: &ctx.ctx,
addr: addr.into(),
addr: Some(addr.into()),
last_seen: 10,
last_seen_autocrypt: 11,
prefer_encrypt: EncryptPreference::Mutual,
@@ -551,7 +536,7 @@ mod tests {
let mut peerstate = Peerstate {
context: &ctx.ctx,
addr: addr.into(),
addr: Some(addr.into()),
last_seen: 10,
last_seen_autocrypt: 11,
prefer_encrypt: EncryptPreference::Mutual,

View File

@@ -230,7 +230,6 @@ pub fn pk_encrypt(
Ok(encoded_msg)
}
#[allow(clippy::implicit_hasher)]
pub fn pk_decrypt(
ctext: &[u8],
private_keys_for_decryption: &Keyring,

View File

@@ -139,20 +139,24 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot {
if invitenumber.is_none() || auth.is_none() {
if let Some(peerstate) = peerstate {
lot.state = LotState::QrFprOk;
let addr = peerstate
.addr
.as_ref()
.map(|s| s.as_str())
.unwrap_or_else(|| "");
lot.id = Contact::add_or_lookup(
context,
name,
peerstate.addr.clone(),
Origin::UnhandledQrScan,
)
.map(|(id, _)| id)
.unwrap_or_default();
lot.id = Contact::add_or_lookup(context, name, addr, Origin::UnhandledQrScan)
.map(|(id, _)| id)
.unwrap_or_default();
let (id, _) = chat::create_or_lookup_by_contact_id(context, lot.id, Blocked::Deaddrop)
.unwrap_or_default();
chat::add_info_msg(context, id, format!("{} verified.", peerstate.addr));
chat::add_info_msg(
context,
id,
format!("{} verified.", peerstate.addr.unwrap_or_default()),
);
} else {
lot.state = LotState::QrFprWithoutAddr;
lot.text1 = Some(dc_format_fingerprint(&fingerprint));

View File

@@ -8,13 +8,13 @@ use crate::config::*;
use crate::constants::*;
use crate::contact::*;
use crate::context::Context;
use crate::dc_mimeparser::*;
use crate::e2ee::*;
use crate::error::Error;
use crate::events::Event;
use crate::key::*;
use crate::lot::LotState;
use crate::message::Message;
use crate::mimeparser::*;
use crate::param::*;
use crate::peerstate::*;
use crate::qr::check_qr;
@@ -242,8 +242,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
// Bob -> Alice
while !context.shall_stop_ongoing() {
// Don't sleep too long, the user is waiting.
std::thread::sleep(std::time::Duration::from_millis(200));
std::thread::sleep(std::time::Duration::new(0, 3_000_000));
}
cleanup(&context, contact_chat_id, true, join_vg)
}
@@ -319,54 +318,35 @@ fn fingerprint_equals_sender(
false
}
pub(crate) struct HandshakeMessageStatus {
pub(crate) hide_this_msg: bool,
pub(crate) delete_this_msg: bool,
pub(crate) stop_ongoing_process: bool,
pub(crate) bob_securejoin_success: Option<bool>,
}
impl Default for HandshakeMessageStatus {
fn default() -> Self {
Self {
hide_this_msg: true,
delete_this_msg: false,
stop_ongoing_process: false,
bob_securejoin_success: None,
}
}
}
/// Handle incoming secure-join handshake.
pub(crate) fn handle_securejoin_handshake(
/* library private: secure-join */
pub fn handle_securejoin_handshake(
context: &Context,
mimeparser: &MimeParser,
contact_id: u32,
) -> Result<HandshakeMessageStatus, Error> {
) -> libc::c_int {
let own_fingerprint: String;
ensure!(
contact_id > DC_CONTACT_ID_LAST_SPECIAL,
"handle_securejoin_handshake(): called with special contact id"
);
let step = mimeparser
.lookup_field("Secure-Join")
.ok_or_else(|| format_err!("This message is not a Secure-Join message"))?;
if contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
return 0;
}
let step = match mimeparser.lookup_optional_field("Secure-Join") {
Some(s) => s,
None => {
return 0;
}
};
info!(
context,
">>>>>>>>>>>>>>>>>>>>>>>>> secure-join message \'{}\' received", step,
);
let (contact_chat_id, contact_chat_id_blocked) =
chat::create_or_lookup_by_contact_id(context, contact_id, Blocked::Not).unwrap_or_default();
if contact_chat_id_blocked != Blocked::Not {
chat::unblock(context, contact_chat_id);
}
let mut ret: libc::c_int = DC_HANDSHAKE_STOP_NORMAL_PROCESSING;
let join_vg = step.starts_with("vg-");
let mut ret = HandshakeMessageStatus::default();
match step.as_str() {
"vg-request" | "vc-request" => {
@@ -378,16 +358,16 @@ pub(crate) fn handle_securejoin_handshake(
// it just ensures, we have Bobs key now. If we do _not_ have the key because eg. MitM has removed it,
// send_message() will fail with the error "End-to-end-encryption unavailable unexpectedly.", so, there is no additional check needed here.
// verify that the `Secure-Join-Invitenumber:`-header matches invitenumber written to the QR code
let invitenumber = match mimeparser.lookup_field("Secure-Join-Invitenumber") {
let invitenumber = match mimeparser.lookup_optional_field("Secure-Join-Invitenumber") {
Some(n) => n,
None => {
warn!(context, "Secure-join denied (invitenumber missing).",);
return Ok(ret);
return ret;
}
};
if !token::exists(context, token::Namespace::InviteNumber, &invitenumber) {
warn!(context, "Secure-join denied (bad invitenumber).",);
return Ok(ret);
return ret;
}
info!(context, "Secure-join requested.",);
@@ -413,7 +393,7 @@ pub(crate) fn handle_securejoin_handshake(
if cond {
warn!(context, "auth-required message out of sync.",);
// no error, just aborted somehow or a mail from another handshake
return Ok(ret);
return ret;
}
let scanned_fingerprint_of_alice = get_qr_attr!(context, fingerprint).to_string();
let auth = get_qr_attr!(context, auth).to_string();
@@ -428,9 +408,8 @@ pub(crate) fn handle_securejoin_handshake(
"Not encrypted."
},
);
ret.stop_ongoing_process = true;
ret.bob_securejoin_success = Some(false);
return Ok(ret);
end_bobs_joining(context, DC_BOB_ERROR);
return ret;
}
if !fingerprint_equals_sender(context, &scanned_fingerprint_of_alice, contact_chat_id) {
could_not_establish_secure_connection(
@@ -438,9 +417,8 @@ pub(crate) fn handle_securejoin_handshake(
contact_chat_id,
"Fingerprint mismatch on joiner-side.",
);
ret.stop_ongoing_process = true;
ret.bob_securejoin_success = Some(false);
return Ok(ret);
end_bobs_joining(context, DC_BOB_ERROR);
return ret;
}
info!(context, "Fingerprint verified.",);
own_fingerprint = get_self_fingerprint(context).unwrap();
@@ -467,7 +445,7 @@ pub(crate) fn handle_securejoin_handshake(
==== Step 6 in "Out-of-band verified groups" protocol ====
============================================================ */
// verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob
let fingerprint = match mimeparser.lookup_field("Secure-Join-Fingerprint") {
let fingerprint = match mimeparser.lookup_optional_field("Secure-Join-Fingerprint") {
Some(fp) => fp,
None => {
could_not_establish_secure_connection(
@@ -475,7 +453,7 @@ pub(crate) fn handle_securejoin_handshake(
contact_chat_id,
"Fingerprint not provided.",
);
return Ok(ret);
return ret;
}
};
if !encrypted_and_signed(mimeparser, &fingerprint) {
@@ -484,7 +462,7 @@ pub(crate) fn handle_securejoin_handshake(
contact_chat_id,
"Auth not encrypted.",
);
return Ok(ret);
return ret;
}
if !fingerprint_equals_sender(context, &fingerprint, contact_chat_id) {
could_not_establish_secure_connection(
@@ -492,11 +470,11 @@ pub(crate) fn handle_securejoin_handshake(
contact_chat_id,
"Fingerprint mismatch on inviter-side.",
);
return Ok(ret);
return ret;
}
info!(context, "Fingerprint verified.",);
// verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code
let auth_0 = match mimeparser.lookup_field("Secure-Join-Auth") {
let auth_0 = match mimeparser.lookup_optional_field("Secure-Join-Auth") {
Some(auth) => auth,
None => {
could_not_establish_secure_connection(
@@ -504,12 +482,12 @@ pub(crate) fn handle_securejoin_handshake(
contact_chat_id,
"Auth not provided.",
);
return Ok(ret);
return ret;
}
};
if !token::exists(context, token::Namespace::Auth, &auth_0) {
could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid.");
return Ok(ret);
return ret;
}
if mark_peer_as_verified(context, fingerprint).is_err() {
could_not_establish_secure_connection(
@@ -517,7 +495,7 @@ pub(crate) fn handle_securejoin_handshake(
contact_chat_id,
"Fingerprint mismatch on inviter-side.",
);
return Ok(ret);
return ret;
}
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited);
info!(context, "Auth verified.",);
@@ -526,17 +504,18 @@ pub(crate) fn handle_securejoin_handshake(
inviter_progress!(context, contact_id, 600);
if join_vg {
let field_grpid = mimeparser
.lookup_field("Secure-Join-Group")
.map(|s| s.as_str())
.unwrap_or_else(|| "");
let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, field_grpid);
.lookup_optional_field("Secure-Join-Group")
.unwrap_or_default();
let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid);
if group_chat_id == 0 {
error!(context, "Chat {} not found.", &field_grpid);
return Ok(ret);
} else if let Err(err) =
chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, true)
{
error!(context, "failed to add contact: {}", err);
return ret;
} else {
if let Err(err) =
chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, true)
{
error!(context, "failed to add contact: {}", err);
}
}
} else {
send_handshake_msg(context, contact_chat_id, "vc-contact-confirm", "", None, "");
@@ -545,11 +524,11 @@ pub(crate) fn handle_securejoin_handshake(
}
"vg-member-added" | "vc-contact-confirm" => {
if join_vg {
ret.hide_this_msg = false;
ret = DC_HANDSHAKE_CONTINUE_NORMAL_PROCESSING;
}
if context.bob.read().unwrap().expects != DC_VC_CONTACT_CONFIRM {
info!(context, "Message belongs to a different handshake.",);
return Ok(ret);
return ret;
}
let cond = {
let bob = context.bob.read().unwrap();
@@ -561,7 +540,7 @@ pub(crate) fn handle_securejoin_handshake(
context,
"Message out of sync or belongs to a different handshake.",
);
return Ok(ret);
return ret;
}
let scanned_fingerprint_of_alice = get_qr_attr!(context, fingerprint).to_string();
@@ -585,8 +564,8 @@ pub(crate) fn handle_securejoin_handshake(
contact_chat_id,
"Contact confirm message not encrypted.",
);
ret.bob_securejoin_success = Some(false);
return Ok(ret);
end_bobs_joining(context, DC_BOB_ERROR);
return ret;
}
if mark_peer_as_verified(context, &scanned_fingerprint_of_alice).is_err() {
@@ -595,17 +574,16 @@ pub(crate) fn handle_securejoin_handshake(
contact_chat_id,
"Fingerprint mismatch on joiner-side.",
);
return Ok(ret);
return ret;
}
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined);
emit_event!(context, Event::ContactsChanged(None));
let cg_member_added = mimeparser
.lookup_field("Chat-Group-Member-Added")
.map(|s| s.as_str())
.unwrap_or_else(|| "");
.lookup_optional_field("Chat-Group-Member-Added")
.unwrap_or_default();
if join_vg && !addr_equals_self(context, cg_member_added) {
info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group).");
return Ok(ret);
return ret;
}
secure_connection_established(context, contact_chat_id);
context.bob.write().unwrap().expects = 0;
@@ -619,8 +597,7 @@ pub(crate) fn handle_securejoin_handshake(
"",
);
}
ret.stop_ongoing_process = true;
ret.bob_securejoin_success = Some(true);
end_bobs_joining(context, DC_BOB_SUCCESS);
}
"vg-member-added-received" => {
/* ============================================================
@@ -630,34 +607,28 @@ pub(crate) fn handle_securejoin_handshake(
if let Ok(contact) = Contact::get_by_id(context, contact_id) {
if contact.is_verified(context) == VerifiedStatus::Unverified {
warn!(context, "vg-member-added-received invalid.",);
return Ok(ret);
return ret;
}
inviter_progress!(context, contact_id, 800);
inviter_progress!(context, contact_id, 1000);
let field_grpid = mimeparser
.lookup_field("Secure-Join-Group")
.map(|s| s.as_str())
.unwrap_or_else(|| "");
let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid);
context.call_cb(Event::SecurejoinMemberAdded {
chat_id: group_chat_id,
contact_id,
});
} else {
warn!(context, "vg-member-added-received invalid.",);
return Ok(ret);
return ret;
}
}
_ => {
warn!(context, "invalid step: {}", step);
}
}
if ret.hide_this_msg {
ret.delete_this_msg = true;
if ret == DC_HANDSHAKE_STOP_NORMAL_PROCESSING {
ret |= DC_HANDSHAKE_ADD_DELETE_JOB;
}
ret
}
Ok(ret)
fn end_bobs_joining(context: &Context, status: libc::c_int) {
context.bob.write().unwrap().status = status;
context.stop_ongoing();
}
fn secure_connection_established(context: &Context, contact_chat_id: u32) {
@@ -693,11 +664,7 @@ fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Res
if let Some(ref mut peerstate) =
Peerstate::from_fingerprint(context, &context.sql, fingerprint.as_ref())
{
if peerstate.set_verified(
PeerstateKeyType::PublicKey,
fingerprint.as_ref(),
PeerstateVerifiedStatus::BidirectVerified,
) {
if peerstate.set_verified(1, fingerprint.as_ref(), 2) {
peerstate.prefer_encrypt = EncryptPreference::Mutual;
peerstate.to_save = Some(ToSave::All);
peerstate
@@ -764,8 +731,11 @@ pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) -> Result<
chat::create_or_lookup_by_contact_id(context, contact_id as u32, Blocked::Deaddrop)
.unwrap_or_default();
let msg = context
.stock_string_repl_str(StockMessage::ContactSetupChanged, peerstate.addr.clone());
let peeraddr: &str = match peerstate.addr {
Some(ref addr) => &addr,
None => "",
};
let msg = context.stock_string_repl_str(StockMessage::ContactSetupChanged, peeraddr);
chat::add_info_msg(context, contact_chat_id, msg);
emit_event!(context, Event::ChatModified(contact_chat_id));

189
src/smtp.rs Normal file
View File

@@ -0,0 +1,189 @@
use lettre::smtp::client::net::*;
use lettre::*;
use crate::constants::*;
use crate::context::Context;
use crate::error::Error;
use crate::events::Event;
use crate::login_param::{dc_build_tls_config, LoginParam};
use crate::oauth2::*;
#[derive(DebugStub)]
pub struct Smtp {
#[debug_stub(some = "SmtpTransport")]
transport: Option<lettre::smtp::SmtpTransport>,
transport_connected: bool,
/// Email address we are sending from.
from: Option<EmailAddress>,
}
impl Smtp {
/// Create a new Smtp instances.
pub fn new() -> Self {
Smtp {
transport: None,
transport_connected: false,
from: None,
}
}
/// Disconnect the SMTP transport and drop it entirely.
pub fn disconnect(&mut self) {
if self.transport.is_none() || !self.transport_connected {
return;
}
let mut transport = self.transport.take().unwrap();
transport.close();
self.transport_connected = false;
}
/// Check if a connection already exists.
pub fn is_connected(&self) -> bool {
self.transport.is_some()
}
/// Connect using the provided login params
pub fn connect(&mut self, context: &Context, lp: &LoginParam) -> Result<(), Error> {
if self.is_connected() {
warn!(context, "SMTP already connected.");
return Ok(());
}
if lp.send_server.is_empty() || lp.send_port == 0 {
context.call_cb(Event::ErrorNetwork("SMTP bad parameters.".into()));
bail!("SMTP Bad parameters");
}
self.from = match EmailAddress::new(lp.addr.clone()) {
Ok(addr) => Some(addr),
Err(err) => {
bail!("invalid login address {}: {}", lp.addr, err);
}
};
let domain = &lp.send_server;
let port = lp.send_port as u16;
let tls_config = dc_build_tls_config(lp.smtp_certificate_checks);
let tls_parameters = ClientTlsParameters::new(domain.to_string(), tls_config);
let (creds, mechanism) = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
// oauth2
let addr = &lp.addr;
let send_pw = &lp.send_pw;
let access_token = dc_get_oauth2_access_token(context, addr, send_pw, false);
ensure!(
access_token.is_some(),
"could not get oaut2_access token addr={}",
addr
);
let user = &lp.send_user;
(
lettre::smtp::authentication::Credentials::new(
user.to_string(),
access_token.unwrap_or_default(),
),
vec![lettre::smtp::authentication::Mechanism::Xoauth2],
)
} else {
// plain
let user = lp.send_user.clone();
let pw = lp.send_pw.clone();
(
lettre::smtp::authentication::Credentials::new(user, pw),
vec![
lettre::smtp::authentication::Mechanism::Plain,
lettre::smtp::authentication::Mechanism::Login,
],
)
};
let security = if 0
!= lp.server_flags & (DC_LP_SMTP_SOCKET_STARTTLS | DC_LP_SMTP_SOCKET_PLAIN) as i32
{
lettre::smtp::ClientSecurity::Opportunistic(tls_parameters)
} else {
lettre::smtp::ClientSecurity::Wrapper(tls_parameters)
};
match lettre::smtp::SmtpClient::new((domain.as_str(), port), security) {
Ok(client) => {
let client = client
.smtp_utf8(true)
.credentials(creds)
.authentication_mechanism(mechanism)
.connection_reuse(lettre::smtp::ConnectionReuseParameters::ReuseUnlimited);
let mut trans = client.transport();
match trans.connect() {
Ok(()) => {
self.transport = Some(trans);
self.transport_connected = true;
context.call_cb(Event::SmtpConnected(format!(
"SMTP-LOGIN as {} ok",
lp.send_user,
)));
return Ok(());
}
Err(err) => {
bail!("SMTP: failed to connect {:?}", err);
}
}
}
Err(err) => {
bail!("SMTP: failed to setup connection {:?}", err);
}
}
}
/// SMTP-Send a prepared mail to recipients.
/// on successful send out Ok() is returned.
pub fn send(
&mut self,
context: &Context,
recipients: Vec<EmailAddress>,
message: Vec<u8>,
job_id: u32,
) -> Result<(), Error> {
let message_len = message.len();
let recipients_display = recipients
.iter()
.map(|x| format!("{}", x))
.collect::<Vec<String>>()
.join(",");
if let Some(ref mut transport) = self.transport {
let envelope = match Envelope::new(self.from.clone(), recipients) {
Ok(env) => env,
Err(err) => {
bail!("{}", err);
}
};
let mail = SendableEmail::new(
envelope,
format!("{}", job_id), // only used for internal logging
message,
);
match transport.send(mail) {
Ok(_) => {
context.call_cb(Event::SmtpMessageSent(format!(
"Message len={} was smtp-sent to {}",
message_len, recipients_display
)));
self.transport_connected = true;
Ok(())
}
Err(err) => {
bail!("SMTP failed len={}: error: {}", message_len, err);
}
}
} else {
bail!(
"uh? SMTP has no transport, failed to send to {:?}",
recipients_display
);
}
}
}

View File

@@ -1,148 +0,0 @@
//! # SMTP transport module
pub mod send;
use lettre::smtp::client::net::*;
use lettre::*;
use crate::constants::*;
use crate::context::Context;
use crate::events::Event;
use crate::login_param::{dc_build_tls_config, LoginParam};
use crate::oauth2::*;
#[derive(Debug, Fail)]
pub enum Error {
#[fail(display = "Bad parameters")]
BadParameters,
#[fail(display = "Invalid login address {}: {}", address, error)]
InvalidLoginAddress {
address: String,
#[cause]
error: lettre::error::Error,
},
#[fail(display = "SMTP failed to connect: {:?}", _0)]
ConnectionFailure(#[cause] lettre::smtp::error::Error),
#[fail(display = "SMTP: failed to setup connection {:?}", _0)]
ConnectionSetupFailure(#[cause] lettre::smtp::error::Error),
#[fail(display = "SMTP: oauth2 error {:?}", _0)]
Oauth2Error { address: String },
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Default, DebugStub)]
pub struct Smtp {
#[debug_stub(some = "SmtpTransport")]
transport: Option<lettre::smtp::SmtpTransport>,
transport_connected: bool,
/// Email address we are sending from.
from: Option<EmailAddress>,
}
impl Smtp {
/// Create a new Smtp instances.
pub fn new() -> Self {
Default::default()
}
/// Disconnect the SMTP transport and drop it entirely.
pub fn disconnect(&mut self) {
if self.transport.is_none() || !self.transport_connected {
return;
}
let mut transport = self.transport.take().unwrap();
transport.close();
self.transport_connected = false;
}
/// Check if a connection already exists.
pub fn is_connected(&self) -> bool {
self.transport.is_some()
}
/// Connect using the provided login params
pub fn connect(&mut self, context: &Context, lp: &LoginParam) -> Result<()> {
if self.is_connected() {
warn!(context, "SMTP already connected.");
return Ok(());
}
if lp.send_server.is_empty() || lp.send_port == 0 {
context.call_cb(Event::ErrorNetwork("SMTP bad parameters.".into()));
return Err(Error::BadParameters);
}
let from =
EmailAddress::new(lp.addr.clone()).map_err(|err| Error::InvalidLoginAddress {
address: lp.addr.clone(),
error: err,
})?;
self.from = Some(from);
let domain = &lp.send_server;
let port = lp.send_port as u16;
let tls_config = dc_build_tls_config(lp.smtp_certificate_checks);
let tls_parameters = ClientTlsParameters::new(domain.to_string(), tls_config);
let (creds, mechanism) = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
// oauth2
let addr = &lp.addr;
let send_pw = &lp.send_pw;
let access_token = dc_get_oauth2_access_token(context, addr, send_pw, false);
if access_token.is_none() {
return Err(Error::Oauth2Error {
address: addr.to_string(),
});
}
let user = &lp.send_user;
(
lettre::smtp::authentication::Credentials::new(
user.to_string(),
access_token.unwrap_or_default(),
),
vec![lettre::smtp::authentication::Mechanism::Xoauth2],
)
} else {
// plain
let user = lp.send_user.clone();
let pw = lp.send_pw.clone();
(
lettre::smtp::authentication::Credentials::new(user, pw),
vec![
lettre::smtp::authentication::Mechanism::Plain,
lettre::smtp::authentication::Mechanism::Login,
],
)
};
let security = if 0
!= lp.server_flags & (DC_LP_SMTP_SOCKET_STARTTLS | DC_LP_SMTP_SOCKET_PLAIN) as i32
{
lettre::smtp::ClientSecurity::Opportunistic(tls_parameters)
} else {
lettre::smtp::ClientSecurity::Wrapper(tls_parameters)
};
let client = lettre::smtp::SmtpClient::new((domain.as_str(), port), security)
.map_err(Error::ConnectionSetupFailure)?;
let client = client
.smtp_utf8(true)
.credentials(creds)
.authentication_mechanism(mechanism)
.connection_reuse(lettre::smtp::ConnectionReuseParameters::ReuseUnlimited);
let mut trans = client.transport();
trans.connect().map_err(Error::ConnectionFailure)?;
self.transport = Some(trans);
self.transport_connected = true;
context.call_cb(Event::SmtpConnected(format!(
"SMTP-LOGIN as {} ok",
lp.send_user,
)));
Ok(())
}
}

View File

@@ -1,64 +0,0 @@
//! # SMTP message sending
use super::Smtp;
use lettre::*;
use crate::context::Context;
use crate::events::Event;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Fail)]
pub enum Error {
#[fail(display = "Envelope error: {}", _0)]
EnvelopeError(#[cause] lettre::error::Error),
#[fail(display = "Send error: {}", _0)]
SendError(#[cause] lettre::smtp::error::Error),
#[fail(display = "SMTP has no transport")]
NoTransport,
}
impl Smtp {
/// Send a prepared mail to recipients.
/// On successful send out Ok() is returned.
pub fn send(
&mut self,
context: &Context,
recipients: Vec<EmailAddress>,
message: Vec<u8>,
job_id: u32,
) -> Result<()> {
let message_len = message.len();
let recipients_display = recipients
.iter()
.map(|x| format!("{}", x))
.collect::<Vec<String>>()
.join(",");
if let Some(ref mut transport) = self.transport {
let envelope =
Envelope::new(self.from.clone(), recipients).map_err(Error::EnvelopeError)?;
let mail = SendableEmail::new(
envelope,
format!("{}", job_id), // only used for internal logging
message,
);
transport.send(mail).map_err(Error::SendError)?;
context.call_cb(Event::SmtpMessageSent(format!(
"Message len={} was smtp-sent to {}",
message_len, recipients_display
)));
self.transport_connected = true;
Ok(())
} else {
warn!(
context,
"uh? SMTP has no transport, failed to send to {}", recipients_display
);
Err(Error::NoTransport)
}
}
}

Some files were not shown because too many files have changed in this diff Show More