diff --git a/CHANGELOG.md b/CHANGELOG.md index 29bd7b02a..21bafce20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,18 +2,32 @@ ## Unreleased -## Changes +### Changes +- deltachat-rpc-client: use `dataclass` for `Account`, `Chat`, `Contact` and `Message` #4042 + +### Fixes +- deltachat-rpc-server: do not block stdin while processing the request. #4041 + deltachat-rpc-server now reads the next request as soon as previous request handler is spawned. + +### API-Changes +- Remove `MimeMessage::from_bytes()` public interface. #4033 + + +## 1.108.0 + +### Changes - Use read/write timeouts instead of per-command timeouts for SMTP #3985 - Cache DNS results for SMTP connections #3985 - Prefer TLS over STARTTLS during autoconfiguration #4021 - Use SOCKS5 configuration for HTTP requests #4017 +- Show non-deltachat emails by default for new installations #4019 -## Fixes +### Fixes - Fix Securejoin for multiple devices on a joining side #3982 - python: handle NULL value returned from `dc_get_msg()` #4020 Account.`get_message_by_id` may return `None` in this case. -## API-Changes +### API-Changes - Remove bitflags from `get_chat_msgs()` interface #4022 C interface is not changed. Rust and JSON-RPC API have `flags` integer argument diff --git a/Cargo.lock b/Cargo.lock index f58d338ee..31f6bd7e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1053,7 +1053,7 @@ checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" [[package]] name = "deltachat" -version = "1.107.0" +version = "1.108.0" dependencies = [ "ansi_term", "anyhow", @@ -1128,7 +1128,7 @@ dependencies = [ [[package]] name = "deltachat-jsonrpc" -version = "1.107.0" +version = "1.108.0" dependencies = [ "anyhow", "async-channel", @@ -1150,7 +1150,7 @@ dependencies = [ [[package]] name = "deltachat-repl" -version = "1.107.0" +version = "1.108.0" dependencies = [ "ansi_term", "anyhow", @@ -1166,7 +1166,7 @@ dependencies = [ [[package]] name = "deltachat-rpc-server" -version = "1.107.0" +version = "1.108.0" dependencies = [ "anyhow", "deltachat-jsonrpc", @@ -1189,7 +1189,7 @@ dependencies = [ [[package]] name = "deltachat_ffi" -version = "1.107.0" +version = "1.108.0" dependencies = [ "anyhow", "deltachat", diff --git a/Cargo.toml b/Cargo.toml index 4b45d003e..2d8f812bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deltachat" -version = "1.107.0" +version = "1.108.0" edition = "2021" license = "MPL-2.0" rust-version = "1.63" diff --git a/deltachat-ffi/Cargo.toml b/deltachat-ffi/Cargo.toml index ca45bf3e1..1e0891cea 100644 --- a/deltachat-ffi/Cargo.toml +++ b/deltachat-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deltachat_ffi" -version = "1.107.0" +version = "1.108.0" description = "Deltachat FFI" edition = "2018" readme = "README.md" diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 8828e388b..1b1187bd7 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -1160,7 +1160,7 @@ uint32_t dc_add_device_msg (dc_context_t* context, const char* /** * Check if a device-message with a given label was ever added. - * Device-messages can be added dc_add_device_msg(). + * Device-messages can be added with dc_add_device_msg(). * * @memberof dc_context_t * @param context The context object. diff --git a/deltachat-ffi/src/lot.rs b/deltachat-ffi/src/lot.rs index 612f7d1cd..52b5a1e44 100644 --- a/deltachat-ffi/src/lot.rs +++ b/deltachat-ffi/src/lot.rs @@ -24,20 +24,15 @@ pub enum Lot { } #[repr(u8)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub enum Meaning { + #[default] None = 0, Text1Draft = 1, Text1Username = 2, Text1Self = 3, } -impl Default for Meaning { - fn default() -> Self { - Meaning::None - } -} - impl Lot { pub fn get_text1(&self) -> Option<&str> { match self { @@ -156,9 +151,9 @@ impl Lot { } #[repr(u32)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub enum LotState { - // Default + #[default] Undefined = 0, // Qr States @@ -222,12 +217,6 @@ pub enum LotState { MsgOutMdnRcvd = 28, } -impl Default for LotState { - fn default() -> Self { - LotState::Undefined - } -} - impl From for LotState { fn from(s: MessageState) -> Self { use MessageState::*; diff --git a/deltachat-jsonrpc/Cargo.toml b/deltachat-jsonrpc/Cargo.toml index 16360b2f8..0ca66a9e5 100644 --- a/deltachat-jsonrpc/Cargo.toml +++ b/deltachat-jsonrpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deltachat-jsonrpc" -version = "1.107.0" +version = "1.108.0" description = "DeltaChat JSON-RPC API" edition = "2021" default-run = "deltachat-jsonrpc-server" diff --git a/deltachat-jsonrpc/src/api/mod.rs b/deltachat-jsonrpc/src/api/mod.rs index a2a1aaf2e..ddb4d193f 100644 --- a/deltachat-jsonrpc/src/api/mod.rs +++ b/deltachat-jsonrpc/src/api/mod.rs @@ -86,6 +86,11 @@ impl CommandApi { #[rpc(all_positional, ts_outdir = "typescript/generated")] impl CommandApi { + /// Test function. + async fn sleep(&self, delay: f64) { + tokio::time::sleep(std::time::Duration::from_secs_f64(delay)).await + } + // --------------------------------------------- // Misc top level functions // --------------------------------------------- diff --git a/deltachat-jsonrpc/typescript/example/example.ts b/deltachat-jsonrpc/typescript/example/example.ts index 15baf68b5..c180a9e31 100644 --- a/deltachat-jsonrpc/typescript/example/example.ts +++ b/deltachat-jsonrpc/typescript/example/example.ts @@ -68,10 +68,7 @@ async function run() { null ); for (const [chatId, _messageId] of chats) { - const chat = await client.rpc.getFullChatById( - selectedAccount, - chatId - ); + const chat = await client.rpc.getFullChatById(selectedAccount, chatId); write($main, `

${chat.name}

`); const messageIds = await client.rpc.getMessageIds( selectedAccount, diff --git a/deltachat-jsonrpc/typescript/example/node-add-account.js b/deltachat-jsonrpc/typescript/example/node-add-account.js index c553ad669..9d3e4a40a 100644 --- a/deltachat-jsonrpc/typescript/example/node-add-account.js +++ b/deltachat-jsonrpc/typescript/example/node-add-account.js @@ -3,24 +3,27 @@ import { DeltaChat } from "../dist/deltachat.js"; run().catch(console.error); async function run() { - const delta = new DeltaChat('ws://localhost:20808/ws'); + const delta = new DeltaChat("ws://localhost:20808/ws"); delta.on("event", (event) => { console.log("event", event.data); }); - const email = process.argv[2] - const password = process.argv[3] - if (!email || !password) throw new Error('USAGE: node node-add-account.js ') - console.log(`creating acccount for ${email}`) - const id = await delta.rpc.addAccount() - console.log(`created account id ${id}`) + const email = process.argv[2]; + const password = process.argv[3]; + if (!email || !password) + throw new Error( + "USAGE: node node-add-account.js " + ); + console.log(`creating acccount for ${email}`); + const id = await delta.rpc.addAccount(); + console.log(`created account id ${id}`); await delta.rpc.setConfig(id, "addr", email); await delta.rpc.setConfig(id, "mail_pw", password); - console.log('configuration updated') - await delta.rpc.configure(id) - console.log('account configured!') + console.log("configuration updated"); + await delta.rpc.configure(id); + console.log("account configured!"); const accounts = await delta.rpc.getAllAccounts(); console.log("accounts", accounts); - console.log("waiting for events...") + console.log("waiting for events..."); } diff --git a/deltachat-jsonrpc/typescript/example/node-demo.js b/deltachat-jsonrpc/typescript/example/node-demo.js index dcf96a4df..83ea89b3f 100644 --- a/deltachat-jsonrpc/typescript/example/node-demo.js +++ b/deltachat-jsonrpc/typescript/example/node-demo.js @@ -10,5 +10,5 @@ async function run() { const accounts = await delta.rpc.getAllAccounts(); console.log("accounts", accounts); - console.log("waiting for events...") + console.log("waiting for events..."); } diff --git a/deltachat-jsonrpc/typescript/package.json b/deltachat-jsonrpc/typescript/package.json index 114f9824d..e93c1f6b3 100644 --- a/deltachat-jsonrpc/typescript/package.json +++ b/deltachat-jsonrpc/typescript/package.json @@ -38,8 +38,8 @@ "example:start": "http-server .", "extract-constants": "node ./scripts/generate-constants.js", "generate-bindings": "cargo test", - "prettier:check": "prettier --check **.ts", - "prettier:fix": "prettier --write **.ts", + "prettier:check": "prettier --check .", + "prettier:fix": "prettier --write .", "test": "run-s test:prepare test:run-coverage test:report-coverage", "test:prepare": "cargo build --package deltachat-rpc-server --bin deltachat-rpc-server", "test:report-coverage": "node report_api_coverage.mjs", @@ -48,5 +48,5 @@ }, "type": "module", "types": "dist/deltachat.d.ts", - "version": "1.107.0" -} \ No newline at end of file + "version": "1.108.0" +} diff --git a/deltachat-jsonrpc/typescript/src/client.ts b/deltachat-jsonrpc/typescript/src/client.ts index 2ed6435ff..9efbab964 100644 --- a/deltachat-jsonrpc/typescript/src/client.ts +++ b/deltachat-jsonrpc/typescript/src/client.ts @@ -43,7 +43,7 @@ export class BaseDeltaChat< const method = request.method; if (method === "event") { const event = request.params! as DCWireEvent; - //@ts-ignore + //@ts-ignore this.emit(event.event.type, event.contextId, event.event as any); this.emit("ALL", event.contextId, event.event as any); diff --git a/deltachat-jsonrpc/typescript/test/basic.ts b/deltachat-jsonrpc/typescript/test/basic.ts index 2df048d48..f09ed6258 100644 --- a/deltachat-jsonrpc/typescript/test/basic.ts +++ b/deltachat-jsonrpc/typescript/test/basic.ts @@ -4,10 +4,7 @@ import chaiAsPromised from "chai-as-promised"; chai.use(chaiAsPromised); import { StdioDeltaChat as DeltaChat } from "../deltachat.js"; -import { - RpcServerHandle, - startServer, -} from "./test_base.js"; +import { RpcServerHandle, startServer } from "./test_base.js"; describe("basic tests", () => { let serverHandle: RpcServerHandle; @@ -15,9 +12,9 @@ describe("basic tests", () => { before(async () => { serverHandle = await startServer(); - dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout) + dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout); // dc.on("ALL", (event) => { - //console.log("event", event); + //console.log("event", event); // }); }); @@ -111,8 +108,8 @@ describe("basic tests", () => { assert((await dc.rpc.getConfig(accountId, "addr")) == "valid@email"); }); it("set invalid key should throw", async function () { - await expect(dc.rpc.setConfig(accountId, "invalid_key", "some value")).to.be - .eventually.rejected; + await expect(dc.rpc.setConfig(accountId, "invalid_key", "some value")).to + .be.eventually.rejected; }); it("get invalid key should throw", async function () { await expect(dc.rpc.getConfig(accountId, "invalid_key")).to.be.eventually @@ -125,7 +122,10 @@ describe("basic tests", () => { it("set and retrive (batch)", async function () { const config = { addr: "valid@email", mail_pw: "1234" }; await dc.rpc.batchSetConfig(accountId, config); - const retrieved = await dc.rpc.batchGetConfig(accountId, Object.keys(config)); + const retrieved = await dc.rpc.batchGetConfig( + accountId, + Object.keys(config) + ); expect(retrieved).to.deep.equal(config); }); it("set and retrive ui.* (batch)", async function () { @@ -134,7 +134,10 @@ describe("basic tests", () => { "ui.enter_key_sends": "true", }; await dc.rpc.batchSetConfig(accountId, config); - const retrieved = await dc.rpc.batchGetConfig(accountId, Object.keys(config)); + const retrieved = await dc.rpc.batchGetConfig( + accountId, + Object.keys(config) + ); expect(retrieved).to.deep.equal(config); }); it("set and retrive mixed(ui and core) (batch)", async function () { @@ -145,7 +148,10 @@ describe("basic tests", () => { mail_pw: "123456", }; await dc.rpc.batchSetConfig(accountId, config); - const retrieved = await dc.rpc.batchGetConfig(accountId, Object.keys(config)); + const retrieved = await dc.rpc.batchGetConfig( + accountId, + Object.keys(config) + ); expect(retrieved).to.deep.equal(config); }); }); diff --git a/deltachat-jsonrpc/typescript/test/test_base.ts b/deltachat-jsonrpc/typescript/test/test_base.ts index 66bc0d72d..e8f604632 100644 --- a/deltachat-jsonrpc/typescript/test/test_base.ts +++ b/deltachat-jsonrpc/typescript/test/test_base.ts @@ -59,13 +59,13 @@ export async function startServer(): Promise { export async function createTempUser(url: string) { const response = await fetch(url, { - method: "POST", + method: "POST", headers: { "cache-control": "no-cache", }, }); - if (!response.ok) throw new Error('Received invalid response') - return response.json(); + if (!response.ok) throw new Error("Received invalid response"); + return response.json(); } function getTargetDir(): Promise { @@ -89,4 +89,3 @@ function getTargetDir(): Promise { ); }); } - diff --git a/deltachat-repl/Cargo.toml b/deltachat-repl/Cargo.toml index 87e3c195f..678d74db1 100644 --- a/deltachat-repl/Cargo.toml +++ b/deltachat-repl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deltachat-repl" -version = "1.107.0" +version = "1.108.0" edition = "2021" [dependencies] diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/account.py b/deltachat-rpc-client/src/deltachat_rpc_client/account.py index 44535da08..63a9b169c 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/account.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/account.py @@ -1,4 +1,5 @@ from typing import TYPE_CHECKING, List, Optional, Tuple, Union +from dataclasses import dataclass from ._utils import AttrDict from .chat import Chat @@ -11,28 +12,17 @@ if TYPE_CHECKING: from .deltachat import DeltaChat +@dataclass class Account: """Delta Chat account.""" - def __init__(self, manager: "DeltaChat", account_id: int) -> None: - self.manager = manager - self.id = account_id + manager: "DeltaChat" + id: int @property def _rpc(self) -> Rpc: return self.manager.rpc - def __eq__(self, other) -> bool: - if not isinstance(other, Account): - return False - return self.id == other.id and self.manager == other.manager - - def __ne__(self, other) -> bool: - return not self == other - - def __repr__(self) -> str: - return f"" - async def wait_for_event(self) -> AttrDict: """Wait until the next event and return it.""" return AttrDict(await self._rpc.wait_for_event(self.id)) diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/chat.py b/deltachat-rpc-client/src/deltachat_rpc_client/chat.py index 73adbfb1c..c2e5ca364 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/chat.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/chat.py @@ -1,6 +1,7 @@ import calendar from datetime import datetime from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union +from dataclasses import dataclass from ._utils import AttrDict from .const import ChatVisibility @@ -12,28 +13,17 @@ if TYPE_CHECKING: from .account import Account +@dataclass class Chat: """Chat object which manages members and through which you can send and retrieve messages.""" - def __init__(self, account: "Account", chat_id: int) -> None: - self.account = account - self.id = chat_id + account: "Account" + id: int @property def _rpc(self) -> Rpc: return self.account._rpc - def __eq__(self, other) -> bool: - if not isinstance(other, Chat): - return False - return self.id == other.id and self.account == other.account - - def __ne__(self, other) -> bool: - return not self == other - - def __repr__(self) -> str: - return f"" - async def delete(self) -> None: """Delete this chat and all its messages. diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/contact.py b/deltachat-rpc-client/src/deltachat_rpc_client/contact.py index 7c5267b4f..7999d59ed 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/contact.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/contact.py @@ -1,4 +1,5 @@ from typing import TYPE_CHECKING +from dataclasses import dataclass from ._utils import AttrDict from .rpc import Rpc @@ -8,6 +9,7 @@ if TYPE_CHECKING: from .chat import Chat +@dataclass class Contact: """ Contact API. @@ -15,20 +17,8 @@ class Contact: Essentially a wrapper for RPC, account ID and a contact ID. """ - def __init__(self, account: "Account", contact_id: int) -> None: - self.account = account - self.id = contact_id - - def __eq__(self, other) -> bool: - if not isinstance(other, Contact): - return False - return self.id == other.id and self.account == other.account - - def __ne__(self, other) -> bool: - return not self == other - - def __repr__(self) -> str: - return f"" + account: "Account" + id: int @property def _rpc(self) -> Rpc: diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/message.py b/deltachat-rpc-client/src/deltachat_rpc_client/message.py index d38d2cbfe..7784d8dac 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/message.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/message.py @@ -1,5 +1,6 @@ import json from typing import TYPE_CHECKING, Union +from dataclasses import dataclass from ._utils import AttrDict from .contact import Contact @@ -9,23 +10,12 @@ if TYPE_CHECKING: from .account import Account +@dataclass class Message: """Delta Chat Message object.""" - def __init__(self, account: "Account", msg_id: int) -> None: - self.account = account - self.id = msg_id - - def __eq__(self, other) -> bool: - if not isinstance(other, Message): - return False - return self.id == other.id and self.account == other.account - - def __ne__(self, other) -> bool: - return not self == other - - def __repr__(self) -> str: - return f"" + account: "Account" + id: int @property def _rpc(self) -> Rpc: diff --git a/deltachat-rpc-client/tests/test_something.py b/deltachat-rpc-client/tests/test_something.py index 83a783f67..47b4cf8e3 100644 --- a/deltachat-rpc-client/tests/test_something.py +++ b/deltachat-rpc-client/tests/test_something.py @@ -1,6 +1,7 @@ from unittest.mock import MagicMock import pytest +import asyncio from deltachat_rpc_client import EventType, events from deltachat_rpc_client.rpc import JsonRpcError @@ -13,6 +14,17 @@ async def test_system_info(rpc) -> None: assert "deltachat_core_version" in system_info +@pytest.mark.asyncio() +async def test_sleep(rpc) -> None: + """Test that long-running task does not block short-running task from completion.""" + sleep_5_task = asyncio.create_task(rpc.sleep(5.0)) + sleep_3_task = asyncio.create_task(rpc.sleep(3.0)) + done, pending = await asyncio.wait([sleep_5_task, sleep_3_task], return_when=asyncio.FIRST_COMPLETED) + assert sleep_3_task in done + assert sleep_5_task in pending + sleep_5_task.cancel() + + @pytest.mark.asyncio() async def test_email_address_validity(rpc) -> None: valid_addresses = [ diff --git a/deltachat-rpc-server/Cargo.toml b/deltachat-rpc-server/Cargo.toml index 2cda17b60..f678e2cb0 100644 --- a/deltachat-rpc-server/Cargo.toml +++ b/deltachat-rpc-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deltachat-rpc-server" -version = "1.107.0" +version = "1.108.0" description = "DeltaChat JSON-RPC server" edition = "2021" readme = "README.md" diff --git a/deltachat-rpc-server/src/bin/deltachat-rpc-server/main.rs b/deltachat-rpc-server/src/bin/deltachat-rpc-server/main.rs index 503fed392..78ee49c37 100644 --- a/deltachat-rpc-server/src/bin/deltachat-rpc-server/main.rs +++ b/deltachat-rpc-server/src/bin/deltachat-rpc-server/main.rs @@ -51,7 +51,10 @@ async fn main() -> Result<()> { let mut lines = BufReader::new(stdin).lines(); while let Some(message) = lines.next_line().await? { log::trace!("RPC recv {}", message); - session.handle_incoming(&message).await; + let session = session.clone(); + tokio::spawn(async move { + session.handle_incoming(&message).await; + }); } log::info!("EOF reached on stdin"); Ok(()) diff --git a/package.json b/package.json index 542dd1c4c..e028cd28b 100644 --- a/package.json +++ b/package.json @@ -60,5 +60,5 @@ "test:mocha": "mocha -r esm node/test/test.js --growl --reporter=spec --bail --exit" }, "types": "node/dist/index.d.ts", - "version": "1.107.0" + "version": "1.108.0" } \ No newline at end of file diff --git a/scripts/set_core_version.py b/scripts/set_core_version.py index 8046081fa..f3f29bbac 100755 --- a/scripts/set_core_version.py +++ b/scripts/set_core_version.py @@ -69,6 +69,7 @@ def main(): "deltachat-ffi/Cargo.toml", "deltachat-jsonrpc/Cargo.toml", "deltachat-rpc-server/Cargo.toml", + "deltachat-repl/Cargo.toml", ] try: opts = parser.parse_args() diff --git a/src/aheader.rs b/src/aheader.rs index 07f18d963..e471634b8 100644 --- a/src/aheader.rs +++ b/src/aheader.rs @@ -11,20 +11,15 @@ use anyhow::{bail, Context as _, Error, Result}; use crate::key::{DcKey, SignedPublicKey}; /// Possible values for encryption preference -#[derive(PartialEq, Eq, Debug, Clone, Copy, FromPrimitive, ToPrimitive)] +#[derive(PartialEq, Eq, Debug, Default, Clone, Copy, FromPrimitive, ToPrimitive)] #[repr(u8)] pub enum EncryptPreference { + #[default] NoPreference = 0, Mutual = 1, Reset = 20, } -impl Default for EncryptPreference { - fn default() -> Self { - EncryptPreference::NoPreference - } -} - impl fmt::Display for EncryptPreference { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match *self { diff --git a/src/chat.rs b/src/chat.rs index 6d23b2048..0017eeabc 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -1,7 +1,5 @@ //! # Chat module. -#![allow(missing_docs)] - use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; use std::fmt; @@ -60,8 +58,10 @@ pub enum ChatItem { }, } +/// Chat protection status. #[derive( Debug, + Default, Display, Clone, Copy, @@ -77,14 +77,14 @@ pub enum ChatItem { )] #[repr(u32)] pub enum ProtectionStatus { + /// Chat is not protected. + #[default] Unprotected = 0, - Protected = 1, -} -impl Default for ProtectionStatus { - fn default() -> Self { - ProtectionStatus::Unprotected - } + /// Chat is protected. + /// + /// All members of the chat must be verified. + Protected = 1, } /// The reason why messages cannot be sent to the chat. @@ -295,7 +295,7 @@ impl ChatId { Ok(chat_id) } - pub async fn set_selfavatar_timestamp(self, context: &Context, timestamp: i64) -> Result<()> { + async fn set_selfavatar_timestamp(self, context: &Context, timestamp: i64) -> Result<()> { context .sql .execute( @@ -680,6 +680,7 @@ impl ChatId { Ok(()) } + /// Returns ID of the draft message, if there is one. async fn get_draft_msg_id(self, context: &Context) -> Result> { let msg_id: Option = context .sql @@ -691,6 +692,7 @@ impl ChatId { Ok(msg_id) } + /// Returns draft message, if there is one. pub async fn get_draft(self, context: &Context) -> Result> { if self.is_special() { return Ok(None); @@ -704,7 +706,7 @@ impl ChatId { } } - /// Delete draft message in specified chat, if there is one. + /// Deletes draft message, if there is one. /// /// Returns `true`, if message was deleted, `false` otherwise. async fn maybe_delete_draft(self, context: &Context) -> Result { @@ -826,6 +828,7 @@ impl ChatId { Ok(count) } + /// Returns the number of fresh messages in the chat. pub async fn get_fresh_msg_cnt(self, context: &Context) -> Result { // this function is typically used to show a badge counter beside _each_ chatlist item. // to make this as fast as possible, esp. on older devices, we added an combined index over the rows used for querying. @@ -892,7 +895,7 @@ impl ChatId { Ok(promoted) } - // Returns true if chat is a saved messages chat. + /// Returns true if chat is a saved messages chat. pub async fn is_self_talk(self, context: &Context) -> Result { Ok(self.get_param(context).await?.exists(Param::Selftalk)) } @@ -1130,15 +1133,29 @@ pub struct Chat { /// Chat type, e.g. 1:1 chat, group chat, mailing list. pub typ: Chattype, + + /// Chat name. pub name: String, + + /// Whether the chat is archived or pinned. pub visibility: ChatVisibility, /// Group ID. pub grpid: String, + + /// Whether the chat is blocked, unblocked or a contact request. pub(crate) blocked: Blocked, + + /// Additional chat parameters stored in the database. pub param: Params, + + /// If location streaming is enabled in the chat. is_sending_locations: bool, + + /// Duration of the chat being muted. pub mute_duration: MuteDuration, + + /// If the chat is protected (verified). protected: ProtectionStatus, } @@ -1258,7 +1275,7 @@ impl Chat { } } - pub async fn update_param(&mut self, context: &Context) -> Result<()> { + pub(crate) async fn update_param(&mut self, context: &Context) -> Result<()> { context .sql .execute( @@ -1314,6 +1331,10 @@ impl Chat { Ok(None) } + /// Returns chat avatar color. + /// + /// For 1:1 chats, the color is calculated from the contact's address. + /// For group chats the color is calculated from the chat name. pub async fn get_color(&self, context: &Context) -> Result { let mut color = 0; @@ -1360,6 +1381,7 @@ impl Chat { }) } + /// Returns chat visibilitiy, e.g. whether it is archived or pinned. pub fn get_visibility(&self) -> ChatVisibility { self.visibility } @@ -1372,10 +1394,12 @@ impl Chat { self.blocked == Blocked::Request } + /// Returns true if the chat is not promoted. pub fn is_unpromoted(&self) -> bool { self.param.get_bool(Param::Unpromoted).unwrap_or_default() } + /// Returns true if the chat is promoted. pub fn is_promoted(&self) -> bool { !self.is_unpromoted() } @@ -1390,6 +1414,7 @@ impl Chat { self.is_sending_locations } + /// Returns true if the chat is currently muted. pub fn is_muted(&self) -> bool { match self.mute_duration { MuteDuration::NotMuted => false, @@ -1974,6 +1999,7 @@ impl ChatIdBlocked { } } +/// Prepares a message for sending. pub async fn prepare_msg(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result { ensure!( !chat_id.is_special(), @@ -2331,6 +2357,9 @@ async fn create_send_msg_job(context: &Context, msg_id: MsgId) -> Result, @@ -2706,10 +2741,14 @@ pub async fn get_chat_media( #[derive(Debug, Clone, PartialEq, Eq)] #[repr(i32)] pub enum Direction { + /// Search forward. Forward = 1, + + /// Search backward. Backward = -1, } +/// Searches next/previous message based on the given message and list of types. pub async fn get_next_media( context: &Context, curr_msg_id: MsgId, @@ -3400,6 +3439,9 @@ pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) Ok(()) } +/// Resends given messages with the same Message-ID. +/// +/// This is primarily intended to make existing webxdcs available to new chat members. pub async fn resend_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> { let mut chat_id = None; let mut msgs: Vec = Vec::new(); @@ -3598,6 +3640,7 @@ pub async fn add_device_msg( add_device_msg_with_importance(context, label, msg, false).await } +/// Returns true if device message with a given label was ever added to the device chat. pub async fn was_device_msg_ever_added(context: &Context, label: &str) -> Result { ensure!(!label.is_empty(), "empty label"); let exists = context @@ -5086,9 +5129,6 @@ mod tests { let alice = TestContext::new_alice().await; let bob = TestContext::new_bob().await; - alice.set_config(Config::ShowEmails, Some("2")).await?; - bob.set_config(Config::ShowEmails, Some("2")).await?; - let alice_bob_contact = alice.add_or_lookup_contact(&bob).await; let contact_id = alice_bob_contact.id; let alice_chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "grp").await?; @@ -5273,12 +5313,6 @@ mod tests { async fn test_classic_email_chat() -> Result<()> { let alice = TestContext::new_alice().await; - // Alice enables receiving classic emails. - alice - .set_config(Config::ShowEmails, Some("2")) - .await - .unwrap(); - // Alice receives a classic (non-chat) message from Bob. receive_imf( &alice, diff --git a/src/config.rs b/src/config.rs index 40aa43f16..91cc37e94 100644 --- a/src/config.rs +++ b/src/config.rs @@ -103,18 +103,26 @@ pub enum Config { /// Own avatar filename. Selfavatar, + /// Send BCC copy to self. + /// + /// Should be enabled for multidevice setups. #[strum(props(default = "1"))] BccSelf, + /// True if encryption is preferred according to Autocrypt standard. #[strum(props(default = "1"))] E2eeEnabled, + /// True if Message Delivery Notifications (read receipts) should + /// be sent and requested. #[strum(props(default = "1"))] MdnsEnabled, + /// True if "Sent" folder should be watched for changes. #[strum(props(default = "0"))] SentboxWatch, + /// True if chat messages should be moved to a separate folder. #[strum(props(default = "1"))] MvboxMove, @@ -125,9 +133,11 @@ pub enum Config { #[strum(props(default = "0"))] OnlyFetchMvbox, - #[strum(props(default = "0"))] // also change ShowEmails.default() on changes + /// Whether to show classic emails or only chat messages. + #[strum(props(default = "2"))] // also change ShowEmails.default() on changes ShowEmails, + /// Quality of the media files to send. #[strum(props(default = "0"))] // also change MediaQuality.default() on changes MediaQuality, @@ -142,6 +152,7 @@ pub enum Config { #[strum(props(default = "1"))] FetchedExistingMsgs, + /// Type of the OpenPGP key to generate. #[strum(props(default = "0"))] KeyGenType, @@ -164,7 +175,9 @@ pub enum Config { #[strum(props(default = "0"))] DeleteDeviceAfter, + /// Save raw MIME messages with headers in the database if true. SaveMimeHeaders, + /// The primary email address. Also see `SecondaryAddrs`. ConfiguredAddr, @@ -196,14 +209,19 @@ pub enum Config { /// Configured SMTP server port. ConfiguredSendPort, ConfiguredSmtpCertificateChecks, + + /// Whether OAuth 2 is used with configured provider. ConfiguredServerFlags, ConfiguredSendSecurity, - ConfiguredE2EEEnabled, ConfiguredInboxFolder, ConfiguredMvboxFolder, ConfiguredSentboxFolder, ConfiguredTimestamp, + + /// ID of the configured provider from the provider database. ConfiguredProvider, + + /// True if account is configured. Configured, /// All secondary self addresses separated by spaces @@ -219,6 +237,7 @@ pub enum Config { #[strum(serialize = "sys.config_keys")] SysConfigKeys, + /// True if it is a bot account. Bot, /// Whether we send a warning if the password is wrong (set to false when we send a warning @@ -293,28 +312,33 @@ impl Context { } } + /// Returns 32-bit signed integer configuration value for the given key. pub async fn get_config_int(&self, key: Config) -> Result { self.get_config(key) .await .map(|s: Option| s.and_then(|s| s.parse().ok()).unwrap_or_default()) } + /// Returns 64-bit signed integer configuration value for the given key. pub async fn get_config_i64(&self, key: Config) -> Result { self.get_config(key) .await .map(|s: Option| s.and_then(|s| s.parse().ok()).unwrap_or_default()) } + /// Returns 64-bit unsigned integer configuration value for the given key. pub async fn get_config_u64(&self, key: Config) -> Result { self.get_config(key) .await .map(|s: Option| s.and_then(|s| s.parse().ok()).unwrap_or_default()) } + /// Returns boolean configuration value for the given key. pub async fn get_config_bool(&self, key: Config) -> Result { Ok(self.get_config_int(key).await? != 0) } + /// Returns true if movebox ("DeltaChat" folder) should be watched. pub(crate) async fn should_watch_mvbox(&self) -> Result { Ok(self.get_config_bool(Config::MvboxMove).await? || self.get_config_bool(Config::OnlyFetchMvbox).await?) diff --git a/src/constants.rs b/src/constants.rs index 8f76c68ac..f83f7d519 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -12,6 +12,7 @@ pub static DC_VERSION_STR: Lazy = Lazy::new(|| env!("CARGO_PKG_VERSION") #[derive( Debug, + Default, Display, Clone, Copy, @@ -26,81 +27,56 @@ pub static DC_VERSION_STR: Lazy = Lazy::new(|| env!("CARGO_PKG_VERSION") )] #[repr(i8)] pub enum Blocked { + #[default] Not = 0, Yes = 1, Request = 2, } -impl Default for Blocked { - fn default() -> Self { - Blocked::Not - } -} - #[derive( - Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql, + Debug, Default, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql, )] #[repr(u8)] pub enum ShowEmails { Off = 0, AcceptedContacts = 1, + #[default] // also change Config.ShowEmails props(default) on changes All = 2, } -impl Default for ShowEmails { - fn default() -> Self { - ShowEmails::Off // also change Config.ShowEmails props(default) on changes - } -} - #[derive( - Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql, + Debug, Default, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql, )] #[repr(u8)] pub enum MediaQuality { + #[default] // also change Config.MediaQuality props(default) on changes Balanced = 0, Worse = 1, } -impl Default for MediaQuality { - fn default() -> Self { - MediaQuality::Balanced // also change Config.MediaQuality props(default) on changes - } -} - /// Type of the key to generate. #[derive( - Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql, + Debug, Default, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql, )] #[repr(u8)] pub enum KeyGenType { + #[default] Default = 0, Rsa2048 = 1, Ed25519 = 2, } -impl Default for KeyGenType { - fn default() -> Self { - KeyGenType::Default - } -} - #[derive( - Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql, + Debug, Default, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql, )] #[repr(i8)] pub enum VideochatType { + #[default] Unknown = 0, BasicWebrtc = 1, Jitsi = 2, } -impl Default for VideochatType { - fn default() -> Self { - VideochatType::Unknown - } -} - pub const DC_HANDSHAKE_CONTINUE_NORMAL_PROCESSING: i32 = 0x01; pub const DC_HANDSHAKE_STOP_NORMAL_PROCESSING: i32 = 0x02; pub const DC_HANDSHAKE_ADD_DELETE_JOB: i32 = 0x04; @@ -135,6 +111,7 @@ pub const DC_CHAT_ID_LAST_SPECIAL: ChatId = ChatId::new(9); #[derive( Debug, + Default, Display, Clone, Copy, @@ -150,6 +127,7 @@ pub const DC_CHAT_ID_LAST_SPECIAL: ChatId = ChatId::new(9); )] #[repr(u32)] pub enum Chattype { + #[default] Undefined = 0, Single = 100, Group = 120, @@ -157,12 +135,6 @@ pub enum Chattype { Broadcast = 160, } -impl Default for Chattype { - fn default() -> Self { - Chattype::Undefined - } -} - pub const DC_MSG_ID_DAYMARKER: u32 = 9; pub const DC_MSG_ID_LAST_SPECIAL: u32 = 9; @@ -244,7 +216,7 @@ mod tests { #[test] fn test_showemails_values() { // values may be written to disk and must not change - assert_eq!(ShowEmails::Off, ShowEmails::default()); + assert_eq!(ShowEmails::All, ShowEmails::default()); assert_eq!(ShowEmails::Off, ShowEmails::from_i32(0).unwrap()); assert_eq!( ShowEmails::AcceptedContacts, diff --git a/src/contact.rs b/src/contact.rs index d811aea7f..b86cbedb7 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -223,12 +223,24 @@ pub struct Contact { /// Possible origins of a contact. #[derive( - Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive, FromSql, ToSql, + Debug, + Default, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + FromPrimitive, + ToPrimitive, + FromSql, + ToSql, )] #[repr(u32)] pub enum Origin { /// Unknown origin. Can be used as a minimum origin to specify that the caller does not care /// about origin of the contact. + #[default] Unknown = 0, /// The contact is a mailing list address, needed to unblock mailing lists @@ -287,12 +299,6 @@ pub enum Origin { ManuallyCreated = 0x0400_0000, } -impl Default for Origin { - fn default() -> Self { - Origin::Unknown - } -} - impl Origin { /// Contacts that are known, i. e. they came in via accepted contacts or /// themselves an accepted contact. Known contacts are shown in the @@ -1515,7 +1521,6 @@ fn split_address_book(book: &str) -> Vec<(&str, &str)> { book.lines() .collect::>() .chunks(2) - .into_iter() .filter_map(|chunk| { let name = chunk.first()?; let addr = chunk.get(1)?; diff --git a/src/context.rs b/src/context.rs index 20413cc08..7db8f7d56 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,7 +1,5 @@ //! Context module. -#![allow(missing_docs)] - use std::collections::{BTreeMap, HashMap}; use std::ffi::OsString; use std::ops::Deref; diff --git a/src/download.rs b/src/download.rs index 1701dd644..fb56f729c 100644 --- a/src/download.rs +++ b/src/download.rs @@ -35,6 +35,7 @@ pub(crate) const MIN_DELETE_SERVER_AFTER: i64 = 48 * 60 * 60; /// Download state of the message. #[derive( Debug, + Default, Display, Clone, Copy, @@ -50,6 +51,7 @@ pub(crate) const MIN_DELETE_SERVER_AFTER: i64 = 48 * 60 * 60; #[repr(u32)] pub enum DownloadState { /// Message is fully downloaded. + #[default] Done = 0, /// Message is partially downloaded and can be fully downloaded at request. @@ -62,12 +64,6 @@ pub enum DownloadState { InProgress = 1000, } -impl Default for DownloadState { - fn default() -> Self { - DownloadState::Done - } -} - impl Context { // Returns validated download limit or `None` for "no limit". pub(crate) async fn download_limit(&self) -> Result> { diff --git a/src/headerdef.rs b/src/headerdef.rs index 30d01222b..6f9dd495a 100644 --- a/src/headerdef.rs +++ b/src/headerdef.rs @@ -70,7 +70,8 @@ pub enum HeaderDef { /// See AuthenticationResults, - _TestHeader, + #[cfg(test)] + TestHeader, } impl HeaderDef { @@ -103,7 +104,7 @@ mod tests { fn kebab_test() { assert_eq!(HeaderDef::From_.get_headername(), "from"); - assert_eq!(HeaderDef::_TestHeader.get_headername(), "test-header"); + assert_eq!(HeaderDef::TestHeader.get_headername(), "test-header"); } #[test] diff --git a/src/html.rs b/src/html.rs index 20bb046c9..d74a5bbac 100644 --- a/src/html.rs +++ b/src/html.rs @@ -240,7 +240,7 @@ fn mimepart_to_data_url(mail: &mailparse::ParsedMail<'_>) -> Result { } impl MsgId { - /// Get HTML from a message-id. + /// Get HTML by database message id. /// This requires `mime_headers` field to be set for the message; /// this is the case at least when `Message.has_html()` returns true /// (we do not save raw mime unconditionally in the database to save space). @@ -435,7 +435,6 @@ test some special html-characters as < > and & but also " and &#x async fn test_html_forwarding() { // alice receives a non-delta html-message let alice = TestContext::new_alice().await; - alice.set_config(Config::ShowEmails, Some("2")).await.ok(); let chat = alice .create_chat_with_contact("", "sender@testrun.org") .await; @@ -481,10 +480,13 @@ test some special html-characters as < > and & but also " and &#x #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_html_forwarding_encrypted() { // Alice receives a non-delta html-message - // (`ShowEmails=1` lets Alice actually receive non-delta messages for known contacts, - // the contact is marked as known by creating a chat using `chat_with_contact()`) + // (`ShowEmails=AcceptedContacts` lets Alice actually receive non-delta messages for known + // contacts, the contact is marked as known by creating a chat using `chat_with_contact()`) let alice = TestContext::new_alice().await; - alice.set_config(Config::ShowEmails, Some("1")).await.ok(); + alice + .set_config(Config::ShowEmails, Some("1")) + .await + .unwrap(); let chat = alice .create_chat_with_contact("", "sender@testrun.org") .await; @@ -502,7 +504,10 @@ test some special html-characters as < > and & but also " and &#x // receive the message on another device let alice = TestContext::new_alice().await; - assert_eq!(alice.get_config_int(Config::ShowEmails).await.unwrap(), 0); // set to "1" above, make sure it is another db + alice + .set_config(Config::ShowEmails, Some("0")) + .await + .unwrap(); let msg = alice.recv_msg(&msg).await; assert_eq!(msg.chat_id, alice.get_self_chat().await.id); assert_eq!(msg.get_from_id(), ContactId::SELF); @@ -550,7 +555,6 @@ test some special html-characters as < > and & but also " and &#x #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_cp1252_html() -> Result<()> { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await?; receive_imf( &t, include_bytes!("../test-data/message/cp1252-html.eml"), diff --git a/src/imap.rs b/src/imap.rs index 7d4e722c4..56a2cc6e6 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -2534,7 +2534,6 @@ mod tests { t.ctx .set_config(Config::MvboxMove, Some(if mvbox_move { "1" } else { "0" })) .await?; - t.ctx.set_config(Config::ShowEmails, Some("2")).await?; if accepted_chat { let contact_id = Contact::create(&t.ctx, "", "bob@example.net").await?; diff --git a/src/imex.rs b/src/imex.rs index 366092170..c6187a208 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -1,7 +1,5 @@ //! # Import/export module. -#![allow(missing_docs)] - use std::any::Any; use std::ffi::OsStr; use std::iter::FusedIterator; @@ -44,6 +42,7 @@ pub use transfer::{get_backup, BackupProvider}; const DBFILE_BACKUP_NAME: &str = "dc_database_backup.sqlite"; const BLOBS_BACKUP_NAME: &str = "blobs_backup"; +/// Import/export command. #[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)] #[repr(u32)] pub enum ImexMode { @@ -226,6 +225,7 @@ pub async fn render_setup_file(context: &Context, passphrase: &str) -> Result String { let mut random_val: u16; let mut rng = thread_rng(); @@ -264,6 +264,10 @@ async fn maybe_add_bcc_self_device_msg(context: &Context) -> Result<()> { Ok(()) } +/// Continue key transfer via Autocrypt Setup Message. +/// +/// `msg_id` is the ID of the received Autocrypt Setup Message. +/// `setup_code` is the code entered by the user. pub async fn continue_key_transfer( context: &Context, msg_id: MsgId, diff --git a/src/message.rs b/src/message.rs index 1b1a1118c..44d6c1194 100644 --- a/src/message.rs +++ b/src/message.rs @@ -244,12 +244,18 @@ pub struct Message { /// ID of the first contact in the `To:` header. pub(crate) to_id: ContactId, + + /// ID of the chat message belongs to. pub(crate) chat_id: ChatId, + + /// Type of the message. pub(crate) viewtype: Viewtype, /// State of the message. pub(crate) state: MessageState, pub(crate) download_state: DownloadState, + + /// Whether the message is hidden. pub(crate) hidden: bool, pub(crate) timestamp_sort: i64, pub(crate) timestamp_sent: i64, @@ -257,8 +263,14 @@ pub struct Message { pub(crate) ephemeral_timer: EphemeralTimer, pub(crate) ephemeral_timestamp: i64, pub(crate) text: Option, + + /// Message subject. pub(crate) subject: String, + + /// `Message-ID` header value. pub(crate) rfc724_mid: String, + + /// `In-Reply-To` header value. pub(crate) in_reply_to: Option, pub(crate) is_dc_message: MessengerMessage, pub(crate) mime_modified: bool, @@ -969,6 +981,7 @@ impl Message { /// For outgoing message, the message could be pending, already delivered or confirmed. #[derive( Debug, + Default, Clone, Copy, PartialEq, @@ -985,6 +998,7 @@ impl Message { #[repr(u32)] pub enum MessageState { /// Undefined message state. + #[default] Undefined = 0, /// Incoming *fresh* message. Fresh messages are neither noticed @@ -1027,12 +1041,6 @@ pub enum MessageState { OutMdnRcvd = 28, } -impl Default for MessageState { - fn default() -> Self { - MessageState::Undefined - } -} - impl std::fmt::Display for MessageState { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( @@ -1902,6 +1910,7 @@ pub(crate) async fn rfc724_mid_exists( /// How a message is primarily displayed. #[derive( Debug, + Default, Display, Clone, Copy, @@ -1917,6 +1926,7 @@ pub(crate) async fn rfc724_mid_exists( #[repr(u32)] pub enum Viewtype { /// Unknown message type. + #[default] Unknown = 0, /// Text message. @@ -1970,12 +1980,6 @@ pub enum Viewtype { Webxdc = 80, } -impl Default for Viewtype { - fn default() -> Self { - Viewtype::Unknown - } -} - impl Viewtype { /// Whether a message with this [`Viewtype`] should have a file attachment. pub fn has_file(&self) -> bool { diff --git a/src/mimefactory.rs b/src/mimefactory.rs index ea8eea85f..e67b0aace 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -2009,7 +2009,7 @@ mod tests { "1.0" ); - let _mime_msg = MimeMessage::from_bytes(context, rendered_msg.message.as_bytes()) + let _mime_msg = MimeMessage::from_bytes(context, rendered_msg.message.as_bytes(), None) .await .unwrap(); } diff --git a/src/mimeparser.rs b/src/mimeparser.rs index d428fc080..4f5f2f1bf 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -48,13 +48,19 @@ use crate::{location, tools}; /// It is created by parsing the raw data of an actual MIME message /// using the [MimeMessage::from_bytes] constructor. #[derive(Debug)] -pub struct MimeMessage { +pub(crate) struct MimeMessage { + /// Parsed MIME parts. pub parts: Vec, + + /// Message headers. header: HashMap, /// Addresses are normalized and lowercased: pub recipients: Vec, + + /// `From:` address. pub from: SingleInfo, + /// 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, @@ -72,6 +78,8 @@ pub struct MimeMessage { /// The set of mail recipient addresses for which gossip headers were applied, regardless of /// whether they modified any peerstates. pub gossiped_addr: HashSet, + + /// True if the message is a forwarded message. pub is_forwarded: bool, pub is_system_message: SystemMessage, pub location_kml: Option, @@ -123,15 +131,26 @@ pub(crate) enum MailinglistType { } #[derive( - Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql, + Debug, Default, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql, )] #[repr(u32)] pub enum SystemMessage { + #[default] Unknown = 0, + + /// Group name changed. GroupNameChanged = 2, + + /// Group avatar changed. GroupImageChanged = 3, + + /// Member was added to the group. MemberAddedToGroup = 4, + + /// Member was removed from the group. MemberRemovedFromGroup = 5, + + /// Autocrypt Setup Message. AutocryptSetupMessage = 6, SecurejoinMessage = 7, LocationStreamingEnabled = 8, @@ -140,41 +159,33 @@ pub enum SystemMessage { /// Chat ephemeral message timer is changed. EphemeralTimerChanged = 10, - // Chat protection state changed + /// Chat protection is enabled. ChatProtectionEnabled = 11, + + /// Chat protection is disabled. ChatProtectionDisabled = 12, /// Self-sent-message that contains only json used for multi-device-sync; /// if possible, we attach that to other messages as for locations. MultiDeviceSync = 20, - // Sync message that contains a json payload - // sent to the other webxdc instances - // These messages are not shown in the chat. + /// Sync message that contains a json payload + /// sent to the other webxdc instances + /// These messages are not shown in the chat. WebxdcStatusUpdate = 30, - // Webxdc info added with `info` set in `send_webxdc_status_update()`. + /// Webxdc info added with `info` set in `send_webxdc_status_update()`. WebxdcInfoMessage = 32, } -impl Default for SystemMessage { - fn default() -> Self { - SystemMessage::Unknown - } -} - const MIME_AC_SETUP_FILE: &str = "application/autocrypt-setup"; impl MimeMessage { - pub async fn from_bytes(context: &Context, body: &[u8]) -> Result { - MimeMessage::from_bytes_with_partial(context, body, None).await - } - /// Parse a mime message. /// /// If `partial` is set, it contains the full message size in bytes /// and `body` contains the header only. - pub(crate) async fn from_bytes_with_partial( + pub(crate) async fn from_bytes( context: &Context, body: &[u8], partial: Option, @@ -1764,16 +1775,32 @@ fn is_known(key: &str) -> bool { ) } +/// Parsed MIME part. #[derive(Debug, Default, Clone)] pub struct Part { + /// Type of the MIME part determining how it should be displayed. pub typ: Viewtype, + + /// MIME type. pub mimetype: Option, + + /// Message text to be displayed in the chat. pub msg: String, + + /// Message text to be displayed in message info. pub msg_raw: Option, + + /// Size of the MIME part in bytes. pub bytes: usize, pub param: Params, + + /// Attachment filename. pub(crate) org_filename: Option, + + /// An error detected during parsing. pub error: Option, + + /// True if conversion from HTML to plaintext failed. pub(crate) dehtml_failed: bool, /// the part is a child or a descendant of multipart/related. @@ -1957,7 +1984,6 @@ mod tests { use super::*; use crate::{ chatlist::Chatlist, - config::Config, constants::{Blocked, DC_DESIRED_TEXT_LEN, DC_ELLIPSIS}, message::{Message, MessageState, MessengerMessage}, receive_imf::receive_imf, @@ -1977,35 +2003,35 @@ mod tests { async fn test_mimeparser_fromheader() { let ctx = TestContext::new_alice().await; - let mimemsg = MimeMessage::from_bytes(&ctx, b"From: g@c.de\n\nhi") + let mimemsg = MimeMessage::from_bytes(&ctx, b"From: g@c.de\n\nhi", None) .await .unwrap(); let contact = mimemsg.from; assert_eq!(contact.addr, "g@c.de"); assert_eq!(contact.display_name, None); - let mimemsg = MimeMessage::from_bytes(&ctx, b"From: g@c.de \n\nhi") + let mimemsg = MimeMessage::from_bytes(&ctx, b"From: g@c.de \n\nhi", None) .await .unwrap(); let contact = mimemsg.from; assert_eq!(contact.addr, "g@c.de"); assert_eq!(contact.display_name, None); - let mimemsg = MimeMessage::from_bytes(&ctx, b"From: \n\nhi") + let mimemsg = MimeMessage::from_bytes(&ctx, b"From: \n\nhi", None) .await .unwrap(); let contact = mimemsg.from; assert_eq!(contact.addr, "g@c.de"); assert_eq!(contact.display_name, None); - let mimemsg = MimeMessage::from_bytes(&ctx, b"From: Goetz C \n\nhi") + let mimemsg = MimeMessage::from_bytes(&ctx, b"From: Goetz C \n\nhi", None) .await .unwrap(); let contact = mimemsg.from; assert_eq!(contact.addr, "g@c.de"); assert_eq!(contact.display_name, Some("Goetz C".to_string())); - let mimemsg = MimeMessage::from_bytes(&ctx, b"From: \"Goetz C\" \n\nhi") + let mimemsg = MimeMessage::from_bytes(&ctx, b"From: \"Goetz C\" \n\nhi", None) .await .unwrap(); let contact = mimemsg.from; @@ -2013,7 +2039,7 @@ mod tests { assert_eq!(contact.display_name, Some("Goetz C".to_string())); let mimemsg = - MimeMessage::from_bytes(&ctx, b"From: =?utf-8?q?G=C3=B6tz?= C \n\nhi") + MimeMessage::from_bytes(&ctx, b"From: =?utf-8?q?G=C3=B6tz?= C \n\nhi", None) .await .unwrap(); let contact = mimemsg.from; @@ -2022,10 +2048,13 @@ mod tests { // although RFC 2047 says, encoded-words shall not appear inside quoted-string, // this combination is used in the wild eg. by MailMate - let mimemsg = - MimeMessage::from_bytes(&ctx, b"From: \"=?utf-8?q?G=C3=B6tz?= C\" \n\nhi") - .await - .unwrap(); + let mimemsg = MimeMessage::from_bytes( + &ctx, + b"From: \"=?utf-8?q?G=C3=B6tz?= C\" \n\nhi", + None, + ) + .await + .unwrap(); let contact = mimemsg.from; assert_eq!(contact.addr, "g@c.de"); assert_eq!(contact.display_name, Some("Götz C".to_string())); @@ -2035,7 +2064,7 @@ mod tests { async fn test_mimeparser_crash() { let context = TestContext::new_alice().await; let raw = include_bytes!("../test-data/message/issue_523.txt"); - let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]) + let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..], None) .await .unwrap(); @@ -2047,7 +2076,7 @@ mod tests { async fn test_get_rfc724_mid_exists() { let context = TestContext::new_alice().await; let raw = include_bytes!("../test-data/message/mail_with_message_id.txt"); - let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]) + let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..], None) .await .unwrap(); @@ -2061,7 +2090,7 @@ mod tests { async fn test_get_rfc724_mid_not_exists() { let context = TestContext::new_alice().await; let raw = include_bytes!("../test-data/message/issue_523.txt"); - let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]) + let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..], None) .await .unwrap(); assert_eq!(mimeparser.get_rfc724_mid(), None); @@ -2258,7 +2287,7 @@ mod tests { test1\n\ "; - let mimeparser = MimeMessage::from_bytes_with_partial(&context.ctx, &raw[..], None).await; + let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..], None).await; assert!(mimeparser.is_err()); } @@ -2273,7 +2302,7 @@ mod tests { \n\ Some reply\n\ "; - let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]) + let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..], None) .await .unwrap(); assert_eq!( @@ -2319,7 +2348,7 @@ mod tests { --==break==--\n\ \n"; - let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]) + let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..], None) .await .unwrap(); @@ -2328,7 +2357,7 @@ mod tests { assert_eq!(of, "no"); // unknown headers do not bubble upwards - let of = mimeparser.get_header(HeaderDef::_TestHeader).unwrap(); + let of = mimeparser.get_header(HeaderDef::TestHeader).unwrap(); assert_eq!(of, "Bar"); // the following fields would bubble up @@ -2353,26 +2382,26 @@ mod tests { let t = TestContext::new_alice().await; let raw = include_bytes!("../test-data/message/mail_attach_txt.eml"); - let mimeparser = MimeMessage::from_bytes(&t, &raw[..]).await.unwrap(); + let mimeparser = MimeMessage::from_bytes(&t, &raw[..], None).await.unwrap(); assert_eq!(mimeparser.user_avatar, None); assert_eq!(mimeparser.group_avatar, None); let raw = include_bytes!("../test-data/message/mail_with_user_avatar.eml"); - let mimeparser = MimeMessage::from_bytes(&t, &raw[..]).await.unwrap(); + let mimeparser = MimeMessage::from_bytes(&t, &raw[..], None).await.unwrap(); assert_eq!(mimeparser.parts.len(), 1); assert_eq!(mimeparser.parts[0].typ, Viewtype::Text); assert!(mimeparser.user_avatar.unwrap().is_change()); assert_eq!(mimeparser.group_avatar, None); let raw = include_bytes!("../test-data/message/mail_with_user_avatar_deleted.eml"); - let mimeparser = MimeMessage::from_bytes(&t, &raw[..]).await.unwrap(); + let mimeparser = MimeMessage::from_bytes(&t, &raw[..], None).await.unwrap(); assert_eq!(mimeparser.parts.len(), 1); assert_eq!(mimeparser.parts[0].typ, Viewtype::Text); assert_eq!(mimeparser.user_avatar, Some(AvatarAction::Delete)); assert_eq!(mimeparser.group_avatar, None); let raw = include_bytes!("../test-data/message/mail_with_user_and_group_avatars.eml"); - let mimeparser = MimeMessage::from_bytes(&t, &raw[..]).await.unwrap(); + let mimeparser = MimeMessage::from_bytes(&t, &raw[..], None).await.unwrap(); assert_eq!(mimeparser.parts.len(), 1); assert_eq!(mimeparser.parts[0].typ, Viewtype::Text); assert!(mimeparser.user_avatar.unwrap().is_change()); @@ -2382,7 +2411,9 @@ mod tests { let raw = include_bytes!("../test-data/message/mail_with_user_and_group_avatars.eml"); let raw = String::from_utf8_lossy(raw).to_string(); let raw = raw.replace("Chat-User-Avatar:", "Xhat-Xser-Xvatar:"); - let mimeparser = MimeMessage::from_bytes(&t, raw.as_bytes()).await.unwrap(); + let mimeparser = MimeMessage::from_bytes(&t, raw.as_bytes(), None) + .await + .unwrap(); assert_eq!(mimeparser.parts.len(), 1); assert_eq!(mimeparser.parts[0].typ, Viewtype::Image); assert_eq!(mimeparser.user_avatar, None); @@ -2394,7 +2425,7 @@ mod tests { let t = TestContext::new_alice().await; let raw = include_bytes!("../test-data/message/videochat_invitation.eml"); - let mimeparser = MimeMessage::from_bytes(&t, &raw[..]).await.unwrap(); + let mimeparser = MimeMessage::from_bytes(&t, &raw[..], None).await.unwrap(); assert_eq!(mimeparser.parts.len(), 1); assert_eq!(mimeparser.parts[0].typ, Viewtype::VideochatInvitation); assert_eq!( @@ -2441,7 +2472,7 @@ Content-Disposition: attachment; filename=\"message.kml\"\n\ --==break==--\n\ ;"; - let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]) + let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..], None) .await .unwrap(); assert_eq!( @@ -2490,7 +2521,7 @@ Disposition: manual-action/MDN-sent-automatically; displayed\n\ --kJBbU58X1xeWNHgBtTbMk80M5qnV4N--\n\ "; - let message = MimeMessage::from_bytes(&context.ctx, &raw[..]) + let message = MimeMessage::from_bytes(&context.ctx, &raw[..], None) .await .unwrap(); assert_eq!( @@ -2570,7 +2601,7 @@ Disposition: manual-action/MDN-sent-automatically; displayed\n\ --outer--\n\ "; - let message = MimeMessage::from_bytes(&context.ctx, &raw[..]) + let message = MimeMessage::from_bytes(&context.ctx, &raw[..], None) .await .unwrap(); assert_eq!( @@ -2617,7 +2648,7 @@ Additional-Message-IDs: \n\ --kJBbU58X1xeWNHgBtTbMk80M5qnV4N--\n\ "; - let message = MimeMessage::from_bytes(&context.ctx, &raw[..]) + let message = MimeMessage::from_bytes(&context.ctx, &raw[..], None) .await .unwrap(); assert_eq!( @@ -2664,7 +2695,7 @@ MDYyMDYxNTE1RTlDOEE4Cj4+CnN0YXJ0eHJlZgo4Mjc4CiUlRU9GCg== ------=_Part_25_46172632.1581201680436-- "#; - let message = MimeMessage::from_bytes(&context.ctx, &raw[..]) + let message = MimeMessage::from_bytes(&context.ctx, &raw[..], None) .await .unwrap(); assert_eq!( @@ -2708,7 +2739,7 @@ MDYyMDYxNTE1RTlDOEE4Cj4+CnN0YXJ0eHJlZgo4Mjc4CiUlRU9GCg== ------=_Part_25_46172632.1581201680436-- "#; - let message = MimeMessage::from_bytes(&t, &raw[..]).await.unwrap(); + let message = MimeMessage::from_bytes(&t, &raw[..], None).await.unwrap(); assert_eq!(message.parts.len(), 1); assert_eq!(message.parts[0].typ, Viewtype::File); @@ -2762,7 +2793,7 @@ CWt6wx7fiLp0qS9RrX75g6Gqw7nfCs6EcBERcIPt7DTe8VStJwf3LWqVwxl4gQl46yhfoqwEO+I= ----11019878869865180-- "#; - let message = MimeMessage::from_bytes(&context.ctx, &raw[..]) + let message = MimeMessage::from_bytes(&context.ctx, &raw[..], None) .await .unwrap(); assert_eq!(message.get_subject(), Some("example".to_string())); @@ -2834,7 +2865,7 @@ CWt6wx7fiLp0qS9RrX75g6Gqw7nfCs6EcBERcIPt7DTe8VStJwf3LWqVwxl4gQl46yhfoqwEO+I= --------------779C1631600DF3DB8C02E53A--"#; - let message = MimeMessage::from_bytes(&context.ctx, &raw[..]) + let message = MimeMessage::from_bytes(&context.ctx, &raw[..], None) .await .unwrap(); assert_eq!(message.get_subject(), Some("Test subject".to_string())); @@ -2905,7 +2936,7 @@ CWt6wx7fiLp0qS9RrX75g6Gqw7nfCs6EcBERcIPt7DTe8VStJwf3LWqVwxl4gQl46yhfoqwEO+I= ------=_NextPart_000_0003_01D622B3.CA753E60-- "##; - let message = MimeMessage::from_bytes(&context.ctx, &raw[..]) + let message = MimeMessage::from_bytes(&context.ctx, &raw[..], None) .await .unwrap(); assert_eq!( @@ -3003,7 +3034,7 @@ From: alice Reply "##; - let message = MimeMessage::from_bytes(&context.ctx, &raw[..]) + let message = MimeMessage::from_bytes(&context.ctx, &raw[..], None) .await .unwrap(); assert_eq!( @@ -3035,7 +3066,7 @@ From: alice > Just a quote. "##; - let message = MimeMessage::from_bytes(&context.ctx, &raw[..]) + let message = MimeMessage::from_bytes(&context.ctx, &raw[..], None) .await .unwrap(); assert_eq!( @@ -3069,7 +3100,7 @@ On 2020-10-25, Bob wrote: > A quote. "##; - let message = MimeMessage::from_bytes(&context.ctx, &raw[..]) + let message = MimeMessage::from_bytes(&context.ctx, &raw[..], None) .await .unwrap(); assert_eq!(message.get_subject(), Some("Re: top posting".to_string())); @@ -3087,7 +3118,7 @@ On 2020-10-25, Bob wrote: async fn test_attachment_quote() { let context = TestContext::new_alice().await; let raw = include_bytes!("../test-data/message/quote_attach.eml"); - let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]) + let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..], None) .await .unwrap(); @@ -3105,7 +3136,7 @@ On 2020-10-25, Bob wrote: async fn test_quote_div() { let t = TestContext::new_alice().await; let raw = include_bytes!("../test-data/message/gmx-quote.eml"); - let mimeparser = MimeMessage::from_bytes(&t, raw).await.unwrap(); + let mimeparser = MimeMessage::from_bytes(&t, raw, None).await.unwrap(); assert_eq!(mimeparser.parts[0].msg, "YIPPEEEEEE\n\nMulti-line"); assert_eq!(mimeparser.parts[0].param.get(Param::Quote).unwrap(), "Now?"); } @@ -3115,7 +3146,7 @@ On 2020-10-25, Bob wrote: // all-inkl.com puts quotes into `
`. let t = TestContext::new_alice().await; let raw = include_bytes!("../test-data/message/allinkl-quote.eml"); - let mimeparser = MimeMessage::from_bytes(&t, raw).await.unwrap(); + let mimeparser = MimeMessage::from_bytes(&t, raw, None).await.unwrap(); assert!(mimeparser.parts[0].msg.starts_with("It's 1.0.")); assert_eq!( mimeparser.parts[0].param.get(Param::Quote).unwrap(), @@ -3126,7 +3157,6 @@ On 2020-10-25, Bob wrote: #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_add_subj_to_multimedia_msg() { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await.unwrap(); receive_imf( &t.ctx, include_bytes!("../test-data/message/subj_with_multimedia_msg.eml"), @@ -3160,7 +3190,7 @@ On 2020-10-25, Bob wrote: async fn test_mime_modified_plain() { let t = TestContext::new_alice().await; let raw = include_bytes!("../test-data/message/text_plain_unspecified.eml"); - let mimeparser = MimeMessage::from_bytes(&t.ctx, raw).await.unwrap(); + let mimeparser = MimeMessage::from_bytes(&t.ctx, raw, None).await.unwrap(); assert!(!mimeparser.is_mime_modified); assert_eq!( mimeparser.parts[0].msg, @@ -3172,7 +3202,7 @@ On 2020-10-25, Bob wrote: async fn test_mime_modified_alt_plain_html() { let t = TestContext::new_alice().await; let raw = include_bytes!("../test-data/message/text_alt_plain_html.eml"); - let mimeparser = MimeMessage::from_bytes(&t.ctx, raw).await.unwrap(); + let mimeparser = MimeMessage::from_bytes(&t.ctx, raw, None).await.unwrap(); assert!(mimeparser.is_mime_modified); assert_eq!( mimeparser.parts[0].msg, @@ -3184,7 +3214,7 @@ On 2020-10-25, Bob wrote: async fn test_mime_modified_alt_plain() { let t = TestContext::new_alice().await; let raw = include_bytes!("../test-data/message/text_alt_plain.eml"); - let mimeparser = MimeMessage::from_bytes(&t.ctx, raw).await.unwrap(); + let mimeparser = MimeMessage::from_bytes(&t.ctx, raw, None).await.unwrap(); assert!(!mimeparser.is_mime_modified); assert_eq!( mimeparser.parts[0].msg, @@ -3199,7 +3229,7 @@ On 2020-10-25, Bob wrote: async fn test_mime_modified_alt_html() { let t = TestContext::new_alice().await; let raw = include_bytes!("../test-data/message/text_alt_html.eml"); - let mimeparser = MimeMessage::from_bytes(&t.ctx, raw).await.unwrap(); + let mimeparser = MimeMessage::from_bytes(&t.ctx, raw, None).await.unwrap(); assert!(mimeparser.is_mime_modified); assert_eq!( mimeparser.parts[0].msg, @@ -3211,7 +3241,7 @@ On 2020-10-25, Bob wrote: async fn test_mime_modified_html() { let t = TestContext::new_alice().await; let raw = include_bytes!("../test-data/message/text_html.eml"); - let mimeparser = MimeMessage::from_bytes(&t.ctx, raw).await.unwrap(); + let mimeparser = MimeMessage::from_bytes(&t.ctx, raw, None).await.unwrap(); assert!(mimeparser.is_mime_modified); assert_eq!( mimeparser.parts[0].msg, @@ -3227,7 +3257,7 @@ On 2020-10-25, Bob wrote: static REPEAT_CNT: usize = 2000; // results in a text of 84k, should be more than DC_DESIRED_TEXT_LEN let long_txt = format!("From: alice@c.de\n\n{}", REPEAT_TXT.repeat(REPEAT_CNT)); - let mimemsg = MimeMessage::from_bytes(&t, long_txt.as_ref()) + let mimemsg = MimeMessage::from_bytes(&t, long_txt.as_ref(), None) .await .unwrap(); assert_eq!(long_txt.matches("just repeated").count(), REPEAT_CNT); @@ -3254,7 +3284,7 @@ On 2020-10-25, Bob wrote: MIME-Version: 1.0\n\ \n\ Does it work with outlook now?\n\ - ") + ", None) .await .unwrap(); assert_eq!( @@ -3410,7 +3440,6 @@ Message. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_ms_exchange_mdn() -> Result<()> { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await?; let original = include_bytes!("../test-data/message/ms_exchange_report_original_message.eml"); @@ -3420,7 +3449,7 @@ Message. // 1. Test mimeparser directly let mdn = include_bytes!("../test-data/message/ms_exchange_report_disposition_notification.eml"); - let mimeparser = MimeMessage::from_bytes(&t.ctx, mdn).await?; + let mimeparser = MimeMessage::from_bytes(&t.ctx, mdn, None).await?; assert_eq!(mimeparser.mdn_reports.len(), 1); assert_eq!( mimeparser.mdn_reports[0].original_message_id.as_deref(), @@ -3446,6 +3475,7 @@ Message. let mime_message = MimeMessage::from_bytes( &alice, include_bytes!("../test-data/message/attached-eml.eml"), + None, ) .await?; @@ -3488,6 +3518,7 @@ Content-Disposition: reaction\n\ \n\ \u{1F44D}" .as_bytes(), + None, ) .await?; diff --git a/src/plaintext.rs b/src/plaintext.rs index 13dd1ac7c..e50311677 100644 --- a/src/plaintext.rs +++ b/src/plaintext.rs @@ -1,13 +1,13 @@ //! Handle plain text together with some attributes. -#![allow(missing_docs)] - use once_cell::sync::Lazy; use crate::simplify::split_lines; +/// Plaintext message body together with format=flowed attributes. #[derive(Debug)] pub struct PlainText { + /// The text itself. pub text: String, /// Text may "flowed" as defined in [RFC 2646](https://tools.ietf.org/html/rfc2646). diff --git a/src/provider.rs b/src/provider.rs index 5d81b642b..565d54d10 100644 --- a/src/provider.rs +++ b/src/provider.rs @@ -1,7 +1,5 @@ //! [Provider database](https://providers.delta.chat/) module. -#![allow(missing_docs)] - mod data; use anyhow::Result; @@ -12,77 +10,133 @@ use crate::config::Config; use crate::context::Context; use crate::provider::data::{PROVIDER_DATA, PROVIDER_IDS, PROVIDER_UPDATED}; +/// Provider status according to manual testing. #[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)] #[repr(u8)] pub enum Status { + /// Provider is known to be working with Delta Chat. Ok = 1, + + /// Provider works with Delta Chat, but requires some preparation, + /// such as changing the settings in the web interface. Preparation = 2, + + /// Provider is known not to work with Delta Chat. Broken = 3, } +/// Server protocol. #[derive(Debug, Display, PartialEq, Eq, Copy, Clone, FromPrimitive, ToPrimitive)] #[repr(u8)] pub enum Protocol { + /// SMTP protocol. Smtp = 1, + + /// IMAP protocol. Imap = 2, } -#[derive(Debug, Display, PartialEq, Eq, Copy, Clone, FromPrimitive, ToPrimitive)] +/// Socket security. +#[derive(Debug, Default, Display, PartialEq, Eq, Copy, Clone, FromPrimitive, ToPrimitive)] #[repr(u8)] pub enum Socket { + /// Unspecified socket security, select automatically. + #[default] Automatic = 0, + + /// TLS connection. Ssl = 1, + + /// STARTTLS connection. Starttls = 2, + + /// No TLS, plaintext connection. Plain = 3, } -impl Default for Socket { - fn default() -> Self { - Socket::Automatic - } -} - +/// Pattern used to construct login usernames from email addresses. #[derive(Debug, PartialEq, Eq, Clone)] #[repr(u8)] pub enum UsernamePattern { + /// Whole email is used as username. Email = 1, + + /// Part of address before `@` is used as username. Emaillocalpart = 2, } +/// Type of OAuth 2 authorization. #[derive(Debug, PartialEq, Eq)] #[repr(u8)] pub enum Oauth2Authorizer { + /// Yandex. Yandex = 1, + + /// Gmail. Gmail = 2, } +/// Email server endpoint. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Server { + /// Server protocol, e.g. SMTP or IMAP. pub protocol: Protocol, + + /// Port security, e.g. TLS or STARTTLS. pub socket: Socket, + + /// Server host. pub hostname: &'static str, + + /// Server port. pub port: u16, + + /// Pattern used to construct login usernames from email addresses. pub username_pattern: UsernamePattern, } +/// Pair of key and value for default configuration. #[derive(Debug, PartialEq, Eq)] pub struct ConfigDefault { + /// Configuration variable name. pub key: Config, + + /// Configuration variable value. pub value: &'static str, } +/// Provider database entry. #[derive(Debug, PartialEq, Eq)] pub struct Provider { /// Unique ID, corresponding to provider database filename. pub id: &'static str, + + /// Provider status according to manual testing. pub status: Status, + + /// Hint to be shown to the user on the login screen. pub before_login_hint: &'static str, + + /// Hint to be added to the device chat after provider configuration. pub after_login_hint: &'static str, + + /// URL of the page with provider overview. pub overview_page: &'static str, + + /// List of provider servers. pub server: Vec, + + /// Default configuration values to set when provider is configured. pub config_defaults: Option>, + + /// True if provider is known to use use proper, + /// not self-signed certificates. pub strict_tls: bool, + + /// Maximum number of recipients the provider allows to send a single email to. pub max_smtp_rcpt_to: Option, + + /// Type of OAuth 2 authorization if provider supports it. pub oauth2_authorizer: Option, } @@ -175,8 +229,7 @@ pub async fn get_provider_by_mx(context: &Context, domain: &str) -> Option<&'sta None } -// TODO: uncomment when clippy starts complaining about it -//#[allow(clippy::manual_map)] // Can't use .map() because the lifetime is not propagated +/// Returns a provider with the given ID from the database. pub fn get_provider_by_id(id: &str) -> Option<&'static Provider> { if let Some(provider) = PROVIDER_IDS.get(id) { Some(provider) @@ -185,7 +238,7 @@ pub fn get_provider_by_id(id: &str) -> Option<&'static Provider> { } } -// returns update timestamp in seconds, UTC, compatible for comparison with time() and database times +/// Returns update timestamp as a unix timestamp compatible for comparison with time() and database times. pub fn get_provider_update_timestamp() -> i64 { NaiveDateTime::new(*PROVIDER_UPDATED, NaiveTime::from_hms_opt(0, 0, 0).unwrap()) .timestamp_millis() diff --git a/src/reaction.rs b/src/reaction.rs index bd1a98c10..d6f2cb6f1 100644 --- a/src/reaction.rs +++ b/src/reaction.rs @@ -287,7 +287,6 @@ pub async fn get_msg_reactions(context: &Context, msg_id: MsgId) -> Result Result<()> { let alice = TestContext::new_alice().await; - alice.set_config(Config::ShowEmails, Some("2")).await?; // Alice receives BCC-self copy of a message sent to Bob. receive_imf( diff --git a/src/receive_imf.rs b/src/receive_imf.rs index 033506340..889cdfb73 100644 --- a/src/receive_imf.rs +++ b/src/receive_imf.rs @@ -103,35 +103,35 @@ pub(crate) async fn receive_imf_inner( ); } - let mut mime_parser = - match MimeMessage::from_bytes_with_partial(context, imf_raw, is_partial_download).await { - Err(err) => { - warn!(context, "receive_imf: can't parse MIME: {:#}", err); - let msg_ids; - if !rfc724_mid.starts_with(GENERATED_PREFIX) { - let row_id = context - .sql - .execute( - "INSERT INTO msgs(rfc724_mid, chat_id) VALUES (?,?)", - paramsv![rfc724_mid, DC_CHAT_ID_TRASH], - ) - .await?; - msg_ids = vec![MsgId::new(u32::try_from(row_id)?)]; - } else { - return Ok(None); - // We don't have an rfc724_mid, there's no point in adding a trash entry - } - - return Ok(Some(ReceivedMsg { - chat_id: DC_CHAT_ID_TRASH, - state: MessageState::Undefined, - sort_timestamp: 0, - msg_ids, - needs_delete_job: false, - })); + let mut mime_parser = match MimeMessage::from_bytes(context, imf_raw, is_partial_download).await + { + Err(err) => { + warn!(context, "receive_imf: can't parse MIME: {:#}", err); + let msg_ids; + if !rfc724_mid.starts_with(GENERATED_PREFIX) { + let row_id = context + .sql + .execute( + "INSERT INTO msgs(rfc724_mid, chat_id) VALUES (?,?)", + paramsv![rfc724_mid, DC_CHAT_ID_TRASH], + ) + .await?; + msg_ids = vec![MsgId::new(u32::try_from(row_id)?)]; + } else { + return Ok(None); + // We don't have an rfc724_mid, there's no point in adding a trash entry } - Ok(mime_parser) => mime_parser, - }; + + return Ok(Some(ReceivedMsg { + chat_id: DC_CHAT_ID_TRASH, + state: MessageState::Undefined, + sort_timestamp: 0, + msg_ids, + needs_delete_job: false, + })); + } + Ok(mime_parser) => mime_parser, + }; // we can not add even an empty record if we have no info whatsoever if !mime_parser.has_headers() { diff --git a/src/receive_imf/tests.rs b/src/receive_imf/tests.rs index 342b2fe36..b2564fc75 100644 --- a/src/receive_imf/tests.rs +++ b/src/receive_imf/tests.rs @@ -5,7 +5,7 @@ use crate::aheader::EncryptPreference; use crate::chat::get_chat_contacts; use crate::chat::{get_chat_msgs, ChatItem, ChatVisibility}; use crate::chatlist::Chatlist; -use crate::constants::DC_GCL_NO_SPECIALS; +use crate::constants::{ShowEmails, DC_GCL_NO_SPECIALS}; use crate::imap::prefetch_should_download; use crate::message::Message; use crate::test_utils::{get_chat_msg, TestContext, TestContextManager}; @@ -20,7 +20,7 @@ async fn test_grpid_simple() { References: \n\ \n\ hello\x00"; - let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]) + let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..], None) .await .unwrap(); assert_eq!(extract_grpid(&mimeparser, HeaderDef::InReplyTo), None); @@ -38,7 +38,7 @@ async fn test_bad_from() { References: \n\ \n\ hello\x00"; - let mimeparser = MimeMessage::from_bytes_with_partial(&context.ctx, &raw[..], None).await; + let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..], None).await; assert!(mimeparser.is_err()); } @@ -52,7 +52,7 @@ async fn test_grpid_from_multiple() { References: , \n\ \n\ hello\x00"; - let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]) + let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..], None) .await .unwrap(); let grpid = Some("HcxyMARjyJy"); @@ -94,7 +94,7 @@ static GRP_MAIL: &[u8] = #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_adhoc_group_show_chats_only() { let t = TestContext::new_alice().await; - assert_eq!(t.get_config_int(Config::ShowEmails).await.unwrap(), 0); + t.set_config(Config::ShowEmails, Some("0")).await.unwrap(); let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); assert_eq!(chats.len(), 0); @@ -175,7 +175,7 @@ async fn test_adhoc_group_show_accepted_contact_accepted() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_adhoc_group_show_all() { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await.unwrap(); + assert_eq!(t.get_config_int(Config::ShowEmails).await.unwrap(), 2); receive_imf(&t, GRP_MAIL, false).await.unwrap(); // adhoc-group with unknown contacts with show_emails=all will show up in a single chat @@ -768,7 +768,6 @@ static GH_MAILINGLIST2: &str = #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_github_mailing_list() -> Result<()> { let t = TestContext::new_alice().await; - t.ctx.set_config(Config::ShowEmails, Some("2")).await?; receive_imf(&t.ctx, GH_MAILINGLIST, false).await?; @@ -840,10 +839,6 @@ static DC_MAILINGLIST2: &[u8] = b"Received: (Postfix, from userid 1000); Mon, 4 #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_classic_mailing_list() -> Result<()> { let t = TestContext::new_alice().await; - t.ctx - .set_config(Config::ShowEmails, Some("2")) - .await - .unwrap(); receive_imf(&t.ctx, DC_MAILINGLIST, false).await.unwrap(); let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); let chat_id = chats.get_chat_id(0).unwrap(); @@ -885,7 +880,6 @@ Hello mailinglist!\r\n" #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_other_device_writes_to_mailinglist() -> Result<()> { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await?; receive_imf(&t, DC_MAILINGLIST, false).await.unwrap(); let first_msg = t.get_last_msg().await; let first_chat = Chat::load_from_db(&t, first_msg.chat_id).await?; @@ -935,10 +929,6 @@ async fn test_other_device_writes_to_mailinglist() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_block_mailing_list() { let t = TestContext::new_alice().await; - t.ctx - .set_config(Config::ShowEmails, Some("2")) - .await - .unwrap(); receive_imf(&t.ctx, DC_MAILINGLIST, false).await.unwrap(); t.evtracker.wait_next_incoming_message().await; @@ -973,7 +963,6 @@ async fn test_block_mailing_list() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_mailing_list_decide_block_then_unblock() { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await.unwrap(); receive_imf(&t, DC_MAILINGLIST, false).await.unwrap(); let blocked = Contact::get_all_blocked(&t).await.unwrap(); @@ -1004,10 +993,6 @@ async fn test_mailing_list_decide_block_then_unblock() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_mailing_list_decide_not_now() { let t = TestContext::new_alice().await; - t.ctx - .set_config(Config::ShowEmails, Some("2")) - .await - .unwrap(); receive_imf(&t.ctx, DC_MAILINGLIST, false).await.unwrap(); @@ -1035,10 +1020,6 @@ async fn test_mailing_list_decide_not_now() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_mailing_list_decide_accept() { let t = TestContext::new_alice().await; - t.ctx - .set_config(Config::ShowEmails, Some("2")) - .await - .unwrap(); receive_imf(&t.ctx, DC_MAILINGLIST, false).await.unwrap(); @@ -1061,7 +1042,6 @@ async fn test_mailing_list_decide_accept() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_mailing_list_multiple_names_in_subject() -> Result<()> { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await?; receive_imf( &t, b"From: Foo Bar \n\ @@ -1086,7 +1066,6 @@ async fn test_mailing_list_multiple_names_in_subject() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_majordomo_mailing_list() -> Result<()> { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await.unwrap(); // test mailing lists not having a `ListId:`-header receive_imf( @@ -1139,7 +1118,6 @@ async fn test_majordomo_mailing_list() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_mailchimp_mailing_list() -> Result<()> { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await.unwrap(); receive_imf( &t, @@ -1173,7 +1151,6 @@ async fn test_mailchimp_mailing_list() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_dhl_mailing_list() -> Result<()> { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await.unwrap(); receive_imf( &t, @@ -1202,7 +1179,6 @@ async fn test_dhl_mailing_list() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_dpd_mailing_list() -> Result<()> { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await.unwrap(); receive_imf( &t, @@ -1231,7 +1207,6 @@ async fn test_dpd_mailing_list() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_xt_local_mailing_list() -> Result<()> { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await?; receive_imf( &t, @@ -1265,7 +1240,6 @@ async fn test_xt_local_mailing_list() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_xing_mailing_list() -> Result<()> { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await?; receive_imf( &t, @@ -1288,7 +1262,6 @@ async fn test_xing_mailing_list() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_ttline_mailing_list() -> Result<()> { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await?; receive_imf( &t, @@ -1309,7 +1282,6 @@ async fn test_ttline_mailing_list() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_mailing_list_with_mimepart_footer() { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await.unwrap(); // the mailing list message contains two top-level texts. // the second text is a footer that is added by some mailing list software @@ -1340,7 +1312,6 @@ async fn test_mailing_list_with_mimepart_footer() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_mailing_list_with_mimepart_footer_signed() { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await.unwrap(); receive_imf( &t, @@ -1365,7 +1336,6 @@ async fn test_mailing_list_with_mimepart_footer_signed() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_apply_mailinglist_changes_assigned_by_reply() { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await.unwrap(); receive_imf(&t, GH_MAILINGLIST, false).await.unwrap(); @@ -1461,10 +1431,6 @@ async fn test_dont_show_noreply_in_contacts_list() { async fn check_dont_show_in_contacts_list(addr: &str) { let t = TestContext::new_alice().await; - t.ctx - .set_config(Config::ShowEmails, Some("2")) - .await - .unwrap(); receive_imf( &t, format!( @@ -1534,7 +1500,6 @@ async fn test_pdf_filename_continuation() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_many_images() { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await.unwrap(); receive_imf( &t, @@ -1811,10 +1776,6 @@ async fn create_test_alias(chat_request: bool, group_request: bool) -> (TestCont }; let alice = TestContext::new_alice().await; - alice - .set_config(Config::ShowEmails, Some("2")) - .await - .unwrap(); receive_imf(&alice, claire_request.as_bytes(), false) .await .unwrap(); @@ -1834,10 +1795,6 @@ async fn create_test_alias(chat_request: bool, group_request: bool) -> (TestCont let claire = TestContext::new().await; claire.configure_addr("claire@example.org").await; - claire - .set_config(Config::ShowEmails, Some("2")) - .await - .unwrap(); receive_imf(&claire, claire_request.as_bytes(), false) .await .unwrap(); @@ -1949,7 +1906,6 @@ async fn test_alias_answer_from_dc() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_dont_assign_to_trash_by_parent() { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await.unwrap(); println!("\n========= Receive a message =========="); receive_imf( &t, @@ -2025,12 +1981,6 @@ Message content", async fn test_outgoing_classic_mail_creates_chat() { let alice = TestContext::new_alice().await; - // Alice enables classic emails. - alice - .set_config(Config::ShowEmails, Some("2")) - .await - .unwrap(); - // Alice downloads outgoing classic email. receive_imf( &alice, @@ -2113,7 +2063,6 @@ Second signature"; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_ignore_footer_status_from_mailinglist() -> Result<()> { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await?; let bob_id = Contact::add_or_lookup( &t, "", @@ -2192,7 +2141,6 @@ Original signature updated", async fn test_chat_assignment_private_classical_reply() { for outgoing_is_classical in &[true, false] { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await.unwrap(); receive_imf( &t, @@ -2278,7 +2226,6 @@ async fn test_chat_assignment_private_chat_reply() { &[(true, true), (false, true), (false, false)] { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await.unwrap(); receive_imf( &t, @@ -2372,7 +2319,6 @@ Sent with my Delta Chat Messenger: https://delta.chat async fn test_chat_assignment_nonprivate_classical_reply() { for outgoing_is_classical in &[true, false] { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await.unwrap(); receive_imf( &t, @@ -2481,8 +2427,6 @@ Reply to all"#, async fn test_chat_assignment_adhoc() -> Result<()> { let alice = TestContext::new_alice().await; let bob = TestContext::new_bob().await; - alice.set_config(Config::ShowEmails, Some("2")).await?; - bob.set_config(Config::ShowEmails, Some("2")).await?; let first_thread_mime = br#"Subject: First thread Message-ID: first@example.org @@ -2588,7 +2532,6 @@ async fn test_read_receipts_dont_create_chats() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_gmx_forwarded_msg() -> Result<()> { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await?; receive_imf( &t, @@ -2633,7 +2576,6 @@ async fn test_incoming_contact_request() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_get_parent_message() -> Result<()> { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await?; let mime = br#"Subject: First Message-ID: first@example.net @@ -2672,7 +2614,7 @@ References: Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no Message with references."#; - let mime_parser = MimeMessage::from_bytes(&t, &mime[..]).await?; + let mime_parser = MimeMessage::from_bytes(&t, &mime[..], None).await?; let parent = get_parent_message(&t, &mime_parser).await?.unwrap(); assert_eq!(parent.id, first.id); @@ -2707,8 +2649,6 @@ async fn test_rfc1847_encapsulation() -> Result<()> { bob.recv_msg(&first_msg).await; message::delete_msgs(&bob, &[bob.get_last_msg().await.id]).await?; - bob.set_config(Config::ShowEmails, Some("2")).await?; - // Alice sends a message to Bob using Thunderbird. let raw = include_bytes!("../../test-data/message/rfc1847_encapsulation.eml"); receive_imf(&bob, raw, false).await?; @@ -2734,7 +2674,6 @@ async fn test_invalid_to_address() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_reply_from_different_addr() -> Result<()> { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await?; // Alice creates a 2-person-group with Bob receive_imf( @@ -3042,7 +2981,6 @@ async fn test_no_private_reply_to_blocked_account() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_thunderbird_autocrypt() -> Result<()> { let t = TestContext::new_bob().await; - t.set_config(Config::ShowEmails, Some("2")).await?; let raw = include_bytes!("../../test-data/message/thunderbird_with_autocrypt.eml"); receive_imf(&t, raw, false).await?; @@ -3058,7 +2996,6 @@ async fn test_thunderbird_autocrypt() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_thunderbird_autocrypt_unencrypted() -> Result<()> { let t = TestContext::new_bob().await; - t.set_config(Config::ShowEmails, Some("2")).await?; let raw = include_bytes!("../../test-data/message/thunderbird_with_autocrypt_unencrypted.eml"); receive_imf(&t, raw, false).await?; @@ -3084,7 +3021,6 @@ async fn test_thunderbird_autocrypt_unencrypted() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_thunderbird_unsigned() -> Result<()> { let alice = TestContext::new_alice().await; - alice.set_config(Config::ShowEmails, Some("2")).await?; // Alice receives an unsigned message from Bob. let raw = include_bytes!("../../test-data/message/thunderbird_encrypted_unsigned.eml"); diff --git a/src/scheduler/connectivity.rs b/src/scheduler/connectivity.rs index 745f0b9e6..6ddc9eede 100644 --- a/src/scheduler/connectivity.rs +++ b/src/scheduler/connectivity.rs @@ -1,5 +1,3 @@ -#![allow(missing_docs)] - use core::fmt; use std::{ops::Deref, sync::Arc}; @@ -29,9 +27,10 @@ pub enum Connectivity { // the top) take priority. This means that e.g. if any folder has an error - usually // because there is no internet connection - the connectivity for the whole // account will be `Notconnected`. -#[derive(Debug, Clone, PartialEq, Eq, EnumProperty, PartialOrd)] +#[derive(Debug, Default, Clone, PartialEq, Eq, EnumProperty, PartialOrd)] enum DetailedConnectivity { Error(String), + #[default] Uninitialized, Connecting, Working, @@ -42,12 +41,6 @@ enum DetailedConnectivity { NotConfigured, } -impl Default for DetailedConnectivity { - fn default() -> Self { - DetailedConnectivity::Uninitialized - } -} - impl DetailedConnectivity { fn to_basic(&self) -> Option { match self { @@ -538,6 +531,7 @@ impl Context { Ok(ret) } + /// Returns true if all background work is done. pub async fn all_work_done(&self) -> bool { let lock = self.scheduler.read().await; let stores: Vec<_> = match &*lock { diff --git a/src/securejoin.rs b/src/securejoin.rs index 834436136..f854b7d98 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -1,7 +1,5 @@ //! Verified contact protocol implementation as [specified by countermitm project](https://countermitm.readthedocs.io/en/stable/new.html#setup-contact-protocol). -#![allow(missing_docs)] - use std::convert::TryFrom; use anyhow::{bail, Context as _, Error, Result}; @@ -35,6 +33,7 @@ use qrinvite::QrInvite; use crate::token::Namespace; +/// Set of characters to percent-encode in email addresses and names. pub const NON_ALPHANUMERIC_WITHOUT_DOT: &AsciiSet = &NON_ALPHANUMERIC.remove(b'.'); macro_rules! inviter_progress { @@ -1387,7 +1386,6 @@ mod tests { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_adhoc_group_no_qr() -> Result<()> { let alice = TestContext::new_alice().await; - alice.set_config(Config::ShowEmails, Some("2")).await?; let mime = br#"Subject: First thread Message-ID: first@example.org diff --git a/src/securejoin/bobstate.rs b/src/securejoin/bobstate.rs index ead6b1c4f..bbc3c14f3 100644 --- a/src/securejoin/bobstate.rs +++ b/src/securejoin/bobstate.rs @@ -236,7 +236,7 @@ impl BobState { /// stage is returned. Once [`BobHandshakeStage::Completed`] or /// [`BobHandshakeStage::Terminated`] are reached this [`BobState`] should be destroyed, /// further calling it will just result in the messages being unused by this handshake. - pub async fn handle_message( + pub(crate) async fn handle_message( &mut self, context: &Context, mime_message: &MimeMessage, diff --git a/src/sql/migrations.rs b/src/sql/migrations.rs index a88596b8f..92f1c9551 100644 --- a/src/sql/migrations.rs +++ b/src/sql/migrations.rs @@ -683,6 +683,13 @@ CREATE INDEX smtp_messageid ON imap(rfc724_mid); ) .await?; } + if dbversion < 98 { + if exists_before_update && sql.get_raw_config_int("show_emails").await?.is_none() { + sql.set_raw_config_int("show_emails", ShowEmails::Off as i32) + .await?; + } + sql.set_db_version(98).await?; + } let new_version = sql .get_raw_config_int(VERSION_CFG) diff --git a/src/test_utils.rs b/src/test_utils.rs index fd879c68a..eb5562db5 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -446,8 +446,8 @@ impl TestContext { /// peerstates will be updated. Later receiving the message using [recv_msg] is /// unlikely to be affected as the peerstate would be processed again in exactly the /// same way. - pub async fn parse_msg(&self, msg: &SentMessage<'_>) -> MimeMessage { - MimeMessage::from_bytes(&self.ctx, msg.payload().as_bytes()) + pub(crate) async fn parse_msg(&self, msg: &SentMessage<'_>) -> MimeMessage { + MimeMessage::from_bytes(&self.ctx, msg.payload().as_bytes(), None) .await .unwrap() } diff --git a/src/token.rs b/src/token.rs index 048bc9f90..30acb81a0 100644 --- a/src/token.rs +++ b/src/token.rs @@ -13,21 +13,16 @@ use crate::tools::{create_id, time}; /// Token namespace #[derive( - Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql, + Debug, Default, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql, )] #[repr(u32)] pub enum Namespace { + #[default] Unknown = 0, Auth = 110, InviteNumber = 100, } -impl Default for Namespace { - fn default() -> Self { - Namespace::Unknown - } -} - /// Saves a token to the database. pub async fn save( context: &Context, diff --git a/src/tools.rs b/src/tools.rs index b21665dc7..f62e4df67 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -689,9 +689,7 @@ mod tests { #![allow(clippy::indexing_slicing)] use super::*; - use crate::{ - config::Config, message::get_msg_info, receive_imf::receive_imf, test_utils::TestContext, - }; + use crate::{message::get_msg_info, receive_imf::receive_imf, test_utils::TestContext}; #[test] fn test_parse_receive_headers() { @@ -762,7 +760,6 @@ DKIM Results: Passed=true, Works=true, Allow_Keychange=true"; async fn check_parse_receive_headers_integration(raw: &[u8], expected: &str) { let t = TestContext::new_alice().await; - t.set_config(Config::ShowEmails, Some("2")).await.unwrap(); receive_imf(&t, raw, false).await.unwrap(); let msg = t.get_last_msg().await; let msg_info = get_msg_info(&t, msg.id).await.unwrap();