Merge branch 'master' into flub/send-backup

This commit is contained in:
Floris Bruynooghe
2023-02-15 14:46:57 +01:00
51 changed files with 477 additions and 443 deletions

View File

@@ -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 {

View File

@@ -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<Option<MsgId>> {
let msg_id: Option<MsgId> = 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<Option<Message>> {
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<bool> {
@@ -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<usize> {
// 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<bool> {
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<u32> {
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<MsgId> {
ensure!(
!chat_id.is_special(),
@@ -2331,6 +2357,9 @@ async fn create_send_msg_job(context: &Context, msg_id: MsgId) -> Result<Option<
Ok(Some(row_id))
}
/// Sends a text message to the given chat.
///
/// Returns database ID of the sent message.
pub async fn send_text_msg(
context: &Context,
chat_id: ChatId,
@@ -2663,6 +2692,12 @@ pub(crate) async fn mark_old_messages_as_noticed(
Ok(())
}
/// Returns all database message IDs of the given types.
///
/// If `chat_id` is None, return messages from any chat.
///
/// `Viewtype::Unknown` can be used for `msg_type2` and `msg_type3`
/// if less than 3 viewtypes are requested.
pub async fn get_chat_media(
context: &Context,
chat_id: Option<ChatId>,
@@ -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<Message> = 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<bool> {
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,

View File

@@ -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<i32> {
self.get_config(key)
.await
.map(|s: Option<String>| 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<i64> {
self.get_config(key)
.await
.map(|s: Option<String>| 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<u64> {
self.get_config(key)
.await
.map(|s: Option<String>| 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<bool> {
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<bool> {
Ok(self.get_config_bool(Config::MvboxMove).await?
|| self.get_config_bool(Config::OnlyFetchMvbox).await?)

View File

@@ -12,6 +12,7 @@ pub static DC_VERSION_STR: Lazy<String> = Lazy::new(|| env!("CARGO_PKG_VERSION")
#[derive(
Debug,
Default,
Display,
Clone,
Copy,
@@ -26,81 +27,56 @@ pub static DC_VERSION_STR: Lazy<String> = 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,

View File

@@ -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::<Vec<&str>>()
.chunks(2)
.into_iter()
.filter_map(|chunk| {
let name = chunk.first()?;
let addr = chunk.get(1)?;

View File

@@ -1,7 +1,5 @@
//! Context module.
#![allow(missing_docs)]
use std::collections::{BTreeMap, HashMap};
use std::ffi::OsString;
use std::ops::Deref;

View File

@@ -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<Option<u32>> {

View File

@@ -70,7 +70,8 @@ pub enum HeaderDef {
/// See <https://datatracker.ietf.org/doc/html/rfc8601>
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]

View File

@@ -240,7 +240,7 @@ fn mimepart_to_data_url(mail: &mailparse::ParsedMail<'_>) -> Result<String> {
}
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 &lt; &gt; and &amp; but also &quot; 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 &lt; &gt; and &amp; but also &quot; 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 &lt; &gt; and &amp; but also &quot; 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 &lt; &gt; and &amp; but also &quot; 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"),

View File

@@ -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?;

View File

@@ -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<St
))
}
/// Creates a new setup code for Autocrypt Setup Message.
pub fn create_setup_code(_context: &Context) -> 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,

View File

@@ -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<String>,
/// 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<String>,
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 {

View File

@@ -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();
}

View File

@@ -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<Part>,
/// Message headers.
header: HashMap<String, String>,
/// Addresses are normalized and lowercased:
pub recipients: Vec<SingleInfo>,
/// `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<String>,
/// True if the message is a forwarded message.
pub is_forwarded: bool,
pub is_system_message: SystemMessage,
pub location_kml: Option<location::Kml>,
@@ -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<Self> {
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<u32>,
@@ -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<Mime>,
/// Message text to be displayed in the chat.
pub msg: String,
/// Message text to be displayed in message info.
pub msg_raw: Option<String>,
/// Size of the MIME part in bytes.
pub bytes: usize,
pub param: Params,
/// Attachment filename.
pub(crate) org_filename: Option<String>,
/// An error detected during parsing.
pub error: Option<String>,
/// 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: <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: Goetz C <g@c.de>\n\nhi")
let mimemsg = MimeMessage::from_bytes(&ctx, b"From: Goetz C <g@c.de>\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\" <g@c.de>\n\nhi")
let mimemsg = MimeMessage::from_bytes(&ctx, b"From: \"Goetz C\" <g@c.de>\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 <g@c.de>\n\nhi")
MimeMessage::from_bytes(&ctx, b"From: =?utf-8?q?G=C3=B6tz?= C <g@c.de>\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\" <g@c.de>\n\nhi")
.await
.unwrap();
let mimemsg = MimeMessage::from_bytes(
&ctx,
b"From: \"=?utf-8?q?G=C3=B6tz?= C\" <g@c.de>\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: <foo@example.com> <foo@example.net>\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 <alice@example.org>
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 <alice@example.org>
> 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 `<blockquote> </blockquote>`.
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?;

View File

@@ -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).

View File

@@ -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<Server>,
/// Default configuration values to set when provider is configured.
pub config_defaults: Option<Vec<ConfigDefault>>,
/// 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<u16>,
/// Type of OAuth 2 authorization if provider supports it.
pub oauth2_authorizer: Option<Oauth2Authorizer>,
}
@@ -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()

View File

@@ -287,7 +287,6 @@ pub async fn get_msg_reactions(context: &Context, msg_id: MsgId) -> Result<React
mod tests {
use super::*;
use crate::chat::get_chat_msgs;
use crate::config::Config;
use crate::constants::DC_CHAT_ID_TRASH;
use crate::contact::{Contact, ContactAddress, Origin};
use crate::download::DownloadState;
@@ -343,7 +342,6 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_receive_reaction() -> 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(

View File

@@ -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() {

View File

@@ -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: <Gr.HcxyMARjyJy.9-uvzWPTLtV@nauta.cu>\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: <Gr.HcxyMARjyJy.9-uvzWPTLtV@nauta.cu>\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: <qweqweqwe>, <Gr.HcxyMARjyJy.9-uvzWPTLtV@nau.ca>\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 <foo@bar.org>\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: <second@example.net> <nonexistent@example.net> <first@example.net>
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");

View File

@@ -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<Connectivity> {
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 {

View File

@@ -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

View File

@@ -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,

View File

@@ -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)

View File

@@ -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()
}

View File

@@ -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,

View File

@@ -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();