Compare commits

...

32 Commits

Author SHA1 Message Date
bjoern
d0bfb555dd prepare 1.59 (#2614)
* update changelog for 1.59.0

* bump version to 1.59.0
2021-08-20 18:20:06 +02:00
bjoern
6ffaa38b37 add 'device chat about' to now existing status (#2613)
the 'device chat about' was shown as a single message
as at that time, there was just not 'status'.

meanwhile, we have the status option,
and it feels much more natural to get the information there,
esp. as the subtitle on all UIs already read
'Messages in this chat are generated locally' -
and a tap on that will show the hint, without scrolling or so.

this also teaches the user where to find such information -
and the "welcome" chat is less spammy and really starts with the text
"Welcome to Delta Chat!"
2021-08-20 12:30:55 +02:00
bjoern
339d46ecf0 update provider database, add yggmail and mail2tor (#2608)
* allow dotless domains and hostnames

* update provider database

ran ./src/provider/update.py ../provider-db/_providers/ > src/provider/data.rs
to pull in recent changes from https://github.com/deltachat/provider-db
2021-08-20 10:48:27 +02:00
bjoern
5399c9151d Add Quota to Connectivity View (#2612)
* add imap::get_quota_roots()

* schedule quote-checking job on getting connectivity-html

* get quota and debug print it

* basic quota output

* update quota at most once per minute, emit event on changes

* use more meaningful names

* add some comments, move update_recent_quota() to quota.rs

* show root name only if there are several roots

* make clippy happy, some refactorings

* allow only one update-quota job per time

* add now supported QUOTA to standards.md
2021-08-20 10:40:24 +02:00
bjoern
53cd633e8d add migrated accounts to events emitter (#2607)
successor of #2559
closes #2606
2021-08-16 22:10:34 +02:00
link2xt
ade39fe026 fix: do not set WantsMdn param for outgoing messages
This bug sometimes results in sending read receipts to self in
multi-device setups.

It happens consistently in a setup where the first device is
configured to move messages to DeltaChat folder and the second device
is not. When both devices receive BCC-self message simultaneously, the
first device moves the message to DeltaChat folder, while the second
device tries to mark the message as seen in the Inbox. Regardless of
whether the second device marks the message as seen successfully or
fails because the message is already moved by the first device,
`Job.markseen_msg_on_imap()` sends the read receipt to the From
address.
2021-08-15 20:39:28 +03:00
B. Petersen
b8dad1dbaf add support for socket PLAIN coming from provider-db 2021-08-15 20:28:09 +03:00
dependabot[bot]
72d503fa32 Merge pull request #2602 from deltachat/dependabot/cargo/bitflags-1.3.1 2021-08-14 16:07:47 +00:00
Hocuri
223aeb7b1a Fix: Make emails forwarded by GMX readable (#2600)
Recognizing these emails as forwarded would probably be too complicated and require too much special-casing, but now the user can access the email text via "Show full message".

fix #2599

Co-authored-by: B. Petersen <r10s@b44t.com>
2021-08-14 18:05:17 +02:00
dependabot[bot]
b315c6f6d5 cargo: bump bitflags from 1.2.1 to 1.3.1
Bumps [bitflags](https://github.com/bitflags/bitflags) from 1.2.1 to 1.3.1.
- [Release notes](https://github.com/bitflags/bitflags/releases)
- [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitflags/bitflags/compare/1.2.1...1.3.1)

---
updated-dependencies:
- dependency-name: bitflags
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-12 21:16:44 +00:00
Hocuri
481276cf46 In dc_maybe_network_lost() directly set the connectivity "Not connected" (#2551)
* In dc_maybe_network_lost() directly set the connectivity "Not connected"

r10s reported that without doing this, the connectivity would stay at
"Connected" for 16 more seconds after network is gone and
dc_maybe_network_lost() was called.

* Set the state for all connections
2021-08-09 17:25:34 +02:00
Hocuri
faab61b0d4 Connectivity view: Only set smtp to "connected" if the last message was actually sent (#2541)
fix #2539

It's always a bit ambiguous which function should do set_err or set last_send_error, I used this rule here:

If some code returns Status::RetryLater, then it sets last_send_error, because Status::RetryLater means that the user won't see the error directly on the message (if we returned Status::Failed(Err(_)), then the message would be shown as failed to the user) Also, smtp_send always sets last_send_error because, well, sending just failed or succeeded.

Also, remove unused field pending_error.
2021-08-09 12:31:33 +02:00
link2xt
20bf41b4e6 Set timestamps for system messages
Previously system messages were always added to the end of the chat,
even if the message triggering them was sent earlier.  This is
especially important for messages about disappearing timer reset
triggered by classic email messages, as they should be placed right
after the message resetting the timer.
2021-08-08 23:03:38 +03:00
link2xt
5a5b80c960 Resultify get_chat_id_by_grpid and create_or_lookup_mailinglist
Use `Option` instead of `Error` to indicate that no chat ID is found.
2021-08-08 16:26:02 +03:00
Simon Laux
ac245a6cb2 accounts: add get_selected_account_id function 2021-08-07 22:39:48 +03:00
dependabot[bot]
126beb62f3 Merge pull request #2529 from deltachat/dependabot/cargo/libc-0.2.98 2021-08-07 18:10:04 +00:00
dependabot[bot]
d79e4a6571 Merge pull request #2527 from deltachat/dependabot/cargo/thiserror-1.0.26 2021-08-07 15:19:23 +00:00
dependabot[bot]
cadc0b2c00 Merge pull request #2585 from deltachat/dependabot/cargo/serde-1.0.127 2021-08-07 15:04:32 +00:00
dependabot[bot]
87071e6d4b cargo: bump thiserror from 1.0.25 to 1.0.26
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.25 to 1.0.26.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.25...1.0.26)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-07 14:49:28 +00:00
dependabot[bot]
057b004553 cargo: bump serde from 1.0.126 to 1.0.127
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.126 to 1.0.127.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.126...v1.0.127)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-07 14:45:51 +00:00
link2xt
4071fe53a0 Fix clippy warnings in deltachat-ffi 2021-08-07 12:03:25 +00:00
link2xt
c3062976c0 Allow clippy::bool_assert_comparison
assert_eq!(var, false) is easier to read than assert!(!var).
2021-08-07 11:48:51 +00:00
link2xt
85efc0ea26 Fix clippy warnings 2021-08-07 11:47:50 +00:00
link2xt
4ef80aaea5 ci: update Rust and Python versions used for testing
- Use latest stable for rustfmt and clippy.
- Update Rust in rust-toolchain and coredeps Docker container to 1.54.0
  and test against it.
- Test against 1.48.0 to ensure libdeltachat can be compiled with Debian
  bullseye "rustc" and "cargo".
2021-08-07 14:22:54 +03:00
link2xt
0f86800f5d ci: build python wheels automatically for py-* tags 2021-08-07 14:22:54 +03:00
link2xt
9c2035538c dc_receive_imf: use None instead of ChatId::new(0) 2021-08-07 00:37:31 +03:00
link2xt
0276938975 ci: update perl
Old miniperl segfaults on the latest manylinux image
2021-08-07 00:00:00 +03:00
Michael Mc Donnell
ee44a162b6 Skip Gmail labels
Gmail labels are not folders and should be skipped. For example, emails
appear in the inbox and under "All Mail" as soon as it is received. The
code used to wrongly conclude that the email had already been moved and
left it in the inbox.
2021-08-04 01:11:35 +03:00
bjoern
c09a83df2b prepare 1.58 (#2584)
* update changelog for 1.58.0

* bump version to 1.58.0
2021-08-02 21:55:32 +02:00
bjoern
8729d2c4aa also move WAL file when moving database (#2583)
the WAL files come from sqlite WAL mode,
they have the same name as the database file with an extra `-wal` suffix.

according to https://sqlite.org/wal.html#the_wal_file ,
the WAL file is part of the persistent state of the database
and should be kept with the database if the database is copied or moved.
2021-08-02 20:43:46 +02:00
gerryfrancis
fdf3397437 Too many words (#2582) 2021-08-02 15:31:41 +02:00
dependabot[bot]
49fc72fa42 cargo: bump libc from 0.2.97 to 0.2.98
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.97 to 0.2.98.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.97...0.2.98)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-23 23:15:57 +00:00
41 changed files with 1068 additions and 439 deletions

View File

@@ -18,7 +18,7 @@ jobs:
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.50.0
toolchain: stable
override: true
- run: rustup component add rustfmt
- uses: actions-rs/cargo@v1
@@ -32,7 +32,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.50.0
toolchain: stable
components: clippy
override: true
- uses: actions-rs/clippy-check@v1
@@ -68,12 +68,23 @@ jobs:
strategy:
matrix:
include:
# Currently used Rust version, same as in `rust-toolchain` file.
- os: ubuntu-latest
rust: 1.50.0
python: 3.6
rust: 1.54.0
python: 3.9
- os: windows-latest
rust: 1.50.0
rust: 1.54.0
python: false # Python bindings compilation on Windows is not supported.
# Minimum Supported Rust Version = 1.48.0
# This is the Debian "bullseye" release version of Rust.
#
# Minimum Supported Python Version = 3.7
# This is the minimum version for which manylinux Python wheels are
# built.
- os: ubuntu-latest
rust: 1.48.0
python: 3.7
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@master

View File

@@ -1,5 +1,34 @@
# Changelog
## 1.59.0
### Added
- add quota information to `dc_get_connectivity_html()`
### Changes
- refactorings #2592 #2570 #2581
- add 'device chat about' to now existing status #2613
- update provider database #2608
### Fixes
- provider database supports socket=PLAIN and dotless domains now #2604 #2608
- add migrated accounts to events emitter #2607
- fix forwarding quote-only mails #2600
- do not set WantsMdn param for outgoing messages #2603
- set timestamps for system messages #2593
- do not treat gmail labels as folders #2587
- avoid timing problems in `dc_maybe_network_lost()` #2551
- only set smtp to "connected" if the last message was actually sent #2541
## 1.58.0
### Fixes
- move WAL file together with database
and avoid using data if the database was not closed correctly before #2583
## 1.57.0
### API Changes

97
Cargo.lock generated
View File

@@ -1,5 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.15.1"
@@ -236,8 +238,7 @@ dependencies = [
[[package]]
name = "async-imap"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbb2df4b37a99456360a9ab475b723e3a499d51e060ab1bdd8d7565d23dcb74b"
source = "git+https://github.com/async-email/async-imap#4ce7da455618c387b87b2905a80935107bc69afc"
dependencies = [
"async-native-tls",
"async-std",
@@ -248,7 +249,7 @@ dependencies = [
"imap-proto",
"lazy_static",
"log",
"nom 5.1.2",
"nom 6.2.1",
"pin-utils",
"rental",
"stop-token",
@@ -516,9 +517,21 @@ checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
[[package]]
name = "bitflags"
version = "1.2.1"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
checksum = "2da1976d75adbe5fbc88130ecd119529cf1cc6a93ae1546d8696ee66f0d21af1"
[[package]]
name = "bitvec"
version = "0.19.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "block-buffer"
@@ -1107,7 +1120,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.57.0"
version = "1.59.0"
dependencies = [
"ansi_term",
"anyhow",
@@ -1132,6 +1145,7 @@ dependencies = [
"futures",
"futures-lite",
"hex",
"humansize",
"image",
"indexmap",
"itertools 0.10.1",
@@ -1185,7 +1199,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.57.0"
version = "1.59.0"
dependencies = [
"anyhow",
"async-std",
@@ -1584,6 +1598,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "funty"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
[[package]]
name = "futures"
version = "0.3.16"
@@ -1914,6 +1934,12 @@ dependencies = [
"uuid",
]
[[package]]
name = "humansize"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026"
[[package]]
name = "humantime"
version = "1.3.0"
@@ -1959,11 +1985,11 @@ dependencies = [
[[package]]
name = "imap-proto"
version = "0.11.0"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3091b99ee5b80f9b010eb6f962af9495ad06561bf662126b077e8ca30e463182"
checksum = "3ad9b46a79efb6078e578ae04e51463d7c3e8767864687f7e63095b3cbefafbb"
dependencies = [
"nom 5.1.2",
"nom 6.2.1",
]
[[package]]
@@ -2120,9 +2146,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.97"
version = "0.2.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
[[package]]
name = "libm"
@@ -2341,6 +2367,19 @@ dependencies = [
"version_check 0.9.3",
]
[[package]]
name = "nom"
version = "6.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c5c51b9083a3c620fa67a2a635d1ce7d95b897e957d6b28ff9a5da960a103a6"
dependencies = [
"bitvec",
"funty",
"lexical-core",
"memchr",
"version_check 0.9.3",
]
[[package]]
name = "num-bigint"
version = "0.2.6"
@@ -2872,6 +2911,12 @@ dependencies = [
"rusqlite",
]
[[package]]
name = "radium"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
[[package]]
name = "radix_trie"
version = "0.2.1"
@@ -3290,9 +3335,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.126"
version = "1.0.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8"
dependencies = [
"serde_derive",
]
@@ -3309,9 +3354,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.126"
version = "1.0.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc"
dependencies = [
"proc-macro2",
"quote",
@@ -3649,6 +3694,12 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tempfile"
version = "3.2.0"
@@ -3683,18 +3734,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.25"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6"
checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.25"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d"
checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
dependencies = [
"proc-macro2",
"quote",
@@ -4164,6 +4215,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "wyz"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
[[package]]
name = "x25519-dalek"
version = "1.1.0"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.57.0"
version = "1.59.0"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
license = "MPL-2.0"
@@ -16,7 +16,7 @@ deltachat_derive = { path = "./deltachat_derive" }
ansi_term = { version = "0.12.1", optional = true }
anyhow = "1.0.42"
async-imap = "0.5.0"
async-imap = { git = "https://github.com/async-email/async-imap" }
async-native-tls = { version = "0.3.3" }
async-smtp = { git = "https://github.com/async-email/async-smtp", rev="c8800625f7cf29f437143ac7e720ac2730a0962f" }
async-std-resolver = "0.20.3"
@@ -25,7 +25,7 @@ async-tar = "0.3.0"
async-trait = "0.1.50"
backtrace = "0.3.59"
base64 = "0.13"
bitflags = "1.1.0"
bitflags = "1.3.1"
byteorder = "1.3.1"
chrono = "0.4.6"
dirs = { version = "3.0.2", optional=true }
@@ -39,7 +39,7 @@ indexmap = "1.7.0"
itertools = "0.10.1"
kamadak-exif = "0.5"
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
libc = "0.2.97"
libc = "0.2.98"
log = {version = "0.4.8", optional = true }
mailparse = "0.13.5"
native-tls = "0.2.3"
@@ -68,10 +68,11 @@ stop-token = "0.2.0"
strum = "0.21.0"
strum_macros = "0.21.1"
surf = { version = "2.0.0-alpha.4", default-features = false, features = ["h1-client"] }
thiserror = "1.0.25"
thiserror = "1.0.26"
toml = "0.5.6"
url = "2.2.2"
uuid = { version = "0.8", features = ["serde", "v4"] }
humansize = "1.1.1"
[dev-dependencies]
ansi_term = "0.12.0"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.57.0"
version = "1.59.0"
description = "Deltachat FFI"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
@@ -22,7 +22,7 @@ num-traits = "0.2.6"
serde_json = "1.0"
async-std = "1.9.0"
anyhow = "1.0.42"
thiserror = "1.0.25"
thiserror = "1.0.26"
rand = "0.7.3"
[features]

View File

@@ -30,6 +30,6 @@ fn main() {
fs::create_dir_all(target_path.join("pkgconfig")).unwrap();
fs::File::create(target_path.join("pkgconfig").join("deltachat.pc"))
.unwrap()
.write_all(&pkg_config.as_bytes())
.write_all(pkg_config.as_bytes())
.unwrap();
}

View File

@@ -217,7 +217,7 @@ pub unsafe extern "C" fn dc_set_config_from_qr(
let ctx = &*context;
block_on(async move {
match qr::set_config_from_qr(&ctx, &qr).await {
match qr::set_config_from_qr(ctx, &qr).await {
Ok(()) => 1,
Err(err) => {
error!(ctx, "Failed to create account from QR code: {}", err);
@@ -303,7 +303,7 @@ pub unsafe extern "C" fn dc_get_oauth2_url(
let redirect = to_string_lossy(redirect);
block_on(async move {
match oauth2::dc_get_oauth2_url(&ctx, &addr, &redirect).await {
match oauth2::dc_get_oauth2_url(ctx, &addr, &redirect).await {
Some(res) => res.strdup(),
None => ptr::null_mut(),
}
@@ -608,7 +608,7 @@ pub unsafe extern "C" fn dc_preconfigure_keypair(
public,
secret,
};
key::store_self_keypair(&ctx, &keypair, key::KeyPairUse::Default).await?;
key::store_self_keypair(ctx, &keypair, key::KeyPairUse::Default).await?;
Ok::<_, anyhow::Error>(1)
})
.log_err(ctx, "Failed to save keypair")
@@ -632,7 +632,7 @@ pub unsafe extern "C" fn dc_get_chatlist(
let qi = if query_id == 0 { None } else { Some(query_id) };
block_on(async move {
match chatlist::Chatlist::try_load(&ctx, flags as usize, qs.as_deref(), qi).await {
match chatlist::Chatlist::try_load(ctx, flags as usize, qs.as_deref(), qi).await {
Ok(list) => {
let ffi_list = ChatlistWrapper { context, list };
Box::into_raw(Box::new(ffi_list))
@@ -654,7 +654,7 @@ pub unsafe extern "C" fn dc_create_chat_by_contact_id(
let ctx = &*context;
block_on(async move {
ChatId::create_for_contact(&ctx, contact_id)
ChatId::create_for_contact(ctx, contact_id)
.await
.log_err(ctx, "Failed to create chat from contact_id")
.map(|id| id.to_u32())
@@ -674,7 +674,7 @@ pub unsafe extern "C" fn dc_get_chat_id_by_contact_id(
let ctx = &*context;
block_on(async move {
ChatId::lookup_by_contact(&ctx, contact_id)
ChatId::lookup_by_contact(ctx, contact_id)
.await
.log_err(ctx, "Failed to get chat for contact_id")
.unwrap_or_default() // unwraps the Result
@@ -697,9 +697,9 @@ pub unsafe extern "C" fn dc_prepare_msg(
let ffi_msg: &mut MessageWrapper = &mut *msg;
block_on(async move {
chat::prepare_msg(&ctx, ChatId::new(chat_id), &mut ffi_msg.message)
chat::prepare_msg(ctx, ChatId::new(chat_id), &mut ffi_msg.message)
.await
.unwrap_or_log_default(&ctx, "Failed to prepare message")
.unwrap_or_log_default(ctx, "Failed to prepare message")
})
.to_u32()
}
@@ -718,9 +718,9 @@ pub unsafe extern "C" fn dc_send_msg(
let ffi_msg = &mut *msg;
block_on(async move {
chat::send_msg(&ctx, ChatId::new(chat_id), &mut ffi_msg.message)
chat::send_msg(ctx, ChatId::new(chat_id), &mut ffi_msg.message)
.await
.unwrap_or_log_default(&ctx, "Failed to send message")
.unwrap_or_log_default(ctx, "Failed to send message")
})
.to_u32()
}
@@ -739,9 +739,9 @@ pub unsafe extern "C" fn dc_send_msg_sync(
let ffi_msg = &mut *msg;
block_on(async move {
chat::send_msg_sync(&ctx, ChatId::new(chat_id), &mut ffi_msg.message)
chat::send_msg_sync(ctx, ChatId::new(chat_id), &mut ffi_msg.message)
.await
.unwrap_or_log_default(&ctx, "Failed to send message")
.unwrap_or_log_default(ctx, "Failed to send message")
})
.to_u32()
}
@@ -760,10 +760,10 @@ pub unsafe extern "C" fn dc_send_text_msg(
let text_to_send = to_string_lossy(text_to_send);
block_on(async move {
chat::send_text_msg(&ctx, ChatId::new(chat_id), text_to_send)
chat::send_text_msg(ctx, ChatId::new(chat_id), text_to_send)
.await
.map(|msg_id| msg_id.to_u32())
.unwrap_or_log_default(&ctx, "Failed to send text message")
.unwrap_or_log_default(ctx, "Failed to send text message")
})
}
@@ -779,10 +779,10 @@ pub unsafe extern "C" fn dc_send_videochat_invitation(
let ctx = &*context;
block_on(async move {
chat::send_videochat_invitation(&ctx, ChatId::new(chat_id))
chat::send_videochat_invitation(ctx, ChatId::new(chat_id))
.await
.map(|msg_id| msg_id.to_u32())
.unwrap_or_log_default(&ctx, "Failed to send video chat invitation")
.unwrap_or_log_default(ctx, "Failed to send video chat invitation")
})
}
@@ -806,7 +806,7 @@ pub unsafe extern "C" fn dc_set_draft(
block_on(async move {
ChatId::new(chat_id)
.set_draft(&ctx, msg)
.set_draft(ctx, msg)
.await
.unwrap_or_log_default(ctx, "failed to set draft");
});
@@ -831,9 +831,9 @@ pub unsafe extern "C" fn dc_add_device_msg(
};
block_on(async move {
chat::add_device_msg(&ctx, to_opt_string_lossy(label).as_deref(), msg)
chat::add_device_msg(ctx, to_opt_string_lossy(label).as_deref(), msg)
.await
.unwrap_or_log_default(&ctx, "Failed to add device message")
.unwrap_or_log_default(ctx, "Failed to add device message")
})
.to_u32()
}
@@ -850,7 +850,7 @@ pub unsafe extern "C" fn dc_was_device_msg_ever_added(
let ctx = &mut *context;
block_on(async move {
chat::was_device_msg_ever_added(&ctx, &to_string_lossy(label))
chat::was_device_msg_ever_added(ctx, &to_string_lossy(label))
.await
.unwrap_or(false) as libc::c_int
})
@@ -865,7 +865,7 @@ pub unsafe extern "C" fn dc_get_draft(context: *mut dc_context_t, chat_id: u32)
let ctx = &*context;
block_on(async move {
match ChatId::new(chat_id).get_draft(&ctx).await {
match ChatId::new(chat_id).get_draft(ctx).await {
Ok(Some(draft)) => {
let ffi_msg = MessageWrapper {
context,
@@ -902,7 +902,7 @@ pub unsafe extern "C" fn dc_get_chat_msgs(
block_on(async move {
Box::into_raw(Box::new(
chat::get_chat_msgs(&ctx, ChatId::new(chat_id), flags, marker_flag)
chat::get_chat_msgs(ctx, ChatId::new(chat_id), flags, marker_flag)
.await
.unwrap_or_log_default(ctx, "failed to get chat msgs")
.into(),
@@ -920,7 +920,7 @@ pub unsafe extern "C" fn dc_get_msg_cnt(context: *mut dc_context_t, chat_id: u32
block_on(async move {
ChatId::new(chat_id)
.get_msg_cnt(&ctx)
.get_msg_cnt(ctx)
.await
.unwrap_or_log_default(ctx, "failed to get msg count") as libc::c_int
})
@@ -939,7 +939,7 @@ pub unsafe extern "C" fn dc_get_fresh_msg_cnt(
block_on(async move {
ChatId::new(chat_id)
.get_fresh_msg_cnt(&ctx)
.get_fresh_msg_cnt(ctx)
.await
.unwrap_or_log_default(ctx, "failed to get fresh msg cnt") as libc::c_int
})
@@ -996,7 +996,7 @@ pub unsafe extern "C" fn dc_marknoticed_chat(context: *mut dc_context_t, chat_id
let ctx = &*context;
block_on(async move {
chat::marknoticed_chat(&ctx, ChatId::new(chat_id))
chat::marknoticed_chat(ctx, ChatId::new(chat_id))
.await
.log_err(ctx, "Failed marknoticed chat")
.unwrap_or(())
@@ -1033,7 +1033,7 @@ pub unsafe extern "C" fn dc_get_chat_media(
block_on(async move {
Box::into_raw(Box::new(
chat::get_chat_media(
&ctx,
ctx,
ChatId::new(chat_id),
msg_type,
or_msg_type2,
@@ -1074,7 +1074,7 @@ pub unsafe extern "C" fn dc_get_next_media(
block_on(async move {
chat::get_next_media(
&ctx,
ctx,
MsgId::new(msg_id),
direction,
msg_type,
@@ -1106,7 +1106,7 @@ pub unsafe extern "C" fn dc_set_chat_protection(
};
block_on(async move {
match ChatId::new(chat_id).set_protection(&ctx, protect).await {
match ChatId::new(chat_id).set_protection(ctx, protect).await {
Ok(()) => 1,
Err(_) => 0,
}
@@ -1139,7 +1139,7 @@ pub unsafe extern "C" fn dc_set_chat_visibility(
block_on(async move {
ChatId::new(chat_id)
.set_visibility(&ctx, visibility)
.set_visibility(ctx, visibility)
.await
.log_err(ctx, "Failed setting chat visibility")
.unwrap_or(())
@@ -1156,7 +1156,7 @@ pub unsafe extern "C" fn dc_delete_chat(context: *mut dc_context_t, chat_id: u32
block_on(async move {
ChatId::new(chat_id)
.delete(&ctx)
.delete(ctx)
.await
.ok_or_log_msg(ctx, "Failed chat delete");
})
@@ -1172,7 +1172,7 @@ pub unsafe extern "C" fn dc_block_chat(context: *mut dc_context_t, chat_id: u32)
block_on(async move {
ChatId::new(chat_id)
.block(&ctx)
.block(ctx)
.await
.ok_or_log_msg(ctx, "Failed chat block");
})
@@ -1188,7 +1188,7 @@ pub unsafe extern "C" fn dc_accept_chat(context: *mut dc_context_t, chat_id: u32
block_on(async move {
ChatId::new(chat_id)
.accept(&ctx)
.accept(ctx)
.await
.ok_or_log_msg(ctx, "Failed chat accept");
})
@@ -1207,7 +1207,7 @@ pub unsafe extern "C" fn dc_get_chat_contacts(
block_on(async move {
let arr = dc_array_t::from(
chat::get_chat_contacts(&ctx, ChatId::new(chat_id))
chat::get_chat_contacts(ctx, ChatId::new(chat_id))
.await
.unwrap_or_log_default(ctx, "Failed get_chat_contacts"),
);
@@ -1254,7 +1254,7 @@ pub unsafe extern "C" fn dc_get_chat(context: *mut dc_context_t, chat_id: u32) -
let ctx = &*context;
block_on(async move {
match chat::Chat::load_from_db(&ctx, ChatId::new(chat_id)).await {
match chat::Chat::load_from_db(ctx, ChatId::new(chat_id)).await {
Ok(chat) => {
let ffi_chat = ChatWrapper { context, chat };
Box::into_raw(Box::new(ffi_chat))
@@ -1283,7 +1283,7 @@ pub unsafe extern "C" fn dc_create_group_chat(
};
block_on(async move {
chat::create_group_chat(&ctx, protect, &to_string_lossy(name))
chat::create_group_chat(ctx, protect, &to_string_lossy(name))
.await
.log_err(ctx, "Failed to create group chat")
.map(|id| id.to_u32())
@@ -1303,7 +1303,7 @@ pub unsafe extern "C" fn dc_is_contact_in_chat(
}
let ctx = &*context;
block_on(async move { chat::is_contact_in_chat(&ctx, ChatId::new(chat_id), contact_id).await })
block_on(async move { chat::is_contact_in_chat(ctx, ChatId::new(chat_id), contact_id).await })
.into()
}
@@ -1320,7 +1320,7 @@ pub unsafe extern "C" fn dc_add_contact_to_chat(
let ctx = &*context;
block_on(async move {
chat::add_contact_to_chat(&ctx, ChatId::new(chat_id), contact_id).await as libc::c_int
chat::add_contact_to_chat(ctx, ChatId::new(chat_id), contact_id).await as libc::c_int
})
}
@@ -1337,10 +1337,10 @@ pub unsafe extern "C" fn dc_remove_contact_from_chat(
let ctx = &*context;
block_on(async move {
chat::remove_contact_from_chat(&ctx, ChatId::new(chat_id), contact_id)
chat::remove_contact_from_chat(ctx, ChatId::new(chat_id), contact_id)
.await
.map(|_| 1)
.unwrap_or_log_default(&ctx, "Failed to remove contact")
.unwrap_or_log_default(ctx, "Failed to remove contact")
})
}
@@ -1358,10 +1358,10 @@ pub unsafe extern "C" fn dc_set_chat_name(
let ctx = &*context;
block_on(async move {
chat::set_chat_name(&ctx, ChatId::new(chat_id), &to_string_lossy(name))
chat::set_chat_name(ctx, ChatId::new(chat_id), &to_string_lossy(name))
.await
.map(|_| 1)
.unwrap_or_log_default(&ctx, "Failed to set chat name")
.unwrap_or_log_default(ctx, "Failed to set chat name")
})
}
@@ -1378,10 +1378,10 @@ pub unsafe extern "C" fn dc_set_chat_profile_image(
let ctx = &*context;
block_on(async move {
chat::set_chat_profile_image(&ctx, ChatId::new(chat_id), to_string_lossy(image))
chat::set_chat_profile_image(ctx, ChatId::new(chat_id), to_string_lossy(image))
.await
.map(|_| 1)
.unwrap_or_log_default(&ctx, "Failed to set profile image")
.unwrap_or_log_default(ctx, "Failed to set profile image")
})
}
@@ -1412,10 +1412,10 @@ pub unsafe extern "C" fn dc_set_chat_mute_duration(
};
block_on(async move {
chat::set_muted(&ctx, ChatId::new(chat_id), muteDuration)
chat::set_muted(ctx, ChatId::new(chat_id), muteDuration)
.await
.map(|_| 1)
.unwrap_or_log_default(&ctx, "Failed to set mute duration")
.unwrap_or_log_default(ctx, "Failed to set mute duration")
})
}
@@ -1432,11 +1432,11 @@ pub unsafe extern "C" fn dc_get_chat_encrinfo(
block_on(async move {
ChatId::new(chat_id)
.get_encryption_info(&ctx)
.get_encryption_info(ctx)
.await
.map(|s| s.strdup())
.unwrap_or_else(|e| {
error!(&ctx, "{}", e);
error!(ctx, "{}", e);
ptr::null_mut()
})
})
@@ -1497,7 +1497,7 @@ pub unsafe extern "C" fn dc_get_msg_info(
let ctx = &*context;
block_on(async move {
message::get_msg_info(&ctx, MsgId::new(msg_id))
message::get_msg_info(ctx, MsgId::new(msg_id))
.await
.unwrap_or_log_default(ctx, "failed to get msg id")
.strdup()
@@ -1515,7 +1515,7 @@ pub unsafe extern "C" fn dc_get_msg_html(
}
let ctx = &*context;
block_on(MsgId::new(msg_id).get_html(&ctx))
block_on(MsgId::new(msg_id).get_html(ctx))
.unwrap_or_log_default(ctx, "Failed get_msg_html")
.strdup()
}
@@ -1532,7 +1532,7 @@ pub unsafe extern "C" fn dc_get_mime_headers(
let ctx = &*context;
block_on(async move {
let mime = message::get_mime_headers(&ctx, MsgId::new(msg_id))
let mime = message::get_mime_headers(ctx, MsgId::new(msg_id))
.await
.unwrap_or_log_default(ctx, "failed to get mime headers");
if mime.is_empty() {
@@ -1555,7 +1555,7 @@ pub unsafe extern "C" fn dc_delete_msgs(
let ctx = &*context;
let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt);
block_on(message::delete_msgs(&ctx, &msg_ids))
block_on(message::delete_msgs(ctx, &msg_ids))
}
#[no_mangle]
@@ -1577,9 +1577,9 @@ pub unsafe extern "C" fn dc_forward_msgs(
let ctx = &*context;
block_on(async move {
chat::forward_msgs(&ctx, &msg_ids[..], ChatId::new(chat_id))
chat::forward_msgs(ctx, &msg_ids[..], ChatId::new(chat_id))
.await
.unwrap_or_log_default(&ctx, "Failed to forward message")
.unwrap_or_log_default(ctx, "Failed to forward message")
})
}
@@ -1596,7 +1596,7 @@ pub unsafe extern "C" fn dc_markseen_msgs(
let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt);
let ctx = &*context;
block_on(message::markseen_msgs(&ctx, msg_ids))
block_on(message::markseen_msgs(ctx, msg_ids))
.log_err(ctx, "failed dc_markseen_msgs() call")
.ok();
}
@@ -1610,19 +1610,19 @@ pub unsafe extern "C" fn dc_get_msg(context: *mut dc_context_t, msg_id: u32) ->
let ctx = &*context;
block_on(async move {
let message = match message::Message::load_from_db(&ctx, MsgId::new(msg_id)).await {
let message = match message::Message::load_from_db(ctx, MsgId::new(msg_id)).await {
Ok(msg) => msg,
Err(e) => {
if msg_id <= constants::DC_MSG_ID_LAST_SPECIAL {
// C-core API returns empty messages, do the same
warn!(
&ctx,
ctx,
"dc_get_msg called with special msg_id={}, returning empty msg", msg_id
);
message::Message::default()
} else {
error!(
&ctx,
ctx,
"dc_get_msg could not retrieve msg_id {}: {}", msg_id, e
);
return ptr::null_mut();
@@ -1656,7 +1656,7 @@ pub unsafe extern "C" fn dc_lookup_contact_id_by_addr(
let ctx = &*context;
block_on(async move {
Contact::lookup_id_by_addr(&ctx, to_string_lossy(addr), Origin::IncomingReplyTo)
Contact::lookup_id_by_addr(ctx, to_string_lossy(addr), Origin::IncomingReplyTo)
.await
.unwrap_or_log_default(ctx, "failed to lookup id")
.unwrap_or(0)
@@ -1677,7 +1677,7 @@ pub unsafe extern "C" fn dc_create_contact(
let name = to_string_lossy(name);
block_on(async move {
Contact::create(&ctx, &name, &to_string_lossy(addr))
Contact::create(ctx, &name, &to_string_lossy(addr))
.await
.unwrap_or(0)
})
@@ -1695,7 +1695,7 @@ pub unsafe extern "C" fn dc_add_address_book(
let ctx = &*context;
block_on(async move {
match Contact::add_address_book(&ctx, &to_string_lossy(addr_book)).await {
match Contact::add_address_book(ctx, &to_string_lossy(addr_book)).await {
Ok(cnt) => cnt as libc::c_int,
Err(_) => 0,
}
@@ -1716,7 +1716,7 @@ pub unsafe extern "C" fn dc_get_contacts(
let query = to_opt_string_lossy(query);
block_on(async move {
match Contact::get_all(&ctx, flags, query).await {
match Contact::get_all(ctx, flags, query).await {
Ok(contacts) => Box::into_raw(Box::new(dc_array_t::from(contacts))),
Err(_) => ptr::null_mut(),
}
@@ -1732,7 +1732,7 @@ pub unsafe extern "C" fn dc_get_blocked_cnt(context: *mut dc_context_t) -> libc:
let ctx = &*context;
block_on(async move {
Contact::get_all_blocked(&ctx)
Contact::get_all_blocked(ctx)
.await
.unwrap_or_log_default(ctx, "failed to get blocked count")
.len() as libc::c_int
@@ -1751,9 +1751,9 @@ pub unsafe extern "C" fn dc_get_blocked_contacts(
block_on(async move {
Box::into_raw(Box::new(dc_array_t::from(
Contact::get_all_blocked(&ctx)
Contact::get_all_blocked(ctx)
.await
.log_err(&ctx, "Can't get blocked contacts")
.log_err(ctx, "Can't get blocked contacts")
.unwrap_or_default(),
)))
})
@@ -1772,13 +1772,13 @@ pub unsafe extern "C" fn dc_block_contact(
let ctx = &*context;
block_on(async move {
if block == 0 {
Contact::unblock(&ctx, contact_id)
Contact::unblock(ctx, contact_id)
.await
.ok_or_log_msg(&ctx, "Can't unblock contact");
.ok_or_log_msg(ctx, "Can't unblock contact");
} else {
Contact::block(&ctx, contact_id)
Contact::block(ctx, contact_id)
.await
.ok_or_log_msg(&ctx, "Can't block contact");
.ok_or_log_msg(ctx, "Can't block contact");
}
});
}
@@ -1795,11 +1795,11 @@ pub unsafe extern "C" fn dc_get_contact_encrinfo(
let ctx = &*context;
block_on(async move {
Contact::get_encrinfo(&ctx, contact_id)
Contact::get_encrinfo(ctx, contact_id)
.await
.map(|s| s.strdup())
.unwrap_or_else(|e| {
error!(&ctx, "{}", e);
error!(ctx, "{}", e);
ptr::null_mut()
})
})
@@ -1817,7 +1817,7 @@ pub unsafe extern "C" fn dc_delete_contact(
let ctx = &*context;
block_on(async move {
match Contact::delete(&ctx, contact_id).await {
match Contact::delete(ctx, contact_id).await {
Ok(_) => 1,
Err(_) => 0,
}
@@ -1836,7 +1836,7 @@ pub unsafe extern "C" fn dc_get_contact(
let ctx = &*context;
block_on(async move {
Contact::get_by_id(&ctx, contact_id)
Contact::get_by_id(ctx, contact_id)
.await
.map(|contact| Box::into_raw(Box::new(ContactWrapper { context, contact })))
.unwrap_or_else(|_| ptr::null_mut())
@@ -1866,7 +1866,7 @@ pub unsafe extern "C" fn dc_imex(
if let Some(param1) = to_opt_string_lossy(param1) {
spawn(async move {
imex::imex(&ctx, what, param1.as_ref())
imex::imex(ctx, what, param1.as_ref())
.await
.log_err(ctx, "IMEX failed")
});
@@ -1887,12 +1887,12 @@ pub unsafe extern "C" fn dc_imex_has_backup(
let ctx = &*context;
block_on(async move {
match imex::has_backup(&ctx, to_string_lossy(dir).as_ref()).await {
match imex::has_backup(ctx, to_string_lossy(dir).as_ref()).await {
Ok(res) => res.strdup(),
Err(err) => {
// do not bubble up error to the user,
// the ui will expect that the file does not exist or cannot be accessed
warn!(&ctx, "dc_imex_has_backup: {}", err);
warn!(ctx, "dc_imex_has_backup: {}", err);
ptr::null_mut()
}
}
@@ -1908,10 +1908,10 @@ pub unsafe extern "C" fn dc_initiate_key_transfer(context: *mut dc_context_t) ->
let ctx = &*context;
block_on(async move {
match imex::initiate_key_transfer(&ctx).await {
match imex::initiate_key_transfer(ctx).await {
Ok(res) => res.strdup(),
Err(err) => {
error!(&ctx, "dc_initiate_key_transfer(): {}", err);
error!(ctx, "dc_initiate_key_transfer(): {}", err);
ptr::null_mut()
}
}
@@ -1934,12 +1934,12 @@ pub unsafe extern "C" fn dc_continue_key_transfer(
let ctx = &*context;
block_on(async move {
match imex::continue_key_transfer(&ctx, MsgId::new(msg_id), &to_string_lossy(setup_code))
match imex::continue_key_transfer(ctx, MsgId::new(msg_id), &to_string_lossy(setup_code))
.await
{
Ok(()) => 1,
Err(err) => {
warn!(&ctx, "dc_continue_key_transfer: {}", err);
warn!(ctx, "dc_continue_key_transfer: {}", err);
0
}
}
@@ -1968,7 +1968,7 @@ pub unsafe extern "C" fn dc_check_qr(
let ctx = &*context;
block_on(async move {
let lot = qr::check_qr(&ctx, &to_string_lossy(qr)).await;
let lot = qr::check_qr(ctx, &to_string_lossy(qr)).await;
Box::into_raw(Box::new(lot))
})
}
@@ -1990,7 +1990,7 @@ pub unsafe extern "C" fn dc_get_securejoin_qr(
};
block_on(async move {
securejoin::dc_get_securejoin_qr(&ctx, chat_id)
securejoin::dc_get_securejoin_qr(ctx, chat_id)
.await
.unwrap_or_else(|| "".to_string())
.strdup()
@@ -2009,7 +2009,7 @@ pub unsafe extern "C" fn dc_join_securejoin(
let ctx = &*context;
block_on(async move {
securejoin::dc_join_securejoin(&ctx, &to_string_lossy(qr))
securejoin::dc_join_securejoin(ctx, &to_string_lossy(qr))
.await
.map(|chatid| chatid.to_u32())
.log_err(ctx, "failed dc_join_securejoin() call")
@@ -2030,7 +2030,7 @@ pub unsafe extern "C" fn dc_send_locations_to_chat(
let ctx = &*context;
block_on(location::send_locations_to_chat(
&ctx,
ctx,
ChatId::new(chat_id),
seconds as i64,
));
@@ -2052,7 +2052,7 @@ pub unsafe extern "C" fn dc_is_sending_locations_to_chat(
Some(ChatId::new(chat_id))
};
block_on(location::is_sending_locations_to_chat(&ctx, chat_id)) as libc::c_int
block_on(location::is_sending_locations_to_chat(ctx, chat_id)) as libc::c_int
}
#[no_mangle]
@@ -2068,7 +2068,7 @@ pub unsafe extern "C" fn dc_set_location(
}
let ctx = &*context;
block_on(location::set(&ctx, latitude, longitude, accuracy)) as _
block_on(location::set(ctx, latitude, longitude, accuracy)) as _
}
#[no_mangle]
@@ -2097,7 +2097,7 @@ pub unsafe extern "C" fn dc_get_locations(
block_on(async move {
let res = location::get_range(
&ctx,
ctx,
chat_id,
contact_id,
timestamp_begin as i64,
@@ -2118,7 +2118,7 @@ pub unsafe extern "C" fn dc_delete_all_locations(context: *mut dc_context_t) {
let ctx = &*context;
block_on(async move {
location::delete_all(&ctx)
location::delete_all(ctx)
.await
.log_err(ctx, "Failed to delete locations")
.ok()
@@ -2385,7 +2385,7 @@ pub unsafe extern "C" fn dc_chatlist_get_summary(
block_on(async move {
let lot = ffi_list
.list
.get_summary(&ctx, index as usize, maybe_chat)
.get_summary(ctx, index as usize, maybe_chat)
.await
.log_err(ctx, "get_summary failed")
.unwrap_or_default();
@@ -2410,7 +2410,7 @@ pub unsafe extern "C" fn dc_chatlist_get_summary2(
Some(MsgId::new(msg_id))
};
block_on(async move {
let lot = Chatlist::get_summary2(&ctx, ChatId::new(chat_id), msg_id, None)
let lot = Chatlist::get_summary2(ctx, ChatId::new(chat_id), msg_id, None)
.await
.log_err(ctx, "get_summary2 failed")
.unwrap_or_default();
@@ -2496,7 +2496,7 @@ pub unsafe extern "C" fn dc_chat_get_profile_image(chat: *mut dc_chat_t) -> *mut
let ctx = &*ffi_chat.context;
block_on(async move {
match ffi_chat.chat.get_profile_image(&ctx).await {
match ffi_chat.chat.get_profile_image(ctx).await {
Ok(Some(p)) => p.to_string_lossy().strdup(),
Ok(None) => ptr::null_mut(),
Err(err) => {
@@ -2516,7 +2516,7 @@ pub unsafe extern "C" fn dc_chat_get_color(chat: *mut dc_chat_t) -> u32 {
let ffi_chat = &*chat;
let ctx = &*ffi_chat.context;
block_on(ffi_chat.chat.get_color(&ctx)).unwrap_or_log_default(ctx, "Failed get_color")
block_on(ffi_chat.chat.get_color(ctx)).unwrap_or_log_default(ctx, "Failed get_color")
}
#[no_mangle]
@@ -2647,25 +2647,25 @@ pub unsafe extern "C" fn dc_chat_get_info_json(
let ctx = &*context;
block_on(async move {
let chat = match chat::Chat::load_from_db(&ctx, ChatId::new(chat_id)).await {
let chat = match chat::Chat::load_from_db(ctx, ChatId::new(chat_id)).await {
Ok(chat) => chat,
Err(err) => {
error!(&ctx, "dc_get_chat_info_json() failed to load chat: {}", err);
error!(ctx, "dc_get_chat_info_json() failed to load chat: {}", err);
return "".strdup();
}
};
let info = match chat.get_info(&ctx).await {
let info = match chat.get_info(ctx).await {
Ok(info) => info,
Err(err) => {
error!(
&ctx,
ctx,
"dc_get_chat_info_json() failed to get chat info: {}", err
);
return "".strdup();
}
};
serde_json::to_string(&info)
.unwrap_or_log_default(&ctx, "dc_get_chat_info_json() failed to serialise to json")
.unwrap_or_log_default(ctx, "dc_get_chat_info_json() failed to serialise to json")
.strdup()
})
}
@@ -2866,7 +2866,7 @@ pub unsafe extern "C" fn dc_msg_get_filebytes(msg: *mut dc_msg_t) -> u64 {
let ffi_msg = &*msg;
let ctx = &*ffi_msg.context;
block_on(ffi_msg.message.get_filebytes(&ctx))
block_on(ffi_msg.message.get_filebytes(ctx))
}
#[no_mangle]
@@ -2958,7 +2958,7 @@ pub unsafe extern "C" fn dc_msg_get_summary(
let ctx = &*ffi_msg.context;
block_on(async move {
let lot = ffi_msg.message.get_summary(&ctx, maybe_chat).await;
let lot = ffi_msg.message.get_summary(ctx, maybe_chat).await;
Box::into_raw(Box::new(lot))
})
}
@@ -2978,7 +2978,7 @@ pub unsafe extern "C" fn dc_msg_get_summarytext(
block_on({
ffi_msg
.message
.get_summarytext(&ctx, approx_characters.try_into().unwrap_or_default())
.get_summarytext(ctx, approx_characters.try_into().unwrap_or_default())
})
.strdup()
}
@@ -3118,7 +3118,7 @@ pub unsafe extern "C" fn dc_msg_get_setupcodebegin(msg: *mut dc_msg_t) -> *mut l
let ffi_msg = &*msg;
let ctx = &*ffi_msg.context;
block_on(ffi_msg.message.get_setupcodebegin(&ctx))
block_on(ffi_msg.message.get_setupcodebegin(ctx))
.unwrap_or_default()
.strdup()
}
@@ -3230,7 +3230,7 @@ pub unsafe extern "C" fn dc_msg_latefiling_mediasize(
block_on({
ffi_msg
.message
.latefiling_mediasize(&ctx, width, height, duration)
.latefiling_mediasize(ctx, width, height, duration)
});
}
@@ -3410,7 +3410,7 @@ pub unsafe extern "C" fn dc_contact_get_profile_image(
block_on(async move {
ffi_contact
.contact
.get_profile_image(&ctx)
.get_profile_image(ctx)
.await
.unwrap_or_log_default(ctx, "failed to get profile image")
.map(|p| p.to_string_lossy().strdup())
@@ -3457,7 +3457,7 @@ pub unsafe extern "C" fn dc_contact_is_verified(contact: *mut dc_contact_t) -> l
let ffi_contact = &*contact;
let ctx = &*ffi_contact.context;
block_on(async move { ffi_contact.contact.is_verified(&ctx).await as libc::c_int })
block_on(async move { ffi_contact.contact.is_verified(ctx).await as libc::c_int })
}
// dc_lot_t

View File

@@ -17,15 +17,12 @@ use std::ptr;
/// }
/// ```
unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
let ret: *mut libc::c_char;
if !s.is_null() {
ret = libc::strdup(s);
assert!(!ret.is_null());
let ret: *mut libc::c_char = if !s.is_null() {
libc::strdup(s)
} else {
ret = libc::calloc(1, 1) as *mut libc::c_char;
assert!(!ret.is_null());
}
libc::calloc(1, 1) as *mut libc::c_char
};
assert!(!ret.is_null());
ret
}

View File

@@ -1 +1 @@
1.50.0
1.54.0

View File

@@ -6,6 +6,14 @@ resources:
branch: master
uri: https://github.com/deltachat/deltachat-core-rust.git
- name: deltachat-core-rust-release
type: git
icon: github
source:
branch: master
uri: https://github.com/deltachat/deltachat-core-rust.git
tag_filter: "py-*"
jobs:
- name: doxygen
plan:
@@ -58,12 +66,15 @@ jobs:
- name: python-x86_64
plan:
- get: deltachat-core-rust
- get: deltachat-core-rust-release
trigger: true
# Build manylinux image with additional dependencies
- task: build-coredeps
privileged: true
config:
inputs:
# Building the latest, not tagged coredeps
- name: deltachat-core-rust
image_resource:
source:
@@ -86,7 +97,7 @@ jobs:
image: coredeps-image
config:
inputs:
- name: deltachat-core-rust
- name: deltachat-core-rust-release
path: .
outputs:
- name: py-docs
@@ -154,12 +165,15 @@ jobs:
- name: python-aarch64
plan:
- get: deltachat-core-rust
- get: deltachat-core-rust-release
trigger: true
# Build manylinux image with additional dependencies
- task: build-coredeps
privileged: true
config:
inputs:
# Building the latest, not tagged coredeps
- name: deltachat-core-rust
image_resource:
source:
@@ -182,7 +196,7 @@ jobs:
image: coredeps-image
config:
inputs:
- name: deltachat-core-rust
- name: deltachat-core-rust-release
path: .
outputs:
- name: py-wheels

View File

@@ -1,7 +1,7 @@
#!/bin/bash
PERL_VERSION=5.30.0
# PERL_SHA256=7e929f64d4cb0e9d1159d4a59fc89394e27fa1f7004d0836ca0d514685406ea8
PERL_VERSION=5.34.0
# PERL_SHA256=551efc818b968b05216024fb0b727ef2ad4c100f8cb6b43fab615fa78ae5be9a
curl -O https://www.cpan.org/src/5.0/perl-${PERL_VERSION}.tar.gz
# echo "${PERL_SHA256} perl-${PERL_VERSION}.tar.gz" | sha256sum -c -
tar -xzf perl-${PERL_VERSION}.tar.gz

View File

@@ -8,9 +8,11 @@ set -e -x
#
# Avoid using rustup here as it depends on reading /proc/self/exe and
# has problems running under QEMU.
curl "https://static.rust-lang.org/dist/rust-1.52.1-$(uname -m)-unknown-linux-gnu.tar.gz" | tar xz
cd "rust-1.52.1-$(uname -m)-unknown-linux-gnu"
RUST_VERSION=1.54.0
curl "https://static.rust-lang.org/dist/rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu.tar.gz" | tar xz
cd "rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu"
./install.sh --prefix=/usr --components=rustc,cargo,"rust-std-$(uname -m)-unknown-linux-gnu"
rustc --version
cd ..
rm -fr "rust-1.52.1-$(uname -m)-unknown-linux-gnu"
rm -fr "rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu"

View File

@@ -1,7 +1,7 @@
#!/bin/bash
PERL_VERSION=5.30.0
# PERL_SHA256=7e929f64d4cb0e9d1159d4a59fc89394e27fa1f7004d0836ca0d514685406ea8
PERL_VERSION=5.34.0
# PERL_SHA256=551efc818b968b05216024fb0b727ef2ad4c100f8cb6b43fab615fa78ae5be9a
curl -O https://www.cpan.org/src/5.0/perl-${PERL_VERSION}.tar.gz
# echo "${PERL_SHA256} perl-${PERL_VERSION}.tar.gz" | sha256sum -c -
tar -xzf perl-${PERL_VERSION}.tar.gz

View File

@@ -3,9 +3,16 @@
set -e -x
# Install Rust
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain "1.50.0-$(uname -m)-unknown-linux-gnu" -y
export PATH=/root/.cargo/bin:$PATH
rustc --version
#
# Path from https://forge.rust-lang.org/infra/other-installation-methods.html
#
# Avoid using rustup here as it depends on reading /proc/self/exe and
# has problems running under QEMU.
RUST_VERSION=1.54.0
# remove some 300-400 MB that we don't need for automated builds
rm -rf "/root/.rustup/toolchains/1.50.0-$(uname -m)-unknown-linux-gnu/share"
curl "https://static.rust-lang.org/dist/rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu.tar.gz" | tar xz
cd "rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu"
./install.sh --prefix=/usr --components=rustc,cargo,"rust-std-$(uname -m)-unknown-linux-gnu"
rustc --version
cd ..
rm -fr "rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu"

View File

@@ -78,6 +78,14 @@ impl Accounts {
self.accounts.read().await.get(&id).cloned()
}
/// Returns the currently selected account's id or None if no account is selected.
pub async fn get_selected_account_id(&self) -> Option<u32> {
match self.config.get_selected_account().await {
0 => None,
id => Some(id),
}
}
/// Select the given account.
pub async fn select_account(&self, id: u32) -> Result<()> {
self.config.select_account(id).await?;
@@ -118,6 +126,7 @@ impl Accounts {
/// Migrate an existing account into this structure.
pub async fn migrate_account(&self, dbfile: PathBuf) -> Result<u32> {
let blobdir = Context::derive_blobdir(&dbfile);
let walfile = Context::derive_walfile(&dbfile);
ensure!(
dbfile.exists().await,
@@ -141,6 +150,7 @@ impl Accounts {
let new_dbfile = account_config.dbfile().into();
let new_blobdir = Context::derive_blobdir(&new_dbfile);
let new_walfile = Context::derive_walfile(&new_dbfile);
let res = {
fs::create_dir_all(&account_config.dir)
@@ -152,6 +162,11 @@ impl Accounts {
fs::rename(&blobdir, &new_blobdir)
.await
.context("failed to rename blobdir")?;
if walfile.exists().await {
fs::rename(&walfile, &new_walfile)
.await
.context("failed to rename walfile")?;
}
Ok(())
};
@@ -164,6 +179,7 @@ impl Accounts {
account_config.id,
)
.await?;
self.emitter.add_account(&ctx).await?;
self.accounts.write().await.insert(account_config.id, ctx);
Ok(account_config.id)
}

View File

@@ -381,7 +381,14 @@ impl ChatId {
msg.param.set_cmd(cmd);
send_msg(context, self, &mut msg).await?;
} else {
add_info_msg_with_cmd(context, self, msg_text, cmd).await?;
add_info_msg_with_cmd(
context,
self,
msg_text,
cmd,
dc_create_smeared_timestamp(context).await,
)
.await?;
}
Ok(())
@@ -1157,7 +1164,7 @@ impl Chat {
}
// the whole list of messages referenced may be huge;
// only use the oldest and and the parent message
// only use the oldest and the parent message
let parent_references = parent_references
.find(' ')
.and_then(|n| parent_references.get(..n))
@@ -1313,7 +1320,7 @@ impl rusqlite::types::FromSql for ChatVisibility {
2 => ChatVisibility::Pinned,
1 => ChatVisibility::Archived,
0 => ChatVisibility::Normal,
// fallback to to Normal for unknown values, may happen eg. on imports created by a newer version.
// fallback to Normal for unknown values, may happen eg. on imports created by a newer version.
_ => ChatVisibility::Normal,
}
})
@@ -2808,10 +2815,10 @@ pub(crate) async fn get_chat_cnt(context: &Context) -> Result<usize> {
pub(crate) async fn get_chat_id_by_grpid(
context: &Context,
grpid: impl AsRef<str>,
) -> Result<(ChatId, bool, Blocked)> {
) -> Result<Option<(ChatId, bool, Blocked)>> {
context
.sql
.query_row(
.query_row_optional(
"SELECT id, blocked, protected FROM chats WHERE grpid=?;",
paramsv![grpid.as_ref()],
|row| {
@@ -2981,6 +2988,7 @@ pub(crate) async fn add_info_msg_with_cmd(
chat_id: ChatId,
text: impl AsRef<str>,
cmd: SystemMessage,
timestamp: i64,
) -> Result<MsgId> {
let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device");
let ephemeral_timer = chat_id.get_ephemeral_timer(context).await?;
@@ -2997,7 +3005,7 @@ pub(crate) async fn add_info_msg_with_cmd(
chat_id,
DC_CONTACT_ID_INFO,
DC_CONTACT_ID_INFO,
dc_create_smeared_timestamp(context).await,
timestamp,
Viewtype::Text,
MessageState::InNoticed,
text.as_ref().to_string(),
@@ -3012,8 +3020,16 @@ pub(crate) async fn add_info_msg_with_cmd(
Ok(msg_id)
}
pub(crate) async fn add_info_msg(context: &Context, chat_id: ChatId, text: impl AsRef<str>) {
if let Err(e) = add_info_msg_with_cmd(context, chat_id, text, SystemMessage::Unknown).await {
/// Adds info message with a given text and `timestamp` to the chat.
pub(crate) async fn add_info_msg(
context: &Context,
chat_id: ChatId,
text: impl AsRef<str>,
timestamp: i64,
) {
if let Err(e) =
add_info_msg_with_cmd(context, chat_id, text, SystemMessage::Unknown, timestamp).await
{
warn!(context, "Could not add info msg: {}", e);
}
}
@@ -3637,7 +3653,7 @@ mod tests {
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo")
.await
.unwrap();
add_info_msg(&t, chat_id, "foo info").await;
add_info_msg(&t, chat_id, "foo info", 200000).await;
let msg = t.get_last_msg_in(chat_id).await;
assert_eq!(msg.get_chat_id(), chat_id);
@@ -3658,6 +3674,7 @@ mod tests {
chat_id,
"foo bar info",
SystemMessage::EphemeralTimerChanged,
10000,
)
.await
.unwrap();

View File

@@ -218,6 +218,7 @@ impl Contact {
} else if contact_id == DC_CONTACT_ID_DEVICE {
contact.name = stock_str::device_messages(context).await;
contact.addr = DC_CONTACT_ID_DEVICE_ADDR.to_string();
contact.status = stock_str::device_messages_hint(context).await;
}
Ok(contact)
}
@@ -1202,7 +1203,8 @@ WHERE type=? AND id IN (
// also unblock mailinglist
// if the contact is a mailinglist address explicitly created to allow unblocking
if !new_blocking && contact.origin == Origin::MailinglistAddress {
if let Ok((chat_id, _, _)) = chat::get_chat_id_by_grpid(context, contact.addr).await {
if let Some((chat_id, _, _)) = chat::get_chat_id_by_grpid(context, contact.addr).await?
{
chat_id.unblock(context).await?;
}
}

View File

@@ -22,6 +22,7 @@ use crate::events::{Event, EventEmitter, EventType, Events};
use crate::key::{DcKey, SignedPublicKey};
use crate::login_param::LoginParam;
use crate::message::{self, MessageState, MsgId};
use crate::quota::QuotaInfo;
use crate::scheduler::Scheduler;
use crate::securejoin::Bob;
use crate::sql::Sql;
@@ -62,6 +63,10 @@ pub struct InnerContext {
pub(crate) scheduler: RwLock<Scheduler>,
pub(crate) ephemeral_task: RwLock<Option<task::JoinHandle<()>>>,
/// Recently loaded quota information, if any.
/// Set to `None` if quota was never tried to load.
pub(crate) quota: RwLock<Option<QuotaInfo>>,
pub(crate) last_full_folder_scan: Mutex<Option<Instant>>,
/// ID for this `Context` in the current process.
@@ -139,6 +144,7 @@ impl Context {
events: Events::default(),
scheduler: RwLock::new(Scheduler::Stopped),
ephemeral_task: RwLock::new(None),
quota: RwLock::new(None),
creation_time: std::time::SystemTime::now(),
last_full_folder_scan: Mutex::new(None),
};
@@ -559,6 +565,13 @@ impl Context {
blob_fname.push("-blobs");
dbfile.with_file_name(blob_fname)
}
pub fn derive_walfile(dbfile: &PathBuf) -> PathBuf {
let mut wal_fname = OsString::new();
wal_fname.push(dbfile.file_name().unwrap_or_default());
wal_fname.push("-wal");
dbfile.with_file_name(wal_fname)
}
}
impl InnerContext {

View File

@@ -371,7 +371,7 @@ async fn add_parts(
prevent_rename: bool,
) -> Result<ChatId> {
let mut state: MessageState;
let mut chat_id = ChatId::new(0);
let mut chat_id = None;
let mut chat_id_blocked = Blocked::Not;
let mut incoming_origin = incoming_origin;
@@ -400,7 +400,7 @@ async fn add_parts(
match show_emails {
ShowEmails::Off => {
info!(context, "Classical email not shown (TRASH)");
chat_id = DC_CHAT_ID_TRASH;
chat_id = Some(DC_CHAT_ID_TRASH);
allow_creation = false;
}
ShowEmails::AcceptedContacts => allow_creation = false,
@@ -425,7 +425,7 @@ async fn add_parts(
// handshake may mark contacts as verified and must be processed before chats are created
if mime_parser.get(HeaderDef::SecureJoin).is_some() {
is_dc_message = MessengerMessage::Yes; // avoid discarding by show_emails setting
chat_id = ChatId::new(0);
chat_id = None;
allow_creation = true;
match handle_securejoin_handshake(context, mime_parser, from_id).await {
Ok(securejoin::HandshakeMessage::Done) => {
@@ -441,9 +441,8 @@ async fn add_parts(
// process messages as "member added" normally
}
Err(err) => {
*hidden = true;
warn!(context, "Error in Secure-Join message handling: {}", err);
return Ok(chat_id);
return Ok(DC_CHAT_ID_TRASH);
}
}
}
@@ -452,21 +451,23 @@ async fn add_parts(
.await
.unwrap_or_default();
if chat_id.is_unset() && mime_parser.failure_report.is_some() {
chat_id = DC_CHAT_ID_TRASH;
if chat_id.is_none() && mime_parser.failure_report.is_some() {
chat_id = Some(DC_CHAT_ID_TRASH);
info!(context, "Message belongs to an NDN (TRASH)",);
}
if chat_id.is_unset() {
if chat_id.is_none() {
// try to assign to a chat based on In-Reply-To/References:
let (new_chat_id, new_chat_id_blocked) =
lookup_chat_by_reply(context, &mime_parser, &parent, from_id, to_ids).await?;
chat_id = new_chat_id;
chat_id_blocked = new_chat_id_blocked;
if let Some((new_chat_id, new_chat_id_blocked)) =
lookup_chat_by_reply(context, &mime_parser, &parent, from_id, to_ids).await?
{
chat_id = Some(new_chat_id);
chat_id_blocked = new_chat_id_blocked;
}
}
if chat_id.is_unset() {
if chat_id.is_none() {
// try to create a group
let create_blocked = match test_normal_chat {
@@ -477,7 +478,7 @@ async fn add_parts(
_ => Blocked::Request,
};
let (new_chat_id, new_chat_id_blocked) = create_or_lookup_group(
if let Some((new_chat_id, new_chat_id_blocked)) = create_or_lookup_group(
context,
&mut mime_parser,
if test_normal_chat.is_none() {
@@ -489,63 +490,69 @@ async fn add_parts(
from_id,
to_ids,
)
.await?;
chat_id = new_chat_id;
chat_id_blocked = new_chat_id_blocked;
if !chat_id.is_unset()
&& chat_id_blocked != Blocked::Not
&& create_blocked == Blocked::Not
.await?
{
new_chat_id.unblock(context).await?;
chat_id_blocked = Blocked::Not;
chat_id = Some(new_chat_id);
chat_id_blocked = new_chat_id_blocked;
if chat_id_blocked != Blocked::Not && create_blocked == Blocked::Not {
new_chat_id.unblock(context).await?;
chat_id_blocked = Blocked::Not;
}
}
}
// In lookup_chat_by_reply() and create_or_lookup_group(), it can happen that the message is put into a chat
// but the From-address is not a member of this chat.
if !chat_id.is_unset() && !chat::is_contact_in_chat(context, chat_id, from_id as u32).await
{
let chat = Chat::load_from_db(context, chat_id).await?;
if chat.is_protected() {
let s = stock_str::unknown_sender_for_chat(context).await;
mime_parser.repl_msg_by_error(s);
} else if let Some(from) = mime_parser.from.first() {
// In non-protected chats, just mark the sender as overridden. Therefore, the UI will prepend `~`
// to the sender's name, indicating to the user that he/she is not part of the group.
let name: &str = from.display_name.as_ref().unwrap_or(&from.addr);
for part in mime_parser.parts.iter_mut() {
part.param.set(Param::OverrideSenderDisplayname, name);
if let Some(chat_id) = chat_id {
if !chat::is_contact_in_chat(context, chat_id, from_id as u32).await {
let chat = Chat::load_from_db(context, chat_id).await?;
if chat.is_protected() {
let s = stock_str::unknown_sender_for_chat(context).await;
mime_parser.repl_msg_by_error(s);
} else if let Some(from) = mime_parser.from.first() {
// In non-protected chats, just mark the sender as overridden. Therefore, the UI will prepend `~`
// to the sender's name, indicating to the user that he/she is not part of the group.
let name: &str = from.display_name.as_ref().unwrap_or(&from.addr);
for part in mime_parser.parts.iter_mut() {
part.param.set(Param::OverrideSenderDisplayname, name);
}
}
}
}
if chat_id.is_unset() {
if chat_id.is_none() {
// check if the message belongs to a mailing list
match mime_parser.get_mailinglist_type() {
MailinglistType::ListIdBased => {
if let Some(list_id) = mime_parser.get(HeaderDef::ListId) {
let (new_chat_id, new_chat_id_blocked) = create_or_lookup_mailinglist(
context,
allow_creation,
list_id,
mime_parser,
)
.await;
chat_id = new_chat_id;
chat_id_blocked = new_chat_id_blocked;
if let Some((new_chat_id, new_chat_id_blocked)) =
create_or_lookup_mailinglist(
context,
allow_creation,
list_id,
mime_parser,
)
.await?
{
chat_id = Some(new_chat_id);
chat_id_blocked = new_chat_id_blocked;
}
}
}
MailinglistType::SenderBased => {
if let Some(sender) = mime_parser.get(HeaderDef::Sender) {
let (new_chat_id, new_chat_id_blocked) = create_or_lookup_mailinglist(
context,
allow_creation,
sender,
mime_parser,
)
.await;
chat_id = new_chat_id;
chat_id_blocked = new_chat_id_blocked;
if let Some((new_chat_id, new_chat_id_blocked)) =
create_or_lookup_mailinglist(
context,
allow_creation,
sender,
mime_parser,
)
.await?
{
chat_id = Some(new_chat_id);
chat_id_blocked = new_chat_id_blocked;
}
}
}
MailinglistType::None => {}
@@ -564,7 +571,7 @@ async fn add_parts(
}
}
if chat_id.is_unset() {
if chat_id.is_none() {
// try to create a normal chat
let create_blocked = if from_id == to_id {
Blocked::Not
@@ -573,40 +580,39 @@ async fn add_parts(
};
if let Some(chat) = test_normal_chat {
chat_id = chat.id;
chat_id = Some(chat.id);
chat_id_blocked = chat.blocked;
} else if allow_creation {
if let Ok(chat) = ChatIdBlocked::get_for_contact(context, from_id, create_blocked)
.await
.log_err(context, "Failed to get (new) chat for contact")
{
chat_id = chat.id;
chat_id = Some(chat.id);
chat_id_blocked = chat.blocked;
}
}
if !chat_id.is_unset() && Blocked::Not != chat_id_blocked {
if Blocked::Not == create_blocked {
chat_id.unblock(context).await?;
chat_id_blocked = Blocked::Not;
} else if parent.is_some() {
// we do not want any chat to be created implicitly. Because of the origin-scale-up,
// the contact requests will pop up and this should be just fine.
Contact::scaleup_origin_by_id(context, from_id, Origin::IncomingReplyTo).await;
info!(
context,
"Message is a reply to a known message, mark sender as known.",
);
if !incoming_origin.is_known() {
incoming_origin = Origin::IncomingReplyTo;
if let Some(chat_id) = chat_id {
if Blocked::Not != chat_id_blocked {
if Blocked::Not == create_blocked {
chat_id.unblock(context).await?;
chat_id_blocked = Blocked::Not;
} else if parent.is_some() {
// we do not want any chat to be created implicitly. Because of the origin-scale-up,
// the contact requests will pop up and this should be just fine.
Contact::scaleup_origin_by_id(context, from_id, Origin::IncomingReplyTo)
.await;
info!(
context,
"Message is a reply to a known message, mark sender as known.",
);
if !incoming_origin.is_known() {
incoming_origin = Origin::IncomingReplyTo;
}
}
}
}
}
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;
info!(context, "No chat id for incoming msg (TRASH)")
}
// if the chat_id is blocked,
// for unknown senders and non-delta-messages set the state to NOTICED
@@ -629,7 +635,7 @@ async fn add_parts(
&& (is_dc_message == MessengerMessage::No)
&& context.is_spam_folder(server_folder).await?;
if is_spam {
chat_id = DC_CHAT_ID_TRASH;
chat_id = Some(DC_CHAT_ID_TRASH);
info!(context, "Message is probably spam (TRASH)");
}
} else {
@@ -643,7 +649,7 @@ async fn add_parts(
// handshake may mark contacts as verified and must be processed before chats are created
if mime_parser.get(HeaderDef::SecureJoin).is_some() {
is_dc_message = MessengerMessage::Yes; // avoid discarding by show_emails setting
chat_id = ChatId::new(0);
chat_id = None;
allow_creation = true;
match observe_securejoin_on_other_device(context, mime_parser, to_id).await {
Ok(securejoin::HandshakeMessage::Done)
@@ -654,9 +660,8 @@ async fn add_parts(
// process messages as "member added" normally
}
Err(err) => {
*hidden = true;
warn!(context, "Error in Secure-Join watching: {}", err);
return Ok(chat_id);
return Ok(DC_CHAT_ID_TRASH);
}
}
}
@@ -682,22 +687,24 @@ async fn add_parts(
if is_draft {
// Most mailboxes have a "Drafts" folder where constantly new emails appear but we don't actually want to show them
info!(context, "Email is probably just a draft (TRASH)");
chat_id = DC_CHAT_ID_TRASH;
chat_id = Some(DC_CHAT_ID_TRASH);
allow_creation = false;
}
if chat_id.is_unset() {
if chat_id.is_none() {
// try to assign to a chat based on In-Reply-To/References:
let (new_chat_id, new_chat_id_blocked) =
lookup_chat_by_reply(context, &mime_parser, &parent, from_id, to_ids).await?;
chat_id = new_chat_id;
chat_id_blocked = new_chat_id_blocked;
if let Some((new_chat_id, new_chat_id_blocked)) =
lookup_chat_by_reply(context, &mime_parser, &parent, from_id, to_ids).await?
{
chat_id = Some(new_chat_id);
chat_id_blocked = new_chat_id_blocked;
}
}
if !to_ids.is_empty() {
if chat_id.is_unset() {
let (new_chat_id, new_chat_id_blocked) = create_or_lookup_group(
if chat_id.is_none() {
if let Some((new_chat_id, new_chat_id_blocked)) = create_or_lookup_group(
context,
&mut mime_parser,
allow_creation,
@@ -705,16 +712,18 @@ async fn add_parts(
from_id,
to_ids,
)
.await?;
chat_id = new_chat_id;
chat_id_blocked = new_chat_id_blocked;
// automatically unblock chat when the user sends a message
if !chat_id.is_unset() && chat_id_blocked != Blocked::Not {
new_chat_id.unblock(context).await?;
chat_id_blocked = Blocked::Not;
.await?
{
chat_id = Some(new_chat_id);
chat_id_blocked = new_chat_id_blocked;
// automatically unblock chat when the user sends a message
if chat_id_blocked != Blocked::Not {
new_chat_id.unblock(context).await?;
chat_id_blocked = Blocked::Not;
}
}
}
if chat_id.is_unset() && allow_creation {
if chat_id.is_none() && allow_creation {
let create_blocked = if !Contact::is_blocked_load(context, to_id).await {
Blocked::Not
} else {
@@ -723,16 +732,15 @@ async fn add_parts(
if let Ok(chat) =
ChatIdBlocked::get_for_contact(context, to_id, create_blocked).await
{
chat_id = chat.id;
chat_id = Some(chat.id);
chat_id_blocked = chat.blocked;
}
if !chat_id.is_unset()
&& Blocked::Not != chat_id_blocked
&& Blocked::Not == create_blocked
{
chat_id.unblock(context).await?;
chat_id_blocked = Blocked::Not;
if let Some(chat_id) = chat_id {
if Blocked::Not != chat_id_blocked && Blocked::Not == create_blocked {
chat_id.unblock(context).await?;
chat_id_blocked = Blocked::Not;
}
}
}
}
@@ -740,7 +748,7 @@ async fn add_parts(
&& to_ids.len() == 1
&& to_ids.contains(&DC_CONTACT_ID_SELF);
if chat_id.is_unset() && self_sent {
if chat_id.is_none() && self_sent {
// from_id==to_id==DC_CONTACT_ID_SELF - this is a self-sent messages,
// maybe an Autocrypt Setup Message
if let Ok(chat) =
@@ -748,27 +756,30 @@ async fn add_parts(
.await
.log_err(context, "Failed to get (new) chat for contact")
{
chat_id = chat.id;
chat_id = Some(chat.id);
chat_id_blocked = chat.blocked;
}
if !chat_id.is_unset() && Blocked::Not != chat_id_blocked {
chat_id.unblock(context).await?;
chat_id_blocked = Blocked::Not;
if let Some(chat_id) = chat_id {
if Blocked::Not != chat_id_blocked {
chat_id.unblock(context).await?;
chat_id_blocked = Blocked::Not;
}
}
}
if chat_id.is_unset() {
chat_id = DC_CHAT_ID_TRASH;
info!(context, "No chat id for outgoing message (TRASH)")
}
}
if fetching_existing_messages && mime_parser.decrypting_failed {
chat_id = DC_CHAT_ID_TRASH;
chat_id = Some(DC_CHAT_ID_TRASH);
// We are only gathering old messages on first start. We do not want to add loads of non-decryptable messages to the chats.
info!(context, "Existing non-decipherable message. (TRASH)");
}
let chat_id = chat_id.unwrap_or_else(|| {
info!(context, "No chat id for message (TRASH)");
DC_CHAT_ID_TRASH
});
// Extract ephemeral timer from the message.
let mut ephemeral_timer = if let Some(value) = mime_parser.get(HeaderDef::EphemeralTimer) {
match value.parse::<EphemeralTimer>() {
@@ -787,6 +798,12 @@ async fn add_parts(
let location_kml_is = mime_parser.location_kml.is_some();
// correct message_timestamp, it should not be used before,
// however, we cannot do this earlier as we need from_id to be set
let in_fresh = state == MessageState::InFresh;
let rcvd_timestamp = time();
let sort_timestamp = calc_sort_timestamp(context, *sent_timestamp, chat_id, in_fresh).await?;
// Apply ephemeral timer changes to the chat.
//
// Only non-hidden timers are applied now. Timers from hidden
@@ -814,6 +831,7 @@ async fn add_parts(
context,
chat_id,
stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await,
sort_timestamp,
)
.await;
}
@@ -857,6 +875,7 @@ async fn add_parts(
context,
chat_id,
format!("Cannot set protection: {}", e),
sort_timestamp,
)
.await;
return Ok(chat_id); // do not return an error as this would result in retrying the message
@@ -872,12 +891,6 @@ async fn add_parts(
}
}
// correct message_timestamp, it should not be used before,
// however, we cannot do this earlier as we need from_id to be set
let in_fresh = state == MessageState::InFresh;
let rcvd_timestamp = time();
let sort_timestamp = calc_sort_timestamp(context, *sent_timestamp, chat_id, in_fresh).await?;
// Ensure replies to messages are sorted after the parent message.
//
// This is useful in a case where sender clocks are not
@@ -936,7 +949,6 @@ async fn add_parts(
let sent_timestamp = *sent_timestamp;
let is_hidden = *hidden;
let chat_id = chat_id;
let mut is_hidden = is_hidden;
let mut ids = Vec::with_capacity(parts.len());
@@ -979,7 +991,8 @@ INSERT INTO msgs
}
}
let mime_modified = save_mime_modified && !part.msg.is_empty();
let part_is_empty = part.msg.is_empty() && part.param.get(Param::Quote).is_none();
let mime_modified = save_mime_modified && !part_is_empty;
if mime_modified {
// Avoid setting mime_modified for more than one part.
save_mime_modified = false;
@@ -1201,7 +1214,7 @@ async fn lookup_chat_by_reply(
parent: &Option<Message>,
from_id: u32,
to_ids: &ContactIds,
) -> Result<(ChatId, Blocked)> {
) -> Result<Option<(ChatId, Blocked)>> {
// Try to assign message to the same chat as the parent message.
// If this was a private message just to self, it was probably a private reply.
@@ -1215,7 +1228,7 @@ async fn lookup_chat_by_reply(
// This message will be assigned to this chat, anyway.
// But if we assigned it now, create_or_lookup_group() will not be called
// and group commands will not be executed.
return Ok((ChatId::new(0), Blocked::Not));
return Ok(None);
}
}
@@ -1224,25 +1237,25 @@ async fn lookup_chat_by_reply(
// (undecipherable group msgs often get assigned to the 1:1 chat with the sender).
// We don't have any way of finding out whether a msg is undecipherable, so we check for
// error.is_some() instead.
return Ok((ChatId::new(0), Blocked::Not));
return Ok(None);
}
if parent_chat.id == DC_CHAT_ID_TRASH {
return Ok((ChatId::new(0), Blocked::Not));
return Ok(None);
}
if is_probably_private_reply(context, to_ids, mime_parser, parent_chat.id, from_id).await? {
return Ok((ChatId::new(0), Blocked::Not));
return Ok(None);
}
info!(
context,
"Assigning message to {} as it's a reply to {}", parent_chat.id, parent.rfc724_mid
);
return Ok((parent_chat.id, parent_chat.blocked));
return Ok(Some((parent_chat.id, parent_chat.blocked)));
}
Ok((ChatId::new(0), Blocked::Not))
Ok(None)
}
/// If this method returns true, the message shall be assigned to the 1:1 chat with the sender.
@@ -1299,7 +1312,7 @@ async fn create_or_lookup_group(
create_blocked: Blocked,
from_id: u32,
to_ids: &ContactIds,
) -> Result<(ChatId, Blocked)> {
) -> Result<Option<(ChatId, Blocked)>> {
let mut chat_id_blocked = Blocked::Not;
let mut recreate_member_list = false;
let mut send_EVENT_CHAT_MODIFIED = false;
@@ -1324,16 +1337,12 @@ async fn create_or_lookup_group(
}
if !allow_creation {
info!(context, "creating ad-hoc group prevented from caller");
return Ok((ChatId::new(0), Blocked::Not));
return Ok(None);
}
return create_adhoc_group(context, mime_parser, create_blocked, &member_ids)
.await
.map(|chat_id| {
chat_id
.map(|chat_id| (chat_id, create_blocked))
.unwrap_or((ChatId::new(0), Blocked::Not))
})
.map(|chat_id| chat_id.map(|chat_id| (chat_id, create_blocked)))
.map_err(|err| {
info!(context, "could not create adhoc-group: {:?}", err);
err
@@ -1341,16 +1350,18 @@ async fn create_or_lookup_group(
};
// check, if we have a chat with this group ID
let (mut chat_id, _, _blocked) = chat::get_chat_id_by_grpid(context, &grpid)
.await
.unwrap_or((ChatId::new(0), false, Blocked::Not));
let mut chat_id = chat::get_chat_id_by_grpid(context, &grpid)
.await?
.map(|(chat_id, _protected, _blocked)| chat_id);
// For chat messages, we don't have to guess (is_*probably*_private_reply()) but we know for sure that
// they belong to the group because of the Chat-Group-Id or Message-Id header
if !mime_parser.has_chat_version()
&& is_probably_private_reply(context, to_ids, mime_parser, chat_id, from_id).await?
{
return Ok((ChatId::new(0), Blocked::Not));
if let Some(chat_id) = chat_id {
if !mime_parser.has_chat_version()
&& is_probably_private_reply(context, to_ids, mime_parser, chat_id, from_id).await?
{
return Ok(None);
}
}
// now we have a grpid that is non-empty
@@ -1421,7 +1432,7 @@ async fn create_or_lookup_group(
.await?
.unwrap_or_default();
if chat_id.is_unset()
if chat_id.is_none()
&& !mime_parser.is_mailinglist_message()
&& !grpid.is_empty()
&& grpname.is_some()
@@ -1446,7 +1457,7 @@ async fn create_or_lookup_group(
if !allow_creation {
info!(context, "creating group forbidden by caller");
return Ok((ChatId::new(0), Blocked::Not));
return Ok(None);
}
chat_id = create_multiuser_record(
@@ -1458,6 +1469,7 @@ async fn create_or_lookup_group(
create_protected,
)
.await
.map(Some)
.unwrap_or_else(|err| {
warn!(
context,
@@ -1467,7 +1479,7 @@ async fn create_or_lookup_group(
err,
);
ChatId::new(0)
None
});
chat_id_blocked = create_blocked;
@@ -1486,22 +1498,22 @@ async fn create_or_lookup_group(
}
// again, check chat_id
if chat_id.is_special() {
if mime_parser.decrypting_failed {
// It is possible that the message was sent to a valid,
// yet unknown group, which was rejected because
// Chat-Group-Name, which is in the encrypted part, was
// not found. We can't create a properly named group in
// this case, so assign error message to 1:1 chat with the
// sender instead.
return Ok((ChatId::new(0), Blocked::Not));
} else {
// The message was decrypted successfully, but contains a late "quit" or otherwise
// unwanted message.
info!(context, "message belongs to unwanted group (TRASH)");
return Ok((DC_CHAT_ID_TRASH, chat_id_blocked));
}
}
let chat_id = if let Some(chat_id) = chat_id {
chat_id
} else if mime_parser.decrypting_failed {
// It is possible that the message was sent to a valid,
// yet unknown group, which was rejected because
// Chat-Group-Name, which is in the encrypted part, was
// not found. We can't create a properly named group in
// this case, so assign error message to 1:1 chat with the
// sender instead.
return Ok(None);
} else {
// The message was decrypted successfully, but contains a late "quit" or otherwise
// unwanted message.
info!(context, "message belongs to unwanted group (TRASH)");
return Ok(Some((DC_CHAT_ID_TRASH, chat_id_blocked)));
};
// We have a valid chat_id > DC_CHAT_ID_LAST_SPECIAL.
@@ -1585,7 +1597,7 @@ async fn create_or_lookup_group(
if send_EVENT_CHAT_MODIFIED {
context.emit_event(EventType::ChatModified(chat_id));
}
Ok((chat_id, chat_id_blocked))
Ok(Some((chat_id, chat_id_blocked)))
}
/// Create or lookup a mailing list chat.
@@ -1603,7 +1615,7 @@ async fn create_or_lookup_mailinglist(
allow_creation: bool,
list_id_header: &str,
mime_parser: &MimeMessage,
) -> (ChatId, Blocked) {
) -> Result<Option<(ChatId, Blocked)>> {
static LIST_ID: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(.+)<(.+)>$").unwrap());
let (mut name, listid) = match LIST_ID.captures(list_id_header) {
Some(cap) => (cap[1].trim().to_string(), cap[2].trim().to_string()),
@@ -1617,8 +1629,8 @@ async fn create_or_lookup_mailinglist(
),
};
if let Ok((chat_id, _, blocked)) = chat::get_chat_id_by_grpid(context, &listid).await {
return (chat_id, blocked);
if let Some((chat_id, _, blocked)) = chat::get_chat_id_by_grpid(context, &listid).await? {
return Ok(Some((chat_id, blocked)));
}
// for mailchimp lists, the name in `ListId` is just a long number.
@@ -1665,7 +1677,7 @@ async fn create_or_lookup_mailinglist(
if allow_creation {
// list does not exist but should be created
match create_multiuser_record(
let chat_id = create_multiuser_record(
context,
Chattype::Mailinglist,
&listid,
@@ -1674,25 +1686,18 @@ async fn create_or_lookup_mailinglist(
ProtectionStatus::Unprotected,
)
.await
{
Ok(chat_id) => {
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await;
(chat_id, Blocked::Request)
}
Err(e) => {
warn!(
context,
"Failed to create mailinglist '{}' for grpid={}: {}",
&name,
&listid,
e.to_string()
);
(ChatId::new(0), Blocked::Request)
}
}
.map_err(|err| {
err.context(format!(
"Failed to create mailinglist '{}' for grpid={}",
&name, &listid
))
})?;
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await;
Ok(Some((chat_id, Blocked::Request)))
} else {
info!(context, "creating list forbidden by caller");
(ChatId::new(0), Blocked::Not)
Ok(None)
}
}
@@ -4441,4 +4446,25 @@ Reply to all"#,
Ok(())
}
#[async_std::test]
async fn test_gmx_forwarded_msg() -> Result<()> {
let t = TestContext::new_alice().await;
t.set_config(Config::ShowEmails, Some("2")).await?;
dc_receive_imf(
&t,
include_bytes!("../test-data/message/gmx-forward.eml"),
"INBOX",
1,
false,
)
.await?;
let msg = t.get_last_msg().await;
assert!(msg.has_html());
assert_eq!(msg.id.get_html(&t).await?.unwrap().replace("\r\n", "\n"), "<html><head></head><body><div style=\"font-family: Verdana;font-size: 12.0px;\"><div>&nbsp;</div>\n\n<div>&nbsp;\n<div>&nbsp;\n<div data-darkreader-inline-border-left=\"\" name=\"quote\" style=\"margin: 10px 5px 5px 10px; padding: 10px 0px 10px 10px; border-left: 2px solid rgb(195, 217, 229); overflow-wrap: break-word; --darkreader-inline-border-left:#274759;\">\n<div style=\"margin:0 0 10px 0;\"><b>Gesendet:</b>&nbsp;Donnerstag, 12. August 2021 um 15:52 Uhr<br/>\n<b>Von:</b>&nbsp;&quot;Claire&quot; &lt;claire@example.org&gt;<br/>\n<b>An:</b>&nbsp;alice@example.com<br/>\n<b>Betreff:</b>&nbsp;subject</div>\n\n<div name=\"quoted-content\">bodytext</div>\n</div>\n</div>\n</div></div></body></html>\n\n");
Ok(())
}
}

View File

@@ -178,7 +178,9 @@ pub async fn try_decrypt(
let mut signatures = HashSet::default();
if let Some(ref mut peerstate) = peerstate {
peerstate.handle_fingerprint_change(context).await?;
peerstate
.handle_fingerprint_change(context, message_time)
.await?;
if let Some(key) = &peerstate.public_key {
public_keyring_for_validate.add(key.clone());
} else if let Some(key) = &peerstate.gossip_key {

View File

@@ -5,10 +5,10 @@
use std::{cmp, cmp::max, collections::BTreeMap};
use anyhow::{bail, format_err, Context as _, Result};
use anyhow::{anyhow, bail, format_err, Context as _, Result};
use async_imap::{
error::Result as ImapResult,
types::{Fetch, Flag, Mailbox, Name, NameAttribute, UnsolicitedResponse},
types::{Fetch, Flag, Mailbox, Name, NameAttribute, Quota, QuotaRoot, UnsolicitedResponse},
};
use async_std::channel::Receiver;
use async_std::prelude::*;
@@ -153,6 +153,10 @@ struct ImapConfig {
/// True if the server has MOVE capability as defined in
/// <https://tools.ietf.org/html/rfc6851>
pub can_move: bool,
/// True if the server has QUOTA capability as defined in
/// <https://tools.ietf.org/html/rfc2087>
pub can_check_quota: bool,
}
impl Imap {
@@ -186,6 +190,7 @@ impl Imap {
selected_folder_needs_expunge: false,
can_idle: false,
can_move: false,
can_check_quota: false,
};
let imap = Imap {
@@ -362,6 +367,7 @@ impl Imap {
Ok(caps) => {
self.config.can_idle = caps.has_str("IDLE");
self.config.can_move = caps.has_str("MOVE");
self.config.can_check_quota = caps.has_str("QUOTA");
self.capabilities_determined = true;
Ok(())
}
@@ -1392,6 +1398,22 @@ impl Imap {
}
unsolicited_exists
}
pub fn can_check_quota(&self) -> bool {
self.config.can_check_quota
}
pub async fn get_quota_roots(
&mut self,
mailbox_name: &str,
) -> Result<(Vec<QuotaRoot>, Vec<Quota>)> {
if let Some(session) = self.session.as_mut() {
let quota_roots = session.get_quota_root(mailbox_name).await?;
Ok(quota_roots)
} else {
Err(anyhow!("Not connected to IMAP, no session"))
}
}
}
/// Try to get the folder meaning by the name of the folder only used if the server does not support XLIST.
@@ -1636,7 +1658,7 @@ pub(crate) async fn prefetch_should_download(
// deleted from the database or has not arrived yet.
if let Some(rfc724_mid) = headers.get_header_value(HeaderDef::MessageId) {
if let Some(group_id) = dc_extract_grpid_from_rfc724_mid(&rfc724_mid) {
if let Ok((_chat_id, _, _)) = get_chat_id_by_grpid(context, group_id).await {
if get_chat_id_by_grpid(context, group_id).await?.is_some() {
return Ok(true);
}
}

View File

@@ -42,6 +42,15 @@ impl Imap {
}
};
// Gmail labels are not folders and should be skipped. For example,
// emails appear in the inbox and under "All Mail" as soon as it is
// received. The code used to wrongly conclude that the email had
// already been moved and left it in the inbox.
let folder_name = folder.name();
if folder_name.starts_with("[Gmail]") {
continue;
}
let folder_meaning = get_folder_meaning(&folder);
let folder_name_meaning = get_folder_meaning_by_name(folder.name());
@@ -93,7 +102,7 @@ impl Imap {
}
}
async fn get_watched_folders(context: &Context) -> Vec<String> {
pub(crate) async fn get_watched_folders(context: &Context) -> Vec<String> {
let mut res = Vec::new();
let folder_watched_configured = &[
(Config::SentboxWatch, Config::ConfiguredSentboxFolder),

View File

@@ -95,6 +95,9 @@ pub enum Action {
FetchExistingMsgs = 110,
MarkseenMsgOnImap = 130,
// this is user initiated so it should have a fairly high priority
UpdateRecentQuota = 140,
// Moving message is prioritized lower than deletion so we don't
// bother moving message if it is already scheduled for deletion.
MoveMsg = 200,
@@ -130,6 +133,7 @@ impl From<Action> for Thread {
ResyncFolders => Thread::Imap,
MarkseenMsgOnImap => Thread::Imap,
MoveMsg => Thread::Imap,
UpdateRecentQuota => Thread::Imap,
MaybeSendLocations => Thread::Smtp,
MaybeSendLocationsEnded => Thread::Smtp,
@@ -148,7 +152,6 @@ pub struct Job {
pub added_timestamp: i64,
pub tries: u32,
pub param: Params,
pub pending_error: Option<String>,
}
impl fmt::Display for Job {
@@ -169,7 +172,6 @@ impl Job {
added_timestamp: timestamp,
tries: 0,
param,
pending_error: None,
}
}
@@ -251,12 +253,13 @@ impl Job {
smtp.connectivity.set_working(context).await;
let status = match smtp.send(context, recipients, message, job_id).await {
let send_result = smtp.send(context, recipients, message, job_id).await;
smtp.last_send_error = send_result.as_ref().err().map(|e| e.to_string());
let status = match send_result {
Err(crate::smtp::send::Error::SmtpSend(err)) => {
// Remote error, retry later.
warn!(context, "SMTP failed to send: {:?}", &err);
smtp.connectivity.set_err(context, &err).await;
self.pending_error = Some(err.to_string());
let res = match err {
async_smtp::smtp::error::Error::Permanent(ref response) => {
@@ -365,6 +368,7 @@ impl Job {
// SMTP server, if not yet done
if let Err(err) = smtp.connect_configured(context).await {
warn!(context, "SMTP connection failure: {:?}", err);
smtp.last_send_error = Some(format!("SMTP connection failure: {:#}", err));
return Status::RetryLater;
}
@@ -407,6 +411,8 @@ impl Job {
}
Err(err) => {
warn!(context, "failed to check message existence: {:?}", err);
smtp.last_send_error =
Some(format!("failed to check message existence: {:#}", err));
return Status::RetryLater;
}
}
@@ -521,6 +527,7 @@ impl Job {
// connect to SMTP server, if not yet done
if let Err(err) = smtp.connect_configured(context).await {
warn!(context, "SMTP connection failure: {:?}", err);
smtp.last_send_error = Some(err.to_string());
return Status::RetryLater;
}
@@ -1145,6 +1152,7 @@ async fn perform_job_action(
sql::housekeeping(context).await.ok_or_log(context);
Status::Finished(Ok(()))
}
Action::UpdateRecentQuota => context.update_recent_quota(connection.inbox()).await,
};
info!(context, "Finished immediate try {} of job {}", tries, job);
@@ -1207,7 +1215,8 @@ pub async fn add(context: &Context, job: Job) {
| Action::ResyncFolders
| Action::MarkseenMsgOnImap
| Action::FetchExistingMsgs
| Action::MoveMsg => {
| Action::MoveMsg
| Action::UpdateRecentQuota => {
info!(context, "interrupt: imap");
context
.interrupt_inbox(InterruptInfo::new(false, None))
@@ -1321,7 +1330,6 @@ LIMIT 1;
added_timestamp: row.get("added_timestamp")?,
tries: row.get("tries")?,
param: row.get::<_, String>("param")?.parse().unwrap_or_default(),
pending_error: None,
};
Ok(job)

View File

@@ -7,7 +7,11 @@
clippy::wildcard_imports,
clippy::needless_borrow
)]
#![allow(clippy::match_bool, clippy::eval_order_dependence)]
#![allow(
clippy::match_bool,
clippy::eval_order_dependence,
clippy::bool_assert_comparison
)]
#[macro_use]
extern crate num_derive;
@@ -71,6 +75,7 @@ pub mod peerstate;
pub mod pgp;
pub mod provider;
pub mod qr;
pub mod quota;
pub mod securejoin;
mod simplify;
mod smtp;

View File

@@ -190,7 +190,7 @@ impl Kml {
}
}
// location streaming
/// Enables location streaming in chat identified by `chat_id` for `seconds` seconds.
pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: i64) {
let now = time();
if !(seconds < 0 || chat_id.is_special()) {
@@ -221,7 +221,7 @@ pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds:
.unwrap_or_default();
} else if 0 == seconds && is_sending_locations_before {
let stock_str = stock_str::msg_location_disabled(context).await;
chat::add_info_msg(context, chat_id, stock_str).await;
chat::add_info_msg(context, chat_id, stock_str, now).await;
}
context.emit_event(EventType::ChatModified(chat_id));
if 0 != seconds {
@@ -716,7 +716,8 @@ pub(crate) async fn job_maybe_send_locations_ended(
.await
);
if !(send_begin != 0 && time() <= send_until) {
let now = time();
if !(send_begin != 0 && now <= send_until) {
// still streaming -
// may happen as several calls to dc_send_locations_to_chat()
// do not un-schedule pending DC_MAYBE_SEND_LOC_ENDED jobs
@@ -735,7 +736,7 @@ pub(crate) async fn job_maybe_send_locations_ended(
);
let stock_str = stock_str::msg_location_disabled(context).await;
chat::add_info_msg(context, chat_id, stock_str).await;
chat::add_info_msg(context, chat_id, stock_str, now).await;
context.emit_event(EventType::ChatModified(chat_id));
}
}

View File

@@ -19,8 +19,8 @@ use crate::constants::{
use crate::contact::{Contact, Origin};
use crate::context::Context;
use crate::dc_tools::{
dc_get_filebytes, dc_get_filemeta, dc_gm2local_offset, dc_read_file, dc_timestamp_to_str,
dc_truncate, time,
dc_create_smeared_timestamp, dc_get_filebytes, dc_get_filemeta, dc_gm2local_offset,
dc_read_file, dc_timestamp_to_str, dc_truncate, time,
};
use crate::ephemeral::Timer as EphemeralTimer;
use crate::events::EventType;
@@ -1805,7 +1805,13 @@ async fn ndn_maybe_add_info_msg(
// Tell the user which of the recipients failed if we know that (because in
// a group, this might otherwise be unclear)
let text = stock_str::failed_sending_to(context, contact.get_display_name()).await;
chat::add_info_msg(context, chat_id, text).await;
chat::add_info_msg(
context,
chat_id,
text,
dc_create_smeared_timestamp(context).await,
)
.await;
context.emit_event(EventType::ChatModified(chat_id));
}
}

View File

@@ -276,7 +276,7 @@ impl MimeMessage {
parser.maybe_remove_bad_parts();
parser.maybe_remove_inline_mailinglist_footer();
parser.heuristically_parse_ndn(context).await;
parser.parse_headers(context).await;
parser.parse_headers(context).await?;
if warn_empty_signature && parser.signatures.is_empty() {
for part in parser.parts.iter_mut() {
@@ -419,7 +419,7 @@ impl MimeMessage {
}
}
async fn parse_headers(&mut self, context: &Context) {
async fn parse_headers(&mut self, context: &Context) -> Result<()> {
self.parse_system_message_headers(context);
self.parse_avatar_headers(context).await;
self.parse_videochat_headers();
@@ -464,15 +464,20 @@ impl MimeMessage {
if !self.decrypting_failed && !self.parts.is_empty() {
if let Some(ref dn_to) = self.chat_disposition_notification_to {
if let Some(from) = self.from.get(0) {
if from.addr.to_lowercase() == dn_to.addr.to_lowercase() {
if let Some(part) = self.parts.last_mut() {
part.param.set_int(Param::WantsMdn, 1);
// Check that the message is not outgoing.
if !context.is_self_addr(&from.addr).await? {
if from.addr.to_lowercase() == dn_to.addr.to_lowercase() {
if let Some(part) = self.parts.last_mut() {
part.param.set_int(Param::WantsMdn, 1);
}
} else {
warn!(
context,
"{} requested a read receipt to {}, ignoring",
from.addr,
dn_to.addr
);
}
} else {
warn!(
context,
"{} requested a read receipt to {}, ignoring", from.addr, dn_to.addr
);
}
}
}
@@ -502,6 +507,8 @@ impl MimeMessage {
part.param.set(Param::Bot, "1");
}
}
Ok(())
}
async fn avatar_action_from_header(
@@ -1347,7 +1354,9 @@ async fn update_gossip_peerstates(
peerstate = Some(p);
}
if let Some(peerstate) = peerstate {
peerstate.handle_fingerprint_change(context).await?;
peerstate
.handle_fingerprint_change(context, message_time)
.await?;
}
gossipped_addr.insert(header.addr.clone());
@@ -2952,4 +2961,36 @@ Some reply
Ok(())
}
// Test that WantsMdn parameter is not set on outgoing messages.
#[async_std::test]
async fn test_outgoing_wants_mdn() -> Result<()> {
let alice = TestContext::new_alice().await;
let bob = TestContext::new_bob().await;
let raw = br###"Date: Thu, 28 Jan 2021 00:26:57 +0000
Chat-Version: 1.0\n\
Message-ID: <foobarbaz@example.org>
To: Bob <bob@example.org>
From: Alice <alice@example.com>
Subject: subject
Chat-Disposition-Notification-To: alice@example.com
Message.
"###;
// Bob receives message.
dc_receive_imf(&bob, raw, "INBOX", 1, false).await?;
let msg = bob.get_last_msg().await;
// Message is incoming.
assert!(msg.param.get_bool(Param::WantsMdn).unwrap());
// Alice receives copy-to-self.
dc_receive_imf(&alice, raw, "INBOX", 1, false).await?;
let msg = alice.get_last_msg().await;
// Message is outgoing, don't send read receipt to self.
assert!(msg.param.get_bool(Param::WantsMdn).is_none());
Ok(())
}
}

View File

@@ -260,7 +260,11 @@ impl Peerstate {
}
/// Adds a warning to the chat corresponding to peerstate if fingerprint has changed.
pub(crate) async fn handle_fingerprint_change(&self, context: &Context) -> Result<()> {
pub(crate) async fn handle_fingerprint_change(
&self,
context: &Context,
timestamp: i64,
) -> Result<()> {
if self.fingerprint_changed {
if let Some(contact_id) = context
.sql
@@ -273,7 +277,7 @@ impl Peerstate {
let msg = stock_str::contact_setup_changed(context, self.addr.clone()).await;
chat::add_info_msg(context, chat_id, msg).await;
chat::add_info_msg(context, chat_id, msg, timestamp).await;
emit_event!(context, EventType::ChatModified(chat_id));
} else {
bail!("contact with peerstate.addr {:?} not found", &self.addr);

View File

@@ -585,8 +585,8 @@ static P_HEY_COM: Lazy<Provider> = Lazy::new(|| {
// i.ua.md: i.ua
static P_I_UA: Lazy<Provider> = Lazy::new(|| Provider {
id: "i.ua",
status: Status::Ok,
before_login_hint: "",
status: Status::Broken,
before_login_hint: "Протокол IMAP не предоставляется и не планируется.",
after_login_hint: "",
overview_page: "https://providers.delta.chat/i-ua",
server: vec![],
@@ -686,6 +686,35 @@ static P_MAIL_RU: Lazy<Provider> = Lazy::new(|| {
}
});
// mail2tor.md: mail2tor.com
static P_MAIL2TOR: Lazy<Provider> = Lazy::new(|| Provider {
id: "mail2tor",
status: Status::Preparation,
before_login_hint: "Tor is needed to connect to the email servers.",
after_login_hint: "",
overview_page: "https://providers.delta.chat/mail2tor",
server: vec![
Server {
protocol: Imap,
socket: Plain,
hostname: "g77kjrad6bafzzyldqvffq6kxlsgphcygptxhnn4xlnktfgaqshilmyd.onion",
port: 143,
username_pattern: Email,
},
Server {
protocol: Smtp,
socket: Plain,
hostname: "xc7tgk2c5onxni2wsy76jslfsitxjbbptejnqhw6gy2ft7khpevhc7ad.onion",
port: 25,
username_pattern: Email,
},
],
config_defaults: None,
strict_tls: true,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
// mailbox.org.md: mailbox.org, secure.mailbox.org
static P_MAILBOX_ORG: Lazy<Provider> = Lazy::new(|| Provider {
id: "mailbox.org",
@@ -1286,6 +1315,25 @@ static P_YANDEX_RU: Lazy<Provider> = Lazy::new(|| Provider {
oauth2_authorizer: Some(Oauth2Authorizer::Yandex),
});
// yggmail.md: yggmail
static P_YGGMAIL: Lazy<Provider> = Lazy::new(|| {
Provider {
id: "yggmail",
status: Status::Preparation,
before_login_hint: "An Yggmail companion app needs to be installed on your device to access the Yggmail network.",
after_login_hint: "Make sure, the Yggmail companion app runs whenever you want to use this account. Note, that you usually cannot write from @yggmail addresses to normal e-mail-addresses (as @gmx.net). However, you can create another account in the normal e-mail-network for this purpose.",
overview_page: "https://providers.delta.chat/yggmail",
server: vec![
Server { protocol: Imap, socket: Plain, hostname: "localhost", port: 1143, username_pattern: Email },
Server { protocol: Smtp, socket: Plain, hostname: "localhost", port: 1025, username_pattern: Email },
],
config_defaults: None,
strict_tls: true,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
}
});
// ziggo.nl.md: ziggo.nl
static P_ZIGGO_NL: Lazy<Provider> = Lazy::new(|| Provider {
id: "ziggo.nl",
@@ -1395,6 +1443,7 @@ pub(crate) static PROVIDER_DATA: Lazy<HashMap<&'static str, &'static Provider>>
("internet.ru", &*P_MAIL_RU),
("bk.ru", &*P_MAIL_RU),
("list.ru", &*P_MAIL_RU),
("mail2tor.com", &*P_MAIL2TOR),
("mailbox.org", &*P_MAILBOX_ORG),
("secure.mailbox.org", &*P_MAILBOX_ORG),
("mailo.com", &*P_MAILO_COM),
@@ -1528,6 +1577,7 @@ pub(crate) static PROVIDER_DATA: Lazy<HashMap<&'static str, &'static Provider>>
("yandex.ua", &*P_YANDEX_RU),
("ya.ru", &*P_YANDEX_RU),
("narod.ru", &*P_YANDEX_RU),
("yggmail", &*P_YGGMAIL),
("ziggo.nl", &*P_ZIGGO_NL),
("zohomail.eu", &*P_ZOHO),
("zoho.com", &*P_ZOHO),
@@ -1568,6 +1618,7 @@ pub(crate) static PROVIDER_IDS: Lazy<HashMap<&'static str, &'static Provider>> =
("kolst.com", &*P_KOLST_COM),
("kontent.com", &*P_KONTENT_COM),
("mail.ru", &*P_MAIL_RU),
("mail2tor", &*P_MAIL2TOR),
("mailbox.org", &*P_MAILBOX_ORG),
("mailo.com", &*P_MAILO_COM),
("nauta.cu", &*P_NAUTA_CU),
@@ -1592,6 +1643,7 @@ pub(crate) static PROVIDER_IDS: Lazy<HashMap<&'static str, &'static Provider>> =
("web.de", &*P_WEB_DE),
("yahoo", &*P_YAHOO),
("yandex.ru", &*P_YANDEX_RU),
("yggmail", &*P_YGGMAIL),
("ziggo.nl", &*P_ZIGGO_NL),
("zoho", &*P_ZOHO),
]
@@ -1601,4 +1653,4 @@ pub(crate) static PROVIDER_IDS: Lazy<HashMap<&'static str, &'static Provider>> =
});
pub static PROVIDER_UPDATED: Lazy<chrono::NaiveDate> =
Lazy::new(|| chrono::NaiveDate::from_ymd(2021, 7, 28));
Lazy::new(|| chrono::NaiveDate::from_ymd(2021, 8, 17));

View File

@@ -63,7 +63,7 @@ def process_data(data, file):
raise TypeError("no domains found")
for domain in data["domains"]:
domain = cleanstr(domain)
if domain == "" or domain.count(".") < 1 or domain.lower() != domain:
if domain == "" or domain.lower() != domain:
raise TypeError("bad domain: " + domain)
global domains_set
@@ -84,7 +84,7 @@ def process_data(data, file):
for s in data["server"]:
hostname = cleanstr(s.get("hostname", ""))
port = int(s.get("port", ""))
if hostname == "" or hostname.count(".") < 1 or port <= 0:
if hostname == "" or hostname.lower() != hostname or port <= 0:
raise TypeError("bad hostname or port")
protocol = s.get("type", "").upper()
@@ -96,7 +96,7 @@ def process_data(data, file):
raise TypeError("bad protocol")
socket = s.get("socket", "").upper()
if socket != "STARTTLS" and socket != "SSL":
if socket != "STARTTLS" and socket != "SSL" and socket != "PLAIN":
raise TypeError("bad socket")
username_pattern = s.get("username_pattern", "EMAIL").upper()

View File

@@ -11,6 +11,7 @@ use crate::config::Config;
use crate::constants::Blocked;
use crate::contact::{addr_normalize, may_be_valid_addr, Contact, Origin};
use crate::context::Context;
use crate::dc_tools::time;
use crate::key::Fingerprint;
use crate::log::LogExt;
use crate::lot::{Lot, LotState};
@@ -160,7 +161,13 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot {
.await
.log_err(context, "Failed to create (new) chat for contact")
{
chat::add_info_msg(context, chat.id, format!("{} verified.", peerstate.addr)).await;
chat::add_info_msg(
context,
chat.id,
format!("{} verified.", peerstate.addr),
time(),
)
.await;
}
} else if let Some(addr) = addr {
lot.state = LotState::QrFprMismatch;
@@ -324,11 +331,9 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<(), Error
let chat_id = if lot.state == LotState::QrReviveVerifyContact {
None
} else {
Some(
get_chat_id_by_grpid(context, &lot.text2.unwrap_or_default())
.await?
.0,
)
get_chat_id_by_grpid(context, &lot.text2.unwrap_or_default())
.await?
.map(|(chat_id, _protected, _blocked)| chat_id)
};
token::save(
context,

100
src/quota.rs Normal file
View File

@@ -0,0 +1,100 @@
use anyhow::{anyhow, Result};
use async_imap::types::{Quota, QuotaResource};
use indexmap::IndexMap;
use crate::context::Context;
use crate::dc_tools::time;
use crate::imap::scan_folders::get_watched_folders;
use crate::imap::Imap;
use crate::job::{Action, Status};
use crate::param::Params;
use crate::{job, EventType};
/// warn about a nearly full mailbox after this usage percentage is reached.
/// quota icon is "yellow".
pub const QUOTA_WARN_THRESHOLD_PERCENTAGE: u64 = 80;
// warning is already issued at QUOTA_WARN_THRESHOLD_PERCENTAGE,
// this threshold only makes the quota icon "red".
pub const QUOTA_ERROR_THRESHOLD_PERCENTAGE: u64 = 99;
// if recent quota is older,
// it is re-fetched on dc_get_connectivity_html()
pub const QUOTA_MAX_AGE_SECONDS: i64 = 60;
#[derive(Debug)]
pub struct QuotaInfo {
/// Recently loaded quota information.
/// set to `Err()` if the provider does not support quota or on other errors,
/// set to `Ok()` for valid quota information.
/// Updated by `Action::UpdateRecentQuota`
pub(crate) recent: Result<IndexMap<String, Vec<QuotaResource>>>,
/// Timestamp when structure was modified.
pub(crate) modified: i64,
}
async fn get_unique_quota_roots_and_usage(
folders: Vec<String>,
imap: &mut Imap,
) -> Result<IndexMap<String, Vec<QuotaResource>>> {
let mut unique_quota_roots: IndexMap<String, Vec<QuotaResource>> = IndexMap::new();
for folder in folders {
let (quota_roots, quotas) = &imap.get_quota_roots(&folder).await?;
// if there are new quota roots found in this imap folder, add them to the list
for qr_entries in quota_roots {
for quota_root_name in &qr_entries.quota_root_names {
// the quota for that quota root
let quota: Quota = quotas
.iter()
.find(|q| &q.root_name == quota_root_name)
.cloned()
.ok_or_else(|| anyhow!("quota_root should have a quota"))?;
// replace old quotas, because between fetching quotaroots for folders,
// messages could be recieved and so the usage could have been changed
*unique_quota_roots
.entry(quota_root_name.clone())
.or_insert(vec![]) = quota.resources;
}
}
}
Ok(unique_quota_roots)
}
impl Context {
// Adds a job to update `quota.recent`
pub(crate) async fn schedule_quota_update(&self) {
job::kill_action(self, Action::UpdateRecentQuota).await;
job::add(
self,
job::Job::new(Action::UpdateRecentQuota, 0, Params::new(), 0),
)
.await;
}
/// Updates `quota.recent`, sets `quota.modified` to the current time
/// and emits an event to let the UIs update connectivity view.
///
/// Called in response to `Action::UpdateRecentQuota`.
pub(crate) async fn update_recent_quota(&self, imap: &mut Imap) -> Status {
if let Err(err) = imap.prepare(self).await {
warn!(self, "could not connect: {:?}", err);
return Status::RetryNow;
}
let quota = if imap.can_check_quota() {
let folders = get_watched_folders(self).await;
get_unique_quota_roots_and_usage(folders, imap).await
} else {
Err(anyhow!("Quota not supported by your provider."))
};
*self.quota.write().await = Some(QuotaInfo {
recent: quota,
modified: time(),
});
self.emit_event(EventType::ConnectivityChanged);
Status::Finished(Ok(()))
}
}

View File

@@ -48,7 +48,7 @@ impl Context {
pub async fn maybe_network_lost(&self) {
let lock = self.scheduler.read().await;
lock.maybe_network_lost().await;
connectivity::idle_interrupted(lock).await;
connectivity::maybe_network_lost(self, lock).await;
}
pub(crate) async fn interrupt_inbox(&self, info: InterruptInfo) {
@@ -298,7 +298,11 @@ async fn smtp_loop(ctx: Context, started: Sender<()>, smtp_handlers: SmtpConnect
None => {
// Fake Idle
info!(ctx, "smtp fake idle - started");
connection.connectivity.set_connected(&ctx).await;
match &connection.last_send_error {
None => connection.connectivity.set_connected(&ctx).await,
Some(err) => connection.connectivity.set_err(&ctx, err).await,
}
interrupt_info = idle_interrupt_receiver.recv().await.unwrap_or_default();
info!(ctx, "smtp fake idle - interrupted")
}

View File

@@ -3,9 +3,14 @@ use std::{ops::Deref, sync::Arc};
use async_std::sync::{Mutex, RwLockReadGuard};
use crate::dc_tools::time;
use crate::events::EventType;
use crate::quota::{
QUOTA_ERROR_THRESHOLD_PERCENTAGE, QUOTA_MAX_AGE_SECONDS, QUOTA_WARN_THRESHOLD_PERCENTAGE,
};
use crate::{config::Config, scheduler::Scheduler};
use crate::{context::Context, log::LogExt};
use humansize::{file_size_opts, FileSize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumProperty, PartialOrd, Ord)]
pub enum Connectivity {
@@ -20,7 +25,7 @@ pub enum Connectivity {
// the top) take priority. This means that e.g. if any folder has an error - usually
// because there is no internet connection - the connectivity for the whole
// account will be `Notconnected`.
#[derive(Debug, Clone, PartialEq, Eq, EnumProperty)]
#[derive(Debug, Clone, PartialEq, Eq, EnumProperty, PartialOrd)]
enum DetailedConnectivity {
Error(String),
Uninitialized,
@@ -193,6 +198,43 @@ pub(crate) async fn idle_interrupted(scheduler: RwLockReadGuard<'_, Scheduler>)
// of what we do here.
}
/// Set the connectivity to "Not connected" after a call to dc_maybe_network_lost().
/// If we did not do this, the connectivity would stay "Connected" for quite a long time
/// after `maybe_network_lost()` was called.
pub(crate) async fn maybe_network_lost(
context: &Context,
scheduler: RwLockReadGuard<'_, Scheduler>,
) {
let stores = match &*scheduler {
Scheduler::Running {
inbox,
mvbox,
sentbox,
..
} => [
inbox.state.connectivity.clone(),
mvbox.state.connectivity.clone(),
sentbox.state.connectivity.clone(),
],
Scheduler::Stopped => return,
};
drop(scheduler);
for store in &stores {
let mut connectivity_lock = store.0.lock().await;
if !matches!(
*connectivity_lock,
DetailedConnectivity::Uninitialized
| DetailedConnectivity::Error(_)
| DetailedConnectivity::NotConfigured,
) {
*connectivity_lock = DetailedConnectivity::Error("Connection lost".to_string());
}
drop(connectivity_lock);
}
context.emit_event(EventType::ConnectivityChanged);
}
impl fmt::Debug for ConnectivityStore {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(guard) = self.0.try_lock() {
@@ -366,6 +408,81 @@ impl Context {
ret += &*escaper::encode_minimal(&detailed.to_string_smtp(self));
ret += "</li></ul>";
ret += "<h3>Quota</h3><ul>";
let quota = self.quota.read().await;
if let Some(quota) = &*quota {
match &quota.recent {
Ok(quota) => {
let roots_cnt = quota.len();
for (root_name, resources) in quota {
use async_imap::types::QuotaResourceName::*;
for resource in resources {
ret += "<li>";
let usage_percent = resource.get_usage_percentage();
if usage_percent >= QUOTA_ERROR_THRESHOLD_PERCENTAGE {
ret += "<span class=\"red dot\"></span> ";
} else if usage_percent >= QUOTA_WARN_THRESHOLD_PERCENTAGE {
ret += "<span class=\"yellow dot\"></span> ";
} else {
ret += "<span class=\"green dot\"></span> ";
}
// root name is empty eg. for gmail and redundant eg. for riseup.
// therefore, use it only if there are really several roots.
if roots_cnt > 1 && !root_name.is_empty() {
ret +=
&format!("<b>{}:</b> ", &*escaper::encode_minimal(root_name));
} else {
info!(self, "connectivity: root name hidden: \"{}\"", root_name);
}
ret += &match &resource.name {
Atom(resource_name) => {
format!(
"<b>{}:</b> {} of {} used",
&*escaper::encode_minimal(resource_name),
resource.usage.to_string(),
resource.limit.to_string(),
)
}
Message => {
format!(
"<b>Messages:</b> {} of {} used",
resource.usage.to_string(),
resource.limit.to_string(),
)
}
Storage => {
let usage = (resource.usage * 1024)
.file_size(file_size_opts::BINARY)
.unwrap_or_default();
let limit = (resource.limit * 1024)
.file_size(file_size_opts::BINARY)
.unwrap_or_default();
format!("<b>Storage:</b> {} of {} used", usage, limit)
}
};
ret += &format!(" ({}%)", usage_percent);
ret += "</li>";
}
}
}
Err(e) => {
ret += format!("<li>{}</li>", e).as_str();
}
}
if quota.modified + QUOTA_MAX_AGE_SECONDS < time() {
self.schedule_quota_update().await;
}
} else {
ret += "<li>One moment...</li>";
self.schedule_quota_update().await;
}
ret += "</ul>";
ret += "</body></html>\n";
ret
}

View File

@@ -3,7 +3,7 @@
use std::convert::TryFrom;
use std::time::{Duration, Instant};
use anyhow::{bail, Context as _, Error, Result};
use anyhow::{anyhow, bail, Context as _, Error, Result};
use async_std::channel::Receiver;
use async_std::sync::Mutex;
use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
@@ -14,6 +14,7 @@ use crate::config::Config;
use crate::constants::{Blocked, Viewtype, DC_CONTACT_ID_LAST_SPECIAL};
use crate::contact::{Contact, Origin, VerifiedStatus};
use crate::context::Context;
use crate::dc_tools::time;
use crate::e2ee::ensure_secret_key_exists;
use crate::events::EventType;
use crate::headerdef::HeaderDef;
@@ -327,14 +328,14 @@ async fn securejoin(context: &Context, qr: &str) -> Result<ChatId, JoinError> {
let start = Instant::now();
let chatid = loop {
{
match chat::get_chat_id_by_grpid(context, &group_id).await {
Ok((chatid, _is_protected, _blocked)) => break chatid,
Err(err) => {
match chat::get_chat_id_by_grpid(context, &group_id).await? {
Some((chatid, _is_protected, _blocked)) => break chatid,
None => {
if start.elapsed() > Duration::from_secs(7) {
context.free_ongoing().await;
return Err(err
.context("Ongoing sender dropped (this is a bug)")
.into());
return Err(JoinError::Other(anyhow!(
"Ongoing sender dropped (this is a bug)"
)));
}
}
}
@@ -650,8 +651,8 @@ pub(crate) async fn handle_securejoin_handshake(
return Ok(HandshakeMessage::Ignore);
}
};
match chat::get_chat_id_by_grpid(context, field_grpid).await {
Ok((group_chat_id, _, _)) => {
match chat::get_chat_id_by_grpid(context, field_grpid).await? {
Some((group_chat_id, _, _)) => {
if let Err(err) =
chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, true)
.await
@@ -659,12 +660,7 @@ pub(crate) async fn handle_securejoin_handshake(
error!(context, "failed to add contact: {}", err);
}
}
Err(err) => {
error!(context, "Chat {} not found: {}", &field_grpid, err);
return Err(
err.context(format!("Chat for group {} not found", &field_grpid))
);
}
None => bail!("Chat {} not found", &field_grpid),
}
} else {
// Alice -> Bob
@@ -864,7 +860,7 @@ async fn secure_connection_established(
"?"
};
let msg = stock_str::contact_verified(context, addr).await;
chat::add_info_msg(context, contact_chat_id, msg).await;
chat::add_info_msg(context, contact_chat_id, msg, time()).await;
emit_event!(context, EventType::ChatModified(contact_chat_id));
info!(context, "StockMessage::ContactVerified posted to 1:1 chat");
@@ -888,7 +884,7 @@ async fn could_not_establish_secure_connection(
)
.await;
chat::add_info_msg(context, contact_chat_id, &msg).await;
chat::add_info_msg(context, contact_chat_id, &msg, time()).await;
error!(
context,
"StockMessage::ContactNotVerified posted to 1:1 chat ({})", details

View File

@@ -12,7 +12,7 @@ use anyhow::{Error, Result};
use async_std::sync::MutexGuard;
use crate::chat::{self, ChatId};
use crate::constants::{Blocked, Viewtype};
use crate::constants::Viewtype;
use crate::contact::{Contact, Origin};
use crate::context::Context;
use crate::events::EventType;
@@ -336,9 +336,10 @@ impl BobState {
// 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, grpid)
.await
.unwrap_or((ChatId::new(0), false, Blocked::Not));
let is_verified_group = chat::get_chat_id_by_grpid(context, grpid)
.await?
.map_or(false, |(_chat_id, is_protected, _blocked)| is_protected);
// when joining a non-verified group
// the vg-member-added message may be unencrypted
// when not all group members have keys or prefer encryption.

View File

@@ -54,6 +54,9 @@ pub(crate) struct Smtp {
last_success: Option<SystemTime>,
pub(crate) connectivity: ConnectivityStore,
/// If sending the last message failed, contains the error message.
pub(crate) last_send_error: Option<String>,
}
impl Smtp {
@@ -100,20 +103,14 @@ impl Smtp {
self.connectivity.set_connecting(context).await;
let lp = LoginParam::from_database(context, "configured_").await?;
let res = self
.connect(
context,
&lp.smtp,
&lp.addr,
lp.server_flags & DC_LP_AUTH_OAUTH2 != 0,
lp.provider.map_or(false, |provider| provider.strict_tls),
)
.await;
if let Err(err) = &res {
self.connectivity.set_err(context, err).await;
}
res
self.connect(
context,
&lp.smtp,
&lp.addr,
lp.server_flags & DC_LP_AUTH_OAUTH2 != 0,
lp.provider.map_or(false, |provider| provider.strict_tls),
)
.await
}
/// Connect using the provided login params.

View File

@@ -897,10 +897,6 @@ impl Context {
// add welcome-messages. by the label, this is done only once,
// if the user has deleted the message or the chat, it is not added again.
let mut msg = Message::new(Viewtype::Text);
msg.text = Some(device_messages_hint(self).await);
chat::add_device_msg(self, Some("core-about-device-chat"), Some(&mut msg)).await?;
let image = include_bytes!("../assets/welcome-image.jpg");
let blob = BlobObject::create(self, "welcome-image.jpg", image).await?;
let mut msg = Message::new(Viewtype::Image);

View File

@@ -413,7 +413,7 @@ impl TestContext {
// This code is mainly the same as `log_msglist` in `cmdline.rs`, so one day, we could
// merge them to a public function in the `deltachat` crate.
#[allow(dead_code)]
#[allow(clippy::clippy::indexing_slicing)]
#[allow(clippy::indexing_slicing)]
pub async fn print_chat(&self, chat_id: ChatId) {
let msglist = chat::get_chat_msgs(self, chat_id, 0x1, None).await.unwrap();
let msglist: Vec<MsgId> = msglist

View File

@@ -10,6 +10,7 @@ Text and Quote encoding | Fixed, Flowed ([RFC 3676](https://tools.ietf.
Filename encoding | Encoded Words ([RFC 2047](https://tools.ietf.org/html/rfc2047)), Encoded Word Extensions ([RFC 2231](https://tools.ietf.org/html/rfc2231))
Identify server folders | IMAP LIST Extension ([RFC 6154](https://tools.ietf.org/html/rfc6154))
Push | IMAP IDLE ([RFC 2177](https://tools.ietf.org/html/rfc2177))
Quota | IMAP QUOTA extension ([RFC 2087](https://tools.ietf.org/html/rfc2087))
Authorization | OAuth2 ([RFC 6749](https://tools.ietf.org/html/rfc6749))
End-to-end encryption | [Autocrypt Level 1](https://autocrypt.org/level1.html), OpenPGP ([RFC 4880](https://tools.ietf.org/html/rfc4880)), Security Multiparts for MIME ([RFC 1847](https://tools.ietf.org/html/rfc1847)) and [“Mixed Up” Encryption repairing](https://tools.ietf.org/id/draft-dkg-openpgp-pgpmime-message-mangling-00.html)
Configuration assistance | [Autoconfigure](https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration) and [Autodiscover](https://technet.microsoft.com/library/bb124251(v=exchg.150).aspx)

View File

@@ -0,0 +1,70 @@
Return-Path: <alice@example.com>
Delivered-To: bob@example.org
Received: from hq5.merlinux.eu
by hq5.merlinux.eu with LMTP
id GJ4eNagpFWF5UwAAPzvFDg
(envelope-from <alice@example.com>)
for <bob@example.org>; Thu, 12 Aug 2021 16:01:12 +0200
Received: from mout.gmx.net (mout.gmx.net [212.227.17.22])
by hq5.merlinux.eu (Postfix) with ESMTPS id 3033227A0003
for <bob@example.org>; Thu, 12 Aug 2021 16:01:12 +0200 (CEST)
Authentication-Results: hq5.merlinux.eu;
dkim=pass (1024-bit key; secure) header.d=gmx.net header.i=@gmx.net header.b="I/oyQzjt";
dkim-atps=neutral
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gmx.net;
s=badeba3b8450; t=1628776871;
bh=yVNQf4XVEEjSt/PzPPM4F9JXYSv2/ynVmb/E4dc6Qpk=;
h=X-UI-Sender-Class:From:To:Subject:Date;
b=I/oyQzjtFVDJiKkKV2/9DimrUXwhNtrHc5sgFkO7HNz6sheW8t0+8WpL76AfLuUU2
KZ/bCPyX3oItKl+31HZMoekrRnDyHiahsF1h3VrSzDXo3K0sk6nmZBjIQLuksGFW5i
/+5TkQ+p79YB/HioYm08pewz08caHfCt3EqcuJik=
X-UI-Sender-Class: 01bb95c1-4bf8-414a-932a-4f6e2808ef9c
Received: from [193.96.224.73] ([193.96.224.73]) by web-mail.gmx.net
(3c-app-gmx-bap57.server.lan [172.19.172.127]) (via HTTP); Thu, 12 Aug 2021
16:01:11 +0200
MIME-Version: 1.0
Message-ID: <trinity-18545f24-4f02-4dc8-9f80-8d2646646d03-1628776871644@3c-app-gmx-bap57>
From: Alice <alice@example.com>
To: bob@example.org
Subject: Fw: subject
Content-Type: text/html; charset=UTF-8
Date: Thu, 12 Aug 2021 16:01:11 +0200
Importance: normal
Sensitivity: Normal
X-UI-Message-Type: mail
X-Priority: 3
X-Provags-ID: V03:K1:vvJyGpka3W40JnBiz1FlxtNcdZw52KVodkX04BbaREoVau28F88gbimeb2Tm1t58pzQDe
HPoPiTTgz3Roj8GM/iIs4FqZxzcPiekR59a/GwFr16mPZQj+1cq6QOk144bXysBz3PHroQrc7Ctx
MtVLAY9w5+Lpuql24x9IqjA0eN2ytYrYYgX60d2FgU8CN/azK0bdEcsdyfwnAcf0bW9UY4ghE8Gt
hRe8z4WV6qEEzlhU+cI1uAixvNdQ6MFoi1oT7LdvfbUdcm1CBytWbbieGF1LjMa5Y+D4MZ3zUiiY
Ys=
X-Spam-Flag: NO
X-UI-Out-Filterresults: notjunk:1;V03:K0:pksZU4GoRZI=:jPKwLt7m9sSdgel28Ha/o7
UdLaJvQkSOD2tUGBq7n9rGeKT3opdBO5SWDRhn/qWLn+muPPYIjwmyE0XiGIjgTxLDJbY/LHL
bNWfcZ+geulQn9vH9muMcAW7ThwACRj3CCtWpc4y5ffTbo8VEinde4C4XFuhSUUdqyzu0GxYc
FklFTMlpL9ELxn5Mo3MaOnzznwrchd/2ogGzFz9wOtYUot+llyK+VLaylMeSSTIWbSLHwmA7l
MwsujGm4OvqP4VXSpVY2MecAGGwEvPsMQ/hfMgDsxRRm3sFsVZf6KFKcngZte0Nq6LZO9QU1x
tAmMgjZYPfOE+YSFiuKJ8E3YlsMk58HYTw/ON+m5T+lXSJeWVLA7sOgk9NKBGi2VzvrRz3YSg
MysXD8/h8PU7Rj7a2pttFyGxuN397xP3u1A+15LH5M2+AhUy4quzmxC0Ozb2chPdMJHgTO99e
5tmLkyYeeREmSB89pFzyOHGghENBflocaDiCidgWm6pd1lfMMjMQ8bA3S/QpE8e913WGCWhVQ
uecX4FBK1VEl8WkE/0GQhY8+2mzBE0+Jo1LCKJtAo9h8bG2fNJkujOpKKvUoududAYuajaHuq
rVl6G/xOP8JB3FDDNhZQptleN3KU5qPqNYz0qYibUCJNadS6XlwrfkZReJOk3yHnbIUvB9IG1
WGu+K/8WtQaYtzmNtZLD3c7YzQZT4v5xzxQ3TtROawkGNGk4gYJTnAd1ZWOkBHEjcSLsYFVYg
nhkLeamJ3KnnkBMJromM0tc0PmSdb/hqD/8hkrWQFvK/nmdNm9+z8UCmCTSDV98UodcwpAkJB
D+/kEFR3Y5K904h2dhgmSbnqZAVEziDNT2TylwBnxrpvIKX5Xw=
<html><head></head><body><div style="font-family: Verdana;font-size: 12.0px;"><div>&nbsp;</div>
<div>&nbsp;
<div>&nbsp;
<div data-darkreader-inline-border-left="" name="quote" style="margin: 10px 5px 5px 10px; padding: 10px 0px 10px 10px; border-left: 2px solid rgb(195, 217, 229); overflow-wrap: break-word; --darkreader-inline-border-left:#274759;">
<div style="margin:0 0 10px 0;"><b>Gesendet:</b>&nbsp;Donnerstag, 12. August 2021 um 15:52 Uhr<br/>
<b>Von:</b>&nbsp;&quot;Claire&quot; &lt;claire@example.org&gt;<br/>
<b>An:</b>&nbsp;alice@example.com<br/>
<b>Betreff:</b>&nbsp;subject</div>
<div name="quoted-content">bodytext</div>
</div>
</div>
</div></div></body></html>