Compare commits

..

60 Commits

Author SHA1 Message Date
holger krekel
fdf8a052e7 make set_core_version return the versions if no args are specified 2019-12-04 15:48:40 +01:00
dignifiedquire
609b5588fa fix(mimefactory): only send Autocrypt-Gossip headers on encrypted messages 2019-12-04 13:58:33 +01:00
pabzm
798072b8ba Fix new event name in Changelog. 2019-12-04 13:52:02 +01:00
Alexander Krotov
d950a58613 Improve documentation 2019-12-04 10:15:40 +01:00
holger krekel
4c68e6fe41 update deps 2019-12-04 09:11:21 +01:00
holger krekel
914ce77b50 adapt changelog 2019-12-04 08:58:48 +01:00
holger krekel
d09989a4ed fix implementation of Autocrypt to encrypt if answering to an encrypted message.
Previously, if any of a chat's peers set prefer_encrypt to false
(i.e. "e2ee_enabled=0" in the config, a misnomer btw) then a
previously encrypted chat would drop to cleartext easily.
2019-12-04 08:57:42 +01:00
holger krekel
6999a4e3a9 streamline decrypt/encrypt logging 2019-12-04 08:57:42 +01:00
Alexander Krotov
069541f374 Create select_folder::Error 2019-12-04 06:13:24 +01:00
Alexander Krotov
49b9b28c99 Move select_folder to a separate submodule 2019-12-04 06:13:24 +01:00
B. Petersen
b274482125 add welcome-image created by @Simon-Laux 2019-12-04 06:11:42 +01:00
B. Petersen
b766a55b0a in set_chat_name(), bind value to sql using ?-operator, add a test to check the name is really changed 2019-12-04 06:11:01 +01:00
B. Petersen
b8057b7ddb remove >1y old file path migration which is now uneeded and has potential issues 2019-12-04 06:11:01 +01:00
B. Petersen
98266f47d6 bind value to sql using ?-operator 2019-12-04 06:11:01 +01:00
B. Petersen
7b83979e10 test that the device-chat is a one-to-one-chat 2019-12-04 06:01:37 +01:00
B. Petersen
79cdcca2d2 check that sending/preparing/forwarding on a chat that cannot send really fails 2019-12-04 06:01:37 +01:00
B. Petersen
4c0d00640b fix repl forward command 2019-12-04 06:01:37 +01:00
Alexander Krotov
ad87b7c4a5 Fix clippy warnings 2019-12-03 20:20:52 +03:00
holger krekel
74f36b264b fix get_recipients with test 2019-12-03 17:51:44 +01:00
holger krekel
2df43b6c5d avoids double-semicolon problem leading to empty messages in pre-master releases 2019-12-03 16:55:53 +01:00
holger krekel
73f80624b2 adding a draft changelog entry for beta.9 2019-12-03 14:48:57 +01:00
björn petersen
fe2a4b7a4a Merge pull request #904 from deltachat/mailparse
Switch mailparsing to mailparse
2019-12-03 14:16:07 +01:00
dignifiedquire
affdf7241f fixup: remove double mimefactory 2019-12-03 12:26:27 +01:00
holger krekel
1c5a3e6698 remove debug logging and fix update_gossip_headers location 2019-12-03 12:25:22 +01:00
dignifiedquire
db88212a64 refactor: unsafe, CStr and libc moved out 2019-12-03 12:25:22 +01:00
dignifiedquire
43074464ac cleanup mimeparser and use mime 2019-12-03 12:25:22 +01:00
dignifiedquire
1e7afa9da0 fix chat-verified 2019-12-03 12:25:22 +01:00
dignifiedquire
e985887739 fix content-type setting and some encryption 2019-12-03 12:25:22 +01:00
dignifiedquire
d5287256e0 fix various parsing and sending issues 2019-12-03 12:25:22 +01:00
dignifiedquire
6bb2adaf45 some fixes for mdn handling 2019-12-03 12:25:22 +01:00
dignifiedquire
64fcd56998 fix mailparsing test 2019-12-03 12:25:22 +01:00
dignifiedquire
7d3a56a870 switch to forked rust-email 2019-12-03 12:25:22 +01:00
dignifiedquire
48dd3b8506 less dead code, and update test 2019-12-03 12:25:22 +01:00
dignifiedquire
734bbff04d cleanup and finish missing parts 2019-12-03 12:25:22 +01:00
dignifiedquire
5adde12dff implement mdn rendering 2019-12-03 12:25:22 +01:00
dignifiedquire
5079e15401 restructure mimefactory 2019-12-03 12:25:22 +01:00
dignifiedquire
9196ed1e9f remove dead code 2019-12-03 12:22:55 +01:00
dignifiedquire
c69a2406ad integrate encryption when sending 2019-12-03 12:22:55 +01:00
dignifiedquire
47197aa495 Implement message generation using lettre_email 2019-12-03 12:22:55 +01:00
dignifiedquire
dbd6303829 remove mmime 2019-12-03 12:22:55 +01:00
dignifiedquire
1f8d2531cc some things 2019-12-03 12:22:55 +01:00
dignifiedquire
d2de2aef07 first compile 2019-12-03 12:22:55 +01:00
Alexander Krotov
e22b4e8430 Use map_err in Smtp.connect() instead of match 2019-12-03 13:35:55 +03:00
björn petersen
af0b7e4701 Merge pull request #941 from deltachat/calc-device-msg-dimensions
calculate image-size in device-messages
2019-12-03 11:10:02 +01:00
Alexander Krotov
acef386403 Remove useless comment from the configure module 2019-12-03 06:22:05 +00:00
Alexander Krotov
15ba9b6295 Resultify outlk_autodiscover 2019-12-03 06:22:05 +00:00
Alexander Krotov
2f47cf0be5 Resultify moz_autoconfigure 2019-12-03 06:22:05 +00:00
Alexander Krotov
774106fc26 Move read_autoconf_file to its own module 2019-12-03 06:22:05 +00:00
Alexander Krotov
1b0ff9f5be Make parse_xml in auto_mozilla.rs private 2019-12-03 06:22:05 +00:00
Alexander Krotov
dfaa6895ae Add an error type to configure::auto_outlook module 2019-12-03 06:22:05 +00:00
Alexander Krotov
cb52a299cc Add an error type to configure::auto_mozilla module 2019-12-03 06:22:05 +00:00
B. Petersen
0ac0851ef3 add empty lines to stock-strings-enum 2019-12-03 06:21:37 +00:00
B. Petersen
c0f177548a add empty lines to configure-enum 2019-12-03 06:21:37 +00:00
B. Petersen
8923c9e5af calculate image-size in device-messages
eg. android relies on that and fallsback to wrong aspect ratios otherwise.
also, this makes job.rs a little more readable
as some things are moved to message.rs :)
2019-12-03 00:58:04 +01:00
björn petersen
e609ebe3f9 Merge pull request #939 from deltachat/update-device-chats
and a function to create & update device chats
2019-12-03 00:52:52 +01:00
B. Petersen
8617c3d359 target comments of @hpk42, format strum manually as skipped by 'cargo fmt' for some reason 2019-12-02 18:52:07 +01:00
B. Petersen
61e97e5e6f add updatedevicechats command to repl tool 2019-12-02 16:33:24 +01:00
B. Petersen
521e44bd54 add ffi for update_device_chats() 2019-12-02 16:33:24 +01:00
B. Petersen
18e91c480b add tests for update_device_chats() 2019-12-02 16:33:24 +01:00
B. Petersen
9d2e6e1c31 add update_device_chats() 2019-12-02 16:33:24 +01:00
47 changed files with 1010 additions and 772 deletions

View File

@@ -1,5 +1,34 @@
# Changelog
## 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

28
Cargo.lock generated
View File

@@ -607,7 +607,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.0.0-beta.8"
version = "1.0.0-beta.9"
dependencies = [
"async-imap 0.1.1 (git+https://github.com/async-email/async-imap)",
"async-std 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -682,9 +682,9 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.0.0-beta.8"
version = "1.0.0-beta.9"
dependencies = [
"deltachat 1.0.0-beta.8",
"deltachat 1.0.0-beta.9",
"deltachat-provider-database 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"human-panic 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1152,7 +1152,7 @@ dependencies = [
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
"http 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
"http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
"indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1192,7 +1192,7 @@ dependencies = [
[[package]]
name = "http"
version = "0.1.20"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1207,7 +1207,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
"http 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
"http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -1249,7 +1249,7 @@ dependencies = [
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
"http 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
"http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
"http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1323,7 +1323,7 @@ dependencies = [
[[package]]
name = "imap-proto"
version = "0.9.1"
source = "git+https://github.com/djc/tokio-imap#a26056915f1d715f97935da1f0c97c6d0174f292"
source = "git+https://github.com/djc/tokio-imap#2b8701b83c50085b7ffcdf26a95146b91f93a6d5"
dependencies = [
"nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -1456,7 +1456,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
"vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
"vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1803,7 +1803,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "pgp"
version = "0.3.2"
source = "git+https://github.com/rpgp/rpgp#4cc60a1e45a781ea6e7f394ae2583844ac75d214"
source = "git+https://github.com/rpgp/rpgp#8928a249f848d46889f618fde914417f698ea76f"
dependencies = [
"aes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2263,7 +2263,7 @@ dependencies = [
"encoding_rs 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
"flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
"http 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
"http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.12.35 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper-rustls 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3069,7 +3069,7 @@ dependencies = [
[[package]]
name = "vcpkg"
version = "0.2.7"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -3477,7 +3477,7 @@ dependencies = [
"checksum hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120"
"checksum hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "023b39be39e3a2da62a94feb433e91e8bcd37676fbc8bea371daf52b7a769a3e"
"checksum hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e"
"checksum http 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "2790658cddc82e82b08e25176c431d7015a0adeb1718498715cbd20138a0bf68"
"checksum http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0"
"checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d"
"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
"checksum human-panic 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "21638c5955a6daf3ecc42cae702335fc37a72a4abcc6959ce457b31a7d43bbdd"
@@ -3679,7 +3679,7 @@ dependencies = [
"checksum utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d"
"checksum uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e1436e58182935dcd9ce0add9ea0b558e8a87befe01c1a301e6020aeb0876363"
"checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a"
"checksum vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "33dd455d0f96e90a75803cfeb7f948768c08d70a6de9a8d2362461935698bf95"
"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.0.0-beta.8"
version = "1.0.0-beta.9"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
license = "MPL"

BIN
assets/welcome-image.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.0.0-beta.8"
version = "1.0.0-beta.9"
description = "Deltachat FFI"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"

View File

@@ -1139,6 +1139,24 @@ void dc_set_draft (dc_context_t* context, uint32_t ch
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().

View File

@@ -851,6 +851,21 @@ pub unsafe extern "C" fn dc_add_device_msg(
.unwrap_or(0)
}
#[no_mangle]
pub unsafe extern "C" fn dc_update_device_chats(context: *mut dc_context_t) {
if context.is_null() {
eprintln!("ignoring careless call to dc_update_device_chats()");
return;
}
let ffi_context = &mut *context;
ffi_context
.with_inner(|ctx| {
ctx.update_device_chats()
.unwrap_or_log_default(ctx, "Failed to add device message")
})
.unwrap_or(())
}
#[no_mangle]
pub unsafe extern "C" fn dc_was_device_msg_ever_added(
context: *mut dc_context_t,

View File

@@ -832,6 +832,9 @@ 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.");
@@ -876,7 +879,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"
);

View File

@@ -629,6 +629,45 @@ class TestOnlineAccount:
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
assert not msg.is_encrypted()
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()

View File

@@ -33,6 +33,8 @@ def replace_toml_version(relpath, newversion):
if __name__ == "__main__":
if len(sys.argv) < 2:
for x in ("Cargo.toml", "deltachat-ffi/Cargo.toml"):
print("{}: {}".format(x, read_toml_version(x)))
raise SystemExit("need argument: new version, example 1.0.0-beta.27")
newversion = sys.argv[1]
if newversion.count(".") < 2:

View File

@@ -483,23 +483,23 @@ mod tests {
#[test]
fn test_suffix() {
let t = dummy_context();
let foo = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
assert_eq!(foo.suffix(), Some("txt"));
let bar = BlobObject::create(&t.ctx, "bar", b"world").unwrap();
assert_eq!(bar.suffix(), None);
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
assert_eq!(blob.suffix(), Some("txt"));
let blob = BlobObject::create(&t.ctx, "bar", b"world").unwrap();
assert_eq!(blob.suffix(), None);
}
#[test]
fn test_create_dup() {
let t = dummy_context();
BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
let foo = t.ctx.get_blobdir().join("foo.txt");
assert!(foo.exists());
let foo_path = t.ctx.get_blobdir().join("foo.txt");
assert!(foo_path.exists());
BlobObject::create(&t.ctx, "foo.txt", b"world").unwrap();
for dirent in fs::read_dir(t.ctx.get_blobdir()).unwrap() {
let fname = dirent.unwrap().file_name();
if fname == foo.file_name().unwrap() {
assert_eq!(fs::read(&foo).unwrap(), b"hello");
if fname == foo_path.file_name().unwrap() {
assert_eq!(fs::read(&foo_path).unwrap(), b"hello");
} else {
let name = fname.to_str().unwrap();
assert!(name.starts_with("foo"));
@@ -512,13 +512,13 @@ mod tests {
fn test_double_ext_preserved() {
let t = dummy_context();
BlobObject::create(&t.ctx, "foo.tar.gz", b"hello").unwrap();
let foo = t.ctx.get_blobdir().join("foo.tar.gz");
assert!(foo.exists());
let foo_path = t.ctx.get_blobdir().join("foo.tar.gz");
assert!(foo_path.exists());
BlobObject::create(&t.ctx, "foo.tar.gz", b"world").unwrap();
for dirent in fs::read_dir(t.ctx.get_blobdir()).unwrap() {
let fname = dirent.unwrap().file_name();
if fname == foo.file_name().unwrap() {
assert_eq!(fs::read(&foo).unwrap(), b"hello");
if fname == foo_path.file_name().unwrap() {
assert_eq!(fs::read(&foo_path).unwrap(), b"hello");
} else {
let name = fname.to_str().unwrap();
println!("{}", name);

View File

@@ -259,8 +259,6 @@ impl Chat {
msg: &mut Message,
timestamp: i64,
) -> Result<MsgId, Error> {
let mut do_guarantee_e2ee: bool;
let e2ee_enabled: bool;
let mut new_references = "".into();
let mut new_in_reply_to = "".into();
let mut msg_id = 0;
@@ -311,24 +309,20 @@ impl Chat {
self.id
);
}
} else {
if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup {
if self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
self.param.remove(Param::Unpromoted);
self.update_param(context)?;
}
}
} else if self.typ == Chattype::Group
|| self.typ == Chattype::VerifiedGroup
&& self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1
{
self.param.remove(Param::Unpromoted);
self.update_param(context)?;
}
/* check if we can guarantee E2EE for this message.
if we guarantee E2EE, and circumstances change
/* check if we want to encrypt this message. If yes and circumstances change
so that E2EE is no longer available at a later point (reset, changed settings),
we do not send the message out at all */
do_guarantee_e2ee = false;
e2ee_enabled = context.get_config_bool(Config::E2eeEnabled);
if e2ee_enabled && msg.param.get_int(Param::ForcePlaintext).unwrap_or_default() == 0 {
we might not send the message out at all */
if msg.param.get_int(Param::ForcePlaintext).unwrap_or_default() == 0 {
let mut can_encrypt = true;
let mut all_mutual = true;
let mut all_mutual = context.get_config_bool(Config::E2eeEnabled);
// take care that this statement returns NULL rows
// if there is no peerstates for a chat member!
@@ -375,18 +369,13 @@ impl Chat {
}
}
if can_encrypt {
if all_mutual {
do_guarantee_e2ee = true;
} else if last_msg_in_chat_encrypted(context, &context.sql, self.id) {
do_guarantee_e2ee = true;
}
if can_encrypt
&& (all_mutual || last_msg_in_chat_encrypted(context, &context.sql, self.id))
{
msg.param.set_int(Param::GuaranteeE2ee, 1);
}
}
if do_guarantee_e2ee {
msg.param.set_int(Param::GuaranteeE2ee, 1);
}
// reset eg. for forwarding
// reset encrypt error state eg. for forwarding
msg.param.remove(Param::ErroneousE2ee);
// set "In-Reply-To:" to identify the message to which the composed message is a reply;
@@ -419,15 +408,15 @@ impl Chat {
} else if !parent_in_reply_to.is_empty() && !parent_rfc724_mid.is_empty() {
new_references = format!("{} {}", parent_in_reply_to, parent_rfc724_mid);
} else if !parent_in_reply_to.is_empty() {
new_references = parent_in_reply_to.clone();
new_references = parent_in_reply_to;
}
}
}
// add independent location to database
if msg.param.exists(Param::SetLatitude) {
if sql::execute(
if msg.param.exists(Param::SetLatitude)
&& sql::execute(
context,
&context.sql,
"INSERT INTO locations \
@@ -442,17 +431,16 @@ impl Chat {
],
)
.is_ok()
{
location_id = sql::get_rowid2(
context,
&context.sql,
"locations",
"timestamp",
timestamp,
"from_id",
DC_CONTACT_ID_SELF as i32,
);
}
{
location_id = sql::get_rowid2(
context,
&context.sql,
"locations",
"timestamp",
timestamp,
"from_id",
DC_CONTACT_ID_SELF as i32,
);
}
// add message to the database
@@ -509,7 +497,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.
///
@@ -874,17 +862,14 @@ pub fn send_msg(context: &Context, chat_id: u32, msg: &mut Message) -> Result<Ms
let forwards = msg.param.get(Param::PrepForwards);
if let Some(forwards) = forwards {
for forward in forwards.split(' ') {
match forward
if let Ok(msg_id) = forward
.parse::<u32>()
.map_err(|_| InvalidMsgId)
.map(|id| MsgId::new(id))
.map(MsgId::new)
{
Ok(msg_id) => {
if let Ok(mut msg) = Message::load_from_db(context, msg_id) {
send_msg(context, 0, &mut msg)?;
};
}
Err(_) => (),
if let Ok(mut msg) = Message::load_from_db(context, msg_id) {
send_msg(context, 0, &mut msg)?;
};
}
}
msg.param.remove(Param::PrepForwards);
@@ -1726,12 +1711,8 @@ pub fn set_chat_name(
if sql::execute(
context,
&context.sql,
format!(
"UPDATE chats SET name='{}' WHERE id={};",
new_name.as_ref(),
chat_id as i32
),
params![],
"UPDATE chats SET name=? WHERE id=?;",
params![new_name.as_ref(), chat_id as i32],
)
.is_ok()
{
@@ -1849,6 +1830,7 @@ pub fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: u32) -> Resul
unarchive(context, chat_id)?;
if let Ok(mut chat) = Chat::load_from_db(context, chat_id) {
ensure!(chat.can_send(), "cannot send to chat #{}", chat_id);
curr_timestamp = dc_create_smeared_timestamps(context, msg_ids.len());
let ids = context.sql.query_map(
format!(
@@ -1984,6 +1966,7 @@ pub fn add_device_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)?;
@@ -2211,6 +2194,7 @@ mod tests {
let chat = Chat::load_from_db(&t.ctx, chat_id);
assert!(chat.is_ok());
let chat = chat.unwrap();
assert_eq!(chat.get_type(), Chattype::Single);
assert!(chat.is_device_talk());
assert!(!chat.is_self_talk());
assert!(!chat.can_send());
@@ -2278,6 +2262,22 @@ mod tests {
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()
@@ -2333,4 +2333,20 @@ 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

@@ -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

View File

@@ -28,27 +28,38 @@ pub enum Config {
SendPort,
SmtpCertificateChecks,
ServerFlags,
#[strum(props(default = "INBOX"))]
ImapFolder,
Displayname,
Selfstatus,
Selfavatar,
#[strum(props(default = "0"))]
BccSelf,
#[strum(props(default = "1"))]
E2eeEnabled,
#[strum(props(default = "1"))]
MdnsEnabled,
#[strum(props(default = "1"))]
InboxWatch,
#[strum(props(default = "1"))]
SentboxWatch,
#[strum(props(default = "1"))]
MvboxWatch,
#[strum(props(default = "1"))]
MvboxMove,
#[strum(props(default = "0"))] // also change ShowEmails.default() on changes
ShowEmails,
SaveMimeHeaders,
ConfiguredAddr,
ConfiguredMailServer,
@@ -66,11 +77,13 @@ pub enum Config {
ConfiguredSendSecurity,
ConfiguredE2EEEnabled,
Configured,
// Deprecated
#[strum(serialize = "sys.version")]
SysVersion,
#[strum(serialize = "sys.msgsize_max_recommended")]
SysMsgsizeMaxRecommended,
#[strum(serialize = "sys.config_keys")]
SysConfigKeys,
}

View File

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

View File

@@ -1,14 +1,43 @@
//! Outlook's Autodiscover
use failure::Fail;
use quick_xml;
use quick_xml::events::BytesEnd;
use crate::constants::*;
use crate::context::Context;
use crate::error::Error;
use crate::login_param::LoginParam;
use super::read_autoconf_file;
use super::read_url::read_url;
#[derive(Debug, Fail)]
pub enum Error {
#[fail(display = "XML error at position {}", position)]
InvalidXml {
position: usize,
#[cause]
error: quick_xml::Error,
},
#[fail(display = "Bad or incomplete autoconfig")]
IncompleteAutoconfig(LoginParam),
#[fail(display = "Failed to get URL {}", _0)]
ReadUrlError(#[cause] super::read_url::Error),
#[fail(display = "Number of redirection is exceeded")]
RedirectionError,
}
pub type Result<T> = std::result::Result<T, Error>;
impl From<super::read_url::Error> for Error {
fn from(err: super::read_url::Error) -> Error {
Error::ReadUrlError(err)
}
}
/// Outlook's Autodiscover
struct OutlookAutodiscover {
pub out: LoginParam,
pub out_imap_set: bool,
@@ -25,7 +54,7 @@ enum ParsingResult {
RedirectUrl(String),
}
fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
fn parse_xml(xml_raw: &str) -> Result<ParsingResult> {
let mut outlk_ad = OutlookAutodiscover {
out: LoginParam::new(),
out_imap_set: false,
@@ -45,8 +74,15 @@ fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
let mut current_tag: Option<String> = None;
loop {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
let event = reader
.read_event(&mut buf)
.map_err(|error| Error::InvalidXml {
position: reader.buffer_position(),
error,
})?;
match event {
quick_xml::events::Event::Start(ref e) => {
let tag = String::from_utf8_lossy(e.name()).trim().to_lowercase();
if tag == "protocol" {
@@ -61,11 +97,11 @@ fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
current_tag = Some(tag);
}
}
Ok(quick_xml::events::Event::End(ref e)) => {
quick_xml::events::Event::End(ref e) => {
outlk_autodiscover_endtag_cb(e, &mut outlk_ad);
current_tag = None;
}
Ok(quick_xml::events::Event::Text(ref e)) => {
quick_xml::events::Event::Text(ref e) => {
let val = e.unescape_and_decode(&reader).unwrap_or_default();
if let Some(ref tag) = current_tag {
@@ -81,21 +117,14 @@ fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
};
}
}
Err(e) => {
bail!(
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
);
}
Ok(quick_xml::events::Event::Eof) => break,
quick_xml::events::Event::Eof => break,
_ => (),
}
buf.clear();
}
// XML redirect via redirecturl
if outlk_ad.config_redirecturl.is_none()
let res = if outlk_ad.config_redirecturl.is_none()
|| outlk_ad.config_redirecturl.as_ref().unwrap().is_empty()
{
if outlk_ad.out.mail_server.is_empty()
@@ -103,41 +132,34 @@ fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
|| outlk_ad.out.send_server.is_empty()
|| outlk_ad.out.send_port == 0
{
let r = outlk_ad.out.to_string();
bail!("Bad or incomplete autoconfig: {}", r,);
return Err(Error::IncompleteAutoconfig(outlk_ad.out));
}
Ok(ParsingResult::LoginParam(outlk_ad.out))
ParsingResult::LoginParam(outlk_ad.out)
} else {
Ok(ParsingResult::RedirectUrl(
outlk_ad.config_redirecturl.unwrap(),
))
}
ParsingResult::RedirectUrl(outlk_ad.config_redirecturl.unwrap())
};
Ok(res)
}
pub fn outlk_autodiscover(
context: &Context,
url: &str,
_param_in: &LoginParam,
) -> Option<LoginParam> {
) -> Result<LoginParam> {
let mut url = url.to_string();
/* Follow up to 10 xml-redirects (http-redirects are followed in read_autoconf_file() */
/* Follow up to 10 xml-redirects (http-redirects are followed in read_url() */
for _i in 0..10 {
if let Some(xml_raw) = read_autoconf_file(context, &url) {
match outlk_parse_xml(&xml_raw) {
Err(err) => {
warn!(context, "{}", err);
return None;
}
Ok(res) => match res {
ParsingResult::RedirectUrl(redirect_url) => url = redirect_url,
ParsingResult::LoginParam(login_param) => return Some(login_param),
},
}
} else {
return None;
let xml_raw = read_url(context, &url)?;
let res = parse_xml(&xml_raw);
if let Err(err) = &res {
warn!(context, "{}", err);
}
match res? {
ParsingResult::RedirectUrl(redirect_url) => url = redirect_url,
ParsingResult::LoginParam(login_param) => return Ok(login_param),
}
}
None
Err(Error::RedirectionError)
}
fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut OutlookAutodiscover) {
@@ -179,7 +201,7 @@ mod tests {
#[test]
fn test_parse_redirect() {
let res = outlk_parse_xml("
let res = parse_xml("
<?xml version=\"1.0\" encoding=\"utf-8\"?>
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
<Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">
@@ -206,7 +228,7 @@ mod tests {
#[test]
fn test_parse_loginparam() {
let res = outlk_parse_xml(
let res = parse_xml(
"\
<?xml version=\"1.0\" encoding=\"utf-8\"?>
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">

View File

@@ -1,5 +1,9 @@
//! 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;
@@ -12,10 +16,8 @@ use crate::login_param::LoginParam;
use crate::oauth2::*;
use crate::param::Params;
mod auto_outlook;
use auto_outlook::outlk_autodiscover;
mod auto_mozilla;
use auto_mozilla::moz_autoconfigure;
use auto_outlook::outlk_autodiscover;
macro_rules! progress {
($context:tt, $progress:expr) => {
@@ -166,7 +168,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);
param_autoconfig = moz_autoconfigure(context, &url, &param).ok();
}
true
}
@@ -178,7 +180,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);
param_autoconfig = moz_autoconfigure(context, &url, &param).ok();
}
true
}
@@ -191,7 +193,7 @@ pub fn JobConfigureImap(context: &Context) {
"https://{}{}/autodiscover/autodiscover.xml",
"", param_domain
);
param_autoconfig = outlk_autodiscover(context, &url, &param);
param_autoconfig = outlk_autodiscover(context, &url, &param).ok();
}
true
}
@@ -202,7 +204,7 @@ pub fn JobConfigureImap(context: &Context) {
"https://{}{}/autodiscover/autodiscover.xml",
"autodiscover.", param_domain
);
param_autoconfig = outlk_autodiscover(context, &url, &param);
param_autoconfig = outlk_autodiscover(context, &url, &param).ok();
}
true
}
@@ -214,7 +216,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);
param_autoconfig = moz_autoconfigure(context, &url, &param).ok();
}
true
}
@@ -226,7 +228,7 @@ pub fn JobConfigureImap(context: &Context) {
"http://{}/.well-known/autoconfig/mail/config-v1.1.xml",
param_domain
);
param_autoconfig = moz_autoconfigure(context, &url, &param);
param_autoconfig = moz_autoconfigure(context, &url, &param).ok();
}
true
}
@@ -236,7 +238,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);
param_autoconfig = moz_autoconfigure(context, &url, &param).ok();
}
true
}
@@ -562,27 +564,6 @@ 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 {

27
src/configure/read_url.rs Normal file
View File

@@ -0,0 +1,27 @@
use crate::context::Context;
use failure::Fail;
#[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

@@ -23,15 +23,17 @@ use crate::stock::StockMessage;
const DC_ORIGIN_MIN_CONTACT_LIST: i32 = 0x100;
/// An object representing a single contact in memory.
///
/// The contact object is not updated.
/// If you want an update, you have to recreate the object.
///
/// The library makes sure
/// only to use names _authorized_ by the contact in `To:` or `Cc:`.
/// _Given-names _as "Daddy" or "Honey" are not used there.
/// *Given-names* as "Daddy" or "Honey" are not used there.
/// For this purpose, internally, two names are tracked -
/// authorized-name and given-name.
/// authorized name and given name.
/// By default, these names are equal, but functions working with contact names
/// only affect the given name.
#[derive(Debug)]
pub struct Contact {
/// The contact ID.
@@ -106,11 +108,6 @@ 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
@@ -208,7 +205,7 @@ impl Contact {
/// Add a single contact as a result of an _explicit_ user action.
///
/// We assume, the contact name, if any, is entered by the user and is used "as is" therefore,
/// normalize() is _not_ called for the name. If the contact is blocked, it is unblocked.
/// normalize() is *not* called for the name. If the contact is blocked, it is unblocked.
///
/// To add a number of contacts, see `dc_add_address_book()` which is much faster for adding
/// a bunch of addresses.
@@ -238,7 +235,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) {
@@ -428,7 +425,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`.
///
@@ -906,7 +903,7 @@ impl Contact {
}
/// Extracts first name from full name.
fn get_first_name<'a>(full_name: &'a str) -> &'a str {
fn get_first_name(full_name: &str) -> &str {
full_name.splitn(2, ' ').next().unwrap_or_default()
}
@@ -933,21 +930,21 @@ fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
}
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
if contact.blocked != new_blocking {
if sql::execute(
if contact.blocked != new_blocking
&& sql::execute(
context,
&context.sql,
"UPDATE contacts SET blocked=? WHERE id=?;",
params![new_blocking as i32, contact_id as i32],
)
.is_ok()
{
// also (un)block all chats with _only_ this contact - we do not delete them to allow a
// non-destructive blocking->unblocking.
// (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
// However, I'm not sure about this point; it may be confusing if the user wants to add other people;
// this would result in recreating the same group...)
if sql::execute(
{
// also (un)block all chats with _only_ this contact - we do not delete them to allow a
// non-destructive blocking->unblocking.
// (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
// However, I'm not sure about this point; it may be confusing if the user wants to add other people;
// this would result in recreating the same group...)
if sql::execute(
context,
&context.sql,
"UPDATE chats SET blocked=? WHERE type=? AND id IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
@@ -956,7 +953,6 @@ fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
Contact::mark_noticed(context, contact_id);
context.call_cb(Event::ContactsChanged(None));
}
}
}
}
}

View File

@@ -1,3 +1,5 @@
//! Contacts module
use std::collections::HashMap;
use std::ffi::OsString;
use std::path::{Path, PathBuf};

View File

@@ -172,7 +172,7 @@ pub fn dc_receive_imf(
// client that relies in the SMTP server to generate one.
// true eg. for the Webmailer used in all-inkl-KAS
match dc_create_incoming_rfc724_mid(sent_timestamp, from_id, &to_ids) {
Some(x) => x.to_string(),
Some(x) => x,
None => {
error!(context, "can not create incoming rfc724_mid");
cleanup(
@@ -407,14 +407,12 @@ fn add_parts(
// try to create a group
// (groups appear automatically only if the _sender_ is known, see core issue #54)
let create_blocked = if 0 != test_normal_chat_id
&& test_normal_chat_id_blocked == Blocked::Not
|| incoming_origin.is_start_new_chat()
{
Blocked::Not
} else {
Blocked::Deaddrop
};
let create_blocked =
if 0 != test_normal_chat_id && test_normal_chat_id_blocked == Blocked::Not {
Blocked::Not
} else {
Blocked::Deaddrop
};
create_or_lookup_group(
context,
@@ -442,7 +440,7 @@ fn add_parts(
if *chat_id == 0 {
// try to create a normal chat
let create_blocked = if incoming_origin.is_start_new_chat() || *from_id == *to_id {
let create_blocked = if *from_id == *to_id {
Blocked::Not
} else {
Blocked::Deaddrop
@@ -545,20 +543,18 @@ fn add_parts(
}
}
}
if *chat_id == 0 {
if to_ids.is_empty() && to_self {
// from_id==to_id==DC_CONTACT_ID_SELF - this is a self-sent messages,
// maybe an Autocrypt Setup Messag
let (id, bl) =
chat::create_or_lookup_by_contact_id(context, DC_CONTACT_ID_SELF, Blocked::Not)
.unwrap_or_default();
*chat_id = id;
chat_id_blocked = bl;
if *chat_id == 0 && to_ids.is_empty() && to_self {
// from_id==to_id==DC_CONTACT_ID_SELF - this is a self-sent messages,
// maybe an Autocrypt Setup Messag
let (id, bl) =
chat::create_or_lookup_by_contact_id(context, DC_CONTACT_ID_SELF, Blocked::Not)
.unwrap_or_default();
*chat_id = id;
chat_id_blocked = bl;
if 0 != *chat_id && Blocked::Not != chat_id_blocked {
chat::unblock(context, *chat_id);
chat_id_blocked = Blocked::Not;
}
if 0 != *chat_id && Blocked::Not != chat_id_blocked {
chat::unblock(context, *chat_id);
chat_id_blocked = Blocked::Not;
}
}
if *chat_id == 0 {
@@ -629,7 +625,7 @@ fn add_parts(
.subject
.as_ref()
.map(|s| s.to_string())
.unwrap_or("".into());
.unwrap_or_else(|| "".into());
txt_raw = Some(format!("{}\n\n{}", subject, msg_raw));
}
if mime_parser.is_system_message != SystemMessage::Unknown {
@@ -705,7 +701,7 @@ fn save_locations(
hidden: i32,
) {
if chat_id <= DC_CHAT_ID_LAST_SPECIAL {
return ();
return;
}
let mut location_id_written = false;
let mut send_event = false;
@@ -714,11 +710,12 @@ fn save_locations(
let locations = &mime_parser.message_kml.as_ref().unwrap().locations;
let newest_location_id =
location::save(context, chat_id, from_id, locations, true).unwrap_or_default();
if 0 != newest_location_id && 0 == hidden {
if location::set_msg_location_id(context, insert_msg_id, newest_location_id).is_ok() {
location_id_written = true;
send_event = true;
}
if 0 != newest_location_id
&& 0 == hidden
&& location::set_msg_location_id(context, insert_msg_id, newest_location_id).is_ok()
{
location_id_written = true;
send_event = true;
}
}
@@ -772,10 +769,8 @@ fn calc_timestamps(
params![chat_id as i32, from_id as i32, *sort_timestamp],
);
if let Some(last_msg_time) = last_msg_time {
if last_msg_time > 0 {
if *sort_timestamp <= last_msg_time {
*sort_timestamp = last_msg_time + 1;
}
if last_msg_time > 0 && *sort_timestamp <= last_msg_time {
*sort_timestamp = last_msg_time + 1;
}
}
}
@@ -916,11 +911,11 @@ fn create_or_lookup_group(
)
} else {
let field = mime_parser.lookup_field("Chat-Group-Name-Changed");
if field.is_some() {
if let Some(field) = field {
X_MrGrpNameChanged = 1;
better_msg = context.stock_system_msg(
StockMessage::MsgGrpName,
&field.unwrap(),
field,
if let Some(ref name) = grpname {
name
} else {
@@ -930,23 +925,22 @@ fn create_or_lookup_group(
);
mime_parser.is_system_message = SystemMessage::GroupNameChanged;
} else {
if let Some(optional_field) = mime_parser.lookup_field("Chat-Group-Image").cloned()
{
// fld_value is a pointer somewhere into mime_parser, must not be freed
X_MrGrpImageChanged = optional_field;
mime_parser.is_system_message = SystemMessage::GroupImageChanged;
better_msg = context.stock_system_msg(
if X_MrGrpImageChanged == "0" {
StockMessage::MsgGrpImgDeleted
} else {
StockMessage::MsgGrpImgChanged
},
"",
"",
from_id as u32,
)
}
} else if let Some(optional_field) =
mime_parser.lookup_field("Chat-Group-Image").cloned()
{
// fld_value is a pointer somewhere into mime_parser, must not be freed
X_MrGrpImageChanged = optional_field;
mime_parser.is_system_message = SystemMessage::GroupImageChanged;
better_msg = context.stock_system_msg(
if X_MrGrpImageChanged == "0" {
StockMessage::MsgGrpImgDeleted
} else {
StockMessage::MsgGrpImgChanged
},
"",
"",
from_id as u32,
)
}
}
}
@@ -954,15 +948,11 @@ fn create_or_lookup_group(
// check, if we have a chat with this group ID
let (mut chat_id, chat_id_verified, _blocked) = chat::get_chat_id_by_grpid(context, &grpid);
if chat_id != 0 {
if chat_id_verified {
if let Err(err) =
check_verified_properties(context, mime_parser, from_id as u32, to_ids)
{
warn!(context, "verification problem: {}", err);
let s = format!("{}. See 'Info' for more details", err);
mime_parser.repl_msg_by_error(s);
}
if chat_id != 0 && chat_id_verified {
if let Err(err) = check_verified_properties(context, mime_parser, from_id as u32, to_ids) {
warn!(context, "verification problem: {}", err);
let s = format!("{}. See 'Info' for more details", err);
mime_parser.repl_msg_by_error(s);
}
}
@@ -1115,13 +1105,12 @@ fn create_or_lookup_group(
if skip.is_none() || !addr_cmp(&self_addr, skip.unwrap()) {
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF);
}
if from_id > DC_CHAT_ID_LAST_SPECIAL {
if !Contact::addr_equals_contact(context, &self_addr, from_id as u32)
&& (skip.is_none()
|| !Contact::addr_equals_contact(context, skip.unwrap(), from_id as u32))
{
chat::add_to_chat_contacts_table(context, chat_id, from_id as u32);
}
if from_id > DC_CHAT_ID_LAST_SPECIAL
&& !Contact::addr_equals_contact(context, &self_addr, from_id as u32)
&& (skip.is_none()
|| !Contact::addr_equals_contact(context, skip.unwrap(), from_id as u32))
{
chat::add_to_chat_contacts_table(context, chat_id, from_id as u32);
}
for &to_id in to_ids.iter() {
if !Contact::addr_equals_contact(context, &self_addr, to_id)
@@ -1160,7 +1149,7 @@ fn create_or_lookup_group(
}
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
return Ok(());
Ok(())
}
/// Handle groups for received messages
@@ -1352,7 +1341,7 @@ fn hex_hash(s: impl AsRef<str>) -> String {
#[allow(non_snake_case)]
fn search_chat_ids_by_contact_ids(
context: &Context,
unsorted_contact_ids: &Vec<u32>,
unsorted_contact_ids: &[u32],
) -> Result<Vec<u32>> {
/* searches chat_id's by the given contact IDs, may return zero, one or more chat_id's */
let mut contact_ids = Vec::with_capacity(23);
@@ -1508,7 +1497,7 @@ fn check_verified_properties(
fn set_better_msg(mime_parser: &mut MimeParser, better_msg: impl AsRef<str>) {
let msg = better_msg.as_ref();
if msg.len() > 0 && !mime_parser.parts.is_empty() {
if !msg.is_empty() && !mime_parser.parts.is_empty() {
let part = &mut mime_parser.parts[0];
if part.typ == Viewtype::Text {
part.msg = msg.to_string();
@@ -1535,12 +1524,12 @@ fn dc_is_reply_to_known_message(context: &Context, mime_parser: &MimeParser) ->
0
}
fn is_known_rfc724_mid_in_list(context: &Context, mid_list: &String) -> bool {
fn is_known_rfc724_mid_in_list(context: &Context, mid_list: &str) -> bool {
if mid_list.is_empty() {
return false;
}
if let Ok(ids) = mailparse::addrparse(mid_list.as_str()) {
if let Ok(ids) = mailparse::addrparse(mid_list) {
for id in ids.iter() {
if is_known_rfc724_mid(context, id) {
return true;
@@ -1588,8 +1577,8 @@ fn dc_is_reply_to_messenger_message(context: &Context, mime_parser: &MimeParser)
0
}
fn is_msgrmsg_rfc724_mid_in_list(context: &Context, mid_list: &String) -> bool {
if let Ok(ids) = mailparse::addrparse(mid_list.as_str()) {
fn is_msgrmsg_rfc724_mid_in_list(context: &Context, mid_list: &str) -> bool {
if let Ok(ids) = mailparse::addrparse(mid_list) {
for id in ids.iter() {
if is_msgrmsg_rfc724_mid(context, id) {
return true;
@@ -1661,7 +1650,7 @@ fn dc_add_or_lookup_contacts_by_address_list(
fn add_or_lookup_contact_by_addr(
context: &Context,
display_name: &Option<String>,
addr: &String,
addr: &str,
origin: Origin,
ids: &mut Vec<u32>,
check_self: &mut bool,

View File

@@ -49,7 +49,7 @@ impl Simplify {
/**
* Simplify Plain Text
*/
#[allow(non_snake_case)]
#[allow(non_snake_case, clippy::mut_range_bound)]
fn simplify_plain_text(&mut self, buf_terminated: &str, is_msgrmsg: bool) -> String {
/* This function ...
... removes all text after the line `-- ` (footer mark)

View File

@@ -63,12 +63,6 @@ pub fn dc_needs_ext_header(to_check: impl AsRef<str>) -> bool {
}
to_check.chars().any(|c| {
!(c.is_ascii_alphanumeric()
|| c == '-'
|| c == '_'
|| c == '_'
|| c == '.'
|| c == '~'
|| c == '%')
!c.is_ascii_alphanumeric() && c != '-' && c != '_' && c != '.' && c != '~' && c != '%'
})
}

View File

@@ -235,13 +235,6 @@ pub(crate) fn dc_extract_grpid_from_rfc724_mid(mid: &str) -> Option<&str> {
None
}
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 {
@@ -778,9 +771,9 @@ mod tests {
#[test]
fn test_dc_create_incoming_rfc724_mid() {
let res = dc_create_incoming_rfc724_mid(123, 45, &vec![6, 7]);
let res = dc_create_incoming_rfc724_mid(123, 45, &[6, 7]);
assert_eq!(res, Some("123-45-7@stub".into()));
let res = dc_create_incoming_rfc724_mid(123, 45, &vec![]);
let res = dc_create_incoming_rfc724_mid(123, 45, &[]);
assert_eq!(res, Some("123-45-0@stub".into()));
}
@@ -859,15 +852,15 @@ mod tests {
#[test]
fn test_listflags_has() {
let listflags: u32 = 0x1101;
assert!(listflags_has(listflags, 0x1) == true);
assert!(listflags_has(listflags, 0x10) == false);
assert!(listflags_has(listflags, 0x100) == true);
assert!(listflags_has(listflags, 0x1000) == true);
assert!(listflags_has(listflags, 0x1));
assert!(!listflags_has(listflags, 0x10));
assert!(listflags_has(listflags, 0x100));
assert!(listflags_has(listflags, 0x1000));
let listflags: u32 = (DC_GCL_ADD_SELF | DC_GCL_VERIFIED_ONLY).try_into().unwrap();
assert!(listflags_has(listflags, DC_GCL_VERIFIED_ONLY) == true);
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == true);
assert!(listflags_has(listflags, DC_GCL_VERIFIED_ONLY));
assert!(listflags_has(listflags, DC_GCL_ADD_SELF));
let listflags: u32 = DC_GCL_VERIFIED_ONLY.try_into().unwrap();
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == false);
assert!(!listflags_has(listflags, DC_GCL_ADD_SELF));
}
#[test]

View File

@@ -98,7 +98,6 @@ impl EncryptHelper {
.iter()
.filter_map(|(state, addr)| state.as_ref().map(|s| (s, addr)))
{
info!(context, "adding for {}: {:?}", addr, peerstate);
let key = peerstate.peek_key(min_verified).ok_or_else(|| {
format_err!("proper enc-key for {} missing, cannot encrypt", addr)
})?;
@@ -122,8 +121,6 @@ pub fn try_decrypt(
mail: &mailparse::ParsedMail<'_>,
message_time: i64,
) -> Result<(Option<Vec<u8>>, HashSet<String>)> {
info!(context, "trying to decrypt");
let from = mail
.headers
.get_first_value("From")?
@@ -213,7 +210,7 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
);
match pgp::create_keypair(&self_addr) {
Some((public_key, private_key)) => {
match dc_key_save_self_keypair(
if dc_key_save_self_keypair(
context,
&public_key,
&private_key,
@@ -221,15 +218,14 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
true,
&context.sql,
) {
true => {
info!(
context,
"Keypair generated in {:.3}s.",
start.elapsed().as_secs()
);
Ok(public_key)
}
false => Err(format_err!("Failed to save keypair")),
info!(
context,
"Keypair generated in {:.3}s.",
start.elapsed().as_secs()
);
Ok(public_key)
} else {
Err(format_err!("Failed to save keypair"))
}
}
None => Err(format_err!("Failed to generate keypair")),
@@ -253,7 +249,7 @@ fn decrypt_if_autocrypt_message<'a>(
let encrypted_data_part = match wrapmime::get_autocrypt_mime(mail) {
Err(err) => {
// not a proper autocrypt message, abort and ignore
warn!(context, "Invalid autocrypt message: {:?}", err);
info!(context, "Not an autocrypt message: {:?}", err);
return Ok(None);
}
Ok(res) => res,
@@ -270,13 +266,12 @@ fn decrypt_if_autocrypt_message<'a>(
/// Returns Ok(None) if nothing encrypted was found.
fn decrypt_part(
context: &Context,
_context: &Context,
mail: &mailparse::ParsedMail<'_>,
private_keyring: &Keyring,
public_keyring_for_validate: &Keyring,
ret_valid_signatures: &mut HashSet<String>,
) -> Result<Option<Vec<u8>>> {
info!(context, "decrypting part");
let data = mail.get_body_raw()?;
if has_decrypted_pgp_armor(&data) {

View File

@@ -1,3 +1,5 @@
//! # Error handling
use failure::Fail;
use lettre_email::mime;

View File

@@ -1,3 +1,5 @@
//! # Events specification
use std::path::PathBuf;
use strum::EnumProperty;
@@ -90,7 +92,7 @@ pub enum Event {
/// However, for ongoing processes (eg. configure())
/// or for functions that are expected to fail (eg. dc_continue_key_transfer())
/// it might be better to delay showing these events until the function has really
/// failed (returned false). It should be sufficient to report only the _last_ error
/// failed (returned false). It should be sufficient to report only the *last* error
/// in a messasge box then.
///
/// @return

View File

@@ -28,15 +28,14 @@ use crate::param::Params;
use crate::stock::StockMessage;
use crate::wrapmime;
pub mod select_folder;
const DC_IMAP_SEEN: usize = 0x0001;
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 Connect without configured params")]
ConnectWithoutConfigure,
@@ -61,15 +60,6 @@ pub enum Error {
#[fail(display = "IMAP server does not have IDLE capability")]
IdleAbilityMissing,
#[fail(display = "IMAP Connection Lost or no connection established")]
ConnectionLost,
#[fail(display = "IMAP close/expunge failed: {}", _0)]
CloseExpungeFailed(#[cause] async_imap::error::Error),
#[fail(display = "IMAP Folder name invalid: {:?}", _0)]
BadFolderName(String),
#[fail(display = "IMAP operation attempted while it is torn down")]
InTeardown,
@@ -79,6 +69,9 @@ pub enum Error {
#[fail(display = "IMAP got error from elsewhere: {:?}", _0)]
WrappedError(#[cause] crate::error::Error),
#[fail(display = "IMAP select folder error")]
SelectFolderError(#[cause] select_folder::Error),
#[fail(display = "IMAP other error: {:?}", _0)]
Other(String),
}
@@ -101,6 +94,12 @@ impl From<Error> for crate::error::Error {
}
}
impl From<select_folder::Error> for Error {
fn from(err: select_folder::Error) -> Error {
Error::SelectFolderError(err)
}
}
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq)]
pub enum ImapActionResult {
Failed,
@@ -114,7 +113,7 @@ const JUST_UID: &str = "(UID)";
const BODY_FLAGS: &str = "(FLAGS BODY.PEEK[])";
const SELECT_ALL: &str = "1:*";
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct Imap {
config: RwLock<ImapConfig>,
session: Mutex<Option<Session>>,
@@ -187,14 +186,7 @@ impl Default for ImapConfig {
impl Imap {
pub fn new() -> Self {
Imap {
session: Mutex::new(None),
config: RwLock::new(ImapConfig::default()),
interrupt: Mutex::new(None),
connected: Mutex::new(false),
skip_next_idle_wait: AtomicBool::new(false),
should_reconnect: AtomicBool::new(false),
}
Default::default()
}
pub async fn is_connected(&self) -> bool {
@@ -233,9 +225,7 @@ impl Imap {
match Client::connect_insecure((imap_server, imap_port)).await {
Ok(client) => {
if (server_flags & DC_LP_IMAP_SOCKET_STARTTLS) != 0 {
let res =
client.secure(imap_server, config.certificate_checks).await;
res
client.secure(imap_server, config.certificate_checks).await
} else {
Ok(client)
}
@@ -271,14 +261,12 @@ impl Imap {
user: imap_user.into(),
access_token: token,
};
let res = client.authenticate("XOAUTH2", &auth).await;
res
client.authenticate("XOAUTH2", &auth).await
} else {
return Err(Error::OauthError);
}
} else {
let res = client.login(imap_user, imap_pw).await;
res
client.login(imap_user, imap_pw).await
}
}
Err(err) => {
@@ -369,7 +357,7 @@ impl Imap {
if self.connect(context, &param) {
self.ensure_configured_folders(context, true)
} else {
Err(Error::ConnectionFailed(format!("{}", param).to_string()))
Err(Error::ConnectionFailed(format!("{}", param)))
}
}
@@ -471,92 +459,6 @@ impl Imap {
})
}
/// select a folder, possibly update uid_validity and, if needed,
/// expunge the folder to remove delete-marked messages.
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;
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) => {
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(())
}
}
fn get_config_last_seen_uid<S: AsRef<str>>(&self, context: &Context, folder: S) -> (u32, u32) {
let key = format!("imap.mailbox.{}", folder.as_ref());
if let Some(entry) = context.sql.get_raw_config(context, &key) {
@@ -1175,15 +1077,15 @@ impl Imap {
}
match self.select_folder(context, Some(&folder)).await {
Ok(()) => None,
Err(Error::ConnectionLost) => {
Err(select_folder::Error::ConnectionLost) => {
warn!(context, "Lost imap connection");
Some(ImapActionResult::RetryLater)
}
Err(Error::NoSession) => {
Err(select_folder::Error::NoSession) => {
warn!(context, "no imap session");
Some(ImapActionResult::Failed)
}
Err(Error::BadFolderName(folder_name)) => {
Err(select_folder::Error::BadFolderName(folder_name)) => {
warn!(context, "invalid folder name: {:?}", folder_name);
Some(ImapActionResult::Failed)
}
@@ -1398,11 +1300,7 @@ impl Imap {
})
}
async fn list_folders<'a>(
&self,
session: &'a mut Session,
context: &Context,
) -> Option<Vec<Name>> {
async fn list_folders(&self, session: &mut Session, context: &Context) -> Option<Vec<Name>> {
// TODO: use xlist when available
match session.list(Some(""), Some("*")).await {
Ok(list) => {
@@ -1470,7 +1368,7 @@ fn get_folder_meaning_by_name(folder_name: &Name) -> FolderMeaning {
let sent_names = vec!["sent", "sent objects", "gesendet"];
let lower = folder_name.name().to_lowercase();
if sent_names.into_iter().find(|s| *s == lower).is_some() {
if sent_names.into_iter().any(|s| s == lower) {
FolderMeaning::SentObjects
} else {
FolderMeaning::Unknown
@@ -1486,15 +1384,12 @@ fn get_folder_meaning(folder_name: &Name) -> FolderMeaning {
let special_names = vec!["\\Spam", "\\Trash", "\\Drafts", "\\Junk"];
for attr in folder_name.attributes() {
match attr {
NameAttribute::Custom(ref label) => {
if special_names.iter().find(|s| *s == label).is_some() {
res = FolderMeaning::Other;
} else if label == "\\Sent" {
res = FolderMeaning::SentObjects
}
if let NameAttribute::Custom(ref label) = attr {
if special_names.iter().any(|s| *s == label) {
res = FolderMeaning::Other;
} else if label == "\\Sent" {
res = FolderMeaning::SentObjects
}
_ => {}
}
}

111
src/imap/select_folder.rs Normal file
View File

@@ -0,0 +1,111 @@
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;
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) => {
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

@@ -53,7 +53,7 @@ pub enum ImexMode {
/// For this purpose, the function creates a job that is executed in the IMAP-thread then;
/// this requires to call dc_perform_inbox_jobs() regularly.
///
/// What to do is defined by the _what_ parameter.
/// What to do is defined by the *what* parameter.
///
/// While dc_imex() returns immediately, the started job may take a while,
/// you can stop it using dc_stop_ongoing_process(). During execution of the job,
@@ -84,27 +84,24 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<Strin
let mut newest_backup_time = 0;
let mut newest_backup_path: Option<std::path::PathBuf> = None;
for dirent in dir_iter {
match dirent {
Ok(dirent) => {
let path = dirent.path();
let name = dirent.file_name();
let name = name.to_string_lossy();
if name.starts_with("delta-chat") && name.ends_with(".bak") {
let sql = Sql::new();
if sql.open(context, &path, true) {
let curr_backup_time = sql
.get_raw_config_int(context, "backup_time")
.unwrap_or_default();
if curr_backup_time > newest_backup_time {
newest_backup_path = Some(path);
newest_backup_time = curr_backup_time;
}
info!(context, "backup_time of {} is {}", name, curr_backup_time);
sql.close(&context);
if let Ok(dirent) = dirent {
let path = dirent.path();
let name = dirent.file_name();
let name = name.to_string_lossy();
if name.starts_with("delta-chat") && name.ends_with(".bak") {
let sql = Sql::new();
if sql.open(context, &path, true) {
let curr_backup_time = sql
.get_raw_config_int(context, "backup_time")
.unwrap_or_default();
if curr_backup_time > newest_backup_time {
newest_backup_path = Some(path);
newest_backup_time = curr_backup_time;
}
info!(context, "backup_time of {} is {}", name, curr_backup_time);
sql.close(&context);
}
}
Err(_) => (),
}
}
match newest_backup_path {
@@ -175,7 +172,7 @@ pub fn render_setup_file(context: &Context, passphrase: &str) -> Result<String>
);
let self_addr = e2ee::ensure_secret_key_exists(context)?;
let private_key = Key::from_self_private(context, self_addr, &context.sql)
.ok_or(format_err!("Failed to get private key."))?;
.ok_or_else(|| format_err!("Failed to get private key."))?;
let ac_headers = match context.get_config_bool(Config::E2eeEnabled) {
false => None,
true => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
@@ -222,7 +219,7 @@ pub fn create_setup_code(_context: &Context) -> String {
for i in 0..9 {
loop {
random_val = rng.gen();
if !(random_val as usize > 60000) {
if random_val as usize <= 60000 {
break;
}
}
@@ -548,7 +545,7 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
}
Ok(()) => {
dest_sql.set_raw_config_int(context, "backup_time", now as i32)?;
context.call_cb(Event::ImexFileWritten(dest_path_filename.clone()));
context.call_cb(Event::ImexFileWritten(dest_path_filename));
Ok(())
}
};

View File

@@ -1,3 +1,8 @@
//! # 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};
@@ -117,6 +122,7 @@ pub struct Job {
}
impl Job {
/// Deletes the job from the database.
fn delete(&self, context: &Context) -> bool {
context
.sql
@@ -124,6 +130,9 @@ impl Job {
.is_ok()
}
/// Updates the job already stored in the database.
///
/// To add a new job, use [job_add].
fn update(&self, context: &Context) -> bool {
sql::execute(
context,
@@ -597,7 +606,7 @@ 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,
});
}
@@ -605,26 +614,7 @@ fn set_delivered(context: &Context, msg_id: MsgId) {
#[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)?;
if chat::msgtype_has_file(msg.type_0) {
let file_param = msg.param.get_path(Param::File, context)?;
if let Some(pathNfilename) = file_param {
if (msg.type_0 == Viewtype::Image || msg.type_0 == Viewtype::Gif)
&& !msg.param.exists(Param::Width)
{
msg.param.set_int(Param::Width, 0);
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) {
msg.param.set_int(Param::Width, width as i32);
msg.param.set_int(Param::Height, height as i32);
}
}
msg.save_param_to_disk(context);
}
}
}
msg.try_calc_and_set_dimensions(context).ok();
/* create message */
let needs_encryption = msg.param.get_int(Param::GuaranteeE2ee).unwrap_or_default();
@@ -912,6 +902,8 @@ 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,

View File

@@ -192,7 +192,6 @@ impl Key {
res.push(c);
res
})
.to_string()
}
pub fn to_armored_string(

View File

@@ -1,8 +1,16 @@
#![deny(clippy::correctness, missing_debug_implementations)]
// TODO: make all of these errors, such that clippy actually passes.
#![warn(clippy::all, clippy::perf, clippy::not_unsafe_ptr_arg_deref)]
// This is nice, but for now just annoying.
#![allow(clippy::unreadable_literal)]
#![deny(clippy::correctness, missing_debug_implementations, clippy::all)]
#![warn(
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
)]
#![feature(ptr_wrapping_offset_from)]
#[macro_use]

View File

@@ -1,3 +1,5 @@
//! # Login parameters
use std::borrow::Cow;
use std::fmt;
@@ -104,7 +106,7 @@ impl LoginParam {
let server_flags = sql.get_raw_config_int(context, key).unwrap_or_default();
LoginParam {
addr: addr.to_string(),
addr,
mail_server,
mail_user,
mail_pw,
@@ -198,6 +200,7 @@ 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())
@@ -206,44 +209,45 @@ 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 = 0;
let mut flag_added = false;
if 1 << bit == 0x2 {
res += "OAUTH2 ";
flag_added = 1;
flag_added = true;
}
if 1 << bit == 0x4 {
res += "AUTH_NORMAL ";
flag_added = 1;
flag_added = true;
}
if 1 << bit == 0x100 {
res += "IMAP_STARTTLS ";
flag_added = 1;
flag_added = true;
}
if 1 << bit == 0x200 {
res += "IMAP_SSL ";
flag_added = 1;
flag_added = true;
}
if 1 << bit == 0x400 {
res += "IMAP_PLAIN ";
flag_added = 1;
flag_added = true;
}
if 1 << bit == 0x10000 {
res += "SMTP_STARTTLS ";
flag_added = 1
flag_added = true;
}
if 1 << bit == 0x20000 {
res += "SMTP_SSL ";
flag_added = 1
flag_added = true;
}
if 1 << bit == 0x40000 {
res += "SMTP_PLAIN ";
flag_added = 1
flag_added = true;
}
if 0 == flag_added {
if 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,3 +1,5 @@
//! # Messages and their identifiers
use std::path::{Path, PathBuf};
use deltachat_derive::{FromSql, ToSql};
@@ -44,7 +46,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,
@@ -60,21 +62,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
}
@@ -82,7 +84,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
}
}
@@ -309,6 +311,32 @@ 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,
@@ -661,7 +689,7 @@ impl Lot {
self.text2 = Some(get_summarytext_by_raw(
msg.type_0,
msg.text.as_ref(),
&mut msg.param,
&msg.param,
SUMMARY_CHARACTERS,
context,
));
@@ -1192,9 +1220,10 @@ pub fn mdn_from_ext(
} // else wait for more receipts
}
}
return match read_by_all {
true => Some((chat_id, msg_id)),
false => None,
return if read_by_all {
Some((chat_id, msg_id))
} else {
None
};
}
None
@@ -1342,50 +1371,32 @@ mod tests {
some_file.set(Param::File, "foo.bar");
assert_eq!(
get_summarytext_by_raw(
Viewtype::Text,
some_text.as_ref(),
&mut Params::new(),
50,
&ctx
),
get_summarytext_by_raw(Viewtype::Text, some_text.as_ref(), &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(), &mut some_file, 50, &ctx,),
get_summarytext_by_raw(Viewtype::Image, no_text.as_ref(), &some_file, 50, &ctx,),
"Image" // file names are not added for images
);
assert_eq!(
get_summarytext_by_raw(Viewtype::Video, no_text.as_ref(), &mut some_file, 50, &ctx,),
get_summarytext_by_raw(Viewtype::Video, no_text.as_ref(), &some_file, 50, &ctx,),
"Video" // file names are not added for videos
);
assert_eq!(
get_summarytext_by_raw(Viewtype::Gif, no_text.as_ref(), &mut some_file, 50, &ctx,),
get_summarytext_by_raw(Viewtype::Gif, no_text.as_ref(), &some_file, 50, &ctx,),
"GIF" // file names are not added for GIFs
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Sticker,
no_text.as_ref(),
&mut some_file,
50,
&ctx,
),
get_summarytext_by_raw(Viewtype::Sticker, no_text.as_ref(), &some_file, 50, &ctx,),
"Sticker" // file names are not added for stickers
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Voice,
empty_text.as_ref(),
&mut some_file,
50,
&ctx,
),
get_summarytext_by_raw(Viewtype::Voice, empty_text.as_ref(), &some_file, 50, &ctx,),
"Voice message" // file names are not added for voice messages, empty text is skipped
);
@@ -1395,13 +1406,7 @@ mod tests {
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Voice,
some_text.as_ref(),
&mut some_file,
50,
&ctx
),
get_summarytext_by_raw(Viewtype::Voice, some_text.as_ref(), &some_file, 50, &ctx),
"Voice message \u{2013} bla bla" // `\u{2013}` explicitly checks for "EN DASH"
);
@@ -1411,24 +1416,12 @@ mod tests {
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Audio,
empty_text.as_ref(),
&mut some_file,
50,
&ctx,
),
get_summarytext_by_raw(Viewtype::Audio, empty_text.as_ref(), &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(),
&mut some_file,
50,
&ctx
),
get_summarytext_by_raw(Viewtype::Audio, some_text.as_ref(), &some_file, 50, &ctx),
"Audio \u{2013} foo.bar \u{2013} bla bla" // file name and text added for audio
);

View File

@@ -131,11 +131,12 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
.get_config(Config::ConfiguredAddr)
.unwrap_or_default();
if !email_to_remove.is_empty() && !addr_cmp(email_to_remove, self_addr) {
if !vec_contains_lowercase(&factory.recipients_addr, &email_to_remove) {
factory.recipients_names.push("".to_string());
factory.recipients_addr.push(email_to_remove.to_string());
}
if !email_to_remove.is_empty()
&& !addr_cmp(email_to_remove, self_addr)
&& !vec_contains_lowercase(&factory.recipients_addr, &email_to_remove)
{
factory.recipients_names.push("".to_string());
factory.recipients_addr.push(email_to_remove.to_string());
}
}
if command != SystemMessage::AutocryptSetupMessage
@@ -299,14 +300,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
return true;
}
match self.msg.param.get_cmd() {
SystemMessage::MemberAddedToGroup => {
return true;
}
_ => {}
}
false
self.msg.param.get_cmd() == SystemMessage::MemberAddedToGroup
}
Loaded::MDN => false,
}
@@ -473,17 +467,6 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
encrypt_helper.should_encrypt(self.context, e2ee_guranteed, &peerstates)?;
let is_encrypted = should_encrypt && force_plaintext == 0;
// Add gossip headers
if do_gossip {
for peerstate in peerstates.iter().filter_map(|(state, _)| state.as_ref()) {
if peerstate.peek_key(min_verified).is_some() {
if let Some(header) = peerstate.render_gossip_header(min_verified) {
protected_headers.push(Header::new("Autocrypt-Gossip".into(), header));
}
}
}
}
let rfc724_mid = match self.loaded {
Loaded::Message => self.msg.rfc724_mid.clone(),
Loaded::MDN => dc_create_outgoing_rfc724_mid(None, &self.from_addr),
@@ -495,10 +478,23 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
unprotected_headers.push(Header::new_with_value("From".into(), vec![from]).unwrap());
let outer_message = if is_encrypted {
// Add gossip headers
if do_gossip {
for peerstate in peerstates.iter().filter_map(|(state, _)| state.as_ref()) {
if peerstate.peek_key(min_verified).is_some() {
if let Some(header) = peerstate.render_gossip_header(min_verified) {
message =
message.header(Header::new("Autocrypt-Gossip".into(), header));
}
}
}
}
// Store protected headers in the inner message.
for header in protected_headers.into_iter() {
message = message.header(header);
}
// Set the appropriate Content-Type for the inner message.
let mut existing_ct = message
.get_header("Content-Type".to_string())
@@ -516,7 +512,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
// Set the appropriate Content-Type for the outer message
let mut outer_message = PartBuilder::new().header((
"Content-Type".to_string(),
"multipart/encrypted; protocol=\"application/pgp-encrypted\";".to_string(),
"multipart/encrypted; protocol=\"application/pgp-encrypted\"".to_string(),
));
// Store the unprotected headers on the outer message.

View File

@@ -148,11 +148,11 @@ impl<'a> MimeParser<'a> {
self.subject = Some(field.clone());
}
if let Some(_) = self.lookup_field("Chat-Version") {
if self.lookup_field("Chat-Version").is_some() {
self.is_send_by_messenger = true
}
if let Some(_) = self.lookup_field("Autocrypt-Setup-Message") {
if self.lookup_field("Autocrypt-Setup-Message").is_some() {
let has_setup_file = self.parts.iter().any(|p| {
p.mimetype.is_some() && p.mimetype.as_ref().unwrap().as_ref() == MIME_AC_SETUP_FILE
});
@@ -188,14 +188,12 @@ impl<'a> MimeParser<'a> {
self.is_system_message = SystemMessage::LocationStreamingEnabled;
}
}
if let Some(_) = self.lookup_field("Chat-Group-Image") {
if !self.parts.is_empty() {
let textpart = &self.parts[0];
if textpart.typ == Viewtype::Text && self.parts.len() >= 2 {
let imgpart = &mut self.parts[1];
if imgpart.typ == Viewtype::Image {
imgpart.is_meta = true;
}
if self.lookup_field("Chat-Group-Image").is_some() && !self.parts.is_empty() {
let textpart = &self.parts[0];
if textpart.typ == Viewtype::Text && self.parts.len() >= 2 {
let imgpart = &mut self.parts[1];
if imgpart.typ == Viewtype::Image {
imgpart.is_meta = true;
}
}
}
@@ -264,11 +262,11 @@ impl<'a> MimeParser<'a> {
}
}
if self.parts.len() == 1 {
if self.parts[0].typ == Viewtype::Audio {
if let Some(_) = self.lookup_field("Chat-Voice-Message") {
let part_mut = &mut self.parts[0];
part_mut.typ = Viewtype::Voice;
}
if self.parts[0].typ == Viewtype::Audio
&& self.lookup_field("Chat-Voice-Message").is_some()
{
let part_mut = &mut self.parts[0];
part_mut.typ = Viewtype::Voice;
}
if self.parts[0].typ == Viewtype::Image {
if let Some(value) = self.lookup_field("Chat-Content") {
@@ -601,7 +599,7 @@ impl<'a> MimeParser<'a> {
// if there is still no filename, guess one
if desired_filename.is_empty() {
if let Some(subtype) = mail.ctype.mimetype.split('/').skip(1).next() {
if let Some(subtype) = mail.ctype.mimetype.split('/').nth(1) {
desired_filename = format!("file.{}", subtype,);
} else {
return Ok(false);
@@ -626,7 +624,7 @@ impl<'a> MimeParser<'a> {
&mut self,
msg_type: Viewtype,
mime_type: Mime,
raw_mime: &String,
raw_mime: &str,
decoded_data: &[u8],
filename: &str,
) {
@@ -685,7 +683,7 @@ impl<'a> MimeParser<'a> {
fn do_add_single_part(&mut self, mut part: Part) {
if self.encrypted {
if self.signatures.len() > 0 {
if !self.signatures.is_empty() {
part.param.set_int(Param::GuaranteeE2ee, 1);
} else {
// XXX if the message was encrypted but not signed
@@ -698,17 +696,15 @@ impl<'a> MimeParser<'a> {
}
pub fn is_mailinglist_message(&self) -> bool {
if let Some(_) = self.lookup_field("List-Id") {
if self.lookup_field("List-Id").is_some() {
return true;
}
if let Some(precedence) = self.lookup_field("Precedence") {
if precedence == "list" || precedence == "bulk" {
return true;
}
precedence == "list" || precedence == "bulk"
} else {
false
}
false
}
pub fn sender_equals_recipient(&self) -> bool {
@@ -965,13 +961,12 @@ fn mailmime_is_attachment_disposition(mail: &mailparse::ParsedMail<'_>) -> bool
}
// returned addresses are normalized.
fn get_recipients<'a, S: AsRef<str>, T: Iterator<Item = (S, S)>>(headers: T) -> HashSet<String> {
fn get_recipients<S: AsRef<str>, T: Iterator<Item = (S, S)>>(headers: T) -> HashSet<String> {
let mut recipients: HashSet<String> = Default::default();
for (hkey, hvalue) in headers {
let hkey = hkey.as_ref();
let hkey = hkey.as_ref().to_lowercase();
let hvalue = hvalue.as_ref();
if hkey == "to" || hkey == "cc" {
if let Ok(addrs) = mailparse::addrparse(hvalue) {
for addr in addrs.iter() {
@@ -1058,6 +1053,17 @@ mod tests {
assert_eq!(mimeparser.get_rfc724_mid(), None);
}
#[test]
fn test_get_recipients() {
let context = dummy_context();
let raw = include_bytes!("../test-data/message/mail_with_cc.txt");
let mimeparser = MimeParser::from_bytes(&context.ctx, &raw[..]).unwrap();
let recipients = get_recipients(mimeparser.header.iter());
assert!(recipients.contains("abc@bcd.com"));
assert!(recipients.contains("def@def.de"));
assert_eq!(recipients.len(), 2);
}
#[test]
fn test_mailparse_content_type() {
let ctype =

View File

@@ -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 [Message] is in the
/// [MessageState::OutPending] state but is already forwarded.
/// This is used when a [crate::message::Message] is in the
/// [crate::message::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(|file| Some(file))
ParamsFile::from_param(context, val).map(Some)
}
/// Gets the parameter and returns a [BlobObject] for it.
@@ -373,7 +373,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 {
assert!(false, "Wrong enum variant");
panic!("Wrong enum variant");
}
}
@@ -383,7 +383,7 @@ mod tests {
if let ParamsFile::Blob(b) = ParamsFile::from_param(&t.ctx, "$BLOBDIR/foo").unwrap() {
assert_eq!(b.as_name(), "$BLOBDIR/foo");
} else {
assert!(false, "Wrong enum variant");
panic!("Wrong enum variant");
}
}

View File

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

View File

@@ -533,12 +533,10 @@ pub(crate) fn handle_securejoin_handshake(
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);
}
} 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, "");
@@ -643,7 +641,7 @@ pub(crate) fn handle_securejoin_handshake(
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: contact_id,
contact_id,
});
} else {
warn!(context, "vg-member-added-received invalid.",);

View File

@@ -33,7 +33,7 @@ pub enum Error {
pub type Result<T> = std::result::Result<T, Error>;
#[derive(DebugStub)]
#[derive(Default, DebugStub)]
pub struct Smtp {
#[debug_stub(some = "SmtpTransport")]
transport: Option<lettre::smtp::SmtpTransport>,
@@ -45,11 +45,7 @@ pub struct Smtp {
impl Smtp {
/// Create a new Smtp instances.
pub fn new() -> Self {
Smtp {
transport: None,
transport_connected: false,
from: None,
}
Default::default()
}
/// Disconnect the SMTP transport and drop it entirely.
@@ -80,15 +76,12 @@ impl Smtp {
return Err(Error::BadParameters);
}
self.from = match EmailAddress::new(lp.addr.clone()) {
Ok(addr) => Some(addr),
Err(err) => {
return Err(Error::InvalidLoginAddress {
address: lp.addr.clone(),
error: err,
})
}
};
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;
@@ -135,30 +128,23 @@ impl Smtp {
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) => return Err(Error::ConnectionFailure(err)),
}
}
Err(err) => {
return Err(Error::ConnectionSetupFailure(err));
}
}
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

@@ -58,7 +58,7 @@ impl Smtp {
context,
"uh? SMTP has no transport, failed to send to {}", recipients_display
);
return Err(Error::NoTransport);
Err(Error::NoTransport)
}
}
}

View File

@@ -68,13 +68,19 @@ pub struct Sql {
in_use: Arc<ThreadLocal<String>>,
}
impl Sql {
pub fn new() -> Sql {
Sql {
impl Default for Sql {
fn default() -> Self {
Self {
pool: RwLock::new(None),
in_use: Arc::new(ThreadLocal::new()),
}
}
}
impl Sql {
pub fn new() -> Sql {
Self::default()
}
pub fn is_open(&self) -> bool {
self.pool.read().unwrap().is_some()
@@ -541,7 +547,6 @@ fn open(
let mut dbversion = dbversion_before_update;
let mut recalc_fingerprints = 0;
let mut update_file_paths = 0;
let mut update_icons = false;
if dbversion < 1 {
@@ -706,19 +711,6 @@ fn open(
"CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);",
params![],
)?;
if dbversion_before_update == 34 {
// migrate database from the use of verified-flags to verified_key,
// _only_ version 34 (0.17.0) has the fields public_key_verified and gossip_key_verified
// this block can be deleted in half a year or so (created 5/2018)
sql.execute(
"UPDATE acpeerstates SET verified_key=gossip_key, verified_key_fingerprint=gossip_key_fingerprint WHERE gossip_key_verified=2;",
params![]
)?;
sql.execute(
"UPDATE acpeerstates SET verified_key=public_key, verified_key_fingerprint=public_key_fingerprint WHERE public_key_verified=2;",
params![]
)?;
}
dbversion = 39;
sql.set_raw_config_int(context, "dbversion", 39)?;
}
@@ -731,20 +723,6 @@ fn open(
dbversion = 40;
sql.set_raw_config_int(context, "dbversion", 40)?;
}
if dbversion < 41 {
info!(context, "[migration] v41");
update_file_paths = 1;
dbversion = 41;
sql.set_raw_config_int(context, "dbversion", 41)?;
}
if dbversion < 42 {
info!(context, "[migration] v42");
// older versions set the txt-field to the filenames, for debugging and fulltext search.
// to allow text+attachment compound messages, we need to reset these fields.
sql.execute("UPDATE msgs SET txt='' WHERE type!=10", params![])?;
dbversion = 42;
sql.set_raw_config_int(context, "dbversion", 42)?;
}
if dbversion < 44 {
info!(context, "[migration] v44");
sql.execute("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", params![])?;
@@ -906,34 +884,6 @@ fn open(
},
)?;
}
if 0 != update_file_paths {
// versions before 2018-08 save the absolute paths in the database files at "param.f=";
// for newer versions, we copy files always to the blob directory and store relative paths.
// this snippet converts older databases and can be removed after some time.
info!(context, "[migration] update file paths");
let repl_from = sql
.get_raw_config(context, "backup_for")
.unwrap_or_else(|| context.get_blobdir().to_string_lossy().into());
let repl_from = dc_ensure_no_slash_safe(&repl_from);
sql.execute(
&format!(
"UPDATE msgs SET param=replace(param, 'f={}/', 'f=$BLOBDIR/')",
repl_from
),
NO_PARAMS,
)?;
sql.execute(
&format!(
"UPDATE chats SET param=replace(param, 'i={}/', 'i=$BLOBDIR/');",
repl_from
),
NO_PARAMS,
)?;
sql.set_raw_config(context, "backup_for", None)?;
}
if update_icons {
update_saved_messages_icon(context)?;
}
@@ -1002,13 +952,12 @@ pub fn get_rowid_with_conn(
// the ORDER BY ensures, this function always returns the most recent id,
// eg. if a Message-ID is split into different messages.
let query = format!(
"SELECT id FROM {} WHERE {}='{}' ORDER BY id DESC",
"SELECT id FROM {} WHERE {}=? ORDER BY id DESC",
table.as_ref(),
field.as_ref(),
value.as_ref()
);
match conn.query_row(&query, NO_PARAMS, |row| row.get::<_, u32>(0)) {
match conn.query_row(&query, params![value.as_ref()], |row| row.get::<_, u32>(0)) {
Ok(id) => id,
Err(err) => {
error!(
@@ -1140,26 +1089,23 @@ pub fn housekeeping(context: &Context) {
unreferenced_count += 1;
match std::fs::metadata(entry.path()) {
Ok(stats) => {
let recently_created = stats.created().is_ok()
&& stats.created().unwrap() > keep_files_newer_than;
let recently_modified = stats.modified().is_ok()
&& stats.modified().unwrap() > keep_files_newer_than;
let recently_accessed = stats.accessed().is_ok()
&& stats.accessed().unwrap() > keep_files_newer_than;
if let Ok(stats) = std::fs::metadata(entry.path()) {
let recently_created =
stats.created().is_ok() && stats.created().unwrap() > keep_files_newer_than;
let recently_modified = stats.modified().is_ok()
&& stats.modified().unwrap() > keep_files_newer_than;
let recently_accessed = stats.accessed().is_ok()
&& stats.accessed().unwrap() > keep_files_newer_than;
if recently_created || recently_modified || recently_accessed {
info!(
context,
"Housekeeping: Keeping new unreferenced file #{}: {:?}",
unreferenced_count,
entry.file_name(),
);
continue;
}
if recently_created || recently_modified || recently_accessed {
info!(
context,
"Housekeeping: Keeping new unreferenced file #{}: {:?}",
unreferenced_count,
entry.file_name(),
);
continue;
}
Err(_) => {}
}
info!(
context,

View File

@@ -5,9 +5,15 @@ use std::borrow::Cow;
use strum::EnumProperty;
use strum_macros::EnumProperty;
use crate::blob::BlobObject;
use crate::chat;
use crate::constants::{Viewtype, DC_CONTACT_ID_SELF};
use crate::contact::*;
use crate::context::Context;
use crate::error::Error;
use crate::message::Message;
use crate::param::Param;
use crate::stock::StockMessage::{DeviceMessagesHint, WelcomeMessage};
/// Stock strings
///
@@ -22,107 +28,174 @@ use crate::error::Error;
pub enum StockMessage {
#[strum(props(fallback = "No messages."))]
NoMessages = 1,
#[strum(props(fallback = "Me"))]
SelfMsg = 2,
#[strum(props(fallback = "Draft"))]
Draft = 3,
#[strum(props(fallback = "%1$s member(s)"))]
Member = 4,
#[strum(props(fallback = "%1$s contact(s)"))]
Contact = 6,
#[strum(props(fallback = "Voice message"))]
VoiceMessage = 7,
#[strum(props(fallback = "Contact requests"))]
DeadDrop = 8,
#[strum(props(fallback = "Image"))]
Image = 9,
#[strum(props(fallback = "Video"))]
Video = 10,
#[strum(props(fallback = "Audio"))]
Audio = 11,
#[strum(props(fallback = "File"))]
File = 12,
#[strum(props(fallback = "Sent with my Delta Chat Messenger: https://delta.chat"))]
StatusLine = 13,
#[strum(props(fallback = "Hello, I\'ve just created the group \"%1$s\" for us."))]
NewGroupDraft = 14,
#[strum(props(fallback = "Group name changed from \"%1$s\" to \"%2$s\"."))]
MsgGrpName = 15,
#[strum(props(fallback = "Group image changed."))]
MsgGrpImgChanged = 16,
#[strum(props(fallback = "Member %1$s added."))]
MsgAddMember = 17,
#[strum(props(fallback = "Member %1$s removed."))]
MsgDelMember = 18,
#[strum(props(fallback = "Group left."))]
MsgGroupLeft = 19,
#[strum(props(fallback = "GIF"))]
Gif = 23,
#[strum(props(fallback = "Encrypted message"))]
EncryptedMsg = 24,
#[strum(props(fallback = "End-to-end encryption available."))]
E2eAvailable = 25,
#[strum(props(fallback = "Transport-encryption."))]
EncrTransp = 27,
#[strum(props(fallback = "No encryption."))]
EncrNone = 28,
#[strum(props(fallback = "This message was encrypted for another setup."))]
CantDecryptMsgBody = 29,
#[strum(props(fallback = "Fingerprints"))]
FingerPrints = 30,
#[strum(props(fallback = "Return receipt"))]
ReadRcpt = 31,
#[strum(props(fallback = "This is a return receipt for the message \"%1$s\"."))]
ReadRcptMailBody = 32,
#[strum(props(fallback = "Group image deleted."))]
MsgGrpImgDeleted = 33,
#[strum(props(fallback = "End-to-end encryption preferred."))]
E2ePreferred = 34,
#[strum(props(fallback = "%1$s verified."))]
ContactVerified = 35,
#[strum(props(fallback = "Cannot verify %1$s"))]
ContactNotVerified = 36,
#[strum(props(fallback = "Changed setup for %1$s"))]
ContactSetupChanged = 37,
#[strum(props(fallback = "Archived chats"))]
ArchivedChats = 40,
#[strum(props(fallback = "Starred messages"))]
StarredMsgs = 41,
#[strum(props(fallback = "Autocrypt Setup Message"))]
AcSetupMsgSubject = 42,
#[strum(props(
fallback = "This is the Autocrypt Setup Message used to transfer your key between clients.\n\nTo decrypt and use your key, open the message in an Autocrypt-compliant client and enter the setup code presented on the generating device."
))]
AcSetupMsgBody = 43,
#[strum(props(fallback = "Messages I sent to myself"))]
SelfTalkSubTitle = 50,
#[strum(props(fallback = "Cannot login as %1$s."))]
CannotLogin = 60,
#[strum(props(fallback = "Could not connect to %1$s: %2$s"))]
ServerResponse = 61,
#[strum(props(fallback = "%1$s by %2$s."))]
MsgActionByUser = 62,
#[strum(props(fallback = "%1$s by me."))]
MsgActionByMe = 63,
#[strum(props(fallback = "Location streaming enabled."))]
MsgLocationEnabled = 64,
#[strum(props(fallback = "Location streaming disabled."))]
MsgLocationDisabled = 65,
#[strum(props(fallback = "Location"))]
Location = 66,
#[strum(props(fallback = "Sticker"))]
Sticker = 67,
#[strum(props(fallback = "Device messages"))]
DeviceMessages = 68,
#[strum(props(fallback = "Saved messages"))]
SavedMessages = 69,
#[strum(props(
fallback = "Messages in this chat are generated locally by your Delta Chat app. \
Its makers use it to inform about app updates and problems during usage."
))]
DeviceMessagesHint = 70,
#[strum(props(fallback = "Welcome to Delta Chat! \
Delta Chat looks and feels like other popular messenger apps, \
but does not involve centralized control, \
tracking or selling you, friends, colleagues or family out to large organizations.\n\n\
Technically, Delta Chat is an email application with a modern chat interface. \
Email in a new dress if you will 👻\n\n\
Use Delta Chat with anyone out of billions of people: just use their e-mail address. \
Recipients don't need to install Delta Chat, visit websites or sign up anywhere - \
however, of course, if they like, you may point them to 👉 https://get.delta.chat"))]
WelcomeMessage = 71,
}
/*
"
*/
impl StockMessage {
/// Default untranslated strings for stock messages.
///
/// These could be used in logging calls, so no logging here.
fn fallback(&self) -> &'static str {
fn fallback(self) -> &'static str {
self.get_str("fallback").unwrap_or_default()
}
}
@@ -167,7 +240,7 @@ impl Context {
.unwrap()
.get(&(id as usize))
{
Some(ref x) => Cow::Owned(x.to_string()),
Some(ref x) => Cow::Owned((*x).to_string()),
None => Cow::Borrowed(id.fallback()),
}
}
@@ -264,6 +337,40 @@ impl Context {
}
}
}
pub fn update_device_chats(&self) -> Result<(), Error> {
// check for the LAST added device message - if it is present, we can skip message creation.
// this is worthwhile as this function is typically called
// by the ui on every probram start or even on every opening of the chatlist.
if chat::was_device_msg_ever_added(&self, "core-welcome")? {
return Ok(());
}
// create saved-messages chat;
// we do this only once, if the user has deleted the chat, he can recreate it manually.
if !self.sql.get_raw_config_bool(&self, "self-chat-added") {
self.sql
.set_raw_config_bool(&self, "self-chat-added", true)?;
chat::create_by_contact_id(&self, DC_CONTACT_ID_SELF)?;
}
// add welcome-messages. by the label, this is done only once,
// if the user has deleted the message or the chat, it is not added again.
let mut msg = Message::new(Viewtype::Text);
msg.text = Some(self.stock_str(DeviceMessagesHint).to_string());
chat::add_device_msg(&self, Some("core-about-device-chat"), Some(&mut msg))?;
let image = include_bytes!("../assets/welcome-image.jpg");
let blob = BlobObject::create(&self, "welcome-image.jpg".to_string(), image)?;
let mut msg = Message::new(Viewtype::Image);
msg.param.set(Param::File, blob.as_name());
chat::add_device_msg(&self, Some("core-welcome-image"), Some(&mut msg))?;
let mut msg = Message::new(Viewtype::Text);
msg.text = Some(self.stock_str(WelcomeMessage).to_string());
chat::add_device_msg(&self, Some("core-welcome"), Some(&mut msg))?;
Ok(())
}
}
#[cfg(test)]
@@ -273,6 +380,7 @@ mod tests {
use crate::constants::DC_CONTACT_ID_SELF;
use crate::chatlist::Chatlist;
use num_traits::ToPrimitive;
#[test]
@@ -428,4 +536,22 @@ mod tests {
"Group name changed from \"Some chat\" to \"Other chat\" by Alice (alice@example.com)."
)
}
#[test]
fn test_update_device_chats() {
let t = dummy_context();
t.ctx.update_device_chats().ok();
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
assert_eq!(chats.len(), 2);
chat::delete(&t.ctx, chats.get_chat_id(0)).ok();
chat::delete(&t.ctx, chats.get_chat_id(1)).ok();
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
assert_eq!(chats.len(), 0);
// a subsequent call to update_device_chats() must not re-add manally deleted messages or chats
t.ctx.update_device_chats().ok();
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
assert_eq!(chats.len(), 0);
}
}

View File

@@ -34,7 +34,7 @@ pub fn test_context(callback: Option<Box<ContextCallback>>) -> TestContext {
None => Box::new(|_, _| 0),
};
let ctx = Context::new(cb, "FakeOs".into(), dbfile).unwrap();
TestContext { ctx: ctx, dir: dir }
TestContext { ctx, dir }
}
/// Return a dummy [TestContext].

View File

@@ -0,0 +1,14 @@
Return-Path: <x@testrun.org>
Received: from hq5.merlinux.eu
by hq5.merlinux.eu (Dovecot) with LMTP id yRKOBakcfV1AewAAPzvFDg
; Sat, 14 Sep 2019 19:00:25 +0200
Received: from localhost (unknown 7.165.105.24])
by hq5.merlinux.eu (Postfix) with ESMTPSA id 8D9844E023;
Sat, 14 Sep 2019 19:00:22 +0200 (CEST)
message-id: <2dfdbde7@example.org>
Date: Sat, 14 Sep 2019 19:00:13 +0200
From: lmn <x@tux.org>
To: abc <abc@bcd.com>
CC: def <def@def.de>
hi