Compare commits

..

73 Commits

Author SHA1 Message Date
dignifiedquire
5a32d29574 wip 2019-11-07 09:15:06 +01:00
holger krekel
15bf53c092 with these lines commented out, it works 2019-11-06 22:20:56 +01:00
holger krekel
f6afd5f7f1 make test fail again 2019-11-06 22:05:11 +01:00
B. Petersen
515f0c5089 correct ffi return value of dc_add_device_msg() 2019-11-06 13:33:30 +01:00
B. Petersen
5a11551b4d block sending to chats that do not support sending (normally, this should already be avoided in the ui) 2019-11-06 13:33:30 +01:00
B. Petersen
49bf99588b show chat-profile-image in repl tool 2019-11-06 13:33:30 +01:00
B. Petersen
231110fb61 add profile-icon for device-chat 2019-11-06 13:33:30 +01:00
B. Petersen
4c30bf80ce target comments of @flub 2019-11-06 13:33:30 +01:00
B. Petersen
f8afefa2c1 get contact- and chat-info for device-messages 2019-11-06 13:33:30 +01:00
B. Petersen
89bb2d0ffe add devicemsg to repl tool 2019-11-06 13:33:30 +01:00
B. Petersen
b5d5d98645 implement add_device_msg() 2019-11-06 13:33:30 +01:00
B. Petersen
89f394ab86 create separate function for preparing a blob 2019-11-06 13:33:30 +01:00
B. Petersen
cbaa4e03b3 basic devicetalk implementation 2019-11-06 13:33:30 +01:00
B. Petersen
50539465b9 prototype a device-chat 2019-11-06 13:33:30 +01:00
B. Petersen
be08bcb22b rename DC_CONTACT_ID_DEVICE to DC_CONTACT_ID_INFO to be in-line with dc_msg_is_info() 2019-11-06 13:33:30 +01:00
holger krekel
dcd92a894e fix export: write backup_time to the destination not the source sql file
and perform slightly cleaner teardown in python
2019-11-06 13:26:32 +01:00
holger krekel
6336eeb568 better error on has_backup() failing 2019-11-06 13:26:32 +01:00
holger krekel
6b18cbda1f refine dc_copy along the lines @flub did for blobstore 2019-11-06 13:26:32 +01:00
Alexander Krotov
cf023ea557 sql: remove unnecessary ? in prepare() and prepare2() 2019-11-06 13:26:03 +01:00
Alexander Krotov
51a804a80f location: use "bool" for "independent" argument 2019-11-06 14:17:59 +03:00
B. Petersen
1a33b1c574 bump version 2019-11-05 17:04:44 +01:00
B. Petersen
67e2e4d415 target comment of @hpk42 2019-11-04 13:20:35 +01:00
B. Petersen
8c2efa707a name special contact-ids where easily possible
the point of this pr is to get an overview
how and where DC_CONTACT_ID_DEVICE is used,
to prepare introducing a device-"chat".

i did not change the sql statements for now
as this would require some more refactoring
and has the potential to introduce bugs.
2019-11-04 13:20:35 +01:00
B. Petersen
87abc6e4a2 adapt wording 2019-11-03 21:36:40 +01:00
B. Petersen
0ea017c53d add repl command for testing interrupt-idle 2019-11-03 21:36:40 +01:00
B. Petersen
b9c7510b58 use boolean for jobs_needed 2019-11-03 21:36:40 +01:00
holger krekel
01e7caf65a use job_id as mail_id for SendableEmail -- it's only an internal id and job_id is unique enough. 2019-11-03 21:36:01 +01:00
holger krekel
1cfeb730c3 try to fix some smtp todos and do better error logging 2019-11-03 21:36:01 +01:00
Floris Bruynooghe
a3b90a08b6 Copy the file contents manually
Before we created an empty file and asked the OS to copy the file.
The OS is very good at this so this is a good idea generally.  However
it seems that in some cases, possibly an Android Dowload folder, we
might be able to create a file but not overwrite it.  Thus refactor
this a bit so we are copying the file ourselves.

There are no new tests here since the behaviour remains identical.
The good news is that the existing tests were good enough to catch
some bugs already.
2019-11-03 20:16:56 +01:00
holger krekel
31571be71e add account.get_chat_by_id API 2019-11-03 20:14:57 +01:00
holger krekel
661fc45106 split "chatting.py" into "contact.py" and "chat.py" to be more reminiscent of the core-rust structure 2019-11-03 20:14:57 +01:00
holger krekel
da64dee3e0 start a changelog for the yet untagged beta.7 2019-11-03 01:32:49 +01:00
holger krekel
cb00f5da79 fix #786 by always succeeding to create a canonical deterministic message-id -- thanks @csb0730 for the analysis of the issue 2019-11-03 01:28:45 +01:00
B. Petersen
e1df41c209 fix tests 2019-11-03 01:19:29 +01:00
B. Petersen
3b64748427 add proptest-regression file 2019-11-03 01:19:29 +01:00
B. Petersen
70cef68eeb display failed messages 2019-11-03 01:19:29 +01:00
holger krekel
c5f64d2988 address @link2xt comments 2019-11-03 00:21:27 +01:00
holger krekel
4eb068613d extend and fix python/test side of location streaming 2019-11-03 00:21:27 +01:00
holger krekel
d774430ec2 simplify location parsing 2019-11-03 00:21:27 +01:00
holger krekel
d24a982757 fix various location-streaming issue, test passes now 2019-11-03 00:21:27 +01:00
holger krekel
d74c70a57c fix and streamline location-outgoing pipeline 2019-11-03 00:21:27 +01:00
holger krekel
a6f0f78588 addresses #757 test and add location streaming python api 2019-11-03 00:21:27 +01:00
B. Petersen
e6d9991581 fix conversion from nanoseconds to milliseconds
1_000_000 nanosecond = 1 millisecond
2019-11-02 15:22:27 +01:00
B. Petersen
ec8dbddcfb tweak ffi documentation wrt certificate checks 2019-11-01 18:06:01 +01:00
B. Petersen
bc699f17d9 avoid usage of get_subtitle() in repl-tool, remove dc_chat_get_subtitle() from documentation. 2019-11-01 13:19:47 +01:00
B. Petersen
832df41130 target comments of @hpk42 and @flub 2019-11-01 00:46:48 +01:00
B. Petersen
5709681076 streamline as_path_unicode(), delete unused as_str() and as_str_safe() 2019-11-01 00:46:48 +01:00
B. Petersen
858baf0c2c prefer to_opt_string_lossy() over as_opt_str() as the latter pancis on non-wellformatted utf-8, delete as_opt_str() 2019-11-01 00:46:48 +01:00
B. Petersen
e4b3e23769 prefer to_string_lossy() over as_str() as the latter pancis on non-wellformatted utf-8 2019-11-01 00:46:48 +01:00
holger krekel
8ce05796da ensure that especially qr tests are rerun 2019-10-31 23:31:09 +01:00
holger krekel
7f8c6d8cca some more display refinements 2019-10-31 23:31:09 +01:00
holger krekel
75ba040531 more error logging with file operations in general 2019-10-31 23:31:09 +01:00
Floris Bruynooghe
faa78e1c04 At least log the error 2019-10-31 22:38:32 +01:00
Floris Bruynooghe
b264d3be3c Be more accepting in creating blobs from existing names
This is an additional fix for #768 aka
commit eac8ad8369
2019-10-31 20:05:09 +01:00
holger krekel
80d7e84e5d prepare 1.0.0-beta.6 2019-10-31 12:34:03 +01:00
B. Petersen
4e37610f21 fix boolean error in chatlist::get_msg_id()
the error led to unusable contact requests,
at least on android and ios (probably also desktop)
because msg_id=dc_chatlist_get_msg_id() always returns 0
and create_chat_by_msg_id(msg_id)
or dc_marknoticed_contact(<get sender from msg_id>)
failed therefore.
2019-10-31 12:31:31 +01:00
Floris Bruynooghe
78030e4a31 Add test for #767
Without a test for this fix, a regression will happen again.

Original PR #767 aka commit 4fed875107
2019-10-30 21:08:25 +01:00
holger krekel
b01c842d7c bump version in toml's and changelog, and trigger deltachat-specific "cargo update". example: python set_core_version.py 1.0.0-beta.6 automates bumping the version and performs some quick sanity checks 2019-10-30 20:05:01 +01:00
holger krekel
c56c10bced remove unneccessary check of is_special() + cleanups 2019-10-30 19:29:13 +01:00
holger krekel
b0ccbc36d9 fix FFI-behaviour: return default empty messages when asked for special ones 2019-10-30 19:29:13 +01:00
holger krekel
9cdfc3409d systematically ignore invalid message ids when passed in through CFFI 2019-10-30 19:29:13 +01:00
björn petersen
fc851f542a Merge pull request #770 from deltachat/tweak-ffi
remove unneeded const attribtute
2019-10-30 19:12:43 +01:00
B. Petersen
7530abd581 remove unneeded const attribtute 2019-10-30 16:36:28 +01:00
holger krekel
a6594a9ae3 add changelog and bump to beta.4 2019-10-30 16:13:34 +01:00
B. Petersen
62019f57e9 fix some doxygen links and overviews 2019-10-30 16:09:04 +01:00
holger krekel
41443bb7f9 fix sending of autocrypt setup message 2019-10-30 15:48:06 +01:00
B. Petersen
8b5f7d98f6 do not escalate attemt to add self to a group to the user, just return false from add_contact_to_chat() 2019-10-30 14:03:54 +01:00
B. Petersen
6fea6f730d fix recognition of mailto-address-qr-codes, add tests 2019-10-30 13:13:02 +01:00
holger krekel
ad42a39a43 amend changelog 2019-10-30 13:12:00 +01:00
B. Petersen
ed9cfedbf3 update changelog 2019-10-30 13:12:00 +01:00
holger krekel
36510d8451 update links / use deltachat/rust-imap master branch 2019-10-30 13:07:44 +01:00
björn petersen
501a6eee69 Merge pull request #765 from deltachat/fix-qr2
fix plus-space-decoding in qr-code
2019-10-30 10:16:57 +01:00
B. Petersen
39cd8465f4 allow plus-space-encoding in qr-code, adapt tests 2019-10-30 00:37:35 +01:00
45 changed files with 1453 additions and 689 deletions

View File

@@ -1,6 +1,50 @@
# Changelog
## 1.0.0-beta2
## 1.0.0-beta.7
- fix location-streaming #782
- fix display of messages that could not be decrypted #785
- fix smtp MAILER-DAEMON bug #786
- fix a logging of durations #783
- add more error logging #779
- do not panic on some bad utf-8 mime #776
## 1.0.0-beta.6
- fix chatlist.get_msg_id to return id, instead of wrongly erroring
## 1.0.0-beta.5
- fix dc_get_msg() to return empty messages when asked for special ones
## 1.0.0-beta.4
- fix more than one sending of autocrypt setup message
- fix recognition of mailto-address-qr-codes, add tests
- tune down error to warning when adding self to chat
## 1.0.0-beta.3
- add back `dc_empty_server()` #682
- if `show_emails` is set to `DC_SHOW_EMAILS_ALL`,
email-based contact requests are added to the chatlist directly
- fix IMAP hangs #717 and cleanups
- several rPGP fixes
- code streamlining and rustifications
## 1.0.0-beta.2
- https://c.delta.chat docs are now regenerated again through our CI
@@ -20,7 +64,7 @@
- some rustifications/boolifications of c-ints
## 1.0.0-beta1
## 1.0.0-beta.1
- first beta of the Delta Chat Rust core library. many fixes of crashes
and other issues compared to 1.0.0-alpha.5.

108
Cargo.lock generated
View File

@@ -244,8 +244,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -343,7 +343,7 @@ dependencies = [
"idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"publicsuffix 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
"try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -422,7 +422,7 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -480,7 +480,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.0.0-beta.3"
version = "1.0.0-beta.7"
dependencies = [
"backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -495,8 +495,8 @@ dependencies = [
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"image-meta 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"imap 1.0.2 (git+https://github.com/deltachat/rust-imap?branch=fix-imap-hang1)",
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"imap 1.0.2 (git+https://github.com/deltachat/rust-imap)",
"itertools 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"jetscii 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lettre 0.9.2 (git+https://github.com/deltachat/lettre)",
@@ -519,10 +519,10 @@ dependencies = [
"rusqlite 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rustyline 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"sanitize-filename 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
"sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
"strum 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
"strum_macros 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -550,9 +550,9 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.0.0-beta.3"
version = "1.0.0-beta.7"
dependencies = [
"deltachat 1.0.0-beta.3",
"deltachat 1.0.0-beta.7",
"deltachat-provider-database 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"human-panic 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -715,7 +715,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"synstructure 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -750,7 +750,7 @@ dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"miniz_oxide 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"miniz_oxide 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -814,7 +814,7 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.1.12"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -854,7 +854,7 @@ name = "heck"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-segmentation 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -910,8 +910,8 @@ dependencies = [
"backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"os_type 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"termcolor 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1005,7 +1005,7 @@ dependencies = [
[[package]]
name = "imap"
version = "1.0.2"
source = "git+https://github.com/deltachat/rust-imap?branch=fix-imap-hang1#b69901986d4a61614be5d79c75b543d8230a4668"
source = "git+https://github.com/deltachat/rust-imap#cdcfb2ebb704676d0ea740153d3afe0b19729929"
dependencies = [
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1043,7 +1043,7 @@ dependencies = [
[[package]]
name = "itertools"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1093,8 +1093,8 @@ dependencies = [
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -1220,7 +1220,7 @@ dependencies = [
[[package]]
name = "miniz_oxide"
version = "0.3.3"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1343,8 +1343,8 @@ dependencies = [
"num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
"zeroize 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -1489,7 +1489,7 @@ dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -1543,7 +1543,7 @@ dependencies = [
"sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
"try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"twofish 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"x25519-dalek 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1729,7 +1729,7 @@ name = "rand"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1772,7 +1772,7 @@ name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1906,7 +1906,7 @@ dependencies = [
"mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2005,7 +2005,7 @@ dependencies = [
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-segmentation 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2085,7 +2085,7 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2095,20 +2095,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde"
version = "1.0.101"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_derive"
version = "1.0.101"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2118,7 +2118,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2128,7 +2128,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -2198,7 +2198,7 @@ dependencies = [
[[package]]
name = "smallvec"
version = "0.6.10"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -2245,7 +2245,7 @@ dependencies = [
"heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2285,7 +2285,7 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.5"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2308,7 +2308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -2504,7 +2504,7 @@ name = "toml"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2561,12 +2561,12 @@ name = "unicode-normalization"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-segmentation"
version = "1.3.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -2799,7 +2799,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"synstructure 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -2895,7 +2895,7 @@ dependencies = [
"checksum futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef"
"checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4"
"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
"checksum getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "473a1265acc8ff1e808cd0a1af8cee3c2ee5200916058a2ca113c29f2d903571"
"checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407"
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
"checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
"checksum h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462"
@@ -2914,11 +2914,11 @@ dependencies = [
"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"
"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
"checksum image-meta 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b00861cbbb254a627d8acc0cec786b484297d896ab8f20fdc8e28536a3e918ef"
"checksum imap 1.0.2 (git+https://github.com/deltachat/rust-imap?branch=fix-imap-hang1)" = "<none>"
"checksum imap 1.0.2 (git+https://github.com/deltachat/rust-imap)" = "<none>"
"checksum imap-proto 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1b92ca529b24c5f80a950abe993d3883df6fe6791d4a46b1fda1eb339796c589"
"checksum indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712d7b3ea5827fcb9d4fda14bf4da5f136f0db2ae9c8f4bd4e2d1c6fde4e6db2"
"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358"
"checksum itertools 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "87fa75c9dea7b07be3138c49abbb83fd4bea199b5cdc76f9804458edc5da0d6e"
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
"checksum jetscii 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5f25cca2463cb19dbb1061eb3bd38a8b5e4ce1cc5a5a9fc0e02de486d92b9b05"
"checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
@@ -2941,7 +2941,7 @@ dependencies = [
"checksum memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f"
"checksum mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "dd1d63acd1b78403cc0c325605908475dd9b9a3acbf65ed8bcab97e27014afcf"
"checksum mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a0ed03949aef72dbdf3116a383d7b38b4768e6f960528cd6a6044aa9ed68599"
"checksum miniz_oxide 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "304f66c19be2afa56530fa7c39796192eef38618da8d19df725ad7c6d6b2aaae"
"checksum miniz_oxide 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6f3f74f726ae935c3f514300cc6773a0c9492abc5e972d42ba0c0ebb88757625"
"checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23"
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e"
@@ -3027,8 +3027,8 @@ dependencies = [
"checksum security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9636f8989cbf61385ae4824b98c1aaa54c994d7d8b41f11c601ed799f0549a56"
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd"
"checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e"
"checksum serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4b39bd9b0b087684013a792c59e3e07a46a01d2322518d8a1104641a0b1be0"
"checksum serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)" = "ca13fc1a832f793322228923fbb3aba9f3f44444898f835d31ad1b74fa0a2bf8"
"checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2"
"checksum serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a"
"checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68"
@@ -3037,7 +3037,7 @@ dependencies = [
"checksum skeptic 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6fb8ed853fdc19ce09752d63f3a2e5b5158aeb261520cd75eb618bd60305165"
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
"checksum slice-deque 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ffddf594f5f597f63533d897427a570dbaa9feabaaa06595b74b71b7014507d7"
"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7"
"checksum smallvec 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cefaa50e76a6f10b86f36e640eb1739eafbd4084865067778463913e43a77ff3"
"checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
"checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
"checksum stream-cipher 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8131256a5896cabcf5eb04f4d6dacbe1aefda854b0d9896e09cb58829ec5638c"
@@ -3049,7 +3049,7 @@ dependencies = [
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
"checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741"
"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf"
"checksum syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0e7bedb3320d0f3035594b0b723c8a28d7d336a3eda3881db79e61d676fb644c"
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
"checksum synstructure 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f085a5855930c0441ca1288cf044ea4aecf4f43a91668abdb870b4ba546a203"
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
@@ -3078,7 +3078,7 @@ dependencies = [
"checksum unicase 2.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2e2e6bd1e59e56598518beb94fd6db628ded570326f0a98c679a304bd9f00150"
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
"checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426"
"checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9"
"checksum unicode-segmentation 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dc5415c074426c7c65db13bd647c23d78c0fb2e10dca0b8fb0f40058a59bccdf"
"checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20"
"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.0.0-beta.3"
version = "1.0.0-beta.7"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
license = "MPL"
@@ -20,7 +20,7 @@ num-derive = "0.2.5"
num-traits = "0.2.6"
native-tls = "0.2.3"
lettre = { git = "https://github.com/deltachat/lettre", branch = "master" }
imap = { git = "https://github.com/deltachat/rust-imap", branch = "fix-imap-hang1" }
imap = { git = "https://github.com/deltachat/rust-imap", branch = "master" }
base64 = "0.10"
charset = "0.1"
percent-encoding = "2.0"

BIN
assets/icon-device.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -43,8 +43,8 @@ if [ -n "$TESTS" ]; then
# we split out qr-tests run to minimize likelyness of flaky tests
# (some qr tests are pretty heavy in terms of send/received
# messages and rust's imap code likely has concurrency problems)
tox --workdir "$TOXWORKDIR" -e py37 -- -k "not qr"
tox --workdir "$TOXWORKDIR" -e py37 -- -k "qr"
tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "not qr"
tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "qr"
unset DCC_PY_LIVECONFIG
tox --workdir "$TOXWORKDIR" -p4 -e lint,py35,py36,doc
tox --workdir "$TOXWORKDIR" -e auditwheels

View File

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

View File

@@ -338,6 +338,8 @@ char* dc_get_blobdir (const dc_context_t* context);
* - `send_pw` = SMTP-password, guessed if left out
* - `send_port` = SMTP-port, guessed if left out
* - `server_flags` = IMAP-/SMTP-flags as a combination of @ref DC_LP flags, guessed if left out
* - `imap_certificate_checks` = how to check IMAP certificates, one of the @ref DC_CERTCK flags, defaults to #DC_CERTCK_AUTO (0)
* - `smtp_certificate_checks` = how to check SMTP certificates, one of the @ref DC_CERTCK flags, defaults to #DC_CERTCK_AUTO (0)
* - `displayname` = Own name to use when sending messages. MUAs are allowed to spread this way eg. using CC, defaults to empty
* - `selfstatus` = Own status to display eg. in email footers, defaults to a standard text
* - `selfavatar` = File containing avatar. Will be copied to blob directory.
@@ -1096,6 +1098,27 @@ uint32_t dc_send_text_msg (dc_context_t* context, uint32_t ch
void dc_set_draft (dc_context_t* context, uint32_t chat_id, dc_msg_t* msg);
/**
* Add a message to the device-chat.
* Device-messages usually contain update information
* and some hints that are added during the program runs, multi-device etc.
*
* Device-messages may be added from the core,
* however, with this function, this can be done from the ui as well.
* If needed, the device-chat is created before.
*
* Sends the event #DC_EVENT_MSGS_CHANGED on success.
* To check, if a given chat is a device-chat, see dc_chat_is_device_talk()
*
* @memberof dc_context_t
* @param context The context as created by dc_context_new().
* @param msg Message to be added to the device-chat.
* The message appears to the user as an incoming message.
* @return The ID of the added message.
*/
uint32_t dc_add_device_msg (dc_context_t* context, dc_msg_t* msg);
/**
* Get draft for a chat, if any.
* See dc_set_draft() for more details about drafts.
@@ -1211,7 +1234,7 @@ void dc_marknoticed_all_chats (dc_context_t* context);
* @memberof dc_context_t
* @param context The context object as returned from dc_context_new().
* @param chat_id The chat ID to get all messages with media from.
* @param msg_type Specify a message type to query here, one of the DC_MSG_* constats.
* @param msg_type Specify a message type to query here, one of the @ref DC_MSG constants.
* @param msg_type2 Alternative message type to search for. 0 to skip.
* @param msg_type3 Alternative message type to search for. 0 to skip.
* @return An array with messages from the given chat ID that have the wanted message types.
@@ -1518,10 +1541,10 @@ void dc_delete_msgs (dc_context_t* context, const uint3
*
* @memberof dc_context_t
* @param context The context object as created by dc_context_new()
* @param flags uint32_t with DC_EMPTY_* flags
* @param flags What to delete, a combination of the @ref DC_EMPTY flags
* @return None.
*/
void dc_empty_server (dc_context_t* context, const uint32_t flags);
void dc_empty_server (dc_context_t* context, uint32_t flags);
/**
@@ -2610,8 +2633,6 @@ int dc_chat_get_type (const dc_chat_t* chat);
*
* To change the name, use dc_set_chat_name()
*
* See also: dc_chat_get_subtitle()
*
* @memberof dc_chat_t
* @param chat The chat object.
* @return Chat name as a string. Must be released using dc_str_unref() after usage. Never NULL.
@@ -2619,13 +2640,13 @@ int dc_chat_get_type (const dc_chat_t* chat);
char* dc_chat_get_name (const dc_chat_t* chat);
/**
/*
* Get a subtitle for a chat. The subtitle is eg. the email-address or the
* number of group members.
*
* See also: dc_chat_get_name()
* Deprecated function. Subtitles should be created in the ui
* where plural forms and other specials can be handled more gracefully.
*
* @memberof dc_chat_t
* @param chat The chat object to calulate the subtitle for.
* @return Subtitle as a string. Must be released using dc_str_unref() after usage. Never NULL.
*/
@@ -2715,6 +2736,39 @@ int dc_chat_is_unpromoted (const dc_chat_t* chat);
int dc_chat_is_self_talk (const dc_chat_t* chat);
/**
* Check if a chat is a device-talk.
* Device-talks contain update information
* and some hints that are added during the program runs, multi-device etc.
*
* From the ui view, device-talks are not very special,
* the user can delete and forward messages, archive the chat, set notifications etc.
*
* Messages may be added from the core to the device chat,
* so the chat just pops up as usual.
* However, if needed the ui can also add messages using dc_add_device_msg()
*
* @memberof dc_chat_t
* @param chat The chat object.
* @return 1=chat is device-talk, 0=chat is no device-talk
*/
int dc_chat_is_device_talk (const dc_chat_t* chat);
/**
* Check if messages can be sent to a give chat.
* This is not true eg. for the deaddrop or for the device-talk, cmp. dc_chat_is_device_talk().
*
* Calling dc_send_msg() for these chats will fail
* and the ui may decide to hide input controls therefore.
*
* @memberof dc_chat_t
* @param chat The chat object.
* @return 1=chat is writable, 0=chat is not writable
*/
int dc_chat_can_send (const dc_chat_t* chat);
/**
* Check if a chat is verified. Verified chats contain only verified members
* and encryption is alwasy enabled. Verified chats are created using
@@ -3370,7 +3424,8 @@ void dc_msg_latefiling_mediasize (dc_msg_t* msg, int width, int hei
#define DC_CONTACT_ID_SELF 1
#define DC_CONTACT_ID_DEVICE 2
#define DC_CONTACT_ID_INFO 2 // centered messages as "member added", used in all chats
#define DC_CONTACT_ID_DEVICE 5 // messages "update info" in the device-chat
#define DC_CONTACT_ID_LAST_SPECIAL 9
@@ -3894,7 +3949,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*
* These constants configure TLS certificate checks for IMAP and SMTP connections.
*
* These constants are set via dc_set_config
* These constants are set via dc_set_config()
* using keys "imap_certificate_checks" and "smtp_certificate_checks".
*
* @addtogroup DC_CERTCK
@@ -3907,8 +3962,8 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
#define DC_CERTCK_AUTO 0
/**
* Strictly check TLS certificates.
* Require that both the certificate and hostname are valid.
* Strictly check TLS certificates;
* require that both the certificate and hostname are valid.
*/
#define DC_CERTCK_STRICT 1
@@ -3927,22 +3982,11 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
* @}
*/
/**
* @defgroup DC_EVENT DC_EVENT
*
* These constants are used as events
* reported to the callback given to dc_context_new().
* If you do not want to handle an event, it is always safe to return 0,
* so there is no need to add a "case" for every event.
*
* @addtogroup DC_EVENT
* @{
*/
/**
* @defgroup DC_EMPTY
* @defgroup DC_EMPTY DC_EMPTY
*
* These constants configure emptying imap folders.
* These constants configure emptying imap folders with dc_empty_server()
*
* @addtogroup DC_EMPTY
* @{
@@ -3963,6 +4007,17 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*/
/**
* @defgroup DC_EVENT DC_EVENT
*
* These constants are used as events
* reported to the callback given to dc_context_new().
* If you do not want to handle an event, it is always safe to return 0,
* so there is no need to add a "case" for every event.
*
* @addtogroup DC_EVENT
* @{
*/
/**
* The library-user may write an informational string to the log.

View File

@@ -811,6 +811,23 @@ pub unsafe extern "C" fn dc_set_draft(
.unwrap_or(())
}
#[no_mangle]
pub unsafe extern "C" fn dc_add_device_msg(context: *mut dc_context_t, msg: *mut dc_msg_t) -> u32 {
if context.is_null() || msg.is_null() {
eprintln!("ignoring careless call to dc_add_device_msg()");
return 0;
}
let ffi_context = &mut *context;
let ffi_msg = &mut *msg;
ffi_context
.with_inner(|ctx| {
chat::add_device_msg(ctx, &mut ffi_msg.message)
.unwrap_or_log_default(ctx, "Failed to add device message")
})
.map(|msg_id| msg_id.to_u32())
.unwrap_or(0)
}
#[no_mangle]
pub unsafe extern "C" fn dc_get_draft(context: *mut dc_context_t, chat_id: u32) -> *mut dc_msg_t {
if context.is_null() {
@@ -1280,8 +1297,7 @@ pub unsafe extern "C" fn dc_delete_msgs(
return;
}
let ffi_context = &*context;
let ids = std::slice::from_raw_parts(msg_ids, msg_cnt as usize);
let msg_ids: Vec<MsgId> = ids.iter().map(|id| MsgId::new(*id)).collect();
let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt);
ffi_context
.with_inner(|ctx| message::delete_msgs(ctx, &msg_ids[..]))
.unwrap_or(())
@@ -1314,8 +1330,7 @@ pub unsafe extern "C" fn dc_forward_msgs(
eprintln!("ignoring careless call to dc_forward_msgs()");
return;
}
let ids = std::slice::from_raw_parts(msg_ids, msg_cnt as usize);
let msg_ids: Vec<MsgId> = ids.iter().map(|id| MsgId::new(*id)).collect();
let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt);
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
@@ -1347,12 +1362,7 @@ pub unsafe extern "C" fn dc_markseen_msgs(
eprintln!("ignoring careless call to dc_markseen_msgs()");
return;
}
let ids = std::slice::from_raw_parts(msg_ids, msg_cnt as usize);
let msg_ids: Vec<MsgId> = ids
.iter()
.filter(|id| **id > DC_MSG_ID_LAST_SPECIAL)
.map(|id| MsgId::new(*id))
.collect();
let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt);
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| message::markseen_msgs(ctx, &msg_ids[..]))
@@ -1370,8 +1380,7 @@ pub unsafe extern "C" fn dc_star_msgs(
eprintln!("ignoring careless call to dc_star_msgs()");
return;
}
let ids = std::slice::from_raw_parts(msg_ids, msg_cnt as usize);
let msg_ids: Vec<MsgId> = ids.iter().map(|id| MsgId::new(*id)).collect();
let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt);
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| message::star_msgs(ctx, &msg_ids[..], star == 1))
@@ -1390,8 +1399,20 @@ pub unsafe extern "C" fn dc_get_msg(context: *mut dc_context_t, msg_id: u32) ->
let message = match message::Message::load_from_db(ctx, MsgId::new(msg_id)) {
Ok(msg) => msg,
Err(e) => {
error!(ctx, "Error getting msg #{}: {}", msg_id, e);
return ptr::null_mut();
if msg_id <= constants::DC_MSG_ID_LAST_SPECIAL {
// C-core API returns empty messages, do the same
warn!(
ctx,
"dc_get_msg called with special msg_id={}, returning empty msg", msg_id
);
message::Message::default()
} else {
error!(
ctx,
"dc_get_msg could not retrieve msg_id {}: {}", msg_id, e
);
return ptr::null_mut();
}
}
};
let ffi_msg = MessageWrapper { context, message };
@@ -2260,6 +2281,26 @@ pub unsafe extern "C" fn dc_chat_is_self_talk(chat: *mut dc_chat_t) -> libc::c_i
ffi_chat.chat.is_self_talk() as libc::c_int
}
#[no_mangle]
pub unsafe extern "C" fn dc_chat_is_device_talk(chat: *mut dc_chat_t) -> libc::c_int {
if chat.is_null() {
eprintln!("ignoring careless call to dc_chat_is_device_talk()");
return 0;
}
let ffi_chat = &*chat;
ffi_chat.chat.is_device_talk() as libc::c_int
}
#[no_mangle]
pub unsafe extern "C" fn dc_chat_can_send(chat: *mut dc_chat_t) -> libc::c_int {
if chat.is_null() {
eprintln!("ignoring careless call to dc_chat_can_send()");
return 0;
}
let ffi_chat = &*chat;
ffi_chat.chat.can_send() as libc::c_int
}
#[no_mangle]
pub unsafe extern "C" fn dc_chat_is_verified(chat: *mut dc_chat_t) -> libc::c_int {
if chat.is_null() {
@@ -3019,3 +3060,14 @@ impl<T, E> ResultNullableExt<T> for Result<T, E> {
}
}
}
fn convert_and_prune_message_ids(msg_ids: *const u32, msg_cnt: libc::c_int) -> Vec<MsgId> {
let ids = unsafe { std::slice::from_raw_parts(msg_ids, msg_cnt as usize) };
let msg_ids: Vec<MsgId> = ids
.iter()
.filter(|id| **id > DC_MSG_ID_LAST_SPECIAL)
.map(|id| MsgId::new(*id))
.collect();
msg_ids
}

View File

@@ -2,7 +2,7 @@ extern crate deltachat_provider_database;
use std::ptr;
use deltachat::dc_tools::{as_str, StrExt};
use deltachat::dc_tools::{to_string_lossy, StrExt};
use deltachat_provider_database::StatusState;
#[no_mangle]
@@ -12,7 +12,7 @@ pub type dc_provider_t = deltachat_provider_database::Provider;
pub unsafe extern "C" fn dc_provider_new_from_domain(
domain: *const libc::c_char,
) -> *const dc_provider_t {
match deltachat_provider_database::get_provider_info(as_str(domain)) {
match deltachat_provider_database::get_provider_info(&to_string_lossy(domain)) {
Some(provider) => provider,
None => ptr::null(),
}
@@ -22,7 +22,8 @@ pub unsafe extern "C" fn dc_provider_new_from_domain(
pub unsafe extern "C" fn dc_provider_new_from_email(
email: *const libc::c_char,
) -> *const dc_provider_t {
let domain = deltachat_provider_database::get_domain_from_email(as_str(email));
let email = to_string_lossy(email);
let domain = deltachat_provider_database::get_domain_from_email(&email);
match deltachat_provider_database::get_provider_info(domain) {
Some(provider) => provider,
None => ptr::null(),

View File

@@ -353,6 +353,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
configure\n\
connect\n\
disconnect\n\
interrupt\n\
maybenetwork\n\
housekeeping\n\
help imex (Import/Export)\n\
@@ -378,6 +379,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
sendimage <file> [<text>]\n\
sendfile <file> [<text>]\n\
draft [<text>]\n\
devicemsg <text>\n\
listmedia\n\
archive <chat-id>\n\
unarchive <chat-id>\n\
@@ -493,6 +495,9 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
"info" => {
println!("{:#?}", context.get_info());
}
"interrupt" => {
interrupt_imap_idle(context);
}
"maybenetwork" => {
maybe_network(context);
}
@@ -517,15 +522,12 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
for i in (0..cnt).rev() {
let chat = Chat::load_from_db(context, chatlist.get_chat_id(i))?;
let temp_subtitle = chat.get_subtitle(context);
let temp_name = chat.get_name();
info!(
context,
"{}#{}: {} [{}] [{} fresh]",
"{}#{}: {} [{} fresh]",
chat_prefix(&chat),
chat.get_id(),
temp_name,
temp_subtitle,
chat.get_name(),
chat::get_fresh_msg_cnt(context, chat.get_id()),
);
let lot = chatlist.get_summary(context, i, Some(&chat));
@@ -583,20 +585,34 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let sel_chat = sel_chat.as_ref().unwrap();
let msglist = chat::get_chat_msgs(context, sel_chat.get_id(), 0x1, None);
let temp2 = sel_chat.get_subtitle(context);
let temp_name = sel_chat.get_name();
let members = chat::get_chat_contacts(context, sel_chat.id);
let subtitle = if sel_chat.is_device_talk() {
"device-talk".to_string()
} else if sel_chat.get_type() == Chattype::Single && members.len() >= 1 {
let contact = Contact::get_by_id(context, members[0])?;
contact.get_addr().to_string()
} else {
format!("{} member(s)", members.len())
};
info!(
context,
"{}#{}: {} [{}]{}",
"{}#{}: {} [{}]{}{}",
chat_prefix(sel_chat),
sel_chat.get_id(),
temp_name,
temp2,
sel_chat.get_name(),
subtitle,
if sel_chat.is_sending_locations() {
"📍"
} else {
""
},
match sel_chat.get_profile_image(context) {
Some(icon) => match icon.to_str() {
Some(icon) => format!(" Icon: {}", icon),
_ => " Icon: Err".to_string(),
},
_ => "".to_string(),
},
);
log_msglist(context, &msglist)?;
if let Some(draft) = chat::get_draft(context, sel_chat.get_id())? {
@@ -814,6 +830,15 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
println!("Draft deleted.");
}
}
"devicemsg" => {
ensure!(
!arg1.is_empty(),
"Please specify text to add as device message."
);
let mut msg = Message::new(Viewtype::Text);
msg.set_text(Some(arg1.to_string()));
chat::add_device_msg(context, &mut msg)?;
}
"listmedia" => {
ensure!(sel_chat.is_some(), "No chat selected.");

View File

@@ -0,0 +1,7 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 03cab93c6d1f3a8245f63cf84dacb307944294fe6333c1e38f078a6600659c7a # shrinks to data = "a\t0aA\ta\t0 \ta\t0 \ta a\t\ta A\tAA0a0a 0\t a\t aA \t a\t A0\t AAa\taA0\taAAaA\t0\taa0a\ta Aa Aaaa A0A\t a aA 0\t A\t0\t0\t\t\t\t\t\tA \t\t a\tA Aa aAA0A0AA0aaA A\t\t aa0\ta\t \tAa\taA\t00 AA A a\tA a aAAa \t 00\t0 \t\t a A 0\t\t\t aAA Aa \taAAAA0a A0A\t\t1\\E$\t$R\tc\t^=\t\"\tQ<Uk\t\t>A\t\t&\t}v&\tM^\'|\tW5?dn\t\t+\t\tP\te`\t\t>:Brlq--?\t$#Q\tK=zJ\tb\"9*.\"\t`\tF&T*\tBs,\tg\'*\\\t:\t?l$\t\t|A\"HR:Hk\t\\KkV\t\t{=&!^e%:|_*0wV\t[$`\t:\t$%f\t\t[!\"Y. \tP\t\th\'?\'/?%:++NfQo#@\"+?\t(\\??{\t\'\'$Dzj\t0.?{s4?&?Y=/yj]Z=\t4n\t?Ja\"\t{I\t$\t;I}_8V\t&\t?N\'\tI2/\t9.\tFT%9%`\'\tz\to7Y\t|AXP&@G12g\t\'w\t\t%??\t\"h$?F\"\"6%q\\\\{\tT\t\"]$87$}h\'\t<\t$\tc%U:mT2:<v\t#Rl!;U\t\t\"^D\tRZ(BZ{n\t%[$fgH`\t{B}:*\t\t%%*F`%W\t//B}PQ\t\tsu2\tLz<1*!p-X\tnKv&&0\thm4<n\\.\\/.w\'\t<)E1g3#ood\t`?\t\\({N?,8_?h\ty\t0%\t*$A\t\t*w-ViQUj\tTiH\t%\t%&0p\'\'\tA%r**Fo\'Z\\\tNI*ely4=I?}$.o`\t$\ts\'&lt\t\",:~=Nv/0O%=+<LS\t%P\'\t$r%d.\t{G{/L:\t&I&8-`cy*\"{\t/%fP9.P<\t\t\'/`\t\t`\t\t`!t:\t::\t\tW\'X^\t@.uL&a\tN\t\t\t.\t?0*\tvUK>UJ\\\tQbj%w]P=Js\t\"R\t&s^?</=%\t\'VI:\" kT`{b*<\t\tF&?Y\t\t:/\t!C\'e0`\t\t\tx-\t*\\N\\wwu.I4\tb/y\t\"P:P%\"\t\tQ][\\\t^x\t\t):\t\t&s\t$1-\t\t\tXt`0\t;\t/UeCP*\"G\t\t\':\tk@91Hf&#\t(Uzz&`p2;\t{]\t\"I_%=\\%,XD\"\'06QfCd<;$:q^\t8:\"*\"H\t\to\t&xK/\t\ty0/<}<j<|*`~::b\t=S.=}=Ki\t<Y.\'{\tf\t{Ub*H%(_4*?*\tn2\t/\'\t\t\t/,4]\tt\t<y\t\t\tWi\t\tT&\"\t\t\t\t\t=/1Wu?\t\'A\"W-P\t$?e\\\t`\t6`vD\t8`\t\tccD J\tY&jI//?_\t\\j\t_\tsiq$\t?9\tQ\t.^%&..?%Jm??A%+<\tN&*\t.g\tS$W\"\"\tMpq\t\t:&\\\thTz&<iz%/%T&\'F\t\\]&\t\t}\t\t\tXr=dtp`:+<,\t%60Y*<(&K*kJ\todO\t=<V?&\tMU/\"\t= Y!)<\tV\t9\t)\t&v8q{\t\t&pT\t3\ttB,qcq\'i$;gv%j_%M_{[\"&;\t\t\t.B;6Y\\%\t\"\tY/a\t\\`:?\t<\t?]\taNwD;\\\t%l*74%5T?QS :~yR/E*R\t\t=u\t\\\t\t.Q<;\\\t_S/{&F$^\tw_>\'h=|%\t\t:W!\\<U2\'$\tb&`\t=|=L9\t\t\t\\WZ:! }{\t ;\t;\t\t 0.*\t.%\"\'r%{<Mh_t[[}\t-\tJo\"b/AC*-=`=T\tz$2\tC\t\t/k{47\"\t\t,q%\tZ\tT3\t\tf>%\t\'?%@^tx\t7\"1Bk{b{n\t\"Pj3\tHc\t\tt\tY<\t#?\tSh\\yk/N\\\t8 7=R4*9Cw&m\t\\-\'f\t|\'#t(Etu.Hdu(E\t%&v:\'aqW~A5\t\t w.s{J%lX<\"\t\'*%m<&:/B<&\':U}$&`.{)\t\t6S\t:/$*kQ-Z\t^\'t${/tH*\'v\t3\t=\t\tDyp:B\t`I_R&4SO\t\t&-j=*.\t87&\'e*( \t\t\t\'<$\\DJ<$p?{}\'&\tv\t\\Xk<Y:Y`!$K{\tF&\tzd\t\t*i$\tj\'\t<)R*\t%?\t!.\t=\"@#~:=*\t\tXO=_T,1\"\'.%%\"`{\\:\t\"\tfkeOb/\'$I~\ta\t|&\t[\\KK\"1&Z\t<k\t\t)%\'-~\"2n\tj\tW?*<@w{g%d\ta\\\'\'I\t;:ySR%ke:4\tc\t$=\t&9P]x4\tJ=\t6C6%a\t`0\tF\tm-\tTr\t}\t\tQum\t&@\typ|w2&\t\t3`i&t\t\tT5\"\t.&b&e*/==1.\'*\\[U*\tqPL%?$-0/}~|q`\t\t}\t$\tq==o+T$\'!H\t\ti&um\"?\"%%\t/\'p\tg>?{0{J{\t\t/\t\t{zKZ&>=\t[\"1h<H%z/8,/]s\tv{7\t\t:j*H,M//\t\t\td\'.)\t"

View File

@@ -8,8 +8,8 @@ high level API reference
- :class:`deltachat.account.Account` (your main entry point, creates the
other classes)
- :class:`deltachat.chatting.Contact`
- :class:`deltachat.chatting.Chat`
- :class:`deltachat.contact.Contact`
- :class:`deltachat.chat.Chat`
- :class:`deltachat.message.Message`
Account
@@ -22,13 +22,13 @@ Account
Contact
-------
.. autoclass:: deltachat.chatting.Contact
.. autoclass:: deltachat.contact.Contact
:members:
Chat
----
.. autoclass:: deltachat.chatting.Chat
.. autoclass:: deltachat.chat.Chat
:members:
Message

View File

@@ -15,12 +15,14 @@ import deltachat
from . import const
from .capi import ffi, lib
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array, DCLot
from .chatting import Contact, Chat, Message
from .chat import Chat
from .message import Message
from .contact import Contact
class Account(object):
""" Each account is tied to a sqlite database file which is fully managed
by the underlying deltachat c-library. All public Account methods are
by the underlying deltachat core library. All public Account methods are
meant to be memory-safe and return memory-safe objects.
"""
def __init__(self, db_path, logid=None, eventlogging=True, debug=True):
@@ -151,6 +153,14 @@ class Account(object):
self.check_is_configured()
return from_dc_charpointer(lib.dc_get_info(self._dc_context))
def get_latest_backupfile(self, backupdir):
""" return the latest backup file in a given directory.
"""
res = lib.dc_imex_has_backup(self._dc_context, as_dc_charpointer(backupdir))
if res == ffi.NULL:
return None
return from_dc_charpointer(res)
def get_blobdir(self):
""" return the directory for files.
@@ -160,9 +170,9 @@ class Account(object):
return from_dc_charpointer(lib.dc_get_blobdir(self._dc_context))
def get_self_contact(self):
""" return this account's identity as a :class:`deltachat.chatting.Contact`.
""" return this account's identity as a :class:`deltachat.contact.Contact`.
:returns: :class:`deltachat.chatting.Contact`
:returns: :class:`deltachat.contact.Contact`
"""
self.check_is_configured()
return Contact(self._dc_context, const.DC_CONTACT_ID_SELF)
@@ -174,7 +184,7 @@ class Account(object):
:param email: email-address (text type)
:param name: display name for this contact (optional)
:returns: :class:`deltachat.chatting.Contact` instance.
:returns: :class:`deltachat.contact.Contact` instance.
"""
name = as_dc_charpointer(name)
email = as_dc_charpointer(email)
@@ -200,7 +210,7 @@ class Account(object):
whose name or e-mail matches query.
:param only_verified: if true only return verified contacts.
:param with_self: if true the self-contact is also returned.
:returns: list of :class:`deltachat.chatting.Contact` objects.
:returns: list of :class:`deltachat.contact.Contact` objects.
"""
flags = 0
query = as_dc_charpointer(query)
@@ -218,7 +228,7 @@ class Account(object):
""" create or get an existing 1:1 chat object for the specified contact or contact id.
:param contact: chat_id (int) or contact object.
:returns: a :class:`deltachat.chatting.Chat` object.
:returns: a :class:`deltachat.chat.Chat` object.
"""
if hasattr(contact, "id"):
if contact._dc_context != self._dc_context:
@@ -235,7 +245,7 @@ class Account(object):
the specified message.
:param message: messsage id or message instance.
:returns: a :class:`deltachat.chatting.Chat` object.
:returns: a :class:`deltachat.chat.Chat` object.
"""
if hasattr(message, "id"):
if self._dc_context != message._dc_context:
@@ -253,7 +263,7 @@ class Account(object):
Chats are unpromoted until the first message is sent.
:param verified: if true only verified contacts can be added.
:returns: a :class:`deltachat.chatting.Chat` object.
:returns: a :class:`deltachat.chat.Chat` object.
"""
bytes_name = name.encode("utf8")
chat_id = lib.dc_create_group_chat(self._dc_context, int(verified), bytes_name)
@@ -262,7 +272,7 @@ class Account(object):
def get_chats(self):
""" return list of chats.
:returns: a list of :class:`deltachat.chatting.Chat` objects.
:returns: a list of :class:`deltachat.chat.Chat` objects.
"""
dc_chatlist = ffi.gc(
lib.dc_get_chatlist(self._dc_context, 0, ffi.NULL, 0),
@@ -280,9 +290,24 @@ class Account(object):
return Chat(self, const.DC_CHAT_ID_DEADDROP)
def get_message_by_id(self, msg_id):
""" return Message instance. """
""" return Message instance.
:param msg_id: integer id of this message.
:returns: :class:`deltachat.message.Message` instance.
"""
return Message.from_db(self, msg_id)
def get_chat_by_id(self, chat_id):
""" return Chat instance.
:param chat_id: integer id of this chat.
:returns: :class:`deltachat.chat.Chat` instance.
:raises: ValueError if chat does not exist.
"""
res = lib.dc_get_chat(self._dc_context, chat_id)
if res == ffi.NULL:
raise ValueError("cannot get chat with id={}".format(chat_id))
lib.dc_chat_unref(res)
return Chat(self, chat_id)
def mark_seen_messages(self, messages):
""" mark the given set of messages as seen.
@@ -299,7 +324,7 @@ class Account(object):
""" Forward list of messages to a chat.
:param messages: list of :class:`deltachat.message.Message` object.
:param chat: :class:`deltachat.chatting.Chat` object.
:param chat: :class:`deltachat.chat.Chat` object.
:returns: None
"""
msg_ids = [msg.id for msg in messages]
@@ -411,7 +436,7 @@ class Account(object):
""" setup contact and return a Chat after contact is established.
Note that this function may block for a long time as messages are exchanged
with the emitter of the QR code. On success a :class:`deltachat.chatting.Chat` instance
with the emitter of the QR code. On success a :class:`deltachat.chat.Chat` instance
is returned.
:param qr: valid "setup contact" QR code (all other QR codes will result in an exception)
"""
@@ -425,7 +450,7 @@ class Account(object):
""" join a chat group through a QR code.
Note that this function may block for a long time as messages are exchanged
with the emitter of the QR code. On success a :class:`deltachat.chatting.Chat` instance
with the emitter of the QR code. On success a :class:`deltachat.chat.Chat` instance
is returned which is the chat that we just joined.
:param qr: valid "join-group" QR code (all other QR codes will result in an exception)
@@ -460,16 +485,20 @@ class Account(object):
def stop_threads(self, wait=True):
""" stop IMAP/SMTP threads. """
self.stop_ongoing()
self._threads.stop(wait=wait)
if self._threads.is_started():
self.stop_ongoing()
self._threads.stop(wait=wait)
def shutdown(self, wait=True):
print("SHUTDOWN", self)
""" stop threads and close and remove underlying dc_context and callbacks. """
if hasattr(self, "_dc_context") and hasattr(self, "_threads"):
# print("SHUTDOWN", self)
self.stop_threads(wait=False)
print("stop_threads", self)
self.stop_threads(wait=wait)
print("close", self)
lib.dc_close(self._dc_context)
self.stop_threads(wait=wait) # to wait for threads
print("clear", self)
#self.stop_threads(wait=wait) # to wait for threads
deltachat.clear_context_callback(self._dc_context)
del self._dc_context
atexit.unregister(self.shutdown)
@@ -492,6 +521,20 @@ class Account(object):
def on_dc_event_imex_file_written(self, data1, data2):
self._imex_events.put(data1)
def set_location(self, latitude=0.0, longitude=0.0, accuracy=0.0):
"""set a new location. It effects all chats where we currently
have enabled location streaming.
:param latitude: float (use 0.0 if not known)
:param longitude: float (use 0.0 if not known)
:param accuracy: float (use 0.0 if not known)
:raises: ValueError if no chat is currently streaming locations
:returns: None
"""
dc_res = lib.dc_set_location(self._dc_context, latitude, longitude, accuracy)
if dc_res == 0:
raise ValueError("no chat is streaming locations")
class IOThreads:
def __init__(self, dc_context, log_event=lambda *args: None):
@@ -527,37 +570,45 @@ class IOThreads:
lib.dc_interrupt_sentbox_idle(self._dc_context)
if wait:
for name, thread in self._name2thread.items():
print("joining", name)
thread.join()
def imap_thread_run(self):
self._log_event("py-bindings-info", 0, "INBOX THREAD START")
while not self._thread_quitflag:
lib.dc_perform_imap_jobs(self._dc_context)
lib.dc_perform_imap_fetch(self._dc_context)
lib.dc_perform_imap_idle(self._dc_context)
if not self._thread_quitflag:
lib.dc_perform_imap_fetch(self._dc_context)
if not self._thread_quitflag:
lib.dc_perform_imap_idle(self._dc_context)
self._log_event("py-bindings-info", 0, "INBOX THREAD FINISHED")
def mvbox_thread_run(self):
self._log_event("py-bindings-info", 0, "MVBOX THREAD START")
while not self._thread_quitflag:
lib.dc_perform_mvbox_jobs(self._dc_context)
lib.dc_perform_mvbox_fetch(self._dc_context)
lib.dc_perform_mvbox_idle(self._dc_context)
if not self._thread_quitflag:
lib.dc_perform_mvbox_fetch(self._dc_context)
if not self._thread_quitflag:
lib.dc_perform_mvbox_idle(self._dc_context)
self._log_event("py-bindings-info", 0, "MVBOX THREAD FINISHED")
def sentbox_thread_run(self):
self._log_event("py-bindings-info", 0, "SENTBOX THREAD START")
while not self._thread_quitflag:
lib.dc_perform_sentbox_jobs(self._dc_context)
lib.dc_perform_sentbox_fetch(self._dc_context)
lib.dc_perform_sentbox_idle(self._dc_context)
if not self._thread_quitflag:
lib.dc_perform_sentbox_fetch(self._dc_context)
if not self._thread_quitflag:
lib.dc_perform_sentbox_idle(self._dc_context)
self._log_event("py-bindings-info", 0, "SENTBOX THREAD FINISHED")
def smtp_thread_run(self):
self._log_event("py-bindings-info", 0, "SMTP THREAD START")
while not self._thread_quitflag:
lib.dc_perform_smtp_jobs(self._dc_context)
lib.dc_perform_smtp_idle(self._dc_context)
if not self._thread_quitflag:
lib.dc_perform_smtp_idle(self._dc_context)
self._log_event("py-bindings-info", 0, "SMTP THREAD FINISHED")

View File

@@ -1,58 +1,15 @@
""" chatting related objects: Contact, Chat, Message. """
""" Chat and Location related API. """
import mimetypes
import calendar
from datetime import datetime
import os
from . import props
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
from .capi import lib, ffi
from . import const
from .message import Message
class Contact(object):
""" Delta-Chat Contact.
You obtain instances of it through :class:`deltachat.account.Account`.
"""
def __init__(self, dc_context, id):
self._dc_context = dc_context
self.id = id
def __eq__(self, other):
return self._dc_context == other._dc_context and self.id == other.id
def __ne__(self, other):
return not (self == other)
def __repr__(self):
return "<Contact id={} addr={} dc_context={}>".format(self.id, self.addr, self._dc_context)
@property
def _dc_contact(self):
return ffi.gc(
lib.dc_get_contact(self._dc_context, self.id),
lib.dc_contact_unref
)
@props.with_doc
def addr(self):
""" normalized e-mail address for this account. """
return from_dc_charpointer(lib.dc_contact_get_addr(self._dc_contact))
@props.with_doc
def display_name(self):
""" display name for this contact. """
return from_dc_charpointer(lib.dc_contact_get_display_name(self._dc_contact))
def is_blocked(self):
""" Return True if the contact is blocked. """
return lib.dc_contact_is_blocked(self._dc_contact)
def is_verified(self):
""" Return True if the contact is verified. """
return lib.dc_contact_is_verified(self._dc_contact)
class Chat(object):
""" Chat object which manages members and through which you can send and retrieve messages.
@@ -312,9 +269,10 @@ class Chat(object):
def get_contacts(self):
""" get all contacts for this chat.
:params: contact object.
:returns: list of :class:`deltachat.chatting.Contact` objects for this chat
:returns: list of :class:`deltachat.contact.Contact` objects for this chat
"""
from .contact import Contact
dc_array = ffi.gc(
lib.dc_get_chat_contacts(self._dc_context, self.id),
lib.dc_array_unref
@@ -365,3 +323,62 @@ class Chat(object):
if dc_res == ffi.NULL:
return None
return from_dc_charpointer(dc_res)
# ------ location streaming API ------------------------------
def is_sending_locations(self):
"""return True if this chat has location-sending enabled currently.
:returns: True if location sending is enabled.
"""
return lib.dc_is_sending_locations_to_chat(self._dc_context, self.id)
def enable_sending_locations(self, seconds):
"""enable sending locations for this chat.
all subsequent messages will carry a location with them.
"""
lib.dc_send_locations_to_chat(self._dc_context, self.id, seconds)
def get_locations(self, contact=None, timestamp_from=None, timestamp_to=None):
"""return list of locations for the given contact in the given timespan.
:param contact: the contact for which locations shall be returned.
:param timespan_from: a datetime object or None (indicating "since beginning")
:param timespan_to: a datetime object or None (indicating up till now)
:returns: list of :class:`deltachat.chat.Location` objects.
"""
if timestamp_from is None:
time_from = 0
else:
time_from = calendar.timegm(timestamp_from.utctimetuple())
if timestamp_to is None:
time_to = 0
else:
time_to = calendar.timegm(timestamp_to.utctimetuple())
if contact is None:
contact_id = 0
else:
contact_id = contact.id
dc_array = lib.dc_get_locations(self._dc_context, self.id, contact_id, time_from, time_to)
return [
Location(
latitude=lib.dc_array_get_latitude(dc_array, i),
longitude=lib.dc_array_get_longitude(dc_array, i),
accuracy=lib.dc_array_get_accuracy(dc_array, i),
timestamp=datetime.utcfromtimestamp(lib.dc_array_get_timestamp(dc_array, i)))
for i in range(lib.dc_array_get_cnt(dc_array))
]
class Location:
def __init__(self, latitude, longitude, accuracy, timestamp):
assert isinstance(timestamp, datetime)
self.latitude = latitude
self.longitude = longitude
self.accuracy = accuracy
self.timestamp = timestamp
def __eq__(self, other):
return self.__dict__ == other.__dict__

View File

@@ -47,7 +47,8 @@ DC_STATE_OUT_FAILED = 24
DC_STATE_OUT_DELIVERED = 26
DC_STATE_OUT_MDN_RCVD = 28
DC_CONTACT_ID_SELF = 1
DC_CONTACT_ID_DEVICE = 2
DC_CONTACT_ID_INFO = 2
DC_CONTACT_ID_DEVICE = 5
DC_CONTACT_ID_LAST_SPECIAL = 9
DC_MSG_TEXT = 10
DC_MSG_IMAGE = 20

View File

@@ -0,0 +1,49 @@
""" Contact object. """
from . import props
from .cutil import from_dc_charpointer
from .capi import lib, ffi
class Contact(object):
""" Delta-Chat Contact.
You obtain instances of it through :class:`deltachat.account.Account`.
"""
def __init__(self, dc_context, id):
self._dc_context = dc_context
self.id = id
def __eq__(self, other):
return self._dc_context == other._dc_context and self.id == other.id
def __ne__(self, other):
return not (self == other)
def __repr__(self):
return "<Contact id={} addr={} dc_context={}>".format(self.id, self.addr, self._dc_context)
@property
def _dc_contact(self):
return ffi.gc(
lib.dc_get_contact(self._dc_context, self.id),
lib.dc_contact_unref
)
@props.with_doc
def addr(self):
""" normalized e-mail address for this account. """
return from_dc_charpointer(lib.dc_contact_get_addr(self._dc_contact))
@props.with_doc
def display_name(self):
""" display name for this contact. """
return from_dc_charpointer(lib.dc_contact_get_display_name(self._dc_contact))
def is_blocked(self):
""" Return True if the contact is blocked. """
return lib.dc_contact_is_blocked(self._dc_contact)
def is_verified(self):
""" Return True if the contact is verified. """
return lib.dc_contact_is_verified(self._dc_contact)

View File

@@ -1,4 +1,4 @@
""" chatting related objects: Contact, Chat, Message. """
""" The Message object. """
import os
import shutil
@@ -13,7 +13,7 @@ class Message(object):
""" Message object.
You obtain instances of it through :class:`deltachat.account.Account` or
:class:`deltachat.chatting.Chat`.
:class:`deltachat.chat.Chat`.
"""
def __init__(self, account, dc_msg):
self.account = account
@@ -169,18 +169,18 @@ class Message(object):
def chat(self):
"""chat this message was posted in.
:returns: :class:`deltachat.chatting.Chat` object
:returns: :class:`deltachat.chat.Chat` object
"""
from .chatting import Chat
from .chat import Chat
chat_id = lib.dc_msg_get_chat_id(self._dc_msg)
return Chat(self.account, chat_id)
def get_sender_contact(self):
"""return the contact of who wrote the message.
:returns: :class:`deltachat.chatting.Contact` instance
:returns: :class:`deltachat.chat.Contact` instance
"""
from .chatting import Contact
from .contact import Contact
contact_id = lib.dc_msg_get_from_id(self._dc_msg)
return Contact(self._dc_context, contact_id)

View File

@@ -2,6 +2,7 @@ from __future__ import print_function
import pytest
import os
import queue
import time
from deltachat import const, Account
from deltachat.message import Message
from datetime import datetime, timedelta
@@ -121,6 +122,12 @@ class TestOfflineChat:
str(chat1)
repr(chat1)
def test_chat_by_id(self, chat1):
chat2 = chat1.account.get_chat_by_id(chat1.id)
assert chat2 == chat1
with pytest.raises(ValueError):
chat1.account.get_chat_by_id(123123)
def test_chat_idempotent(self, chat1, ac1):
contact1 = chat1.get_contacts()[0]
chat2 = ac1.create_chat_by_contact(contact1.id)
@@ -635,18 +642,29 @@ class TestOnlineAccount:
assert os.path.exists(msg_in.filename)
assert os.stat(msg_in.filename).st_size == os.stat(path).st_size
def test_import_export_online_all(self, acfactory, tmpdir):
def test_import_export_online_all_twice(self, acfactory, tmpdir, lp):
ac1 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac1, 1000)
lp.sec("create some chat content")
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
chat.send_text("msg1")
backupdir = tmpdir.mkdir("backup")
lp.sec("export all to {}".format(backupdir))
path = ac1.export_all(backupdir.strpath)
assert os.path.exists(path)
t = time.time()
lp.sec("get fresh empty account")
ac2 = acfactory.get_unconfigured_account()
lp.sec("get latest backup file")
path2 = ac2.get_latest_backupfile(backupdir.strpath)
assert path2 == path
lp.sec("import backup and check it's proper")
ac2.import_all(path)
contacts = ac2.get_contacts(query="some1")
assert len(contacts) == 1
@@ -657,6 +675,17 @@ class TestOnlineAccount:
assert len(messages) == 1
assert messages[0].text == "msg1"
# wait until a second passed since last backup
# because get_latest_backupfile() shall return the latest backup
# from a UI it's unlikely anyone manages to export two
# backups in one second.
time.sleep(max(0, 1 - (time.time() - t)))
lp.sec("Second-time export all to {}".format(backupdir))
path2 = ac1.export_all(backupdir.strpath)
assert os.path.exists(path2)
assert path2 != path
assert ac2.get_latest_backupfile(backupdir.strpath) == path2
def test_ac_setup_message(self, acfactory, lp):
# note that the receiving account needs to be configured and running
# before ther setup message is send. DC does not read old messages
@@ -682,6 +711,27 @@ class TestOnlineAccount:
msg.continue_key_transfer(setup_code)
assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"]
def test_ac_setup_message_twice(self, acfactory, lp):
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.clone_online_account(ac1)
ac2._evlogger.set_timeout(30)
wait_configuration_progress(ac2, 1000)
wait_configuration_progress(ac1, 1000)
lp.sec("trigger ac setup message but ignore")
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
ac1.initiate_key_transfer()
ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
lp.sec("trigger second ac setup message, wait for receive ")
setup_code2 = ac1.initiate_key_transfer()
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
msg = ac2.get_message_by_id(ev[2])
assert msg.is_setup_message()
assert msg.get_setupcodebegin() == setup_code2[:2]
lp.sec("process second setup message")
msg.continue_key_transfer(setup_code2)
assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"]
def test_qr_setup_contact(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("ac1: create QR code and let ac2 scan it, starting the securejoin")
@@ -789,6 +839,54 @@ class TestOnlineAccount:
assert chat1b.get_profile_image() is None
assert chat.get_profile_image() is None
def test_send_receive_locations(self, acfactory, lp):
now = datetime.utcnow()
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("ac1: create chat with ac2")
chat1 = self.get_chat(ac1, ac2)
chat2 = self.get_chat(ac2, ac1)
assert not chat1.is_sending_locations()
with pytest.raises(ValueError):
ac1.set_location(latitude=0.0, longitude=10.0)
ac1._evlogger.consume_events()
ac2._evlogger.consume_events()
lp.sec("ac1: enable location sending in chat")
chat1.enable_sending_locations(seconds=100)
assert chat1.is_sending_locations()
ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
ac1.set_location(latitude=2.0, longitude=3.0, accuracy=0.5)
ac1._evlogger.get_matching("DC_EVENT_LOCATION_CHANGED")
chat1.send_text("hello")
ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
lp.sec("ac2: wait for incoming location message")
ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG") # "enabled-location streaming"
# currently core emits location changed before event_incoming message
ac2._evlogger.get_matching("DC_EVENT_LOCATION_CHANGED")
ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG") # text message with location
locations = chat2.get_locations()
assert len(locations) == 1
assert locations[0].latitude == 2.0
assert locations[0].longitude == 3.0
assert locations[0].accuracy == 0.5
assert locations[0].timestamp > now
contact = ac2.create_contact(ac1.get_config("addr"))
locations2 = chat2.get_locations(contact=contact)
assert len(locations2) == 1
assert locations2 == locations
contact = ac2.create_contact("nonexisting@example.org")
locations3 = chat2.get_locations(contact=contact)
assert not locations3
class TestOnlineConfigureFails:
def test_invalid_password(self, acfactory):

View File

@@ -95,6 +95,13 @@ def test_markseen_invalid_message_ids(acfactory):
ac1._evlogger.ensure_event_not_queued("DC_EVENT_WARNING|DC_EVENT_ERROR")
def test_get_special_message_id_returns_empty_message(acfactory):
ac1 = acfactory.get_configured_offline_account()
for i in range(1, 10):
msg = ac1.get_message_by_id(i)
assert msg.id == 0
def test_provider_info():
provider = lib.dc_provider_new_from_email(cutil.as_dc_charpointer("ex@example.com"))
assert cutil.from_dc_charpointer(

61
set_core_version.py Normal file
View File

@@ -0,0 +1,61 @@
#!/usr/bin/env python
import os
import sys
import re
import pathlib
import subprocess
rex = re.compile(r'version = "(\S+)"')
def read_toml_version(relpath):
p = pathlib.Path(relpath)
assert p.exists()
for line in open(str(p)):
m = rex.match(line)
if m is not None:
return m.group(1)
raise ValueError("no version found in {}".format(relpath))
def replace_toml_version(relpath, newversion):
p = pathlib.Path(relpath)
assert p.exists()
tmp_path = str(p) + "_tmp"
with open(tmp_path, "w") as f:
for line in open(str(p)):
m = rex.match(line)
if m is not None:
f.write('version = "{}"\n'.format(newversion))
else:
f.write(line)
os.rename(tmp_path, str(p))
if __name__ == "__main__":
if len(sys.argv) < 2:
raise SystemExit("need argument: new version, example 1.0.0-beta.27")
newversion = sys.argv[1]
if newversion.count(".") < 2:
raise SystemExit("need at least two dots in version")
core_toml = read_toml_version("Cargo.toml")
ffi_toml = read_toml_version("deltachat-ffi/Cargo.toml")
assert core_toml == ffi_toml, (core_toml, ffi_toml)
for line in open("CHANGELOG.md"):
## 1.0.0-beta5
if line.startswith("## "):
if line[2:].strip().startswith(newversion):
break
else:
raise SystemExit("CHANGELOG.md contains no entry for version: {}".format(newversion))
replace_toml_version("Cargo.toml", newversion)
replace_toml_version("deltachat-ffi/Cargo.toml", newversion)
subprocess.call(["cargo", "update", "-p", "deltachat"])
print("after commit make sure to: ")
print("")
print(" git tag {}".format(newversion))
print("")

View File

@@ -1,3 +1,4 @@
use std::ffi::OsStr;
use std::fmt;
use std::fs;
use std::io::Write;
@@ -24,7 +25,8 @@ impl<'a> BlobObject<'a> {
/// Creates a new file in the blob directory. The name will be
/// derived from the platform-agnostic basename of the suggested
/// name, followed by a random number and followed by a possible
/// extension. The `data` will be written into the file.
/// extension. The `data` will be written into the file without
/// race-conditions.
///
/// # Errors
///
@@ -41,29 +43,33 @@ impl<'a> BlobObject<'a> {
data: &[u8],
) -> std::result::Result<BlobObject<'a>, BlobError> {
let blobdir = context.get_blobdir();
let (stem, ext) = BlobObject::sanitise_name(suggested_name.as_ref().to_string());
let mut name = format!("{}{}", stem, ext);
let (stem, ext) = BlobObject::sanitise_name(suggested_name.as_ref());
let (name, mut file) = BlobObject::create_new_file(&blobdir, &stem, &ext)?;
file.write_all(data)
.map_err(|err| BlobError::new_write_failure(blobdir, &name, err))?;
let blob = BlobObject {
blobdir,
name: format!("$BLOBDIR/{}", name),
};
context.call_cb(Event::NewBlobFile(blob.as_name().to_string()));
Ok(blob)
}
// Creates a new file, returning a tuple of the name and the handle.
fn create_new_file(dir: &Path, stem: &str, ext: &str) -> Result<(String, fs::File), BlobError> {
let max_attempt = 15;
let mut name = format!("{}{}", stem, ext);
for attempt in 0..max_attempt {
let path = blobdir.join(&name);
let path = dir.join(&name);
match fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(&path)
{
Ok(mut file) => {
file.write_all(data)
.map_err(|err| BlobError::new_write_failure(blobdir, &name, err))?;
let blob = BlobObject {
blobdir,
name: format!("$BLOBDIR/{}", name),
};
context.call_cb(Event::NewBlobFile(blob.as_name().to_string()));
return Ok(blob);
}
Ok(file) => return Ok((name, file)),
Err(err) => {
if attempt == max_attempt {
return Err(BlobError::new_create_failure(blobdir, &name, err));
return Err(BlobError::new_create_failure(dir, &name, err));
} else {
name = format!("{}-{}{}", stem, rand::random::<u32>(), ext);
}
@@ -71,7 +77,7 @@ impl<'a> BlobObject<'a> {
}
}
Err(BlobError::new_create_failure(
blobdir,
dir,
&name,
format_err!("Unreachable code - supposedly"),
))
@@ -80,7 +86,9 @@ impl<'a> BlobObject<'a> {
/// Creates a new blob object with unique name by copying an existing file.
///
/// This creates a new blob as described in [BlobObject::create]
/// but also copies an existing file into it.
/// but also copies an existing file into it. This is done in a
/// in way which avoids race-conditions when multiple files are
/// concurrently created.
///
/// # Errors
///
@@ -91,11 +99,24 @@ impl<'a> BlobObject<'a> {
context: &'a Context,
src: impl AsRef<Path>,
) -> std::result::Result<BlobObject<'a>, BlobError> {
let blob = BlobObject::create(context, src.as_ref().to_string_lossy(), b"")?;
fs::copy(src.as_ref(), blob.to_abs_path()).map_err(|err| {
fs::remove_file(blob.to_abs_path()).ok();
BlobError::new_copy_failure(blob.blobdir, &blob.name, src.as_ref(), err)
let mut src_file = fs::File::open(src.as_ref()).map_err(|err| {
BlobError::new_copy_failure(context.get_blobdir(), "", src.as_ref(), err)
})?;
let (stem, ext) = BlobObject::sanitise_name(&src.as_ref().to_string_lossy());
let (name, mut dst_file) = BlobObject::create_new_file(context.get_blobdir(), &stem, &ext)?;
std::io::copy(&mut src_file, &mut dst_file).map_err(|err| {
{
// Attempt to remove the failed file, swallow errors resulting from that.
let path = context.get_blobdir().join(&name);
fs::remove_file(path).ok();
}
BlobError::new_copy_failure(context.get_blobdir(), &name, src.as_ref(), err)
})?;
let blob = BlobObject {
blobdir: context.get_blobdir(),
name: format!("$BLOBDIR/{}", name),
};
context.call_cb(Event::NewBlobFile(blob.as_name().to_string()));
Ok(blob)
}
@@ -145,6 +166,9 @@ impl<'a> BlobObject<'a> {
.as_ref()
.strip_prefix(context.get_blobdir())
.map_err(|_| BlobError::new_wrong_blobdir(context.get_blobdir(), path.as_ref()))?;
if !BlobObject::is_acceptible_blob_name(&rel_path) {
return Err(BlobError::new_wrong_name(path.as_ref()));
}
let name = rel_path
.to_str()
.ok_or_else(|| BlobError::new_wrong_name(path.as_ref()))?;
@@ -171,8 +195,7 @@ impl<'a> BlobObject<'a> {
true => name.splitn(2, '/').last().unwrap().to_string(),
false => name,
};
let (stem, ext) = BlobObject::sanitise_name(name.clone());
if format!("{}{}", stem, ext) != name.as_ref() {
if !BlobObject::is_acceptible_blob_name(&name) {
return Err(BlobError::new_wrong_name(name));
}
Ok(BlobObject {
@@ -236,7 +259,8 @@ impl<'a> BlobObject<'a> {
/// ".txt")` while "bar" is returned as `("bar", "")`.
///
/// The extension part will always be lowercased.
fn sanitise_name(mut name: String) -> (String, String) {
fn sanitise_name(name: &str) -> (String, String) {
let mut name = name.to_string();
for part in name.rsplit('/') {
if part.len() > 0 {
name = part.to_string();
@@ -254,17 +278,40 @@ impl<'a> BlobObject<'a> {
windows: true,
replacement: "",
};
let clean = sanitize_filename::sanitize_with_options(name, opts);
let mut iter = clean.rsplitn(2, '.');
let mut ext = iter.next().unwrap_or_default().to_string();
let mut stem = iter.next().unwrap_or_default().to_string();
ext.truncate(32);
stem.truncate(32);
stem.truncate(64);
match stem.len() {
0 => (ext, "".to_string()),
_ => (stem, format!(".{}", ext).to_lowercase()),
}
}
/// Checks whether a name is a valid blob name.
///
/// This is slightly less strict than stanitise_name, presumably
/// someone already created a file with such a name so we just
/// ensure it's not actually a path in disguise is actually utf-8.
fn is_acceptible_blob_name(name: impl AsRef<OsStr>) -> bool {
let uname = match name.as_ref().to_str() {
Some(name) => name,
None => return false,
};
if uname.find('/').is_some() {
return false;
}
if uname.find('\\').is_some() {
return false;
}
if uname.find('\0').is_some() {
return false;
}
true
}
}
impl<'a> fmt::Display for BlobObject<'a> {
@@ -563,10 +610,10 @@ mod tests {
#[test]
fn test_create_long_names() {
let t = dummy_context();
let s = "12312312039182039182039812039810293810293810293810293801293801293123123";
let blob = BlobObject::create(&t.ctx, s, b"data").unwrap();
let s = "1".repeat(150);
let blob = BlobObject::create(&t.ctx, &s, b"data").unwrap();
let blobname = blob.as_name().split('/').last().unwrap();
assert!(s.len() > blobname.len());
assert!(blobname.len() < 128);
}
#[test]
@@ -603,4 +650,25 @@ mod tests {
let data = fs::read(blob.to_abs_path()).unwrap();
assert_eq!(data, b"boo");
}
#[test]
fn test_create_from_name_long() {
let t = dummy_context();
let src_ext = t.dir.path().join("autocrypt-setup-message-4137848473.html");
fs::write(&src_ext, b"boo").unwrap();
let blob = BlobObject::create_from_path(&t.ctx, &src_ext).unwrap();
assert_eq!(
blob.as_name(),
"$BLOBDIR/autocrypt-setup-message-4137848473.html"
);
}
#[test]
fn test_is_blob_name() {
assert!(BlobObject::is_acceptible_blob_name("foo"));
assert!(BlobObject::is_acceptible_blob_name("foo.txt"));
assert!(BlobObject::is_acceptible_blob_name("f".repeat(128)));
assert!(!BlobObject::is_acceptible_blob_name("foo/bar"));
assert!(!BlobObject::is_acceptible_blob_name("foo\\bar"));
assert!(!BlobObject::is_acceptible_blob_name("foo\x00bar"));
}
}

View File

@@ -97,6 +97,8 @@ impl Chat {
if chat.param.exists(Param::Selftalk) {
chat.name = context.stock_str(StockMessage::SelfMsg).into();
} else if chat.param.exists(Param::Devicetalk) {
chat.name = context.stock_str(StockMessage::DeviceMessages).into();
}
}
}
@@ -109,6 +111,14 @@ impl Chat {
self.param.exists(Param::Selftalk)
}
pub fn is_device_talk(&self) -> bool {
self.param.exists(Param::Devicetalk)
}
pub fn can_send(&self) -> bool {
self.id > DC_CHAT_ID_LAST_SPECIAL && !self.is_device_talk()
}
pub fn update_param(&mut self, context: &Context) -> Result<(), Error> {
sql::execute(
context,
@@ -258,7 +268,7 @@ impl Chat {
}
if (self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup)
&& !is_contact_in_chat(context, self.id, 1 as u32)
&& !is_contact_in_chat(context, self.id, DC_CONTACT_ID_SELF)
{
emit_event!(
context,
@@ -414,7 +424,7 @@ impl Chat {
&context.sql,
"INSERT INTO locations \
(timestamp,from_id,chat_id, latitude,longitude,independent)\
VALUES (?,?,?, ?,?,1);",
VALUES (?,?,?, ?,?,1);", // 1=DC_CONTACT_ID_SELF
params![
timestamp,
DC_CONTACT_ID_SELF,
@@ -446,7 +456,7 @@ impl Chat {
params![
new_rfc724_mid,
self.id as i32,
1i32,
DC_CONTACT_ID_SELF,
to_id as i32,
timestamp,
msg.type_0,
@@ -582,6 +592,12 @@ pub fn set_blocking(context: &Context, chat_id: u32, new_blocking: Blocked) -> b
.is_ok()
}
fn copy_device_icon_to_blobs(context: &Context) -> Result<String, Error> {
let icon = include_bytes!("../assets/icon-device.png");
let blob = BlobObject::create(context, "icon-device.png".to_string(), icon)?;
Ok(blob.as_name().to_string())
}
pub fn create_or_lookup_by_contact_id(
context: &Context,
contact_id: u32,
@@ -605,7 +621,14 @@ pub fn create_or_lookup_by_contact_id(
"INSERT INTO chats (type, name, param, blocked, grpid) VALUES({}, '{}', '{}', {}, '{}')",
100,
chat_name,
if contact_id == DC_CONTACT_ID_SELF as u32 { "K=1" } else { "" },
match contact_id {
DC_CONTACT_ID_SELF => "K=1".to_string(), // K = Param::Selftalk
DC_CONTACT_ID_DEVICE => {
let icon = copy_device_icon_to_blobs(context)?;
format!("D=1\ni={}", icon) // D = Param::Devicetalk, i = Param::ProfileImage
},
_ => "".to_string()
},
create_blocked as u8,
contact.get_addr(),
),
@@ -677,8 +700,7 @@ pub fn msgtype_has_file(msgtype: Viewtype) -> bool {
}
}
fn prepare_msg_common(context: &Context, chat_id: u32, msg: &mut Message) -> Result<MsgId, Error> {
msg.id = MsgId::new_unset();
fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> {
if msg.type_0 == Viewtype::Text {
// the caller should check if the message text is empty
} else if msgtype_has_file(msg.type_0) {
@@ -714,10 +736,16 @@ fn prepare_msg_common(context: &Context, chat_id: u32, msg: &mut Message) -> Res
} else {
bail!("Cannot send messages of type #{}.", msg.type_0);
}
Ok(())
}
fn prepare_msg_common(context: &Context, chat_id: u32, msg: &mut Message) -> Result<MsgId, Error> {
msg.id = MsgId::new_unset();
prepare_msg_blob(context, msg)?;
unarchive(context, chat_id)?;
let mut chat = Chat::load_from_db(context, chat_id)?;
ensure!(chat.can_send(), "cannot send to chat #{}", chat_id);
// The OutPreparing state is set by dc_prepare_msg() before it
// calls this function and the message is left in the OutPreparing
@@ -914,7 +942,7 @@ fn do_set_draft(context: &Context, chat_id: u32, msg: &mut Message) -> Result<()
VALUES (?,?,?, ?,?,?,?,?);",
params![
chat_id as i32,
1,
DC_CONTACT_ID_SELF,
time(),
msg.type_0,
MessageState::OutDraft,
@@ -993,8 +1021,8 @@ pub fn get_chat_msgs(
" ON m.chat_id=chats.id",
" LEFT JOIN contacts",
" ON m.from_id=contacts.id",
" WHERE m.from_id!=1",
" AND m.from_id!=2",
" WHERE m.from_id!=1", // 1=DC_CONTACT_ID_SELF
" AND m.from_id!=2", // 2=DC_CONTACT_ID_INFO
" AND m.hidden=0",
" AND chats.blocked=2",
" AND contacts.blocked=0",
@@ -1350,7 +1378,7 @@ pub fn create_group_chat(
let chat_id = sql::get_rowid(context, &context.sql, "chats", "grpid", grpid);
if chat_id != 0 {
if add_to_chat_contacts_table(context, chat_id, 1) {
if add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF) {
let mut draft_msg = Message::new(Viewtype::Text);
draft_msg.set_text(Some(draft_txt));
set_draft_raw(context, chat_id, &mut draft_msg);
@@ -1436,7 +1464,11 @@ pub(crate) fn add_contact_to_chat_ex(
if contact.get_addr() == &self_addr {
// ourself is added using DC_CONTACT_ID_SELF, do not add this address explicitly.
// if SELF is not in the group, members cannot be added at all.
bail!("invalid attempt to add self e-mail address to group");
warn!(
context,
"invalid attempt to add self e-mail address to group"
);
return Ok(false);
}
if is_contact_in_chat(context, chat_id, contact_id) {
@@ -1553,7 +1585,7 @@ pub fn remove_contact_from_chat(
/* this allows to delete pending references to deleted contacts. Of course, this should _not_ happen. */
if let Ok(chat) = Chat::load_from_db(context, chat_id) {
if real_group_exists(context, chat_id) {
if !is_contact_in_chat(context, chat_id, 1 as u32) {
if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF) {
emit_event!(
context,
Event::ErrorSelfNotInGroup(
@@ -1649,7 +1681,7 @@ pub fn set_chat_name(
if real_group_exists(context, chat_id) {
if chat.name == new_name.as_ref() {
success = true;
} else if !is_contact_in_chat(context, chat_id, 1) {
} else if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF) {
emit_event!(
context,
Event::ErrorSelfNotInGroup("Cannot set chat name; self not in group".into())
@@ -1894,15 +1926,46 @@ pub fn get_chat_id_by_grpid(context: &Context, grpid: impl AsRef<str>) -> (u32,
.unwrap_or((0, false, Blocked::Not))
}
pub fn add_device_msg(context: &Context, chat_id: u32, text: impl AsRef<str>) {
pub fn add_device_msg(context: &Context, msg: &mut Message) -> Result<MsgId, Error> {
let (chat_id, _blocked) =
create_or_lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE, Blocked::Not)?;
let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device");
prepare_msg_blob(context, msg)?;
unarchive(context, chat_id)?;
context.sql.execute(
"INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,param,rfc724_mid) \
VALUES (?,?,?, ?,?,?, ?,?,?);",
params![
chat_id,
DC_CONTACT_ID_DEVICE,
DC_CONTACT_ID_SELF,
dc_create_smeared_timestamp(context),
msg.type_0,
MessageState::InFresh,
msg.text.as_ref().map_or("", String::as_str),
msg.param.to_string(),
rfc724_mid,
],
)?;
let row_id = sql::get_rowid(context, &context.sql, "msgs", "rfc724_mid", &rfc724_mid);
let msg_id = MsgId::new(row_id);
context.call_cb(Event::IncomingMsg { chat_id, msg_id });
Ok(msg_id)
}
pub fn add_info_msg(context: &Context, chat_id: u32, text: impl AsRef<str>) {
let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device");
if context.sql.execute(
"INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,rfc724_mid) VALUES (?,?,?, ?,?,?, ?,?);",
params![
chat_id as i32,
2,
2,
DC_CONTACT_ID_INFO,
DC_CONTACT_ID_INFO,
dc_create_smeared_timestamp(context),
Viewtype::Text,
MessageState::InNoticed,
@@ -1962,4 +2025,13 @@ mod tests {
let draft_text = draft.get_text();
assert_eq!(msg_text, draft_text);
}
#[test]
fn test_add_contact_to_chat_ex_add_self() {
// Adding self to a contact should succeed, even though it's pointless.
let t = test_context(Some(Box::new(logging_cb)));
let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap();
let added = add_contact_to_chat_ex(&t.ctx, chat_id, DC_CONTACT_ID_SELF, false).unwrap();
assert_eq!(added, false);
}
}

View File

@@ -251,7 +251,7 @@ impl Chatlist {
///
/// To get the message object from the message ID, use dc_get_msg().
pub fn get_msg_id(&self, index: usize) -> Result<MsgId> {
ensure!(index >= self.ids.len(), "Chatlist index out of range");
ensure!(index < self.ids.len(), "Chatlist index out of range");
Ok(self.ids[index].1)
}
@@ -294,18 +294,14 @@ impl Chatlist {
let lastmsg_id = self.ids[index].1;
let mut lastcontact = None;
let lastmsg = if !lastmsg_id.is_special() {
if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
if lastmsg.from_id != 1
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
{
lastcontact = Contact::load_from_db(context, lastmsg.from_id).ok();
}
Some(lastmsg)
} else {
None
let lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
if lastmsg.from_id != DC_CONTACT_ID_SELF
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
{
lastcontact = Contact::load_from_db(context, lastmsg.from_id).ok();
}
Some(lastmsg)
} else {
None
};

View File

@@ -62,16 +62,16 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
let mut param_autoconfig: Option<LoginParam> = None;
context.inbox.read().unwrap().disconnect(context);
context.inbox.write().unwrap().disconnect(context);
context
.sentbox_thread
.read()
.write()
.unwrap()
.imap
.disconnect(context);
context
.mvbox_thread
.read()
.write()
.unwrap()
.imap
.disconnect(context);
@@ -357,7 +357,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
};
context
.inbox
.read()
.write()
.unwrap()
.configure_folders(context, flags);
true
@@ -398,7 +398,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
}
}
if imap_connected_here {
context.inbox.read().unwrap().disconnect(context);
context.inbox.write().unwrap().disconnect(context);
}
if smtp_connected_here {
context.smtp.clone().lock().unwrap().disconnect();
@@ -484,7 +484,7 @@ fn try_imap_one_param(context: &Context, param: &LoginParam) -> Option<bool> {
param.mail_user, param.mail_server, param.mail_port, param.server_flags
);
info!(context, "Trying: {}", inf);
if context.inbox.read().unwrap().connect(context, &param) {
if context.inbox.write().unwrap().connect(context, &param) {
info!(context, "success: {}", inf);
return Some(true);
}
@@ -531,27 +531,32 @@ fn try_smtp_one_param(context: &Context, param: &LoginParam) -> Option<bool> {
param.send_user, param.send_server, param.send_port, param.server_flags
);
info!(context, "Trying: {}", inf);
if context
match context
.smtp
.clone()
.lock()
.unwrap()
.connect(context, &param)
{
info!(context, "success: {}", inf);
return Some(true);
Ok(()) => {
info!(context, "success: {}", inf);
Some(true)
}
Err(err) => {
if context.shall_stop_ongoing() {
Some(false)
} else {
warn!(context, "could not connect: {}", err);
None
}
}
}
if context.shall_stop_ongoing() {
return Some(false);
}
info!(context, "could not connect: {}", inf);
None
}
/*******************************************************************************
* Connect to configured account
******************************************************************************/
pub fn dc_connect_to_configured_imap(context: &Context, imap: &Imap) -> libc::c_int {
pub fn dc_connect_to_configured_imap(context: &Context, imap: &mut Imap) -> libc::c_int {
let mut ret_connected = 0;
if imap.is_connected() {

View File

@@ -130,7 +130,8 @@ const DC_MAX_GET_INFO_LEN: usize = 100000;
pub const DC_CONTACT_ID_UNDEFINED: u32 = 0;
pub const DC_CONTACT_ID_SELF: u32 = 1;
pub const DC_CONTACT_ID_DEVICE: u32 = 2;
pub const DC_CONTACT_ID_INFO: u32 = 2;
pub const DC_CONTACT_ID_DEVICE: u32 = 5;
pub const DC_CONTACT_ID_LAST_SPECIAL: u32 = 9;
pub const DC_CREATE_MVBOX: usize = 1;

View File

@@ -153,7 +153,16 @@ impl Contact {
blocked: false,
origin: Origin::Unknown,
};
return Ok(contact);
} else if contact_id == DC_CONTACT_ID_DEVICE {
let contact = Contact {
id: contact_id,
name: context.stock_str(StockMessage::DeviceMessages).into(),
authname: "".into(),
addr: "device@localhost".into(),
blocked: false,
origin: Origin::Unknown,
};
return Ok(contact);
}
@@ -264,7 +273,7 @@ impl Contact {
.unwrap_or_default();
if addr_normalized == addr_self {
return 1;
return DC_CONTACT_ID_SELF;
}
context.sql.query_get_value(
@@ -301,7 +310,7 @@ impl Contact {
.unwrap_or_default();
if addr == addr_self {
return Ok((1, sth_modified));
return Ok((DC_CONTACT_ID_SELF, sth_modified));
}
if !may_be_valid_addr(&addr) {

View File

@@ -43,6 +43,7 @@ pub struct Context {
blobdir: PathBuf,
pub sql: Sql,
pub inbox: Arc<RwLock<Imap>>,
pub(crate) inbox_watch: Arc<(Mutex<bool>, Condvar)>,
pub perform_inbox_jobs_needed: Arc<RwLock<bool>>,
pub probe_imap_network: Arc<RwLock<bool>>,
pub sentbox_thread: Arc<RwLock<JobThread>>,
@@ -115,10 +116,15 @@ impl Context {
"Blobdir does not exist: {}",
blobdir.display()
);
let inbox_watch = Arc::new((Mutex::new(false), Condvar::new()));
let inbox = Arc::new(RwLock::new(Imap::new(inbox_watch.clone())));
let ctx = Context {
blobdir,
dbfile,
inbox: Arc::new(RwLock::new(Imap::new())),
inbox,
inbox_watch,
cb,
os_name: Some(os_name),
running_state: Arc::new(RwLock::new(Default::default())),
@@ -132,12 +138,10 @@ impl Context {
sentbox_thread: Arc::new(RwLock::new(JobThread::new(
"SENTBOX",
"configured_sentbox_folder",
Imap::new(),
))),
mvbox_thread: Arc::new(RwLock::new(JobThread::new(
"MVBOX",
"configured_mvbox_folder",
Imap::new(),
))),
probe_imap_network: Arc::new(RwLock::new(false)),
perform_inbox_jobs_needed: Arc::new(RwLock::new(false)),
@@ -454,19 +458,28 @@ impl Context {
}
}
}
pub fn interrupt_inbox_idle(&self) {
let &(ref lock, ref cvar) = &*self.inbox_watch.clone();
let mut watch = lock.lock().unwrap();
*watch = true;
cvar.notify_one();
}
}
impl Drop for Context {
fn drop(&mut self) {
info!(self, "disconnecting INBOX-watch",);
self.inbox.read().unwrap().disconnect(self);
self.inbox.write().unwrap().disconnect(self);
info!(self, "disconnecting sentbox-thread",);
self.sentbox_thread.read().unwrap().imap.disconnect(self);
self.sentbox_thread.write().unwrap().imap.disconnect(self);
info!(self, "disconnecting mvbox-thread",);
self.mvbox_thread.read().unwrap().imap.disconnect(self);
self.mvbox_thread.write().unwrap().imap.disconnect(self);
info!(self, "disconnecting SMTP");
self.smtp.clone().lock().unwrap().disconnect();
self.sql.close(self);
info!(self, "Context closed");
}
}

View File

@@ -117,18 +117,30 @@ impl<'a> MimeParser<'a> {
);
if r == MAILIMF_NO_ERROR as libc::c_int && !self.mimeroot.is_null() {
let (encrypted, signatures, gossipped_addr) =
e2ee::try_decrypt(self.context, self.mimeroot)?;
self.encrypted = encrypted;
self.signatures = signatures;
self.gossipped_addr = gossipped_addr;
match e2ee::try_decrypt(self.context, self.mimeroot) {
Ok((encrypted, signatures, gossipped_addr)) => {
self.encrypted = encrypted;
self.signatures = signatures;
self.gossipped_addr = gossipped_addr;
}
Err(err) => {
// continue with the current, still encrypted, mime tree.
// unencrypted parts will be replaced by an error message
// that is added as "the message" to the chat then.
//
// if we just return here, the header is missing
// and the caller cannot display the message
// and try to assign the message to a chat
warn!(self.context, "decryption failed: {}", err);
}
}
self.parse_mime_recursive(self.mimeroot);
if let Some(field) = self.lookup_field("Subject") {
if (*field).fld_type == MAILIMF_FIELD_SUBJECT as libc::c_int {
let subj = (*(*field).fld_data.fld_subject).sbj_value;
self.subject = as_opt_str(subj).map(dc_decode_header_words);
self.subject = to_opt_string_lossy(subj).map(|x| dc_decode_header_words(&x));
}
}
@@ -599,7 +611,7 @@ impl<'a> MimeParser<'a> {
let mut decoded_data = match wrapmime::mailmime_transfer_decode(mime) {
Ok(decoded_data) => decoded_data,
Err(_) => {
// Note that it's now always an error - might be no data
// Note that it's not always an error - might be no data
return false;
}
};
@@ -630,7 +642,7 @@ impl<'a> MimeParser<'a> {
self.context,
"Cannot convert {} bytes from \"{}\" to \"utf-8\".",
decoded_data.len(),
as_str(charset),
to_string_lossy(charset),
);
}
}
@@ -729,33 +741,21 @@ impl<'a> MimeParser<'a> {
if !(*mime).mm_content_type.is_null()
&& !(*(*mime).mm_content_type).ct_subtype.is_null()
{
desired_filename =
format!("file.{}", as_str((*(*mime).mm_content_type).ct_subtype));
desired_filename = format!(
"file.{}",
to_string_lossy((*(*mime).mm_content_type).ct_subtype)
);
} else {
return false;
}
}
if desired_filename.starts_with("location") && desired_filename.ends_with(".kml") {
if !decoded_data.is_empty() {
let d = std::string::String::from_utf8_lossy(&decoded_data);
self.location_kml = location::Kml::parse(self.context, &d).ok();
}
} else if desired_filename.starts_with("message")
&& desired_filename.ends_with(".kml")
{
if !decoded_data.is_empty() {
let d = std::string::String::from_utf8_lossy(&decoded_data);
self.message_kml = location::Kml::parse(self.context, &d).ok();
}
} else if !decoded_data.is_empty() {
self.do_add_single_file_part(
msg_type,
mime_type,
raw_mime.as_ref(),
&decoded_data,
&desired_filename,
);
}
self.do_add_single_file_part(
msg_type,
mime_type,
raw_mime.as_ref(),
&decoded_data,
&desired_filename,
);
}
_ => {}
}
@@ -769,20 +769,44 @@ impl<'a> MimeParser<'a> {
mime_type: libc::c_int,
raw_mime: Option<&String>,
decoded_data: &[u8],
desired_filename: &str,
filename: &str,
) {
/* write decoded data to new blob file */
let blob = match BlobObject::create(self.context, desired_filename, decoded_data) {
if decoded_data.is_empty() {
return;
}
// treat location/message kml file attachments specially
if filename.ends_with(".kml") {
// XXX what if somebody sends eg an "location-highlights.kml"
// attachment unrelated to location streaming?
if filename.starts_with("location") || filename.starts_with("message") {
let parsed = location::Kml::parse(self.context, decoded_data)
.map_err(|err| {
warn!(self.context, "failed to parse kml part: {}", err);
})
.ok();
if filename.starts_with("location") {
self.location_kml = parsed;
} else {
self.message_kml = parsed;
}
return;
}
}
/* we have a regular file attachment,
write decoded data to new blob object */
let blob = match BlobObject::create(self.context, filename, decoded_data) {
Ok(blob) => blob,
Err(err) => {
error!(
self.context,
"Could not add blob for mime part {}, error {}", desired_filename, err
"Could not add blob for mime part {}, error {}", filename, err
);
return;
}
};
/* create and register Mime part referencing the new Blob object */
let mut part = Part::default();
part.typ = msg_type;
part.mimetype = mime_type;
@@ -802,10 +826,15 @@ impl<'a> MimeParser<'a> {
}
fn do_add_single_part(&mut self, mut part: Part) {
if self.encrypted && self.signatures.len() > 0 {
part.param.set_int(Param::GuaranteeE2ee, 1);
} else if self.encrypted {
part.param.set_int(Param::ErroneousE2ee, 0x2);
if self.encrypted {
if self.signatures.len() > 0 {
part.param.set_int(Param::GuaranteeE2ee, 1);
} else {
// XXX if the message was encrypted but not signed
// it's not neccessarily an error we need to signal.
// we could just treat it as if it was not encrypted.
part.param.set_int(Param::ErroneousE2ee, 0x2);
}
}
self.parts.push(part);
}
@@ -850,7 +879,8 @@ impl<'a> MimeParser<'a> {
}) as *mut mailimf_mailbox;
if !mb.is_null() {
let from_addr_norm = addr_normalize(as_str((*mb).mb_addr_spec));
let from_addr = to_string_lossy((*mb).mb_addr_spec);
let from_addr_norm = addr_normalize(&from_addr);
let recipients = wrapmime::mailimf_get_recipients(self.header_root);
if recipients.len() == 1 && recipients.contains(from_addr_norm) {
sender_equals_recipient = true;
@@ -989,15 +1019,16 @@ unsafe fn mailmime_get_mime_type(mime: *mut Mailmime) -> (libc::c_int, Viewtype,
}
}
let raw_mime = reconcat_mime(Some("text"), as_opt_str((*c).ct_subtype));
let raw_mime = reconcat_mime(Some("text"), to_opt_string_lossy((*c).ct_subtype));
(DC_MIMETYPE_FILE, Viewtype::File, Some(raw_mime))
}
MAILMIME_DISCRETE_TYPE_IMAGE => {
let subtype = as_opt_str((*c).ct_subtype);
let msg_type = match subtype {
let subtype = to_opt_string_lossy((*c).ct_subtype);
let msg_type = match subtype.as_ref().map(|x| x.as_str()) {
Some("gif") => Viewtype::Gif,
Some("svg+xml") => {
let raw_mime = reconcat_mime(Some("image"), as_opt_str((*c).ct_subtype));
let raw_mime =
reconcat_mime(Some("image"), to_opt_string_lossy((*c).ct_subtype));
return (DC_MIMETYPE_FILE, Viewtype::File, Some(raw_mime));
}
_ => Viewtype::Image,
@@ -1007,11 +1038,11 @@ unsafe fn mailmime_get_mime_type(mime: *mut Mailmime) -> (libc::c_int, Viewtype,
(DC_MIMETYPE_IMAGE, msg_type, Some(raw_mime))
}
MAILMIME_DISCRETE_TYPE_AUDIO => {
let raw_mime = reconcat_mime(Some("audio"), as_opt_str((*c).ct_subtype));
let raw_mime = reconcat_mime(Some("audio"), to_opt_string_lossy((*c).ct_subtype));
(DC_MIMETYPE_AUDIO, Viewtype::Audio, Some(raw_mime))
}
MAILMIME_DISCRETE_TYPE_VIDEO => {
let raw_mime = reconcat_mime(Some("video"), as_opt_str((*c).ct_subtype));
let raw_mime = reconcat_mime(Some("video"), to_opt_string_lossy((*c).ct_subtype));
(DC_MIMETYPE_VIDEO, Viewtype::Video, Some(raw_mime))
}
_ => {
@@ -1022,13 +1053,15 @@ unsafe fn mailmime_get_mime_type(mime: *mut Mailmime) -> (libc::c_int, Viewtype,
b"autocrypt-setup\x00" as *const u8 as *const libc::c_char,
) == 0i32
{
let raw_mime = reconcat_mime(None, as_opt_str((*c).ct_subtype));
let raw_mime = reconcat_mime(None, to_opt_string_lossy((*c).ct_subtype));
return (DC_MIMETYPE_AC_SETUP_FILE, Viewtype::File, Some(raw_mime));
}
let raw_mime = reconcat_mime(
as_opt_str((*(*(*c).ct_type).tp_data.tp_discrete_type).dt_extension),
as_opt_str((*c).ct_subtype),
to_opt_string_lossy((*(*(*c).ct_type).tp_data.tp_discrete_type).dt_extension)
.as_ref()
.map(|x| x.as_str()),
to_opt_string_lossy((*c).ct_subtype),
);
(DC_MIMETYPE_FILE, Viewtype::File, Some(raw_mime))
@@ -1038,9 +1071,9 @@ unsafe fn mailmime_get_mime_type(mime: *mut Mailmime) -> (libc::c_int, Viewtype,
if (*(*(*c).ct_type).tp_data.tp_composite_type).ct_type
== MAILMIME_COMPOSITE_TYPE_MULTIPART as libc::c_int
{
let subtype = as_opt_str((*c).ct_subtype);
let subtype = to_opt_string_lossy((*c).ct_subtype);
let mime_type = match subtype {
let mime_type = match subtype.as_ref().map(|x| x.as_str()) {
Some("alternative") => DC_MIMETYPE_MP_ALTERNATIVE,
Some("related") => DC_MIMETYPE_MP_RELATED,
Some("encrypted") => {
@@ -1075,9 +1108,9 @@ unsafe fn mailmime_get_mime_type(mime: *mut Mailmime) -> (libc::c_int, Viewtype,
}
}
fn reconcat_mime(typ: Option<&str>, subtype: Option<&str>) -> String {
fn reconcat_mime(typ: Option<&str>, subtype: Option<String>) -> String {
let typ = typ.unwrap_or("application");
let subtype = subtype.unwrap_or("octet-stream");
let subtype = subtype.unwrap_or("octet-stream".to_string());
format!("{}/{}", typ, subtype)
}
@@ -1224,13 +1257,13 @@ mod tests {
#[ignore]
#[test]
fn test_dc_mailmime_parse_crash_fuzzy(data in "[!-~\t ]{2000,}") {
// this test doesn't exercise much of dc_mimeparser anymore
// because a missing From-field early aborts parsing
let context = dummy_context();
let mut mimeparser = MimeParser::new(&context.ctx);
unsafe {
assert!(mimeparser.parse(data.as_bytes()).is_err());
}
// parsing should always succeed
// but the returned header will normally be empty on random data
assert!(unsafe {mimeparser.parse(data.as_bytes()).is_ok()});
assert!(mimeparser.header.is_empty());
}
}

View File

@@ -65,12 +65,12 @@ pub unsafe fn dc_receive_imf(
let mut mime_parser = MimeParser::new(context);
if let Err(err) = mime_parser.parse(imf_raw) {
error!(context, "dc_receive_imf parse error: {}", err);
warn!(context, "dc_receive_imf parse error: {}", err);
};
if mime_parser.header.is_empty() {
// Error - even adding an empty record won't help as we do not know the message ID
info!(context, "No header.");
warn!(context, "No header.");
return;
}
@@ -227,7 +227,7 @@ pub unsafe fn dc_receive_imf(
&mut created_db_entries,
&mut create_event_to_send,
) {
info!(context, "{}", err);
warn!(context, "{}", err);
cleanup(
context,
@@ -257,7 +257,7 @@ pub unsafe fn dc_receive_imf(
);
}
if !mime_parser.message_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL {
if mime_parser.location_kml.is_some() || mime_parser.message_kml.is_some() {
save_locations(
context,
&mime_parser,
@@ -391,7 +391,7 @@ unsafe fn add_parts(
} else {
MessageState::InFresh
};
*to_id = 1;
*to_id = DC_CONTACT_ID_SELF;
// handshake messages must be processed _before_ chats are created
// (eg. contacs may be marked as verified)
if mime_parser.lookup_field("Secure-Join").is_some() {
@@ -551,8 +551,9 @@ unsafe fn add_parts(
if to_ids.is_empty() && 0 != 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, 1, Blocked::Not)
.unwrap_or_default();
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;
@@ -604,6 +605,7 @@ unsafe fn add_parts(
// into only one message; mails sent by other clients may result in several messages
// (eg. one per attachment))
let icnt = mime_parser.parts.len();
let mut txt_raw = None;
context.sql.prepare(
@@ -644,17 +646,6 @@ unsafe fn add_parts(
.set_int(Param::Cmd, mime_parser.is_system_message as i32);
}
/*
info!(
context,
"received mime message {:?}",
String::from_utf8_lossy(std::slice::from_raw_parts(
imf_raw_not_terminated as *const u8,
imf_raw_bytes,
))
);
*/
stmt.execute(params![
rfc724_mid,
server_folder.as_ref(),
@@ -808,9 +799,9 @@ unsafe fn handle_reports(
&& !of_org_msgid.is_null()
&& !(*of_org_msgid).fld_value.is_null()
{
if let Ok(rfc724_mid) = wrapmime::parse_message_id(as_str(
(*of_org_msgid).fld_value,
)) {
if let Ok(rfc724_mid) = wrapmime::parse_message_id(
&to_string_lossy((*of_org_msgid).fld_value),
) {
if let Some((chat_id, msg_id)) = message::mdn_from_ext(
context,
from_id,
@@ -850,13 +841,16 @@ fn save_locations(
insert_msg_id: MsgId,
hidden: i32,
) {
if chat_id <= DC_CHAT_ID_LAST_SPECIAL {
return ();
}
let mut location_id_written = false;
let mut send_event = false;
if !mime_parser.message_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL as libc::c_uint {
if mime_parser.message_kml.is_some() {
let locations = &mime_parser.message_kml.as_ref().unwrap().locations;
let newest_location_id =
location::save(context, chat_id, from_id, locations, 1).unwrap_or_default();
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;
@@ -865,15 +859,14 @@ fn save_locations(
}
}
if !mime_parser.location_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL as libc::c_uint {
if mime_parser.location_kml.is_some() {
if let Some(ref addr) = mime_parser.location_kml.as_ref().unwrap().addr {
if let Ok(contact) = Contact::get_by_id(context, from_id) {
if !contact.get_addr().is_empty()
&& contact.get_addr().to_lowercase() == addr.to_lowercase()
{
if contact.get_addr().to_lowercase() == addr.to_lowercase() {
let locations = &mime_parser.location_kml.as_ref().unwrap().locations;
let newest_location_id =
location::save(context, chat_id, from_id, locations, 0).unwrap_or_default();
location::save(context, chat_id, from_id, locations, false)
.unwrap_or_default();
if newest_location_id != 0 && hidden == 0 && !location_id_written {
if let Err(err) = location::set_msg_location_id(
context,
@@ -993,7 +986,7 @@ unsafe fn create_or_lookup_group(
let fld_message_id = (*field).fld_data.fld_message_id;
if !fld_message_id.is_null() {
if let Some(extracted_grpid) =
dc_extract_grpid_from_rfc724_mid(as_str((*fld_message_id).mid_value))
dc_extract_grpid_from_rfc724_mid(&to_string_lossy((*fld_message_id).mid_value))
{
grpid = extracted_grpid.to_string();
} else {
@@ -1359,8 +1352,8 @@ unsafe fn create_or_lookup_adhoc_group(
if !member_ids.contains(&from_id) {
member_ids.push(from_id);
}
if !member_ids.contains(&1) {
member_ids.push(1);
if !member_ids.contains(&DC_CONTACT_ID_SELF) {
member_ids.push(DC_CONTACT_ID_SELF);
}
if member_ids.len() < 3 {
// too few contacts given
@@ -1480,7 +1473,7 @@ fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String {
.sql
.query_map(
format!(
"SELECT addr FROM contacts WHERE id IN({}) AND id!=1",
"SELECT addr FROM contacts WHERE id IN({}) AND id!=1", // 1=DC_CONTACT_ID_SELF
member_ids_str
),
params![],
@@ -1534,7 +1527,7 @@ fn search_chat_ids_by_contact_ids(
WHERE cc.chat_id IN(SELECT chat_id FROM chats_contacts WHERE contact_id IN({})) \
AND c.type=120 \
AND cc.contact_id!=1 \
ORDER BY cc.chat_id, cc.contact_id;",
ORDER BY cc.chat_id, cc.contact_id;", // 1=DC_CONTACT_ID_SELF
contact_ids_str
),
params![],
@@ -1731,7 +1724,7 @@ fn is_known_rfc724_mid(context: &Context, rfc724_mid: *const libc::c_char) -> li
LEFT JOIN chats c ON m.chat_id=c.id \
WHERE m.rfc724_mid=? \
AND m.chat_id>9 AND c.blocked=0;",
params![as_str(rfc724_mid)],
params![to_string_lossy(rfc724_mid)],
)
.unwrap_or_default() as libc::c_int
}
@@ -1783,11 +1776,7 @@ unsafe fn is_msgrmsg_rfc724_mid_in_list(context: &Context, mid_list: *const clis
while !cur.is_null() {
if 0 != is_msgrmsg_rfc724_mid(
context,
if !cur.is_null() {
as_str((*cur).data as *const libc::c_char)
} else {
""
},
&to_string_lossy((*cur).data as *const libc::c_char),
) {
return 1;
}
@@ -1923,7 +1912,7 @@ unsafe fn add_or_lookup_contact_by_addr(
.get_config(Config::ConfiguredAddr)
.unwrap_or_default();
if addr_cmp(self_addr, as_str(addr_spec)) {
if addr_cmp(self_addr, to_string_lossy(addr_spec)) {
*check_self = 1;
}
@@ -1933,13 +1922,18 @@ unsafe fn add_or_lookup_contact_by_addr(
/* add addr_spec if missing, update otherwise */
let mut display_name_dec = "".to_string();
if !display_name_enc.is_null() {
let tmp = dc_decode_header_words(as_str(display_name_enc));
let tmp = dc_decode_header_words(&to_string_lossy(display_name_enc));
display_name_dec = normalize_name(&tmp);
}
/*can be NULL*/
let row_id = Contact::add_or_lookup(context, display_name_dec, as_str(addr_spec), origin)
.map(|(id, _)| id)
.unwrap_or_default();
let row_id = Contact::add_or_lookup(
context,
display_name_dec,
to_string_lossy(addr_spec),
origin,
)
.map(|(id, _)| id)
.unwrap_or_default();
if 0 != row_id && !ids.contains(&row_id) {
ids.push(row_id);
};

View File

@@ -82,7 +82,7 @@ pub(crate) fn dc_str_from_clist(list: *const clist, delimiter: &str) -> String {
if !res.is_empty() {
res += delimiter;
}
res += as_str(rfc724_mid as *const libc::c_char);
res += &to_string_lossy(rfc724_mid as *const libc::c_char);
}
}
res
@@ -249,12 +249,10 @@ pub(crate) fn dc_create_incoming_rfc724_mid(
contact_id_from: u32,
contact_ids_to: &[u32],
) -> Option<String> {
if contact_ids_to.is_empty() {
return None;
}
/* find out the largest receiver ID (we could also take the smallest, but it should be unique) */
let largest_id_to = contact_ids_to.iter().max().copied().unwrap_or_default();
/* create a deterministic rfc724_mid from input such that
repeatedly calling it with the same input results in the same Message-id */
let largest_id_to = contact_ids_to.iter().max().copied().unwrap_or_default();
let result = format!(
"{}-{}-{}@stub",
message_timestamp, contact_id_from, largest_id_to
@@ -303,9 +301,9 @@ pub(crate) fn dc_extract_grpid_from_rfc724_mid_list(list: *const clist) -> *mut
if !list.is_null() {
unsafe {
for cur in (*list).into_iter() {
let mid = as_str(cur as *const libc::c_char);
let mid = to_string_lossy(cur as *const libc::c_char);
if let Some(grpid) = dc_extract_grpid_from_rfc724_mid(mid) {
if let Some(grpid) = dc_extract_grpid_from_rfc724_mid(&mid) {
return grpid.strdup();
}
}
@@ -420,8 +418,8 @@ pub(crate) fn dc_delete_file(context: &Context, path: impl AsRef<std::path::Path
context.call_cb(Event::DeletedBlobFile(dpath));
true
}
Err(_err) => {
warn!(context, "Cannot delete \"{}\".", dpath);
Err(err) => {
warn!(context, "Cannot delete \"{}\": {}", dpath, err);
false
}
}
@@ -429,20 +427,55 @@ pub(crate) fn dc_delete_file(context: &Context, path: impl AsRef<std::path::Path
pub(crate) fn dc_copy_file(
context: &Context,
src: impl AsRef<std::path::Path>,
dest: impl AsRef<std::path::Path>,
src_path: impl AsRef<std::path::Path>,
dest_path: impl AsRef<std::path::Path>,
) -> bool {
let src_abs = dc_get_abs_path(context, &src);
let dest_abs = dc_get_abs_path(context, &dest);
match fs::copy(&src_abs, &dest_abs) {
let src_abs = dc_get_abs_path(context, &src_path);
let mut src_file = match fs::File::open(&src_abs) {
Ok(file) => file,
Err(err) => {
warn!(
context,
"failed to open for read '{}': {}",
src_abs.display(),
err
);
return false;
}
};
let dest_abs = dc_get_abs_path(context, &dest_path);
let mut dest_file = match fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(&dest_abs)
{
Ok(file) => file,
Err(err) => {
warn!(
context,
"failed to open for write '{}': {}",
dest_abs.display(),
err
);
return false;
}
};
match std::io::copy(&mut src_file, &mut dest_file) {
Ok(_) => true,
Err(_) => {
Err(err) => {
error!(
context,
"Cannot copy \"{}\" to \"{}\".",
src.as_ref().display(),
dest.as_ref().display(),
"Cannot copy \"{}\" to \"{}\": {}",
src_abs.display(),
dest_abs.display(),
err
);
{
// Attempt to remove the failed file, swallow errors resulting from that.
fs::remove_file(dest_abs).ok();
}
false
}
}
@@ -453,11 +486,12 @@ pub(crate) fn dc_create_folder(context: &Context, path: impl AsRef<std::path::Pa
if !path_abs.exists() {
match fs::create_dir_all(path_abs) {
Ok(_) => true,
Err(_err) => {
Err(err) => {
warn!(
context,
"Cannot create directory \"{}\".",
"Cannot create directory \"{}\": {}",
path.as_ref().display(),
err
);
false
}
@@ -470,12 +504,13 @@ pub(crate) fn dc_create_folder(context: &Context, path: impl AsRef<std::path::Pa
/// Write a the given content to provied file path.
pub(crate) fn dc_write_file(context: &Context, path: impl AsRef<Path>, buf: &[u8]) -> bool {
let path_abs = dc_get_abs_path(context, &path);
if let Err(_err) = fs::write(&path_abs, buf) {
if let Err(err) = fs::write(&path_abs, buf) {
warn!(
context,
"Cannot write {} bytes to \"{}\".",
"Cannot write {} bytes to \"{}\": {}",
buf.len(),
path.as_ref().display(),
err
);
false
} else {
@@ -494,8 +529,9 @@ pub fn dc_read_file<P: AsRef<std::path::Path>>(
Err(err) => {
warn!(
context,
"Cannot read \"{}\" or file is empty.",
path.as_ref().display()
"Cannot read \"{}\" or file is empty: {}",
path.as_ref().display(),
err
);
Err(err.into())
}
@@ -513,8 +549,9 @@ pub fn dc_open_file<P: AsRef<std::path::Path>>(
Err(err) => {
warn!(
context,
"Cannot read \"{}\" or file is empty.",
path.as_ref().display()
"Cannot read \"{}\" or file is empty: {}",
path.as_ref().display(),
err
);
Err(err.into())
}
@@ -704,27 +741,6 @@ pub fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
Some(to_string_lossy(s))
}
pub fn as_str<'a>(s: *const libc::c_char) -> &'a str {
as_str_safe(s).unwrap_or_else(|err| panic!("{}", err))
}
/// Converts a C string to either a Rust `&str` or `None` if it is a null pointer.
pub fn as_opt_str<'a>(s: *const libc::c_char) -> Option<&'a str> {
if s.is_null() {
return None;
}
Some(as_str(s))
}
fn as_str_safe<'a>(s: *const libc::c_char) -> Result<&'a str, Error> {
assert!(!s.is_null(), "cannot be used on null pointers");
let cstr = unsafe { CStr::from_ptr(s) };
cstr.to_str()
.map_err(|err| format_err!("Non utf8 string: '{:?}' ({:?})", cstr.to_bytes(), err))
}
/// Convert a C `*char` pointer to a [std::path::Path] slice.
///
/// This converts a `*libc::c_char` pointer to a [Path] slice. This
@@ -761,7 +777,11 @@ pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
#[allow(dead_code)]
fn as_path_unicode<'a>(s: *const libc::c_char) -> &'a std::path::Path {
assert!(!s.is_null(), "cannot be used on null pointers");
std::path::Path::new(as_str(s))
let cstr = unsafe { CStr::from_ptr(s) };
let str = cstr.to_str().unwrap_or_else(|err| panic!("{}", err));
std::path::Path::new(str)
}
pub(crate) fn time() -> i64 {
@@ -1244,6 +1264,8 @@ mod tests {
fn test_dc_create_incoming_rfc724_mid() {
let res = dc_create_incoming_rfc724_mid(123, 45, &vec![6, 7]);
assert_eq!(res, Some("123-45-7@stub".into()));
let res = dc_create_incoming_rfc724_mid(123, 45, &vec![]);
assert_eq!(res, Some("123-45-0@stub".into()));
}
#[test]
@@ -1295,6 +1317,9 @@ mod tests {
assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
// attempting to copy a second time should fail
assert!(!dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7);
let buf = dc_read_file(context, "$BLOBDIR/dada").unwrap();

View File

@@ -449,7 +449,7 @@ fn update_gossip_peerstates(
let optional_field = unsafe { *optional_field };
if !optional_field.fld_name.is_null()
&& as_str(optional_field.fld_name) == "Autocrypt-Gossip"
&& to_string_lossy(optional_field.fld_name) == "Autocrypt-Gossip"
{
let value = to_string_lossy(optional_field.fld_value);
let gossip_header = Aheader::from_str(&value);
@@ -655,7 +655,7 @@ fn contains_report(mime: *mut Mailmime) -> bool {
if tp_type == MAILMIME_TYPE_COMPOSITE_TYPE as libc::c_int
&& ct_type == MAILMIME_COMPOSITE_TYPE_MULTIPART as libc::c_int
&& as_str(unsafe { (*mime.mm_content_type).ct_subtype }) == "report"
&& to_string_lossy(unsafe { (*mime.mm_content_type).ct_subtype }) == "report"
{
return true;
}

View File

@@ -1,8 +1,5 @@
use std::net;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Condvar, Mutex, RwLock,
};
use std::sync::{Arc, Condvar, Mutex};
use std::time::{Duration, SystemTime};
use crate::configure::dc_connect_to_configured_imap;
@@ -11,7 +8,7 @@ use crate::context::Context;
use crate::dc_receive_imf::dc_receive_imf;
use crate::error::Error;
use crate::events::Event;
use crate::job::{connect_to_inbox, job_add, Action};
use crate::job::{connect_to_inbox, job_add_no_interrupt, Action};
use crate::login_param::{dc_build_tls, CertificateChecks, LoginParam};
use crate::message::{self, update_msg_move_state, update_server_uid};
use crate::oauth2::dc_get_oauth2_access_token;
@@ -36,14 +33,13 @@ const SELECT_ALL: &str = "1:*";
#[derive(Debug)]
pub struct Imap {
config: Arc<RwLock<ImapConfig>>,
config: ImapConfig,
watch: Arc<(Mutex<bool>, Condvar)>,
session: Arc<Mutex<Option<Session>>>,
stream: Arc<RwLock<Option<net::TcpStream>>>,
connected: Arc<Mutex<bool>>,
should_reconnect: AtomicBool,
stream: Option<net::TcpStream>,
connected: bool,
should_reconnect: bool,
}
#[derive(Debug)]
@@ -361,27 +357,27 @@ impl Default for ImapConfig {
}
impl Imap {
pub fn new() -> Self {
pub fn new(watch: Arc<(Mutex<bool>, Condvar)>) -> Self {
Imap {
session: Arc::new(Mutex::new(None)),
stream: Arc::new(RwLock::new(None)),
config: Arc::new(RwLock::new(ImapConfig::default())),
watch: Arc::new((Mutex::new(false), Condvar::new())),
connected: Arc::new(Mutex::new(false)),
should_reconnect: AtomicBool::new(false),
stream: None,
config: ImapConfig::default(),
watch,
connected: false,
should_reconnect: false,
}
}
pub fn is_connected(&self) -> bool {
*self.connected.lock().unwrap()
self.connected
}
pub fn should_reconnect(&self) -> bool {
self.should_reconnect.load(Ordering::Relaxed)
self.should_reconnect
}
fn setup_handle_if_needed(&self, context: &Context) -> bool {
if self.config.read().unwrap().imap_server.is_empty() {
fn setup_handle_if_needed(&mut self, context: &Context) -> bool {
if self.config.imap_server.is_empty() {
return false;
}
@@ -389,16 +385,16 @@ impl Imap {
self.unsetup_handle(context);
}
if self.is_connected() && self.stream.read().unwrap().is_some() {
self.should_reconnect.store(false, Ordering::Relaxed);
if self.is_connected() && self.stream.is_some() {
self.should_reconnect = false;
return true;
}
let server_flags = self.config.read().unwrap().server_flags as i32;
let server_flags = self.config.server_flags as i32;
let connection_res: imap::error::Result<Client> =
if (server_flags & (DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_PLAIN)) != 0 {
let config = self.config.read().unwrap();
let config = &self.config;
let imap_server: &str = config.imap_server.as_ref();
let imap_port = config.imap_port;
@@ -410,7 +406,7 @@ impl Imap {
}
})
} else {
let config = self.config.read().unwrap();
let config = &self.config;
let imap_server: &str = config.imap_server.as_ref();
let imap_port = config.imap_port;
@@ -423,7 +419,7 @@ impl Imap {
let login_res = match connection_res {
Ok(client) => {
let config = self.config.read().unwrap();
let config = &self.config;
let imap_user: &str = config.imap_user.as_ref();
let imap_pw: &str = config.imap_pw.as_ref();
@@ -444,7 +440,7 @@ impl Imap {
}
}
Err(err) => {
let config = self.config.read().unwrap();
let config = &self.config;
let imap_server: &str = config.imap_server.as_ref();
let imap_port = config.imap_port;
let message = context.stock_string_repl_str2(
@@ -459,17 +455,17 @@ impl Imap {
}
};
self.should_reconnect.store(false, Ordering::Relaxed);
self.should_reconnect = false;
match login_res {
Ok((session, stream)) => {
*self.session.lock().unwrap() = Some(session);
*self.stream.write().unwrap() = Some(stream);
self.stream = Some(stream);
true
}
Err((err, _)) => {
let imap_user = self.config.read().unwrap().imap_user.to_owned();
let message = context.stock_string_repl_str(StockMessage::CannotLogin, &imap_user);
let imap_user = &self.config.imap_user;
let message = context.stock_string_repl_str(StockMessage::CannotLogin, imap_user);
emit_event!(
context,
@@ -482,10 +478,10 @@ impl Imap {
}
}
fn unsetup_handle(&self, context: &Context) {
fn unsetup_handle(&mut self, context: &Context) {
info!(context, "IMAP unsetup_handle step 1 (closing down stream).");
let stream = self.stream.write().unwrap().take();
if let Some(stream) = stream {
if let Some(stream) = self.stream.take() {
if let Err(err) = stream.shutdown(net::Shutdown::Both) {
warn!(context, "failed to shutdown connection: {:?}", err);
}
@@ -502,13 +498,13 @@ impl Imap {
}
info!(context, "IMAP unsetup_handle step 3 (clearing config).");
self.config.write().unwrap().selected_folder = None;
self.config.write().unwrap().selected_mailbox = None;
self.config.selected_folder = None;
self.config.selected_mailbox = None;
info!(context, "IMAP unsetup_handle step 4 (disconnected).",);
}
fn free_connect_params(&self) {
let mut cfg = self.config.write().unwrap();
fn free_connect_params(&mut self) {
let mut cfg = &mut self.config;
cfg.addr = "".into();
cfg.imap_server = "".into();
@@ -522,7 +518,7 @@ impl Imap {
cfg.watch_folder = None;
}
pub fn connect(&self, context: &Context, lp: &LoginParam) -> bool {
pub fn connect(&mut self, context: &Context, lp: &LoginParam) -> bool {
if lp.mail_server.is_empty() || lp.mail_user.is_empty() || lp.mail_pw.is_empty() {
return false;
}
@@ -539,7 +535,7 @@ impl Imap {
let imap_pw = &lp.mail_pw;
let server_flags = lp.server_flags as usize;
let mut config = self.config.write().unwrap();
let mut config = &mut self.config;
config.addr = addr.to_string();
config.imap_server = imap_server.to_string();
config.imap_port = imap_port;
@@ -587,41 +583,43 @@ impl Imap {
if teardown {
self.unsetup_handle(context);
self.free_connect_params();
self.connected = false;
false
} else {
self.config.write().unwrap().can_idle = can_idle;
self.config.write().unwrap().has_xlist = has_xlist;
*self.connected.lock().unwrap() = true;
self.config.can_idle = can_idle;
self.config.has_xlist = has_xlist;
self.connected = true;
true
}
}
pub fn disconnect(&self, context: &Context) {
if self.is_connected() {
self.unsetup_handle(context);
self.free_connect_params();
*self.connected.lock().unwrap() = false;
}
pub fn disconnect(&mut self, context: &Context) {
// if self.is_connected() {
info!(context, "disconnecting imap connection");
self.unsetup_handle(context);
self.free_connect_params();
// }
self.connected = false;
}
pub fn set_watch_folder(&self, watch_folder: String) {
self.config.write().unwrap().watch_folder = Some(watch_folder);
pub fn set_watch_folder(&mut self, watch_folder: String) {
self.config.watch_folder = Some(watch_folder);
}
pub fn fetch(&self, context: &Context) -> bool {
pub fn fetch(&mut self, context: &Context) -> bool {
if !self.is_connected() || !context.sql.is_open() {
return false;
}
self.setup_handle_if_needed(context);
let watch_folder = self.config.read().unwrap().watch_folder.to_owned();
let watch_folder = self.config.watch_folder.clone();
if let Some(ref watch_folder) = watch_folder {
// as during the fetch commands, new messages may arrive, we fetch until we do not
// get any more. if IDLE is called directly after, there is only a small chance that
// messages are missed and delayed until the next IDLE call
loop {
info!(context, "imap: fetching single folder");
if self.fetch_from_single_folder(context, watch_folder) == 0 {
break;
}
@@ -632,9 +630,10 @@ impl Imap {
}
}
fn select_folder<S: AsRef<str>>(&self, context: &Context, folder: Option<S>) -> usize {
if self.session.lock().unwrap().is_none() {
let mut cfg = self.config.write().unwrap();
fn select_folder<S: AsRef<str>>(&mut self, context: &Context, folder: Option<S>) -> usize {
info!(context, "select folder, waiting for session lock");
if !self.is_connected() {
let mut cfg = &mut self.config;
cfg.selected_folder = None;
cfg.selected_folder_needs_expunge = false;
return 0;
@@ -643,7 +642,7 @@ impl Imap {
// 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().unwrap().selected_folder {
if let Some(ref selected_folder) = self.config.selected_folder {
if folder.as_ref() == selected_folder {
return 1;
}
@@ -651,9 +650,13 @@ impl Imap {
}
// deselect existing folder, if needed (it's also done implicitly by SELECT, however, without EXPUNGE then)
let needs_expunge = { self.config.read().unwrap().selected_folder_needs_expunge };
let needs_expunge: bool = self.config.selected_folder_needs_expunge;
if needs_expunge {
if let Some(ref folder) = self.config.read().unwrap().selected_folder {
if !self.is_connected() {
return 0;
}
if let Some(ref folder) = self.config.selected_folder {
info!(context, "Expunge messages in \"{}\".", folder);
// A CLOSE-SELECT is considerably faster than an EXPUNGE-SELECT, see
@@ -672,15 +675,19 @@ impl Imap {
return 0;
}
}
self.config.write().unwrap().selected_folder_needs_expunge = false;
self.config.selected_folder_needs_expunge = false;
}
// select new folder
if let Some(ref folder) = folder {
if !self.is_connected() {
return 0;
}
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.select(folder) {
Ok(mailbox) => {
let mut config = self.config.write().unwrap();
let mut config = &mut self.config;
config.selected_folder = Some(folder.as_ref().to_string());
config.selected_mailbox = Some(mailbox);
}
@@ -692,13 +699,13 @@ impl Imap {
err
);
self.config.write().unwrap().selected_folder = None;
self.should_reconnect.store(true, Ordering::Relaxed);
self.config.selected_folder = None;
self.should_reconnect = true;
return 0;
}
}
} else {
unreachable!();
return 0;
}
}
@@ -727,7 +734,7 @@ impl Imap {
}
}
fn fetch_from_single_folder<S: AsRef<str>>(&self, context: &Context, folder: S) -> usize {
fn fetch_from_single_folder<S: AsRef<str>>(&mut self, context: &Context, folder: S) -> usize {
if !self.is_connected() {
info!(
context,
@@ -738,7 +745,10 @@ impl Imap {
return 0;
}
if self.select_folder(context, Some(&folder)) == 0 {
info!(context, "selecting folder");
let r = self.select_folder(context, Some(&folder));
info!(context, "selecting folder done {}", r);
if r == 0 {
info!(
context,
"Cannot select folder \"{}\" for fetching.",
@@ -751,8 +761,11 @@ impl Imap {
// compare last seen UIDVALIDITY against the current one
let (mut uid_validity, mut last_seen_uid) = self.get_config_last_seen_uid(context, &folder);
let config = self.config.read().unwrap();
let mailbox = config.selected_mailbox.as_ref().expect("just selected");
let mailbox = self
.config
.selected_mailbox
.as_ref()
.expect("just selected");
if mailbox.uid_validity.is_none() {
error!(
@@ -784,12 +797,17 @@ impl Imap {
}
let list = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
if !self.is_connected() {
return 0;
}
// `FETCH <message sequence number> (UID)`
let set = format!("{}", mailbox.exists);
info!(context, "session fetch {}", &set);
match session.fetch(set, PREFETCH_FLAGS) {
Ok(list) => list,
Err(_err) => {
self.should_reconnect.store(true, Ordering::Relaxed);
self.should_reconnect = true;
info!(
context,
"No result returned for folder \"{}\".",
@@ -826,6 +844,9 @@ impl Imap {
let mut new_last_seen_uid = 0;
let list = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
if !self.is_connected() {
return 0;
}
// fetch messages with larger UID than the last one seen
// (`UID FETCH lastseenuid+1:*)`, see RFC 4549
let set = format!("{}:*", last_seen_uid + 1);
@@ -915,7 +936,7 @@ impl Imap {
}
fn fetch_single_msg<S: AsRef<str>>(
&self,
&mut self,
context: &Context,
folder: S,
server_uid: u32,
@@ -933,7 +954,7 @@ impl Imap {
match session.uid_fetch(set, BODY_FLAGS) {
Ok(msgs) => msgs,
Err(err) => {
self.should_reconnect.store(true, Ordering::Relaxed);
self.should_reconnect = true;
warn!(
context,
"Error on fetching message #{} from folder \"{}\"; retry={}; error={}.",
@@ -982,15 +1003,16 @@ impl Imap {
1
}
pub fn idle(&self, context: &Context) {
if !self.config.read().unwrap().can_idle {
pub fn idle(&mut self, context: &Context) {
info!(context, "IDLE START");
if !self.config.can_idle {
return self.fake_idle(context);
}
self.setup_handle_if_needed(context);
let watch_folder = self.config.read().unwrap().watch_folder.clone();
if self.select_folder(context, watch_folder.as_ref()) == 0 {
let watch_folder = self.config.watch_folder.clone();
if self.select_folder(context, watch_folder) == 0 {
warn!(context, "IMAP-IDLE not setup.",);
return self.fake_idle(context);
@@ -1047,7 +1069,7 @@ impl Imap {
let &(ref lock, ref cvar) = &*self.watch.clone();
let mut watch = lock.lock().unwrap();
let handle_res = |res| match res {
let mut handle_res = |res| match res {
Ok(()) => {
info!(context, "IMAP-IDLE has data.");
}
@@ -1058,7 +1080,7 @@ impl Imap {
info!(context, "IMAP-IDLE wait cancelled, we will reconnect soon.");
self.unsetup_handle(context);
info!(context, "IMAP-IDLE has SHUTDOWN");
self.should_reconnect.store(true, Ordering::Relaxed);
self.should_reconnect = true;
}
_ => {
warn!(context, "IMAP-IDLE returns unknown value: {}", err);
@@ -1074,22 +1096,25 @@ impl Imap {
let res = cvar.wait(watch).unwrap();
watch = res;
if *watch {
if let Ok(res) = worker.as_ref().unwrap().try_recv() {
let msg = worker.as_ref().unwrap().try_recv();
drop(worker.take());
if let Ok(res) = msg {
handle_res(res);
} else {
info!(context, "IMAP-IDLE interrupted");
}
drop(worker.take());
break;
}
}
}
*watch = false;
info!(context, "IDLE STOP");
}
fn fake_idle(&self, context: &Context) {
fn fake_idle(&mut self, context: &Context) {
// Idle using timeouts. This is also needed if we're not yet configured -
// in this case, we're waiting for a configure job
let fake_idle_start_time = SystemTime::now();
@@ -1134,7 +1159,7 @@ impl Imap {
// try to connect with proper login params
// (setup_handle_if_needed might not know about them if we
// never successfully connected)
if dc_connect_to_configured_imap(context, &self) != 0 {
if dc_connect_to_configured_imap(context, self) != 0 {
return;
}
// we cannot connect, wait long next time (currently 60 secs, see above)
@@ -1146,8 +1171,7 @@ impl Imap {
// will have already fetched the messages so perform_*_fetch
// will not find any new.
let watch_folder = self.config.read().unwrap().watch_folder.clone();
if let Some(watch_folder) = watch_folder {
if let Some(ref watch_folder) = self.config.watch_folder.clone() {
if 0 != self.fetch_from_single_folder(context, watch_folder) {
do_fake_idle = false;
}
@@ -1155,17 +1179,17 @@ impl Imap {
}
}
pub fn interrupt_idle(&self) {
// interrupt idle
let &(ref lock, ref cvar) = &*self.watch.clone();
let mut watch = lock.lock().unwrap();
// pub fn interrupt_idle(&self) {
// // interrupt idle
// let &(ref lock, ref cvar) = &*self.watch.clone();
// let mut watch = lock.lock().unwrap();
*watch = true;
cvar.notify_one();
}
// *watch = true;
// cvar.notify_one();
// }
pub fn mv(
&self,
&mut self,
context: &Context,
folder: &str,
uid: u32,
@@ -1223,7 +1247,7 @@ impl Imap {
warn!(context, "Cannot mark {} as \"Deleted\" after copy.", uid);
ImapResult::Failed
} else {
self.config.write().unwrap().selected_folder_needs_expunge = true;
self.config.selected_folder_needs_expunge = true;
ImapResult::Success
}
}
@@ -1272,7 +1296,7 @@ impl Imap {
}
pub fn prepare_imap_operation_on_msg(
&self,
&mut self,
context: &Context,
folder: &str,
uid: u32,
@@ -1280,7 +1304,7 @@ impl Imap {
if uid == 0 {
return Some(ImapResult::Failed);
} else if !self.is_connected() {
connect_to_inbox(context, &self);
connect_to_inbox(context, self);
if !self.is_connected() {
return Some(ImapResult::RetryLater);
}
@@ -1296,7 +1320,7 @@ impl Imap {
}
}
pub fn set_seen(&self, context: &Context, folder: &str, uid: u32) -> ImapResult {
pub fn set_seen(&mut self, context: &Context, folder: &str, uid: u32) -> ImapResult {
if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, uid) {
return imapresult;
}
@@ -1316,7 +1340,7 @@ impl Imap {
// only returns 0 on connection problems; we should try later again in this case *
pub fn delete_msg(
&self,
&mut self,
context: &Context,
message_id: &str,
folder: &str,
@@ -1383,12 +1407,12 @@ impl Imap {
display_imap_id, message_id
))
);
self.config.write().unwrap().selected_folder_needs_expunge = true;
self.config.selected_folder_needs_expunge = true;
ImapResult::Success
}
}
pub fn configure_folders(&self, context: &Context, flags: libc::c_int) {
pub fn configure_folders(&mut self, context: &Context, flags: libc::c_int) {
if !self.is_connected() {
return;
}
@@ -1396,7 +1420,7 @@ impl Imap {
info!(context, "Configuring IMAP-folders.");
let folders = self.list_folders(context).unwrap();
let delimiter = self.config.read().unwrap().imap_delimiter;
let delimiter = self.config.imap_delimiter;
let fallback_folder = format!("INBOX{}DeltaChat", delimiter);
let mut mvbox_folder = folders
@@ -1498,7 +1522,7 @@ impl Imap {
}
}
pub fn empty_folder(&self, context: &Context, folder: &str) {
pub fn empty_folder(&mut self, context: &Context, folder: &str) {
info!(context, "emptying folder {}", folder);
if folder.is_empty() || self.select_folder(context, Some(&folder)) == 0 {
@@ -1510,7 +1534,7 @@ impl Imap {
warn!(context, "Cannot empty folder {}", folder);
} else {
// we now trigger expunge to actually delete messages
self.config.write().unwrap().selected_folder_needs_expunge = true;
self.config.selected_folder_needs_expunge = true;
if self.select_folder::<String>(context, None) == 0 {
warn!(
context,
@@ -1573,7 +1597,7 @@ fn precheck_imf(context: &Context, rfc724_mid: &str, server_folder: &str, server
{
if old_server_folder.is_empty() && old_server_uid == 0 {
info!(context, "[move] detected bbc-self {}", rfc724_mid,);
job_add(
job_add_no_interrupt(
context,
Action::MarkseenMsgOnImap,
msg_id.to_u32() as i32,

View File

@@ -1,5 +1,5 @@
use core::cmp::{max, min};
use std::path::{Path, PathBuf};
use std::path::Path;
use num_traits::FromPrimitive;
use rand::{thread_rng, Rng};
@@ -75,7 +75,7 @@ pub fn imex(context: &Context, what: ImexMode, param1: Option<impl AsRef<Path>>)
job_add(context, Action::ImexImap, 0, param, 0);
}
/// Returns the filename of the backup if found, nullptr otherwise.
/// Returns the filename of the backup found (otherwise an error)
pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<String> {
let dir_name = dir_name.as_ref();
let dir_iter = std::fs::read_dir(dir_name)?;
@@ -90,13 +90,15 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<Strin
if name.starts_with("delta-chat") && name.ends_with(".bak") {
let sql = Sql::new();
if sql.open(context, &path, true) {
let curr_backup_time =
sql.get_raw_config_int(context, "backup_time")
.unwrap_or_default() as u64;
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);
}
}
}
@@ -105,7 +107,7 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<Strin
}
match newest_backup_path {
Some(path) => Ok(path.to_string_lossy().into_owned()),
None => bail!("no backup found"),
None => bail!("no backup found in {}", dir_name.display()),
}
}
@@ -231,11 +233,7 @@ pub fn create_setup_code(_context: &Context) -> String {
pub fn continue_key_transfer(context: &Context, msg_id: MsgId, setup_code: &str) -> Result<()> {
ensure!(!msg_id.is_special(), "wrong id");
let msg = Message::load_from_db(context, msg_id);
if msg.is_err() {
bail!("Message is no Autocrypt Setup Message.");
}
let msg = msg.unwrap_or_default();
let msg = Message::load_from_db(context, msg_id)?;
ensure!(
msg.is_setupmessage(),
"Message is no Autocrypt Setup Message."
@@ -487,51 +485,56 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
// let dest_path_filename = dc_get_next_backup_file(context, dir, res);
let now = time();
let dest_path_filename = dc_get_next_backup_path(dir, now)?;
let dest_path_string = dest_path_filename.to_string_lossy().to_string();
sql::housekeeping(context);
sql::try_execute(context, &context.sql, "VACUUM;").ok();
// we close the database during the copy of the dbfile
context.sql.close(context);
info!(
context,
"Backup \"{}\" to \"{}\".",
"Backup '{}' to '{}'.",
context.get_dbfile().display(),
dest_path_filename.display(),
);
let copied = dc_copy_file(context, context.get_dbfile(), &dest_path_filename);
context.sql.open(&context, &context.get_dbfile(), false);
if !copied {
let s = dest_path_filename.to_string_lossy().to_string();
bail!(
"could not copy file from {:?} to {:?}",
context.get_dbfile(),
s
"could not copy file from '{}' to '{}'",
context.get_dbfile().display(),
dest_path_string
);
}
match add_files_to_export(context, &dest_path_filename) {
let dest_sql = Sql::new();
ensure!(
dest_sql.open(context, &dest_path_filename, false),
"could not open exported database {}",
dest_path_string
);
let res = match add_files_to_export(context, &dest_sql) {
Err(err) => {
dc_delete_file(context, &dest_path_filename);
error!(context, "backup failed: {}", err);
Err(err)
}
Ok(()) => {
context
.sql
.set_raw_config_int(context, "backup_time", now as i32)?;
dest_sql.set_raw_config_int(context, "backup_time", now as i32)?;
context.call_cb(Event::ImexFileWritten(dest_path_filename.clone()));
Ok(())
}
}
};
dest_sql.close(context);
res
}
fn add_files_to_export(context: &Context, dest_path_filename: &PathBuf) -> Result<()> {
fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
// add all files as blobs to the database copy (this does not require
// the source to be locked, neigher the destination as it is used only here)
let sql = Sql::new();
ensure!(
sql.open(context, &dest_path_filename, false),
"could not open db"
);
if !sql.table_exists("backup_blobs") {
sql::execute(
context,

View File

@@ -135,8 +135,7 @@ impl Job {
if !context.smtp.lock().unwrap().is_connected() {
let loginparam = LoginParam::from_database(context, "configured_");
let connected = context.smtp.lock().unwrap().connect(context, &loginparam);
if !connected {
if connected.is_err() {
self.try_again_later(3, None);
return;
}
@@ -173,10 +172,10 @@ impl Job {
// its ok/error response processing. Note that if a message
// was sent we need to mark it in the database ASAP as we
// otherwise might send it twice.
let mut sock = context.smtp.lock().unwrap();
match sock.send(context, recipients_list, body) {
let mut smtp = context.smtp.lock().unwrap();
match smtp.send(context, recipients_list, body, self.job_id) {
Err(err) => {
sock.disconnect();
smtp.disconnect();
warn!(context, "smtp failed: {}", err);
self.try_again_later(-1, Some(err.to_string()));
}
@@ -220,7 +219,7 @@ impl Job {
#[allow(non_snake_case)]
fn do_DC_JOB_MOVE_MSG(&mut self, context: &Context) {
let inbox = context.inbox.read().unwrap();
let mut inbox = context.inbox.write().unwrap();
if let Ok(msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
if context
@@ -265,7 +264,7 @@ impl Job {
#[allow(non_snake_case)]
fn do_DC_JOB_DELETE_MSG_ON_IMAP(&mut self, context: &Context) {
let inbox = context.inbox.read().unwrap();
let mut inbox = context.inbox.write().unwrap();
if let Ok(mut msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
if !msg.rfc724_mid.is_empty() {
@@ -293,7 +292,7 @@ impl Job {
#[allow(non_snake_case)]
fn do_DC_JOB_EMPTY_SERVER(&mut self, context: &Context) {
let inbox = context.inbox.read().unwrap();
let mut inbox = context.inbox.write().unwrap();
if self.foreign_id & DC_EMPTY_MVBOX > 0 {
if let Some(mvbox_folder) = context
.sql
@@ -309,7 +308,7 @@ impl Job {
#[allow(non_snake_case)]
fn do_DC_JOB_MARKSEEN_MSG_ON_IMAP(&mut self, context: &Context) {
let inbox = context.inbox.read().unwrap();
let mut inbox = context.inbox.write().unwrap();
if let Ok(msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
let folder = msg.server_folder.as_ref().unwrap();
@@ -343,7 +342,7 @@ impl Job {
.unwrap_or_default()
.to_string();
let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32;
let inbox = context.inbox.read().unwrap();
let mut inbox = context.inbox.write().unwrap();
if inbox.set_seen(context, &folder, uid) == ImapResult::RetryLater {
self.try_again_later(3i32, None);
return;
@@ -384,10 +383,10 @@ pub fn job_kill_action(context: &Context, action: Action) -> bool {
}
pub fn perform_imap_fetch(context: &Context) {
let inbox = context.inbox.read().unwrap();
let mut inbox = context.inbox.write().unwrap();
let start = std::time::Instant::now();
if 0 == connect_to_inbox(context, &inbox) {
if 0 == connect_to_inbox(context, &mut inbox) {
return;
}
if !context.get_config_bool(Config::InboxWatch) {
@@ -403,14 +402,14 @@ pub fn perform_imap_fetch(context: &Context) {
info!(
context,
"INBOX-fetch done in {:.4} ms.",
start.elapsed().as_nanos() as f64 / 1000.0,
start.elapsed().as_nanos() as f64 / 1_000_000.0,
);
}
pub fn perform_imap_idle(context: &Context) {
let inbox = context.inbox.read().unwrap();
let mut inbox = context.inbox.write().unwrap();
connect_to_inbox(context, &inbox);
connect_to_inbox(context, &mut inbox);
if *context.perform_inbox_jobs_needed.clone().read().unwrap() {
info!(
@@ -419,8 +418,11 @@ pub fn perform_imap_idle(context: &Context) {
);
return;
}
drop(inbox);
info!(context, "INBOX-IDLE started...");
inbox.idle(context);
context.inbox.write().unwrap().idle(context);
info!(context, "INBOX-IDLE ended.");
}
@@ -439,7 +441,7 @@ pub fn perform_mvbox_idle(context: &Context) {
context
.mvbox_thread
.read()
.write()
.unwrap()
.idle(context, use_network);
}
@@ -463,7 +465,7 @@ pub fn perform_sentbox_idle(context: &Context) {
context
.sentbox_thread
.read()
.write()
.unwrap()
.idle(context, use_network);
}
@@ -927,7 +929,7 @@ fn suspend_smtp_thread(context: &Context, suspend: bool) {
}
}
pub fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
pub fn connect_to_inbox(context: &Context, inbox: &mut Imap) -> libc::c_int {
let ret_connected = dc_connect_to_configured_imap(context, inbox);
if 0 != ret_connected {
inbox.set_watch_folder("INBOX".into());
@@ -981,6 +983,24 @@ pub fn job_add(
foreign_id: libc::c_int,
param: Params,
delay_seconds: i64,
) {
job_add_no_interrupt(context, action, foreign_id, param, delay_seconds);
let thread: Thread = action.into();
match thread {
Thread::Imap => interrupt_imap_idle(context),
Thread::Smtp => interrupt_smtp_idle(context),
Thread::Unknown => {}
}
}
pub fn job_add_no_interrupt(
context: &Context,
action: Action,
foreign_id: libc::c_int,
param: Params,
delay_seconds: i64,
) {
if action == Action::Unknown {
error!(context, "Invalid action passed to job_add");
@@ -1003,28 +1023,30 @@ pub fn job_add(
(timestamp + delay_seconds as i64)
]
).ok();
match thread {
Thread::Imap => interrupt_imap_idle(context),
Thread::Smtp => interrupt_smtp_idle(context),
Thread::Unknown => {}
}
}
pub fn interrupt_smtp_idle(context: &Context) {
info!(context, "Interrupting SMTP-idle...",);
let &(ref lock, ref cvar) = &*context.smtp_state.clone();
let mut state = lock.lock().unwrap();
{
let mut state = lock.lock().unwrap();
state.perform_jobs_needed = 1;
state.idle = true;
state.perform_jobs_needed = 1;
state.idle = true;
info!(context, "smtp interrupt jobs written");
}
cvar.notify_one();
info!(context, "smtp interrupt done");
}
pub fn interrupt_imap_idle(context: &Context) {
info!(context, "Interrupting IMAP-IDLE...",);
info!(context, "Interrupting INBOX-IDLE...",);
*context.perform_inbox_jobs_needed.write().unwrap() = true;
context.inbox.read().unwrap().interrupt_idle();
info!(context, "interrupt jobs written");
context.interrupt_inbox_idle();
info!(context, "interrupt imap done");
}

View File

@@ -9,23 +9,28 @@ pub struct JobThread {
pub name: &'static str,
pub folder_config_name: &'static str,
pub imap: Imap,
watch: Arc<(Mutex<bool>, Condvar)>,
pub state: Arc<(Mutex<JobState>, Condvar)>,
}
#[derive(Clone, Debug, Default)]
pub struct JobState {
idle: bool,
jobs_needed: i32,
jobs_needed: bool,
suspended: bool,
using_handle: bool,
}
impl JobThread {
pub fn new(name: &'static str, folder_config_name: &'static str, imap: Imap) -> Self {
pub fn new(name: &'static str, folder_config_name: &'static str) -> Self {
let watch = Arc::new((Mutex::new(false), Condvar::new()));
let imap = Imap::new(watch.clone());
JobThread {
name,
folder_config_name,
imap,
watch,
state: Arc::new((Mutex::new(Default::default()), Condvar::new())),
}
}
@@ -58,18 +63,27 @@ impl JobThread {
pub fn interrupt_idle(&self, context: &Context) {
{
self.state.0.lock().unwrap().jobs_needed = 1;
self.state.0.lock().unwrap().jobs_needed = true;
}
info!(context, "Interrupting {}-IDLE...", self.name);
self.imap.interrupt_idle();
// interrupt imap idle
let &(ref lock, ref cvar) = &*self.watch.clone();
{
let mut watch = lock.lock().unwrap();
*watch = true;
cvar.notify_one();
}
let &(ref lock, ref cvar) = &*self.state.clone();
let mut state = lock.lock().unwrap();
state.idle = true;
{
let mut state = lock.lock().unwrap();
state.idle = true;
}
cvar.notify_one();
info!(context, "{}-idle interrupt done", self.name);
}
pub fn fetch(&mut self, context: &Context, use_network: bool) {
@@ -106,12 +120,12 @@ impl JobThread {
self.state.0.lock().unwrap().using_handle = false;
}
fn connect_to_imap(&self, context: &Context) -> bool {
fn connect_to_imap(&mut self, context: &Context) -> bool {
if self.imap.is_connected() {
return true;
}
let mut ret_connected = dc_connect_to_configured_imap(context, &self.imap) != 0;
let mut ret_connected = dc_connect_to_configured_imap(context, &mut self.imap) != 0;
if ret_connected {
if context
@@ -134,18 +148,18 @@ impl JobThread {
ret_connected
}
pub fn idle(&self, context: &Context, use_network: bool) {
pub fn idle(&mut self, context: &Context, use_network: bool) {
{
let &(ref lock, ref cvar) = &*self.state.clone();
let mut state = lock.lock().unwrap();
if 0 != state.jobs_needed {
if state.jobs_needed {
info!(
context,
"{}-IDLE will not be started as it was interrupted while not ideling.",
self.name,
);
state.jobs_needed = 0;
state.jobs_needed = false;
return;
}

View File

@@ -62,14 +62,11 @@ impl Kml {
Default::default()
}
pub fn parse(context: &Context, content: impl AsRef<str>) -> Result<Self, Error> {
ensure!(
content.as_ref().len() <= (1024 * 1024),
"A kml-files with {} bytes is larger than reasonably expected.",
content.as_ref().len()
);
pub fn parse(context: &Context, content: &[u8]) -> Result<Self, Error> {
ensure!(content.len() <= 1024 * 1024, "kml-file is too large");
let mut reader = quick_xml::Reader::from_str(content.as_ref());
let to_parse = String::from_utf8_lossy(content);
let mut reader = quick_xml::Reader::from_str(&to_parse);
reader.trim_text(true);
let mut kml = Kml::new();
@@ -222,7 +219,7 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
} else if 0 == seconds && is_sending_locations_before {
let stock_str =
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
chat::add_device_msg(context, chat_id, stock_str);
chat::add_info_msg(context, chat_id, stock_str);
}
context.call_cb(Event::ChatModified(chat_id));
if 0 != seconds {
@@ -278,7 +275,7 @@ pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> b
accuracy,
time(),
chat_id,
1,
DC_CONTACT_ID_SELF,
]
) {
warn!(context, "failed to store location {:?}", err);
@@ -369,9 +366,6 @@ pub fn delete_all(context: &Context) -> Result<(), Error> {
}
pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error> {
let now = time();
let mut location_count = 0;
let mut ret = String::new();
let mut last_added_location_id = 0;
let self_addr = context
@@ -388,14 +382,17 @@ pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error>
Ok((send_begin, send_until, last_sent))
})?;
if !(locations_send_begin == 0 || now > locations_send_until) {
let now = time();
let mut location_count = 0;
let mut ret = String::new();
if locations_send_begin != 0 && now <= locations_send_until {
ret += &format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"{}\">\n",
self_addr,
);
context.sql.query_map(
"SELECT id, latitude, longitude, accuracy, timestamp\
"SELECT id, latitude, longitude, accuracy, timestamp \
FROM locations WHERE from_id=? \
AND timestamp>=? \
AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?)) \
@@ -416,7 +413,7 @@ pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error>
for row in rows {
let (location_id, latitude, longitude, accuracy, timestamp) = row?;
ret += &format!(
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n\x00",
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n",
timestamp,
accuracy,
longitude,
@@ -428,10 +425,10 @@ pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error>
Ok(())
}
)?;
ret += "</Document>\n</kml>";
}
ensure!(location_count > 0, "No locations processed");
ret += "</Document>\n</kml>";
Ok((ret, last_added_location_id))
}
@@ -495,7 +492,7 @@ pub fn save(
chat_id: u32,
contact_id: u32,
locations: &[Location],
independent: i32,
independent: bool,
) -> Result<u32, Error> {
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat id");
context.sql.prepare2(
@@ -510,7 +507,7 @@ pub fn save(
for location in locations {
let exists = stmt_test.exists(params![location.timestamp, contact_id as i32])?;
if 0 != independent || !exists {
if independent || !exists {
stmt_insert.execute(params![
location.timestamp,
contact_id as i32,
@@ -654,7 +651,7 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut Job) {
params![chat_id as i32],
).is_ok() {
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
chat::add_device_msg(context, chat_id, stock_str);
chat::add_info_msg(context, chat_id, stock_str);
context.call_cb(Event::ChatModified(chat_id));
}
}
@@ -672,9 +669,9 @@ mod tests {
let context = dummy_context();
let xml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"user@example.org\">\n<Placemark><Timestamp><when>2019-03-06T21:09:57Z</when></Timestamp><Point><coordinates accuracy=\"32.000000\">9.423110,53.790302</coordinates></Point></Placemark>\n<PlaceMARK>\n<Timestamp><WHEN > \n\t2018-12-13T22:11:12Z\t</WHEN></Timestamp><Point><coordinates aCCuracy=\"2.500000\"> 19.423110 \t , \n 63.790302\n </coordinates></Point></PlaceMARK>\n</Document>\n</kml>";
b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"user@example.org\">\n<Placemark><Timestamp><when>2019-03-06T21:09:57Z</when></Timestamp><Point><coordinates accuracy=\"32.000000\">9.423110,53.790302</coordinates></Point></Placemark>\n<PlaceMARK>\n<Timestamp><WHEN > \n\t2018-12-13T22:11:12Z\t</WHEN></Timestamp><Point><coordinates aCCuracy=\"2.500000\"> 19.423110 \t , \n 63.790302\n </coordinates></Point></PlaceMARK>\n</Document>\n</kml>";
let kml = Kml::parse(&context.ctx, &xml).expect("parsing failed");
let kml = Kml::parse(&context.ctx, xml).expect("parsing failed");
assert!(kml.addr.is_some());
assert_eq!(kml.addr.as_ref().unwrap(), "user@example.org",);

View File

@@ -476,8 +476,8 @@ impl Message {
pub fn is_info(&self) -> bool {
let cmd = self.param.get_cmd();
self.from_id == DC_CONTACT_ID_DEVICE as libc::c_uint
|| self.to_id == DC_CONTACT_ID_DEVICE as libc::c_uint
self.from_id == DC_CONTACT_ID_INFO as libc::c_uint
|| self.to_id == DC_CONTACT_ID_INFO as libc::c_uint
|| cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
}
@@ -714,7 +714,7 @@ pub fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
ret += "\n";
}
if msg.from_id == 2 || msg.to_id == 2 {
if msg.from_id == DC_CONTACT_ID_INFO || msg.to_id == DC_CONTACT_ID_INFO {
// device-internal message, no further details needed
return ret;
}

View File

@@ -484,6 +484,7 @@ impl<'a> MimeFactory<'a> {
if !meta_part.is_null() {
mailmime_smart_add_part(message, meta_part);
}
if self.msg.param.exists(Param::SetLatitude) {
let param = &self.msg.param;
let kml_file = location::get_message_kml(
@@ -500,18 +501,21 @@ impl<'a> MimeFactory<'a> {
}
if location::is_sending_locations_to_chat(context, self.msg.chat_id) {
if let Ok((kml_file, last_added_location_id)) =
location::get_kml(context, self.msg.chat_id)
{
wrapmime::add_filename_part(
message,
"location.kml",
"application/vnd.google-earth.kml+xml",
&kml_file,
)?;
if !self.msg.param.exists(Param::SetLatitude) {
// otherwise, the independent location is already filed
self.out_last_added_location_id = last_added_location_id;
match location::get_kml(context, self.msg.chat_id) {
Ok((kml_content, last_added_location_id)) => {
wrapmime::add_filename_part(
message,
"location.kml",
"application/vnd.google-earth.kml+xml",
&kml_content,
)?;
if !self.msg.param.exists(Param::SetLatitude) {
// otherwise, the independent location is already filed
self.out_last_added_location_id = last_added_location_id;
}
}
Err(err) => {
warn!(context, "mimefactory: could not get location: {}", err);
}
}
}
@@ -655,8 +659,6 @@ impl<'a> MimeFactory<'a> {
}
pub fn load_msg(context: &Context, msg_id: MsgId) -> Result<MimeFactory, Error> {
ensure!(!msg_id.is_special(), "Invalid chat id");
let msg = Message::load_from_db(context, msg_id)?;
let chat = Chat::load_from_db(context, msg.chat_id)?;
let mut factory = MimeFactory::new(context, msg);

View File

@@ -76,6 +76,8 @@ pub enum Param {
ProfileImage = b'i',
// For Chats
Selftalk = b'K',
// For Chats
Devicetalk = b'D',
// For QR
Auth = b's',
// For QR

View File

@@ -90,7 +90,8 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot {
// what is up with that param name?
let name = if let Some(encoded_name) = param.get(Param::SetLongitude) {
match percent_decode_str(encoded_name).decode_utf8() {
let encoded_name = encoded_name.replace("+", "%20"); // sometimes spaces are encoded as `+`
match percent_decode_str(&encoded_name).decode_utf8() {
Ok(name) => name.to_string(),
Err(err) => return format_err!("Invalid name: {}", err).into(),
}
@@ -104,7 +105,8 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot {
let grpname = if grpid.is_some() {
if let Some(encoded_name) = param.get(Param::GroupName) {
match percent_decode_str(encoded_name).decode_utf8() {
let encoded_name = encoded_name.replace("+", "%20"); // sometimes spaces are encoded as `+`
match percent_decode_str(&encoded_name).decode_utf8() {
Ok(name) => Some(name.to_string()),
Err(err) => return format_err!("Invalid group name: {}", err).into(),
}
@@ -148,7 +150,7 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot {
let (id, _) = chat::create_or_lookup_by_contact_id(context, lot.id, Blocked::Deaddrop)
.unwrap_or_default();
chat::add_device_msg(
chat::add_info_msg(
context,
id,
format!("{} verified.", peerstate.addr.unwrap_or_default()),
@@ -186,7 +188,7 @@ fn decode_mailto(context: &Context, qr: &str) -> Lot {
let addr = if let Some(query_index) = payload.find('?') {
&payload[..query_index]
} else {
return format_err!("Invalid mailto found").into();
payload
};
let addr = match normalize_address(addr) {
@@ -405,13 +407,21 @@ mod tests {
&ctx.ctx,
"mailto:stress@test.local?subject=hello&body=world",
);
println!("{:?}", res);
assert_eq!(res.get_state(), LotState::QrAddr);
assert_ne!(res.get_id(), 0);
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
assert_eq!(contact.get_addr(), "stress@test.local");
let res = check_qr(&ctx.ctx, "mailto:no-questionmark@example.org");
assert_eq!(res.get_state(), LotState::QrAddr);
assert_ne!(res.get_id(), 0);
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
assert_eq!(contact.get_addr(), "no-questionmark@example.org");
let res = check_qr(&ctx.ctx, "mailto:no-addr");
assert_eq!(res.get_state(), LotState::QrError);
assert!(res.get_text1().is_some());
}
#[test]
@@ -434,12 +444,13 @@ mod tests {
let res = check_qr(
&ctx.ctx,
"OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&g=testtesttest&x=h-0oKQf2CDK&i=9JEXlxAqGM0&s=0V7LzL9cxRL"
"OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&g=test%20%3F+test%20%21&x=h-0oKQf2CDK&i=9JEXlxAqGM0&s=0V7LzL9cxRL"
);
println!("{:?}", res);
assert_eq!(res.get_state(), LotState::QrAskVerifyGroup);
assert_ne!(res.get_id(), 0);
assert_eq!(res.get_text1().unwrap(), "test ? test !");
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
assert_eq!(contact.get_addr(), "cli@deltachat.de");
@@ -451,7 +462,7 @@ mod tests {
let res = check_qr(
&ctx.ctx,
"OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&n=&i=TbnwJ6lSvD5&s=0ejvbdFSQxB"
"OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&n=J%C3%B6rn%20P.+P.&i=TbnwJ6lSvD5&s=0ejvbdFSQxB"
);
println!("{:?}", res);
@@ -460,5 +471,6 @@ mod tests {
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
assert_eq!(contact.get_addr(), "cli@deltachat.de");
assert_eq!(contact.get_name(), "Jörn P. P.");
}
}

View File

@@ -638,7 +638,7 @@ fn secure_connection_established(context: &Context, contact_chat_id: u32) {
"?"
};
let msg = context.stock_string_repl_str(StockMessage::ContactVerified, addr);
chat::add_device_msg(context, contact_chat_id, msg);
chat::add_info_msg(context, contact_chat_id, msg);
emit_event!(context, Event::ChatModified(contact_chat_id));
}
@@ -654,7 +654,7 @@ fn could_not_establish_secure_connection(context: &Context, contact_chat_id: u32
},
);
chat::add_device_msg(context, contact_chat_id, &msg);
chat::add_info_msg(context, contact_chat_id, &msg);
error!(context, "{} ({})", &msg, details);
}
@@ -735,7 +735,7 @@ pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) -> Result<
};
let msg = context.stock_string_repl_str(StockMessage::ContactSetupChanged, peeraddr);
chat::add_device_msg(context, contact_chat_id, msg);
chat::add_info_msg(context, contact_chat_id, msg);
emit_event!(context, Event::ChatModified(contact_chat_id));
}
}

View File

@@ -44,27 +44,24 @@ impl Smtp {
}
/// Connect using the provided login params
pub fn connect(&mut self, context: &Context, lp: &LoginParam) -> bool {
pub fn connect(&mut self, context: &Context, lp: &LoginParam) -> Result<(), Error> {
if self.is_connected() {
warn!(context, "SMTP already connected.");
return true;
return Ok(());
}
if lp.send_server.is_empty() || lp.send_port == 0 {
context.call_cb(Event::ErrorNetwork("SMTP bad parameters.".into()));
bail!("SMTP Bad parameters");
}
self.from = if let Ok(addr) = EmailAddress::new(lp.addr.clone()) {
Some(addr)
} else {
None
self.from = match EmailAddress::new(lp.addr.clone()) {
Ok(addr) => Some(addr),
Err(err) => {
bail!("invalid login address {}: {}", lp.addr, err);
}
};
if self.from.is_none() {
// TODO: print error
return false;
}
let domain = &lp.send_server;
let port = lp.send_port as u16;
@@ -76,11 +73,12 @@ impl Smtp {
let addr = &lp.addr;
let send_pw = &lp.send_pw;
let access_token = dc_get_oauth2_access_token(context, addr, send_pw, false);
if access_token.is_none() {
return false;
}
ensure!(
access_token.is_some(),
"could not get oaut2_access token addr={}",
addr
);
let user = &lp.send_user;
(
lettre::smtp::authentication::Credentials::new(
user.to_string(),
@@ -125,27 +123,27 @@ impl Smtp {
"SMTP-LOGIN as {} ok",
lp.send_user,
)));
return true;
return Ok(());
}
Err(err) => {
warn!(context, "SMTP: failed to connect {:?}", err);
bail!("SMTP: failed to connect {:?}", err);
}
}
}
Err(err) => {
warn!(context, "SMTP: failed to setup connection {:?}", err);
bail!("SMTP: failed to setup connection {:?}", err);
}
}
false
}
/// SMTP-Send a prepared mail to recipients.
/// returns boolean whether send was successful.
/// on successful send out Ok() is returned.
pub fn send<'a>(
&mut self,
context: &Context,
recipients: Vec<EmailAddress>,
message: Vec<u8>,
job_id: u32,
) -> Result<(), Error> {
let message_len = message.len();
@@ -156,12 +154,15 @@ impl Smtp {
.join(",");
if let Some(ref mut transport) = self.transport {
let envelope = Envelope::new(self.from.clone(), recipients);
ensure!(envelope.is_ok(), "internal smtp-message construction fail");
let envelope = envelope.unwrap();
let envelope = match Envelope::new(self.from.clone(), recipients) {
Ok(env) => env,
Err(err) => {
bail!("{}", err);
}
};
let mail = SendableEmail::new(
envelope,
"mail-id".into(), // TODO: random id
format!("{}", job_id), // only used for internal logging
message,
);

View File

@@ -92,8 +92,7 @@ impl Sql {
self.start_stmt(sql.to_string());
self.with_conn(|conn| {
let stmt = conn.prepare(sql)?;
let res = g(stmt, conn)?;
Ok(res)
g(stmt, conn)
})
}
@@ -106,8 +105,7 @@ impl Sql {
let stmt1 = conn.prepare(sql1)?;
let stmt2 = conn.prepare(sql2)?;
let res = g(stmt1, stmt2, conn)?;
Ok(res)
g(stmt1, stmt2, conn)
})
}
@@ -385,8 +383,8 @@ fn open(
)?;
sql.execute(
"INSERT INTO contacts (id,name,origin) VALUES \
(1,'self',262144), (2,'device',262144), (3,'rsvd',262144), \
(4,'rsvd',262144), (5,'rsvd',262144), (6,'rsvd',262144), \
(1,'self',262144), (2,'info',262144), (3,'rsvd',262144), \
(4,'rsvd',262144), (5,'device',262144), (6,'rsvd',262144), \
(7,'rsvd',262144), (8,'rsvd',262144), (9,'rsvd',262144);",
params![],
)?;

View File

@@ -110,6 +110,8 @@ pub enum StockMessage {
Location = 66,
#[strum(props(fallback = "Sticker"))]
Sticker = 67,
#[strum(props(fallback = "Device Messages"))]
DeviceMessages = 68,
}
impl StockMessage {

View File

@@ -143,7 +143,8 @@ pub fn get_field_date(imffields: *mut mailimf_fields) -> Result<i64, Error> {
fn mailimf_get_recipients_add_addr(recipients: &mut HashSet<String>, mb: *mut mailimf_mailbox) {
if !mb.is_null() {
let addr_norm = addr_normalize(as_str(unsafe { (*mb).mb_addr_spec }));
let addr = to_string_lossy(unsafe { (*mb).mb_addr_spec });
let addr_norm = addr_normalize(&addr);
recipients.insert(addr_norm.into());
}
}
@@ -382,8 +383,8 @@ pub fn mailimf_find_first_addr(mb_list: *const mailimf_mailbox_list) -> Option<S
for cur in unsafe { (*(*mb_list).mb_list).into_iter() } {
let mb = cur as *mut mailimf_mailbox;
if !mb.is_null() && !unsafe { (*mb).mb_addr_spec.is_null() } {
let addr = unsafe { as_str((*mb).mb_addr_spec) };
return Some(addr_normalize(addr).to_string());
let addr = unsafe { to_string_lossy((*mb).mb_addr_spec) };
return Some(addr_normalize(&addr).to_string());
}
}
@@ -489,7 +490,9 @@ pub fn content_type_needs_encoding(content: *const mailmime_content) -> bool {
if (*(*content).ct_type).tp_type == MAILMIME_TYPE_COMPOSITE_TYPE as libc::c_int {
let composite = (*(*content).ct_type).tp_data.tp_composite_type;
match (*composite).ct_type as u32 {
MAILMIME_COMPOSITE_TYPE_MESSAGE => as_str((*content).ct_subtype) != "rfc822",
MAILMIME_COMPOSITE_TYPE_MESSAGE => {
to_string_lossy((*content).ct_subtype) != "rfc822"
}
MAILMIME_COMPOSITE_TYPE_MULTIPART => false,
_ => false,
}