mirror of
https://github.com/chatmail/core.git
synced 2026-04-05 15:02:11 +03:00
Compare commits
1 Commits
v1.135.0
...
iequidoo/i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7d88fa2cd |
100
CHANGELOG.md
100
CHANGELOG.md
@@ -1,104 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [1.135.0] - 2024-02-13
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Add wildcard pattern support to provider database.
|
||||
- Add device message about outgoing undecryptable messages ([#5164](https://github.com/deltachat/deltachat-core-rust/pull/5164)).
|
||||
- Context::set_config(): Restart IO scheduler if needed ([#5111](https://github.com/deltachat/deltachat-core-rust/pull/5111)).
|
||||
- Server_sent_unsolicited_exists(): Log folder name.
|
||||
- Cache system time instead of looking at the clock several times in a row.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Dehtml: Don't just truncate text when trying to decode ([#5223](https://github.com/deltachat/deltachat-core-rust/pull/5223)).
|
||||
- Mark the gossip keys from the message as verified, not the ones from the db ([#5247](https://github.com/deltachat/deltachat-core-rust/pull/5247)).
|
||||
- Guarantee immediate message deletion if delete_server_after == 0 ([#5201](https://github.com/deltachat/deltachat-core-rust/pull/5201)).
|
||||
- Never allow a message timestamp to be a lot in the future ([#5249](https://github.com/deltachat/deltachat-core-rust/pull/5249)).
|
||||
- Imap::configure_mvbox: Do select_with_uidvalidity() before return.
|
||||
- ImapSession::select_or_create_folder(): Don't fail if folder is created in parallel.
|
||||
- Emit ConfigSynced event on the second device.
|
||||
- Create mvbox on setting mvbox_move.
|
||||
- Use SystemTime instead of Instant everywhere.
|
||||
- Restore database rows removed in previous release; this ensures compatibility when adding second device or importing backup and not all devices run the new core ([#5254](https://github.com/deltachat/deltachat-core-rust/pull/5254))
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- cargo: Bump image from 0.24.7 to 0.24.8.
|
||||
- cargo: Bump chrono from 0.4.31 to 0.4.33.
|
||||
- cargo: Bump futures-lite from 2.1.0 to 2.2.0.
|
||||
- cargo: Bump pin-project from 1.1.3 to 1.1.4.
|
||||
- cargo: Bump iana-time-zone from yanked 0.1.59 to 0.1.60.
|
||||
- cargo: Bump smallvec from 1.11.2 to 1.13.1.
|
||||
- cargo: Bump base64 from 0.21.5 to 0.21.7.
|
||||
- cargo: Bump regex from 1.10.2 to 1.10.3.
|
||||
- cargo: Bump libc from 0.2.151 to 0.2.153.
|
||||
- cargo: Bump reqwest from 0.11.23 to 0.11.24.
|
||||
- cargo: Bump axum from 0.7.3 to 0.7.4.
|
||||
- cargo: Bump uuid from 1.6.1 to 1.7.0.
|
||||
- cargo: Bump fast-socks5 from 0.9.2 to 0.9.5.
|
||||
- cargo: Bump serde_json from 1.0.111 to 1.0.113.
|
||||
- cargo: Bump syn from 2.0.46 to 2.0.48.
|
||||
- cargo: Bump serde from 1.0.194 to 1.0.196.
|
||||
- cargo: Bump toml from 0.8.8 to 0.8.10.
|
||||
- cargo: Update to strum 0.26.
|
||||
- Cargo update.
|
||||
- scripts: Do not install deltachat-rpc-client twice.
|
||||
|
||||
### Other
|
||||
|
||||
- Update welcome image, thanks @paulaluap
|
||||
.
|
||||
- Merge pull request #5243 from deltachat/dependabot/cargo/pin-project-1.1.4
|
||||
|
||||
.
|
||||
- Merge pull request #5241 from deltachat/dependabot/cargo/futures-lite-2.2.0
|
||||
|
||||
.
|
||||
- Merge pull request #5236 from deltachat/dependabot/cargo/chrono-0.4.33
|
||||
|
||||
.
|
||||
- Merge pull request #5235 from deltachat/dependabot/cargo/image-0.24.8
|
||||
|
||||
.
|
||||
- Basic self-reporting, core part ([#5129](https://github.com/deltachat/deltachat-core-rust/pull/5129))
|
||||
|
||||
Part of https://github.com/deltachat/deltachat-android/issues/2909
|
||||
|
||||
For now, this is only sending a few basic metrics..
|
||||
- Do not change db schema in an incompatible way ([#5254](https://github.com/deltachat/deltachat-core-rust/pull/5254))
|
||||
|
||||
PR #5099 removed some columns in the database that were actually in use.
|
||||
|
||||
usually, to not worsen UX unnecessarily
|
||||
(releases take time - in between, "Add Second Device", "Backup" etc.
|
||||
would fail), we try to avoid such schema changes (checking for
|
||||
db-version would avoid import etc. but would still worse UX),
|
||||
see discussion at #2294.
|
||||
|
||||
these are the errors, the user will be confronted with otherwise:
|
||||
|
||||
<img width=400
|
||||
src=https://github.com/deltachat/deltachat-core-rust/assets/9800740/e3f0fd6e-a7a9-43f6-9023-0ae003985425>
|
||||
|
||||
it is not great to maintain the old columns, but well :)
|
||||
|
||||
as no official releases with newer cores are rolled out yet, i think, it
|
||||
is fine to change the "107" migration
|
||||
and not copy things a second time in a newer migration.
|
||||
|
||||
(this issue happens to me during testing, and is probably also the issue
|
||||
reported by @lk108 for ubuntu-touch).
|
||||
|
||||
### Refactor
|
||||
|
||||
- Resultify token::exists.
|
||||
|
||||
### Tests
|
||||
|
||||
- Delete_server_after="1" should cause immediate message deletion ([#5201](https://github.com/deltachat/deltachat-core-rust/pull/5201)).
|
||||
|
||||
## [1.134.0] - 2024-01-31
|
||||
|
||||
### API-Changes
|
||||
@@ -3575,4 +3476,3 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
|
||||
[1.133.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.133.0...v1.133.1
|
||||
[1.133.2]: https://github.com/deltachat/deltachat-core-rust/compare/v1.133.1...v1.133.2
|
||||
[1.134.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.133.2...v1.134.0
|
||||
[1.135.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.134.0...v1.135.0
|
||||
|
||||
496
Cargo.lock
generated
496
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.135.0"
|
||||
version = "1.134.0"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.70"
|
||||
@@ -75,8 +75,8 @@ qrcodegen = "1.7.0"
|
||||
quick-xml = "0.31"
|
||||
quoted_printable = "0.5"
|
||||
rand = "0.8"
|
||||
regex = "1.10"
|
||||
reqwest = { version = "0.11.24", features = ["json"] }
|
||||
regex = "1.9"
|
||||
reqwest = { version = "0.11.23", features = ["json"] }
|
||||
rusqlite = { version = "0.30", features = ["sqlcipher"] }
|
||||
rust-hsluv = "0.1"
|
||||
sanitize-filename = "0.5"
|
||||
@@ -85,8 +85,8 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
sha-1 = "0.10"
|
||||
sha2 = "0.10"
|
||||
smallvec = "1"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
strum = "0.25"
|
||||
strum_macros = "0.25"
|
||||
tagger = "4.3.4"
|
||||
textwrap = "0.16.0"
|
||||
thiserror = "1"
|
||||
|
||||
13
RELEASE.md
13
RELEASE.md
@@ -9,16 +9,13 @@ For example, to release version 1.116.0 of the core, do the following steps.
|
||||
|
||||
3. Update the changelog: `git cliff --unreleased --tag 1.116.0 --prepend CHANGELOG.md` or `git cliff -u -t 1.116.0 -p CHANGELOG.md`.
|
||||
|
||||
4. add a link to compare previous with current version to the end of CHANGELOG.md:
|
||||
`[1.116.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.115.2...v1.116.0`
|
||||
4. Update the version by running `scripts/set_core_version.py 1.116.0`.
|
||||
|
||||
5. Update the version by running `scripts/set_core_version.py 1.116.0`.
|
||||
|
||||
6. Commit the changes as `chore(release): prepare for 1.116.0`.
|
||||
5. Commit the changes as `chore(release): prepare for 1.116.0`.
|
||||
Optionally, use a separate branch like `prep-1.116.0` for this commit and open a PR for review.
|
||||
|
||||
7. Tag the release: `git tag -a v1.116.0`.
|
||||
6. Tag the release: `git tag -a v1.116.0`.
|
||||
|
||||
8. Push the release tag: `git push origin v1.116.0`.
|
||||
7. Push the release tag: `git push origin v1.116.0`.
|
||||
|
||||
9. Create a GitHub release: `gh release create v1.116.0 -n ''`.
|
||||
8. Create a GitHub release: `gh release create v1.116.0 -n ''`.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.135.0"
|
||||
version = "1.134.0"
|
||||
description = "Deltachat FFI"
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -423,16 +423,19 @@ char* dc_get_blobdir (const dc_context_t* context);
|
||||
* Sending messages to self is needed for a proper multi-account setup,
|
||||
* however, on the other hand, may lead to unwanted notifications in non-delta clients.
|
||||
* - `sentbox_watch`= 1=watch `Sent`-folder for changes,
|
||||
* 0=do not watch the `Sent`-folder (default).
|
||||
* 0=do not watch the `Sent`-folder (default),
|
||||
* changes require restarting IO by calling dc_stop_io() and then dc_start_io().
|
||||
* - `mvbox_move` = 1=detect chat messages,
|
||||
* move them to the `DeltaChat` folder,
|
||||
* and watch the `DeltaChat` folder for updates (default),
|
||||
* 0=do not move chat-messages
|
||||
* changes require restarting IO by calling dc_stop_io() and then dc_start_io().
|
||||
* - `only_fetch_mvbox` = 1=Do not fetch messages from folders other than the
|
||||
* `DeltaChat` folder. Messages will still be fetched from the
|
||||
* spam folder and `sendbox_watch` will also still be respected
|
||||
* if enabled.
|
||||
* 0=watch all folders normally (default)
|
||||
* changes require restarting IO by calling dc_stop_io() and then dc_start_io().
|
||||
* - `show_emails` = DC_SHOW_EMAILS_OFF (0)=
|
||||
* show direct replies to chats only,
|
||||
* DC_SHOW_EMAILS_ACCEPTED_CONTACTS (1)=
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "1.135.0"
|
||||
version = "1.134.0"
|
||||
description = "DeltaChat JSON-RPC API"
|
||||
edition = "2021"
|
||||
default-run = "deltachat-jsonrpc-server"
|
||||
|
||||
@@ -251,7 +251,7 @@ impl CommandApi {
|
||||
|
||||
/// Starts background tasks for a single account.
|
||||
async fn start_io(&self, account_id: u32) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let mut ctx = self.get_context(account_id).await?;
|
||||
ctx.start_io().await;
|
||||
Ok(())
|
||||
}
|
||||
@@ -325,11 +325,6 @@ impl CommandApi {
|
||||
ctx.get_info().await
|
||||
}
|
||||
|
||||
async fn draft_self_report(&self, account_id: u32) -> Result<u32> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
Ok(ctx.draft_self_report().await?.to_u32())
|
||||
}
|
||||
|
||||
/// Sets the given configuration key.
|
||||
async fn set_config(&self, account_id: u32, key: String, value: Option<String>) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
@@ -402,7 +397,7 @@ impl CommandApi {
|
||||
/// Configures this account with the currently set parameters.
|
||||
/// Setup the credential config before calling this.
|
||||
async fn configure(&self, account_id: u32) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let mut ctx = self.get_context(account_id).await?;
|
||||
ctx.stop_io().await;
|
||||
let result = ctx.configure().await;
|
||||
if result.is_err() {
|
||||
@@ -2125,6 +2120,13 @@ async fn set_config(
|
||||
value,
|
||||
)
|
||||
.await?;
|
||||
|
||||
match key {
|
||||
"sentbox_watch" | "mvbox_move" | "only_fetch_mvbox" => {
|
||||
ctx.restart_io_if_running().await;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -53,5 +53,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "1.135.0"
|
||||
"version": "1.134.0"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-repl"
|
||||
version = "1.135.0"
|
||||
version = "1.134.0"
|
||||
license = "MPL-2.0"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -401,7 +401,7 @@ enum ExitResult {
|
||||
|
||||
async fn handle_cmd(
|
||||
line: &str,
|
||||
ctx: Context,
|
||||
mut ctx: Context,
|
||||
selected_chat: &mut ChatId,
|
||||
) -> Result<ExitResult, Error> {
|
||||
let mut args = line.splitn(2, ' ');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "1.135.0"
|
||||
version = "1.134.0"
|
||||
description = "DeltaChat JSON-RPC server"
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -250,7 +250,6 @@ describe('Basic offline Tests', function () {
|
||||
'journal_mode',
|
||||
'key_gen_type',
|
||||
'last_housekeeping',
|
||||
'last_cant_decrypt_outgoing_msgs',
|
||||
'level',
|
||||
'mdns_enabled',
|
||||
'media_quality',
|
||||
|
||||
@@ -55,5 +55,5 @@
|
||||
"test:mocha": "mocha node/test/test.mjs --growl --reporter=spec --bail --exit"
|
||||
},
|
||||
"types": "node/dist/index.d.ts",
|
||||
"version": "1.135.0"
|
||||
"version": "1.134.0"
|
||||
}
|
||||
|
||||
@@ -627,7 +627,14 @@ def test_verified_group_vs_delete_server_after(acfactory, tmp_path, lp):
|
||||
chat2 = ac2.qr_join_chat(qr)
|
||||
ac1._evtracker.wait_securejoin_inviter_progress(1000)
|
||||
# Wait for "Member Me (<addr>) added by <addr>." message.
|
||||
msg_in = ac2._evtracker.wait_next_incoming_message()
|
||||
try:
|
||||
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG", timeout=60)
|
||||
msg_in = ac2.get_message_by_id(ev.data2)
|
||||
except Exception:
|
||||
ac2.stop_io()
|
||||
ac2.start_io()
|
||||
msg_in = ac2._evtracker.wait_next_incoming_message()
|
||||
assert False
|
||||
assert msg_in.is_system_message()
|
||||
|
||||
lp.sec("ac2: waiting for 'member added' to be deleted on the server")
|
||||
|
||||
@@ -382,21 +382,6 @@ def test_webxdc_download_on_demand(acfactory, data, lp):
|
||||
assert msgs_changed_event.data2 == 0
|
||||
|
||||
|
||||
def test_enable_mvbox_move(acfactory, lp):
|
||||
(ac1,) = acfactory.get_online_accounts(1)
|
||||
|
||||
lp.sec("ac2: start without mvbox thread")
|
||||
ac2 = acfactory.new_online_configuring_account(mvbox_move=False)
|
||||
acfactory.bring_accounts_online()
|
||||
|
||||
lp.sec("ac2: configuring mvbox")
|
||||
ac2.set_config("mvbox_move", "1")
|
||||
|
||||
lp.sec("ac1: send message and wait for ac2 to receive it")
|
||||
acfactory.get_accepted_chat(ac1, ac2).send_text("message1")
|
||||
assert ac2._evtracker.wait_next_incoming_message().text == "message1"
|
||||
|
||||
|
||||
def test_mvbox_sentbox_threads(acfactory, lp):
|
||||
lp.sec("ac1: start with mvbox thread")
|
||||
ac1 = acfactory.new_online_configuring_account(mvbox_move=True, sentbox_watch=True)
|
||||
|
||||
@@ -1 +1 @@
|
||||
2024-02-13
|
||||
2024-01-31
|
||||
@@ -9,4 +9,5 @@ python3 -m venv venv
|
||||
venv/bin/pip install ./python
|
||||
venv/bin/pip install ./deltachat-rpc-client
|
||||
venv/bin/pip install sphinx breathe sphinx_rtd_theme
|
||||
venv/bin/pip install ./deltachat-rpc-client
|
||||
venv/bin/sphinx-build -b html -a python/doc/ dist/html
|
||||
|
||||
@@ -220,9 +220,9 @@ if __name__ == "__main__":
|
||||
|
||||
process_dir(Path(sys.argv[1]))
|
||||
|
||||
out_all += "pub(crate) static PROVIDER_DATA: [(&str, &Provider); " + str(len(domains_set)) + "] = [\n";
|
||||
out_all += "pub(crate) static PROVIDER_DATA: Lazy<HashMap<&'static str, &'static Provider>> = Lazy::new(|| HashMap::from([\n"
|
||||
out_all += out_domains
|
||||
out_all += "];\n\n"
|
||||
out_all += "]));\n\n"
|
||||
|
||||
out_all += "pub(crate) static PROVIDER_IDS: Lazy<HashMap<&'static str, &'static Provider>> = Lazy::new(|| HashMap::from([\n"
|
||||
out_all += out_ids
|
||||
|
||||
@@ -6,7 +6,7 @@ set -euo pipefail
|
||||
export TZ=UTC
|
||||
|
||||
# Provider database revision.
|
||||
REV=2f3db24107e4802c2df0aa0a40f0e144006c0a9b
|
||||
REV=18f714cf73d0bdfb8b013fa344494ab80c92b477
|
||||
|
||||
CORE_ROOT="$PWD"
|
||||
TMP="$(mktemp -d)"
|
||||
|
||||
@@ -216,7 +216,7 @@ async fn update_authservid_candidates(
|
||||
if old_ids != new_ids {
|
||||
let new_config = new_ids.into_iter().collect::<Vec<_>>().join(" ");
|
||||
context
|
||||
.set_config_internal(Config::AuthservIdCandidates, Some(&new_config))
|
||||
.set_config(Config::AuthservIdCandidates, Some(&new_config))
|
||||
.await?;
|
||||
// Updating the authservid candidates may mean that we now consider
|
||||
// emails as "failed" which "passed" previously, so we need to
|
||||
|
||||
16
src/chat.rs
16
src/chat.rs
@@ -783,9 +783,7 @@ impl ChatId {
|
||||
|
||||
context.emit_msgs_changed_without_ids();
|
||||
|
||||
context
|
||||
.set_config_internal(Config::LastHousekeeping, None)
|
||||
.await?;
|
||||
context.set_config(Config::LastHousekeeping, None).await?;
|
||||
context.scheduler.interrupt_inbox().await;
|
||||
|
||||
if chat.is_self_talk() {
|
||||
@@ -2806,14 +2804,12 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -
|
||||
);
|
||||
}
|
||||
|
||||
let now = time();
|
||||
|
||||
if rendered_msg.is_gossiped {
|
||||
msg.chat_id.set_gossiped_timestamp(context, now).await?;
|
||||
msg.chat_id.set_gossiped_timestamp(context, time()).await?;
|
||||
}
|
||||
|
||||
if let Some(last_added_location_id) = rendered_msg.last_added_location_id {
|
||||
if let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, now).await {
|
||||
if let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, time()).await {
|
||||
error!(context, "Failed to set kml sent_timestamp: {err:#}.");
|
||||
}
|
||||
if !msg.hidden {
|
||||
@@ -2832,7 +2828,7 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -
|
||||
}
|
||||
|
||||
if attach_selfavatar {
|
||||
if let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, now).await {
|
||||
if let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, time()).await {
|
||||
error!(context, "Failed to set selfavatar timestamp: {err:#}.");
|
||||
}
|
||||
}
|
||||
@@ -4270,9 +4266,7 @@ pub(crate) async fn delete_and_reset_all_device_msgs(context: &Context) -> Resul
|
||||
(),
|
||||
)
|
||||
.await?;
|
||||
context
|
||||
.set_config_internal(Config::QuotaExceeding, None)
|
||||
.await?;
|
||||
context.set_config(Config::QuotaExceeding, None).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
205
src/config.rs
205
src/config.rs
@@ -10,7 +10,7 @@ use strum::{EnumProperty, IntoEnumIterator};
|
||||
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
|
||||
|
||||
use crate::blob::BlobObject;
|
||||
use crate::constants::{self, DC_VERSION_STR};
|
||||
use crate::constants::DC_VERSION_STR;
|
||||
use crate::contact::addr_cmp;
|
||||
use crate::context::Context;
|
||||
use crate::events::EventType;
|
||||
@@ -291,9 +291,6 @@ pub enum Config {
|
||||
/// Timestamp of the last time housekeeping was run
|
||||
LastHousekeeping,
|
||||
|
||||
/// Timestamp of the last `CantDecryptOutgoingMsgs` notification.
|
||||
LastCantDecryptOutgoingMsgs,
|
||||
|
||||
/// To how many seconds to debounce scan_all_folders. Used mainly in tests, to disable debouncing completely.
|
||||
#[strum(props(default = "60"))]
|
||||
ScanAllFoldersDebounceSecs,
|
||||
@@ -350,10 +347,6 @@ pub enum Config {
|
||||
/// Row ID of the key in the `keypairs` table
|
||||
/// used for signatures, encryption to self and included in `Autocrypt` header.
|
||||
KeyId,
|
||||
|
||||
/// This key is sent to the self_reporting bot so that the bot can recognize the user
|
||||
/// without storing the email address
|
||||
SelfReportingId,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -367,23 +360,11 @@ impl Config {
|
||||
/// multiple users are sharing an account. Another example is `Self::SyncMsgs` itself which
|
||||
/// mustn't be controlled by other devices.
|
||||
pub(crate) fn is_synced(&self) -> bool {
|
||||
// We don't restart IO from the synchronisation code, so this is to be on the safe side.
|
||||
if self.needs_io_restart() {
|
||||
return false;
|
||||
}
|
||||
matches!(
|
||||
self,
|
||||
Self::Displayname | Self::MdnsEnabled | Self::ShowEmails
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether the config option needs an IO scheduler restart to take effect.
|
||||
pub(crate) fn needs_io_restart(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Config::MvboxMove | Config::OnlyFetchMvbox | Config::SentboxWatch
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
@@ -503,50 +484,10 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_config(key: Config, value: Option<&str>) -> Result<()> {
|
||||
match key {
|
||||
Config::Socks5Enabled
|
||||
| Config::BccSelf
|
||||
| Config::E2eeEnabled
|
||||
| Config::MdnsEnabled
|
||||
| Config::SentboxWatch
|
||||
| Config::MvboxMove
|
||||
| Config::OnlyFetchMvbox
|
||||
| Config::FetchExistingMsgs
|
||||
| Config::DeleteToTrash
|
||||
| Config::SaveMimeHeaders
|
||||
| Config::Configured
|
||||
| Config::Bot
|
||||
| Config::NotifyAboutWrongPw
|
||||
| Config::SyncMsgs
|
||||
| Config::SignUnencrypted
|
||||
| Config::DisableIdle => {
|
||||
ensure!(
|
||||
matches!(value, None | Some("0") | Some("1")),
|
||||
"Boolean value must be either 0 or 1"
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the given config key and make it effective.
|
||||
/// This may restart the IO scheduler. If `None` is passed as a value the value is cleared and
|
||||
/// set to the default if there is one.
|
||||
/// Set the given config key.
|
||||
/// If `None` is passed as a value the value is cleared and set to the default if there is one.
|
||||
pub async fn set_config(&self, key: Config, value: Option<&str>) -> Result<()> {
|
||||
Self::check_config(key, value)?;
|
||||
|
||||
let _pause = match key.needs_io_restart() {
|
||||
true => self.scheduler.pause(self.clone()).await?,
|
||||
_ => Default::default(),
|
||||
};
|
||||
self.set_config_internal(key, value).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn set_config_internal(&self, key: Config, value: Option<&str>) -> Result<()> {
|
||||
self.set_config_ex(Sync, key, value).await
|
||||
self.set_config_ex(key.is_synced().into(), key, value).await
|
||||
}
|
||||
|
||||
pub(crate) async fn set_config_ex(
|
||||
@@ -555,10 +496,7 @@ impl Context {
|
||||
key: Config,
|
||||
mut value: Option<&str>,
|
||||
) -> Result<()> {
|
||||
Self::check_config(key, value)?;
|
||||
let sync = sync == Sync && key.is_synced();
|
||||
let better_value;
|
||||
|
||||
match key {
|
||||
Config::Selfavatar => {
|
||||
self.sql
|
||||
@@ -591,27 +529,42 @@ impl Context {
|
||||
}
|
||||
self.sql.set_raw_config(key.as_ref(), value).await?;
|
||||
}
|
||||
Config::Socks5Enabled
|
||||
| Config::BccSelf
|
||||
| Config::E2eeEnabled
|
||||
| Config::MdnsEnabled
|
||||
| Config::SentboxWatch
|
||||
| Config::MvboxMove
|
||||
| Config::OnlyFetchMvbox
|
||||
| Config::FetchExistingMsgs
|
||||
| Config::DeleteToTrash
|
||||
| Config::SaveMimeHeaders
|
||||
| Config::Configured
|
||||
| Config::Bot
|
||||
| Config::NotifyAboutWrongPw
|
||||
| Config::SyncMsgs
|
||||
| Config::SignUnencrypted
|
||||
| Config::DisableIdle => {
|
||||
ensure!(
|
||||
matches!(value, None | Some("0") | Some("1")),
|
||||
"Boolean value must be either 0 or 1"
|
||||
);
|
||||
self.sql.set_raw_config(key.as_ref(), value).await?;
|
||||
}
|
||||
Config::Addr => {
|
||||
self.sql
|
||||
.set_raw_config(key.as_ref(), value.map(|s| s.to_lowercase()).as_deref())
|
||||
.await?;
|
||||
}
|
||||
Config::MvboxMove => {
|
||||
self.sql.set_raw_config(key.as_ref(), value).await?;
|
||||
self.sql
|
||||
.set_raw_config(constants::DC_FOLDERS_CONFIGURED_KEY, None)
|
||||
.await?;
|
||||
}
|
||||
_ => {
|
||||
self.sql.set_raw_config(key.as_ref(), value).await?;
|
||||
}
|
||||
}
|
||||
if key.is_synced() {
|
||||
self.emit_event(EventType::ConfigSynced { key });
|
||||
}
|
||||
if !sync {
|
||||
|
||||
if sync != Sync {
|
||||
return Ok(());
|
||||
}
|
||||
self.emit_event(EventType::ConfigSynced { key });
|
||||
let Some(val) = value else {
|
||||
return Ok(());
|
||||
};
|
||||
@@ -636,7 +589,8 @@ impl Context {
|
||||
|
||||
/// Set the given config to a boolean value.
|
||||
pub async fn set_config_bool(&self, key: Config, value: bool) -> Result<()> {
|
||||
self.set_config(key, from_bool(value)).await?;
|
||||
self.set_config(key, if value { Some("1") } else { Some("0") })
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -656,11 +610,6 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a value for use in `Context::set_config_*()` for the given `bool`.
|
||||
pub(crate) fn from_bool(val: bool) -> Option<&'static str> {
|
||||
Some(if val { "1" } else { "0" })
|
||||
}
|
||||
|
||||
// Separate impl block for self address handling
|
||||
impl Context {
|
||||
/// Determine whether the specified addr maps to the/a self addr.
|
||||
@@ -687,13 +636,13 @@ impl Context {
|
||||
let mut secondary_addrs = self.get_all_self_addrs().await?;
|
||||
// never store a primary address also as a secondary
|
||||
secondary_addrs.retain(|a| !addr_cmp(a, primary_new));
|
||||
self.set_config_internal(
|
||||
self.set_config(
|
||||
Config::SecondaryAddrs,
|
||||
Some(secondary_addrs.join(" ").as_str()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.set_config_internal(Config::ConfiguredAddr, Some(primary_new))
|
||||
self.set_config(Config::ConfiguredAddr, Some(primary_new))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
@@ -919,9 +868,42 @@ mod tests {
|
||||
// versions.
|
||||
alice0.set_config(Config::MdnsEnabled, None).await?;
|
||||
assert_eq!(alice0.get_config_bool(Config::MdnsEnabled).await?, true);
|
||||
alice0
|
||||
.evtracker
|
||||
.get_matching(|e| {
|
||||
matches!(
|
||||
e,
|
||||
EventType::ConfigSynced {
|
||||
key: Config::MdnsEnabled
|
||||
}
|
||||
)
|
||||
})
|
||||
.await;
|
||||
alice0.set_config_bool(Config::MdnsEnabled, false).await?;
|
||||
alice0
|
||||
.evtracker
|
||||
.get_matching(|e| {
|
||||
matches!(
|
||||
e,
|
||||
EventType::ConfigSynced {
|
||||
key: Config::MdnsEnabled
|
||||
}
|
||||
)
|
||||
})
|
||||
.await;
|
||||
sync(&alice0, &alice1).await;
|
||||
assert_eq!(alice1.get_config_bool(Config::MdnsEnabled).await?, false);
|
||||
alice1
|
||||
.evtracker
|
||||
.get_matching(|e| {
|
||||
matches!(
|
||||
e,
|
||||
EventType::ConfigSynced {
|
||||
key: Config::MdnsEnabled
|
||||
}
|
||||
)
|
||||
})
|
||||
.await;
|
||||
|
||||
let show_emails = alice0.get_config_bool(Config::ShowEmails).await?;
|
||||
alice0
|
||||
@@ -952,59 +934,4 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_event_config_synced() -> Result<()> {
|
||||
let alice0 = TestContext::new_alice().await;
|
||||
let alice1 = TestContext::new_alice().await;
|
||||
for a in [&alice0, &alice1] {
|
||||
a.set_config_bool(Config::SyncMsgs, true).await?;
|
||||
}
|
||||
|
||||
alice0
|
||||
.set_config(Config::Displayname, Some("Alice Sync"))
|
||||
.await?;
|
||||
alice0
|
||||
.evtracker
|
||||
.get_matching(|e| {
|
||||
matches!(
|
||||
e,
|
||||
EventType::ConfigSynced {
|
||||
key: Config::Displayname
|
||||
}
|
||||
)
|
||||
})
|
||||
.await;
|
||||
sync(&alice0, &alice1).await;
|
||||
assert_eq!(
|
||||
alice1.get_config(Config::Displayname).await?,
|
||||
Some("Alice Sync".to_string())
|
||||
);
|
||||
alice1
|
||||
.evtracker
|
||||
.get_matching(|e| {
|
||||
matches!(
|
||||
e,
|
||||
EventType::ConfigSynced {
|
||||
key: Config::Displayname
|
||||
}
|
||||
)
|
||||
})
|
||||
.await;
|
||||
|
||||
alice0.set_config(Config::Displayname, None).await?;
|
||||
alice0
|
||||
.evtracker
|
||||
.get_matching(|e| {
|
||||
matches!(
|
||||
e,
|
||||
EventType::ConfigSynced {
|
||||
key: Config::Displayname
|
||||
}
|
||||
)
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
||||
use server_params::{expand_param_vector, ServerParams};
|
||||
use tokio::task;
|
||||
|
||||
use crate::config::{self, Config};
|
||||
use crate::config::Config;
|
||||
use crate::contact::addr_cmp;
|
||||
use crate::context::Context;
|
||||
use crate::imap::Imap;
|
||||
@@ -112,13 +112,12 @@ impl Context {
|
||||
let mut param = LoginParam::load_candidate_params(self).await?;
|
||||
let old_addr = self.get_config(Config::ConfiguredAddr).await?;
|
||||
let success = configure(self, &mut param).await;
|
||||
self.set_config_internal(Config::NotifyAboutWrongPw, None)
|
||||
.await?;
|
||||
self.set_config(Config::NotifyAboutWrongPw, None).await?;
|
||||
|
||||
on_configure_completed(self, param, old_addr).await?;
|
||||
|
||||
success?;
|
||||
self.set_config_internal(Config::NotifyAboutWrongPw, Some("1"))
|
||||
self.set_config(Config::NotifyAboutWrongPw, Some("1"))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -474,7 +473,7 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
|
||||
|
||||
// the trailing underscore is correct
|
||||
param.save_as_configured_params(ctx).await?;
|
||||
ctx.set_config_internal(Config::ConfiguredTimestamp, Some(&time().to_string()))
|
||||
ctx.set_config(Config::ConfiguredTimestamp, Some(&time().to_string()))
|
||||
.await?;
|
||||
|
||||
progress!(ctx, 920);
|
||||
@@ -482,7 +481,7 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
|
||||
e2ee::ensure_secret_key_exists(ctx).await?;
|
||||
info!(ctx, "key generation completed");
|
||||
|
||||
ctx.set_config_internal(Config::FetchedExistingMsgs, config::from_bool(false))
|
||||
ctx.set_config_bool(Config::FetchedExistingMsgs, false)
|
||||
.await?;
|
||||
ctx.scheduler.interrupt_inbox().await;
|
||||
|
||||
|
||||
@@ -206,8 +206,6 @@ pub(crate) const WORSE_AVATAR_SIZE: u32 = 128;
|
||||
pub const BALANCED_IMAGE_SIZE: u32 = 1280;
|
||||
pub const WORSE_IMAGE_SIZE: u32 = 640;
|
||||
|
||||
// Key for the folder configuration version (see below).
|
||||
pub(crate) const DC_FOLDERS_CONFIGURED_KEY: &str = "folders_configured";
|
||||
// this value can be increased if the folder configuration is changed and must be redone on next program start
|
||||
pub(crate) const DC_FOLDERS_CONFIGURED_VERSION: i32 = 4;
|
||||
|
||||
@@ -217,7 +215,7 @@ pub(crate) const DC_FOLDERS_CONFIGURED_VERSION: i32 = 4;
|
||||
pub(crate) const DEFAULT_MAX_SMTP_RCPT_TO: usize = 50;
|
||||
|
||||
/// How far the last quota check needs to be in the past to be checked by the background function (in seconds).
|
||||
pub(crate) const DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT: u64 = 12 * 60 * 60; // 12 hours
|
||||
pub(crate) const DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT: i64 = 12 * 60 * 60; // 12 hours
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -1550,7 +1550,7 @@ pub(crate) async fn set_profile_image(
|
||||
if contact_id == ContactId::SELF {
|
||||
if was_encrypted {
|
||||
context
|
||||
.set_config_internal(Config::Selfavatar, Some(profile_image))
|
||||
.set_config(Config::Selfavatar, Some(profile_image))
|
||||
.await?;
|
||||
} else {
|
||||
info!(context, "Do not use unencrypted selfavatar.");
|
||||
@@ -1563,9 +1563,7 @@ pub(crate) async fn set_profile_image(
|
||||
AvatarAction::Delete => {
|
||||
if contact_id == ContactId::SELF {
|
||||
if was_encrypted {
|
||||
context
|
||||
.set_config_internal(Config::Selfavatar, None)
|
||||
.await?;
|
||||
context.set_config(Config::Selfavatar, None).await?;
|
||||
} else {
|
||||
info!(context, "Do not use unencrypted selfavatar deletion.");
|
||||
}
|
||||
@@ -1597,7 +1595,7 @@ pub(crate) async fn set_status(
|
||||
if contact_id == ContactId::SELF {
|
||||
if encrypted && has_chat_version {
|
||||
context
|
||||
.set_config_internal(Config::Selfstatus, Some(&status))
|
||||
.set_config(Config::Selfstatus, Some(&status))
|
||||
.await?;
|
||||
}
|
||||
} else {
|
||||
@@ -1732,12 +1730,6 @@ impl RecentlySeenLoop {
|
||||
async fn run(context: Context, interrupt: Receiver<RecentlySeenInterrupt>) {
|
||||
type MyHeapElem = (Reverse<i64>, ContactId);
|
||||
|
||||
let now = SystemTime::now();
|
||||
let now_ts = now
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs() as i64;
|
||||
|
||||
// Priority contains all recently seen sorted by the timestamp
|
||||
// when they become not recently seen.
|
||||
//
|
||||
@@ -1748,7 +1740,7 @@ impl RecentlySeenLoop {
|
||||
.query_map(
|
||||
"SELECT id, last_seen FROM contacts
|
||||
WHERE last_seen > ?",
|
||||
(now_ts - SEEN_RECENTLY_SECONDS,),
|
||||
(time() - SEEN_RECENTLY_SECONDS,),
|
||||
|row| {
|
||||
let contact_id: ContactId = row.get("id")?;
|
||||
let last_seen: i64 = row.get("last_seen")?;
|
||||
@@ -1763,6 +1755,8 @@ impl RecentlySeenLoop {
|
||||
.unwrap_or_default();
|
||||
|
||||
loop {
|
||||
let now = SystemTime::now();
|
||||
|
||||
let (until, contact_id) =
|
||||
if let Some((Reverse(timestamp), contact_id)) = unseen_queue.peek() {
|
||||
(
|
||||
|
||||
168
src/context.rs
168
src/context.rs
@@ -6,34 +6,29 @@ use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant, SystemTime};
|
||||
|
||||
use anyhow::{bail, ensure, Context as _, Result};
|
||||
use async_channel::{self as channel, Receiver, Sender};
|
||||
use pgp::SignedPublicKey;
|
||||
use ratelimit::Ratelimit;
|
||||
use tokio::sync::{Mutex, Notify, RwLock};
|
||||
|
||||
use crate::aheader::EncryptPreference;
|
||||
use crate::chat::{get_chat_cnt, ChatId, ProtectionStatus};
|
||||
use crate::chat::{get_chat_cnt, ChatId};
|
||||
use crate::config::Config;
|
||||
use crate::constants::{
|
||||
self, DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_CHAT_ID_TRASH, DC_VERSION_STR,
|
||||
};
|
||||
use crate::constants::{DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_VERSION_STR};
|
||||
use crate::contact::Contact;
|
||||
use crate::debug_logging::DebugLogging;
|
||||
use crate::events::{Event, EventEmitter, EventType, Events};
|
||||
use crate::imap::{FolderMeaning, Imap, ServerMetadata};
|
||||
use crate::key::{load_self_public_key, load_self_secret_key, DcKey as _};
|
||||
use crate::key::{load_self_public_key, DcKey as _};
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::message::{self, Message, MessageState, MsgId, Viewtype};
|
||||
use crate::peerstate::Peerstate;
|
||||
use crate::message::{self, MessageState, MsgId};
|
||||
use crate::quota::QuotaInfo;
|
||||
use crate::scheduler::{convert_folder_meaning, SchedulerState};
|
||||
use crate::sql::Sql;
|
||||
use crate::stock_str::StockStrings;
|
||||
use crate::timesmearing::SmearedTimestamp;
|
||||
use crate::tools::{self, create_id, duration_to_str, time, time_elapsed};
|
||||
use crate::tools::{duration_to_str, time};
|
||||
|
||||
/// Builder for the [`Context`].
|
||||
///
|
||||
@@ -233,7 +228,7 @@ pub struct InnerContext {
|
||||
/// IMAP METADATA.
|
||||
pub(crate) metadata: RwLock<Option<ServerMetadata>>,
|
||||
|
||||
pub(crate) last_full_folder_scan: Mutex<Option<tools::Time>>,
|
||||
pub(crate) last_full_folder_scan: Mutex<Option<Instant>>,
|
||||
|
||||
/// ID for this `Context` in the current process.
|
||||
///
|
||||
@@ -241,7 +236,7 @@ pub struct InnerContext {
|
||||
/// be identified by this ID.
|
||||
pub(crate) id: u32,
|
||||
|
||||
creation_time: tools::Time,
|
||||
creation_time: SystemTime,
|
||||
|
||||
/// The text of the last error logged and emitted as an event.
|
||||
/// If the ui wants to display an error after a failure,
|
||||
@@ -262,7 +257,7 @@ enum RunningState {
|
||||
Running { cancel_sender: Sender<()> },
|
||||
|
||||
/// Cancel signal has been sent, waiting for ongoing process to be freed.
|
||||
ShallStop { request: tools::Time },
|
||||
ShallStop { request: Instant },
|
||||
|
||||
/// There is no ongoing process, a new one can be allocated.
|
||||
Stopped,
|
||||
@@ -394,7 +389,7 @@ impl Context {
|
||||
new_msgs_notify,
|
||||
server_id: RwLock::new(None),
|
||||
metadata: RwLock::new(None),
|
||||
creation_time: tools::Time::now(),
|
||||
creation_time: std::time::SystemTime::now(),
|
||||
last_full_folder_scan: Mutex::new(None),
|
||||
last_error: std::sync::RwLock::new("".to_string()),
|
||||
debug_logging: std::sync::RwLock::new(None),
|
||||
@@ -408,7 +403,7 @@ impl Context {
|
||||
}
|
||||
|
||||
/// Starts the IO scheduler.
|
||||
pub async fn start_io(&self) {
|
||||
pub async fn start_io(&mut self) {
|
||||
if !self.is_configured().await.unwrap_or_default() {
|
||||
warn!(self, "can not start io on a context that is not configured");
|
||||
return;
|
||||
@@ -454,7 +449,7 @@ impl Context {
|
||||
}
|
||||
|
||||
let address = self.get_primary_self_addr().await?;
|
||||
let time_start = tools::Time::now();
|
||||
let time_start = std::time::SystemTime::now();
|
||||
info!(self, "background_fetch started fetching {address}");
|
||||
|
||||
let _pause_guard = self.scheduler.pause(self.clone()).await?;
|
||||
@@ -476,10 +471,7 @@ impl Context {
|
||||
let quota = self.quota.read().await;
|
||||
quota
|
||||
.as_ref()
|
||||
.filter(|quota| {
|
||||
time_elapsed("a.modified)
|
||||
> Duration::from_secs(DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT)
|
||||
})
|
||||
.filter(|quota| quota.modified + DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT > time())
|
||||
.is_none()
|
||||
};
|
||||
|
||||
@@ -492,7 +484,7 @@ impl Context {
|
||||
info!(
|
||||
self,
|
||||
"background_fetch done for {address} took {:?}",
|
||||
time_elapsed(&time_start),
|
||||
time_start.elapsed().unwrap_or_default()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@@ -594,7 +586,7 @@ impl Context {
|
||||
pub(crate) async fn free_ongoing(&self) {
|
||||
let mut s = self.running_state.write().await;
|
||||
if let RunningState::ShallStop { request } = *s {
|
||||
info!(self, "Ongoing stopped in {:?}", time_elapsed(&request));
|
||||
info!(self, "Ongoing stopped in {:?}", request.elapsed());
|
||||
}
|
||||
*s = RunningState::Stopped;
|
||||
}
|
||||
@@ -609,7 +601,7 @@ impl Context {
|
||||
}
|
||||
info!(self, "Signaling the ongoing process to stop ASAP.",);
|
||||
*s = RunningState::ShallStop {
|
||||
request: tools::Time::now(),
|
||||
request: Instant::now(),
|
||||
};
|
||||
}
|
||||
RunningState::ShallStop { .. } | RunningState::Stopped => {
|
||||
@@ -675,7 +667,7 @@ impl Context {
|
||||
let only_fetch_mvbox = self.get_config_int(Config::OnlyFetchMvbox).await?;
|
||||
let folders_configured = self
|
||||
.sql
|
||||
.get_raw_config_int(constants::DC_FOLDERS_CONFIGURED_KEY)
|
||||
.get_raw_config_int("folders_configured")
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
@@ -767,10 +759,7 @@ impl Context {
|
||||
res.insert("sentbox_watch", sentbox_watch.to_string());
|
||||
res.insert("mvbox_move", mvbox_move.to_string());
|
||||
res.insert("only_fetch_mvbox", only_fetch_mvbox.to_string());
|
||||
res.insert(
|
||||
constants::DC_FOLDERS_CONFIGURED_KEY,
|
||||
folders_configured.to_string(),
|
||||
);
|
||||
res.insert("folders_configured", folders_configured.to_string());
|
||||
res.insert("configured_inbox_folder", configured_inbox_folder);
|
||||
res.insert("configured_sentbox_folder", configured_sentbox_folder);
|
||||
res.insert("configured_mvbox_folder", configured_mvbox_folder);
|
||||
@@ -821,12 +810,6 @@ impl Context {
|
||||
.await?
|
||||
.to_string(),
|
||||
);
|
||||
res.insert(
|
||||
"last_cant_decrypt_outgoing_msgs",
|
||||
self.get_config_int(Config::LastCantDecryptOutgoingMsgs)
|
||||
.await?
|
||||
.to_string(),
|
||||
);
|
||||
res.insert(
|
||||
"scan_all_folders_debounce_secs",
|
||||
self.get_config_int(Config::ScanAllFoldersDebounceSecs)
|
||||
@@ -870,97 +853,12 @@ impl Context {
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
let elapsed = time_elapsed(&self.creation_time);
|
||||
res.insert("uptime", duration_to_str(elapsed));
|
||||
let elapsed = self.creation_time.elapsed();
|
||||
res.insert("uptime", duration_to_str(elapsed.unwrap_or_default()));
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
async fn get_self_report(&self) -> Result<String> {
|
||||
let mut res = String::new();
|
||||
res += &format!("core_version {}\n", get_version_str());
|
||||
|
||||
let num_msgs: u32 = self
|
||||
.sql
|
||||
.query_get_value(
|
||||
"SELECT COUNT(*) FROM msgs WHERE hidden=0 AND chat_id!=?",
|
||||
(DC_CHAT_ID_TRASH,),
|
||||
)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
res += &format!("num_msgs {}\n", num_msgs);
|
||||
|
||||
let num_chats: u32 = self
|
||||
.sql
|
||||
.query_get_value("SELECT COUNT(*) FROM chats WHERE id>9 AND blocked!=1", ())
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
res += &format!("num_chats {}\n", num_chats);
|
||||
|
||||
let db_size = tokio::fs::metadata(&self.sql.dbfile).await?.len();
|
||||
res += &format!("db_size_bytes {}\n", db_size);
|
||||
|
||||
let secret_key = &load_self_secret_key(self).await?.primary_key;
|
||||
let key_created = secret_key.created_at().timestamp();
|
||||
res += &format!("key_created {}\n", key_created);
|
||||
|
||||
let self_reporting_id = match self.get_config(Config::SelfReportingId).await? {
|
||||
Some(id) => id,
|
||||
None => {
|
||||
let id = create_id();
|
||||
self.set_config(Config::SelfReportingId, Some(&id)).await?;
|
||||
id
|
||||
}
|
||||
};
|
||||
res += &format!("self_reporting_id {}", self_reporting_id);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Drafts a message with statistics about the usage of Delta Chat.
|
||||
/// The user can inspect the message if they want, and then hit "Send".
|
||||
///
|
||||
/// On the other end, a bot will receive the message and make it available
|
||||
/// to Delta Chat's developers.
|
||||
pub async fn draft_self_report(&self) -> Result<ChatId> {
|
||||
const SELF_REPORTING_BOT: &str = "self_reporting@testrun.org";
|
||||
|
||||
let contact_id = Contact::create(self, "Statistics bot", SELF_REPORTING_BOT).await?;
|
||||
let chat_id = ChatId::create_for_contact(self, contact_id).await?;
|
||||
|
||||
// We're including the bot's public key in Delta Chat
|
||||
// so that the first message to the bot can directly be encrypted:
|
||||
let public_key = SignedPublicKey::from_base64(
|
||||
"xjMEZbfBlBYJKwYBBAHaRw8BAQdABpLWS2PUIGGo4pslVt4R8sylP5wZihmhf1DTDr3oCM\
|
||||
PNHDxzZWxmX3JlcG9ydGluZ0B0ZXN0cnVuLm9yZz7CiwQQFggAMwIZAQUCZbfBlAIbAwQLCQgHBhUI\
|
||||
CQoLAgMWAgEWIQTS2i16sHeYTckGn284K3M5Z4oohAAKCRA4K3M5Z4oohD8dAQCQV7CoH6UP4PD+Nq\
|
||||
I4kW5tbbqdh2AnDROg60qotmLExAEAxDfd3QHAK9f8b9qQUbLmHIztCLxhEuVbWPBEYeVW0gvOOARl\
|
||||
t8GUEgorBgEEAZdVAQUBAQdAMBUhYoAAcI625vGZqnM5maPX4sGJ7qvJxPAFILPy6AcDAQgHwngEGB\
|
||||
YIACAFAmW3wZQCGwwWIQTS2i16sHeYTckGn284K3M5Z4oohAAKCRA4K3M5Z4oohPwCAQCvzk1ObIkj\
|
||||
2GqsuIfaULlgdnfdZY8LNary425CEfHZDQD5AblXVrlMO1frdlc/Vo9z3pEeCrfYdD7ITD3/OeVoiQ\
|
||||
4=",
|
||||
)?;
|
||||
let mut peerstate = Peerstate::from_public_key(
|
||||
SELF_REPORTING_BOT,
|
||||
0,
|
||||
EncryptPreference::Mutual,
|
||||
&public_key,
|
||||
);
|
||||
let fingerprint = public_key.fingerprint();
|
||||
peerstate.set_verified(public_key, fingerprint, "".to_string())?;
|
||||
peerstate.save_to_db(&self.sql).await?;
|
||||
chat_id
|
||||
.set_protection(self, ProtectionStatus::Protected, time(), Some(contact_id))
|
||||
.await?;
|
||||
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = self.get_self_report().await?;
|
||||
|
||||
chat_id.set_draft(self, Some(&mut msg)).await?;
|
||||
|
||||
Ok(chat_id)
|
||||
}
|
||||
|
||||
/// Get a list of fresh, unmuted messages in unblocked chats.
|
||||
///
|
||||
/// The list starts with the most recent message
|
||||
@@ -1217,7 +1115,7 @@ pub fn get_version_str() -> &'static str {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::{Duration, SystemTime};
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use strum::IntoEnumIterator;
|
||||
@@ -1231,9 +1129,8 @@ mod tests {
|
||||
use crate::constants::Chattype;
|
||||
use crate::contact::ContactId;
|
||||
use crate::message::{Message, Viewtype};
|
||||
use crate::mimeparser::SystemMessage;
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::test_utils::{get_chat_msg, TestContext};
|
||||
use crate::test_utils::TestContext;
|
||||
use crate::tools::create_outgoing_rfc724_mid;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
@@ -1472,7 +1369,6 @@ mod tests {
|
||||
"mail_security",
|
||||
"notify_about_wrong_pw",
|
||||
"save_mime_headers",
|
||||
"self_reporting_id",
|
||||
"selfstatus",
|
||||
"send_server",
|
||||
"send_user",
|
||||
@@ -1773,24 +1669,4 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_draft_self_report() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
let chat_id = alice.draft_self_report().await?;
|
||||
let msg = get_chat_msg(&alice, chat_id, 0, 1).await;
|
||||
assert_eq!(msg.get_info_type(), SystemMessage::ChatProtectionEnabled);
|
||||
|
||||
let chat = Chat::load_from_db(&alice, chat_id).await?;
|
||||
assert!(chat.is_protected());
|
||||
|
||||
let mut draft = chat_id.get_draft(&alice).await?.unwrap();
|
||||
assert!(draft.text.starts_with("core_version"));
|
||||
|
||||
// Test that sending into the protected chat works:
|
||||
let _sent = alice.send_msg(chat_id, &mut draft).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,17 +590,13 @@ pub(crate) async fn delete_expired_imap_messages(context: &Context) -> Result<()
|
||||
match context.get_config_delete_server_after().await? {
|
||||
None => (0, 0),
|
||||
Some(delete_server_after) => (
|
||||
match delete_server_after {
|
||||
// Guarantee immediate deletion.
|
||||
0 => i64::MAX,
|
||||
_ => now - delete_server_after,
|
||||
},
|
||||
now - delete_server_after,
|
||||
now - max(delete_server_after, MIN_DELETE_SERVER_AFTER),
|
||||
),
|
||||
};
|
||||
let target = context.get_delete_msgs_target().await?;
|
||||
|
||||
context
|
||||
let msg_cnt = context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE imap
|
||||
@@ -619,7 +615,38 @@ pub(crate) async fn delete_expired_imap_messages(context: &Context) -> Result<()
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!(
|
||||
context,
|
||||
"delete_expired_imap_messages: {threshold_timestamp}: Marked {msg_cnt} messages.",
|
||||
);
|
||||
let Some(rfc724_mid) = context
|
||||
.sql
|
||||
.query_get_value::<String>(
|
||||
"SELECT rfc724_mid from imap WHERE folder='INBOX' AND uid=14",
|
||||
(),
|
||||
)
|
||||
.await?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
info!(
|
||||
context,
|
||||
"delete_expired_imap_messages: rfc724_mid={rfc724_mid}",
|
||||
);
|
||||
let Some(timestamp) = context
|
||||
.sql
|
||||
.query_get_value::<i64>(
|
||||
"SELECT timestamp from msgs WHERE rfc724_mid=?",
|
||||
(rfc724_mid,),
|
||||
)
|
||||
.await?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
info!(
|
||||
context,
|
||||
"delete_expired_imap_messages: timestamp={timestamp}",
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1131,7 +1158,6 @@ mod tests {
|
||||
(1030, now - 19 * HOUR, 0),
|
||||
(2000, now - 18 * HOUR, now - HOUR),
|
||||
(2020, now - 17 * HOUR, now + HOUR),
|
||||
(3000, now + HOUR, 0),
|
||||
] {
|
||||
let message_id = id.to_string();
|
||||
t.sql
|
||||
@@ -1217,10 +1243,6 @@ mod tests {
|
||||
0
|
||||
);
|
||||
|
||||
t.set_config(Config::DeleteServerAfter, Some("1")).await?;
|
||||
delete_expired_imap_messages(&t).await?;
|
||||
test_marked_for_deletion(&t, 3000).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use mailparse::{MailHeader, MailHeaderMap};
|
||||
|
||||
#[derive(Debug, Display, Clone, PartialEq, Eq, IntoStaticStr)]
|
||||
#[derive(Debug, Display, Clone, PartialEq, Eq, EnumVariantNames, IntoStaticStr)]
|
||||
#[strum(serialize_all = "kebab_case")]
|
||||
#[allow(missing_docs)]
|
||||
pub enum HeaderDef {
|
||||
|
||||
67
src/imap.rs
67
src/imap.rs
@@ -22,7 +22,9 @@ use tokio::sync::RwLock;
|
||||
|
||||
use crate::chat::{self, ChatId, ChatIdBlocked};
|
||||
use crate::config::Config;
|
||||
use crate::constants::{self, Blocked, Chattype, ShowEmails, DC_FETCH_EXISTING_MSGS_COUNT};
|
||||
use crate::constants::{
|
||||
Blocked, Chattype, ShowEmails, DC_FETCH_EXISTING_MSGS_COUNT, DC_FOLDERS_CONFIGURED_VERSION,
|
||||
};
|
||||
use crate::contact::{normalize_name, Contact, ContactAddress, ContactId, Modifier, Origin};
|
||||
use crate::context::Context;
|
||||
use crate::events::EventType;
|
||||
@@ -449,10 +451,7 @@ impl Imap {
|
||||
&& err.to_string().to_lowercase().contains("authentication")
|
||||
&& context.get_config_bool(Config::NotifyAboutWrongPw).await?
|
||||
{
|
||||
if let Err(e) = context
|
||||
.set_config_internal(Config::NotifyAboutWrongPw, None)
|
||||
.await
|
||||
{
|
||||
if let Err(e) = context.set_config(Config::NotifyAboutWrongPw, None).await {
|
||||
warn!(context, "{:#}", e);
|
||||
}
|
||||
drop(lock);
|
||||
@@ -1765,24 +1764,18 @@ impl Imap {
|
||||
context: &Context,
|
||||
create_mvbox: bool,
|
||||
) -> Result<()> {
|
||||
let folders_configured = context
|
||||
.sql
|
||||
.get_raw_config_int(constants::DC_FOLDERS_CONFIGURED_KEY)
|
||||
.await?;
|
||||
if folders_configured.unwrap_or_default() >= constants::DC_FOLDERS_CONFIGURED_VERSION {
|
||||
let folders_configured = context.sql.get_raw_config_int("folders_configured").await?;
|
||||
if folders_configured.unwrap_or_default() >= DC_FOLDERS_CONFIGURED_VERSION {
|
||||
return Ok(());
|
||||
}
|
||||
if let Err(err) = self.connect(context).await {
|
||||
self.connectivity.set_err(context, &err).await;
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
self.configure_folders(context, create_mvbox).await
|
||||
}
|
||||
|
||||
/// Attempts to configure mvbox.
|
||||
///
|
||||
/// Tries to find any folder in the given list of `folders`. If none is found, tries to create
|
||||
/// `folders[0]`. This method does not use LIST command to ensure that
|
||||
/// any of them in the same order. This method does not use LIST command to ensure that
|
||||
/// configuration works even if mailbox lookup is forbidden via Access Control List (see
|
||||
/// <https://datatracker.ietf.org/doc/html/rfc4314>).
|
||||
///
|
||||
@@ -1811,28 +1804,24 @@ impl Imap {
|
||||
"MVBOX-folder {:?} successfully selected, using it.", &folder
|
||||
);
|
||||
session.close().await?;
|
||||
// Before moving emails to the mvbox we need to remember its UIDVALIDITY, otherwise
|
||||
// emails moved before that wouldn't be fetched but considered "old" instead.
|
||||
self.select_with_uidvalidity(context, folder).await?;
|
||||
return Ok(Some(folder));
|
||||
}
|
||||
}
|
||||
|
||||
if !create_mvbox {
|
||||
return Ok(None);
|
||||
}
|
||||
let Some(folder) = folders.first() else {
|
||||
return Ok(None);
|
||||
};
|
||||
match self.select_with_uidvalidity(context, folder).await {
|
||||
Ok(_) => {
|
||||
info!(context, "MVBOX-folder {} created.", folder);
|
||||
return Ok(Some(folder));
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(context, "Cannot create MVBOX-folder {:?}: {}", folder, err);
|
||||
if create_mvbox {
|
||||
for folder in folders {
|
||||
match session.create(&folder).await {
|
||||
Ok(_) => {
|
||||
info!(context, "MVBOX-folder {} created.", &folder);
|
||||
return Ok(Some(folder));
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(context, "Cannot create MVBOX-folder {:?}: {}", &folder, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
@@ -1884,23 +1873,20 @@ impl Imap {
|
||||
.context("failed to configure mvbox")?;
|
||||
|
||||
context
|
||||
.set_config_internal(Config::ConfiguredInboxFolder, Some("INBOX"))
|
||||
.set_config(Config::ConfiguredInboxFolder, Some("INBOX"))
|
||||
.await?;
|
||||
if let Some(mvbox_folder) = mvbox_folder {
|
||||
info!(context, "Setting MVBOX FOLDER TO {}", &mvbox_folder);
|
||||
context
|
||||
.set_config_internal(Config::ConfiguredMvboxFolder, Some(mvbox_folder))
|
||||
.set_config(Config::ConfiguredMvboxFolder, Some(mvbox_folder))
|
||||
.await?;
|
||||
}
|
||||
for (config, name) in folder_configs {
|
||||
context.set_config_internal(config, Some(&name)).await?;
|
||||
context.set_config(config, Some(&name)).await?;
|
||||
}
|
||||
context
|
||||
.sql
|
||||
.set_raw_config_int(
|
||||
constants::DC_FOLDERS_CONFIGURED_KEY,
|
||||
constants::DC_FOLDERS_CONFIGURED_VERSION,
|
||||
)
|
||||
.set_raw_config_int("folders_configured", DC_FOLDERS_CONFIGURED_VERSION)
|
||||
.await?;
|
||||
|
||||
info!(context, "FINISHED configuring IMAP-folders.");
|
||||
@@ -1918,14 +1904,13 @@ impl Session {
|
||||
use async_imap::imap_proto::ResponseCode;
|
||||
use UnsolicitedResponse::*;
|
||||
|
||||
let folder = self.selected_folder.as_deref().unwrap_or_default();
|
||||
let mut unsolicited_exists = false;
|
||||
while let Ok(response) = self.unsolicited_responses.try_recv() {
|
||||
match response {
|
||||
Exists(_) => {
|
||||
info!(
|
||||
context,
|
||||
"Need to refetch {folder:?}, got unsolicited EXISTS {response:?}"
|
||||
"Need to fetch again, got unsolicited EXISTS {:?}", response
|
||||
);
|
||||
unsolicited_exists = true;
|
||||
}
|
||||
@@ -1944,7 +1929,7 @@ impl Session {
|
||||
) => {}
|
||||
|
||||
_ => {
|
||||
info!(context, "{folder:?}: got unsolicited response {response:?}")
|
||||
info!(context, "got unsolicited response {:?}", response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
use async_channel::Receiver;
|
||||
@@ -11,7 +11,6 @@ use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
use crate::imap::{client::IMAP_TIMEOUT, FolderMeaning};
|
||||
use crate::log::LogExt;
|
||||
use crate::tools::{self, time_elapsed};
|
||||
|
||||
/// Timeout after which IDLE is finished
|
||||
/// if there are no responses from the server.
|
||||
@@ -107,7 +106,7 @@ impl Imap {
|
||||
// Idle using polling. This is also needed if we're not yet configured -
|
||||
// in this case, we're waiting for a configure job (and an interrupt).
|
||||
|
||||
let fake_idle_start_time = tools::Time::now();
|
||||
let fake_idle_start_time = SystemTime::now();
|
||||
|
||||
// Do not poll, just wait for an interrupt when no folder is passed in.
|
||||
let watch_folder = if let Some(watch_folder) = watch_folder {
|
||||
@@ -197,7 +196,11 @@ impl Imap {
|
||||
info!(
|
||||
context,
|
||||
"IMAP-fake-IDLE done after {:.4}s",
|
||||
time_elapsed(&fake_idle_start_time).as_millis() as f64 / 1000.,
|
||||
SystemTime::now()
|
||||
.duration_since(fake_idle_start_time)
|
||||
.unwrap_or_default()
|
||||
.as_millis() as f64
|
||||
/ 1000.,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::{collections::BTreeMap, time::Instant};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use futures::TryStreamExt;
|
||||
@@ -7,7 +7,6 @@ use super::{get_folder_meaning_by_attrs, get_folder_meaning_by_name};
|
||||
use crate::config::Config;
|
||||
use crate::imap::Imap;
|
||||
use crate::log::LogExt;
|
||||
use crate::tools::{self, time_elapsed};
|
||||
use crate::{context::Context, imap::FolderMeaning};
|
||||
|
||||
impl Imap {
|
||||
@@ -16,7 +15,7 @@ impl Imap {
|
||||
// First of all, debounce to once per minute:
|
||||
let mut last_scan = context.last_full_folder_scan.lock().await;
|
||||
if let Some(last_scan) = *last_scan {
|
||||
let elapsed_secs = time_elapsed(&last_scan).as_secs();
|
||||
let elapsed_secs = last_scan.elapsed().as_secs();
|
||||
let debounce_secs = context
|
||||
.get_config_u64(Config::ScanAllFoldersDebounceSecs)
|
||||
.await?;
|
||||
@@ -90,11 +89,11 @@ impl Imap {
|
||||
Config::ConfiguredTrashFolder,
|
||||
] {
|
||||
context
|
||||
.set_config_internal(conf, folder_configs.get(&conf).map(|s| s.as_str()))
|
||||
.set_config(conf, folder_configs.get(&conf).map(|s| s.as_str()))
|
||||
.await?;
|
||||
}
|
||||
|
||||
last_scan.replace(tools::Time::now());
|
||||
last_scan.replace(Instant::now());
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
|
||||
@@ -112,16 +112,11 @@ impl ImapSession {
|
||||
Err(err) => match err {
|
||||
Error::NoFolder(..) => {
|
||||
info!(context, "Failed to select folder {} because it does not exist, trying to create it.", folder);
|
||||
let create_res = self.create(folder).await;
|
||||
if let Err(ref err) = create_res {
|
||||
info!(context, "Couldn't select folder, then create() failed: {err:#}.");
|
||||
// Need to recheck, could have been created in parallel.
|
||||
}
|
||||
let select_res = self.select_folder(context, Some(folder)).await.with_context(|| format!("failed to select newely created folder {folder}"));
|
||||
if select_res.is_err() {
|
||||
create_res?;
|
||||
}
|
||||
select_res
|
||||
self.create(folder).await.with_context(|| {
|
||||
format!("Couldn't select folder ('{err}'), then create() failed")
|
||||
})?;
|
||||
|
||||
Ok(self.select_folder(context, Some(folder)).await.with_context(|| format!("failed to select newely created folder {folder}"))?)
|
||||
}
|
||||
_ => Err(err).with_context(|| format!("failed to select folder {folder} with error other than NO, not trying to create it")),
|
||||
},
|
||||
|
||||
16
src/imex.rs
16
src/imex.rs
@@ -534,7 +534,7 @@ async fn export_backup(context: &Context, dir: &Path, passphrase: String) -> Res
|
||||
let _d1 = DeleteOnDrop(temp_db_path.clone());
|
||||
let _d2 = DeleteOnDrop(temp_path.clone());
|
||||
|
||||
export_database(context, &temp_db_path, passphrase, now)
|
||||
export_database(context, &temp_db_path, passphrase)
|
||||
.await
|
||||
.context("could not export database")?;
|
||||
|
||||
@@ -770,27 +770,19 @@ where
|
||||
/// overwritten.
|
||||
///
|
||||
/// This also verifies that IO is not running during the export.
|
||||
async fn export_database(
|
||||
context: &Context,
|
||||
dest: &Path,
|
||||
passphrase: String,
|
||||
timestamp: i64,
|
||||
) -> Result<()> {
|
||||
async fn export_database(context: &Context, dest: &Path, passphrase: String) -> Result<()> {
|
||||
ensure!(
|
||||
!context.scheduler.is_running().await,
|
||||
"cannot export backup, IO is running"
|
||||
);
|
||||
let timestamp = timestamp.try_into().context("32-bit UNIX time overflow")?;
|
||||
let now = time().try_into().context("32-bit UNIX time overflow")?;
|
||||
|
||||
// TODO: Maybe introduce camino crate for UTF-8 paths where we need them.
|
||||
let dest = dest
|
||||
.to_str()
|
||||
.with_context(|| format!("path {} is not valid unicode", dest.display()))?;
|
||||
|
||||
context
|
||||
.sql
|
||||
.set_raw_config_int("backup_time", timestamp)
|
||||
.await?;
|
||||
context.sql.set_raw_config_int("backup_time", now).await?;
|
||||
sql::housekeeping(context).await.log_err(context).ok();
|
||||
context
|
||||
.sql
|
||||
|
||||
@@ -52,7 +52,6 @@ use crate::context::Context;
|
||||
use crate::message::{Message, Viewtype};
|
||||
use crate::qr::{self, Qr};
|
||||
use crate::stock_str::backup_transfer_msg_body;
|
||||
use crate::tools::time;
|
||||
use crate::{e2ee, EventType};
|
||||
|
||||
use super::{export_database, DBFILE_BACKUP_NAME};
|
||||
@@ -159,7 +158,7 @@ impl BackupProvider {
|
||||
// Generate the token up front: we also use it to encrypt the database.
|
||||
let token = AuthToken::generate();
|
||||
context.emit_event(SendProgress::Started.into());
|
||||
export_database(context, dbfile, token.to_string(), time())
|
||||
export_database(context, dbfile, token.to_string())
|
||||
.await
|
||||
.context("Database export failed")?;
|
||||
context.emit_event(SendProgress::DatabaseExported.into());
|
||||
|
||||
18
src/key.rs
18
src/key.rs
@@ -18,7 +18,7 @@ use crate::constants::KeyGenType;
|
||||
use crate::context::Context;
|
||||
use crate::log::LogExt;
|
||||
use crate::pgp::KeyPair;
|
||||
use crate::tools::{self, time_elapsed, EmailAddress};
|
||||
use crate::tools::EmailAddress;
|
||||
|
||||
/// Convenience trait for working with keys.
|
||||
///
|
||||
@@ -204,7 +204,7 @@ async fn generate_keypair(context: &Context) -> Result<KeyPair> {
|
||||
match load_keypair(context, &addr).await? {
|
||||
Some(key_pair) => Ok(key_pair),
|
||||
None => {
|
||||
let start = tools::Time::now();
|
||||
let start = std::time::SystemTime::now();
|
||||
let keytype = KeyGenType::from_i32(context.get_config_int(Config::KeyGenType).await?)
|
||||
.unwrap_or_default();
|
||||
info!(context, "Generating keypair with type {}", keytype);
|
||||
@@ -216,7 +216,7 @@ async fn generate_keypair(context: &Context) -> Result<KeyPair> {
|
||||
info!(
|
||||
context,
|
||||
"Keypair generated in {:.3}s.",
|
||||
time_elapsed(&start).as_secs(),
|
||||
start.elapsed().unwrap_or_default().as_secs()
|
||||
);
|
||||
Ok(keypair)
|
||||
}
|
||||
@@ -294,17 +294,11 @@ pub async fn store_self_keypair(
|
||||
KeyPairUse::ReadOnly => false,
|
||||
};
|
||||
|
||||
// `addr` and `is_default` written for compatibility with older versions,
|
||||
// until new cores are rolled out everywhere.
|
||||
// otherwise "add second device" or "backup" may break.
|
||||
// moreover, this allows downgrades to the previous version.
|
||||
// writing of `addr` and `is_default` can be removed ~ 2024-08
|
||||
let addr = keypair.addr.to_string();
|
||||
transaction
|
||||
.execute(
|
||||
"INSERT OR REPLACE INTO keypairs (public_key, private_key, addr, is_default)
|
||||
VALUES (?,?,?,?)",
|
||||
(&public_key, &secret_key, addr, is_default),
|
||||
"INSERT OR REPLACE INTO keypairs (public_key, private_key)
|
||||
VALUES (?,?)",
|
||||
(&public_key, &secret_key),
|
||||
)
|
||||
.context("Failed to insert keypair")?;
|
||||
|
||||
|
||||
@@ -139,9 +139,8 @@ impl Kml {
|
||||
match chrono::NaiveDateTime::parse_from_str(&val, "%Y-%m-%dT%H:%M:%SZ") {
|
||||
Ok(res) => {
|
||||
self.curr.timestamp = res.timestamp();
|
||||
let now = time();
|
||||
if self.curr.timestamp > now {
|
||||
self.curr.timestamp = now;
|
||||
if self.curr.timestamp > time() {
|
||||
self.curr.timestamp = time();
|
||||
}
|
||||
}
|
||||
Err(_err) => {
|
||||
@@ -334,13 +333,12 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64
|
||||
return Ok(true);
|
||||
}
|
||||
let mut continue_streaming = false;
|
||||
let now = time();
|
||||
|
||||
let chats = context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT id FROM chats WHERE locations_send_until>?;",
|
||||
(now,),
|
||||
(time(),),
|
||||
|row| row.get::<_, i32>(0),
|
||||
|chats| {
|
||||
chats
|
||||
@@ -358,7 +356,7 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64
|
||||
latitude,
|
||||
longitude,
|
||||
accuracy,
|
||||
now,
|
||||
time(),
|
||||
chat_id,
|
||||
ContactId::SELF,
|
||||
)).await.context("Failed to store location")?;
|
||||
|
||||
@@ -1522,9 +1522,7 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
|
||||
|
||||
if !msg_ids.is_empty() {
|
||||
// Run housekeeping to delete unused blobs.
|
||||
context
|
||||
.set_config_internal(Config::LastHousekeeping, None)
|
||||
.await?;
|
||||
context.set_config(Config::LastHousekeeping, None).await?;
|
||||
}
|
||||
|
||||
// Interrupt Inbox loop to start message deletion and run housekeeping.
|
||||
@@ -1552,7 +1550,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()>
|
||||
let old_last_msg_id = MsgId::new(context.get_config_u32(Config::LastMsgId).await?);
|
||||
let last_msg_id = msg_ids.iter().fold(&old_last_msg_id, std::cmp::max);
|
||||
context
|
||||
.set_config_internal(Config::LastMsgId, Some(&last_msg_id.to_u32().to_string()))
|
||||
.set_config_u32(Config::LastMsgId, last_msg_id.to_u32())
|
||||
.await?;
|
||||
|
||||
let msgs = context
|
||||
|
||||
@@ -69,8 +69,6 @@ pub(crate) struct MimeMessage {
|
||||
/// Whether the From address was repeated in the signed part
|
||||
/// (and we know that the signer intended to send from this address)
|
||||
pub from_is_signed: bool,
|
||||
/// Whether the message is incoming or outgoing (self-sent).
|
||||
pub incoming: bool,
|
||||
/// The List-Post address is only set for mailing lists. Users can send
|
||||
/// messages to this address to post them to the list.
|
||||
pub list_post: Option<String>,
|
||||
@@ -210,12 +208,11 @@ impl MimeMessage {
|
||||
) -> Result<Self> {
|
||||
let mail = mailparse::parse_mail(body)?;
|
||||
|
||||
let timestamp_rcvd = smeared_time(context);
|
||||
let timestamp_sent = mail
|
||||
let message_time = mail
|
||||
.headers
|
||||
.get_header_value(HeaderDef::Date)
|
||||
.and_then(|v| mailparse::dateparse(&v).ok())
|
||||
.map_or(timestamp_rcvd, |value| min(value, timestamp_rcvd + 60));
|
||||
.unwrap_or_default();
|
||||
let mut hop_info = parse_receive_headers(&mail.get_headers());
|
||||
|
||||
let mut headers = Default::default();
|
||||
@@ -282,7 +279,7 @@ impl MimeMessage {
|
||||
let private_keyring = load_self_secret_keyring(context).await?;
|
||||
|
||||
let mut decryption_info =
|
||||
prepare_decryption(context, &mail, &from.addr, timestamp_sent).await?;
|
||||
prepare_decryption(context, &mail, &from.addr, message_time).await?;
|
||||
|
||||
// Memory location for a possible decrypted message.
|
||||
let mut mail_raw = Vec::new();
|
||||
@@ -328,7 +325,7 @@ impl MimeMessage {
|
||||
let gossip_headers = mail.headers.get_all_values("Autocrypt-Gossip");
|
||||
gossiped_keys = update_gossip_peerstates(
|
||||
context,
|
||||
timestamp_sent,
|
||||
message_time,
|
||||
&from.addr,
|
||||
&recipients,
|
||||
gossip_headers,
|
||||
@@ -379,12 +376,12 @@ impl MimeMessage {
|
||||
|
||||
// If it is not a read receipt, degrade encryption.
|
||||
if let (Some(peerstate), Ok(mail)) = (&mut decryption_info.peerstate, mail) {
|
||||
if timestamp_sent > peerstate.last_seen_autocrypt
|
||||
if message_time > peerstate.last_seen_autocrypt
|
||||
&& mail.ctype.mimetype != "multipart/report"
|
||||
// Disallowing keychanges is disabled for now:
|
||||
// && decryption_info.dkim_results.allow_keychange
|
||||
{
|
||||
peerstate.degrade_encryption(timestamp_sent);
|
||||
peerstate.degrade_encryption(message_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -398,7 +395,12 @@ impl MimeMessage {
|
||||
}
|
||||
}
|
||||
|
||||
let incoming = !context.is_self_addr(&from.addr).await?;
|
||||
let timestamp_rcvd = smeared_time(context);
|
||||
let timestamp_sent = headers
|
||||
.get(HeaderDef::Date.get_headername())
|
||||
.and_then(|value| mailparse::dateparse(value).ok())
|
||||
.map_or(timestamp_rcvd, |value| min(value, timestamp_rcvd + 60));
|
||||
|
||||
let mut parser = MimeMessage {
|
||||
parts: Vec::new(),
|
||||
headers,
|
||||
@@ -406,7 +408,6 @@ impl MimeMessage {
|
||||
list_post,
|
||||
from,
|
||||
from_is_signed,
|
||||
incoming,
|
||||
chat_disposition_notification_to,
|
||||
decryption_info,
|
||||
decrypting_failed: mail.is_err(),
|
||||
@@ -2236,7 +2237,6 @@ mod tests {
|
||||
message::{Message, MessageState, MessengerMessage},
|
||||
receive_imf::receive_imf,
|
||||
test_utils::TestContext,
|
||||
tools::time,
|
||||
};
|
||||
|
||||
impl AvatarAction {
|
||||
@@ -3849,40 +3849,4 @@ Content-Disposition: reaction\n\
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_time_in_future() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
let beginning_time = time();
|
||||
|
||||
// Receive a message with a date far in the future (year 3004)
|
||||
// I'm just going to assume that no one uses this code after the year 3000
|
||||
let mime_message = MimeMessage::from_bytes(
|
||||
&alice,
|
||||
b"To: alice@example.org\n\
|
||||
From: bob@example.net\n\
|
||||
Date: Today, 29 February 3004 00:00:10 -800\n\
|
||||
Message-ID: 56789@example.net\n\
|
||||
Subject: Meeting\n\
|
||||
Mime-Version: 1.0 (1.0)\n\
|
||||
Content-Type: text/plain; charset=utf-8\n\
|
||||
\n\
|
||||
Hi",
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// We do allow the time to be in the future a bit (because of unsynchronized clocks),
|
||||
// but only 60 seconds:
|
||||
assert!(mime_message.decryption_info.message_time <= time() + 60);
|
||||
assert!(mime_message.decryption_info.message_time >= beginning_time + 60);
|
||||
assert_eq!(
|
||||
mime_message.decryption_info.message_time,
|
||||
mime_message.timestamp_sent
|
||||
);
|
||||
assert!(mime_message.timestamp_rcvd <= time());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,28 +97,13 @@ pub struct Peerstate {
|
||||
impl Peerstate {
|
||||
/// Creates a peerstate from the `Autocrypt` header.
|
||||
pub fn from_header(header: &Aheader, message_time: i64) -> Self {
|
||||
Self::from_public_key(
|
||||
&header.addr,
|
||||
message_time,
|
||||
header.prefer_encrypt,
|
||||
&header.public_key,
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a peerstate from the given public key.
|
||||
pub fn from_public_key(
|
||||
addr: &str,
|
||||
last_seen: i64,
|
||||
prefer_encrypt: EncryptPreference,
|
||||
public_key: &SignedPublicKey,
|
||||
) -> Self {
|
||||
Peerstate {
|
||||
addr: addr.to_string(),
|
||||
last_seen,
|
||||
last_seen_autocrypt: last_seen,
|
||||
prefer_encrypt,
|
||||
public_key: Some(public_key.clone()),
|
||||
public_key_fingerprint: Some(public_key.fingerprint()),
|
||||
addr: header.addr.clone(),
|
||||
last_seen: message_time,
|
||||
last_seen_autocrypt: message_time,
|
||||
prefer_encrypt: header.prefer_encrypt,
|
||||
public_key: Some(header.public_key.clone()),
|
||||
public_key_fingerprint: Some(header.public_key.fingerprint()),
|
||||
gossip_key: None,
|
||||
gossip_key_fingerprint: None,
|
||||
gossip_timestamp: 0,
|
||||
|
||||
@@ -215,18 +215,8 @@ pub async fn get_provider_info(
|
||||
|
||||
/// Finds a provider in offline database based on domain.
|
||||
pub fn get_provider_by_domain(domain: &str) -> Option<&'static Provider> {
|
||||
let domain = domain.to_lowercase();
|
||||
for (pattern, provider) in PROVIDER_DATA {
|
||||
if let Some(suffix) = pattern.strip_prefix('*') {
|
||||
// Wildcard domain pattern.
|
||||
//
|
||||
// For example, `suffix` is ".hermes.radio" for "*.hermes.radio" pattern.
|
||||
if domain.ends_with(suffix) {
|
||||
return Some(provider);
|
||||
}
|
||||
} else if pattern == domain {
|
||||
return Some(provider);
|
||||
}
|
||||
if let Some(provider) = PROVIDER_DATA.get(domain.to_lowercase().as_str()) {
|
||||
return Some(*provider);
|
||||
}
|
||||
|
||||
None
|
||||
@@ -236,42 +226,33 @@ pub fn get_provider_by_domain(domain: &str) -> Option<&'static Provider> {
|
||||
///
|
||||
/// For security reasons, only Gmail can be configured this way.
|
||||
pub async fn get_provider_by_mx(context: &Context, domain: &str) -> Option<&'static Provider> {
|
||||
let Ok(resolver) = get_resolver() else {
|
||||
warn!(context, "Cannot get a resolver to check MX records.");
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut fqdn: String = domain.to_string();
|
||||
if !fqdn.ends_with('.') {
|
||||
fqdn.push('.');
|
||||
}
|
||||
|
||||
let Ok(mx_domains) = resolver.mx_lookup(fqdn).await else {
|
||||
warn!(context, "Cannot resolve MX records for {domain:?}.");
|
||||
return None;
|
||||
};
|
||||
|
||||
for (provider_domain_pattern, provider) in PROVIDER_DATA {
|
||||
if provider.id != "gmail" {
|
||||
// MX lookup is limited to Gmail for security reasons
|
||||
continue;
|
||||
if let Ok(resolver) = get_resolver() {
|
||||
let mut fqdn: String = domain.to_string();
|
||||
if !fqdn.ends_with('.') {
|
||||
fqdn.push('.');
|
||||
}
|
||||
|
||||
if provider_domain_pattern.starts_with('*') {
|
||||
// Skip wildcard patterns.
|
||||
continue;
|
||||
}
|
||||
if let Ok(mx_domains) = resolver.mx_lookup(fqdn).await {
|
||||
for (provider_domain, provider) in &*PROVIDER_DATA {
|
||||
if provider.id != "gmail" {
|
||||
// MX lookup is limited to Gmail for security reasons
|
||||
continue;
|
||||
}
|
||||
|
||||
let provider_fqdn = provider_domain_pattern.to_string() + ".";
|
||||
let provider_fqdn_dot = ".".to_string() + &provider_fqdn;
|
||||
let provider_fqdn = provider_domain.to_string() + ".";
|
||||
let provider_fqdn_dot = ".".to_string() + &provider_fqdn;
|
||||
|
||||
for mx_domain in mx_domains.iter() {
|
||||
let mx_domain = mx_domain.exchange().to_lowercase().to_utf8();
|
||||
for mx_domain in mx_domains.iter() {
|
||||
let mx_domain = mx_domain.exchange().to_lowercase().to_utf8();
|
||||
|
||||
if mx_domain == provider_fqdn || mx_domain.ends_with(&provider_fqdn_dot) {
|
||||
return Some(provider);
|
||||
if mx_domain == provider_fqdn || mx_domain.ends_with(&provider_fqdn_dot) {
|
||||
return Some(provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!(context, "cannot get a resolver to check MX records.");
|
||||
}
|
||||
|
||||
None
|
||||
|
||||
@@ -593,7 +593,7 @@ static P_GMX_NET: Provider = Provider {
|
||||
oauth2_authorizer: None,
|
||||
};
|
||||
|
||||
// hermes.radio.md: *.hermes.radio, *.aco-connexion.org
|
||||
// hermes.radio.md: ac.hermes.radio, ac1.hermes.radio, ac2.hermes.radio, ac3.hermes.radio, ac4.hermes.radio, ac5.hermes.radio, ac6.hermes.radio, ac7.hermes.radio, ac8.hermes.radio, ac9.hermes.radio, ac10.hermes.radio, ac11.hermes.radio, ac12.hermes.radio, ac13.hermes.radio, ac14.hermes.radio, ac15.hermes.radio, ka.hermes.radio, ka1.hermes.radio, ka2.hermes.radio, ka3.hermes.radio, ka4.hermes.radio, ka5.hermes.radio, ka6.hermes.radio, ka7.hermes.radio, ka8.hermes.radio, ka9.hermes.radio, ka10.hermes.radio, ka11.hermes.radio, ka12.hermes.radio, ka13.hermes.radio, ka14.hermes.radio, ka15.hermes.radio, ec.hermes.radio, ec1.hermes.radio, ec2.hermes.radio, ec3.hermes.radio, ec4.hermes.radio, ec5.hermes.radio, ec6.hermes.radio, ec7.hermes.radio, ec8.hermes.radio, ec9.hermes.radio, ec10.hermes.radio, ec11.hermes.radio, ec12.hermes.radio, ec13.hermes.radio, ec14.hermes.radio, ec15.hermes.radio, hermes.radio
|
||||
static P_HERMES_RADIO: Provider = Provider {
|
||||
id: "hermes.radio",
|
||||
status: Status::Ok,
|
||||
@@ -1608,326 +1608,375 @@ static P_ZOHO: Provider = Provider {
|
||||
oauth2_authorizer: None,
|
||||
};
|
||||
|
||||
pub(crate) static PROVIDER_DATA: [(&str, &Provider); 318] = [
|
||||
("163.com", &P_163),
|
||||
("aktivix.org", &P_AKTIVIX_ORG),
|
||||
("aol.com", &P_AOL),
|
||||
("arcor.de", &P_ARCOR_DE),
|
||||
("autistici.org", &P_AUTISTICI_ORG),
|
||||
("delta.blinzeln.de", &P_BLINDZELN_ORG),
|
||||
("delta.blindzeln.org", &P_BLINDZELN_ORG),
|
||||
("bluewin.ch", &P_BLUEWIN_CH),
|
||||
("buzon.uy", &P_BUZON_UY),
|
||||
("c1.testrun.org", &P_C1_TESTRUN_ORG),
|
||||
("c2.testrun.org", &P_C2_TESTRUN_ORG),
|
||||
("c3.testrun.org", &P_C3_TESTRUN_ORG),
|
||||
("chello.at", &P_CHELLO_AT),
|
||||
("xfinity.com", &P_COMCAST),
|
||||
("comcast.net", &P_COMCAST),
|
||||
("dismail.de", &P_DISMAIL_DE),
|
||||
("disroot.org", &P_DISROOT),
|
||||
("e.email", &P_E_EMAIL),
|
||||
("espiv.net", &P_ESPIV_NET),
|
||||
("example.com", &P_EXAMPLE_COM),
|
||||
("example.org", &P_EXAMPLE_COM),
|
||||
("example.net", &P_EXAMPLE_COM),
|
||||
("123mail.org", &P_FASTMAIL),
|
||||
("150mail.com", &P_FASTMAIL),
|
||||
("150ml.com", &P_FASTMAIL),
|
||||
("16mail.com", &P_FASTMAIL),
|
||||
("2-mail.com", &P_FASTMAIL),
|
||||
("4email.net", &P_FASTMAIL),
|
||||
("50mail.com", &P_FASTMAIL),
|
||||
("airpost.net", &P_FASTMAIL),
|
||||
("allmail.net", &P_FASTMAIL),
|
||||
("bestmail.us", &P_FASTMAIL),
|
||||
("cluemail.com", &P_FASTMAIL),
|
||||
("elitemail.org", &P_FASTMAIL),
|
||||
("emailcorner.net", &P_FASTMAIL),
|
||||
("emailengine.net", &P_FASTMAIL),
|
||||
("emailengine.org", &P_FASTMAIL),
|
||||
("emailgroups.net", &P_FASTMAIL),
|
||||
("emailplus.org", &P_FASTMAIL),
|
||||
("emailuser.net", &P_FASTMAIL),
|
||||
("eml.cc", &P_FASTMAIL),
|
||||
("f-m.fm", &P_FASTMAIL),
|
||||
("fast-email.com", &P_FASTMAIL),
|
||||
("fast-mail.org", &P_FASTMAIL),
|
||||
("fastem.com", &P_FASTMAIL),
|
||||
("fastemail.us", &P_FASTMAIL),
|
||||
("fastemailer.com", &P_FASTMAIL),
|
||||
("fastest.cc", &P_FASTMAIL),
|
||||
("fastimap.com", &P_FASTMAIL),
|
||||
("fastmail.cn", &P_FASTMAIL),
|
||||
("fastmail.co.uk", &P_FASTMAIL),
|
||||
("fastmail.com", &P_FASTMAIL),
|
||||
("fastmail.com.au", &P_FASTMAIL),
|
||||
("fastmail.de", &P_FASTMAIL),
|
||||
("fastmail.es", &P_FASTMAIL),
|
||||
("fastmail.fm", &P_FASTMAIL),
|
||||
("fastmail.fr", &P_FASTMAIL),
|
||||
("fastmail.im", &P_FASTMAIL),
|
||||
("fastmail.in", &P_FASTMAIL),
|
||||
("fastmail.jp", &P_FASTMAIL),
|
||||
("fastmail.mx", &P_FASTMAIL),
|
||||
("fastmail.net", &P_FASTMAIL),
|
||||
("fastmail.nl", &P_FASTMAIL),
|
||||
("fastmail.org", &P_FASTMAIL),
|
||||
("fastmail.se", &P_FASTMAIL),
|
||||
("fastmail.to", &P_FASTMAIL),
|
||||
("fastmail.tw", &P_FASTMAIL),
|
||||
("fastmail.uk", &P_FASTMAIL),
|
||||
("fastmail.us", &P_FASTMAIL),
|
||||
("fastmailbox.net", &P_FASTMAIL),
|
||||
("fastmessaging.com", &P_FASTMAIL),
|
||||
("fea.st", &P_FASTMAIL),
|
||||
("fmail.co.uk", &P_FASTMAIL),
|
||||
("fmailbox.com", &P_FASTMAIL),
|
||||
("fmgirl.com", &P_FASTMAIL),
|
||||
("fmguy.com", &P_FASTMAIL),
|
||||
("ftml.net", &P_FASTMAIL),
|
||||
("h-mail.us", &P_FASTMAIL),
|
||||
("hailmail.net", &P_FASTMAIL),
|
||||
("imap-mail.com", &P_FASTMAIL),
|
||||
("imap.cc", &P_FASTMAIL),
|
||||
("imapmail.org", &P_FASTMAIL),
|
||||
("inoutbox.com", &P_FASTMAIL),
|
||||
("internet-e-mail.com", &P_FASTMAIL),
|
||||
("internet-mail.org", &P_FASTMAIL),
|
||||
("internetemails.net", &P_FASTMAIL),
|
||||
("internetmailing.net", &P_FASTMAIL),
|
||||
("jetemail.net", &P_FASTMAIL),
|
||||
("justemail.net", &P_FASTMAIL),
|
||||
("letterboxes.org", &P_FASTMAIL),
|
||||
("mail-central.com", &P_FASTMAIL),
|
||||
("mail-page.com", &P_FASTMAIL),
|
||||
("mailandftp.com", &P_FASTMAIL),
|
||||
("mailas.com", &P_FASTMAIL),
|
||||
("mailbolt.com", &P_FASTMAIL),
|
||||
("mailc.net", &P_FASTMAIL),
|
||||
("mailcan.com", &P_FASTMAIL),
|
||||
("mailforce.net", &P_FASTMAIL),
|
||||
("mailftp.com", &P_FASTMAIL),
|
||||
("mailhaven.com", &P_FASTMAIL),
|
||||
("mailingaddress.org", &P_FASTMAIL),
|
||||
("mailite.com", &P_FASTMAIL),
|
||||
("mailmight.com", &P_FASTMAIL),
|
||||
("mailnew.com", &P_FASTMAIL),
|
||||
("mailsent.net", &P_FASTMAIL),
|
||||
("mailservice.ms", &P_FASTMAIL),
|
||||
("mailup.net", &P_FASTMAIL),
|
||||
("mailworks.org", &P_FASTMAIL),
|
||||
("ml1.net", &P_FASTMAIL),
|
||||
("mm.st", &P_FASTMAIL),
|
||||
("myfastmail.com", &P_FASTMAIL),
|
||||
("mymacmail.com", &P_FASTMAIL),
|
||||
("nospammail.net", &P_FASTMAIL),
|
||||
("ownmail.net", &P_FASTMAIL),
|
||||
("petml.com", &P_FASTMAIL),
|
||||
("postinbox.com", &P_FASTMAIL),
|
||||
("postpro.net", &P_FASTMAIL),
|
||||
("proinbox.com", &P_FASTMAIL),
|
||||
("promessage.com", &P_FASTMAIL),
|
||||
("realemail.net", &P_FASTMAIL),
|
||||
("reallyfast.biz", &P_FASTMAIL),
|
||||
("reallyfast.info", &P_FASTMAIL),
|
||||
("rushpost.com", &P_FASTMAIL),
|
||||
("sent.as", &P_FASTMAIL),
|
||||
("sent.at", &P_FASTMAIL),
|
||||
("sent.com", &P_FASTMAIL),
|
||||
("speedpost.net", &P_FASTMAIL),
|
||||
("speedymail.org", &P_FASTMAIL),
|
||||
("ssl-mail.com", &P_FASTMAIL),
|
||||
("swift-mail.com", &P_FASTMAIL),
|
||||
("the-fastest.net", &P_FASTMAIL),
|
||||
("the-quickest.com", &P_FASTMAIL),
|
||||
("theinternetemail.com", &P_FASTMAIL),
|
||||
("veryfast.biz", &P_FASTMAIL),
|
||||
("veryspeedy.net", &P_FASTMAIL),
|
||||
("warpmail.net", &P_FASTMAIL),
|
||||
("xsmail.com", &P_FASTMAIL),
|
||||
("yepmail.net", &P_FASTMAIL),
|
||||
("your-mail.com", &P_FASTMAIL),
|
||||
("firemail.at", &P_FIREMAIL_DE),
|
||||
("firemail.de", &P_FIREMAIL_DE),
|
||||
("five.chat", &P_FIVE_CHAT),
|
||||
("freenet.de", &P_FREENET_DE),
|
||||
("gmail.com", &P_GMAIL),
|
||||
("googlemail.com", &P_GMAIL),
|
||||
("google.com", &P_GMAIL),
|
||||
("gmx.net", &P_GMX_NET),
|
||||
("gmx.de", &P_GMX_NET),
|
||||
("gmx.at", &P_GMX_NET),
|
||||
("gmx.ch", &P_GMX_NET),
|
||||
("gmx.org", &P_GMX_NET),
|
||||
("gmx.eu", &P_GMX_NET),
|
||||
("gmx.info", &P_GMX_NET),
|
||||
("gmx.biz", &P_GMX_NET),
|
||||
("gmx.com", &P_GMX_NET),
|
||||
("*.hermes.radio", &P_HERMES_RADIO),
|
||||
("*.aco-connexion.org", &P_HERMES_RADIO),
|
||||
("hey.com", &P_HEY_COM),
|
||||
("i.ua", &P_I_UA),
|
||||
("i3.net", &P_I3_NET),
|
||||
("icloud.com", &P_ICLOUD),
|
||||
("me.com", &P_ICLOUD),
|
||||
("mac.com", &P_ICLOUD),
|
||||
("ik.me", &P_INFOMANIAK_COM),
|
||||
("kolst.com", &P_KOLST_COM),
|
||||
("kontent.com", &P_KONTENT_COM),
|
||||
("mail.de", &P_MAIL_DE),
|
||||
("mail.ru", &P_MAIL_RU),
|
||||
("inbox.ru", &P_MAIL_RU),
|
||||
("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),
|
||||
("nauta.cu", &P_NAUTA_CU),
|
||||
("naver.com", &P_NAVER),
|
||||
("nine.testrun.org", &P_NINE_TESTRUN_ORG),
|
||||
("nubo.coop", &P_NUBO_COOP),
|
||||
("hotmail.com", &P_OUTLOOK_COM),
|
||||
("outlook.com", &P_OUTLOOK_COM),
|
||||
("office365.com", &P_OUTLOOK_COM),
|
||||
("outlook.com.tr", &P_OUTLOOK_COM),
|
||||
("live.com", &P_OUTLOOK_COM),
|
||||
("outlook.de", &P_OUTLOOK_COM),
|
||||
("ouvaton.org", &P_OUVATON_COOP),
|
||||
("posteo.de", &P_POSTEO),
|
||||
("posteo.af", &P_POSTEO),
|
||||
("posteo.at", &P_POSTEO),
|
||||
("posteo.be", &P_POSTEO),
|
||||
("posteo.ca", &P_POSTEO),
|
||||
("posteo.ch", &P_POSTEO),
|
||||
("posteo.cl", &P_POSTEO),
|
||||
("posteo.co", &P_POSTEO),
|
||||
("posteo.co.uk", &P_POSTEO),
|
||||
("posteo.com.br", &P_POSTEO),
|
||||
("posteo.cr", &P_POSTEO),
|
||||
("posteo.cz", &P_POSTEO),
|
||||
("posteo.dk", &P_POSTEO),
|
||||
("posteo.ee", &P_POSTEO),
|
||||
("posteo.es", &P_POSTEO),
|
||||
("posteo.eu", &P_POSTEO),
|
||||
("posteo.fi", &P_POSTEO),
|
||||
("posteo.gl", &P_POSTEO),
|
||||
("posteo.gr", &P_POSTEO),
|
||||
("posteo.hn", &P_POSTEO),
|
||||
("posteo.hr", &P_POSTEO),
|
||||
("posteo.hu", &P_POSTEO),
|
||||
("posteo.ie", &P_POSTEO),
|
||||
("posteo.in", &P_POSTEO),
|
||||
("posteo.is", &P_POSTEO),
|
||||
("posteo.it", &P_POSTEO),
|
||||
("posteo.jp", &P_POSTEO),
|
||||
("posteo.la", &P_POSTEO),
|
||||
("posteo.li", &P_POSTEO),
|
||||
("posteo.lt", &P_POSTEO),
|
||||
("posteo.lu", &P_POSTEO),
|
||||
("posteo.me", &P_POSTEO),
|
||||
("posteo.mx", &P_POSTEO),
|
||||
("posteo.my", &P_POSTEO),
|
||||
("posteo.net", &P_POSTEO),
|
||||
("posteo.nl", &P_POSTEO),
|
||||
("posteo.no", &P_POSTEO),
|
||||
("posteo.nz", &P_POSTEO),
|
||||
("posteo.org", &P_POSTEO),
|
||||
("posteo.pe", &P_POSTEO),
|
||||
("posteo.pl", &P_POSTEO),
|
||||
("posteo.pm", &P_POSTEO),
|
||||
("posteo.pt", &P_POSTEO),
|
||||
("posteo.ro", &P_POSTEO),
|
||||
("posteo.ru", &P_POSTEO),
|
||||
("posteo.se", &P_POSTEO),
|
||||
("posteo.sg", &P_POSTEO),
|
||||
("posteo.si", &P_POSTEO),
|
||||
("posteo.tn", &P_POSTEO),
|
||||
("posteo.uk", &P_POSTEO),
|
||||
("posteo.us", &P_POSTEO),
|
||||
("protonmail.com", &P_PROTONMAIL),
|
||||
("protonmail.ch", &P_PROTONMAIL),
|
||||
("pm.me", &P_PROTONMAIL),
|
||||
("qq.com", &P_QQ),
|
||||
("foxmail.com", &P_QQ),
|
||||
("riseup.net", &P_RISEUP_NET),
|
||||
("rogers.com", &P_ROGERS_COM),
|
||||
("sonic.net", &P_SONIC),
|
||||
("systemausfall.org", &P_SYSTEMAUSFALL_ORG),
|
||||
("solidaris.me", &P_SYSTEMAUSFALL_ORG),
|
||||
("systemli.org", &P_SYSTEMLI_ORG),
|
||||
("t-online.de", &P_T_ONLINE),
|
||||
("magenta.de", &P_T_ONLINE),
|
||||
("testrun.org", &P_TESTRUN),
|
||||
("tiscali.it", &P_TISCALI_IT),
|
||||
("tutanota.com", &P_TUTANOTA),
|
||||
("tutanota.de", &P_TUTANOTA),
|
||||
("tutamail.com", &P_TUTANOTA),
|
||||
("tuta.io", &P_TUTANOTA),
|
||||
("keemail.me", &P_TUTANOTA),
|
||||
("ukr.net", &P_UKR_NET),
|
||||
("undernet.uy", &P_UNDERNET_UY),
|
||||
("vfemail.net", &P_VFEMAIL),
|
||||
("vivaldi.net", &P_VIVALDI),
|
||||
("vodafone.de", &P_VODAFONE_DE),
|
||||
("vodafonemail.de", &P_VODAFONE_DE),
|
||||
("web.de", &P_WEB_DE),
|
||||
("email.de", &P_WEB_DE),
|
||||
("flirt.ms", &P_WEB_DE),
|
||||
("hallo.ms", &P_WEB_DE),
|
||||
("kuss.ms", &P_WEB_DE),
|
||||
("love.ms", &P_WEB_DE),
|
||||
("magic.ms", &P_WEB_DE),
|
||||
("singles.ms", &P_WEB_DE),
|
||||
("cool.ms", &P_WEB_DE),
|
||||
("kanzler.ms", &P_WEB_DE),
|
||||
("okay.ms", &P_WEB_DE),
|
||||
("party.ms", &P_WEB_DE),
|
||||
("pop.ms", &P_WEB_DE),
|
||||
("stars.ms", &P_WEB_DE),
|
||||
("techno.ms", &P_WEB_DE),
|
||||
("clever.ms", &P_WEB_DE),
|
||||
("deutschland.ms", &P_WEB_DE),
|
||||
("genial.ms", &P_WEB_DE),
|
||||
("ich.ms", &P_WEB_DE),
|
||||
("online.ms", &P_WEB_DE),
|
||||
("smart.ms", &P_WEB_DE),
|
||||
("wichtig.ms", &P_WEB_DE),
|
||||
("action.ms", &P_WEB_DE),
|
||||
("fussball.ms", &P_WEB_DE),
|
||||
("joker.ms", &P_WEB_DE),
|
||||
("planet.ms", &P_WEB_DE),
|
||||
("power.ms", &P_WEB_DE),
|
||||
("yahoo.com", &P_YAHOO),
|
||||
("yahoo.de", &P_YAHOO),
|
||||
("yahoo.it", &P_YAHOO),
|
||||
("yahoo.fr", &P_YAHOO),
|
||||
("yahoo.es", &P_YAHOO),
|
||||
("yahoo.se", &P_YAHOO),
|
||||
("yahoo.co.uk", &P_YAHOO),
|
||||
("yahoo.co.nz", &P_YAHOO),
|
||||
("yahoo.com.au", &P_YAHOO),
|
||||
("yahoo.com.ar", &P_YAHOO),
|
||||
("yahoo.com.br", &P_YAHOO),
|
||||
("yahoo.com.mx", &P_YAHOO),
|
||||
("ymail.com", &P_YAHOO),
|
||||
("rocketmail.com", &P_YAHOO),
|
||||
("yahoodns.net", &P_YAHOO),
|
||||
("yandex.com", &P_YANDEX_RU),
|
||||
("yandex.by", &P_YANDEX_RU),
|
||||
("yandex.kz", &P_YANDEX_RU),
|
||||
("yandex.ru", &P_YANDEX_RU),
|
||||
("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),
|
||||
("zohomail.com", &P_ZOHO),
|
||||
("zoho.com", &P_ZOHO),
|
||||
];
|
||||
pub(crate) static PROVIDER_DATA: Lazy<HashMap<&'static str, &'static Provider>> = Lazy::new(|| {
|
||||
HashMap::from([
|
||||
("163.com", &P_163),
|
||||
("aktivix.org", &P_AKTIVIX_ORG),
|
||||
("aol.com", &P_AOL),
|
||||
("arcor.de", &P_ARCOR_DE),
|
||||
("autistici.org", &P_AUTISTICI_ORG),
|
||||
("delta.blinzeln.de", &P_BLINDZELN_ORG),
|
||||
("delta.blindzeln.org", &P_BLINDZELN_ORG),
|
||||
("bluewin.ch", &P_BLUEWIN_CH),
|
||||
("buzon.uy", &P_BUZON_UY),
|
||||
("c1.testrun.org", &P_C1_TESTRUN_ORG),
|
||||
("c2.testrun.org", &P_C2_TESTRUN_ORG),
|
||||
("c3.testrun.org", &P_C3_TESTRUN_ORG),
|
||||
("chello.at", &P_CHELLO_AT),
|
||||
("xfinity.com", &P_COMCAST),
|
||||
("comcast.net", &P_COMCAST),
|
||||
("dismail.de", &P_DISMAIL_DE),
|
||||
("disroot.org", &P_DISROOT),
|
||||
("e.email", &P_E_EMAIL),
|
||||
("espiv.net", &P_ESPIV_NET),
|
||||
("example.com", &P_EXAMPLE_COM),
|
||||
("example.org", &P_EXAMPLE_COM),
|
||||
("example.net", &P_EXAMPLE_COM),
|
||||
("123mail.org", &P_FASTMAIL),
|
||||
("150mail.com", &P_FASTMAIL),
|
||||
("150ml.com", &P_FASTMAIL),
|
||||
("16mail.com", &P_FASTMAIL),
|
||||
("2-mail.com", &P_FASTMAIL),
|
||||
("4email.net", &P_FASTMAIL),
|
||||
("50mail.com", &P_FASTMAIL),
|
||||
("airpost.net", &P_FASTMAIL),
|
||||
("allmail.net", &P_FASTMAIL),
|
||||
("bestmail.us", &P_FASTMAIL),
|
||||
("cluemail.com", &P_FASTMAIL),
|
||||
("elitemail.org", &P_FASTMAIL),
|
||||
("emailcorner.net", &P_FASTMAIL),
|
||||
("emailengine.net", &P_FASTMAIL),
|
||||
("emailengine.org", &P_FASTMAIL),
|
||||
("emailgroups.net", &P_FASTMAIL),
|
||||
("emailplus.org", &P_FASTMAIL),
|
||||
("emailuser.net", &P_FASTMAIL),
|
||||
("eml.cc", &P_FASTMAIL),
|
||||
("f-m.fm", &P_FASTMAIL),
|
||||
("fast-email.com", &P_FASTMAIL),
|
||||
("fast-mail.org", &P_FASTMAIL),
|
||||
("fastem.com", &P_FASTMAIL),
|
||||
("fastemail.us", &P_FASTMAIL),
|
||||
("fastemailer.com", &P_FASTMAIL),
|
||||
("fastest.cc", &P_FASTMAIL),
|
||||
("fastimap.com", &P_FASTMAIL),
|
||||
("fastmail.cn", &P_FASTMAIL),
|
||||
("fastmail.co.uk", &P_FASTMAIL),
|
||||
("fastmail.com", &P_FASTMAIL),
|
||||
("fastmail.com.au", &P_FASTMAIL),
|
||||
("fastmail.de", &P_FASTMAIL),
|
||||
("fastmail.es", &P_FASTMAIL),
|
||||
("fastmail.fm", &P_FASTMAIL),
|
||||
("fastmail.fr", &P_FASTMAIL),
|
||||
("fastmail.im", &P_FASTMAIL),
|
||||
("fastmail.in", &P_FASTMAIL),
|
||||
("fastmail.jp", &P_FASTMAIL),
|
||||
("fastmail.mx", &P_FASTMAIL),
|
||||
("fastmail.net", &P_FASTMAIL),
|
||||
("fastmail.nl", &P_FASTMAIL),
|
||||
("fastmail.org", &P_FASTMAIL),
|
||||
("fastmail.se", &P_FASTMAIL),
|
||||
("fastmail.to", &P_FASTMAIL),
|
||||
("fastmail.tw", &P_FASTMAIL),
|
||||
("fastmail.uk", &P_FASTMAIL),
|
||||
("fastmail.us", &P_FASTMAIL),
|
||||
("fastmailbox.net", &P_FASTMAIL),
|
||||
("fastmessaging.com", &P_FASTMAIL),
|
||||
("fea.st", &P_FASTMAIL),
|
||||
("fmail.co.uk", &P_FASTMAIL),
|
||||
("fmailbox.com", &P_FASTMAIL),
|
||||
("fmgirl.com", &P_FASTMAIL),
|
||||
("fmguy.com", &P_FASTMAIL),
|
||||
("ftml.net", &P_FASTMAIL),
|
||||
("h-mail.us", &P_FASTMAIL),
|
||||
("hailmail.net", &P_FASTMAIL),
|
||||
("imap-mail.com", &P_FASTMAIL),
|
||||
("imap.cc", &P_FASTMAIL),
|
||||
("imapmail.org", &P_FASTMAIL),
|
||||
("inoutbox.com", &P_FASTMAIL),
|
||||
("internet-e-mail.com", &P_FASTMAIL),
|
||||
("internet-mail.org", &P_FASTMAIL),
|
||||
("internetemails.net", &P_FASTMAIL),
|
||||
("internetmailing.net", &P_FASTMAIL),
|
||||
("jetemail.net", &P_FASTMAIL),
|
||||
("justemail.net", &P_FASTMAIL),
|
||||
("letterboxes.org", &P_FASTMAIL),
|
||||
("mail-central.com", &P_FASTMAIL),
|
||||
("mail-page.com", &P_FASTMAIL),
|
||||
("mailandftp.com", &P_FASTMAIL),
|
||||
("mailas.com", &P_FASTMAIL),
|
||||
("mailbolt.com", &P_FASTMAIL),
|
||||
("mailc.net", &P_FASTMAIL),
|
||||
("mailcan.com", &P_FASTMAIL),
|
||||
("mailforce.net", &P_FASTMAIL),
|
||||
("mailftp.com", &P_FASTMAIL),
|
||||
("mailhaven.com", &P_FASTMAIL),
|
||||
("mailingaddress.org", &P_FASTMAIL),
|
||||
("mailite.com", &P_FASTMAIL),
|
||||
("mailmight.com", &P_FASTMAIL),
|
||||
("mailnew.com", &P_FASTMAIL),
|
||||
("mailsent.net", &P_FASTMAIL),
|
||||
("mailservice.ms", &P_FASTMAIL),
|
||||
("mailup.net", &P_FASTMAIL),
|
||||
("mailworks.org", &P_FASTMAIL),
|
||||
("ml1.net", &P_FASTMAIL),
|
||||
("mm.st", &P_FASTMAIL),
|
||||
("myfastmail.com", &P_FASTMAIL),
|
||||
("mymacmail.com", &P_FASTMAIL),
|
||||
("nospammail.net", &P_FASTMAIL),
|
||||
("ownmail.net", &P_FASTMAIL),
|
||||
("petml.com", &P_FASTMAIL),
|
||||
("postinbox.com", &P_FASTMAIL),
|
||||
("postpro.net", &P_FASTMAIL),
|
||||
("proinbox.com", &P_FASTMAIL),
|
||||
("promessage.com", &P_FASTMAIL),
|
||||
("realemail.net", &P_FASTMAIL),
|
||||
("reallyfast.biz", &P_FASTMAIL),
|
||||
("reallyfast.info", &P_FASTMAIL),
|
||||
("rushpost.com", &P_FASTMAIL),
|
||||
("sent.as", &P_FASTMAIL),
|
||||
("sent.at", &P_FASTMAIL),
|
||||
("sent.com", &P_FASTMAIL),
|
||||
("speedpost.net", &P_FASTMAIL),
|
||||
("speedymail.org", &P_FASTMAIL),
|
||||
("ssl-mail.com", &P_FASTMAIL),
|
||||
("swift-mail.com", &P_FASTMAIL),
|
||||
("the-fastest.net", &P_FASTMAIL),
|
||||
("the-quickest.com", &P_FASTMAIL),
|
||||
("theinternetemail.com", &P_FASTMAIL),
|
||||
("veryfast.biz", &P_FASTMAIL),
|
||||
("veryspeedy.net", &P_FASTMAIL),
|
||||
("warpmail.net", &P_FASTMAIL),
|
||||
("xsmail.com", &P_FASTMAIL),
|
||||
("yepmail.net", &P_FASTMAIL),
|
||||
("your-mail.com", &P_FASTMAIL),
|
||||
("firemail.at", &P_FIREMAIL_DE),
|
||||
("firemail.de", &P_FIREMAIL_DE),
|
||||
("five.chat", &P_FIVE_CHAT),
|
||||
("freenet.de", &P_FREENET_DE),
|
||||
("gmail.com", &P_GMAIL),
|
||||
("googlemail.com", &P_GMAIL),
|
||||
("google.com", &P_GMAIL),
|
||||
("gmx.net", &P_GMX_NET),
|
||||
("gmx.de", &P_GMX_NET),
|
||||
("gmx.at", &P_GMX_NET),
|
||||
("gmx.ch", &P_GMX_NET),
|
||||
("gmx.org", &P_GMX_NET),
|
||||
("gmx.eu", &P_GMX_NET),
|
||||
("gmx.info", &P_GMX_NET),
|
||||
("gmx.biz", &P_GMX_NET),
|
||||
("gmx.com", &P_GMX_NET),
|
||||
("ac.hermes.radio", &P_HERMES_RADIO),
|
||||
("ac1.hermes.radio", &P_HERMES_RADIO),
|
||||
("ac2.hermes.radio", &P_HERMES_RADIO),
|
||||
("ac3.hermes.radio", &P_HERMES_RADIO),
|
||||
("ac4.hermes.radio", &P_HERMES_RADIO),
|
||||
("ac5.hermes.radio", &P_HERMES_RADIO),
|
||||
("ac6.hermes.radio", &P_HERMES_RADIO),
|
||||
("ac7.hermes.radio", &P_HERMES_RADIO),
|
||||
("ac8.hermes.radio", &P_HERMES_RADIO),
|
||||
("ac9.hermes.radio", &P_HERMES_RADIO),
|
||||
("ac10.hermes.radio", &P_HERMES_RADIO),
|
||||
("ac11.hermes.radio", &P_HERMES_RADIO),
|
||||
("ac12.hermes.radio", &P_HERMES_RADIO),
|
||||
("ac13.hermes.radio", &P_HERMES_RADIO),
|
||||
("ac14.hermes.radio", &P_HERMES_RADIO),
|
||||
("ac15.hermes.radio", &P_HERMES_RADIO),
|
||||
("ka.hermes.radio", &P_HERMES_RADIO),
|
||||
("ka1.hermes.radio", &P_HERMES_RADIO),
|
||||
("ka2.hermes.radio", &P_HERMES_RADIO),
|
||||
("ka3.hermes.radio", &P_HERMES_RADIO),
|
||||
("ka4.hermes.radio", &P_HERMES_RADIO),
|
||||
("ka5.hermes.radio", &P_HERMES_RADIO),
|
||||
("ka6.hermes.radio", &P_HERMES_RADIO),
|
||||
("ka7.hermes.radio", &P_HERMES_RADIO),
|
||||
("ka8.hermes.radio", &P_HERMES_RADIO),
|
||||
("ka9.hermes.radio", &P_HERMES_RADIO),
|
||||
("ka10.hermes.radio", &P_HERMES_RADIO),
|
||||
("ka11.hermes.radio", &P_HERMES_RADIO),
|
||||
("ka12.hermes.radio", &P_HERMES_RADIO),
|
||||
("ka13.hermes.radio", &P_HERMES_RADIO),
|
||||
("ka14.hermes.radio", &P_HERMES_RADIO),
|
||||
("ka15.hermes.radio", &P_HERMES_RADIO),
|
||||
("ec.hermes.radio", &P_HERMES_RADIO),
|
||||
("ec1.hermes.radio", &P_HERMES_RADIO),
|
||||
("ec2.hermes.radio", &P_HERMES_RADIO),
|
||||
("ec3.hermes.radio", &P_HERMES_RADIO),
|
||||
("ec4.hermes.radio", &P_HERMES_RADIO),
|
||||
("ec5.hermes.radio", &P_HERMES_RADIO),
|
||||
("ec6.hermes.radio", &P_HERMES_RADIO),
|
||||
("ec7.hermes.radio", &P_HERMES_RADIO),
|
||||
("ec8.hermes.radio", &P_HERMES_RADIO),
|
||||
("ec9.hermes.radio", &P_HERMES_RADIO),
|
||||
("ec10.hermes.radio", &P_HERMES_RADIO),
|
||||
("ec11.hermes.radio", &P_HERMES_RADIO),
|
||||
("ec12.hermes.radio", &P_HERMES_RADIO),
|
||||
("ec13.hermes.radio", &P_HERMES_RADIO),
|
||||
("ec14.hermes.radio", &P_HERMES_RADIO),
|
||||
("ec15.hermes.radio", &P_HERMES_RADIO),
|
||||
("hermes.radio", &P_HERMES_RADIO),
|
||||
("hey.com", &P_HEY_COM),
|
||||
("i.ua", &P_I_UA),
|
||||
("i3.net", &P_I3_NET),
|
||||
("icloud.com", &P_ICLOUD),
|
||||
("me.com", &P_ICLOUD),
|
||||
("mac.com", &P_ICLOUD),
|
||||
("ik.me", &P_INFOMANIAK_COM),
|
||||
("kolst.com", &P_KOLST_COM),
|
||||
("kontent.com", &P_KONTENT_COM),
|
||||
("mail.de", &P_MAIL_DE),
|
||||
("mail.ru", &P_MAIL_RU),
|
||||
("inbox.ru", &P_MAIL_RU),
|
||||
("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),
|
||||
("nauta.cu", &P_NAUTA_CU),
|
||||
("naver.com", &P_NAVER),
|
||||
("nine.testrun.org", &P_NINE_TESTRUN_ORG),
|
||||
("nubo.coop", &P_NUBO_COOP),
|
||||
("hotmail.com", &P_OUTLOOK_COM),
|
||||
("outlook.com", &P_OUTLOOK_COM),
|
||||
("office365.com", &P_OUTLOOK_COM),
|
||||
("outlook.com.tr", &P_OUTLOOK_COM),
|
||||
("live.com", &P_OUTLOOK_COM),
|
||||
("outlook.de", &P_OUTLOOK_COM),
|
||||
("ouvaton.org", &P_OUVATON_COOP),
|
||||
("posteo.de", &P_POSTEO),
|
||||
("posteo.af", &P_POSTEO),
|
||||
("posteo.at", &P_POSTEO),
|
||||
("posteo.be", &P_POSTEO),
|
||||
("posteo.ca", &P_POSTEO),
|
||||
("posteo.ch", &P_POSTEO),
|
||||
("posteo.cl", &P_POSTEO),
|
||||
("posteo.co", &P_POSTEO),
|
||||
("posteo.co.uk", &P_POSTEO),
|
||||
("posteo.com.br", &P_POSTEO),
|
||||
("posteo.cr", &P_POSTEO),
|
||||
("posteo.cz", &P_POSTEO),
|
||||
("posteo.dk", &P_POSTEO),
|
||||
("posteo.ee", &P_POSTEO),
|
||||
("posteo.es", &P_POSTEO),
|
||||
("posteo.eu", &P_POSTEO),
|
||||
("posteo.fi", &P_POSTEO),
|
||||
("posteo.gl", &P_POSTEO),
|
||||
("posteo.gr", &P_POSTEO),
|
||||
("posteo.hn", &P_POSTEO),
|
||||
("posteo.hr", &P_POSTEO),
|
||||
("posteo.hu", &P_POSTEO),
|
||||
("posteo.ie", &P_POSTEO),
|
||||
("posteo.in", &P_POSTEO),
|
||||
("posteo.is", &P_POSTEO),
|
||||
("posteo.it", &P_POSTEO),
|
||||
("posteo.jp", &P_POSTEO),
|
||||
("posteo.la", &P_POSTEO),
|
||||
("posteo.li", &P_POSTEO),
|
||||
("posteo.lt", &P_POSTEO),
|
||||
("posteo.lu", &P_POSTEO),
|
||||
("posteo.me", &P_POSTEO),
|
||||
("posteo.mx", &P_POSTEO),
|
||||
("posteo.my", &P_POSTEO),
|
||||
("posteo.net", &P_POSTEO),
|
||||
("posteo.nl", &P_POSTEO),
|
||||
("posteo.no", &P_POSTEO),
|
||||
("posteo.nz", &P_POSTEO),
|
||||
("posteo.org", &P_POSTEO),
|
||||
("posteo.pe", &P_POSTEO),
|
||||
("posteo.pl", &P_POSTEO),
|
||||
("posteo.pm", &P_POSTEO),
|
||||
("posteo.pt", &P_POSTEO),
|
||||
("posteo.ro", &P_POSTEO),
|
||||
("posteo.ru", &P_POSTEO),
|
||||
("posteo.se", &P_POSTEO),
|
||||
("posteo.sg", &P_POSTEO),
|
||||
("posteo.si", &P_POSTEO),
|
||||
("posteo.tn", &P_POSTEO),
|
||||
("posteo.uk", &P_POSTEO),
|
||||
("posteo.us", &P_POSTEO),
|
||||
("protonmail.com", &P_PROTONMAIL),
|
||||
("protonmail.ch", &P_PROTONMAIL),
|
||||
("pm.me", &P_PROTONMAIL),
|
||||
("qq.com", &P_QQ),
|
||||
("foxmail.com", &P_QQ),
|
||||
("riseup.net", &P_RISEUP_NET),
|
||||
("rogers.com", &P_ROGERS_COM),
|
||||
("sonic.net", &P_SONIC),
|
||||
("systemausfall.org", &P_SYSTEMAUSFALL_ORG),
|
||||
("solidaris.me", &P_SYSTEMAUSFALL_ORG),
|
||||
("systemli.org", &P_SYSTEMLI_ORG),
|
||||
("t-online.de", &P_T_ONLINE),
|
||||
("magenta.de", &P_T_ONLINE),
|
||||
("testrun.org", &P_TESTRUN),
|
||||
("tiscali.it", &P_TISCALI_IT),
|
||||
("tutanota.com", &P_TUTANOTA),
|
||||
("tutanota.de", &P_TUTANOTA),
|
||||
("tutamail.com", &P_TUTANOTA),
|
||||
("tuta.io", &P_TUTANOTA),
|
||||
("keemail.me", &P_TUTANOTA),
|
||||
("ukr.net", &P_UKR_NET),
|
||||
("undernet.uy", &P_UNDERNET_UY),
|
||||
("vfemail.net", &P_VFEMAIL),
|
||||
("vivaldi.net", &P_VIVALDI),
|
||||
("vodafone.de", &P_VODAFONE_DE),
|
||||
("vodafonemail.de", &P_VODAFONE_DE),
|
||||
("web.de", &P_WEB_DE),
|
||||
("email.de", &P_WEB_DE),
|
||||
("flirt.ms", &P_WEB_DE),
|
||||
("hallo.ms", &P_WEB_DE),
|
||||
("kuss.ms", &P_WEB_DE),
|
||||
("love.ms", &P_WEB_DE),
|
||||
("magic.ms", &P_WEB_DE),
|
||||
("singles.ms", &P_WEB_DE),
|
||||
("cool.ms", &P_WEB_DE),
|
||||
("kanzler.ms", &P_WEB_DE),
|
||||
("okay.ms", &P_WEB_DE),
|
||||
("party.ms", &P_WEB_DE),
|
||||
("pop.ms", &P_WEB_DE),
|
||||
("stars.ms", &P_WEB_DE),
|
||||
("techno.ms", &P_WEB_DE),
|
||||
("clever.ms", &P_WEB_DE),
|
||||
("deutschland.ms", &P_WEB_DE),
|
||||
("genial.ms", &P_WEB_DE),
|
||||
("ich.ms", &P_WEB_DE),
|
||||
("online.ms", &P_WEB_DE),
|
||||
("smart.ms", &P_WEB_DE),
|
||||
("wichtig.ms", &P_WEB_DE),
|
||||
("action.ms", &P_WEB_DE),
|
||||
("fussball.ms", &P_WEB_DE),
|
||||
("joker.ms", &P_WEB_DE),
|
||||
("planet.ms", &P_WEB_DE),
|
||||
("power.ms", &P_WEB_DE),
|
||||
("yahoo.com", &P_YAHOO),
|
||||
("yahoo.de", &P_YAHOO),
|
||||
("yahoo.it", &P_YAHOO),
|
||||
("yahoo.fr", &P_YAHOO),
|
||||
("yahoo.es", &P_YAHOO),
|
||||
("yahoo.se", &P_YAHOO),
|
||||
("yahoo.co.uk", &P_YAHOO),
|
||||
("yahoo.co.nz", &P_YAHOO),
|
||||
("yahoo.com.au", &P_YAHOO),
|
||||
("yahoo.com.ar", &P_YAHOO),
|
||||
("yahoo.com.br", &P_YAHOO),
|
||||
("yahoo.com.mx", &P_YAHOO),
|
||||
("ymail.com", &P_YAHOO),
|
||||
("rocketmail.com", &P_YAHOO),
|
||||
("yahoodns.net", &P_YAHOO),
|
||||
("yandex.com", &P_YANDEX_RU),
|
||||
("yandex.by", &P_YANDEX_RU),
|
||||
("yandex.kz", &P_YANDEX_RU),
|
||||
("yandex.ru", &P_YANDEX_RU),
|
||||
("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),
|
||||
("zohomail.com", &P_ZOHO),
|
||||
("zoho.com", &P_ZOHO),
|
||||
])
|
||||
});
|
||||
|
||||
pub(crate) static PROVIDER_IDS: Lazy<HashMap<&'static str, &'static Provider>> = Lazy::new(|| {
|
||||
HashMap::from([
|
||||
@@ -2001,4 +2050,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_opt(2024, 2, 5).unwrap());
|
||||
Lazy::new(|| chrono::NaiveDate::from_ymd_opt(2023, 11, 5).unwrap());
|
||||
|
||||
14
src/qr.rs
14
src/qr.rs
@@ -383,7 +383,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
||||
.await
|
||||
.with_context(|| format!("can't check if address {addr:?} is our address"))?
|
||||
{
|
||||
if token::exists(context, token::Namespace::InviteNumber, &invitenumber).await? {
|
||||
if token::exists(context, token::Namespace::InviteNumber, &invitenumber).await {
|
||||
Ok(Qr::WithdrawVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
@@ -413,7 +413,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
||||
})
|
||||
}
|
||||
} else if context.is_self_addr(&addr).await? {
|
||||
if token::exists(context, token::Namespace::InviteNumber, &invitenumber).await? {
|
||||
if token::exists(context, token::Namespace::InviteNumber, &invitenumber).await {
|
||||
Ok(Qr::WithdrawVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
@@ -560,12 +560,8 @@ async fn set_account_from_qr(context: &Context, qr: &str) -> Result<()> {
|
||||
"Cannot create account, response from {url_str:?} is malformed:\n{response_text:?}"
|
||||
)
|
||||
})?;
|
||||
context
|
||||
.set_config_internal(Config::Addr, Some(&email))
|
||||
.await?;
|
||||
context
|
||||
.set_config_internal(Config::MailPw, Some(&password))
|
||||
.await?;
|
||||
context.set_config(Config::Addr, Some(&email)).await?;
|
||||
context.set_config(Config::MailPw, Some(&password)).await?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -593,7 +589,7 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<()> {
|
||||
instance_pattern,
|
||||
} => {
|
||||
context
|
||||
.set_config_internal(Config::WebrtcInstance, Some(&instance_pattern))
|
||||
.set_config(Config::WebrtcInstance, Some(&instance_pattern))
|
||||
.await?;
|
||||
}
|
||||
Qr::WithdrawVerifyContact {
|
||||
|
||||
@@ -163,9 +163,7 @@ pub(crate) async fn configure_from_login_qr(
|
||||
address: &str,
|
||||
options: LoginOptions,
|
||||
) -> Result<()> {
|
||||
context
|
||||
.set_config_internal(Config::Addr, Some(address))
|
||||
.await?;
|
||||
context.set_config(Config::Addr, Some(address)).await?;
|
||||
|
||||
match options {
|
||||
LoginOptions::V1 {
|
||||
@@ -183,35 +181,27 @@ pub(crate) async fn configure_from_login_qr(
|
||||
smtp_security,
|
||||
smtp_certificate_checks,
|
||||
} => {
|
||||
context
|
||||
.set_config_internal(Config::MailPw, Some(&mail_pw))
|
||||
.await?;
|
||||
context.set_config(Config::MailPw, Some(&mail_pw)).await?;
|
||||
if let Some(value) = imap_host {
|
||||
context
|
||||
.set_config_internal(Config::MailServer, Some(&value))
|
||||
.await?;
|
||||
context.set_config(Config::MailServer, Some(&value)).await?;
|
||||
}
|
||||
if let Some(value) = imap_port {
|
||||
context
|
||||
.set_config_internal(Config::MailPort, Some(&value.to_string()))
|
||||
.set_config(Config::MailPort, Some(&value.to_string()))
|
||||
.await?;
|
||||
}
|
||||
if let Some(value) = imap_username {
|
||||
context
|
||||
.set_config_internal(Config::MailUser, Some(&value))
|
||||
.await?;
|
||||
context.set_config(Config::MailUser, Some(&value)).await?;
|
||||
}
|
||||
if let Some(value) = imap_password {
|
||||
context
|
||||
.set_config_internal(Config::MailPw, Some(&value))
|
||||
.await?;
|
||||
context.set_config(Config::MailPw, Some(&value)).await?;
|
||||
}
|
||||
if let Some(value) = imap_security {
|
||||
let code = value
|
||||
.to_u8()
|
||||
.context("could not convert imap security value to number")?;
|
||||
context
|
||||
.set_config_internal(Config::MailSecurity, Some(&code.to_string()))
|
||||
.set_config(Config::MailSecurity, Some(&code.to_string()))
|
||||
.await?;
|
||||
}
|
||||
if let Some(value) = imap_certificate_checks {
|
||||
@@ -219,35 +209,29 @@ pub(crate) async fn configure_from_login_qr(
|
||||
.to_u32()
|
||||
.context("could not convert imap certificate checks value to number")?;
|
||||
context
|
||||
.set_config_internal(Config::ImapCertificateChecks, Some(&code.to_string()))
|
||||
.set_config(Config::ImapCertificateChecks, Some(&code.to_string()))
|
||||
.await?;
|
||||
}
|
||||
if let Some(value) = smtp_host {
|
||||
context
|
||||
.set_config_internal(Config::SendServer, Some(&value))
|
||||
.await?;
|
||||
context.set_config(Config::SendServer, Some(&value)).await?;
|
||||
}
|
||||
if let Some(value) = smtp_port {
|
||||
context
|
||||
.set_config_internal(Config::SendPort, Some(&value.to_string()))
|
||||
.set_config(Config::SendPort, Some(&value.to_string()))
|
||||
.await?;
|
||||
}
|
||||
if let Some(value) = smtp_username {
|
||||
context
|
||||
.set_config_internal(Config::SendUser, Some(&value))
|
||||
.await?;
|
||||
context.set_config(Config::SendUser, Some(&value)).await?;
|
||||
}
|
||||
if let Some(value) = smtp_password {
|
||||
context
|
||||
.set_config_internal(Config::SendPw, Some(&value))
|
||||
.await?;
|
||||
context.set_config(Config::SendPw, Some(&value)).await?;
|
||||
}
|
||||
if let Some(value) = smtp_security {
|
||||
let code = value
|
||||
.to_u8()
|
||||
.context("could not convert smtp security value to number")?;
|
||||
context
|
||||
.set_config_internal(Config::SendSecurity, Some(&code.to_string()))
|
||||
.set_config(Config::SendSecurity, Some(&code.to_string()))
|
||||
.await?;
|
||||
}
|
||||
if let Some(value) = smtp_certificate_checks {
|
||||
@@ -255,7 +239,7 @@ pub(crate) async fn configure_from_login_qr(
|
||||
.to_u32()
|
||||
.context("could not convert smtp certificate checks value to number")?;
|
||||
context
|
||||
.set_config_internal(Config::SmtpCertificateChecks, Some(&code.to_string()))
|
||||
.set_config(Config::SmtpCertificateChecks, Some(&code.to_string()))
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
18
src/quota.rs
18
src/quota.rs
@@ -12,7 +12,7 @@ use crate::imap::scan_folders::get_watched_folders;
|
||||
use crate::imap::session::Session as ImapSession;
|
||||
use crate::imap::Imap;
|
||||
use crate::message::{Message, Viewtype};
|
||||
use crate::tools;
|
||||
use crate::tools::time;
|
||||
use crate::{stock_str, EventType};
|
||||
|
||||
/// warn about a nearly full mailbox after this usage percentage is reached.
|
||||
@@ -40,8 +40,8 @@ pub struct QuotaInfo {
|
||||
/// set to `Ok()` for valid quota information.
|
||||
pub(crate) recent: Result<BTreeMap<String, Vec<QuotaResource>>>,
|
||||
|
||||
/// When the structure was modified.
|
||||
pub(crate) modified: tools::Time,
|
||||
/// Timestamp when structure was modified.
|
||||
pub(crate) modified: i64,
|
||||
}
|
||||
|
||||
async fn get_unique_quota_roots_and_usage(
|
||||
@@ -132,17 +132,13 @@ impl Context {
|
||||
highest,
|
||||
self.get_config_int(Config::QuotaExceeding).await? as u64,
|
||||
) {
|
||||
self.set_config_internal(
|
||||
Config::QuotaExceeding,
|
||||
Some(&highest.to_string()),
|
||||
)
|
||||
.await?;
|
||||
self.set_config(Config::QuotaExceeding, Some(&highest.to_string()))
|
||||
.await?;
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = stock_str::quota_exceeding(self, highest).await;
|
||||
add_device_msg_with_importance(self, None, Some(&mut msg), true).await?;
|
||||
} else if highest <= QUOTA_ALLCLEAR_PERCENTAGE {
|
||||
self.set_config_internal(Config::QuotaExceeding, None)
|
||||
.await?;
|
||||
self.set_config(Config::QuotaExceeding, None).await?;
|
||||
}
|
||||
}
|
||||
Err(err) => warn!(self, "cannot get highest quota usage: {:#}", err),
|
||||
@@ -151,7 +147,7 @@ impl Context {
|
||||
|
||||
*self.quota.write().await = Some(QuotaInfo {
|
||||
recent: quota,
|
||||
modified: tools::Time::now(),
|
||||
modified: time(),
|
||||
});
|
||||
|
||||
self.emit_event(EventType::ConnectivityChanged);
|
||||
|
||||
@@ -38,7 +38,7 @@ use crate::simplify;
|
||||
use crate::sql;
|
||||
use crate::stock_str;
|
||||
use crate::sync::Sync::*;
|
||||
use crate::tools::{self, buf_compress, extract_grpid_from_rfc724_mid, strip_rtlo_characters};
|
||||
use crate::tools::{buf_compress, extract_grpid_from_rfc724_mid, strip_rtlo_characters};
|
||||
use crate::{contact, imap};
|
||||
|
||||
/// This is the struct that is returned after receiving one email (aka MIME message).
|
||||
@@ -220,6 +220,7 @@ pub(crate) async fn receive_imf_inner(
|
||||
context,
|
||||
"Receiving message {rfc724_mid_orig:?}, seen={seen}...",
|
||||
);
|
||||
let incoming = !context.is_self_addr(&mime_parser.from.addr).await?;
|
||||
|
||||
// check, if the mail is already in our database.
|
||||
// make sure, this check is done eg. before securejoin-processing.
|
||||
@@ -277,7 +278,7 @@ pub(crate) async fn receive_imf_inner(
|
||||
// Need to update chat id in the db.
|
||||
} else if let Some(msg_id) = replace_msg_id {
|
||||
info!(context, "Message is already downloaded.");
|
||||
if mime_parser.incoming {
|
||||
if incoming {
|
||||
return Ok(None);
|
||||
}
|
||||
// For the case if we missed a successful SMTP response. Be optimistic that the message is
|
||||
@@ -330,7 +331,7 @@ pub(crate) async fn receive_imf_inner(
|
||||
let to_ids = add_or_lookup_contacts_by_address_list(
|
||||
context,
|
||||
&mime_parser.recipients,
|
||||
if !mime_parser.incoming {
|
||||
if !incoming {
|
||||
Origin::OutgoingTo
|
||||
} else if incoming_origin.is_known() {
|
||||
Origin::IncomingTo
|
||||
@@ -345,7 +346,7 @@ pub(crate) async fn receive_imf_inner(
|
||||
let received_msg;
|
||||
if mime_parser.get_header(HeaderDef::SecureJoin).is_some() {
|
||||
let res;
|
||||
if mime_parser.incoming {
|
||||
if incoming {
|
||||
res = handle_securejoin_handshake(context, &mime_parser, from_id)
|
||||
.await
|
||||
.context("error in Secure-Join message handling")?;
|
||||
@@ -412,6 +413,7 @@ pub(crate) async fn receive_imf_inner(
|
||||
context,
|
||||
&mut mime_parser,
|
||||
imf_raw,
|
||||
incoming,
|
||||
&to_ids,
|
||||
rfc724_mid_orig,
|
||||
from_id,
|
||||
@@ -569,7 +571,7 @@ pub(crate) async fn receive_imf_inner(
|
||||
} else if !chat_id.is_trash() {
|
||||
let fresh = received_msg.state == MessageState::InFresh;
|
||||
for msg_id in &received_msg.msg_ids {
|
||||
chat_id.emit_msg_event(context, *msg_id, mime_parser.incoming && fresh);
|
||||
chat_id.emit_msg_event(context, *msg_id, incoming && fresh);
|
||||
}
|
||||
}
|
||||
context.new_msgs_notify.notify_one();
|
||||
@@ -645,6 +647,7 @@ async fn add_parts(
|
||||
context: &Context,
|
||||
mime_parser: &mut MimeMessage,
|
||||
imf_raw: &[u8],
|
||||
incoming: bool,
|
||||
to_ids: &[ContactId],
|
||||
rfc724_mid: &str,
|
||||
from_id: ContactId,
|
||||
@@ -712,9 +715,8 @@ async fn add_parts(
|
||||
// (of course, the user can add other chats manually later)
|
||||
let to_id: ContactId;
|
||||
let state: MessageState;
|
||||
let mut hidden = false;
|
||||
let mut needs_delete_job = false;
|
||||
if mime_parser.incoming {
|
||||
if incoming {
|
||||
to_id = ContactId::SELF;
|
||||
|
||||
let test_normal_chat = if from_id == ContactId::UNDEFINED {
|
||||
@@ -1011,37 +1013,6 @@ async fn add_parts(
|
||||
}
|
||||
}
|
||||
|
||||
if mime_parser.decrypting_failed && !fetching_existing_messages {
|
||||
if chat_id.is_none() {
|
||||
chat_id = Some(DC_CHAT_ID_TRASH);
|
||||
} else {
|
||||
hidden = true;
|
||||
}
|
||||
let last_time = context
|
||||
.get_config_i64(Config::LastCantDecryptOutgoingMsgs)
|
||||
.await?;
|
||||
let now = tools::time();
|
||||
let update_config = if last_time.saturating_add(24 * 60 * 60) <= now {
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = stock_str::cant_decrypt_outgoing_msgs(context).await;
|
||||
chat::add_device_msg(context, None, Some(&mut msg))
|
||||
.await
|
||||
.log_err(context)
|
||||
.ok();
|
||||
true
|
||||
} else {
|
||||
last_time > now
|
||||
};
|
||||
if update_config {
|
||||
context
|
||||
.set_config_internal(
|
||||
Config::LastCantDecryptOutgoingMsgs,
|
||||
Some(&now.to_string()),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
if !to_ids.is_empty() {
|
||||
if chat_id.is_none() {
|
||||
if let Some((new_chat_id, new_chat_id_blocked)) = create_or_lookup_group(
|
||||
@@ -1184,7 +1155,7 @@ async fn add_parts(
|
||||
context,
|
||||
mime_parser.timestamp_sent,
|
||||
sort_to_bottom,
|
||||
mime_parser.incoming,
|
||||
incoming,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1278,7 +1249,7 @@ async fn add_parts(
|
||||
// -> Showing info messages everytime would be a lot of noise
|
||||
// 3. The info messages that are shown to the user ("Your chat partner
|
||||
// likely reinstalled DC" or similar) would be wrong.
|
||||
if chat.is_protected() && (mime_parser.incoming || chat.typ != Chattype::Single) {
|
||||
if chat.is_protected() && (incoming || chat.typ != Chattype::Single) {
|
||||
if let VerifiedEncryption::NotVerified(err) = verified_encryption {
|
||||
warn!(context, "Verification problem: {err:#}.");
|
||||
let s = format!("{err}. See 'Info' for more details");
|
||||
@@ -1444,7 +1415,7 @@ INSERT INTO msgs
|
||||
rfc724_mid, chat_id,
|
||||
from_id, to_id, timestamp, timestamp_sent,
|
||||
timestamp_rcvd, type, state, msgrmsg,
|
||||
txt, subject, txt_raw, param, hidden,
|
||||
txt, subject, txt_raw, param,
|
||||
bytes, mime_headers, mime_compressed, mime_in_reply_to,
|
||||
mime_references, mime_modified, error, ephemeral_timer,
|
||||
ephemeral_timestamp, download_state, hop_info
|
||||
@@ -1453,7 +1424,7 @@ INSERT INTO msgs
|
||||
?,
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?, 1,
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?
|
||||
@@ -1463,7 +1434,7 @@ SET rfc724_mid=excluded.rfc724_mid, chat_id=excluded.chat_id,
|
||||
from_id=excluded.from_id, to_id=excluded.to_id, timestamp_sent=excluded.timestamp_sent,
|
||||
type=excluded.type, msgrmsg=excluded.msgrmsg,
|
||||
txt=excluded.txt, subject=excluded.subject, txt_raw=excluded.txt_raw, param=excluded.param,
|
||||
hidden=excluded.hidden,bytes=excluded.bytes, mime_headers=excluded.mime_headers,
|
||||
bytes=excluded.bytes, mime_headers=excluded.mime_headers,
|
||||
mime_compressed=excluded.mime_compressed, mime_in_reply_to=excluded.mime_in_reply_to,
|
||||
mime_references=excluded.mime_references, mime_modified=excluded.mime_modified, error=excluded.error, ephemeral_timer=excluded.ephemeral_timer,
|
||||
ephemeral_timestamp=excluded.ephemeral_timestamp, download_state=excluded.download_state, hop_info=excluded.hop_info
|
||||
@@ -1490,7 +1461,6 @@ RETURNING id
|
||||
} else {
|
||||
param.to_string()
|
||||
},
|
||||
hidden,
|
||||
part.bytes as isize,
|
||||
if (save_mime_headers || mime_modified) && !trash {
|
||||
mime_headers.clone()
|
||||
@@ -1556,7 +1526,7 @@ RETURNING id
|
||||
);
|
||||
|
||||
// new outgoing message from another device marks the chat as noticed.
|
||||
if !mime_parser.incoming && !chat_id.is_special() {
|
||||
if !incoming && !chat_id.is_special() {
|
||||
chat::marknoticed_chat_if_older_than(context, chat_id, sort_timestamp).await?;
|
||||
}
|
||||
|
||||
@@ -1579,7 +1549,7 @@ RETURNING id
|
||||
}
|
||||
}
|
||||
|
||||
if !mime_parser.incoming && is_mdn && is_dc_message == MessengerMessage::Yes {
|
||||
if !incoming && is_mdn && is_dc_message == MessengerMessage::Yes {
|
||||
// Normally outgoing MDNs sent by us never appear in mailboxes, but Gmail saves all
|
||||
// outgoing messages, including MDNs, to the Sent folder. If we detect such saved MDN,
|
||||
// delete it.
|
||||
|
||||
@@ -28,24 +28,11 @@ async fn test_grpid_simple() {
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..], None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(mimeparser.incoming, true);
|
||||
assert_eq!(extract_grpid(&mimeparser, HeaderDef::InReplyTo), None);
|
||||
let grpid = Some("HcxyMARjyJy");
|
||||
assert_eq!(extract_grpid(&mimeparser, HeaderDef::References), grpid);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_outgoing() -> Result<()> {
|
||||
let context = TestContext::new_alice().await;
|
||||
let raw = b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||
From: alice@example.org\n\
|
||||
\n\
|
||||
hello";
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..], None).await?;
|
||||
assert_eq!(mimeparser.incoming, false);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_bad_from() {
|
||||
let context = TestContext::new_alice().await;
|
||||
@@ -3232,42 +3219,6 @@ async fn test_blocked_contact_creates_group() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_outgoing_undecryptable() -> Result<()> {
|
||||
let alice = &TestContext::new().await;
|
||||
alice.configure_addr("alice@example.org").await;
|
||||
|
||||
let raw = include_bytes!("../../test-data/message/thunderbird_with_autocrypt.eml");
|
||||
receive_imf(alice, raw, false).await?;
|
||||
|
||||
let bob_contact_id = Contact::lookup_id_by_addr(alice, "bob@example.net", Origin::OutgoingTo)
|
||||
.await?
|
||||
.unwrap();
|
||||
assert!(ChatId::lookup_by_contact(alice, bob_contact_id)
|
||||
.await?
|
||||
.is_none());
|
||||
|
||||
let dev_chat_id = ChatId::lookup_by_contact(alice, ContactId::DEVICE)
|
||||
.await?
|
||||
.unwrap();
|
||||
let dev_msg = alice.get_last_msg_in(dev_chat_id).await;
|
||||
assert!(dev_msg.error().is_none());
|
||||
assert!(dev_msg
|
||||
.text
|
||||
.contains(&stock_str::cant_decrypt_outgoing_msgs(alice).await));
|
||||
|
||||
let raw = include_bytes!("../../test-data/message/thunderbird_encrypted_signed.eml");
|
||||
receive_imf(alice, raw, false).await?;
|
||||
|
||||
assert!(ChatId::lookup_by_contact(alice, bob_contact_id)
|
||||
.await?
|
||||
.is_none());
|
||||
// The device message mustn't be added too frequently.
|
||||
assert_eq!(alice.get_last_msg_in(dev_chat_id).await.id, dev_msg.id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_thunderbird_autocrypt() -> Result<()> {
|
||||
let t = TestContext::new_bob().await;
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::cmp;
|
||||
use std::iter::{self, once};
|
||||
use std::num::NonZeroUsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{bail, Context as _, Error, Result};
|
||||
use async_channel::{self as channel, Receiver, Sender};
|
||||
@@ -13,7 +12,7 @@ use tokio::sync::{oneshot, RwLock, RwLockWriteGuard};
|
||||
use tokio::task;
|
||||
|
||||
use self::connectivity::ConnectivityStore;
|
||||
use crate::config::{self, Config};
|
||||
use crate::config::Config;
|
||||
use crate::contact::{ContactId, RecentlySeenLoop};
|
||||
use crate::context::Context;
|
||||
use crate::download::{download_msg, DownloadState};
|
||||
@@ -25,7 +24,7 @@ use crate::log::LogExt;
|
||||
use crate::message::MsgId;
|
||||
use crate::smtp::{send_smtp_messages, Smtp};
|
||||
use crate::sql;
|
||||
use crate::tools::{self, duration_to_str, maybe_add_time_based_warnings, time, time_elapsed};
|
||||
use crate::tools::{duration_to_str, maybe_add_time_based_warnings, time};
|
||||
|
||||
pub(crate) mod connectivity;
|
||||
|
||||
@@ -291,7 +290,7 @@ enum InnerSchedulerState {
|
||||
///
|
||||
/// Returned by [`SchedulerState::pause`]. To resume the IO scheduler simply drop this
|
||||
/// guard.
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct IoPausedGuard {
|
||||
sender: Option<oneshot::Sender<()>>,
|
||||
}
|
||||
@@ -399,7 +398,7 @@ async fn inbox_loop(
|
||||
let quota = ctx.quota.read().await;
|
||||
quota
|
||||
.as_ref()
|
||||
.filter(|quota| time_elapsed("a.modified) > Duration::from_secs(60))
|
||||
.filter(|quota| quota.modified + 60 > time())
|
||||
.is_none()
|
||||
};
|
||||
|
||||
@@ -440,12 +439,8 @@ async fn inbox_loop(
|
||||
//
|
||||
// This operation is not critical enough to retry,
|
||||
// especially if the error is persistent.
|
||||
if let Err(err) = ctx
|
||||
.set_config_internal(
|
||||
Config::FetchedExistingMsgs,
|
||||
config::from_bool(true),
|
||||
)
|
||||
.await
|
||||
if let Err(err) =
|
||||
ctx.set_config_bool(Config::FetchedExistingMsgs, true).await
|
||||
{
|
||||
warn!(ctx, "Can't set Config::FetchedExistingMsgs: {:#}", err);
|
||||
}
|
||||
@@ -522,20 +517,6 @@ pub async fn convert_folder_meaning(
|
||||
/// critical operation fails such as fetching new messages fails, connection is reset via
|
||||
/// `trigger_reconnect`, so a fresh one can be opened.
|
||||
async fn fetch_idle(ctx: &Context, connection: &mut Imap, folder_meaning: FolderMeaning) {
|
||||
let create_mvbox = true;
|
||||
if let Err(err) = connection
|
||||
.ensure_configured_folders(ctx, create_mvbox)
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
ctx,
|
||||
"Cannot watch {folder_meaning}, ensure_configured_folders() failed: {:#}", err,
|
||||
);
|
||||
connection
|
||||
.fake_idle(ctx, None, FolderMeaning::Unknown)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
let (folder_config, watch_folder) = match convert_folder_meaning(ctx, folder_meaning).await {
|
||||
Ok(meaning) => meaning,
|
||||
Err(error) => {
|
||||
@@ -786,7 +767,7 @@ async fn smtp_loop(
|
||||
// again, we increase the timeout exponentially, in order not to do lots of
|
||||
// unnecessary retries.
|
||||
if let Some(t) = timeout {
|
||||
let now = tools::Time::now();
|
||||
let now = tokio::time::Instant::now();
|
||||
info!(
|
||||
ctx,
|
||||
"smtp has messages to retry, planning to retry {} seconds later", t,
|
||||
@@ -797,7 +778,7 @@ async fn smtp_loop(
|
||||
})
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let slept = time_elapsed(&now).as_secs();
|
||||
let slept = (tokio::time::Instant::now() - now).as_secs();
|
||||
timeout = Some(cmp::max(
|
||||
t,
|
||||
slept.saturating_add(rand::thread_rng().gen_range((slept / 2)..=slept)),
|
||||
|
||||
@@ -314,7 +314,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
};
|
||||
if !token::exists(context, token::Namespace::InviteNumber, invitenumber).await? {
|
||||
if !token::exists(context, token::Namespace::InviteNumber, invitenumber).await {
|
||||
warn!(context, "Secure-join denied (bad invitenumber).");
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
@@ -398,7 +398,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
.await?;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
};
|
||||
if !token::exists(context, token::Namespace::Auth, auth).await? {
|
||||
if !token::exists(context, token::Namespace::Auth, auth).await {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_id,
|
||||
|
||||
13
src/smtp.rs
13
src/smtp.rs
@@ -2,7 +2,7 @@
|
||||
|
||||
pub mod send;
|
||||
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use anyhow::{bail, format_err, Context as _, Error, Result};
|
||||
use async_smtp::response::{Category, Code, Detail};
|
||||
@@ -28,7 +28,6 @@ use crate::scheduler::connectivity::ConnectivityStore;
|
||||
use crate::socks::Socks5Config;
|
||||
use crate::sql;
|
||||
use crate::stock_str::unencrypted_email;
|
||||
use crate::tools::{self, time_elapsed};
|
||||
|
||||
/// SMTP connection, write and read timeout.
|
||||
const SMTP_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
@@ -44,7 +43,7 @@ pub(crate) struct Smtp {
|
||||
/// Timestamp of last successful send/receive network interaction
|
||||
/// (eg connect or send succeeded). On initialization and disconnect
|
||||
/// it is set to None.
|
||||
last_success: Option<tools::Time>,
|
||||
last_success: Option<SystemTime>,
|
||||
|
||||
pub(crate) connectivity: ConnectivityStore,
|
||||
|
||||
@@ -73,7 +72,11 @@ impl Smtp {
|
||||
/// have been successfully used the last 60 seconds
|
||||
pub fn has_maybe_stale_connection(&self) -> bool {
|
||||
if let Some(last_success) = self.last_success {
|
||||
time_elapsed(&last_success).as_secs() > 60
|
||||
SystemTime::now()
|
||||
.duration_since(last_success)
|
||||
.unwrap_or_default()
|
||||
.as_secs()
|
||||
> 60
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -333,7 +336,7 @@ impl Smtp {
|
||||
}
|
||||
|
||||
self.transport = Some(transport);
|
||||
self.last_success = Some(tools::Time::now());
|
||||
self.last_success = Some(SystemTime::now());
|
||||
|
||||
context.emit_event(EventType::SmtpConnected(format!(
|
||||
"SMTP-LOGIN as {} ok",
|
||||
|
||||
@@ -6,7 +6,6 @@ use super::Smtp;
|
||||
use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
use crate::events::EventType;
|
||||
use crate::tools;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
@@ -56,7 +55,7 @@ impl Smtp {
|
||||
format!("Message len={message_len_bytes} was SMTP-sent to {recipients_display}");
|
||||
info!(context, "{info_msg}.");
|
||||
context.emit_event(EventType::SmtpMessageSent(info_msg));
|
||||
self.last_success = Some(tools::Time::now());
|
||||
self.last_success = Some(std::time::SystemTime::now());
|
||||
} else {
|
||||
warn!(
|
||||
context,
|
||||
|
||||
10
src/sql.rs
10
src/sql.rs
@@ -248,7 +248,7 @@ impl Sql {
|
||||
msg.set_text(stock_str::delete_server_turned_off(context).await);
|
||||
add_device_msg(context, None, Some(&mut msg)).await?;
|
||||
context
|
||||
.set_config_internal(Config::DeleteServerAfter, Some("0"))
|
||||
.set_config(Config::DeleteServerAfter, Some("0"))
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
@@ -259,14 +259,12 @@ impl Sql {
|
||||
match blob.recode_to_avatar_size(context).await {
|
||||
Ok(()) => {
|
||||
context
|
||||
.set_config_internal(Config::Selfavatar, Some(&avatar))
|
||||
.set_config(Config::Selfavatar, Some(&avatar))
|
||||
.await?
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(context, "Migrations can't recode avatar, removing. {:#}", e);
|
||||
context
|
||||
.set_config_internal(Config::Selfavatar, None)
|
||||
.await?
|
||||
context.set_config(Config::Selfavatar, None).await?
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -704,7 +702,7 @@ pub async fn housekeeping(context: &Context) -> Result<()> {
|
||||
// Setting `Config::LastHousekeeping` at the beginning avoids endless loops when things do not
|
||||
// work out for whatever reason or are interrupted by the OS.
|
||||
if let Err(e) = context
|
||||
.set_config_internal(Config::LastHousekeeping, Some(&time().to_string()))
|
||||
.set_config(Config::LastHousekeeping, Some(&time().to_string()))
|
||||
.await
|
||||
{
|
||||
warn!(context, "Can't set config: {e:#}.");
|
||||
|
||||
@@ -368,7 +368,7 @@ UPDATE chats SET protected=1, type=120 WHERE type=130;"#,
|
||||
if let Ok(addr) = context.get_primary_self_addr().await {
|
||||
if let Ok(domain) = EmailAddress::new(&addr).map(|email| email.domain) {
|
||||
context
|
||||
.set_config_internal(
|
||||
.set_config(
|
||||
Config::ConfiguredProvider,
|
||||
get_provider_by_domain(&domain).map(|provider| provider.id),
|
||||
)
|
||||
@@ -900,17 +900,6 @@ CREATE INDEX msgs_status_updates_index2 ON msgs_status_updates (uid);
|
||||
.await?;
|
||||
}
|
||||
|
||||
if dbversion < 110 {
|
||||
sql.execute_migration(
|
||||
"ALTER TABLE keypairs ADD COLUMN addr TEXT DEFAULT '' COLLATE NOCASE;
|
||||
ALTER TABLE keypairs ADD COLUMN is_default INTEGER DEFAULT 0;
|
||||
ALTER TABLE keypairs ADD COLUMN created INTEGER DEFAULT 0;
|
||||
UPDATE keypairs SET addr=(SELECT value FROM config WHERE keyname='configured_addr'), is_default=1;",
|
||||
110,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let new_version = sql
|
||||
.get_raw_config_int(VERSION_CFG)
|
||||
.await?
|
||||
|
||||
@@ -424,11 +424,6 @@ pub enum StockMessage {
|
||||
fallback = "⚠️ Your email provider %1$s requires end-to-end encryption which is not setup yet."
|
||||
))]
|
||||
InvalidUnencryptedMail = 174,
|
||||
|
||||
#[strum(props(
|
||||
fallback = "⚠️ It seems you are using Delta Chat on multiple devices that cannot decrypt each other's outgoing messages. To fix this, on the older device use \"Settings / Add Second Device\" and follow the instructions."
|
||||
))]
|
||||
CantDecryptOutgoingMsgs = 175,
|
||||
}
|
||||
|
||||
impl StockMessage {
|
||||
@@ -755,11 +750,6 @@ pub(crate) async fn cant_decrypt_msg_body(context: &Context) -> String {
|
||||
translated(context, StockMessage::CantDecryptMsgBody).await
|
||||
}
|
||||
|
||||
/// Stock string:`Got outgoing message(s) encrypted for another setup...`.
|
||||
pub(crate) async fn cant_decrypt_outgoing_msgs(context: &Context) -> String {
|
||||
translated(context, StockMessage::CantDecryptOutgoingMsgs).await
|
||||
}
|
||||
|
||||
/// Stock string: `Fingerprints`.
|
||||
pub(crate) async fn finger_prints(context: &Context) -> String {
|
||||
translated(context, StockMessage::FingerPrints).await
|
||||
|
||||
14
src/sync.rs
14
src/sync.rs
@@ -514,7 +514,7 @@ mod tests {
|
||||
async fn test_execute_sync_items() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
assert!(!token::exists(&t, Namespace::Auth, "yip-auth").await?);
|
||||
assert!(!token::exists(&t, Namespace::Auth, "yip-auth").await);
|
||||
|
||||
let sync_items = t
|
||||
.parse_sync_items(
|
||||
@@ -537,10 +537,10 @@ mod tests {
|
||||
.await?
|
||||
.is_none()
|
||||
);
|
||||
assert!(token::exists(&t, Namespace::InviteNumber, "yip-in").await?);
|
||||
assert!(token::exists(&t, Namespace::Auth, "yip-auth").await?);
|
||||
assert!(!token::exists(&t, Namespace::Auth, "non-existent").await?);
|
||||
assert!(!token::exists(&t, Namespace::Auth, "directly deleted").await?);
|
||||
assert!(token::exists(&t, Namespace::InviteNumber, "yip-in").await);
|
||||
assert!(token::exists(&t, Namespace::Auth, "yip-auth").await);
|
||||
assert!(!token::exists(&t, Namespace::Auth, "non-existent").await);
|
||||
assert!(!token::exists(&t, Namespace::Auth, "directly deleted").await);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -577,13 +577,13 @@ mod tests {
|
||||
let alice2 = TestContext::new_alice().await;
|
||||
alice2.set_config_bool(Config::SyncMsgs, true).await?;
|
||||
alice2.recv_msg(&sent_msg).await;
|
||||
assert!(token::exists(&alice2, token::Namespace::Auth, "testtoken").await?);
|
||||
assert!(token::exists(&alice2, token::Namespace::Auth, "testtoken").await);
|
||||
assert_eq!(Chatlist::try_load(&alice2, 0, None, None).await?.len(), 0);
|
||||
|
||||
// the same sync message sent to bob must not be executed
|
||||
let bob = TestContext::new_bob().await;
|
||||
bob.recv_msg(&sent_msg).await;
|
||||
assert!(!token::exists(&bob, token::Namespace::Auth, "testtoken").await?);
|
||||
assert!(!token::exists(&bob, token::Namespace::Auth, "testtoken").await);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -103,15 +103,15 @@ pub async fn lookup_or_new(
|
||||
token
|
||||
}
|
||||
|
||||
pub async fn exists(context: &Context, namespace: Namespace, token: &str) -> Result<bool> {
|
||||
let exists = context
|
||||
pub async fn exists(context: &Context, namespace: Namespace, token: &str) -> bool {
|
||||
context
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT COUNT(*) FROM tokens WHERE namespc=? AND token=?;",
|
||||
(namespace, token),
|
||||
)
|
||||
.await?;
|
||||
Ok(exists)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub async fn delete(context: &Context, namespace: Namespace, token: &str) -> Result<()> {
|
||||
|
||||
11
src/tools.rs
11
src/tools.rs
@@ -9,13 +9,6 @@ use std::io::{Cursor, Write};
|
||||
use std::mem;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::from_utf8;
|
||||
// If a time value doesn't need to be sent to another host, saved to the db or otherwise used across
|
||||
// program restarts, a monotonically nondecreasing clock (`Instant`) should be used. But as
|
||||
// `Instant` may use `libc::clock_gettime(CLOCK_MONOTONIC)`, e.g. on Android, and does not advance
|
||||
// while being in deep sleep mode, we use `SystemTime` instead, but add an alias for it to document
|
||||
// why `Instant` isn't used in those places. Also this can help to switch to another clock impl if
|
||||
// we find any.
|
||||
pub use std::time::SystemTime as Time;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
@@ -489,10 +482,6 @@ pub(crate) fn time() -> i64 {
|
||||
.as_secs() as i64
|
||||
}
|
||||
|
||||
pub(crate) fn time_elapsed(time: &Time) -> Duration {
|
||||
time.elapsed().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Struct containing all mailto information
|
||||
#[derive(Debug, Default, Eq, PartialEq)]
|
||||
pub struct MailTo {
|
||||
|
||||
@@ -9,6 +9,8 @@ User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101
|
||||
Content-Language: en-US
|
||||
To: bob@example.net
|
||||
From: Alice <alice@example.org>
|
||||
X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0;
|
||||
attachmentreminder=0; deliveryformat=0
|
||||
X-Identity-Key: id3
|
||||
Fcc: imap://alice%40example.org@in.example.org/Sent
|
||||
Subject: ...
|
||||
|
||||
Reference in New Issue
Block a user