Compare commits

...

77 Commits

Author SHA1 Message Date
holger krekel
2773b89815 prep beta.23 2020-01-21 21:40:32 +01:00
holger krekel
949d93fdaa - actually also upload py docs, not only c docs
- delete unused files on remote
- show a project number in doxygen
2020-01-21 19:14:16 +01:00
Alexander Krotov
90a4303c8e Make it possible to compile with stable Rust 2020-01-21 19:50:42 +03:00
holger krekel
d3b1972505 fix master build 2020-01-21 17:27:21 +01:00
holger krekel
3807d5fbd0 bringing back wheel building for linux, and fixing c.delta.chat 2020-01-21 17:25:01 +01:00
holger krekel
e49e2021e5 i don't think this fuzzy test is very useful -- the mime-parser will end very early on random-input and DC's parsing machinery probably almost never runs. 2020-01-21 16:00:59 +01:00
Alexander Krotov
84a5276ab0 Fix build
0f52f63863 rebase broke the build
2020-01-21 17:17:51 +03:00
Alexander Krotov
1732c3b350 Reduce the scope of clippy exceptions
This prevents creation of new overly complex functions.
2020-01-21 14:49:53 +01:00
Alexander Krotov
0f52f63863 Add "Auto-Submitted: auto-replied" header to MDNs
This header is specified in RFC 3834.
2020-01-21 16:29:18 +03:00
B. Petersen
0043e95ba7 fix message deletion on server
before a message is actually deleted,
we verify that server_folder+server_uid matches the Message-ID,
so that we do not delete the wrong message by accident.
the reason for that is that, to my knowledge, server_folder+server_uid
are not necessarily stable but Message-ID is.

anyway, this check was broken and set the server_uid to 0 in all cases -
where it should be set to 0 _only_ on mismatches.
2020-01-21 16:27:21 +03:00
Alexander Krotov
a3f2088046 Fix the message printed on job success
"Cannot be retried" does not make it clear if the job succeeded or failed.
2020-01-20 10:47:32 +01:00
Floris Bruynooghe
186f5553b8 Introduce a ChatId newtype
This doesn't try and change the way ChatId is used.  It still allows
creating them with 0 and lets some function use a ChatId(0) as error
return.
2020-01-19 23:42:08 +01:00
Alexander Krotov
d8454d9da5 Deduplicate SendMdn and SendMsgToSmtp code 2020-01-20 00:35:11 +03:00
Alexander Krotov
318194a216 Rename Param::MessageId into Param::MsgId
MsgId refers to database ID, same as crate::message::MsgId newtype.
2020-01-20 00:35:11 +03:00
Alexander Krotov
b50d2358d3 Rename X-Additional-Message-IDs into Additional-Message-IDs
X- prefixes are deprecated in https://tools.ietf.org/html/rfc6648
2020-01-20 00:35:11 +03:00
Alexander Krotov
763587ffb4 Load only one job at a time
As a result of job, other jobs can be added or deleted. To avoid
processing deleted jobs and to process other jobs in the correct order,
we should reload them after performing each job.

However, we don't use "LIMIT 1" in SQL queries, because we want to be
able to skip over invalid jobs, and checking if job is invalid can only
be done in Rust.
2020-01-20 00:35:11 +03:00
Alexander Krotov
8a375c12e9 Mark two messages as seen in test_send_and_receive_message_markseen
This may or may not send a combined MDN out.  We don't test for it,
but the test ensures that *if combined MDNs are sent in this case*,
then we receive them correctly.
2020-01-20 00:35:11 +03:00
Alexander Krotov
11afdb51f3 Log it when MDNs are combined 2020-01-20 00:35:11 +03:00
Alexander Krotov
e4353f4650 Aggregate SendMdn jobs 2020-01-20 00:35:11 +03:00
Alexander Krotov
9d4437a7f5 Make it possible to add X-Additional-Message-IDs to MDNs 2020-01-20 00:35:11 +03:00
Alexander Krotov
493bf5ed08 Parse additional message IDs in MDNs 2020-01-20 00:35:11 +03:00
Alexander Krotov
93800fd834 Construct list of recipients in SendMdn
There is always one recipient for MDNs, so it is easier to only handle
this case.
2020-01-20 00:35:11 +03:00
Alexander Krotov
79bc34ed76 Move check for blocked contact from mimefactory to SendMdn job 2020-01-20 00:35:11 +03:00
Alexander Krotov
b40ad7e87e Store contact ID in SendMdn job foreign_id
This change makes it possible to find all pending MDNs for the contact
with an SQL query.
2020-01-20 00:35:11 +03:00
Alexander Krotov
1ab3fba212 Move check for enabled MDNs from message rendering to MDN job 2020-01-20 00:35:11 +03:00
Alexander Krotov
3cd0bbc0f4 Move foreign_id handling out of mimefactory 2020-01-20 00:35:11 +03:00
Alexander Krotov
a806728e43 Make a new job type for MDN sending
Now MDN is generated on every try instead of being stored in a blob.
2020-01-20 00:35:11 +03:00
Alexander Krotov
683037ca69 Remove no-op jobs SendMdnOld and SendMsgToSmtpOld 2020-01-20 00:35:11 +03:00
Alexander Krotov
a113127df1 Implement Display for MessageState 2020-01-20 00:35:11 +03:00
Alexander Krotov
fecfaaf812 Test MDN parsing 2020-01-20 00:35:11 +03:00
Alexander Krotov
8df8f1f6f7 Update deltachat_derive metadata
Same as core and FFI.
2020-01-19 21:40:22 +01:00
Alexander Krotov
5502ca5f58 Update deltachat_derive dependencies 2020-01-19 21:40:22 +01:00
Alexander Krotov
75b8cfa92e Disable unnecessary image crate features
TIFF file format and parallel processing of JPEGs are not necessary to
recode avatars.
2020-01-18 00:30:52 +03:00
B. Petersen
f7b23fb0e2 check that recoding of jpg and png files actually works 2020-01-18 00:30:21 +03:00
B. Petersen
42f7abf2f5 for test_selfavatar*(), use the same function signature as for other tests 2020-01-18 00:30:21 +03:00
Alexander Krotov
08dd0e34d1 Make reqwest features "blocking" and "json" non-optional 2020-01-17 16:01:11 +03:00
Floris Bruynooghe
7540770dec Resultify get_chat_id_by_grpid
I want to avoid having to be able to represent a chat_id of 0 in order
to more nicely turn chat_id into a ChatId newtype.
2020-01-16 21:46:06 +01:00
Alexander Krotov
213c5df706 mimefactory: remove unused "increation" field 2020-01-16 23:37:53 +03:00
B. Petersen
578e4b2785 re-add some comment to the securejoin-flow 2020-01-16 17:06:24 +01:00
Floris Bruynooghe
8f8db0c431 Small language improvement 2020-01-15 07:27:07 +01:00
Alexander Krotov
df7253b13e Document MessageState and fix some typos 2020-01-15 00:48:10 +03:00
Alexander Krotov
fcafd63f41 Remove NOTE about third-party libraries
"libs" directory has been removed during transition to Rust
2020-01-14 10:29:08 +03:00
Floris Bruynooghe
c82fdb9fa1 Refactor return values from secure-join message handling
This return value was very complicated to understand.  Some failure
returns were returned as Err and some as Ok with no consistency, but
resulting in the same behaviour.

This refactor makes the handle_securejoin_handshake the sole place
responsible for maintaining the state of the secure join
process (context.bob) and also in charge of terminating the ongoing
process.  This is none of receive_imf's business.

The remaining returns are now cleanly classified in application-errors
and protocol errors:

Applications errors result in an Err and mean there is a bug or
something else serious went wrong, like database access suddenly
failed or so.  In this case receive_imf is still responsible for
clearing the state and resetting ongoing-process.  It may be possible
this should still be moved back to securejoin.rs so that recieve_imf
doesn't need to know anything about this either.

Protocol errors are not failures for receive_imf, it just means the
received message didn't follow the protocol.  Receive_imf in this case
is told to ignore the message: that is hide it but not delete it.

Other Ok returns also only say what needs to happen to the message:

- It's fully processed and needs no further processing, instead should
  be removed

- It should still be processed as a normal received message.

This changes some behaviour: if the chat creation/lookup for the
contact fails this is treated as an application error.  Previously
this was silently ignored and send_msg() would be called with a 0
chat_id without checking the response.  This resulted in the protocol
quitely being blocked.

This all shhould now make it easier to resultify more of the functions
called by this function, instead of having to deal with very
complicated application logic hidden in the return code.
2020-01-13 20:46:58 +01:00
Alexander Krotov
454c52f4ab Use correct SPDX license identifier in Cargo.toml 2020-01-13 10:48:16 +00:00
B. Petersen
73c69c3f93 fix optimisation 2020-01-13 01:43:27 +01:00
bjoern
614ec30e05 Update src/mimefactory.rs
Co-Authored-By: Alexander Krotov <ilabdsf@gmail.com>
2020-01-13 01:43:27 +01:00
B. Petersen
d7c07e0ed3 set subject to group-name, tweak prefixes
this commit sets the subject of groups to the name of the group.
if the message is a reply in the group, also the prefix `Re:` is added.
the Chat: prefix is removed for groups,
also the Fwd: prefix, as it this would be expected to be set to the
group name of the forwarded message, which is not really compatible with the
rule "group-name = subject" (forwarded messages are detected by the body,
not by the subject)
2020-01-13 01:43:27 +01:00
Alexander Krotov
08bd16c1b6 Update rusqlite 2020-01-12 21:54:38 +00:00
Alexander Krotov
4f06c72798 Remove top_evil_rs.py 2020-01-12 09:02:30 +00:00
Alexander Krotov
166fe2a4e5 Forbid unsafe code in deltachat crate 2020-01-12 09:02:30 +00:00
Alexander Krotov
b7635a71c8 Do not call unsafe rusqlite::ffi::sqlite3_threadsafe()
rusqlite already checks that SQLite is safe to use in multi-threaded mode when it opens the database and returns rusqlite::Error::SqliteSingleThreadedMode otherwise.
2020-01-12 09:02:30 +00:00
Alexander Krotov
6ad4bdea83 Make get_parent_mime_headers() private 2020-01-12 09:12:24 +01:00
Alexander Krotov
efb9a11d22 Do not select drafts as parent message
Also create a common parent_query() function to make sure we use the
same message as a parent when determining if it is encrypted and when
actually replying to it.
2020-01-12 09:12:24 +01:00
Alexander Krotov
047d09bcb1 Do not pass &Sql to parent_is_encrypted() 2020-01-12 09:12:24 +01:00
Alexander Krotov
e1ca6b5181 Rename last_msg_in_chat_encrypted into parent_is_encrypted 2020-01-12 09:12:24 +01:00
Alexander Krotov
36a2569537 Make last_msg_in_chat_encrypted a member function 2020-01-12 09:12:24 +01:00
Alexander Krotov
10ceddfa67 Set draft in test_reply_encrypted test
Adding unencrypted draft to chat makes the job of determining whether
last message is encrypted harder.
2020-01-12 09:12:24 +01:00
holger krekel
929677afe0 fix typo 2020-01-11 21:10:08 +00:00
Alexander Krotov
ee4adc1363 Return &str from extract_grpid 2020-01-11 14:59:53 +01:00
Alexander Krotov
c7b2bdfaac mimeparser: simplify get_subject() and get_rfc724_mid 2020-01-11 14:59:53 +01:00
Alexander Krotov
fde8fb960b oauth2: replace unwrap() and is_none() with "if let" 2020-01-11 14:59:53 +01:00
Alexander Krotov
9a43d26c60 dc_receive_imf: do not unwrap() Some(..) immediately after creation 2020-01-11 14:59:53 +01:00
Alexander Krotov
742f603b3b Use if let to avoid unwrap() of peerstate 2020-01-11 14:59:53 +01:00
Alexander Krotov
a82f2a5df3 Simplify needs_encoding() and add a test 2020-01-11 14:59:53 +01:00
Alexander Krotov
06bc5513ae Use Vec::first to avoid explicit is_empty() check 2020-01-11 14:59:53 +01:00
Alexander Krotov
47b937f880 Turn some enum Param comments into doc-comments 2020-01-11 15:27:09 +03:00
Alexander Krotov
775d27b6a9 Update group test 2020-01-10 15:20:23 +03:00
Alexander Krotov
6c838ab57c Never reset gossip timestamp for all chats at the same time 2020-01-10 15:20:23 +03:00
Alexander Krotov
8eebd2aa67 Do not reset gossip timestamp when group members change 2020-01-10 15:20:23 +03:00
Alexander Krotov
fc78a08657 Fix inverted should_do_gossip condition 2020-01-10 15:20:23 +03:00
Alexander Krotov
7b3cc95ab7 Derive Debug for Loaded and MimeFactory 2020-01-10 06:42:36 +00:00
Alexander Krotov
bd70765b7d Join recipients_names and recipients_addr into one vector
This change ensures on the type level that number of names and addresses
is the same.
2020-01-10 06:42:36 +00:00
Alexander Krotov
5b424aec22 Remove temporary variable "row" 2020-01-10 06:42:36 +00:00
Alexander Krotov
d6cc0694f0 Move Chat from MimeFactory to Loaded::Message 2020-01-10 06:42:36 +00:00
Alexander Krotov
9738515129 Construct immutable MimeFactory in from_msg 2020-01-10 06:42:36 +00:00
Alexander Krotov
b05ee10f41 Return Err from MimeFactory::from_msg on database errors 2020-01-10 06:42:36 +00:00
Alexander Krotov
67d85f0f86 Make all MimeFactory fields private 2020-01-10 06:42:36 +00:00
49 changed files with 2146 additions and 1495 deletions

View File

@@ -120,32 +120,19 @@ jobs:
paths:
- c-docs
# build_test_docs_wheel:
# docker:
# - image: deltachat/coredeps
# environment:
# TESTS: 1
# DOCS: 1
# working_directory: /mnt/crate
# steps:
# - *restore-workspace
# - *restore-cache
# - run:
# name: build docs, run tests and build wheels
# command: ci_scripts/run-python.sh
# - run:
# name: copying docs and wheels to workspace
# command: |
# mkdir -p workspace/python
# # cp -av docs workspace/c-docs
# cp -av python/.docker-tox/wheelhouse workspace/
# cp -av python/doc/_build/ workspace/py-docs
# - persist_to_workspace:
# root: workspace
# paths:
# # - c-docs
# - py-docs
# - wheelhouse
remote_python_packaging:
machine: true
steps:
- checkout
# the following commands on success produces
# workspace/{wheelhouse,py-docs} as artefact directories
- run: bash ci_scripts/remote_python_packaging.sh
- persist_to_workspace:
root: workspace
paths:
# - c-docs
- py-docs
- wheelhouse
remote_tests_rust:
machine: true
@@ -157,10 +144,7 @@ jobs:
machine: true
steps:
- checkout
#- attach_workspace:
# at: workspace
- run: ci_scripts/remote_tests_python.sh
# workspace/py-docs workspace/wheelhouse workspace/c-docs
upload_docs_wheels:
machine: true
@@ -168,6 +152,7 @@ jobs:
- checkout
- attach_workspace:
at: workspace
- run: pyenv versions
- run: pyenv global 3.5.2
- run: ls -laR workspace
- run: ci_scripts/ci_upload.sh workspace/py-docs workspace/wheelhouse workspace/c-docs
@@ -193,10 +178,15 @@ workflows:
- remote_tests_python
# - upload_docs_wheels:
# requires:
# - build_test_docs_wheel
# - build_doxygen
- remote_python_packaging:
requires:
- remote_tests_python
- remote_tests_rust
- upload_docs_wheels:
requires:
- remote_python_packaging
- build_doxygen
# - rustfmt:
# requires:
# - cargo_fetch

View File

@@ -1,5 +1,27 @@
# Changelog
## 1.0.0-beta.23
- #1197 fix imap-deletion of messages
- #1171 Combine multiple MDNs into a single mail, reducing traffic
- #1155 fix to not send out gossip always, reducing traffic
- #1160 fix reply-to-encrypted determination
- #1182 Add "Auto-Submitted: auto-replied" header to MDNs
- #1194 produce python wheels again, fix c/py.delta.chat
master-deployment
- rust-level housekeeping and improvements #1161 #1186 #1185 #1190 #1194 #1199 #1191 #1190 #1184 and more
- #1063 clarify licensing
- #1147 use mailparse 0.10.2
## 1.0.0-beta.22
- #1095 normalize email lineends to CRLF

245
Cargo.lock generated
View File

@@ -84,7 +84,7 @@ version = "1.1.1"
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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -179,9 +179,9 @@ name = "async-trait"
version = "0.1.21"
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)",
"proc-macro2 1.0.8 (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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -528,14 +528,6 @@ dependencies = [
"scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-queue"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-utils"
version = "0.6.6"
@@ -561,7 +553,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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -592,10 +584,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -605,7 +597,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"darling_core 0.10.2 (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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -628,7 +620,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.0.0-beta.22"
version = "1.0.0-beta.23"
dependencies = [
"async-imap 0.1.1 (git+https://github.com/async-email/async-imap?branch=dcc-stable)",
"async-native-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -641,14 +633,14 @@ dependencies = [
"charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
"debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"deltachat_derive 0.1.0",
"deltachat_derive 1.0.0-beta.22",
"email 0.0.21 (git+https://github.com/deltachat/rust-email)",
"encoded-words 0.1.0 (git+https://github.com/async-email/encoded-words)",
"escaper 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"image 0.22.3 (registry+https://github.com/rust-lang/crates.io-index)",
"image 0.22.4 (registry+https://github.com/rust-lang/crates.io-index)",
"image-meta 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -666,11 +658,11 @@ dependencies = [
"proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"quick-xml 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)",
"r2d2 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)",
"r2d2_sqlite 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"r2d2_sqlite 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"reqwest 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rusqlite 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rusqlite 0.21.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.104 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -697,17 +689,17 @@ dependencies = [
[[package]]
name = "deltachat_derive"
version = "0.1.0"
version = "1.0.0-beta.22"
dependencies = [
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.14.9 (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.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "deltachat_ffi"
version = "1.0.0-beta.22"
version = "1.0.0-beta.23"
dependencies = [
"deltachat 1.0.0-beta.22",
"deltachat 1.0.0-beta.23",
"deltachat-provider-database 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"human-panic 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -723,9 +715,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"derive_builder_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.8 (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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -734,9 +726,9 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.8 (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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -940,9 +932,9 @@ name = "failure_derive"
version = "0.1.6"
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)",
"proc-macro2 1.0.8 (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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
"synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -1080,9 +1072,9 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.8 (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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1329,7 +1321,7 @@ dependencies = [
[[package]]
name = "image"
version = "0.22.3"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1339,8 +1331,6 @@ dependencies = [
"num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"png 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)",
"scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"tiff 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1404,7 +1394,6 @@ version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1493,7 +1482,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libsqlite3-sys"
version = "0.16.0"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1728,24 +1717,14 @@ dependencies = [
"zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-derive"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-derive"
version = "0.3.0"
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)",
"proc-macro2 1.0.8 (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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1978,9 +1957,9 @@ name = "pin-project-internal"
version = "0.4.6"
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)",
"proc-macro2 1.0.8 (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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2040,9 +2019,9 @@ name = "proc-macro-hack"
version = "0.5.11"
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)",
"proc-macro2 1.0.8 (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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2052,15 +2031,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "proc-macro2"
version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro2"
version = "1.0.6"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2111,20 +2082,12 @@ name = "quote"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "quote"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "1.0.2"
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)",
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2144,11 +2107,11 @@ dependencies = [
[[package]]
name = "r2d2_sqlite"
version = "0.12.0"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"r2d2 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)",
"rusqlite 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rusqlite 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2297,28 +2260,6 @@ dependencies = [
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rayon"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon-core 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rayon-core"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rdrand"
version = "0.4.0"
@@ -2381,9 +2322,9 @@ name = "rental-impl"
version = "0.5.5"
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)",
"proc-macro2 1.0.8 (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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2450,13 +2391,13 @@ dependencies = [
[[package]]
name = "rusqlite"
version = "0.20.0"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"fallible-iterator 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"fallible-streaming-iterator 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libsqlite3-sys 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libsqlite3-sys 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2556,11 +2497,6 @@ dependencies = [
"parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "scoped_threadpool"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "scopeguard"
version = "1.0.0"
@@ -2612,9 +2548,9 @@ name = "serde_derive"
version = "1.0.104"
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)",
"proc-macro2 1.0.8 (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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2729,9 +2665,9 @@ name = "snafu-derive"
version = "0.6.0"
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)",
"proc-macro2 1.0.8 (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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2787,9 +2723,9 @@ version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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)",
"proc-macro2 1.0.8 (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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2809,30 +2745,10 @@ dependencies = [
[[package]]
name = "syn"
version = "0.14.9"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "0.15.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "1.0.11"
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)",
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -2850,9 +2766,9 @@ name = "synstructure"
version = "0.12.3"
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)",
"proc-macro2 1.0.8 (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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -2907,9 +2823,9 @@ name = "thiserror-impl"
version = "1.0.9"
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)",
"proc-macro2 1.0.8 (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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2928,17 +2844,6 @@ dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tiff"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "time"
version = "0.1.42"
@@ -3072,11 +2977,6 @@ name = "unicode-xid"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.2.0"
@@ -3193,9 +3093,9 @@ dependencies = [
"bumpalo 3.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.8 (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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-shared 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -3224,9 +3124,9 @@ name = "wasm-bindgen-macro-support"
version = "0.2.58"
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)",
"proc-macro2 1.0.8 (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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-backend 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-shared 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -3244,9 +3144,9 @@ dependencies = [
"anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
"heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.8 (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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-backend 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
"weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -3381,9 +3281,9 @@ name = "zeroize_derive"
version = "1.0.0"
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)",
"proc-macro2 1.0.8 (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.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
"synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -3451,7 +3351,6 @@ dependencies = [
"checksum crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c"
"checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca"
"checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac"
"checksum crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dfd6515864a82d2f877b42813d4553292c6659498c9a2aa31bab5a15243c2700"
"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6"
"checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4"
"checksum ctor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8ce37ad4184ab2ce004c33bf6379185d3b1c95801cab51026bd271bf68eedc"
@@ -3533,7 +3432,7 @@ dependencies = [
"checksum hyper-tls 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ab58a31960b2f78c5c24cf255216789863754438a1e48849a956846f899e762e"
"checksum ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
"checksum image 0.22.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4be8aaefbe7545dc42ae925afb55a0098f226a3fe5ef721872806f44f57826"
"checksum image 0.22.4 (registry+https://github.com/rust-lang/crates.io-index)" = "53cb19c4e35102e5c6fb9ade5e0e236c5588424dc171a849af3141bf0b47768a"
"checksum image-meta 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b00861cbbb254a627d8acc0cec786b484297d896ab8f20fdc8e28536a3e918ef"
"checksum imap-proto 0.9.1 (git+https://github.com/djc/tokio-imap)" = "<none>"
"checksum indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712d7b3ea5827fcb9d4fda14bf4da5f136f0db2ae9c8f4bd4e2d1c6fde4e6db2"
@@ -3552,7 +3451,7 @@ dependencies = [
"checksum lexical-core 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2304bccb228c4b020f3a4835d247df0a02a7c4686098d4167762cfbbe4c5cb14"
"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
"checksum libm 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
"checksum libsqlite3-sys 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5e5b95e89c330291768dc840238db7f9e204fd208511ab6319b56193a7f2ae25"
"checksum libsqlite3-sys 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)" = "266eb8c361198e8d1f682bc974e5d9e2ae90049fb1943890904d11dad7d4a77d"
"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
"checksum lock_api 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e57b3997725d2b60dbec1297f6c2e2957cc383db1cebd6be812163f969c7d586"
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
@@ -3578,7 +3477,6 @@ dependencies = [
"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
"checksum nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c618b63422da4401283884e6668d39f819a106ef51f5f59b81add00075da35ca"
"checksum num-bigint-dig 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3d03c330f9f7a2c19e3c0b42698e48141d0809c78cd9b6219f85bd7d7e892aa"
"checksum num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "eafd0b45c5537c3ba526f79d3e75120036502bebacbb3f3220914067ce39dbf2"
"checksum num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0c8b15b261814f992e33760b1fca9fe8b693d8a65299f20c9901688636cfb746"
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
"checksum num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "76bd5272412d173d6bf9afdf98db8612bbabc9a7a830b7bfc9c188911716132e"
@@ -3611,18 +3509,16 @@ dependencies = [
"checksum pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "717ee476b1690853d222af4634056d830b5197ffd747726a9a1eee6da9f49074"
"checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5"
"checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e"
"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27"
"checksum proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548"
"checksum proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cf147e022eacf0c8a054ab864914a7602618adba841d800a9a9868a5237a529f"
"checksum pulldown-cmark 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eef52fac62d0ea7b9b4dc7da092aa64ea7ec3d90af6679422d3d7e0e14b6ee15"
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
"checksum quick-xml 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fe1e430bdcf30c9fdc25053b9c459bb1a4672af4617b6c783d7d91dc17c6bbb0"
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
"checksum quoted_printable 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "86cedf331228892e747bb85beb130b6bb23fc628c40dde9ea01eb6becea3c798"
"checksum r2d2 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)" = "22b5c5fc5fba064373f03887337b412e0e1562d63023393db77251146cb75553"
"checksum r2d2_sqlite 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "806e268035ce9e5a604bf617ac8a073ef28b59ef2e48e8338db0baf530caef33"
"checksum r2d2_sqlite 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b061f5b16692bbe81eeb260f92e6fc7d13aea455c4cbe67f5c4aa20aa92d1d9e"
"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
"checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412"
@@ -3638,8 +3534,6 @@ dependencies = [
"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
"checksum rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "43739f8831493b276363637423d3622d4bd6394ab6f0a9c4a552e208aeb7fddd"
"checksum rayon-core 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8bf17de6f23b05473c437eb958b9c850bfc8af0961fe17b4cc92d5a627b4791"
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
"checksum redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d"
@@ -3651,7 +3545,7 @@ dependencies = [
"checksum reqwest 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "03c6cbd2bc1c1cb7052dbe30f4a70cf65811967c800f2dfbb2e6036dc9ee2553"
"checksum ripemd160 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad5112e0dbbb87577bfbc56c42450235e3012ce336e29c5befd7807bd626da4a"
"checksum rsa 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6ed8692d8e0ea3baae03f0f32ecfc13a6c6f1f85fcd6d9fdefcdf364e70f4df9"
"checksum rusqlite 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2a194373ef527035645a1bc21b10dc2125f73497e6e155771233eb187aedd051"
"checksum rusqlite 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "64a656821bb6317a84b257737b7934f79c0dbb7eb694710475908280ebad3e64"
"checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf"
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
@@ -3663,7 +3557,6 @@ dependencies = [
"checksum sanitize-filename 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23fd0fec94ec480abfd86bb8f4f6c57e0efb36dac5c852add176ea7b04c74801"
"checksum schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "87f550b06b6cba9c8b8be3ee73f391990116bf527450d2556e9b9ce263b9a021"
"checksum scheduled-thread-pool 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f5de7bc31f28f8e6c28df5e1bf3d10610f5fdc14cc95f272853512c70a2bd779"
"checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"
"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d"
"checksum security-framework 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8ef2429d7cefe5fd28bd1d2ed41c944547d4ff84776f5935b456da44593a16df"
"checksum security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e31493fc37615debb8c5090a7aeb4a9730bc61e77ab10b9af59f1a202284f895"
@@ -3694,9 +3587,7 @@ dependencies = [
"checksum strum_macros 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0054a7df764039a6cd8592b9de84be4bec368ff081d203a7d5371cbfa8e65c81"
"checksum subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941"
"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.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238"
"checksum syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4ff033220a41d1a57d8125eab57bf5263783dfdcc18688b1dacc6ce9651ef8"
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
@@ -3707,7 +3598,6 @@ dependencies = [
"checksum thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2e25d25307eb8436894f727aba8f65d07adf02e5b35a13cebed48bd282bfef"
"checksum thread-local-object 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7da3caa820d0308c84c8654f6cafd81cc3195d45433311cbe22fcf44fc8be071"
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
"checksum tiff 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d7b7c2cfc4742bd8a32f2e614339dd8ce30dbcf676bb262bd63a2327bc5df57d"
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
"checksum tokio 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "a9d5acfe1b1130d50ac2286a2f1f8cf49309680366ceb7609ce369b75c9058d4"
"checksum tokio-tls 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7bde02a3a5291395f59b06ec6945a3077602fac2b07eeeaf0dee2122f3619828"
@@ -3725,7 +3615,6 @@ dependencies = [
"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
"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"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum unsafe-any 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f"
"checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61"

View File

@@ -1,9 +1,9 @@
[package]
name = "deltachat"
version = "1.0.0-beta.22"
version = "1.0.0-beta.23"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
license = "MPL"
license = "MPL-2.0"
[profile.release]
lto = true
@@ -17,7 +17,7 @@ hex = "0.4.0"
sha2 = "0.8.0"
rand = "0.7.0"
smallvec = "1.0.0"
reqwest = "0.10.0"
reqwest = { version = "0.10.0", features = ["blocking", "json"] }
num-derive = "0.3.0"
num-traits = "0.2.6"
async-smtp = { git = "https://github.com/async-email/async-smtp" }
@@ -42,8 +42,8 @@ indexmap = "1.3.0"
rustyline = "4.1.0"
lazy_static = "1.4.0"
regex = "1.1.6"
rusqlite = { version = "0.20", features = ["bundled"] }
r2d2_sqlite = "0.12.0"
rusqlite = { version = "0.21", features = ["bundled"] }
r2d2_sqlite = "0.13.0"
r2d2 = "0.8.5"
strum = "0.16.0"
strum_macros = "0.16.0"
@@ -61,7 +61,7 @@ stop-token = { version = "0.1.1", features = ["unstable"] }
mailparse = "0.10.2"
encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" }
native-tls = "0.2.3"
image = "0.22.3"
image = { version = "0.22.4", default-features=false, features = ["gif_codec", "jpeg", "ico", "png_codec", "pnm", "webp", "bmp"] }
[dev-dependencies]
tempfile = "3.0"
@@ -85,7 +85,7 @@ path = "examples/repl/main.rs"
[features]
default = ["nightly", "ringbuf", "reqwest/blocking", "reqwest/json"]
default = ["nightly", "ringbuf"]
vendored = ["native-tls/vendored", "reqwest/native-tls-vendored"]
nightly = ["pgp/nightly"]
ringbuf = ["pgp/ringbuf"]

View File

@@ -2,9 +2,6 @@ The files in this directory and under its subdirectories
are (c) 2019 by Bjoern Petersen and contributors and released under the
Mozilla Public License Version 2.0, see below for a copy.
NOTE that the files in the "libs" directory are copyrighted by third parties
and come with their own respective licenses.
Mozilla Public License Version 2.0
==================================

View File

@@ -14,24 +14,22 @@ DOXYDOCDIR=${3:?directory where doxygen docs to be found}
export BRANCH=${CIRCLE_BRANCH:?specify branch for uploading purposes}
# DISABLED: python docs to py.delta.chat
#ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@py.delta.chat mkdir -p build/${BRANCH}
#rsync -avz \
# -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
# "$PYDOCDIR/html/" \
# delta@py.delta.chat:build/${BRANCH}
# python docs to py.delta.chat
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@py.delta.chat mkdir -p build/${BRANCH}
rsync -avz \
--delete \
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
"$PYDOCDIR/html/" \
delta@py.delta.chat:build/${BRANCH}
# C docs to c.delta.chat
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@c.delta.chat mkdir -p build-c/${BRANCH}
rsync -avz \
--delete \
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
"$DOXYDOCDIR/html/" \
delta@c.delta.chat:build-c/${BRANCH}
exit 0
# OUTDATED -- for re-use from python release-scripts
echo -----------------------
echo upload wheels
echo -----------------------
@@ -39,6 +37,7 @@ echo -----------------------
# Bundle external shared libraries into the wheels
pushd $WHEELHOUSEDIR
pip3 install -U pip
pip3 install devpi-client
devpi use https://m.devpi.net
devpi login dc --password $DEVPI_LOGIN
@@ -50,6 +49,9 @@ devpi use dc/$N_BRANCH || {
devpi use dc/$N_BRANCH
}
devpi index $N_BRANCH bases=/root/pypi
devpi upload deltachat*.whl
devpi upload deltachat*
popd
# remove devpi non-master dc indices if thy are too old
python ci_scripts/cleanup_devpi_indices.py

View File

@@ -0,0 +1,69 @@
"""
Remove old "dc" indices except for master which always stays.
"""
from requests import Session
import datetime
import sys
import subprocess
MAXDAYS=7
session = Session()
session.headers["Accept"] = "application/json"
def get_indexes(baseurl, username):
response = session.get(baseurl + username)
assert response.status_code == 200
result = response.json()["result"]
return result["indexes"]
def get_projectnames(baseurl, username, indexname):
response = session.get(baseurl + username + "/" + indexname)
assert response.status_code == 200
result = response.json()["result"]
return result["projects"]
def get_release_dates(baseurl, username, indexname, projectname):
response = session.get(baseurl + username + "/" + indexname + "/" + projectname)
assert response.status_code == 200
result = response.json()["result"]
dates = set()
for value in result.values():
if "+links" not in value:
continue
for link in value["+links"]:
for log in link["log"]:
dates.add(tuple(log["when"]))
return dates
def run():
baseurl = "https://m.devpi.net/"
username = "dc"
for indexname in get_indexes(baseurl, username):
projectnames = get_projectnames(baseurl, username, indexname)
if indexname == "master" or not indexname:
continue
assert projectnames == ["deltachat"]
for projectname in projectnames:
dates = get_release_dates(baseurl, username, indexname, projectname)
if not dates:
print(
"%s has no releases" % (baseurl + username + "/" + indexname),
file=sys.stderr)
date = datetime.datetime.now()
else:
date = datetime.datetime(*max(dates))
if (datetime.datetime.now() - date) > datetime.timedelta(days=MAXDAYS):
assert username and indexname
url = baseurl + username + "/" + indexname
subprocess.check_call(["devpi", "index", "-y", "--delete", url])
if __name__ == '__main__':
run()

View File

@@ -5,14 +5,11 @@ RUN echo /usr/local/lib64 > /etc/ld.so.conf.d/local.conf && \
echo /usr/local/lib >> /etc/ld.so.conf.d/local.conf
ENV PKG_CONFIG_PATH /usr/local/lib64/pkgconfig:/usr/local/lib/pkgconfig
# Install a recent Perl, needed to install OpenSSL
# Install a recent Perl, needed to install the openssl crate
ADD deps/build_perl.sh /builder/build_perl.sh
RUN rm /usr/bin/perl
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_perl.sh && cd .. && rm -r tmp1
# Install OpenSSL
ADD deps/build_openssl.sh /builder/build_openssl.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_openssl.sh && cd .. && rm -r tmp1
ENV PIP_DISABLE_PIP_VERSION_CHECK 1
# Install python tools (auditwheels,tox, ...)

View File

@@ -3,6 +3,9 @@
set -e -x
# Install Rust
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2019-09-12 -y
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2019-11-06 -y
export PATH=/root/.cargo/bin:$PATH
rustc --version
# remove some 300-400 MB that we don't need for automated builds
rm -rf /root/.rustup/toolchains/nightly-2019-11-06-x86_64-unknown-linux-gnu/share/

View File

@@ -1,49 +0,0 @@
#!/bin/bash
#
# Build the Delta Chat C/Rust library
#
set -e -x
# perform clean build of core and install
export TOXWORKDIR=.docker-tox
# build core library
cargo build --release -p deltachat_ffi
# configure access to a base python and
# to several python interpreters needed by tox below
export PATH=$PATH:/opt/python/cp35-cp35m/bin
export PYTHONDONTWRITEBYTECODE=1
pushd /bin
ln -s /opt/python/cp27-cp27m/bin/python2.7
ln -s /opt/python/cp36-cp36m/bin/python3.6
ln -s /opt/python/cp37-cp37m/bin/python3.7
popd
#
# run python tests
#
if [ -n "$TESTS" ]; then
echo ----------------
echo run python tests
echo ----------------
pushd python
# first run all tests ...
rm -rf tests/__pycache__
rm -rf src/deltachat/__pycache__
export PYTHONDONTWRITEBYTECODE=1
tox --workdir "$TOXWORKDIR" -e py27,py35,py36,py37
popd
fi
if [ -n "$DOCS" ]; then
echo -----------------------
echo generating python docs
echo -----------------------
(cd python && tox --workdir "$TOXWORKDIR" -e doc)
fi

View File

@@ -0,0 +1,51 @@
#!/bin/bash
export BRANCH=${CIRCLE_BRANCH:?branch to build}
export REPONAME=${CIRCLE_PROJECT_REPONAME:?repository name}
export SSHTARGET=${SSHTARGET-ci@b1.delta.chat}
# we construct the BUILDDIR such that we can easily share the
# CARGO_TARGET_DIR between runs ("..")
export BUILDDIR=ci_builds/$REPONAME/$BRANCH/${CIRCLE_JOB:?jobname}/${CIRCLE_BUILD_NUM:?circle-build-number}
echo "--- Copying files to $SSHTARGET:$BUILDDIR"
set -xe
ssh -oBatchMode=yes -oStrictHostKeyChecking=no $SSHTARGET mkdir -p "$BUILDDIR"
git ls-files >.rsynclist
# we seem to need .git for setuptools_scm versioning
find .git >>.rsynclist
rsync --delete --files-from=.rsynclist -az ./ "$SSHTARGET:$BUILDDIR"
set +x
# we have to create a remote file for the remote-docker run
# so we can do a simple ssh command with a TTY
# so that when our job dies, all container-runs are aborted.
# sidenote: the circle-ci machinery will kill ongoing jobs
# if there are new commits and we want to ensure that
# everything is terminated/cleaned up and we have no orphaned
# useless still-running docker-containers consuming resources.
ssh $SSHTARGET bash -c "cat >$BUILDDIR/exec_docker_run" <<_HERE
set +x -e
cd $BUILDDIR
export DCC_PY_LIVECONFIG=$DCC_PY_LIVECONFIG
set -x
# run everything else inside docker
docker run -e DCC_PY_LIVECONFIG \
--rm -it -v \$(pwd):/mnt -w /mnt \
deltachat/coredeps ci_scripts/run_all.sh
_HERE
echo "--- Running $CIRCLE_JOB remotely"
ssh -t $SSHTARGET bash "$BUILDDIR/exec_docker_run"
mkdir -p workspace
rsync -avz "$SSHTARGET:$BUILDDIR/python/.docker-tox/wheelhouse" workspace/
rsync -avz "$SSHTARGET:$BUILDDIR/python/.docker-tox/dist/*" workspace/wheelhouse/
rsync -avz "$SSHTARGET:$BUILDDIR/python/doc/_build/" workspace/py-docs

View File

@@ -3,5 +3,4 @@
set -ex
cd deltachat-ffi
doxygen
PROJECT_NUMBER=$(git log -1 --format "%h (%cd)") doxygen

48
ci_scripts/run_all.sh Executable file
View File

@@ -0,0 +1,48 @@
#!/bin/bash
#
# Build the Delta Chat Core Rust library, Python wheels and docs
set -e -x
# Perform clean build of core and install.
export TOXWORKDIR=.docker-tox
# compile core lib
export PATH=/root/.cargo/bin:$PATH
cargo build --release -p deltachat_ffi
# cargo test --all --all-features
# Statically link against libdeltachat.a.
export DCC_RS_DEV=$(pwd)
# Configure access to a base python and to several python interpreters
# needed by tox below.
export PATH=$PATH:/opt/python/cp35-cp35m/bin
export PYTHONDONTWRITEBYTECODE=1
pushd /bin
ln -s /opt/python/cp27-cp27m/bin/python2.7
ln -s /opt/python/cp36-cp36m/bin/python3.6
ln -s /opt/python/cp37-cp37m/bin/python3.7
ln -s /opt/python/cp38-cp38/bin/python3.8
popd
pushd python
# prepare a clean tox run
rm -rf tests/__pycache__
rm -rf src/deltachat/__pycache__
mkdir -p $TOXWORKDIR
# disable live-account testing to speed up test runs and wheel building
# XXX we may switch on some live-tests on for better ensurances
# Note that the independent remote_tests_python step does all kinds of
# live-testing already.
unset DCC_PY_LIVECONFIG
tox --workdir "$TOXWORKDIR" -e py35,py36,py37,py38,auditwheels
popd
echo -----------------------
echo generating python docs
echo -----------------------
(cd python && tox --workdir "$TOXWORKDIR" -e doc)

View File

@@ -1,11 +1,11 @@
[package]
name = "deltachat_ffi"
version = "1.0.0-beta.22"
version = "1.0.0-beta.23"
description = "Deltachat FFI"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
readme = "README.md"
license = "MIT OR Apache-2.0"
license = "MPL-2.0"
keywords = ["deltachat", "chat", "openpgp", "email", "encryption"]
categories = ["cryptography", "std", "email"]

View File

@@ -2989,15 +2989,15 @@ int dc_msg_get_viewtype (const dc_msg_t* msg);
* Get the state of a message.
*
* Incoming message states:
* - DC_STATE_IN_FRESH (10) - Incoming _fresh_ message. Fresh messages are not noticed nor seen and are typically shown in notifications. Use dc_get_fresh_msgs() to get all fresh messages.
* - DC_STATE_IN_NOTICED (13) - Incoming _noticed_ message. Eg. chat opened but message not yet read - noticed messages are not counted as unread but did not marked as read nor resulted in MDNs. Use dc_marknoticed_chat() or dc_marknoticed_contact() to mark messages as being noticed.
* - DC_STATE_IN_SEEN (16) - Incoming message, really _seen_ by the user. Marked as read on IMAP and MDN may be send. Use dc_markseen_msgs() to mark messages as being seen.
* - DC_STATE_IN_FRESH (10) - Incoming _fresh_ message. Fresh messages are neither noticed nor seen and are typically shown in notifications. Use dc_get_fresh_msgs() to get all fresh messages.
* - DC_STATE_IN_NOTICED (13) - Incoming _noticed_ message. E.g. chat opened but message not yet read - noticed messages are not counted as unread but did not marked as read nor resulted in MDNs. Use dc_marknoticed_chat() or dc_marknoticed_contact() to mark messages as being noticed.
* - DC_STATE_IN_SEEN (16) - Incoming message, really _seen_ by the user. Marked as read on IMAP and MDN may be sent. Use dc_markseen_msgs() to mark messages as being seen.
*
* Outgoing message states:
* - DC_STATE_OUT_PREPARING (18) - For files which need time to be prepared before they can be sent,
* the message enters this state before DC_STATE_OUT_PENDING.
* - DC_STATE_OUT_DRAFT (19) - Message saved as draft using dc_set_draft()
* - DC_STATE_OUT_PENDING (20) - The user has send the "send" button but the
* - DC_STATE_OUT_PENDING (20) - The user has pressed the "send" button but the
* message is not yet sent and is pending in some way. Maybe we're offline (no checkmark).
* - DC_STATE_OUT_FAILED (24) - _Unrecoverable_ error (_recoverable_ errors result in pending messages), you'll receive the event #DC_EVENT_MSG_FAILED.
* - DC_STATE_OUT_DELIVERED (26) - Outgoing message successfully delivered to server (one checkmark). Note, that already delivered messages may get into the state DC_STATE_OUT_FAILED if we get such a hint from the server.

View File

@@ -24,6 +24,7 @@ use std::sync::RwLock;
use libc::uintptr_t;
use num_traits::{FromPrimitive, ToPrimitive};
use deltachat::chat::ChatId;
use deltachat::constants::DC_MSG_ID_LAST_SPECIAL;
use deltachat::contact::Contact;
use deltachat::context::Context;
@@ -151,12 +152,12 @@ impl ContextWrapper {
ffi_cb(
self,
event_id,
chat_id as uintptr_t,
chat_id.to_u32() as uintptr_t,
msg_id.to_u32() as uintptr_t,
);
}
Event::ChatModified(chat_id) => {
ffi_cb(self, event_id, chat_id as uintptr_t, 0);
ffi_cb(self, event_id, chat_id.to_u32() as uintptr_t, 0);
}
Event::ContactsChanged(id) | Event::LocationChanged(id) => {
let id = id.unwrap_or_default();
@@ -191,7 +192,7 @@ impl ContextWrapper {
ffi_cb(
self,
event_id,
chat_id as uintptr_t,
chat_id.to_u32() as uintptr_t,
contact_id as uintptr_t,
);
}
@@ -707,7 +708,9 @@ pub unsafe extern "C" fn dc_create_chat_by_msg_id(context: *mut dc_context_t, ms
ffi_context
.with_inner(|ctx| {
chat::create_by_msg_id(ctx, MsgId::new(msg_id))
.unwrap_or_log_default(ctx, "Failed to create chat")
.log_err(ctx, "Failed to create chat from msg_id")
.map(|id| id.to_u32())
.unwrap_or(0)
})
.unwrap_or(0)
}
@@ -725,7 +728,9 @@ pub unsafe extern "C" fn dc_create_chat_by_contact_id(
ffi_context
.with_inner(|ctx| {
chat::create_by_contact_id(ctx, contact_id)
.unwrap_or_log_default(ctx, "Failed to create chat")
.log_err(ctx, "Failed to create chat from contact_id")
.map(|id| id.to_u32())
.unwrap_or(0)
})
.unwrap_or(0)
}
@@ -741,7 +746,12 @@ pub unsafe extern "C" fn dc_get_chat_id_by_contact_id(
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| chat::get_by_contact_id(ctx, contact_id).unwrap_or(0))
.with_inner(|ctx| {
chat::get_by_contact_id(ctx, contact_id)
.log_err(ctx, "Failed to get chat for contact_id")
.map(|id| id.to_u32())
.unwrap_or(0)
})
.unwrap_or(0)
}
@@ -759,7 +769,7 @@ pub unsafe extern "C" fn dc_prepare_msg(
let ffi_msg: &mut MessageWrapper = &mut *msg;
ffi_context
.with_inner(|ctx| {
chat::prepare_msg(ctx, chat_id, &mut ffi_msg.message)
chat::prepare_msg(ctx, ChatId::new(chat_id), &mut ffi_msg.message)
.unwrap_or_log_default(ctx, "Failed to prepare message")
})
.map(|msg_id| msg_id.to_u32())
@@ -780,7 +790,7 @@ pub unsafe extern "C" fn dc_send_msg(
let ffi_msg = &mut *msg;
ffi_context
.with_inner(|ctx| {
chat::send_msg(ctx, chat_id, &mut ffi_msg.message)
chat::send_msg(ctx, ChatId::new(chat_id), &mut ffi_msg.message)
.unwrap_or_log_default(ctx, "Failed to send message")
})
.map(|msg_id| msg_id.to_u32())
@@ -801,7 +811,7 @@ pub unsafe extern "C" fn dc_send_text_msg(
let text_to_send = to_string_lossy(text_to_send);
ffi_context
.with_inner(|ctx| {
chat::send_text_msg(ctx, chat_id, text_to_send)
chat::send_text_msg(ctx, ChatId::new(chat_id), text_to_send)
.map(|msg_id| msg_id.to_u32())
.unwrap_or_log_default(ctx, "Failed to send text message")
})
@@ -826,7 +836,7 @@ pub unsafe extern "C" fn dc_set_draft(
Some(&mut ffi_msg.message)
};
ffi_context
.with_inner(|ctx| chat::set_draft(ctx, chat_id, msg))
.with_inner(|ctx| chat::set_draft(ctx, ChatId::new(chat_id), msg))
.unwrap_or(())
}
@@ -901,7 +911,7 @@ pub unsafe extern "C" fn dc_get_draft(context: *mut dc_context_t, chat_id: u32)
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| match chat::get_draft(ctx, chat_id) {
.with_inner(|ctx| match chat::get_draft(ctx, ChatId::new(chat_id)) {
Ok(Some(draft)) => {
let ffi_msg = MessageWrapper {
context,
@@ -938,7 +948,7 @@ pub unsafe extern "C" fn dc_get_chat_msgs(
ffi_context
.with_inner(|ctx| {
let arr = dc_array_t::from(
chat::get_chat_msgs(ctx, chat_id, flags, marker_flag)
chat::get_chat_msgs(ctx, ChatId::new(chat_id), flags, marker_flag)
.iter()
.map(|msg_id| msg_id.to_u32())
.collect::<Vec<u32>>(),
@@ -956,7 +966,7 @@ pub unsafe extern "C" fn dc_get_msg_cnt(context: *mut dc_context_t, chat_id: u32
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| chat::get_msg_cnt(ctx, chat_id) as libc::c_int)
.with_inner(|ctx| chat::get_msg_cnt(ctx, ChatId::new(chat_id)) as libc::c_int)
.unwrap_or(0)
}
@@ -971,7 +981,7 @@ pub unsafe extern "C" fn dc_get_fresh_msg_cnt(
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| chat::get_fresh_msg_cnt(ctx, chat_id) as libc::c_int)
.with_inner(|ctx| chat::get_fresh_msg_cnt(ctx, ChatId::new(chat_id)) as libc::c_int)
.unwrap_or(0)
}
@@ -1006,7 +1016,9 @@ pub unsafe extern "C" fn dc_marknoticed_chat(context: *mut dc_context_t, chat_id
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
chat::marknoticed_chat(ctx, chat_id).log_err(ctx, "Failed marknoticed chat")
chat::marknoticed_chat(ctx, ChatId::new(chat_id))
.log_err(ctx, "Failed marknoticed chat")
.unwrap_or(())
})
.unwrap_or(())
}
@@ -1020,7 +1032,9 @@ pub unsafe extern "C" fn dc_marknoticed_all_chats(context: *mut dc_context_t) {
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
chat::marknoticed_all_chats(ctx).log_err(ctx, "Failed marknoticed all chats")
chat::marknoticed_all_chats(ctx)
.log_err(ctx, "Failed marknoticed all chats")
.unwrap_or(())
})
.unwrap_or(())
}
@@ -1054,10 +1068,16 @@ pub unsafe extern "C" fn dc_get_chat_media(
ffi_context
.with_inner(|ctx| {
let arr = dc_array_t::from(
chat::get_chat_media(ctx, chat_id, msg_type, or_msg_type2, or_msg_type3)
.iter()
.map(|msg_id| msg_id.to_u32())
.collect::<Vec<u32>>(),
chat::get_chat_media(
ctx,
ChatId::new(chat_id),
msg_type,
or_msg_type2,
or_msg_type3,
)
.iter()
.map(|msg_id| msg_id.to_u32())
.collect::<Vec<u32>>(),
);
Box::into_raw(Box::new(arr))
})
@@ -1124,7 +1144,11 @@ pub unsafe extern "C" fn dc_archive_chat(
return;
};
ffi_context
.with_inner(|ctx| chat::archive(ctx, chat_id, archive).log_err(ctx, "Failed archive chat"))
.with_inner(|ctx| {
chat::archive(ctx, ChatId::new(chat_id), archive)
.log_err(ctx, "Failed archive chat")
.unwrap_or(())
})
.unwrap_or(())
}
@@ -1136,7 +1160,11 @@ pub unsafe extern "C" fn dc_delete_chat(context: *mut dc_context_t, chat_id: u32
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| chat::delete(ctx, chat_id).log_err(ctx, "Failed chat delete"))
.with_inner(|ctx| {
chat::delete(ctx, ChatId::new(chat_id))
.log_err(ctx, "Failed chat delete")
.unwrap_or(())
})
.unwrap_or(())
}
@@ -1152,7 +1180,7 @@ pub unsafe extern "C" fn dc_get_chat_contacts(
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
let arr = dc_array_t::from(chat::get_chat_contacts(ctx, chat_id));
let arr = dc_array_t::from(chat::get_chat_contacts(ctx, ChatId::new(chat_id)));
Box::into_raw(Box::new(arr))
})
.unwrap_or_else(|_| ptr::null_mut())
@@ -1172,7 +1200,7 @@ pub unsafe extern "C" fn dc_search_msgs(
ffi_context
.with_inner(|ctx| {
let arr = dc_array_t::from(
ctx.search_msgs(chat_id, to_string_lossy(query))
ctx.search_msgs(ChatId::new(chat_id), to_string_lossy(query))
.iter()
.map(|msg_id| msg_id.to_u32())
.collect::<Vec<u32>>(),
@@ -1190,13 +1218,15 @@ pub unsafe extern "C" fn dc_get_chat(context: *mut dc_context_t, chat_id: u32) -
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| match chat::Chat::load_from_db(ctx, chat_id) {
Ok(chat) => {
let ffi_chat = ChatWrapper { context, chat };
Box::into_raw(Box::new(ffi_chat))
}
Err(_) => ptr::null_mut(),
})
.with_inner(
|ctx| match chat::Chat::load_from_db(ctx, ChatId::new(chat_id)) {
Ok(chat) => {
let ffi_chat = ChatWrapper { context, chat };
Box::into_raw(Box::new(ffi_chat))
}
Err(_) => ptr::null_mut(),
},
)
.unwrap_or_else(|_| ptr::null_mut())
}
@@ -1219,7 +1249,9 @@ pub unsafe extern "C" fn dc_create_group_chat(
ffi_context
.with_inner(|ctx| {
chat::create_group_chat(ctx, verified, to_string_lossy(name))
.unwrap_or_log_default(ctx, "Failed to create group chat")
.log_err(ctx, "Failed to create group chat")
.map(|id| id.to_u32())
.unwrap_or(0)
})
.unwrap_or(0)
}
@@ -1236,7 +1268,7 @@ pub unsafe extern "C" fn dc_is_contact_in_chat(
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| chat::is_contact_in_chat(ctx, chat_id, contact_id))
.with_inner(|ctx| chat::is_contact_in_chat(ctx, ChatId::new(chat_id), contact_id))
.unwrap_or_default()
.into()
}
@@ -1253,7 +1285,9 @@ pub unsafe extern "C" fn dc_add_contact_to_chat(
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| chat::add_contact_to_chat(ctx, chat_id, contact_id) as libc::c_int)
.with_inner(|ctx| {
chat::add_contact_to_chat(ctx, ChatId::new(chat_id), contact_id) as libc::c_int
})
.unwrap_or(0)
}
@@ -1270,7 +1304,7 @@ pub unsafe extern "C" fn dc_remove_contact_from_chat(
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
chat::remove_contact_from_chat(ctx, chat_id, contact_id)
chat::remove_contact_from_chat(ctx, ChatId::new(chat_id), contact_id)
.map(|_| 1)
.unwrap_or_log_default(ctx, "Failed to remove contact")
})
@@ -1290,7 +1324,7 @@ pub unsafe extern "C" fn dc_set_chat_name(
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
chat::set_chat_name(ctx, chat_id, to_string_lossy(name))
chat::set_chat_name(ctx, ChatId::new(chat_id), to_string_lossy(name))
.map(|_| 1)
.unwrap_or_log_default(ctx, "Failed to set chat name")
})
@@ -1310,7 +1344,7 @@ pub unsafe extern "C" fn dc_set_chat_profile_image(
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
chat::set_chat_profile_image(ctx, chat_id, to_string_lossy(image))
chat::set_chat_profile_image(ctx, ChatId::new(chat_id), to_string_lossy(image))
.map(|_| 1)
.unwrap_or_log_default(ctx, "Failed to set profile image")
})
@@ -1399,7 +1433,7 @@ pub unsafe extern "C" fn dc_forward_msgs(
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
chat::forward_msgs(ctx, &msg_ids[..], chat_id)
chat::forward_msgs(ctx, &msg_ids[..], ChatId::new(chat_id))
.unwrap_or_log_default(ctx, "Failed to forward message")
})
.unwrap_or_default()
@@ -1812,7 +1846,7 @@ pub unsafe extern "C" fn dc_get_securejoin_qr(
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
securejoin::dc_get_securejoin_qr(ctx, chat_id)
securejoin::dc_get_securejoin_qr(ctx, ChatId::new(chat_id))
.unwrap_or_else(|| "".to_string())
.strdup()
})
@@ -1830,7 +1864,7 @@ pub unsafe extern "C" fn dc_join_securejoin(
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| securejoin::dc_join_securejoin(ctx, &to_string_lossy(qr)))
.with_inner(|ctx| securejoin::dc_join_securejoin(ctx, &to_string_lossy(qr)).to_u32())
.unwrap_or(0)
}
@@ -1846,7 +1880,9 @@ pub unsafe extern "C" fn dc_send_locations_to_chat(
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| location::send_locations_to_chat(ctx, chat_id, seconds as i64))
.with_inner(|ctx| {
location::send_locations_to_chat(ctx, ChatId::new(chat_id), seconds as i64)
})
.ok();
}
@@ -1861,7 +1897,9 @@ pub unsafe extern "C" fn dc_is_sending_locations_to_chat(
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| location::is_sending_locations_to_chat(ctx, chat_id) as libc::c_int)
.with_inner(|ctx| {
location::is_sending_locations_to_chat(ctx, ChatId::new(chat_id)) as libc::c_int
})
.unwrap_or(0)
}
@@ -1899,7 +1937,7 @@ pub unsafe extern "C" fn dc_get_locations(
.with_inner(|ctx| {
let res = location::get_range(
ctx,
chat_id,
ChatId::new(chat_id),
contact_id,
timestamp_begin as i64,
timestamp_end as i64,
@@ -2021,8 +2059,7 @@ pub unsafe extern "C" fn dc_array_get_chat_id(
eprintln!("ignoring careless call to dc_array_get_chat_id()");
return 0;
}
(*array).get_location(index).chat_id
(*array).get_location(index).chat_id.to_u32()
}
#[no_mangle]
pub unsafe extern "C" fn dc_array_get_contact_id(
@@ -2159,7 +2196,7 @@ pub unsafe extern "C" fn dc_chatlist_get_chat_id(
return 0;
}
let ffi_list = &*chatlist;
ffi_list.list.get_chat_id(index as usize)
ffi_list.list.get_chat_id(index as usize).to_u32()
}
#[no_mangle]
@@ -2251,7 +2288,7 @@ pub unsafe extern "C" fn dc_chat_get_id(chat: *mut dc_chat_t) -> u32 {
return 0;
}
let ffi_chat = &*chat;
ffi_chat.chat.get_id()
ffi_chat.chat.get_id().to_u32()
}
#[no_mangle]
@@ -2398,7 +2435,7 @@ pub unsafe extern "C" fn dc_chat_get_info_json(
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
let chat = match chat::Chat::load_from_db(ctx, chat_id) {
let chat = match chat::Chat::load_from_db(ctx, ChatId::new(chat_id)) {
Ok(chat) => chat,
Err(err) => {
error!(ctx, "dc_get_chat_info_json() failed to load chat: {}", err);
@@ -2494,7 +2531,7 @@ pub unsafe extern "C" fn dc_msg_get_chat_id(msg: *mut dc_msg_t) -> u32 {
return 0;
}
let ffi_msg = &*msg;
ffi_msg.message.get_chat_id()
ffi_msg.message.get_chat_id().to_u32()
}
#[no_mangle]
@@ -3119,12 +3156,12 @@ pub unsafe extern "C" fn dc_str_unref(s: *mut libc::c_char) {
pub mod providers;
pub trait ResultExt<T> {
pub trait ResultExt<T, E> {
fn unwrap_or_log_default(self, context: &context::Context, message: &str) -> T;
fn log_err(&self, context: &context::Context, message: &str);
fn log_err(self, context: &context::Context, message: &str) -> Result<T, E>;
}
impl<T: Default, E: std::fmt::Display> ResultExt<T> for Result<T, E> {
impl<T: Default, E: std::fmt::Display> ResultExt<T, E> for Result<T, E> {
fn unwrap_or_log_default(self, context: &context::Context, message: &str) -> T {
match self {
Ok(t) => t,
@@ -3135,10 +3172,11 @@ impl<T: Default, E: std::fmt::Display> ResultExt<T> for Result<T, E> {
}
}
fn log_err(&self, context: &context::Context, message: &str) {
if let Err(err) = self {
error!(context, "{}: {}", message, err);
}
fn log_err(self, context: &context::Context, message: &str) -> Result<T, E> {
self.map_err(|err| {
warn!(context, "{}: {}", message, err);
err
})
}
}

View File

@@ -1,12 +1,13 @@
[package]
name = "deltachat_derive"
version = "0.1.0"
authors = ["Dmitry Bogatov <KAction@debian.org>"]
version = "1.0.0-beta.22"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
license = "MPL-2.0"
[lib]
proc-macro = true
[dependencies]
syn = "0.14.4"
quote = "0.6.3"
syn = "1.0.13"
quote = "1.0.2"

View File

@@ -1,7 +1,7 @@
use std::path::Path;
use std::str::FromStr;
use deltachat::chat::{self, Chat};
use deltachat::chat::{self, Chat, ChatId};
use deltachat::chatlist::*;
use deltachat::config;
use deltachat::constants::*;
@@ -84,7 +84,7 @@ fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
}
context.call_cb(Event::MsgsChanged {
chat_id: 0,
chat_id: ChatId::new(0),
msg_id: MsgId::new(0),
});
@@ -166,7 +166,7 @@ fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int {
println!("Import: {} items read from \"{}\".", read_cnt, &real_spec);
if read_cnt > 0 {
context.call_cb(Event::MsgsChanged {
chat_id: 0,
chat_id: ChatId::new(0),
msg_id: MsgId::new(0),
});
}
@@ -299,7 +299,7 @@ fn chat_prefix(chat: &Chat) -> &'static str {
pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
let chat_id = *context.cmdline_sel_chat_id.read().unwrap();
let mut sel_chat = if chat_id > 0 {
let mut sel_chat = if !chat_id.is_unset() {
Chat::load_from_db(context, chat_id).ok()
} else {
None
@@ -549,7 +549,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
);
}
}
if location::is_sending_locations_to_chat(context, 0) {
if location::is_sending_locations_to_chat(context, ChatId::new(0)) {
println!("Location streaming enabled.");
}
println!("{} chats", cnt);
@@ -559,8 +559,8 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
bail!("Argument [chat-id] is missing.");
}
if !arg1.is_empty() {
let chat_id = arg1.parse()?;
println!("Selecting chat #{}", chat_id);
let chat_id = ChatId::new(arg1.parse()?);
println!("Selecting chat {}", chat_id);
sel_chat = Some(Chat::load_from_db(context, chat_id)?);
*context.cmdline_sel_chat_id.write().unwrap() = chat_id;
}
@@ -787,7 +787,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
let chat = if let Some(ref sel_chat) = sel_chat {
sel_chat.get_id()
} else {
0 as libc::c_uint
ChatId::new(0)
};
let msglist = context.search_msgs(chat, arg1);
@@ -846,12 +846,12 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
}
"archive" | "unarchive" => {
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
let chat_id = arg1.parse()?;
let chat_id = ChatId::new(arg1.parse()?);
chat::archive(context, chat_id, arg0 == "archive")?;
}
"delchat" => {
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
let chat_id = arg1.parse()?;
let chat_id = ChatId::new(arg1.parse()?);
chat::delete(context, chat_id)?;
}
"msginfo" => {
@@ -873,7 +873,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
);
let mut msg_ids = [MsgId::new(0); 1];
let chat_id = arg2.parse()?;
let chat_id = ChatId::new(arg2.parse()?);
msg_ids[0] = MsgId::new(arg1.parse()?);
chat::forward_msgs(context, &msg_ids, chat_id)?;
}

View File

@@ -20,6 +20,7 @@ use std::process::Command;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use deltachat::chat::ChatId;
use deltachat::config;
use deltachat::configure::*;
use deltachat::context::*;
@@ -484,9 +485,10 @@ fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult, failu
}
"getqr" | "getbadqr" => {
start_threads(ctx.clone());
if let Some(mut qr) =
dc_get_securejoin_qr(&ctx.read().unwrap(), arg1.parse().unwrap_or_default())
{
if let Some(mut qr) = dc_get_securejoin_qr(
&ctx.read().unwrap(),
ChatId::new(arg1.parse().unwrap_or_default()),
) {
if !qr.is_empty() {
if arg0 == "getbadqr" && qr.len() > 40 {
qr.replace_range(12..22, "0000000000")

View File

@@ -75,42 +75,31 @@ With ``DCC_PY_LIVECONFIG`` set pytest invocations will use real
e-mail accounts and run through all functional "liveconfig" tests.
============================================================================================================================
(21-Dec-2019) THE BELOW WHEELS ARE CURRENTLY NOT WORKING/BROKEN, COMPILE FROM SOURCE USING ABOVE INSTRUCTIONS INSTEAD
============================================================================================================================
Installing pre-built packages (linux-only) (OUTDATED)
Installing pre-built packages (Linux-only)
========================================================
If you have a linux system you may install the ``deltachat`` binary "wheel" package
If you have a Linux system you may try to install the ``deltachat`` binary "wheel" packages
without any "build-from-source" steps.
1. `Install virtualenv <https://virtualenv.pypa.io/en/stable/installation/>`_,
First of all we suggest to `Install virtualenv <https://virtualenv.pypa.io/en/stable/installation/>`_,
then create a fresh python environment and activate it in your shell::
virtualenv venv # or: python -m venv
source venv/bin/activate
Afterwards, invoking ``python`` or ``pip install`` will only
modify files in your ``venv`` directory and leave your system installation
alone.
2. Install the wheel for linux::
pip install deltachat
Verify it worked by typing::
python -c "import deltachat"
Installing a wheel from a PR/branch (OUTDATED)
-------------------------------------------------
Afterwards, invoking ``python`` or ``pip install`` will only
modify files in your ``venv`` directory and leave your system installation
alone.
For Linux, we automatically build wheels for all github PR branches
and push them to a python package index. To install the latest github ``master`` branch::
and push them to a python package index. To install the latest
github ``master`` branch::
pip install -i https://m.devpi.net/dc/master deltachat
pip install --pre -i https://m.devpi.net/dc/master deltachat
To verify it worked::
python -c "import deltachat"
.. note::
@@ -119,10 +108,6 @@ and push them to a python package index. To install the latest github ``master``
`in contact with us <https://delta.chat/en/contribute>`_.
Code examples
=============
@@ -133,15 +118,11 @@ You may look at `examples <https://py.delta.chat/examples.html>`_.
.. _`deltachat-core`: https://github.com/deltachat/deltachat-core-rust
Building manylinux1 wheels
==========================
.. note::
This section may not fully work.
Building manylinux1 based wheels
================================
Building portable manylinux1 wheels which come with libdeltachat.so
and all it's dependencies is easy using the provided docker tooling.
can be done with docker-tooling.
using docker pull / premade images
------------------------------------
@@ -154,9 +135,9 @@ organization::
This docker image can be used to run tests and build Python wheels for all interpreters::
$ bash ci_scripts/ci_run.sh
This command runs tests and build-wheel scripts in a docker container.
$ docker run -e DCC_PY_LIVECONFIG \
--rm -it -v \$(pwd):/mnt -w /mnt \
deltachat/coredeps ci_scripts/run_all.sh
Optionally build your own docker image

View File

@@ -678,14 +678,26 @@ class TestOnlineAccount:
chat2b.mark_noticed()
assert chat2b.count_fresh_messages() == 0
lp.sec("mark message as seen on ac2, wait for changes on ac1")
ac2.mark_seen_messages([msg_in])
ac2._evlogger.consume_events()
lp.sec("sending a second message from ac1 to ac2")
msg_out2 = chat.send_text("message2")
lp.sec("wait for ac2 to receive second message")
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
assert ev[2] == msg_out2.id
msg_in2 = ac2.get_message_by_id(msg_out2.id)
lp.sec("mark messages as seen on ac2, wait for changes on ac1")
ac2.mark_seen_messages([msg_in, msg_in2])
lp.step("1")
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_READ")
assert ev[1] > const.DC_CHAT_ID_LAST_SPECIAL
assert ev[2] > const.DC_MSG_ID_LAST_SPECIAL
for i in range(2):
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_READ")
assert ev[1] > const.DC_CHAT_ID_LAST_SPECIAL
assert ev[2] > const.DC_MSG_ID_LAST_SPECIAL
lp.step("2")
assert msg_out.is_out_mdn_received()
assert msg_out2.is_out_mdn_received()
lp.sec("check that a second call to mark_seen does not create change or smtp job")
ac2._evlogger.consume_events()
@@ -829,7 +841,21 @@ class TestOnlineAccount:
print("ac1: e2ee_enabled={}".format(ac1.get_config("e2ee_enabled")))
print("ac2: e2ee_enabled={}".format(ac2.get_config("e2ee_enabled")))
ac1.set_config("e2ee_enabled", "0")
chat.send_text("message2 -- should be encrypted")
# Set unprepared and unencrypted draft to test that it is not
# taken into account when determining whether last message is
# encrypted.
msg_draft = Message.new_empty(ac1, "text")
msg_draft.set_text("message2 -- should be encrypted")
chat.set_draft(msg_draft)
# Get the draft, prepare and send it.
msg_draft = chat.get_draft()
msg_out = chat.prepare_message(msg_draft)
chat.send_prepared(msg_out)
chat.set_draft(None)
assert chat.get_draft() is None
lp.sec("wait for ac2 to receive message")
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
@@ -1177,29 +1203,46 @@ class TestOnlineAccount:
class TestGroupStressTests:
def test_group_many_members_add_leave_remove(self, acfactory, lp):
lp.sec("creating and configuring five accounts")
ac1 = acfactory.get_online_configuring_account()
accounts = [acfactory.get_online_configuring_account() for i in range(3)]
wait_configuration_progress(ac1, 1000)
accounts = [acfactory.get_online_configuring_account() for i in range(5)]
for acc in accounts:
wait_configuration_progress(acc, 1000)
ac1 = accounts.pop()
lp.sec("ac1: setting up contacts with 4 other members")
contacts = []
for acc, name in zip(accounts, list("äöüsr")):
contact = ac1.create_contact(acc.get_config("addr"), name=name)
contacts.append(contact)
# make sure we accept the "hi" message
ac1.create_chat_by_contact(contact)
# make sure the other side accepts our messages
c1 = acc.create_contact(ac1.get_config("addr"), "ä member")
chat1 = acc.create_chat_by_contact(c1)
# send a message to get the contact key via autocrypt header
chat1.send_text("hi")
msg = ac1.wait_next_incoming_message()
assert msg.text == "hi"
# Save fifth account for later
ac5 = accounts.pop()
contact5 = contacts.pop()
lp.sec("ac1: creating group chat with 3 other members")
chat = ac1.create_group_chat("title1")
contacts = []
chars = list("äöüsr")
for acc in accounts:
contact = ac1.create_contact(acc.get_config("addr"), name=chars.pop())
contacts.append(contact)
for contact in contacts:
chat.add_contact(contact)
# make sure the other side accepts our messages
c1 = acc.create_contact(ac1.get_config("addr"), "ä member")
acc.create_chat_by_contact(c1)
assert not chat.is_promoted()
lp.sec("ac1: send mesage to new group chat")
chat.send_text("hello")
msg = chat.send_text("hello")
assert chat.is_promoted()
assert msg.is_encrypted()
gossiped_timestamp = chat.get_summary()["gossiped_timestamp"]
assert gossiped_timestamp > 0
num_contacts = len(chat.get_contacts())
assert num_contacts == 3 + 1
@@ -1215,19 +1258,45 @@ class TestGroupStressTests:
ac3 = accounts[1]
msg3 = ac3.wait_next_incoming_message()
assert msg3.text == "hello"
contacts = ac3.get_contacts()
assert len(contacts) == 3
ac3_contacts = ac3.get_contacts()
assert len(ac3_contacts) == 3
ac4_contacts = ac3.get_contacts(query=accounts[2].get_config("addr"))
assert len(ac4_contacts) == 1
lp.sec("ac1: removing one contacts and checking things are right")
to_remove = msg.chat.get_contacts()[-1]
lp.sec("ac2: removing one contact")
to_remove = contacts[-1]
msg.chat.remove_contact(to_remove)
lp.sec("ac1: receiving system message about contact removal")
sysmsg = ac1.wait_next_incoming_message()
assert to_remove.addr in sysmsg.text
assert len(sysmsg.chat.get_contacts()) == 3
# Receiving message about removed contact does not reset gossip
assert chat.get_summary()["gossiped_timestamp"] == gossiped_timestamp
lp.sec("ac1: sending another message to the chat")
chat.send_text("hello2")
msg = ac2.wait_next_incoming_message()
assert msg.text == "hello2"
assert chat.get_summary()["gossiped_timestamp"] == gossiped_timestamp
lp.sec("ac1: adding fifth member to the chat")
chat.add_contact(contact5)
# Additng contact to chat resets gossiped_timestamp
assert chat.get_summary()["gossiped_timestamp"] >= gossiped_timestamp
lp.sec("ac2: receiving system message about contact addition")
sysmsg = ac2.wait_next_incoming_message()
assert contact5.addr in sysmsg.text
assert len(sysmsg.chat.get_contacts()) == 4
lp.sec("ac5: waiting for message about addition to the chat")
sysmsg = ac5.wait_next_incoming_message()
msg = sysmsg.chat.send_text("hello!")
# Message should be encrypted because keys of other members are gossiped
assert msg.is_encrypted()
class TestOnlineConfigureFails:
def test_invalid_password(self, acfactory):

View File

@@ -8,7 +8,7 @@ envlist =
[testenv]
commands =
pytest -n6 --reruns 2 --reruns-delay 5 -v -rsXx {posargs:tests}
# python tests/package_wheels.py {toxworkdir}/wheelhouse
python tests/package_wheels.py {toxworkdir}/wheelhouse
passenv =
TRAVIS
DCC_RS_DEV

View File

@@ -153,8 +153,8 @@ The message-id MUST have the format `Gr.<group-id>.<unique data>`.
Hello group - this group contains three members
Messengers adding the member list in the form `Name <email-address>`
MUST take care only to spread the names authorized by the contacts themselves.
Otherwise, names as _Daddy_ or _Honey_ may be spread
MUST take care only to distribute the names authorized by the contacts themselves.
Otherwise, names as _Daddy_ or _Honey_ may be distributed
(this issue is also true for normal MUAs, however,
for more contact- and chat-centralized apps
such situations happen more frequently).
@@ -288,7 +288,7 @@ to add a `Chat-Group-Avatar` only on image changes.
# Set profile image
A user MAY have a profile-image that MAY be spread to their contacts.
A user MAY have a profile-image that MAY be distributed to their contacts.
To change or set the profile-image,
the messenger MUST attach an image file to a message
and MUST add the header `Chat-User-Avatar`
@@ -297,7 +297,7 @@ with the value set to the image name.
To remove the profile-image,
the messenger MUST add the header `Chat-User-Avatar: 0`.
To spread the image,
To distribute the image,
the messenger MAY send the profile image
together with the next mail to a given contact
(to do this only once,

File diff suppressed because it is too large Load Diff

View File

@@ -36,7 +36,7 @@ use crate::stock::StockMessage;
#[derive(Debug)]
pub struct Chatlist {
/// Stores pairs of `chat_id, message_id`
ids: Vec<(u32, MsgId)>,
ids: Vec<(ChatId, MsgId)>,
}
impl Chatlist {
@@ -91,7 +91,7 @@ impl Chatlist {
let mut add_archived_link_item = false;
let process_row = |row: &rusqlite::Row| {
let chat_id: u32 = row.get(0)?;
let chat_id: ChatId = row.get(0)?;
let msg_id: MsgId = row.get(1).unwrap_or_default();
Ok((chat_id, msg_id))
};
@@ -211,7 +211,10 @@ impl Chatlist {
)?;
if 0 == listflags & DC_GCL_NO_SPECIALS {
if let Some(last_deaddrop_fresh_msg_id) = get_last_deaddrop_fresh_msg(context) {
ids.insert(0, (DC_CHAT_ID_DEADDROP, last_deaddrop_fresh_msg_id));
ids.insert(
0,
(ChatId::new(DC_CHAT_ID_DEADDROP), last_deaddrop_fresh_msg_id),
);
}
add_archived_link_item = true;
}
@@ -220,9 +223,9 @@ impl Chatlist {
if add_archived_link_item && dc_get_archived_cnt(context) > 0 {
if ids.is_empty() && 0 != listflags & DC_GCL_ADD_ALLDONE_HINT {
ids.push((DC_CHAT_ID_ALLDONE_HINT, MsgId::new(0)));
ids.push((ChatId::new(DC_CHAT_ID_ALLDONE_HINT), MsgId::new(0)));
}
ids.push((DC_CHAT_ID_ARCHIVED_LINK, MsgId::new(0)));
ids.push((ChatId::new(DC_CHAT_ID_ARCHIVED_LINK), MsgId::new(0)));
}
Ok(Chatlist { ids })
@@ -241,9 +244,9 @@ impl Chatlist {
/// Get a single chat ID of a chatlist.
///
/// To get the message object from the message ID, use dc_get_chat().
pub fn get_chat_id(&self, index: usize) -> u32 {
pub fn get_chat_id(&self, index: usize) -> ChatId {
if index >= self.ids.len() {
return 0;
return ChatId::new(0);
}
self.ids[index].0
}
@@ -307,7 +310,7 @@ impl Chatlist {
None
};
if chat.id == DC_CHAT_ID_ARCHIVED_LINK {
if chat.id.is_archived_link() {
ret.text2 = None;
} else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED
{

View File

@@ -190,7 +190,9 @@ mod tests {
use std::str::FromStr;
use std::string::ToString;
use crate::constants::AVATAR_SIZE;
use crate::test_utils::*;
use image::GenericImageView;
use std::fs::File;
use std::io::Write;
@@ -212,32 +214,55 @@ mod tests {
}
#[test]
fn test_selfavatar_outside_blobdir() -> failure::Fallible<()> {
fn test_selfavatar_outside_blobdir() {
let t = dummy_context();
let avatar_src = t.dir.path().join("avatar.jpg");
let avatar_bytes = include_bytes!("../test-data/image/avatar1000x1000.jpg");
File::create(&avatar_src)?.write_all(avatar_bytes)?;
File::create(&avatar_src)
.unwrap()
.write_all(avatar_bytes)
.unwrap();
let avatar_blob = t.ctx.get_blobdir().join("avatar.jpg");
assert!(!avatar_blob.exists());
t.ctx
.set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap()))?;
.set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap()))
.unwrap();
assert!(avatar_blob.exists());
assert!(std::fs::metadata(&avatar_blob).unwrap().len() < avatar_bytes.len() as u64);
let avatar_cfg = t.ctx.get_config(Config::Selfavatar);
assert_eq!(avatar_cfg, avatar_blob.to_str().map(|s| s.to_string()));
Ok(())
let img = image::open(avatar_src).unwrap();
assert_eq!(img.width(), 1000);
assert_eq!(img.height(), 1000);
let img = image::open(avatar_blob).unwrap();
assert_eq!(img.width(), AVATAR_SIZE);
assert_eq!(img.height(), AVATAR_SIZE);
}
#[test]
fn test_selfavatar_in_blobdir() -> failure::Fallible<()> {
fn test_selfavatar_in_blobdir() {
let t = dummy_context();
let avatar_src = t.ctx.get_blobdir().join("avatar.jpg");
let avatar_bytes = include_bytes!("../test-data/image/avatar1000x1000.jpg");
File::create(&avatar_src)?.write_all(avatar_bytes)?;
let avatar_src = t.ctx.get_blobdir().join("avatar.png");
let avatar_bytes = include_bytes!("../test-data/image/avatar900x900.png");
File::create(&avatar_src)
.unwrap()
.write_all(avatar_bytes)
.unwrap();
let img = image::open(&avatar_src).unwrap();
assert_eq!(img.width(), 900);
assert_eq!(img.height(), 900);
t.ctx
.set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap()))?;
.set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap()))
.unwrap();
let avatar_cfg = t.ctx.get_config(Config::Selfavatar);
assert_eq!(avatar_cfg, avatar_src.to_str().map(|s| s.to_string()));
Ok(())
let img = image::open(avatar_src).unwrap();
assert_eq!(img.width(), AVATAR_SIZE);
assert_eq!(img.height(), AVATAR_SIZE);
}
}

View File

@@ -49,7 +49,7 @@ pub fn dc_is_configured(context: &Context) -> bool {
/*******************************************************************************
* Configure JOB
******************************************************************************/
#[allow(non_snake_case, unused_must_use)]
#[allow(non_snake_case, unused_must_use, clippy::cognitive_complexity)]
pub fn JobConfigureImap(context: &Context) -> job::Status {
if !context.sql.is_open() {
error!(context, "Cannot configure, database not opened.",);

View File

@@ -49,6 +49,8 @@ pub const DC_HANDSHAKE_CONTINUE_NORMAL_PROCESSING: i32 = 0x01;
pub const DC_HANDSHAKE_STOP_NORMAL_PROCESSING: i32 = 0x02;
pub const DC_HANDSHAKE_ADD_DELETE_JOB: i32 = 0x04;
pub(crate) const DC_FROM_HANDSHAKE: i32 = 0x01;
pub const DC_GCL_ARCHIVED_ONLY: usize = 0x01;
pub const DC_GCL_NO_SPECIALS: usize = 0x02;
pub const DC_GCL_ADD_ALLDONE_HINT: usize = 0x04;

View File

@@ -7,6 +7,7 @@ use itertools::Itertools;
use rusqlite;
use crate::aheader::EncryptPreference;
use crate::chat::ChatId;
use crate::config::Config;
use crate::constants::*;
use crate::context::Context;
@@ -262,7 +263,7 @@ impl Contact {
.is_ok()
{
context.call_cb(Event::MsgsChanged {
chat_id: 0,
chat_id: ChatId::new(0),
msg_id: MsgId::new(0),
});
}

View File

@@ -50,7 +50,7 @@ pub struct Context {
#[debug_stub = "Callback"]
cb: Box<ContextCallback>,
pub os_name: Option<String>,
pub cmdline_sel_chat_id: Arc<RwLock<u32>>,
pub cmdline_sel_chat_id: Arc<RwLock<ChatId>>,
pub bob: Arc<RwLock<BobStatus>>,
pub last_smeared_timestamp: RwLock<i64>,
pub running_state: Arc<RwLock<RunningState>>,
@@ -75,10 +75,6 @@ pub fn get_info() -> HashMap<&'static str, String> {
let mut res = HashMap::new();
res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR));
res.insert("sqlite_version", rusqlite::version().to_string());
res.insert(
"sqlite_thread_safe",
unsafe { rusqlite::ffi::sqlite3_threadsafe() }.to_string(),
);
res.insert("arch", (std::mem::size_of::<usize>() * 8).to_string());
res.insert("level", "awesome".into());
res
@@ -120,7 +116,7 @@ impl Context {
oauth2_critical: Arc::new(Mutex::new(())),
bob: Arc::new(RwLock::new(Default::default())),
last_smeared_timestamp: RwLock::new(0),
cmdline_sel_chat_id: Arc::new(RwLock::new(0)),
cmdline_sel_chat_id: Arc::new(RwLock::new(ChatId::new(0))),
inbox_thread: Arc::new(RwLock::new(JobThread::new(
"INBOX",
"configured_inbox_folder",
@@ -349,7 +345,7 @@ impl Context {
}
#[allow(non_snake_case)]
pub fn search_msgs(&self, chat_id: u32, query: impl AsRef<str>) -> Vec<MsgId> {
pub fn search_msgs(&self, chat_id: ChatId, query: impl AsRef<str>) -> Vec<MsgId> {
let real_query = query.as_ref().trim();
if real_query.is_empty() {
return Vec::new();
@@ -357,7 +353,7 @@ impl Context {
let strLikeInText = format!("%{}%", real_query);
let strLikeBeg = format!("{}%", real_query);
let query = if 0 != chat_id {
let query = if !chat_id.is_unset() {
concat!(
"SELECT m.id AS id, m.timestamp AS timestamp",
" FROM msgs m",
@@ -389,7 +385,7 @@ impl Context {
self.sql
.query_map(
query,
params![chat_id as i32, &strLikeInText, &strLikeBeg],
params![chat_id, &strLikeInText, &strLikeBeg],
|row| row.get::<_, MsgId>("id"),
|rows| {
let mut ret = Vec::new();

View File

@@ -3,7 +3,7 @@ use sha2::{Digest, Sha256};
use num_traits::FromPrimitive;
use crate::chat::{self, Chat};
use crate::chat::{self, Chat, ChatId};
use crate::config::Config;
use crate::constants::*;
use crate::contact::*;
@@ -17,7 +17,7 @@ use crate::message::{self, MessageState, MessengerMessage, MsgId};
use crate::mimeparser::*;
use crate::param::*;
use crate::peerstate::*;
use crate::securejoin::handle_securejoin_handshake;
use crate::securejoin::{self, handle_securejoin_handshake};
use crate::sql;
use crate::stock::StockMessage;
use crate::{contact, location};
@@ -61,7 +61,7 @@ pub fn dc_receive_imf(
ensure!(mime_parser.has_headers(), "No Headers Found");
// the function returns the number of created messages in the database
let mut chat_id = 0;
let mut chat_id = ChatId::new(0);
let mut hidden = false;
let mut needs_delete_job = false;
@@ -74,17 +74,17 @@ pub fn dc_receive_imf(
// helper method to handle early exit and memory cleanup
let cleanup = |context: &Context,
create_event_to_send: &Option<CreateEvent>,
created_db_entries: &Vec<(usize, MsgId)>| {
created_db_entries: &Vec<(ChatId, MsgId)>| {
if let Some(create_event_to_send) = create_event_to_send {
for (chat_id, msg_id) in created_db_entries {
let event = match create_event_to_send {
CreateEvent::MsgsChanged => Event::MsgsChanged {
msg_id: *msg_id,
chat_id: *chat_id as u32,
chat_id: *chat_id,
},
CreateEvent::IncomingMsg => Event::IncomingMsg {
msg_id: *msg_id,
chat_id: *chat_id as u32,
chat_id: *chat_id,
},
};
context.call_cb(event);
@@ -249,6 +249,7 @@ pub fn dc_receive_imf(
Ok(())
}
#[allow(clippy::too_many_arguments, clippy::cognitive_complexity)]
fn add_parts(
context: &Context,
mut mime_parser: &mut MimeMessage,
@@ -263,11 +264,11 @@ fn add_parts(
from_id: u32,
from_id_blocked: bool,
hidden: &mut bool,
chat_id: &mut u32,
chat_id: &mut ChatId,
flags: u32,
needs_delete_job: &mut bool,
insert_msg_id: &mut MsgId,
created_db_entries: &mut Vec<(usize, MsgId)>,
created_db_entries: &mut Vec<(ChatId, MsgId)>,
create_event_to_send: &mut Option<CreateEvent>,
) -> Result<()> {
let mut state: MessageState;
@@ -308,7 +309,7 @@ fn add_parts(
{
// this message is a classic email not a chat-message nor a reply to one
if show_emails == ShowEmails::Off {
*chat_id = DC_CHAT_ID_TRASH;
*chat_id = ChatId::new(DC_CHAT_ID_TRASH);
allow_creation = false
} else if show_emails == ShowEmails::AcceptedContacts {
allow_creation = false
@@ -328,36 +329,33 @@ fn add_parts(
MessageState::InFresh
};
to_id = DC_CONTACT_ID_SELF;
let mut needs_stop_ongoing_process = false;
// handshake messages must be processed _before_ chats are created
// (eg. contacs may be marked as verified)
if mime_parser.get(HeaderDef::SecureJoin).is_some() {
// avoid discarding by show_emails setting
msgrmsg = MessengerMessage::Yes;
*chat_id = 0;
*chat_id = ChatId::new(0);
allow_creation = true;
match handle_securejoin_handshake(context, mime_parser, from_id) {
Ok(ret) => {
if ret.hide_this_msg {
*hidden = true;
*needs_delete_job = ret.delete_this_msg;
state = MessageState::InSeen;
}
if let Some(status) = ret.bob_securejoin_success {
context.bob.write().unwrap().status = status as i32;
}
needs_stop_ongoing_process = ret.stop_ongoing_process;
Ok(securejoin::HandshakeMessage::Done) => {
*hidden = true;
*needs_delete_job = true;
state = MessageState::InSeen;
}
Ok(securejoin::HandshakeMessage::Ignore) => {
*hidden = true;
state = MessageState::InSeen;
}
Ok(securejoin::HandshakeMessage::Propagate) => {
// Message will still be processed as "member
// added" or similar system message.
}
Err(err) => {
// maybe this message belongs to an aborted scan,
// however, by the explicit header check we really know
// that it is a Secure-Join message that should be hidden in the chat view.
*hidden = true;
warn!(
context,
"Unexpected messaged passed to Secure-Join handshake protocol: {}", err
);
context.bob.write().unwrap().status = 0; // secure-join failed
context.stop_ongoing();
error!(context, "Error in Secure-Join message handling: {}", err);
}
}
}
@@ -367,12 +365,12 @@ fn add_parts(
// get the chat_id - a chat_id here is no indicator that the chat is displayed in the normal list,
// it might also be blocked and displayed in the deaddrop as a result
if *chat_id == 0 {
if chat_id.is_unset() {
// try to create a group
// (groups appear automatically only if the _sender_ is known, see core issue #54)
let create_blocked =
if 0 != test_normal_chat_id && test_normal_chat_id_blocked == Blocked::Not {
if !test_normal_chat_id.is_unset() && test_normal_chat_id_blocked == Blocked::Not {
Blocked::Not
} else {
Blocked::Deaddrop
@@ -388,21 +386,24 @@ fn add_parts(
)?;
*chat_id = new_chat_id;
chat_id_blocked = new_chat_id_blocked;
if *chat_id != 0 && chat_id_blocked != Blocked::Not && create_blocked == Blocked::Not {
if !chat_id.is_unset()
&& chat_id_blocked != Blocked::Not
&& create_blocked == Blocked::Not
{
chat::unblock(context, new_chat_id);
chat_id_blocked = Blocked::Not;
}
}
if *chat_id == 0 {
if chat_id.is_unset() {
// check if the message belongs to a mailing list
if mime_parser.is_mailinglist_message() {
*chat_id = DC_CHAT_ID_TRASH;
*chat_id = ChatId::new(DC_CHAT_ID_TRASH);
info!(context, "Message belongs to a mailing list and is ignored.",);
}
}
if *chat_id == 0 {
if chat_id.is_unset() {
// try to create a normal chat
let create_blocked = if from_id == to_id {
Blocked::Not
@@ -410,7 +411,7 @@ fn add_parts(
Blocked::Deaddrop
};
if 0 != test_normal_chat_id {
if !test_normal_chat_id.is_unset() {
*chat_id = test_normal_chat_id;
chat_id_blocked = test_normal_chat_id_blocked;
} else if allow_creation {
@@ -420,7 +421,7 @@ fn add_parts(
*chat_id = id;
chat_id_blocked = bl;
}
if 0 != *chat_id && Blocked::Not != chat_id_blocked {
if !chat_id.is_unset() && Blocked::Not != chat_id_blocked {
if Blocked::Not == create_blocked {
chat::unblock(context, *chat_id);
chat_id_blocked = Blocked::Not;
@@ -438,9 +439,9 @@ fn add_parts(
}
}
}
if *chat_id == 0 {
if chat_id.is_unset() {
// maybe from_id is null or sth. else is suspicious, move message to trash
*chat_id = DC_CHAT_ID_TRASH;
*chat_id = ChatId::new(DC_CHAT_ID_TRASH);
}
// if the chat_id is blocked,
@@ -454,13 +455,6 @@ fn add_parts(
{
state = MessageState::InNoticed;
}
if needs_stop_ongoing_process {
// The Secure-Join protocol finished and the group
// creation handling is done. Stopping the ongoing
// process will let dc_join_securejoin() return.
context.stop_ongoing();
}
} else {
// Outgoing
@@ -469,7 +463,7 @@ fn add_parts(
state = MessageState::OutDelivered;
to_id = to_ids.get_index(0).cloned().unwrap_or_default();
if !to_ids.is_empty() {
if *chat_id == 0 {
if chat_id.is_unset() {
let (new_chat_id, new_chat_id_blocked) = create_or_lookup_group(
context,
&mut mime_parser,
@@ -481,12 +475,12 @@ fn add_parts(
*chat_id = new_chat_id;
chat_id_blocked = new_chat_id_blocked;
// automatically unblock chat when the user sends a message
if *chat_id != 0 && chat_id_blocked != Blocked::Not {
if !chat_id.is_unset() && chat_id_blocked != Blocked::Not {
chat::unblock(context, new_chat_id);
chat_id_blocked = Blocked::Not;
}
}
if *chat_id == 0 && allow_creation {
if chat_id.is_unset() && allow_creation {
let create_blocked = if MessengerMessage::No != msgrmsg
&& !Contact::is_blocked_load(context, to_id)
{
@@ -499,7 +493,7 @@ fn add_parts(
*chat_id = id;
chat_id_blocked = bl;
if 0 != *chat_id
if !chat_id.is_unset()
&& Blocked::Not != chat_id_blocked
&& Blocked::Not == create_blocked
{
@@ -512,7 +506,7 @@ fn add_parts(
&& to_ids.len() == 1
&& to_ids.contains(&DC_CONTACT_ID_SELF);
if *chat_id == 0 && self_sent {
if chat_id.is_unset() && self_sent {
// from_id==to_id==DC_CONTACT_ID_SELF - this is a self-sent messages,
// maybe an Autocrypt Setup Messag
let (id, bl) =
@@ -521,13 +515,13 @@ fn add_parts(
*chat_id = id;
chat_id_blocked = bl;
if 0 != *chat_id && Blocked::Not != chat_id_blocked {
if !chat_id.is_unset() && Blocked::Not != chat_id_blocked {
chat::unblock(context, *chat_id);
chat_id_blocked = Blocked::Not;
}
}
if *chat_id == 0 {
*chat_id = DC_CHAT_ID_TRASH;
if chat_id.is_unset() {
*chat_id = ChatId::new(DC_CHAT_ID_TRASH);
}
}
// correct message_timestamp, it should not be used before,
@@ -598,7 +592,7 @@ fn add_parts(
rfc724_mid,
server_folder.as_ref(),
server_uid as i32,
*chat_id as i32,
*chat_id,
from_id as i32,
to_id as i32,
sort_timestamp,
@@ -626,7 +620,7 @@ fn add_parts(
let row_id =
sql::get_rowid_with_conn(context, conn, "msgs", "rfc724_mid", &rfc724_mid);
*insert_msg_id = MsgId::new(row_id);
created_db_entries.push((*chat_id as usize, *insert_msg_id));
created_db_entries.push((*chat_id, *insert_msg_id));
}
Ok(())
},
@@ -638,7 +632,7 @@ fn add_parts(
);
// check event to send
if *chat_id == DC_CHAT_ID_TRASH {
if chat_id.is_trash() {
*create_event_to_send = None;
} else if incoming && state == MessageState::InFresh {
if from_id_blocked {
@@ -656,12 +650,12 @@ fn add_parts(
fn save_locations(
context: &Context,
mime_parser: &MimeMessage,
chat_id: u32,
chat_id: ChatId,
from_id: u32,
insert_msg_id: MsgId,
hidden: bool,
) {
if chat_id <= DC_CHAT_ID_LAST_SPECIAL {
if chat_id.is_special() {
return;
}
let mut location_id_written = false;
@@ -707,9 +701,10 @@ fn save_locations(
}
}
#[allow(clippy::too_many_arguments)]
fn calc_timestamps(
context: &Context,
chat_id: u32,
chat_id: ChatId,
from_id: u32,
message_timestamp: i64,
is_fresh_msg: bool,
@@ -727,7 +722,7 @@ fn calc_timestamps(
let last_msg_time: Option<i64> = context.sql.query_get_value(
context,
"SELECT MAX(timestamp) FROM msgs WHERE chat_id=? and from_id!=? AND timestamp>=?",
params![chat_id as i32, from_id as i32, *sort_timestamp],
params![chat_id, from_id as i32, *sort_timestamp],
);
if let Some(last_msg_time) = last_msg_time {
if last_msg_time > 0 && *sort_timestamp <= last_msg_time {
@@ -751,7 +746,7 @@ fn calc_timestamps(
/// - create an ad-hoc group based on the recipient list
///
/// on success the function returns the found/created (chat_id, chat_blocked) tuple .
#[allow(non_snake_case)]
#[allow(non_snake_case, clippy::cognitive_complexity)]
fn create_or_lookup_group(
context: &Context,
mime_parser: &mut MimeMessage,
@@ -759,7 +754,7 @@ fn create_or_lookup_group(
create_blocked: Blocked,
from_id: u32,
to_ids: &ContactIds,
) -> Result<(u32, Blocked)> {
) -> Result<(ChatId, Blocked)> {
let mut chat_id_blocked = Blocked::Not;
let mut recreate_member_list = false;
let mut send_EVENT_CHAT_MODIFIED = false;
@@ -787,10 +782,10 @@ fn create_or_lookup_group(
}
if grpid.is_empty() {
if let Some(extracted_grpid) = extract_grpid(mime_parser, HeaderDef::InReplyTo) {
grpid = extracted_grpid;
grpid = extracted_grpid.to_string();
} else if let Some(extracted_grpid) = extract_grpid(mime_parser, HeaderDef::References)
{
grpid = extracted_grpid;
grpid = extracted_grpid.to_string();
} else {
return create_or_lookup_adhoc_group(
context,
@@ -830,14 +825,14 @@ fn create_or_lookup_group(
} else {
let field = mime_parser.get(HeaderDef::ChatGroupMemberAdded).cloned();
if let Some(optional_field) = field {
X_MrAddToGrp = Some(optional_field);
mime_parser.is_system_message = SystemMessage::MemberAddedToGroup;
better_msg = context.stock_system_msg(
StockMessage::MsgAddMember,
X_MrAddToGrp.as_ref().unwrap(),
&optional_field,
"",
from_id as u32,
)
);
X_MrAddToGrp = Some(optional_field);
} else {
let field = mime_parser.get(HeaderDef::ChatGroupNameChanged);
if let Some(field) = field {
@@ -877,8 +872,9 @@ fn create_or_lookup_group(
set_better_msg(mime_parser, &better_msg);
// check, if we have a chat with this group ID
let (mut chat_id, chat_id_verified, _blocked) = chat::get_chat_id_by_grpid(context, &grpid);
if chat_id != 0 {
let (mut chat_id, chat_id_verified, _blocked) = chat::get_chat_id_by_grpid(context, &grpid)
.unwrap_or((ChatId::new(0), false, Blocked::Not));
if !chat_id.is_error() {
if chat_id_verified {
if let Err(err) =
check_verified_properties(context, mime_parser, from_id as u32, to_ids)
@@ -906,7 +902,7 @@ fn create_or_lookup_group(
.get_config(Config::ConfiguredAddr)
.unwrap_or_default();
if chat_id == 0
if chat_id.is_error()
&& !mime_parser.is_mailinglist_message()
&& !grpid.is_empty()
&& grpname.is_some()
@@ -932,7 +928,7 @@ fn create_or_lookup_group(
if !allow_creation {
info!(context, "creating group forbidden by caller");
return Ok((0, Blocked::Not));
return Ok((ChatId::new(0), Blocked::Not));
}
chat_id = create_group_record(
@@ -947,9 +943,9 @@ fn create_or_lookup_group(
}
// again, check chat_id
if chat_id <= DC_CHAT_ID_LAST_SPECIAL {
if chat_id.is_special() {
return if group_explicitly_left {
Ok((DC_CHAT_ID_TRASH, chat_id_blocked))
Ok((ChatId::new(DC_CHAT_ID_TRASH), chat_id_blocked))
} else {
create_or_lookup_adhoc_group(
context,
@@ -989,7 +985,7 @@ fn create_or_lookup_group(
context,
&context.sql,
"UPDATE chats SET name=? WHERE id=?;",
params![grpname, chat_id as i32],
params![grpname, chat_id],
)
.is_ok()
{
@@ -1027,7 +1023,7 @@ fn create_or_lookup_group(
context,
&context.sql,
"DELETE FROM chats_contacts WHERE chat_id=?;",
params![chat_id as i32],
params![chat_id],
)
.ok();
if skip.is_none() || !addr_cmp(&self_addr, skip.unwrap()) {
@@ -1049,7 +1045,6 @@ fn create_or_lookup_group(
}
}
send_EVENT_CHAT_MODIFIED = true;
chat::reset_gossiped_timestamp(context, chat_id)?;
}
if send_EVENT_CHAT_MODIFIED {
@@ -1059,17 +1054,13 @@ fn create_or_lookup_group(
}
/// try extract a grpid from a message-id list header value
fn extract_grpid(mime_parser: &MimeMessage, headerdef: HeaderDef) -> Option<String> {
if let Some(value) = mime_parser.get(headerdef) {
for part in value.split(',').map(str::trim) {
if !part.is_empty() {
if let Some(extracted_grpid) = dc_extract_grpid_from_rfc724_mid(part) {
return Some(extracted_grpid.to_string());
}
}
}
}
None
fn extract_grpid<'a>(mime_parser: &'a MimeMessage, headerdef: HeaderDef) -> Option<&'a str> {
let header = mime_parser.get(headerdef)?;
let parts = header
.split(',')
.map(str::trim)
.filter(|part| !part.is_empty());
parts.filter_map(dc_extract_grpid_from_rfc724_mid).next()
}
/// Handle groups for received messages, return chat_id/Blocked status on success
@@ -1080,7 +1071,7 @@ fn create_or_lookup_adhoc_group(
create_blocked: Blocked,
from_id: u32,
to_ids: &ContactIds,
) -> Result<(u32, Blocked)> {
) -> Result<(ChatId, Blocked)> {
// if we're here, no grpid was found, check if there is an existing
// ad-hoc group matching the to-list or if we should and can create one
// (we do not want to heuristically look at the likely mangled Subject)
@@ -1092,7 +1083,7 @@ fn create_or_lookup_adhoc_group(
context,
"not creating ad-hoc group for mailing list message"
);
return Ok((0, Blocked::Not));
return Ok((ChatId::new(0), Blocked::Not));
}
let mut member_ids: Vec<u32> = to_ids.iter().copied().collect();
@@ -1105,7 +1096,7 @@ fn create_or_lookup_adhoc_group(
if member_ids.len() < 3 {
info!(context, "not creating ad-hoc group: too few contacts");
return Ok((0, Blocked::Not));
return Ok((ChatId::new(0), Blocked::Not));
}
let chat_ids = search_chat_ids_by_contact_ids(context, &member_ids)?;
@@ -1113,25 +1104,35 @@ fn create_or_lookup_adhoc_group(
let chat_ids_str = join(chat_ids.iter().map(|x| x.to_string()), ",");
let res = context.sql.query_row(
format!(
"SELECT c.id, c.blocked FROM chats c \
LEFT JOIN msgs m ON m.chat_id=c.id WHERE c.id IN({}) ORDER BY m.timestamp DESC, m.id DESC LIMIT 1;",
"SELECT c.id,
c.blocked
FROM chats c
LEFT JOIN msgs m
ON m.chat_id=c.id
WHERE c.id IN({})
ORDER BY m.timestamp DESC,
m.id DESC
LIMIT 1;",
chat_ids_str
),
params![],
|row| {
Ok((row.get::<_, i32>(0)?, row.get::<_, Option<Blocked>>(1)?.unwrap_or_default()))
}
Ok((
row.get::<_, ChatId>(0)?,
row.get::<_, Option<Blocked>>(1)?.unwrap_or_default(),
))
},
);
if let Ok((id, id_blocked)) = res {
/* success, chat found */
return Ok((id as u32, id_blocked));
return Ok((id, id_blocked));
}
}
if !allow_creation {
info!(context, "creating ad-hoc group prevented from caller");
return Ok((0, Blocked::Not));
return Ok((ChatId::new(0), Blocked::Not));
}
// we do not check if the message is a reply to another group, this may result in
@@ -1145,7 +1146,7 @@ fn create_or_lookup_adhoc_group(
context,
"failed to create ad-hoc grpid for {:?}", member_ids
);
return Ok((0, Blocked::Not));
return Ok((ChatId::new(0), Blocked::Not));
}
// use subject as initial chat name
let grpname = mime_parser.get_subject().unwrap_or_else(|| {
@@ -1153,7 +1154,7 @@ fn create_or_lookup_adhoc_group(
});
// create group record
let new_chat_id = create_group_record(
let new_chat_id: ChatId = create_group_record(
context,
&grpid,
grpname,
@@ -1175,7 +1176,7 @@ fn create_group_record(
grpname: impl AsRef<str>,
create_blocked: Blocked,
create_verified: VerifiedStatus,
) -> u32 {
) -> ChatId {
if sql::execute(
context,
&context.sql,
@@ -1194,10 +1195,24 @@ fn create_group_record(
)
.is_err()
{
return 0;
warn!(
context,
"Failed to create group '{}' for grpid={}",
grpname.as_ref(),
grpid.as_ref()
);
return ChatId::new(0);
}
sql::get_rowid(context, &context.sql, "chats", "grpid", grpid.as_ref())
let row_id = sql::get_rowid(context, &context.sql, "chats", "grpid", grpid.as_ref());
let chat_id = ChatId::new(row_id);
info!(
context,
"Created group '{}' grpid={} as {}",
grpname.as_ref(),
grpid.as_ref(),
chat_id
);
chat_id
}
fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String {
@@ -1247,7 +1262,7 @@ fn hex_hash(s: impl AsRef<str>) -> String {
fn search_chat_ids_by_contact_ids(
context: &Context,
unsorted_contact_ids: &[u32],
) -> Result<Vec<u32>> {
) -> Result<Vec<ChatId>> {
/* searches chat_id's by the given contact IDs, may return zero, one or more chat_id's */
let mut contact_ids = Vec::with_capacity(23);
let mut chat_ids = Vec::with_capacity(23);
@@ -1264,19 +1279,19 @@ fn search_chat_ids_by_contact_ids(
let contact_ids_str = join(contact_ids.iter().map(|x| x.to_string()), ",");
context.sql.query_map(
format!(
"SELECT DISTINCT cc.chat_id, cc.contact_id \
FROM chats_contacts cc \
LEFT JOIN chats c ON c.id=cc.chat_id \
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;", // 1=DC_CONTACT_ID_SELF
"SELECT DISTINCT cc.chat_id, cc.contact_id
FROM chats_contacts cc
LEFT JOIN chats c ON c.id=cc.chat_id
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;", // 1=DC_CONTACT_ID_SELF
contact_ids_str
),
params![],
|row| Ok((row.get::<_, u32>(0)?, row.get::<_, u32>(1)?)),
|row| Ok((row.get::<_, ChatId>(0)?, row.get::<_, u32>(1)?)),
|rows| {
let mut last_chat_id = 0;
let mut last_chat_id = ChatId::new(0);
let mut matches = 0;
let mut mismatches = 0;
@@ -1375,31 +1390,31 @@ fn check_verified_properties(
context.is_self_addr(&to_addr)
);
let mut is_verified = _is_verified != 0;
let mut peerstate = Peerstate::from_addr(context, &context.sql, &to_addr);
let peerstate = Peerstate::from_addr(context, &context.sql, &to_addr);
// mark gossiped keys (if any) as verified
if mimeparser.gossipped_addr.contains(&to_addr) && peerstate.is_some() {
let peerstate = peerstate.as_mut().unwrap();
// if we're here, we know the gossip key is verified:
// - use the gossip-key as verified-key if there is no verified-key
// - OR if the verified-key does not match public-key or gossip-key
// (otherwise a verified key can _only_ be updated through QR scan which might be annoying,
// see https://github.com/nextleap-project/countermitm/issues/46 for a discussion about this point)
if !is_verified
|| peerstate.verified_key_fingerprint != peerstate.public_key_fingerprint
&& peerstate.verified_key_fingerprint != peerstate.gossip_key_fingerprint
{
info!(context, "{} has verified {}.", contact.get_addr(), to_addr,);
let fp = peerstate.gossip_key_fingerprint.clone();
if let Some(fp) = fp {
peerstate.set_verified(
PeerstateKeyType::GossipKey,
&fp,
PeerstateVerifiedStatus::BidirectVerified,
);
peerstate.save_to_db(&context.sql, false)?;
is_verified = true;
if mimeparser.gossipped_addr.contains(&to_addr) {
if let Some(mut peerstate) = peerstate {
// if we're here, we know the gossip key is verified:
// - use the gossip-key as verified-key if there is no verified-key
// - OR if the verified-key does not match public-key or gossip-key
// (otherwise a verified key can _only_ be updated through QR scan which might be annoying,
// see https://github.com/nextleap-project/countermitm/issues/46 for a discussion about this point)
if !is_verified
|| peerstate.verified_key_fingerprint != peerstate.public_key_fingerprint
&& peerstate.verified_key_fingerprint != peerstate.gossip_key_fingerprint
{
info!(context, "{} has verified {}.", contact.get_addr(), to_addr,);
let fp = peerstate.gossip_key_fingerprint.clone();
if let Some(fp) = fp {
peerstate.set_verified(
PeerstateKeyType::GossipKey,
&fp,
PeerstateVerifiedStatus::BidirectVerified,
);
peerstate.save_to_db(&context.sql, false)?;
is_verified = true;
}
}
}
}
@@ -1625,7 +1640,7 @@ mod tests {
hello\x00";
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
assert_eq!(extract_grpid(&mimeparser, HeaderDef::InReplyTo), None);
let grpid = Some("HcxyMARjyJy".to_string());
let grpid = Some("HcxyMARjyJy");
assert_eq!(extract_grpid(&mimeparser, HeaderDef::References), grpid);
}
@@ -1639,7 +1654,7 @@ mod tests {
\n\
hello\x00";
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
let grpid = Some("HcxyMARjyJy".to_string());
let grpid = Some("HcxyMARjyJy");
assert_eq!(extract_grpid(&mimeparser, HeaderDef::InReplyTo), grpid);
assert_eq!(extract_grpid(&mimeparser, HeaderDef::References), grpid);
}

View File

@@ -653,7 +653,6 @@ mod tests {
);
}
#[test]
#[test]
fn test_dc_extract_grpid_from_rfc724_mid() {
// Should return None if we pass invalid mid

View File

@@ -4,6 +4,7 @@ use std::path::PathBuf;
use strum::EnumProperty;
use crate::chat::ChatId;
use crate::message::MsgId;
impl Event {
@@ -107,36 +108,36 @@ pub enum Event {
/// - Chats created, deleted or archived
/// - A draft has been set
#[strum(props(id = "2000"))]
MsgsChanged { chat_id: u32, msg_id: MsgId },
MsgsChanged { chat_id: ChatId, msg_id: MsgId },
/// There is a fresh message. Typically, the user will show an notification
/// when receiving this message.
///
/// There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
#[strum(props(id = "2005"))]
IncomingMsg { chat_id: u32, msg_id: MsgId },
IncomingMsg { chat_id: ChatId, msg_id: MsgId },
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
/// DC_STATE_OUT_DELIVERED, see dc_msg_get_state().
#[strum(props(id = "2010"))]
MsgDelivered { chat_id: u32, msg_id: MsgId },
MsgDelivered { chat_id: ChatId, msg_id: MsgId },
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
/// DC_STATE_OUT_FAILED, see dc_msg_get_state().
#[strum(props(id = "2012"))]
MsgFailed { chat_id: u32, msg_id: MsgId },
MsgFailed { chat_id: ChatId, msg_id: MsgId },
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
/// DC_STATE_OUT_MDN_RCVD, see dc_msg_get_state().
#[strum(props(id = "2015"))]
MsgRead { chat_id: u32, msg_id: MsgId },
MsgRead { chat_id: ChatId, msg_id: MsgId },
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
/// Or the verify state of a chat has changed.
/// See dc_set_chat_name(), dc_set_chat_profile_image(), dc_add_contact_to_chat()
/// and dc_remove_contact_from_chat().
#[strum(props(id = "2020"))]
ChatModified(u32),
ChatModified(ChatId),
/// Contact(s) created, renamed, blocked or deleted.
///
@@ -205,5 +206,5 @@ pub enum Event {
/// @param data1 (int) chat_id
/// @param data2 (int) contact_id
#[strum(props(id = "2062"))]
SecurejoinMemberAdded { chat_id: u32, contact_id: u32 },
SecurejoinMemberAdded { chat_id: ChatId, contact_id: u32 },
}

View File

@@ -10,6 +10,10 @@ pub enum HeaderDef {
Cc,
Disposition,
OriginalMessageId,
/// Delta Chat extension for message IDs in combined MDNs
AdditionalMessageIds,
ListId,
References,
InReplyTo,

View File

@@ -957,8 +957,8 @@ impl Imap {
remote_message_id,
message_id,
);
*uid = 0;
}
*uid = 0;
}
Err(err) => {
warn!(

View File

@@ -6,15 +6,17 @@
use std::{fmt, time};
use deltachat_derive::{FromSql, ToSql};
use itertools::Itertools;
use rand::{thread_rng, Rng};
use async_std::task;
use crate::blob::BlobObject;
use crate::chat;
use crate::chat::{self, ChatId};
use crate::config::Config;
use crate::configure::*;
use crate::constants::*;
use crate::contact::Contact;
use crate::context::{Context, PerformJobsNeeded};
use crate::dc_tools::*;
use crate::error::{Error, Result};
@@ -25,7 +27,7 @@ use crate::location;
use crate::login_param::LoginParam;
use crate::message::MsgId;
use crate::message::{self, Message, MessageState};
use crate::mimefactory::{vec_contains_lowercase, MimeFactory, RenderedEmail};
use crate::mimefactory::{MimeFactory, RenderedEmail};
use crate::param::*;
use crate::sql;
@@ -88,9 +90,7 @@ pub enum Action {
// Jobs in the SMTP-thread, range from DC_SMTP_THREAD..DC_SMTP_THREAD+999
MaybeSendLocations = 5005, // low priority ...
MaybeSendLocationsEnded = 5007,
SendMdnOld = 5010,
SendMdn = 5011,
SendMsgToSmtpOld = 5900,
SendMdn = 5010,
SendMsgToSmtp = 5901, // ... high priority
}
@@ -118,9 +118,7 @@ impl From<Action> for Thread {
MaybeSendLocations => Thread::Smtp,
MaybeSendLocationsEnded => Thread::Smtp,
SendMdnOld => Thread::Smtp,
SendMdn => Thread::Smtp,
SendMsgToSmtpOld => Thread::Smtp,
SendMsgToSmtp => Thread::Smtp,
}
}
@@ -171,6 +169,53 @@ impl Job {
.is_ok()
}
fn smtp_send<F>(
&mut self,
context: &Context,
recipients: Vec<async_smtp::EmailAddress>,
message: Vec<u8>,
job_id: u32,
success_cb: F,
) -> Status
where
F: FnOnce() -> Result<()>,
{
// hold the smtp lock during sending of a job and
// 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 smtp = context.smtp.lock().unwrap();
if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
info!(context, "smtp-sending out mime message:");
println!("{}", String::from_utf8_lossy(&message));
}
match task::block_on(smtp.send(context, recipients, message, job_id)) {
Err(crate::smtp::send::Error::SendError(err)) => {
// Remote error, retry later.
warn!(context, "SMTP failed to send: {}", err);
smtp.disconnect();
self.pending_error = Some(err.to_string());
Status::RetryLater
}
Err(crate::smtp::send::Error::EnvelopeError(err)) => {
// Local error, job is invalid, do not retry.
smtp.disconnect();
warn!(context, "SMTP job is invalid: {}", err);
Status::Finished(Err(Error::SmtpError(err)))
}
Err(crate::smtp::send::Error::NoTransport) => {
// Should never happen.
// It does not even make sense to disconnect here.
error!(context, "SMTP job failed because SMTP has no transport");
Status::Finished(Err(format_err!("SMTP has not transport")))
}
Ok(()) => {
job_try!(success_cb());
Status::Finished(Ok(()))
}
}
}
#[allow(non_snake_case)]
fn SendMsgToSmtp(&mut self, context: &Context) -> Status {
/* connect to SMTP server, if not yet done */
@@ -216,45 +261,118 @@ impl Job {
)));
};
// hold the smtp lock during sending of a job and
// 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 smtp = context.smtp.lock().unwrap();
if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
info!(context, "smtp-sending out mime message:");
println!("{}", String::from_utf8_lossy(&body));
}
match task::block_on(smtp.send(context, recipients_list, body, self.job_id)) {
Err(crate::smtp::send::Error::SendError(err)) => {
// Remote error, retry later.
warn!(context, "SMTP failed to send: {}", err);
smtp.disconnect();
self.pending_error = Some(err.to_string());
Status::RetryLater
let foreign_id = self.foreign_id;
self.smtp_send(context, recipients_list, body, self.job_id, || {
// smtp success, update db ASAP, then delete smtp file
if 0 != foreign_id {
set_delivered(context, MsgId::new(foreign_id));
}
Err(crate::smtp::send::Error::EnvelopeError(err)) => {
// Local error, job is invalid, do not retry.
smtp.disconnect();
warn!(context, "SMTP job is invalid: {}", err);
Status::Finished(Err(Error::SmtpError(err)))
}
Err(crate::smtp::send::Error::NoTransport) => {
// Should never happen.
// It does not even make sense to disconnect here.
error!(context, "SMTP job failed because SMTP has no transport");
Status::Finished(Err(format_err!("SMTP has not transport")))
}
Ok(()) => {
// smtp success, update db ASAP, then delete smtp file
if 0 != self.foreign_id {
set_delivered(context, MsgId::new(self.foreign_id));
}
// now also delete the generated file
dc_delete_file(context, filename);
Status::Finished(Ok(()))
// now also delete the generated file
dc_delete_file(context, filename);
Ok(())
})
}
/// Get `SendMdn` jobs with foreign_id equal to `contact_id` excluding the `job_id` job.
fn get_additional_mdn_jobs(
&self,
context: &Context,
contact_id: u32,
) -> sql::Result<(Vec<u32>, Vec<String>)> {
// Extract message IDs from job parameters
let res: Vec<(u32, MsgId)> = context.sql.query_map(
"SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?",
params![contact_id, self.job_id],
|row| {
let job_id: u32 = row.get(0)?;
let params_str: String = row.get(1)?;
let params: Params = params_str.parse().unwrap_or_default();
Ok((job_id, params))
},
|jobs| {
let res = jobs
.filter_map(|row| {
let (job_id, params) = row.ok()?;
let msg_id = params.get_msg_id()?;
Some((job_id, msg_id))
})
.collect();
Ok(res)
},
)?;
// Load corresponding RFC724 message IDs
let mut job_ids = Vec::new();
let mut rfc724_mids = Vec::new();
for (job_id, msg_id) in res {
if let Ok(Message { rfc724_mid, .. }) = Message::load_from_db(context, msg_id) {
job_ids.push(job_id);
rfc724_mids.push(rfc724_mid);
}
}
Ok((job_ids, rfc724_mids))
}
#[allow(non_snake_case)]
fn SendMdn(&mut self, context: &Context) -> Status {
if !context.get_config_bool(Config::MdnsEnabled) {
// User has disabled MDNs after job scheduling but before
// execution.
return Status::Finished(Err(format_err!("MDNs are disabled")));
}
let contact_id = self.foreign_id;
let contact = job_try!(Contact::load_from_db(context, contact_id));
if contact.is_blocked() {
return Status::Finished(Err(format_err!("Contact is blocked")));
}
let msg_id = if let Some(msg_id) = self.param.get_msg_id() {
msg_id
} else {
return Status::Finished(Err(format_err!(
"SendMdn job has invalid parameters: {}",
self.param
)));
};
// Try to aggregate other SendMdn jobs and send a combined MDN.
let (additional_job_ids, additional_rfc724_mids) = self
.get_additional_mdn_jobs(context, contact_id)
.unwrap_or_default();
if !additional_rfc724_mids.is_empty() {
info!(
context,
"SendMdn job: aggregating {} additional MDNs",
additional_rfc724_mids.len()
)
}
let msg = job_try!(Message::load_from_db(context, msg_id));
let mimefactory = job_try!(MimeFactory::from_mdn(context, &msg, additional_rfc724_mids));
let rendered_msg = job_try!(mimefactory.render());
let body = rendered_msg.message;
let addr = contact.get_addr();
let recipient = job_try!(async_smtp::EmailAddress::new(addr.to_string())
.map_err(|err| format_err!("invalid recipient: {} {:?}", addr, err)));
let recipients = vec![recipient];
/* connect to SMTP server, if not yet done */
if !context.smtp.lock().unwrap().is_connected() {
let loginparam = LoginParam::from_database(context, "configured_");
if let Err(err) = context.smtp.lock().unwrap().connect(context, &loginparam) {
warn!(context, "SMTP connection failure: {:?}", err);
return Status::RetryLater;
}
}
self.smtp_send(context, recipients, body, self.job_id, || {
// Remove additional SendMdn jobs we have aggretated into this one.
job_kill_ids(context, &additional_job_ids)?;
Ok(())
})
}
#[allow(non_snake_case)]
@@ -363,7 +481,7 @@ impl Job {
if msg.param.get_bool(Param::WantsMdn).unwrap_or_default()
&& context.get_config_bool(Config::MdnsEnabled)
{
if let Err(err) = send_mdn(context, msg.id) {
if let Err(err) = send_mdn(context, &msg) {
warn!(context, "could not send out mdn for {}: {}", msg.id, err);
return Status::Finished(Err(err));
}
@@ -422,6 +540,19 @@ pub fn job_kill_action(context: &Context, action: Action) -> bool {
.is_ok()
}
/// Remove jobs with specified IDs.
pub fn job_kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> {
sql::execute(
context,
&context.sql,
format!(
"DELETE FROM jobs WHERE id IN({})",
job_ids.iter().map(|_| "?").join(",")
),
job_ids,
)
}
pub fn perform_inbox_fetch(context: &Context) {
let use_network = context.get_config_bool(Config::InboxWatch);
@@ -633,7 +764,7 @@ pub fn job_action_exists(context: &Context, action: Action) -> bool {
fn set_delivered(context: &Context, msg_id: MsgId) {
message::update_msg_state(context, msg_id, MessageState::OutDelivered);
let chat_id: i32 = context
let chat_id: ChatId = context
.sql
.query_get_value(
context,
@@ -641,10 +772,7 @@ fn set_delivered(context: &Context, msg_id: MsgId) {
params![msg_id],
)
.unwrap_or_default();
context.call_cb(Event::MsgDelivered {
chat_id: chat_id as u32,
msg_id,
});
context.call_cb(Event::MsgDelivered { chat_id, msg_id });
}
/* special case for DC_JOB_SEND_MSG_TO_SMTP */
@@ -683,8 +811,12 @@ pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
);
}
let lowercase_from = rendered_msg.from.to_lowercase();
if context.get_config_bool(Config::BccSelf)
&& !vec_contains_lowercase(&rendered_msg.recipients, &rendered_msg.from)
&& !rendered_msg
.recipients
.iter()
.any(|x| x.to_lowercase() == lowercase_from)
{
rendered_msg.recipients.push(rendered_msg.from.clone());
}
@@ -727,7 +859,7 @@ pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
msg.save_param_to_disk(context);
}
add_smtp_job(context, Action::SendMsgToSmtp, &rendered_msg)?;
add_smtp_job(context, Action::SendMsgToSmtp, msg.id, &rendered_msg)?;
Ok(())
}
@@ -752,9 +884,7 @@ pub fn perform_sentbox_jobs(context: &Context) {
}
fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
let jobs: Vec<Job> = load_jobs(context, thread, probe_network);
for mut job in jobs {
while let Some(mut job) = load_next_job(context, thread, probe_network) {
info!(context, "{}-job {} started...", thread, job);
// some configuration jobs are "exclusive":
@@ -793,7 +923,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
Action::MarkseenMsgOnImap => job.MarkseenMsgOnImap(context),
Action::MarkseenMdnOnImap => job.MarkseenMdnOnImap(context),
Action::MoveMsg => job.MoveMsg(context),
Action::SendMdn => job.SendMsgToSmtp(context),
Action::SendMdn => job.SendMdn(context),
Action::ConfigureImap => JobConfigureImap(context),
Action::ImexImap => match JobImexImap(context, &job) {
Ok(()) => Status::Finished(Ok(())),
@@ -810,8 +940,6 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
sql::housekeeping(context);
Status::Finished(Ok(()))
}
Action::SendMdnOld => Status::Finished(Ok(())),
Action::SendMsgToSmtpOld => Status::Finished(Ok(())),
};
info!(
@@ -907,10 +1035,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
"{} removes job {} as it failed with error {:?}", thread, job, err
);
} else {
info!(
context,
"{} removes job {} as it cannot be retried", thread, job
);
info!(context, "{} removes job {} as it succeeded", thread, job);
}
job.delete(context);
@@ -942,17 +1067,21 @@ fn suspend_smtp_thread(context: &Context, suspend: bool) {
}
}
fn send_mdn(context: &Context, msg_id: MsgId) -> Result<()> {
let msg = Message::load_from_db(context, msg_id)?;
let mimefactory = MimeFactory::from_mdn(context, &msg)?;
let rendered_msg = mimefactory.render()?;
fn send_mdn(context: &Context, msg: &Message) -> Result<()> {
let mut param = Params::new();
param.set(Param::MsgId, msg.id.to_u32().to_string());
add_smtp_job(context, Action::SendMdn, &rendered_msg)?;
job_add(context, Action::SendMdn, msg.from_id as i32, param, 0);
Ok(())
}
fn add_smtp_job(context: &Context, action: Action, rendered_msg: &RenderedEmail) -> Result<()> {
fn add_smtp_job(
context: &Context,
action: Action,
msg_id: MsgId,
rendered_msg: &RenderedEmail,
) -> Result<()> {
ensure!(
!rendered_msg.recipients.is_empty(),
"no recipients for smtp job set"
@@ -965,16 +1094,7 @@ fn add_smtp_job(context: &Context, action: Action, rendered_msg: &RenderedEmail)
param.set(Param::File, blob.as_name());
param.set(Param::Recipients, &recipients);
job_add(
context,
action,
rendered_msg
.foreign_id
.map(|v| v.to_u32() as i32)
.unwrap_or_default(),
param,
0,
);
job_add(context, action, msg_id.to_u32() as i32, param, 0);
Ok(())
}
@@ -1035,7 +1155,7 @@ pub fn interrupt_smtp_idle(context: &Context) {
/// IMAP jobs. The `probe_network` parameter decides how to query
/// jobs, this is tricky and probably wrong currently. Look at the
/// SQL queries for details.
fn load_jobs(context: &Context, thread: Thread, probe_network: bool) -> Vec<Job> {
fn load_next_job(context: &Context, thread: Thread, probe_network: bool) -> Option<Job> {
let query = if !probe_network {
// processing for first-try and after backoff-timeouts:
// process jobs in the order they were added.
@@ -1077,14 +1197,13 @@ fn load_jobs(context: &Context, thread: Thread, probe_network: bool) -> Vec<Job>
Ok(job)
},
|jobs| {
let mut ret: Vec<Job> = Vec::new();
for job in jobs {
match job {
Ok(j) => ret.push(j),
Ok(j) => return Ok(Some(j)),
Err(e) => warn!(context, "Bad job from the database: {}", e),
}
}
Ok(ret)
Ok(None)
},
)
.unwrap_or_default()
@@ -1117,15 +1236,17 @@ mod tests {
}
#[test]
fn test_load_jobs() {
fn test_load_next_job() {
// We want to ensure that loading jobs skips over jobs which
// fails to load from the database instead of failing to load
// all jobs.
let t = dummy_context();
insert_job(&t.ctx, 0);
insert_job(&t.ctx, -1); // This can not be loaded into Job struct.
let jobs = load_next_job(&t.ctx, Thread::from(Action::MoveMsg), false);
assert!(jobs.is_none());
insert_job(&t.ctx, 1);
let jobs = load_jobs(&t.ctx, Thread::from(Action::MoveMsg), false);
assert_eq!(jobs.len(), 2);
let jobs = load_next_job(&t.ctx, Thread::from(Action::MoveMsg), false);
assert!(jobs.is_some());
}
}

View File

@@ -1,9 +1,6 @@
#![forbid(unsafe_code)]
#![deny(clippy::correctness, missing_debug_implementations, clippy::all)]
// for now we hide warnings to not clutter/hide errors during "cargo clippy"
#![allow(clippy::cognitive_complexity, clippy::too_many_arguments)]
#![allow(clippy::match_bool)]
#![feature(ptr_wrapping_offset_from)]
#![feature(drain_filter)]
#[macro_use]
extern crate failure_derive;

View File

@@ -4,7 +4,7 @@ use bitflags::bitflags;
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::chat;
use crate::chat::{self, ChatId};
use crate::config::Config;
use crate::constants::*;
use crate::context::*;
@@ -28,7 +28,7 @@ pub struct Location {
pub timestamp: i64,
pub contact_id: u32,
pub msg_id: u32,
pub chat_id: u32,
pub chat_id: ChatId,
pub marker: Option<String>,
pub independent: u32,
}
@@ -193,9 +193,9 @@ impl Kml {
}
// location streaming
pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
pub fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: i64) {
let now = time();
if !(seconds < 0 || chat_id <= DC_CHAT_ID_LAST_SPECIAL) {
if !(seconds < 0 || chat_id.is_special()) {
let is_sending_locations_before = is_sending_locations_to_chat(context, chat_id);
if sql::execute(
context,
@@ -207,7 +207,7 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
params![
if 0 != seconds { now } else { 0 },
if 0 != seconds { now + seconds } else { 0 },
chat_id as i32,
chat_id,
],
)
.is_ok()
@@ -229,7 +229,7 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
job_add(
context,
job::Action::MaybeSendLocationsEnded,
chat_id as i32,
chat_id.to_u32() as i32,
Params::new(),
seconds + 1,
);
@@ -251,12 +251,12 @@ fn schedule_MAYBE_SEND_LOCATIONS(context: &Context, force_schedule: bool) {
};
}
pub fn is_sending_locations_to_chat(context: &Context, chat_id: u32) -> bool {
pub fn is_sending_locations_to_chat(context: &Context, chat_id: ChatId) -> bool {
context
.sql
.exists(
"SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;",
params![if chat_id == 0 { 1 } else { 0 }, chat_id as i32, time()],
params![if chat_id.is_unset() { 1 } else { 0 }, chat_id, time()],
)
.unwrap_or_default()
}
@@ -302,7 +302,7 @@ pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> b
pub fn get_range(
context: &Context,
chat_id: u32,
chat_id: ChatId,
contact_id: u32,
timestamp_from: i64,
mut timestamp_to: i64,
@@ -320,8 +320,8 @@ pub fn get_range(
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \
ORDER BY l.timestamp DESC, l.id DESC, msg_id DESC;",
params![
if chat_id == 0 { 1 } else { 0 },
chat_id as i32,
if chat_id.is_unset() { 1 } else { 0 },
chat_id,
if contact_id == 0 { 1 } else { 0 },
contact_id as i32,
timestamp_from,
@@ -371,7 +371,7 @@ pub fn delete_all(context: &Context) -> Result<(), Error> {
Ok(())
}
pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error> {
pub fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32), Error> {
let mut last_added_location_id = 0;
let self_addr = context
@@ -380,7 +380,7 @@ pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error>
let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row(
"SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;",
params![chat_id as i32], |row| {
params![chat_id], |row| {
let send_begin: i64 = row.get(0)?;
let send_until: i64 = row.get(1)?;
let last_sent: i64 = row.get(2)?;
@@ -465,14 +465,14 @@ pub fn get_message_kml(timestamp: i64, latitude: f64, longitude: f64) -> String
pub fn set_kml_sent_timestamp(
context: &Context,
chat_id: u32,
chat_id: ChatId,
timestamp: i64,
) -> Result<(), Error> {
sql::execute(
context,
&context.sql,
"UPDATE chats SET locations_last_sent=? WHERE id=?;",
params![timestamp, chat_id as i32],
params![timestamp, chat_id],
)?;
Ok(())
@@ -495,12 +495,12 @@ pub fn set_msg_location_id(
pub fn save(
context: &Context,
chat_id: u32,
chat_id: ChatId,
contact_id: u32,
locations: &[Location],
independent: bool,
) -> Result<u32, Error> {
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat id");
ensure!(!chat_id.is_special(), "Invalid chat id");
context
.sql
.prepare2(
@@ -520,7 +520,7 @@ pub fn save(
stmt_insert.execute(params![
location.timestamp,
contact_id as i32,
chat_id as i32,
chat_id,
location.latitude,
location.longitude,
location.accuracy,
@@ -562,7 +562,7 @@ pub fn JobMaybeSendLocations(context: &Context, _job: &Job) -> job::Status {
WHERE locations_send_until>?;",
params![now],
|row| {
let chat_id: i32 = row.get(0)?;
let chat_id: ChatId = row.get(0)?;
let locations_send_begin: i64 = row.get(1)?;
let locations_last_sent: i64 = row.get(2)?;
continue_streaming = true;
@@ -629,7 +629,7 @@ pub fn JobMaybeSendLocations(context: &Context, _job: &Job) -> job::Status {
for (chat_id, mut msg) in msgs.into_iter() {
// TODO: better error handling
chat::send_msg(context, chat_id as u32, &mut msg).unwrap_or_default();
chat::send_msg(context, chat_id, &mut msg).unwrap_or_default();
}
}
if continue_streaming {
@@ -644,11 +644,11 @@ pub fn JobMaybeSendLocationsEnded(context: &Context, job: &mut Job) -> job::Stat
// the function checks, if location-streaming is really ended;
// if so, a device-message is added if not yet done.
let chat_id = job.foreign_id;
let chat_id = ChatId::new(job.foreign_id);
let (send_begin, send_until) = job_try!(context.sql.query_row(
"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?",
params![chat_id as i32],
params![chat_id],
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)),
));
@@ -660,7 +660,7 @@ pub fn JobMaybeSendLocationsEnded(context: &Context, job: &mut Job) -> job::Stat
// not streaming, device-message already sent
job_try!(context.sql.execute(
"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?",
params![chat_id as i32],
params![chat_id],
));
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);

View File

@@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
use deltachat_derive::{FromSql, ToSql};
use failure::Fail;
use crate::chat::{self, Chat};
use crate::chat::{self, Chat, ChatId};
use crate::constants::*;
use crate::contact::*;
use crate::context::*;
@@ -176,7 +176,7 @@ pub struct Message {
pub(crate) id: MsgId,
pub(crate) from_id: u32,
pub(crate) to_id: u32,
pub(crate) chat_id: u32,
pub(crate) chat_id: ChatId,
pub(crate) viewtype: Viewtype,
pub(crate) state: MessageState,
pub(crate) hidden: bool,
@@ -400,9 +400,9 @@ impl Message {
self.from_id
}
pub fn get_chat_id(&self) -> u32 {
pub fn get_chat_id(&self) -> ChatId {
if self.chat_blocked != Blocked::Not {
1
ChatId::new(DC_CHAT_ID_DEADDROP)
} else {
self.chat_id
}
@@ -610,18 +610,48 @@ impl Message {
}
}
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
#[repr(i32)]
pub enum MessageState {
Undefined = 0,
/// Incoming *fresh* message. Fresh messages are neither noticed
/// nor seen and are typically shown in notifications.
InFresh = 10,
/// Incoming *noticed* message. E.g. chat opened but message not
/// yet read - noticed messages are not counted as unread but did
/// not marked as read nor resulted in MDNs.
InNoticed = 13,
/// Incoming message, really *seen* by the user. Marked as read on
/// IMAP and MDN may be sent.
InSeen = 16,
/// For files which need time to be prepared before they can be
/// sent, the message enters this state before
/// OutPending.
OutPreparing = 18,
/// Message saved as draft.
OutDraft = 19,
/// The user has pressed the "send" button but the message is not
/// yet sent and is pending in some way. Maybe we're offline (no
/// checkmark).
OutPending = 20,
/// *Unrecoverable* error (*recoverable* errors result in pending
/// messages).
OutFailed = 24,
/// Outgoing message successfully delivered to server (one
/// checkmark). Note, that already delivered messages may get into
/// the OutFailed state if we get such a hint from the server.
OutDelivered = 26,
/// Outgoing message read by the recipient (two checkmarks; this
/// requires goodwill on the receiver's side)
OutMdnRcvd = 28,
}
@@ -631,6 +661,27 @@ impl Default for MessageState {
}
}
impl std::fmt::Display for MessageState {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Undefined => "Undefined",
Self::InFresh => "Fresh",
Self::InNoticed => "Noticed",
Self::InSeen => "Seen",
Self::OutPreparing => "Preparing",
Self::OutDraft => "Draft",
Self::OutPending => "Pending",
Self::OutFailed => "Failed",
Self::OutDelivered => "Delivered",
Self::OutMdnRcvd => "Read",
}
)
}
}
impl From<MessageState> for LotState {
fn from(s: MessageState) -> Self {
use MessageState::*;
@@ -686,7 +737,7 @@ impl Lot {
self.text1 = None;
self.text1_meaning = Meaning::None;
} else {
if chat.id == DC_CHAT_ID_DEADDROP {
if chat.id.is_deaddrop() {
if let Some(contact) = contact {
self.text1 = Some(contact.get_display_name().into());
} else {
@@ -785,19 +836,7 @@ pub fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
}
}
ret += "State: ";
use MessageState::*;
match msg.state {
InFresh => ret += "Fresh",
InNoticed => ret += "Noticed",
InSeen => ret += "Seen",
OutDelivered => ret += "Delivered",
OutFailed => ret += "Failed",
OutMdnRcvd => ret += "Read",
OutPending => ret += "Pending",
OutPreparing => ret += "Preparing",
_ => ret += &format!("{}", msg.state),
}
ret += &format!("State: {}", msg.state);
if msg.has_location() {
ret += ", Location sent";
@@ -890,7 +929,7 @@ pub fn delete_msgs(context: &Context, msg_ids: &[MsgId]) {
delete_poi_location(context, msg.location_id);
}
}
update_msg_chat_id(context, *msg_id, DC_CHAT_ID_TRASH);
update_msg_chat_id(context, *msg_id, ChatId::new(DC_CHAT_ID_TRASH));
job_add(
context,
Action::DeleteMsgOnImap,
@@ -902,7 +941,7 @@ pub fn delete_msgs(context: &Context, msg_ids: &[MsgId]) {
if !msg_ids.is_empty() {
context.call_cb(Event::MsgsChanged {
chat_id: 0,
chat_id: ChatId::new(0),
msg_id: MsgId::new(0),
});
job_kill_action(context, Action::Housekeeping);
@@ -910,12 +949,12 @@ pub fn delete_msgs(context: &Context, msg_ids: &[MsgId]) {
};
}
fn update_msg_chat_id(context: &Context, msg_id: MsgId, chat_id: u32) -> bool {
fn update_msg_chat_id(context: &Context, msg_id: MsgId, chat_id: ChatId) -> bool {
sql::execute(
context,
&context.sql,
"UPDATE msgs SET chat_id=? WHERE id=?;",
params![chat_id as i32, msg_id],
params![chat_id, msg_id],
)
.is_ok()
}
@@ -994,7 +1033,7 @@ pub fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool {
if send_event {
context.call_cb(Event::MsgsChanged {
chat_id: 0,
chat_id: ChatId::new(0),
msg_id: MsgId::new(0),
});
}
@@ -1105,14 +1144,14 @@ pub fn exists(context: &Context, msg_id: MsgId) -> bool {
return false;
}
let chat_id: Option<u32> = context.sql.query_get_value(
let chat_id: Option<ChatId> = context.sql.query_get_value(
context,
"SELECT chat_id FROM msgs WHERE id=?;",
params![msg_id],
);
if let Some(chat_id) = chat_id {
chat_id != DC_CHAT_ID_TRASH
!chat_id.is_trash()
} else {
false
}
@@ -1150,7 +1189,7 @@ pub fn mdn_from_ext(
from_id: u32,
rfc724_mid: &str,
timestamp_sent: i64,
) -> Option<(u32, MsgId)> {
) -> Option<(ChatId, MsgId)> {
if from_id <= DC_MSG_ID_LAST_SPECIAL || rfc724_mid.is_empty() {
return None;
}
@@ -1170,7 +1209,7 @@ pub fn mdn_from_ext(
|row| {
Ok((
row.get::<_, MsgId>("msg_id")?,
row.get::<_, u32>("chat_id")?,
row.get::<_, ChatId>("chat_id")?,
row.get::<_, Chattype>("type")?,
row.get::<_, MessageState>("state")?,
))

View File

@@ -11,36 +11,35 @@ use crate::dc_tools::*;
use crate::e2ee::*;
use crate::error::Error;
use crate::location;
use crate::message::MsgId;
use crate::message::{self, Message};
use crate::mimeparser::SystemMessage;
use crate::param::*;
use crate::peerstate::{Peerstate, PeerstateVerifiedStatus};
use crate::stock::StockMessage;
#[derive(Clone, Copy, Eq, PartialEq)]
#[derive(Debug, Clone)]
pub enum Loaded {
Message,
MDN,
Message { chat: Chat },
MDN { additional_msg_ids: Vec<String> },
}
/// Helper to construct mime messages.
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct MimeFactory<'a, 'b> {
pub from_addr: String,
pub from_displayname: String,
pub selfstatus: String,
pub recipients_names: Vec<String>,
pub recipients_addr: Vec<String>,
pub timestamp: i64,
pub loaded: Loaded,
pub msg: &'b Message,
pub chat: Option<Chat>,
pub increation: bool,
pub in_reply_to: String,
pub references: String,
pub req_mdn: bool,
pub context: &'a Context,
from_addr: String,
from_displayname: String,
selfstatus: String,
/// Vector of pairs of recipient name and address
recipients: Vec<(String, String)>,
timestamp: i64,
loaded: Loaded,
msg: &'b Message,
in_reply_to: String,
references: String,
req_mdn: bool,
context: &'a Context,
last_added_location_id: u32,
attach_selfavatar: bool,
}
@@ -53,8 +52,6 @@ pub struct RenderedEmail {
pub is_encrypted: bool,
pub is_gossiped: bool,
pub last_added_location_id: u32,
/// None for MDN, the message id otherwise
pub foreign_id: Option<MsgId>,
pub from: String,
pub recipients: Vec<String>,
@@ -71,44 +68,22 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
) -> Result<MimeFactory<'a, 'b>, Error> {
let chat = Chat::load_from_db(context, msg.chat_id)?;
let mut factory = MimeFactory {
from_addr: context
.get_config(Config::ConfiguredAddr)
.unwrap_or_default(),
from_displayname: context.get_config(Config::Displayname).unwrap_or_default(),
selfstatus: context
.get_config(Config::Selfstatus)
.unwrap_or_else(|| context.stock_str(StockMessage::StatusLine).to_string()),
recipients_names: Vec::with_capacity(5),
recipients_addr: Vec::with_capacity(5),
timestamp: msg.timestamp_sort,
loaded: Loaded::Message,
msg,
chat: Some(chat),
increation: msg.is_increation(),
in_reply_to: String::default(),
references: String::default(),
req_mdn: false,
last_added_location_id: 0,
attach_selfavatar: add_selfavatar,
context,
};
// just set the chat above
let chat = factory.chat.as_ref().unwrap();
let from_addr = context
.get_config(Config::ConfiguredAddr)
.unwrap_or_default();
let from_displayname = context.get_config(Config::Displayname).unwrap_or_default();
let mut recipients = Vec::with_capacity(5);
let mut req_mdn = false;
if chat.is_self_talk() {
factory
.recipients_names
.push(factory.from_displayname.to_string());
factory.recipients_addr.push(factory.from_addr.to_string());
recipients.push((from_displayname.to_string(), from_addr.to_string()));
} else {
context.sql.query_map(
"SELECT c.authname, c.addr \
FROM chats_contacts cc \
LEFT JOIN contacts c ON cc.contact_id=c.id \
WHERE cc.chat_id=? AND cc.contact_id>9;",
params![factory.msg.chat_id as i32],
params![msg.chat_id],
|row| {
let authname: String = row.get(0)?;
let addr: String = row.get(1)?;
@@ -117,17 +92,15 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|rows| {
for row in rows {
let (authname, addr) = row?;
if !vec_contains_lowercase(&factory.recipients_addr, &addr) {
factory.recipients_addr.push(addr);
factory.recipients_names.push(authname);
if !recipients_contain_addr(&recipients, &addr) {
recipients.push((authname, addr));
}
}
Ok(())
},
)?;
let command = factory.msg.param.get_cmd();
let msg = &factory.msg;
let command = msg.param.get_cmd();
/* for added members, the list is just fine */
if command == SystemMessage::MemberRemovedFromGroup {
@@ -139,20 +112,19 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
if !email_to_remove.is_empty()
&& !addr_cmp(email_to_remove, self_addr)
&& !vec_contains_lowercase(&factory.recipients_addr, &email_to_remove)
&& !recipients_contain_addr(&recipients, &email_to_remove)
{
factory.recipients_names.push("".to_string());
factory.recipients_addr.push(email_to_remove.to_string());
recipients.push(("".to_string(), email_to_remove.to_string()));
}
}
if command != SystemMessage::AutocryptSetupMessage
&& command != SystemMessage::SecurejoinMessage
&& context.get_config_bool(Config::MdnsEnabled)
{
factory.req_mdn = true;
req_mdn = true;
}
}
let row = context.sql.query_row(
let (in_reply_to, references) = context.sql.query_row(
"SELECT mime_in_reply_to, mime_references FROM msgs WHERE id=?",
params![msg.id],
|row| {
@@ -164,39 +136,37 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
render_rfc724_mid_list(&references),
))
},
);
match row {
Ok((in_reply_to, references)) => {
factory.in_reply_to = in_reply_to;
factory.references = references;
}
Err(err) => {
error!(
context,
"mimefactory: failed to load mime_in_reply_to: {:?}", err
);
}
}
)?;
let factory = MimeFactory {
from_addr,
from_displayname,
selfstatus: context
.get_config(Config::Selfstatus)
.unwrap_or_else(|| context.stock_str(StockMessage::StatusLine).to_string()),
recipients,
timestamp: msg.timestamp_sort,
loaded: Loaded::Message { chat },
msg,
in_reply_to,
references,
req_mdn,
last_added_location_id: 0,
attach_selfavatar: add_selfavatar,
context,
};
Ok(factory)
}
pub fn from_mdn(context: &'a Context, msg: &'b Message) -> Result<Self, Error> {
// MDNs not enabled - check this is late, in the job. the
// user may have changed its choice while offline ...
ensure!(
context.get_config_bool(Config::MdnsEnabled),
"MDNs meanwhile disabled"
);
pub fn from_mdn(
context: &'a Context,
msg: &'b Message,
additional_msg_ids: Vec<String>,
) -> Result<Self, Error> {
ensure!(!msg.chat_id.is_special(), "Invalid chat id");
let contact = Contact::load_from_db(context, msg.from_id)?;
// Do not send MDNs trash etc.; chats.blocked is already checked by the caller
// in dc_markseen_msgs()
ensure!(!contact.is_blocked(), "Contact blocked");
ensure!(msg.chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat id");
Ok(MimeFactory {
context,
from_addr: context
@@ -206,13 +176,13 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
selfstatus: context
.get_config(Config::Selfstatus)
.unwrap_or_else(|| context.stock_str(StockMessage::StatusLine).to_string()),
recipients_names: vec![contact.get_authname().to_string()],
recipients_addr: vec![contact.get_addr().to_string()],
recipients: vec![(
contact.get_authname().to_string(),
contact.get_addr().to_string(),
)],
timestamp: dc_create_smeared_timestamp(context),
loaded: Loaded::MDN,
loaded: Loaded::MDN { additional_msg_ids },
msg,
chat: None,
increation: false,
in_reply_to: String::default(),
references: String::default(),
req_mdn: false,
@@ -228,10 +198,10 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
.ok_or_else(|| format_err!("Not configured"))?;
Ok(self
.recipients_addr
.recipients
.iter()
.filter(|addr| *addr != &self_addr)
.map(|addr| {
.filter(|(_, addr)| addr != &self_addr)
.map(|(_, addr)| {
(
Peerstate::from_addr(self.context, &self.context.sql, addr),
addr.as_str(),
@@ -240,10 +210,10 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
.collect())
}
fn is_e2ee_guranteed(&self) -> bool {
match self.loaded {
Loaded::Message => {
if self.chat.as_ref().unwrap().typ == Chattype::VerifiedGroup {
fn is_e2ee_guaranteed(&self) -> bool {
match &self.loaded {
Loaded::Message { chat } => {
if chat.typ == Chattype::VerifiedGroup {
return true;
}
@@ -264,28 +234,26 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
false
}
Loaded::MDN => false,
Loaded::MDN { .. } => false,
}
}
fn min_verified(&self) -> PeerstateVerifiedStatus {
match self.loaded {
Loaded::Message => {
let chat = self.chat.as_ref().unwrap();
match &self.loaded {
Loaded::Message { chat } => {
if chat.typ == Chattype::VerifiedGroup {
PeerstateVerifiedStatus::BidirectVerified
} else {
PeerstateVerifiedStatus::Unverified
}
}
Loaded::MDN => PeerstateVerifiedStatus::Unverified,
Loaded::MDN { .. } => PeerstateVerifiedStatus::Unverified,
}
}
fn should_force_plaintext(&self) -> i32 {
match self.loaded {
Loaded::Message => {
let chat = self.chat.as_ref().unwrap();
match &self.loaded {
Loaded::Message { chat } => {
if chat.typ == Chattype::VerifiedGroup {
0
} else {
@@ -295,30 +263,28 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
.unwrap_or_default()
}
}
Loaded::MDN => ForcePlaintext::NoAutocryptHeader as i32,
Loaded::MDN { .. } => ForcePlaintext::NoAutocryptHeader as i32,
}
}
fn should_do_gossip(&self) -> bool {
match self.loaded {
Loaded::Message => {
let chat = self.chat.as_ref().unwrap();
match &self.loaded {
Loaded::Message { chat } => {
// beside key- and member-changes, force re-gossip every 48 hours
let gossiped_timestamp = chat.get_gossiped_timestamp(self.context);
if gossiped_timestamp == 0 || (gossiped_timestamp + (2 * 24 * 60 * 60)) > time() {
if time() > gossiped_timestamp + (2 * 24 * 60 * 60) {
return true;
}
self.msg.param.get_cmd() == SystemMessage::MemberAddedToGroup
}
Loaded::MDN => false,
Loaded::MDN { .. } => false,
}
}
fn grpimage(&self) -> Option<String> {
match self.loaded {
Loaded::Message => {
let chat = self.chat.as_ref().unwrap();
match &self.loaded {
Loaded::Message { chat } => {
let cmd = self.msg.param.get_cmd();
match cmd {
@@ -342,51 +308,37 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
None
}
Loaded::MDN => None,
Loaded::MDN { .. } => None,
}
}
fn subject_str(&self) -> String {
match self.loaded {
Loaded::Message => {
match self.chat {
Some(ref chat) => {
let raw = message::get_summarytext_by_raw(
self.msg.viewtype,
self.msg.text.as_ref(),
&self.msg.param,
32,
self.context,
);
let mut lines = raw.lines();
let raw_subject = if let Some(line) = lines.next() {
line
} else {
""
};
let afwd_email = self.msg.param.exists(Param::Forwarded);
let fwd = if afwd_email { "Fwd: " } else { "" };
if self.msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage {
// do not add the "Chat:" prefix for setup messages
self.context
.stock_str(StockMessage::AcSetupMsgSubject)
.into_owned()
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup
{
format!("Chat: {}: {}{}", chat.name, fwd, raw_subject)
} else {
format!("Chat: {}{}", fwd, raw_subject)
}
}
None => String::new(),
Loaded::Message { ref chat } => {
if self.msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage {
self.context
.stock_str(StockMessage::AcSetupMsgSubject)
.into_owned()
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
let re = if self.in_reply_to.is_empty() {
""
} else {
"Re: "
};
format!("{}{}", re, chat.name)
} else {
let raw = message::get_summarytext_by_raw(
self.msg.viewtype,
self.msg.text.as_ref(),
&self.msg.param,
32,
self.context,
);
let raw_subject = raw.lines().next().unwrap_or_default();
format!("Chat: {}", raw_subject)
}
}
Loaded::MDN => {
let e = self.context.stock_str(StockMessage::ReadRcpt);
format!("Chat: {}", e)
}
Loaded::MDN { .. } => self.context.stock_str(StockMessage::ReadRcpt).into_owned(),
}
}
@@ -405,10 +357,8 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
self.from_addr.clone(),
);
let mut to = Vec::with_capacity(self.recipients_names.len());
let name_iter = self.recipients_names.iter();
let addr_iter = self.recipients_addr.iter();
for (name, addr) in name_iter.zip(addr_iter) {
let mut to = Vec::with_capacity(self.recipients.len());
for (name, addr) in self.recipients.iter() {
if name.is_empty() {
to.push(Address::new_mailbox(addr.clone()));
} else {
@@ -451,6 +401,13 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
));
unprotected_headers.push(Header::new("Chat-Version".to_string(), "1.0".to_string()));
if let Loaded::MDN { .. } = self.loaded {
unprotected_headers.push(Header::new(
"Auto-Submitted".to_string(),
"auto-replied".to_string(),
));
}
if self.req_mdn {
// we use "Chat-Disposition-Notification-To"
// because replies to "Disposition-Notification-To" are weird in many cases
@@ -465,16 +422,16 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
let grpimage = self.grpimage();
let force_plaintext = self.should_force_plaintext();
let subject_str = self.subject_str();
let e2ee_guranteed = self.is_e2ee_guranteed();
let e2ee_guaranteed = self.is_e2ee_guaranteed();
let mut encrypt_helper = EncryptHelper::new(self.context)?;
let subject = encode_words(&subject_str);
let mut message = match self.loaded {
Loaded::Message => {
Loaded::Message { .. } => {
self.render_message(&mut protected_headers, &mut unprotected_headers, &grpimage)?
}
Loaded::MDN => self.render_mdn()?,
Loaded::MDN { .. } => self.render_mdn()?,
};
if force_plaintext != ForcePlaintext::NoAutocryptHeader as i32 {
@@ -487,12 +444,12 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
let peerstates = self.peerstates_for_recipients()?;
let should_encrypt =
encrypt_helper.should_encrypt(self.context, e2ee_guranteed, &peerstates)?;
encrypt_helper.should_encrypt(self.context, e2ee_guaranteed, &peerstates)?;
let is_encrypted = should_encrypt && force_plaintext == 0;
let rfc724_mid = match self.loaded {
Loaded::Message => self.msg.rfc724_mid.clone(),
Loaded::MDN => dc_create_outgoing_rfc724_mid(None, &self.from_addr),
Loaded::Message { .. } => self.msg.rfc724_mid.clone(),
Loaded::MDN { .. } => dc_create_outgoing_rfc724_mid(None, &self.from_addr),
};
// we could also store the message-id in the protected headers
@@ -601,11 +558,9 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
};
let MimeFactory {
recipients_addr,
recipients,
from_addr,
last_added_location_id,
msg,
loaded,
..
} = self;
@@ -615,16 +570,13 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
is_encrypted,
is_gossiped,
last_added_location_id,
foreign_id: match loaded {
Loaded::Message => Some(msg.id),
Loaded::MDN => None,
},
recipients: recipients_addr,
recipients: recipients.into_iter().map(|(_, addr)| addr).collect(),
from: from_addr,
rfc724_mid,
})
}
#[allow(clippy::cognitive_complexity)]
fn render_message(
&mut self,
protected_headers: &mut Vec<Header>,
@@ -632,7 +584,10 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
grpimage: &Option<String>,
) -> Result<PartBuilder, Error> {
let context = self.context;
let chat = self.chat.as_ref().unwrap();
let chat = match &self.loaded {
Loaded::Message { chat } => chat,
Loaded::MDN { .. } => bail!("Attempt to render MDN as a message"),
};
let command = self.msg.param.get_cmd();
let mut placeholdertext = None;
let mut meta_part = None;
@@ -666,7 +621,9 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
email_to_add.into(),
));
}
if 0 != self.msg.param.get_int(Param::Arg2).unwrap_or_default() & 0x1 {
if 0 != self.msg.param.get_int(Param::Arg2).unwrap_or_default()
& DC_FROM_HANDSHAKE
{
info!(
context,
"sending secure-join message \'{}\' >>>>>>>>>>>>>>>>>>>>>>>>>",
@@ -950,6 +907,13 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
// are forwarded for any reasons (eg. gmail always forwards to IMAP), we have no chance to decrypt them;
// this issue is fixed with 0.9.4
let additional_msg_ids = match &self.loaded {
Loaded::Message { .. } => bail!("Attempt to render a message as MDN"),
Loaded::MDN {
additional_msg_ids, ..
} => additional_msg_ids,
};
let mut message = PartBuilder::new().header((
"Content-Type".to_string(),
"multipart/report; report-type=disposition-notification".to_string(),
@@ -991,10 +955,22 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
version, self.from_addr, self.from_addr, self.msg.rfc724_mid
);
let extension_fields = if additional_msg_ids.is_empty() {
"".to_string()
} else {
"Additional-Message-IDs: ".to_string()
+ &additional_msg_ids
.iter()
.map(|mid| render_rfc724_mid(&mid))
.collect::<Vec<String>>()
.join(" ")
+ "\r\n"
};
message = message.child(
PartBuilder::new()
.content_type(&"message/disposition-notification".parse().unwrap())
.body(message_text2)
.body(message_text2 + &extension_fields)
.build(),
);
@@ -1112,14 +1088,11 @@ fn build_selfavatar_file(context: &Context, path: String) -> Result<(PartBuilder
Ok((part, filename_to_send))
}
pub(crate) fn vec_contains_lowercase(vec: &[String], part: &str) -> bool {
let partlc = part.to_lowercase();
for cur in vec.iter() {
if cur.to_lowercase() == partlc {
return true;
}
}
false
fn recipients_contain_addr(recipients: &[(String, String)], addr: &str) -> bool {
let addr_lc = addr.to_lowercase();
recipients
.iter()
.any(|(_, cur)| cur.to_lowercase() == addr_lc)
}
fn is_file_size_okay(context: &Context, msg: &Message) -> bool {
@@ -1160,14 +1133,8 @@ fn encode_words(word: &str) -> String {
}
pub fn needs_encoding(to_check: impl AsRef<str>) -> bool {
let to_check = to_check.as_ref();
if to_check.is_empty() {
return false;
}
to_check.chars().any(|c| {
!c.is_ascii_alphanumeric() && c != '-' && c != '_' && c != '.' && c != '~' && c != '%'
!to_check.as_ref().chars().all(|c| {
c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.' || c == '~' || c == '%'
})
}
@@ -1226,4 +1193,12 @@ mod tests {
FBQUFBQUFBQQ==";
assert_eq!(wrapped_base64_encode(input), output);
}
#[test]
fn test_needs_encoding() {
assert!(!needs_encoding(""));
assert!(!needs_encoding("foobar"));
assert!(needs_encoding(" "));
assert!(needs_encoding("foo bar"));
}
}

View File

@@ -49,7 +49,7 @@ pub struct MimeMessage<'a> {
pub message_kml: Option<location::Kml>,
pub user_avatar: AvatarAction,
pub group_avatar: AvatarAction,
reports: Vec<Report>,
pub(crate) reports: Vec<Report>,
}
#[derive(Debug, PartialEq)]
@@ -175,12 +175,19 @@ impl<'a> MimeMessage<'a> {
Ok(parser)
}
#[allow(clippy::cognitive_complexity)]
fn parse_headers(&mut self) -> Result<()> {
if self.get(HeaderDef::AutocryptSetupMessage).is_some() {
self.parts.drain_filter(|part| {
part.mimetype.is_some()
&& part.mimetype.as_ref().unwrap().as_ref() != MIME_AC_SETUP_FILE
});
self.parts = self
.parts
.iter()
.filter(|part| {
part.mimetype.is_none()
|| part.mimetype.as_ref().unwrap().as_ref() == MIME_AC_SETUP_FILE
})
.cloned()
.collect();
if self.parts.len() == 1 {
self.is_system_message = SystemMessage::AutocryptSetupMessage;
} else {
@@ -367,14 +374,9 @@ impl<'a> MimeMessage<'a> {
}
pub(crate) fn get_subject(&self) -> Option<String> {
if let Some(s) = self.get(HeaderDef::Subject) {
if s.is_empty() {
return None;
}
Some(s.to_string())
} else {
None
}
self.get(HeaderDef::Subject)
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
}
pub fn get(&self, headerdef: HeaderDef) -> Option<&String> {
@@ -713,11 +715,8 @@ impl<'a> MimeMessage<'a> {
}
pub fn get_rfc724_mid(&self) -> Option<String> {
if let Some(msgid) = self.get(HeaderDef::MessageId) {
parse_message_id(msgid)
} else {
None
}
self.get(HeaderDef::MessageId)
.and_then(|msgid| parse_message_id(msgid))
}
fn merge_headers(&mut self, fields: &[mailparse::MailHeader<'_>]) {
@@ -750,8 +749,17 @@ impl<'a> MimeMessage<'a> {
.flatten()
.and_then(|v| parse_message_id(&v))
{
let additional_message_ids = report_fields
.get_first_value(&HeaderDef::AdditionalMessageIds.get_headername())
.ok()
.flatten()
.map_or_else(Vec::new, |v| {
v.split(' ').filter_map(parse_message_id).collect()
});
return Ok(Some(Report {
original_message_id,
additional_message_ids,
}));
}
}
@@ -778,14 +786,18 @@ impl<'a> MimeMessage<'a> {
let mut mdn_recognized = false;
for report in &self.reports {
if let Some((chat_id, msg_id)) = message::mdn_from_ext(
self.context,
from_id,
&report.original_message_id,
sent_timestamp,
) {
self.context.call_cb(Event::MsgRead { chat_id, msg_id });
mdn_recognized = true;
for original_message_id in
std::iter::once(&report.original_message_id).chain(&report.additional_message_ids)
{
if let Some((chat_id, msg_id)) = message::mdn_from_ext(
self.context,
from_id,
original_message_id,
sent_timestamp,
) {
self.context.call_cb(Event::MsgRead { chat_id, msg_id });
mdn_recognized = true;
}
}
}
@@ -860,8 +872,11 @@ fn update_gossip_peerstates(
}
#[derive(Debug)]
struct Report {
pub(crate) struct Report {
/// Original-Message-ID header
original_message_id: String,
/// Additional-Message-IDs
additional_message_ids: Vec<String>,
}
fn parse_message_id(field: &str) -> Option<String> {
@@ -1036,7 +1051,6 @@ fn compare_addrs(a: &mailparse::MailAddr, b: &mailparse::MailAddr) -> bool {
mod tests {
use super::*;
use crate::test_utils::*;
use proptest::prelude::*;
#[test]
fn test_dc_mimeparser_crash() {
@@ -1048,17 +1062,6 @@ mod tests {
assert_eq!(mimeparser.parts.len(), 1);
}
proptest! {
#[ignore]
#[test]
fn test_dc_mailmime_parse_crash_fuzzy(data in "[!-~\t ]{2000,}") {
let context = dummy_context();
// just don't crash
let _ = MimeMessage::from_bytes(&context.ctx, data.as_bytes());
}
}
#[test]
fn test_get_rfc724_mid_exists() {
let context = dummy_context();
@@ -1270,4 +1273,176 @@ Content-Disposition: attachment; filename=\"message.kml\"\n\
// and only goes into message_kml.
assert_eq!(mimeparser.parts.len(), 1);
}
#[test]
fn test_parse_mdn() {
let context = dummy_context();
let raw = b"Subject: =?utf-8?q?Chat=3A_Message_opened?=\n\
Date: Mon, 10 Jan 2020 00:00:00 +0000\n\
Chat-Version: 1.0\n\
Message-ID: <bar@example.org>\n\
To: Alice <alice@example.org>\n\
From: Bob <bob@example.org>\n\
Content-Type: multipart/report; report-type=disposition-notification;\n\t\
boundary=\"kJBbU58X1xeWNHgBtTbMk80M5qnV4N\"\n\
\n\
\n\
--kJBbU58X1xeWNHgBtTbMk80M5qnV4N\n\
Content-Type: text/plain; charset=utf-8\n\
\n\
The \"Encrypted message\" message you sent was displayed on the screen of the recipient.\n\
\n\
This is no guarantee the content was read.\n\
\n\
\n\
--kJBbU58X1xeWNHgBtTbMk80M5qnV4N\n\
Content-Type: message/disposition-notification\n\
\n\
Reporting-UA: Delta Chat 1.0.0-beta.22\n\
Original-Recipient: rfc822;bob@example.org\n\
Final-Recipient: rfc822;bob@example.org\n\
Original-Message-ID: <foo@example.org>\n\
Disposition: manual-action/MDN-sent-automatically; displayed\n\
\n\
\n\
--kJBbU58X1xeWNHgBtTbMk80M5qnV4N--\n\
";
let message = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
assert_eq!(
message.get_subject(),
Some("Chat: Message opened".to_string())
);
assert_eq!(message.parts.len(), 0);
assert_eq!(message.reports.len(), 1);
}
/// Test parsing multiple MDNs combined in a single message.
///
/// RFC 6522 specifically allows MDNs to be nested inside
/// multipart MIME messages.
#[test]
fn test_parse_multiple_mdns() {
let context = dummy_context();
let raw = b"Subject: =?utf-8?q?Chat=3A_Message_opened?=\n\
Date: Mon, 10 Jan 2020 00:00:00 +0000\n\
Chat-Version: 1.0\n\
Message-ID: <foo@example.org>\n\
To: Alice <alice@example.org>\n\
From: Bob <bob@example.org>\n\
Content-Type: multipart/parallel; boundary=outer\n\
\n\
This is a multipart MDN.\n\
\n\
--outer\n\
Content-Type: multipart/report; report-type=disposition-notification;\n\t\
boundary=kJBbU58X1xeWNHgBtTbMk80M5qnV4N\n\
\n\
\n\
--kJBbU58X1xeWNHgBtTbMk80M5qnV4N\n\
Content-Type: text/plain; charset=utf-8\n\
\n\
The \"Encrypted message\" message you sent was displayed on the screen of the recipient.\n\
\n\
This is no guarantee the content was read.\n\
\n\
\n\
--kJBbU58X1xeWNHgBtTbMk80M5qnV4N\n\
Content-Type: message/disposition-notification\n\
\n\
Reporting-UA: Delta Chat 1.0.0-beta.22\n\
Original-Recipient: rfc822;bob@example.org\n\
Final-Recipient: rfc822;bob@example.org\n\
Original-Message-ID: <bar@example.org>\n\
Disposition: manual-action/MDN-sent-automatically; displayed\n\
\n\
\n\
--kJBbU58X1xeWNHgBtTbMk80M5qnV4N--\n\
--outer\n\
Content-Type: multipart/report; report-type=disposition-notification;\n\t\
boundary=zuOJlsTfZAukyawEPVdIgqWjaM9w2W\n\
\n\
\n\
--zuOJlsTfZAukyawEPVdIgqWjaM9w2W\n\
Content-Type: text/plain; charset=utf-8\n\
\n\
The \"Encrypted message\" message you sent was displayed on the screen of the recipient.\n\
\n\
This is no guarantee the content was read.\n\
\n\
\n\
--zuOJlsTfZAukyawEPVdIgqWjaM9w2W\n\
Content-Type: message/disposition-notification\n\
\n\
Reporting-UA: Delta Chat 1.0.0-beta.22\n\
Original-Recipient: rfc822;bob@example.org\n\
Final-Recipient: rfc822;bob@example.org\n\
Original-Message-ID: <baz@example.org>\n\
Disposition: manual-action/MDN-sent-automatically; displayed\n\
\n\
\n\
--zuOJlsTfZAukyawEPVdIgqWjaM9w2W--\n\
--outer--\n\
";
let message = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
assert_eq!(
message.get_subject(),
Some("Chat: Message opened".to_string())
);
assert_eq!(message.parts.len(), 0);
assert_eq!(message.reports.len(), 2);
}
#[test]
fn test_parse_mdn_with_additional_message_ids() {
let context = dummy_context();
let raw = b"Subject: =?utf-8?q?Chat=3A_Message_opened?=\n\
Date: Mon, 10 Jan 2020 00:00:00 +0000\n\
Chat-Version: 1.0\n\
Message-ID: <bar@example.org>\n\
To: Alice <alice@example.org>\n\
From: Bob <bob@example.org>\n\
Content-Type: multipart/report; report-type=disposition-notification;\n\t\
boundary=\"kJBbU58X1xeWNHgBtTbMk80M5qnV4N\"\n\
\n\
\n\
--kJBbU58X1xeWNHgBtTbMk80M5qnV4N\n\
Content-Type: text/plain; charset=utf-8\n\
\n\
The \"Encrypted message\" message you sent was displayed on the screen of the recipient.\n\
\n\
This is no guarantee the content was read.\n\
\n\
\n\
--kJBbU58X1xeWNHgBtTbMk80M5qnV4N\n\
Content-Type: message/disposition-notification\n\
\n\
Reporting-UA: Delta Chat 1.0.0-beta.22\n\
Original-Recipient: rfc822;bob@example.org\n\
Final-Recipient: rfc822;bob@example.org\n\
Original-Message-ID: <foo@example.org>\n\
Disposition: manual-action/MDN-sent-automatically; displayed\n\
Additional-Message-IDs: <foo@example.com> <foo@example.net>\n\
\n\
\n\
--kJBbU58X1xeWNHgBtTbMk80M5qnV4N--\n\
";
let message = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
assert_eq!(
message.get_subject(),
Some("Chat: Message opened".to_string())
);
assert_eq!(message.parts.len(), 0);
assert_eq!(message.reports.len(), 1);
assert_eq!(message.reports[0].original_message_id, "foo@example.org");
assert_eq!(
&message.reports[0].additional_message_ids,
&["foo@example.com", "foo@example.net"]
);
}
}

View File

@@ -312,13 +312,12 @@ impl Oauth2 {
}
if let Ok(response) = parsed {
// serde_json::Value.as_str() removes the quotes of json-strings
let addr = response.get("email");
if addr.is_none() {
if let Some(addr) = response.get("email") {
Some(addr.to_string())
} else {
warn!(context, "E-mail missing in userinfo.");
return None;
None
}
let addr = addr.unwrap().as_str();
addr.map(|addr| addr.to_string())
} else {
warn!(context, "Failed to parse userinfo.");
None

View File

@@ -8,6 +8,7 @@ use num_traits::FromPrimitive;
use crate::blob::{BlobError, BlobObject};
use crate::context::Context;
use crate::error;
use crate::message::MsgId;
use crate::mimeparser::SystemMessage;
/// Available param keys.
@@ -96,26 +97,29 @@ pub enum Param {
/// For Jobs: space-separated list of message recipients
Recipients = b'R',
// For Groups
/// For Groups
Unpromoted = b'U',
// For Groups and Contacts
/// For Groups and Contacts
ProfileImage = b'i',
// For Chats
/// For Chats
Selftalk = b'K',
// For Chats
/// For Chats
Devicetalk = b'D',
// For QR
/// For QR
Auth = b's',
// For QR
/// For QR
GroupId = b'x',
// For QR
/// For QR
GroupName = b'g',
/// For MDN-sending job
MsgId = b'I',
}
/// Possible values for `Param::ForcePlaintext`.
@@ -312,6 +316,12 @@ impl Params {
Ok(Some(path))
}
pub fn get_msg_id(&self) -> Option<MsgId> {
self.get(Param::MsgId)
.and_then(|x| x.parse::<u32>().ok())
.map(MsgId::new)
}
/// Set the given paramter to the passed in `i32`.
pub fn set_int(&mut self, key: Param, value: i32) -> &mut Self {
self.set(key, format!("{}", value));

View File

@@ -5,7 +5,6 @@ use std::fmt;
use num_traits::FromPrimitive;
use crate::aheader::*;
use crate::chat::*;
use crate::constants::*;
use crate::context::Context;
use crate::key::*;
@@ -418,7 +417,6 @@ impl<'a> Peerstate<'a> {
&self.addr,
],
)?;
reset_gossiped_timestamp(self.context, 0)?;
} else if self.to_save == Some(ToSave::Timestamps) {
sql::execute(
self.context,

View File

@@ -3,7 +3,7 @@
use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
use crate::aheader::EncryptPreference;
use crate::chat::{self, Chat};
use crate::chat::{self, Chat, ChatId};
use crate::config::*;
use crate::constants::*;
use crate::contact::*;
@@ -65,15 +65,18 @@ macro_rules! get_qr_attr {
};
}
pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<String> {
/* =========================================================
pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> Option<String> {
/*=======================================================
==== Alice - the inviter side ====
==== Step 1 in "Setup verified contact" protocol ====
========================================================= */
=======================================================*/
let fingerprint: String;
ensure_secret_key_exists(context).ok();
// invitenumber will be used to allow starting the handshake,
// auth will be used to verify the fingerprint
let invitenumber = token::lookup_or_new(context, token::Namespace::InviteNumber, group_chat_id);
let auth = token::lookup_or_new(context, token::Namespace::Auth, group_chat_id);
let self_addr = match context.get_config(Config::ConfiguredAddr) {
@@ -98,7 +101,8 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<Str
let self_name_urlencoded =
utf8_percent_encode(&self_name, NON_ALPHANUMERIC_WITHOUT_DOT).to_string();
let qr = if 0 != group_chat_id {
let qr = if !group_chat_id.is_unset() {
// parameters used: a=g=x=i=s=
if let Ok(chat) = Chat::load_from_db(context, group_chat_id) {
let group_name = chat.get_name();
let group_name_urlencoded =
@@ -118,6 +122,7 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<Str
return None;
}
} else {
// parameters used: a=n=i=s=
Some(format!(
"OPENPGP4FPR:{}#a={}&n={}&i={}&s={}",
fingerprint, self_addr_urlencoded, self_name_urlencoded, &invitenumber, &auth,
@@ -138,36 +143,39 @@ fn get_self_fingerprint(context: &Context) -> Option<String> {
None
}
pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
pub fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId {
let cleanup =
|context: &Context, contact_chat_id: u32, ongoing_allocated: bool, join_vg: bool| {
|context: &Context, contact_chat_id: ChatId, ongoing_allocated: bool, join_vg: bool| {
let mut bob = context.bob.write().unwrap();
bob.expects = 0;
let ret_chat_id = if bob.status == DC_BOB_SUCCESS {
let ret_chat_id: ChatId = if bob.status == DC_BOB_SUCCESS {
if join_vg {
chat::get_chat_id_by_grpid(
context,
bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap(),
)
.unwrap_or((ChatId::new(0), false, Blocked::Not))
.0
} else {
contact_chat_id
}
} else {
0
ChatId::new(0)
};
bob.qr_scan = None;
if ongoing_allocated {
context.free_ongoing();
}
ret_chat_id as u32
ret_chat_id
};
/* ==========================================================
/*========================================================
==== Bob - the joiner's side =====
==== Step 2 in "Setup verified contact" protocol =====
========================================================== */
let mut contact_chat_id: u32 = 0;
========================================================*/
let mut contact_chat_id = ChatId::new(0);
let mut join_vg: bool = false;
info!(context, "Requesting secure-join ...",);
@@ -181,11 +189,13 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
error!(context, "Unknown QR code.",);
return cleanup(&context, contact_chat_id, true, join_vg);
}
contact_chat_id = chat::create_by_contact_id(context, qr_scan.id).unwrap_or_default();
if contact_chat_id == 0 {
error!(context, "Unknown contact.",);
return cleanup(&context, contact_chat_id, true, join_vg);
}
contact_chat_id = match chat::create_by_contact_id(context, qr_scan.id) {
Ok(chat_id) => chat_id,
Err(_) => {
error!(context, "Unknown contact.");
return cleanup(&context, contact_chat_id, true, join_vg);
}
};
if context.shall_stop_ongoing() {
return cleanup(&context, contact_chat_id, true, join_vg);
}
@@ -209,10 +219,14 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
.unwrap(),
contact_chat_id,
) {
// the scanned fingerprint matches Alice's key,
// we can proceed to step 4b) directly and save two mails
info!(context, "Taking protocol shortcut.");
context.bob.write().unwrap().expects = DC_VC_CONTACT_CONFIRM;
joiner_progress!(context, chat_id_2_contact_id(context, contact_chat_id), 400);
let own_fingerprint = get_self_fingerprint(context).unwrap_or_default();
// Bob -> Alice
send_handshake_msg(
context,
contact_chat_id,
@@ -231,6 +245,8 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
);
} else {
context.bob.write().unwrap().expects = DC_VC_AUTH_REQUIRED;
// Bob -> Alice
send_handshake_msg(
context,
contact_chat_id,
@@ -241,7 +257,6 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
);
}
// Bob -> Alice
while !context.shall_stop_ongoing() {
// Don't sleep too long, the user is waiting.
std::thread::sleep(std::time::Duration::from_millis(200));
@@ -251,7 +266,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
fn send_handshake_msg(
context: &Context,
contact_chat_id: u32,
contact_chat_id: ChatId,
step: &str,
param2: impl AsRef<str>,
fingerprint: Option<String>,
@@ -288,7 +303,7 @@ fn send_handshake_msg(
chat::send_msg(context, contact_chat_id, &mut msg).unwrap_or_default();
}
fn chat_id_2_contact_id(context: &Context, contact_chat_id: u32) -> u32 {
fn chat_id_2_contact_id(context: &Context, contact_chat_id: ChatId) -> u32 {
let contacts = chat::get_chat_contacts(context, contact_chat_id);
if contacts.len() == 1 {
contacts[0]
@@ -300,7 +315,7 @@ fn chat_id_2_contact_id(context: &Context, contact_chat_id: u32) -> u32 {
fn fingerprint_equals_sender(
context: &Context,
fingerprint: impl AsRef<str>,
contact_chat_id: u32,
contact_chat_id: ChatId,
) -> bool {
let contacts = chat::get_chat_contacts(context, contact_chat_id);
@@ -319,80 +334,115 @@ fn fingerprint_equals_sender(
}
false
}
pub(crate) struct HandshakeMessageStatus {
pub(crate) hide_this_msg: bool,
pub(crate) delete_this_msg: bool,
pub(crate) stop_ongoing_process: bool,
pub(crate) bob_securejoin_success: Option<bool>,
#[derive(Fail, Debug)]
pub(crate) enum HandshakeError {
#[fail(display = "Can not be called with special contact ID")]
SpecialContactId,
#[fail(display = "Not a Secure-Join message")]
NotSecureJoinMsg,
#[fail(
display = "Failed to look up or create chat for contact #{}",
contact_id
)]
NoChat {
contact_id: u32,
#[cause]
cause: Error,
},
#[fail(display = "Chat for group {} not found", group)]
ChatNotFound { group: String },
#[fail(display = "No configured self address found")]
NoSelfAddr,
}
impl Default for HandshakeMessageStatus {
fn default() -> Self {
Self {
hide_this_msg: true,
delete_this_msg: false,
stop_ongoing_process: false,
bob_securejoin_success: None,
}
}
/// What to do with a Secure-Join handshake message after it was handled.
pub(crate) enum HandshakeMessage {
/// The message has been fully handled and should be removed/delete.
Done,
/// The message should be ignored/hidden, but not removed/deleted.
Ignore,
/// The message should be further processed by incoming message handling.
Propagate,
}
/// Handle incoming secure-join handshake.
///
/// This function will update the securejoin state in [Context::bob]
/// and also terminate the ongoing process using
/// [Context::stop_ongoing] as required by the protocol.
///
/// A message which results in [Err] will be hidden from the user but
/// not deleted, it may be a valid message for something else we are
/// not aware off. E.g. it could be part of a handshake performed by
/// another DC app on the same account.
///
/// When handle_securejoin_handshake() is called,
/// the message is not yet filed in the database;
/// this is done by receive_imf() later on as needed.
pub(crate) fn handle_securejoin_handshake(
context: &Context,
mimeparser: &MimeMessage,
mime_message: &MimeMessage,
contact_id: u32,
) -> Result<HandshakeMessageStatus, Error> {
) -> Result<HandshakeMessage, HandshakeError> {
let own_fingerprint: String;
ensure!(
contact_id > DC_CONTACT_ID_LAST_SPECIAL,
"handle_securejoin_handshake(): called with special contact id"
);
let step = mimeparser
if contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
return Err(HandshakeError::SpecialContactId);
}
let step = mime_message
.get(HeaderDef::SecureJoin)
.ok_or_else(|| format_err!("This message is not a Secure-Join message"))?;
.ok_or(HandshakeError::NotSecureJoinMsg)?;
info!(
context,
">>>>>>>>>>>>>>>>>>>>>>>>> secure-join message \'{}\' received", step,
);
let (contact_chat_id, contact_chat_id_blocked) =
chat::create_or_lookup_by_contact_id(context, contact_id, Blocked::Not).unwrap_or_default();
if contact_chat_id_blocked != Blocked::Not {
chat::unblock(context, contact_chat_id);
}
let contact_chat_id =
match chat::create_or_lookup_by_contact_id(context, contact_id, Blocked::Not) {
Ok((chat_id, blocked)) => {
if blocked != Blocked::Not {
chat::unblock(context, chat_id);
}
chat_id
}
Err(err) => {
return Err(HandshakeError::NoChat {
contact_id,
cause: err,
});
}
};
let join_vg = step.starts_with("vg-");
let mut ret = HandshakeMessageStatus::default();
match step.as_str() {
"vg-request" | "vc-request" => {
/* =========================================================
/*=======================================================
==== Alice - the inviter side ====
==== Step 3 in "Setup verified contact" protocol ====
========================================================= */
// this message may be unencrypted (Bob, the joinder and the sender, might not have Alice's key yet)
=======================================================*/
// this message may be unencrypted (Bob, the joiner and the sender, might not have Alice's key yet)
// it just ensures, we have Bobs key now. If we do _not_ have the key because eg. MitM has removed it,
// send_message() will fail with the error "End-to-end-encryption unavailable unexpectedly.", so, there is no additional check needed here.
// verify that the `Secure-Join-Invitenumber:`-header matches invitenumber written to the QR code
let invitenumber = match mimeparser.get(HeaderDef::SecureJoinInvitenumber) {
let invitenumber = match mime_message.get(HeaderDef::SecureJoinInvitenumber) {
Some(n) => n,
None => {
warn!(context, "Secure-join denied (invitenumber missing).",);
return Ok(ret);
warn!(context, "Secure-join denied (invitenumber missing)");
return Ok(HandshakeMessage::Ignore);
}
};
if !token::exists(context, token::Namespace::InviteNumber, &invitenumber) {
warn!(context, "Secure-join denied (bad invitenumber).",);
return Ok(ret);
warn!(context, "Secure-join denied (bad invitenumber).");
return Ok(HandshakeMessage::Ignore);
}
info!(context, "Secure-join requested.",);
inviter_progress!(context, contact_id, 300);
// Alice -> Bob
send_handshake_msg(
context,
contact_chat_id,
@@ -401,8 +451,15 @@ pub(crate) fn handle_securejoin_handshake(
None,
"",
);
Ok(HandshakeMessage::Done)
}
"vg-auth-required" | "vc-auth-required" => {
/*========================================================
==== Bob - the joiner's side =====
==== Step 4 in "Setup verified contact" protocol =====
========================================================*/
// verify that Alice's Autocrypt key and fingerprint matches the QR-code
let cond = {
let bob = context.bob.read().unwrap();
let scan = bob.qr_scan.as_ref();
@@ -412,26 +469,26 @@ pub(crate) fn handle_securejoin_handshake(
};
if cond {
warn!(context, "auth-required message out of sync.",);
warn!(context, "auth-required message out of sync.");
// no error, just aborted somehow or a mail from another handshake
return Ok(ret);
return Ok(HandshakeMessage::Ignore);
}
let scanned_fingerprint_of_alice = get_qr_attr!(context, fingerprint).to_string();
let auth = get_qr_attr!(context, auth).to_string();
if !encrypted_and_signed(mimeparser, &scanned_fingerprint_of_alice) {
if !encrypted_and_signed(mime_message, &scanned_fingerprint_of_alice) {
could_not_establish_secure_connection(
context,
contact_chat_id,
if mimeparser.was_encrypted() {
if mime_message.was_encrypted() {
"No valid signature."
} else {
"Not encrypted."
},
);
ret.stop_ongoing_process = true;
ret.bob_securejoin_success = Some(false);
return Ok(ret);
context.bob.write().unwrap().status = 0; // secure-join failed
context.stop_ongoing();
return Ok(HandshakeMessage::Ignore);
}
if !fingerprint_equals_sender(context, &scanned_fingerprint_of_alice, contact_chat_id) {
could_not_establish_secure_connection(
@@ -439,15 +496,16 @@ pub(crate) fn handle_securejoin_handshake(
contact_chat_id,
"Fingerprint mismatch on joiner-side.",
);
ret.stop_ongoing_process = true;
ret.bob_securejoin_success = Some(false);
return Ok(ret);
context.bob.write().unwrap().status = 0; // secure-join failed
context.stop_ongoing();
return Ok(HandshakeMessage::Ignore);
}
info!(context, "Fingerprint verified.",);
own_fingerprint = get_self_fingerprint(context).unwrap();
joiner_progress!(context, contact_id, 400);
context.bob.write().unwrap().expects = DC_VC_CONTACT_CONFIRM;
// Bob -> Alice
send_handshake_msg(
context,
contact_chat_id,
@@ -460,15 +518,17 @@ pub(crate) fn handle_securejoin_handshake(
"".to_string()
},
);
Ok(HandshakeMessage::Done)
}
"vg-request-with-auth" | "vc-request-with-auth" => {
/* ============================================================
/*==========================================================
==== Alice - the inviter side ====
==== Steps 5+6 in "Setup verified contact" protocol ====
==== Step 6 in "Out-of-band verified groups" protocol ====
============================================================ */
==========================================================*/
// verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob
let fingerprint = match mimeparser.get(HeaderDef::SecureJoinFingerprint) {
let fingerprint = match mime_message.get(HeaderDef::SecureJoinFingerprint) {
Some(fp) => fp,
None => {
could_not_establish_secure_connection(
@@ -476,16 +536,16 @@ pub(crate) fn handle_securejoin_handshake(
contact_chat_id,
"Fingerprint not provided.",
);
return Ok(ret);
return Ok(HandshakeMessage::Ignore);
}
};
if !encrypted_and_signed(mimeparser, &fingerprint) {
if !encrypted_and_signed(mime_message, &fingerprint) {
could_not_establish_secure_connection(
context,
contact_chat_id,
"Auth not encrypted.",
);
return Ok(ret);
return Ok(HandshakeMessage::Ignore);
}
if !fingerprint_equals_sender(context, &fingerprint, contact_chat_id) {
could_not_establish_secure_connection(
@@ -493,11 +553,11 @@ pub(crate) fn handle_securejoin_handshake(
contact_chat_id,
"Fingerprint mismatch on inviter-side.",
);
return Ok(ret);
return Ok(HandshakeMessage::Ignore);
}
info!(context, "Fingerprint verified.",);
// verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code
let auth_0 = match mimeparser.get(HeaderDef::SecureJoinAuth) {
let auth_0 = match mime_message.get(HeaderDef::SecureJoinAuth) {
Some(auth) => auth,
None => {
could_not_establish_secure_connection(
@@ -505,12 +565,12 @@ pub(crate) fn handle_securejoin_handshake(
contact_chat_id,
"Auth not provided.",
);
return Ok(ret);
return Ok(HandshakeMessage::Ignore);
}
};
if !token::exists(context, token::Namespace::Auth, &auth_0) {
could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid.");
return Ok(ret);
return Ok(HandshakeMessage::Ignore);
}
if mark_peer_as_verified(context, fingerprint).is_err() {
could_not_establish_secure_connection(
@@ -518,7 +578,7 @@ pub(crate) fn handle_securejoin_handshake(
contact_chat_id,
"Fingerprint mismatch on inviter-side.",
);
return Ok(ret);
return Ok(HandshakeMessage::Ignore);
}
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited);
info!(context, "Auth verified.",);
@@ -526,31 +586,47 @@ pub(crate) fn handle_securejoin_handshake(
emit_event!(context, Event::ContactsChanged(Some(contact_id)));
inviter_progress!(context, contact_id, 600);
if join_vg {
let field_grpid = mimeparser
.get(HeaderDef::SecureJoinGroup)
.map(|s| s.as_str())
.unwrap_or_else(|| "");
let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, field_grpid);
if group_chat_id == 0 {
error!(context, "Chat {} not found.", &field_grpid);
return Ok(ret);
} else if let Err(err) =
chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, true)
{
error!(context, "failed to add contact: {}", err);
// the vg-member-added message is special:
// this is a normal Chat-Group-Member-Added message
// with an additional Secure-Join header
let field_grpid = match mime_message.get(HeaderDef::SecureJoinGroup) {
Some(s) => s.as_str(),
None => {
warn!(context, "Missing Secure-Join-Group header");
return Ok(HandshakeMessage::Ignore);
}
};
match chat::get_chat_id_by_grpid(context, field_grpid) {
Ok((group_chat_id, _, _)) => {
if let Err(err) =
chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, true)
{
error!(context, "failed to add contact: {}", err);
}
}
Err(err) => {
error!(context, "Chat {} not found: {}", &field_grpid, err);
return Err(HandshakeError::ChatNotFound {
group: field_grpid.to_string(),
});
}
}
} else {
// Alice -> Bob
send_handshake_msg(context, contact_chat_id, "vc-contact-confirm", "", None, "");
inviter_progress!(context, contact_id, 1000);
}
Ok(HandshakeMessage::Done)
}
"vg-member-added" | "vc-contact-confirm" => {
if join_vg {
ret.hide_this_msg = false;
}
/*=======================================================
==== Bob - the joiner's side ====
==== Step 7 in "Setup verified contact" protocol ====
=======================================================*/
if context.bob.read().unwrap().expects != DC_VC_CONTACT_CONFIRM {
info!(context, "Message belongs to a different handshake.",);
return Ok(ret);
return Ok(HandshakeMessage::Propagate);
}
let cond = {
let bob = context.bob.read().unwrap();
@@ -562,13 +638,19 @@ pub(crate) fn handle_securejoin_handshake(
context,
"Message out of sync or belongs to a different handshake.",
);
return Ok(ret);
return Ok(HandshakeMessage::Propagate);
}
let scanned_fingerprint_of_alice = get_qr_attr!(context, fingerprint).to_string();
let vg_expect_encrypted = if join_vg {
let group_id = get_qr_attr!(context, text2).to_string();
let (_, is_verified_group, _) = chat::get_chat_id_by_grpid(context, group_id);
// This is buggy, is_verified_group will always be
// false since the group is created by receive_imf by
// the very handshake message we're handling now. But
// only after we have returned. It does not impact
// the security invariants of secure-join however.
let (_, is_verified_group, _) = chat::get_chat_id_by_grpid(context, &group_id)
.unwrap_or((ChatId::new(0), false, Blocked::Not));
// when joining a non-verified group
// the vg-member-added message may be unencrypted
// when not all group members have keys or prefer encryption.
@@ -579,15 +661,15 @@ pub(crate) fn handle_securejoin_handshake(
true
};
if vg_expect_encrypted
&& !encrypted_and_signed(mimeparser, &scanned_fingerprint_of_alice)
&& !encrypted_and_signed(mime_message, &scanned_fingerprint_of_alice)
{
could_not_establish_secure_connection(
context,
contact_chat_id,
"Contact confirm message not encrypted.",
);
ret.bob_securejoin_success = Some(false);
return Ok(ret);
context.bob.write().unwrap().status = 0;
return Ok(HandshakeMessage::Propagate);
}
if mark_peer_as_verified(context, &scanned_fingerprint_of_alice).is_err() {
@@ -596,21 +678,26 @@ pub(crate) fn handle_securejoin_handshake(
contact_chat_id,
"Fingerprint mismatch on joiner-side.",
);
return Ok(ret);
return Ok(HandshakeMessage::Propagate);
}
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined);
emit_event!(context, Event::ContactsChanged(None));
let cg_member_added = mimeparser
let cg_member_added = mime_message
.get(HeaderDef::ChatGroupMemberAdded)
.map(|s| s.as_str())
.unwrap_or_else(|| "");
if join_vg && !context.is_self_addr(cg_member_added)? {
if join_vg
&& !context
.is_self_addr(cg_member_added)
.map_err(|_| HandshakeError::NoSelfAddr)?
{
info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group).");
return Ok(ret);
return Ok(HandshakeMessage::Propagate);
}
secure_connection_established(context, contact_chat_id);
context.bob.write().unwrap().expects = 0;
if join_vg {
// Bob -> Alice
send_handshake_msg(
context,
contact_chat_id,
@@ -620,48 +707,52 @@ pub(crate) fn handle_securejoin_handshake(
"",
);
}
ret.stop_ongoing_process = true;
ret.bob_securejoin_success = Some(true);
context.bob.write().unwrap().status = 1;
context.stop_ongoing();
Ok(HandshakeMessage::Propagate)
}
"vg-member-added-received" => {
/* ============================================================
/*==========================================================
==== Alice - the inviter side ====
==== Step 8 in "Out-of-band verified groups" protocol ====
============================================================ */
==========================================================*/
if let Ok(contact) = Contact::get_by_id(context, contact_id) {
if contact.is_verified(context) == VerifiedStatus::Unverified {
warn!(context, "vg-member-added-received invalid.",);
return Ok(ret);
return Ok(HandshakeMessage::Ignore);
}
inviter_progress!(context, contact_id, 800);
inviter_progress!(context, contact_id, 1000);
let field_grpid = mimeparser
let field_grpid = mime_message
.get(HeaderDef::SecureJoinGroup)
.map(|s| s.as_str())
.unwrap_or_else(|| "");
let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid);
let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid)
.map_err(|err| {
warn!(context, "Failed to lookup chat_id from grpid: {}", err);
HandshakeError::ChatNotFound {
group: field_grpid.to_string(),
}
})?;
context.call_cb(Event::SecurejoinMemberAdded {
chat_id: group_chat_id,
contact_id,
});
Ok(HandshakeMessage::Done)
} else {
warn!(context, "vg-member-added-received invalid.",);
return Ok(ret);
Ok(HandshakeMessage::Ignore)
}
}
_ => {
warn!(context, "invalid step: {}", step);
Ok(HandshakeMessage::Ignore)
}
}
if ret.hide_this_msg {
ret.delete_this_msg = true;
}
Ok(ret)
}
fn secure_connection_established(context: &Context, contact_chat_id: u32) {
fn secure_connection_established(context: &Context, contact_chat_id: ChatId) {
let contact_id: u32 = chat_id_2_contact_id(context, contact_chat_id);
let contact = Contact::get_by_id(context, contact_id);
let addr = if let Ok(ref contact) = contact {
@@ -674,7 +765,11 @@ fn secure_connection_established(context: &Context, contact_chat_id: u32) {
emit_event!(context, Event::ChatModified(contact_chat_id));
}
fn could_not_establish_secure_connection(context: &Context, contact_chat_id: u32, details: &str) {
fn could_not_establish_secure_connection(
context: &Context,
contact_chat_id: ChatId,
details: &str,
) {
let contact_id = chat_id_2_contact_id(context, contact_chat_id);
let contact = Contact::get_by_id(context, contact_id);
let msg = context.stock_string_repl_str(

View File

@@ -365,6 +365,7 @@ fn table_exists(conn: &Connection, name: impl AsRef<str>) -> Result<bool> {
Ok(exists)
}
#[allow(clippy::cognitive_complexity)]
fn open(
context: &Context,
sql: &Sql,

View File

@@ -6,6 +6,7 @@
use deltachat_derive::*;
use crate::chat::ChatId;
use crate::context::Context;
use crate::dc_tools::*;
use crate::sql;
@@ -27,28 +28,28 @@ impl Default for Namespace {
/// Creates a new token and saves it into the database.
/// Returns created token.
pub fn save(context: &Context, namespace: Namespace, foreign_id: u32) -> String {
pub fn save(context: &Context, namespace: Namespace, foreign_id: ChatId) -> String {
// foreign_id may be 0
let token = dc_create_id();
sql::execute(
context,
&context.sql,
"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);",
params![namespace, foreign_id as i32, &token, time()],
params![namespace, foreign_id, &token, time()],
)
.ok();
token
}
pub fn lookup(context: &Context, namespace: Namespace, foreign_id: u32) -> Option<String> {
pub fn lookup(context: &Context, namespace: Namespace, foreign_id: ChatId) -> Option<String> {
context.sql.query_get_value::<_, String>(
context,
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;",
params![namespace, foreign_id as i32],
params![namespace, foreign_id],
)
}
pub fn lookup_or_new(context: &Context, namespace: Namespace, foreign_id: u32) -> String {
pub fn lookup_or_new(context: &Context, namespace: Namespace, foreign_id: ChatId) -> String {
lookup(context, namespace, foreign_id).unwrap_or_else(|| save(context, namespace, foreign_id))
}

View File

@@ -1,40 +0,0 @@
#!/usr/bin/env python3
from pathlib import Path
import os
import re
if __name__ == "__main__":
if Path('src/top_evil_rs.py').exists():
os.chdir('src')
filestats = []
for fn in Path(".").glob("**/*.rs"):
s = fn.read_text()
s = re.sub(r"(?m)///.*$", "", s) # remove comments
unsafe = s.count("unsafe")
free = s.count("free(")
unsafe_fn = s.count("unsafe fn")
chars = s.count("c_char") + s.count("CStr")
libc = s.count("libc")
filestats.append((fn, unsafe, free, unsafe_fn, chars, libc))
sum_unsafe, sum_free, sum_unsafe_fn, sum_chars, sum_libc = 0, 0, 0, 0, 0
for fn, unsafe, free, unsafe_fn, chars, libc in reversed(sorted(filestats, key=lambda x: sum(x[1:]))):
if unsafe + free + unsafe_fn + chars + libc == 0:
continue
print("{0: <25} unsafe: {1: >3} free: {2: >3} unsafe-fn: {3: >3} chars: {4: >3} libc: {5: >3}".format(str(fn), unsafe, free, unsafe_fn, chars, libc))
sum_unsafe += unsafe
sum_free += free
sum_unsafe_fn += unsafe_fn
sum_chars += chars
sum_libc += libc
print()
print("total unsafe:", sum_unsafe)
print("total free:", sum_free)
print("total unsafe-fn:", sum_unsafe_fn)
print("total c_chars:", sum_chars)
print("total libc:", sum_libc)

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB