mirror of
https://github.com/chatmail/core.git
synced 2026-07-22 19:03:22 +03:00
Compare commits
6 Commits
v1.159.1
...
iequidoo/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5083e2f067 | ||
|
|
f311cae5ad | ||
|
|
7e8e4d2f39 | ||
|
|
1379821b03 | ||
|
|
1722cb8851 | ||
|
|
49c300d2ac |
@@ -439,7 +439,7 @@ impl CommandApi {
|
||||
/// Setup the credential config before calling this.
|
||||
///
|
||||
/// Deprecated as of 2025-02; use `add_transport_from_qr()`
|
||||
/// or `add_transport()` instead.
|
||||
/// or `add_or_update_transport()` instead.
|
||||
async fn configure(&self, account_id: u32) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
ctx.stop_io().await;
|
||||
@@ -483,21 +483,30 @@ impl CommandApi {
|
||||
/// from a server encoded in a QR code.
|
||||
/// - [Self::list_transports()] to get a list of all configured transports.
|
||||
/// - [Self::delete_transport()] to remove a transport.
|
||||
async fn add_transport(&self, account_id: u32, param: EnteredLoginParam) -> Result<()> {
|
||||
async fn add_or_update_transport(
|
||||
&self,
|
||||
account_id: u32,
|
||||
param: EnteredLoginParam,
|
||||
) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
ctx.add_transport(¶m.try_into()?).await
|
||||
ctx.add_or_update_transport(¶m.try_into()?).await
|
||||
}
|
||||
|
||||
/// Deprecated 2025-04. Alias for [Self::add_or_update_transport()].
|
||||
async fn add_transport(&self, account_id: u32, param: EnteredLoginParam) -> Result<()> {
|
||||
self.add_or_update_transport(account_id, param).await
|
||||
}
|
||||
|
||||
/// Adds a new email account as a transport
|
||||
/// using the server encoded in the QR code.
|
||||
/// See [Self::add_transport].
|
||||
/// See [Self::add_or_update_transport].
|
||||
async fn add_transport_from_qr(&self, account_id: u32, qr: String) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
ctx.add_transport_from_qr(&qr).await
|
||||
}
|
||||
|
||||
/// Returns the list of all email accounts that are used as a transport in the current profile.
|
||||
/// Use [Self::add_transport()] to add or change a transport
|
||||
/// Use [Self::add_or_update_transport()] to add or change a transport
|
||||
/// and [Self::delete_transport()] to delete a transport.
|
||||
async fn list_transports(&self, account_id: u32) -> Result<Vec<EnteredLoginParam>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
|
||||
@@ -111,9 +111,9 @@ class Account:
|
||||
yield self._rpc.configure.future(self.id)
|
||||
|
||||
@futuremethod
|
||||
def add_transport(self, params):
|
||||
def add_or_update_transport(self, params):
|
||||
"""Add a new transport."""
|
||||
yield self._rpc.add_transport.future(self.id, params)
|
||||
yield self._rpc.add_or_update_transport.future(self.id, params)
|
||||
|
||||
def bring_online(self):
|
||||
"""Start I/O and wait until IMAP becomes IDLE."""
|
||||
|
||||
@@ -34,7 +34,7 @@ class ACFactory:
|
||||
addr, password = self.get_credentials()
|
||||
account = self.get_unconfigured_account()
|
||||
params = {"addr": addr, "password": password}
|
||||
yield account.add_transport.future(params)
|
||||
yield account.add_or_update_transport.future(params)
|
||||
|
||||
assert account.is_configured()
|
||||
return account
|
||||
|
||||
@@ -17,7 +17,7 @@ def test_event_on_configuration(acfactory: ACFactory) -> None:
|
||||
account = acfactory.get_unconfigured_account()
|
||||
account.clear_all_events()
|
||||
assert not account.is_configured()
|
||||
future = account.add_transport.future({"addr": addr, "password": password})
|
||||
future = account.add_or_update_transport.future({"addr": addr, "password": password})
|
||||
while True:
|
||||
event = account.wait_for_event()
|
||||
if event.kind == EventType.ACCOUNTS_ITEM_CHANGED:
|
||||
|
||||
@@ -63,7 +63,7 @@ def test_acfactory(acfactory) -> None:
|
||||
def test_configure_starttls(acfactory) -> None:
|
||||
addr, password = acfactory.get_credentials()
|
||||
account = acfactory.get_unconfigured_account()
|
||||
account.add_transport(
|
||||
account.add_or_update_transport(
|
||||
{
|
||||
"addr": addr,
|
||||
"password": password,
|
||||
@@ -80,7 +80,7 @@ def test_configure_ip(acfactory) -> None:
|
||||
ip_address = socket.gethostbyname(addr.rsplit("@")[-1])
|
||||
|
||||
with pytest.raises(JsonRpcError):
|
||||
account.add_transport(
|
||||
account.add_or_update_transport(
|
||||
{
|
||||
"addr": addr,
|
||||
"password": password,
|
||||
@@ -94,7 +94,7 @@ def test_configure_alternative_port(acfactory) -> None:
|
||||
"""Test that configuration with alternative port 443 works."""
|
||||
addr, password = acfactory.get_credentials()
|
||||
account = acfactory.get_unconfigured_account()
|
||||
account.add_transport(
|
||||
account.add_or_update_transport(
|
||||
{
|
||||
"addr": addr,
|
||||
"password": password,
|
||||
@@ -108,7 +108,7 @@ def test_configure_alternative_port(acfactory) -> None:
|
||||
def test_list_transports(acfactory) -> None:
|
||||
addr, password = acfactory.get_credentials()
|
||||
account = acfactory.get_unconfigured_account()
|
||||
account.add_transport(
|
||||
account.add_or_update_transport(
|
||||
{
|
||||
"addr": addr,
|
||||
"password": password,
|
||||
@@ -420,7 +420,7 @@ def test_wait_next_messages(acfactory) -> None:
|
||||
addr, password = acfactory.get_credentials()
|
||||
bot = acfactory.get_unconfigured_account()
|
||||
bot.set_config("bot", "1")
|
||||
bot.add_transport({"addr": addr, "password": password})
|
||||
bot.add_or_update_transport({"addr": addr, "password": password})
|
||||
assert bot.is_configured()
|
||||
|
||||
# There are no old messages and the call returns immediately.
|
||||
@@ -603,7 +603,7 @@ def test_reactions_for_a_reordering_move(acfactory, direct_imap):
|
||||
|
||||
addr, password = acfactory.get_credentials()
|
||||
ac2 = acfactory.get_unconfigured_account()
|
||||
ac2.add_transport({"addr": addr, "password": password})
|
||||
ac2.add_or_update_transport({"addr": addr, "password": password})
|
||||
ac2.set_config("mvbox_move", "1")
|
||||
assert ac2.is_configured()
|
||||
|
||||
@@ -713,12 +713,11 @@ def test_get_http_response(acfactory):
|
||||
|
||||
def test_configured_imap_certificate_checks(acfactory):
|
||||
alice = acfactory.new_configured_account()
|
||||
configured_certificate_checks = alice.get_config("configured_imap_certificate_checks")
|
||||
|
||||
# Certificate checks should be configured (not None)
|
||||
assert configured_certificate_checks
|
||||
assert "cert_automatic" in alice.get_info().used_account_settings
|
||||
|
||||
# 0 is the value old Delta Chat core versions used
|
||||
# "cert_old_automatic" is the value old Delta Chat core versions used
|
||||
# to mean user entered "imap_certificate_checks=0" (Automatic)
|
||||
# and configuration failed to use strict TLS checks
|
||||
# so it switched strict TLS checks off.
|
||||
@@ -729,7 +728,7 @@ def test_configured_imap_certificate_checks(acfactory):
|
||||
#
|
||||
# Core 1.142.4, 1.142.5 and 1.142.6 saved this value due to bug.
|
||||
# This test is a regression test to prevent this happening again.
|
||||
assert configured_certificate_checks != "0"
|
||||
assert "cert_old_automatic" not in alice.get_info().used_account_settings
|
||||
|
||||
|
||||
def test_no_old_msg_is_fresh(acfactory):
|
||||
|
||||
@@ -482,12 +482,8 @@ class ACFactory:
|
||||
addr = f"{acname}@offline.org"
|
||||
ac.update_config(
|
||||
{
|
||||
"addr": addr,
|
||||
"displayname": acname,
|
||||
"mail_pw": "123",
|
||||
"configured_addr": addr,
|
||||
"configured_mail_pw": "123",
|
||||
"configured": "1",
|
||||
"displayname": acname,
|
||||
},
|
||||
)
|
||||
self._preconfigure_key(ac)
|
||||
|
||||
@@ -1502,15 +1502,6 @@ def test_connectivity(acfactory, lp):
|
||||
assert len(msgs) == 2
|
||||
assert msgs[1].text == "Hi 2"
|
||||
|
||||
lp.sec("Test that the connectivity is NOT_CONNECTED if the password is wrong")
|
||||
|
||||
ac1.set_config("configured_mail_pw", "abc")
|
||||
ac1.stop_io()
|
||||
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_NOT_CONNECTED)
|
||||
ac1.start_io()
|
||||
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_CONNECTING)
|
||||
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_NOT_CONNECTED)
|
||||
|
||||
|
||||
def test_fetch_deleted_msg(acfactory, lp):
|
||||
"""This is a regression test: Messages with \\Deleted flag were downloaded again and again,
|
||||
|
||||
@@ -2553,10 +2553,8 @@ async fn test_broadcast() -> Result<()> {
|
||||
let sent_msg = alice.pop_sent_msg().await;
|
||||
let msg = bob.parse_msg(&sent_msg).await;
|
||||
assert!(msg.was_encrypted());
|
||||
assert!(msg
|
||||
.get_header(HeaderDef::ChatGroupMemberTimestamps)
|
||||
.is_none());
|
||||
assert!(msg.get_header(HeaderDef::AutocryptGossip).is_none());
|
||||
assert!(!msg.header_exists(HeaderDef::ChatGroupMemberTimestamps));
|
||||
assert!(!msg.header_exists(HeaderDef::AutocryptGossip));
|
||||
let msg = bob.recv_msg(&sent_msg).await;
|
||||
assert_eq!(msg.get_text(), "ola!");
|
||||
assert_eq!(msg.subject, "Broadcast list");
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::env;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{ensure, Context as _, Result};
|
||||
use anyhow::{bail, ensure, Context as _, Result};
|
||||
use base64::Engine as _;
|
||||
use deltachat_contact_tools::{addr_cmp, sanitize_single_line};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -13,10 +13,12 @@ use strum_macros::{AsRefStr, Display, EnumIter, EnumString};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::blob::BlobObject;
|
||||
use crate::configure::EnteredLoginParam;
|
||||
use crate::constants;
|
||||
use crate::context::Context;
|
||||
use crate::events::EventType;
|
||||
use crate::log::LogExt;
|
||||
use crate::login_param::ConfiguredLoginParam;
|
||||
use crate::mimefactory::RECOMMENDED_FILE_SIZE;
|
||||
use crate::provider::{get_provider_by_id, Provider};
|
||||
use crate::sync::{self, Sync::*, SyncData};
|
||||
@@ -525,21 +527,22 @@ impl Context {
|
||||
// Default values
|
||||
let val = match key {
|
||||
Config::BccSelf => match Box::pin(self.is_chatmail()).await? {
|
||||
false => Some("1"),
|
||||
true => Some("0"),
|
||||
false => Some("1".to_string()),
|
||||
true => Some("0".to_string()),
|
||||
},
|
||||
Config::ConfiguredInboxFolder => Some("INBOX"),
|
||||
Config::ConfiguredInboxFolder => Some("INBOX".to_string()),
|
||||
Config::DeleteServerAfter => {
|
||||
match !Box::pin(self.get_config_bool(Config::BccSelf)).await?
|
||||
&& Box::pin(self.is_chatmail()).await?
|
||||
{
|
||||
true => Some("1"),
|
||||
false => Some("0"),
|
||||
true => Some("1".to_string()),
|
||||
false => Some("0".to_string()),
|
||||
}
|
||||
}
|
||||
_ => key.get_str("default"),
|
||||
Config::Addr => self.get_config_opt(Config::ConfiguredAddr).await?,
|
||||
_ => key.get_str("default").map(|s| s.to_string()),
|
||||
};
|
||||
Ok(val.map(|s| s.to_string()))
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
/// Returns Some(T) if a value for the given key is set and was successfully parsed.
|
||||
@@ -805,6 +808,19 @@ impl Context {
|
||||
.set_raw_config(constants::DC_FOLDERS_CONFIGURED_KEY, None)
|
||||
.await?;
|
||||
}
|
||||
Config::ConfiguredAddr => {
|
||||
if self.is_configured().await? {
|
||||
bail!("Cannot change ConfiguredAddr");
|
||||
}
|
||||
if let Some(addr) = value {
|
||||
info!(self, "Creating a pseudo configured account which will not be able to send or receive messages. Only meant for tests!");
|
||||
ConfiguredLoginParam::from_json(&format!(
|
||||
r#"{{"addr":"{addr}","imap":[],"imap_user":"","imap_password":"","smtp":[],"smtp_user":"","smtp_password":"","certificate_checks":"Automatic","oauth2":false}}"#
|
||||
))?
|
||||
.save_to_transports_table(self, &EnteredLoginParam::default())
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.sql.set_raw_config(key.as_ref(), value).await?;
|
||||
}
|
||||
@@ -891,6 +907,7 @@ impl Context {
|
||||
/// primary address (if exists) as a secondary address.
|
||||
///
|
||||
/// This should only be used by test code and during configure.
|
||||
#[cfg(test)] // AEAP is disabled, but there are still tests for it
|
||||
pub(crate) async fn set_primary_self_addr(&self, primary_new: &str) -> Result<()> {
|
||||
self.quota.write().await.take();
|
||||
|
||||
@@ -904,7 +921,8 @@ impl Context {
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.set_config_internal(Config::ConfiguredAddr, Some(primary_new))
|
||||
self.sql
|
||||
.set_raw_config(Config::ConfiguredAddr.as_ref(), Some(primary_new))
|
||||
.await?;
|
||||
self.emit_event(EventType::ConnectivityChanged);
|
||||
Ok(())
|
||||
|
||||
@@ -35,8 +35,7 @@ use crate::login_param::{
|
||||
};
|
||||
use crate::message::Message;
|
||||
use crate::oauth2::get_oauth2_addr;
|
||||
use crate::provider::{Protocol, Socket, UsernamePattern};
|
||||
use crate::qr::set_account_from_qr;
|
||||
use crate::provider::{Protocol, Provider, Socket, UsernamePattern};
|
||||
use crate::smtp::Smtp;
|
||||
use crate::sync::Sync::*;
|
||||
use crate::tools::time;
|
||||
@@ -63,13 +62,13 @@ macro_rules! progress {
|
||||
impl Context {
|
||||
/// Checks if the context is already configured.
|
||||
pub async fn is_configured(&self) -> Result<bool> {
|
||||
self.sql.get_raw_config_bool("configured").await
|
||||
self.sql.exists("SELECT COUNT(*) FROM transports", ()).await
|
||||
}
|
||||
|
||||
/// Configures this account with the currently provided parameters.
|
||||
///
|
||||
/// Deprecated since 2025-02; use `add_transport_from_qr()`
|
||||
/// or `add_transport()` instead.
|
||||
/// or `add_or_update_transport()` instead.
|
||||
pub async fn configure(&self) -> Result<()> {
|
||||
let param = EnteredLoginParam::load(self).await?;
|
||||
|
||||
@@ -105,7 +104,7 @@ impl Context {
|
||||
/// from a server encoded in a QR code.
|
||||
/// - [Self::list_transports()] to get a list of all configured transports.
|
||||
/// - [Self::delete_transport()] to remove a transport.
|
||||
pub async fn add_transport(&self, param: &EnteredLoginParam) -> Result<()> {
|
||||
pub async fn add_or_update_transport(&self, param: &EnteredLoginParam) -> Result<()> {
|
||||
self.stop_io().await;
|
||||
let result = self.add_transport_inner(param).await;
|
||||
if result.is_err() {
|
||||
@@ -156,12 +155,23 @@ impl Context {
|
||||
|
||||
/// Adds a new email account as a transport
|
||||
/// using the server encoded in the QR code.
|
||||
/// See [Self::add_transport].
|
||||
/// See [Self::add_or_update_transport].
|
||||
pub async fn add_transport_from_qr(&self, qr: &str) -> Result<()> {
|
||||
self.stop_io().await;
|
||||
|
||||
// This code first sets the deprecated Config::Addr, Config::MailPw, etc.
|
||||
// and then calls configure(), which loads them again.
|
||||
// At some point, we will remove configure()
|
||||
// and then simplify the code
|
||||
// to directly create an EnteredLoginParam.
|
||||
let result = async move {
|
||||
set_account_from_qr(self, qr).await?;
|
||||
match crate::qr::check_qr(self, qr).await? {
|
||||
crate::qr::Qr::Account { .. } => crate::qr::set_account_from_qr(self, qr).await?,
|
||||
crate::qr::Qr::Login { address, options } => {
|
||||
crate::qr::configure_from_login_qr(self, &address, options).await?
|
||||
}
|
||||
_ => bail!("QR code does not contain account"),
|
||||
}
|
||||
self.configure().await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -178,12 +188,24 @@ impl Context {
|
||||
}
|
||||
|
||||
/// Returns the list of all email accounts that are used as a transport in the current profile.
|
||||
/// Use [Self::add_transport()] to add or change a transport
|
||||
/// Use [Self::add_or_update_transport()] to add or change a transport
|
||||
/// and [Self::delete_transport()] to delete a transport.
|
||||
pub async fn list_transports(&self) -> Result<Vec<EnteredLoginParam>> {
|
||||
let param = EnteredLoginParam::load(self).await?;
|
||||
let transports = self
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT entered_param FROM transports",
|
||||
(),
|
||||
|row| row.get::<_, String>(0),
|
||||
|rows| {
|
||||
rows.flatten()
|
||||
.map(|s| Ok(serde_json::from_str(&s)?))
|
||||
.collect::<Result<Vec<EnteredLoginParam>>>()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(vec![param])
|
||||
Ok(transports)
|
||||
}
|
||||
|
||||
/// Removes the transport with the specified email address
|
||||
@@ -197,20 +219,20 @@ impl Context {
|
||||
info!(self, "Configure ...");
|
||||
|
||||
let old_addr = self.get_config(Config::ConfiguredAddr).await?;
|
||||
let configured_param = configure(self, param).await?;
|
||||
let provider = configure(self, param).await?;
|
||||
self.set_config_internal(Config::NotifyAboutWrongPw, Some("1"))
|
||||
.await?;
|
||||
on_configure_completed(self, configured_param, old_addr).await?;
|
||||
on_configure_completed(self, provider, old_addr).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_configure_completed(
|
||||
context: &Context,
|
||||
param: ConfiguredLoginParam,
|
||||
provider: Option<&'static Provider>,
|
||||
old_addr: Option<String>,
|
||||
) -> Result<()> {
|
||||
if let Some(provider) = param.provider {
|
||||
if let Some(provider) = provider {
|
||||
if let Some(config_defaults) = provider.config_defaults {
|
||||
for def in config_defaults {
|
||||
if !context.config_exists(def.key).await? {
|
||||
@@ -446,7 +468,7 @@ async fn get_configured_param(
|
||||
Ok(configured_login_param)
|
||||
}
|
||||
|
||||
async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<ConfiguredLoginParam> {
|
||||
async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Option<&'static Provider>> {
|
||||
progress!(ctx, 1);
|
||||
|
||||
let ctx2 = ctx.clone();
|
||||
@@ -556,7 +578,11 @@ async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Configure
|
||||
}
|
||||
}
|
||||
|
||||
configured_param.save_as_configured_params(ctx).await?;
|
||||
let provider = configured_param.provider;
|
||||
configured_param
|
||||
.save_to_transports_table(ctx, param)
|
||||
.await?;
|
||||
|
||||
ctx.set_config_internal(Config::ConfiguredTimestamp, Some(&time().to_string()))
|
||||
.await?;
|
||||
|
||||
@@ -572,7 +598,7 @@ async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Configure
|
||||
ctx.sql.set_raw_config_bool("configured", true).await?;
|
||||
ctx.emit_event(EventType::AccountsItemChanged);
|
||||
|
||||
Ok(configured_param)
|
||||
Ok(provider)
|
||||
}
|
||||
|
||||
/// Retrieve available autoconfigurations.
|
||||
|
||||
@@ -48,12 +48,13 @@ impl EncryptHelper {
|
||||
context: &Context,
|
||||
peerstates: &[(Option<Peerstate>, String)],
|
||||
) -> Result<bool> {
|
||||
let is_chatmail = context.is_chatmail().await?;
|
||||
// For chatmail we ignore the encryption preference,
|
||||
// because we can either send encrypted or not at all.
|
||||
let encrypt_if_possible = context.is_chatmail().await?
|
||||
&& !context.get_config_bool(Config::SignUnencrypted).await?;
|
||||
for (peerstate, _addr) in peerstates {
|
||||
if let Some(peerstate) = peerstate {
|
||||
// For chatmail we ignore the encryption preference,
|
||||
// because we can either send encrypted or not at all.
|
||||
if is_chatmail || peerstate.prefer_encrypt != EncryptPreference::Reset {
|
||||
if encrypt_if_possible || peerstate.prefer_encrypt != EncryptPreference::Reset {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,26 +2,38 @@
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::{format_err, Context as _, Result};
|
||||
use deltachat_contact_tools::EmailAddress;
|
||||
use anyhow::{bail, ensure, format_err, Context as _, Result};
|
||||
use deltachat_contact_tools::{addr_cmp, addr_normalize, EmailAddress};
|
||||
use num_traits::ToPrimitive as _;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::configure::server_params::{expand_param_vector, ServerParams};
|
||||
use crate::constants::{DC_LP_AUTH_FLAGS, DC_LP_AUTH_NORMAL, DC_LP_AUTH_OAUTH2};
|
||||
use crate::constants::{DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2};
|
||||
use crate::context::Context;
|
||||
use crate::net::load_connection_timestamp;
|
||||
pub use crate::net::proxy::ProxyConfig;
|
||||
pub use crate::provider::Socket;
|
||||
use crate::provider::{Protocol, Provider, UsernamePattern};
|
||||
use crate::provider::{get_provider_by_id, Protocol, Provider, UsernamePattern};
|
||||
use crate::sql::Sql;
|
||||
use crate::tools::ToOption;
|
||||
|
||||
/// User-entered setting for certificate checks.
|
||||
///
|
||||
/// Should be saved into `imap_certificate_checks` before running configuration.
|
||||
#[derive(Copy, Clone, Debug, Default, Display, FromPrimitive, ToPrimitive, PartialEq, Eq)]
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Default,
|
||||
Display,
|
||||
FromPrimitive,
|
||||
ToPrimitive,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
#[repr(u32)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum EnteredCertificateChecks {
|
||||
@@ -44,7 +56,9 @@ pub enum EnteredCertificateChecks {
|
||||
}
|
||||
|
||||
/// Values saved into `imap_certificate_checks`.
|
||||
#[derive(Copy, Clone, Debug, Display, FromPrimitive, ToPrimitive, PartialEq, Eq)]
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Display, FromPrimitive, ToPrimitive, PartialEq, Eq, Serialize, Deserialize,
|
||||
)]
|
||||
#[repr(u32)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub(crate) enum ConfiguredCertificateChecks {
|
||||
@@ -81,7 +95,7 @@ pub(crate) enum ConfiguredCertificateChecks {
|
||||
}
|
||||
|
||||
/// Login parameters for a single server, either IMAP or SMTP
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct EnteredServerLoginParam {
|
||||
/// Server hostname or IP address.
|
||||
pub server: String,
|
||||
@@ -104,7 +118,7 @@ pub struct EnteredServerLoginParam {
|
||||
}
|
||||
|
||||
/// Login parameters entered by the user.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct EnteredLoginParam {
|
||||
/// Email address.
|
||||
pub addr: String,
|
||||
@@ -451,6 +465,22 @@ pub(crate) struct ConfiguredLoginParam {
|
||||
pub oauth2: bool,
|
||||
}
|
||||
|
||||
/// The representation of ConfiguredLoginParam in the database,
|
||||
/// saved as Json.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct ConfiguredLoginParamJson {
|
||||
pub addr: String,
|
||||
pub imap: Vec<ConfiguredServerLoginParam>,
|
||||
pub imap_user: String,
|
||||
pub imap_password: String,
|
||||
pub smtp: Vec<ConfiguredServerLoginParam>,
|
||||
pub smtp_user: String,
|
||||
pub smtp_password: String,
|
||||
pub provider_id: Option<String>,
|
||||
pub certificate_checks: ConfiguredCertificateChecks,
|
||||
pub oauth2: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for ConfiguredLoginParam {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let addr = &self.addr;
|
||||
@@ -487,6 +517,26 @@ impl ConfiguredLoginParam {
|
||||
///
|
||||
/// Returns `None` if account is not configured.
|
||||
pub(crate) async fn load(context: &Context) -> Result<Option<Self>> {
|
||||
let Some(self_addr) = context.get_config(Config::ConfiguredAddr).await? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let json: Option<String> = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
"SELECT configured_param FROM transports WHERE addr=?",
|
||||
(&self_addr,),
|
||||
)
|
||||
.await?;
|
||||
if let Some(json) = json {
|
||||
Ok(Some(Self::from_json(&json)?))
|
||||
} else {
|
||||
bail!("Self address {self_addr} doesn't have a corresponding transport");
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads legacy configured param. Only used for tests and the migration.
|
||||
pub(crate) async fn load_legacy(context: &Context) -> Result<Option<Self>> {
|
||||
if !context.get_config_bool(Config::Configured).await? {
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -753,84 +803,82 @@ impl ConfiguredLoginParam {
|
||||
}))
|
||||
}
|
||||
|
||||
/// Save this loginparam to the database.
|
||||
pub(crate) async fn save_as_configured_params(&self, context: &Context) -> Result<()> {
|
||||
context.set_primary_self_addr(&self.addr).await?;
|
||||
|
||||
pub(crate) async fn save_to_transports_table(
|
||||
self,
|
||||
context: &Context,
|
||||
entered_param: &EnteredLoginParam,
|
||||
) -> Result<()> {
|
||||
let addr = addr_normalize(&self.addr);
|
||||
let configured_addr = context.get_config(Config::ConfiguredAddr).await?;
|
||||
if let Some(configured_addr) = configured_addr {
|
||||
ensure!(
|
||||
addr_cmp(&configured_addr, &addr,),
|
||||
"Adding a second transport is not supported right now."
|
||||
);
|
||||
}
|
||||
context
|
||||
.set_config(
|
||||
Config::ConfiguredImapServers,
|
||||
Some(&serde_json::to_string(&self.imap)?),
|
||||
)
|
||||
.await?;
|
||||
context
|
||||
.set_config(
|
||||
Config::ConfiguredSmtpServers,
|
||||
Some(&serde_json::to_string(&self.smtp)?),
|
||||
)
|
||||
.await?;
|
||||
|
||||
context
|
||||
.set_config(Config::ConfiguredMailUser, Some(&self.imap_user))
|
||||
.await?;
|
||||
context
|
||||
.set_config(Config::ConfiguredMailPw, Some(&self.imap_password))
|
||||
.await?;
|
||||
|
||||
context
|
||||
.set_config(Config::ConfiguredSendUser, Some(&self.smtp_user))
|
||||
.await?;
|
||||
context
|
||||
.set_config(Config::ConfiguredSendPw, Some(&self.smtp_password))
|
||||
.await?;
|
||||
|
||||
context
|
||||
.set_config_u32(
|
||||
Config::ConfiguredImapCertificateChecks,
|
||||
self.certificate_checks as u32,
|
||||
)
|
||||
.await?;
|
||||
context
|
||||
.set_config_u32(
|
||||
Config::ConfiguredSmtpCertificateChecks,
|
||||
self.certificate_checks as u32,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Remove legacy settings.
|
||||
context
|
||||
.set_config(Config::ConfiguredMailServer, None)
|
||||
.await?;
|
||||
context.set_config(Config::ConfiguredMailPort, None).await?;
|
||||
context
|
||||
.set_config(Config::ConfiguredMailSecurity, None)
|
||||
.await?;
|
||||
context
|
||||
.set_config(Config::ConfiguredSendServer, None)
|
||||
.await?;
|
||||
context.set_config(Config::ConfiguredSendPort, None).await?;
|
||||
context
|
||||
.set_config(Config::ConfiguredSendSecurity, None)
|
||||
.await?;
|
||||
|
||||
let server_flags = match self.oauth2 {
|
||||
true => DC_LP_AUTH_OAUTH2,
|
||||
false => DC_LP_AUTH_NORMAL,
|
||||
};
|
||||
context
|
||||
.set_config_u32(Config::ConfiguredServerFlags, server_flags as u32)
|
||||
.await?;
|
||||
|
||||
context
|
||||
.set_config(
|
||||
Config::ConfiguredProvider,
|
||||
.sql
|
||||
.set_raw_config(
|
||||
Config::ConfiguredProvider.as_ref(),
|
||||
self.provider.map(|provider| provider.id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"INSERT INTO transports (addr, entered_param, configured_param)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT (addr)
|
||||
DO UPDATE SET entered_param=excluded.entered_param, configured_param=excluded.configured_param",
|
||||
(
|
||||
self.addr.clone(),
|
||||
serde_json::to_string(entered_param)?,
|
||||
self.into_json()?,
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(Config::ConfiguredAddr.as_ref(), Some(&addr))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn from_json(json: &str) -> Result<Self> {
|
||||
let json: ConfiguredLoginParamJson = serde_json::from_str(json)?;
|
||||
|
||||
let provider = json.provider_id.and_then(|id| get_provider_by_id(&id));
|
||||
|
||||
Ok(ConfiguredLoginParam {
|
||||
addr: json.addr,
|
||||
imap: json.imap,
|
||||
imap_user: json.imap_user,
|
||||
imap_password: json.imap_password,
|
||||
smtp: json.smtp,
|
||||
smtp_user: json.smtp_user,
|
||||
smtp_password: json.smtp_password,
|
||||
provider,
|
||||
certificate_checks: json.certificate_checks,
|
||||
oauth2: json.oauth2,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn into_json(self) -> Result<String> {
|
||||
let json = ConfiguredLoginParamJson {
|
||||
addr: self.addr,
|
||||
imap: self.imap,
|
||||
imap_user: self.imap_user,
|
||||
imap_password: self.imap_password,
|
||||
smtp: self.smtp,
|
||||
smtp_user: self.smtp_user,
|
||||
smtp_password: self.smtp_password,
|
||||
provider_id: self.provider.map(|p| p.id.to_string()),
|
||||
certificate_checks: self.certificate_checks,
|
||||
oauth2: self.oauth2,
|
||||
};
|
||||
Ok(serde_json::to_string(&json)?)
|
||||
}
|
||||
|
||||
pub(crate) fn strict_tls(&self, connected_through_proxy: bool) -> bool {
|
||||
let provider_strict_tls = self.provider.map(|provider| provider.opt.strict_tls);
|
||||
match self.certificate_checks {
|
||||
@@ -848,8 +896,10 @@ impl ConfiguredLoginParam {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::log::LogExt as _;
|
||||
use crate::provider::get_provider_by_id;
|
||||
use crate::test_utils::TestContext;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_certificate_checks_display() {
|
||||
@@ -961,18 +1011,33 @@ mod tests {
|
||||
oauth2: false,
|
||||
};
|
||||
|
||||
param.save_as_configured_params(&t).await?;
|
||||
param
|
||||
.clone()
|
||||
.save_to_transports_table(&t, &EnteredLoginParam::default())
|
||||
.await?;
|
||||
let expected_param = r#"{"addr":"alice@example.org","imap":[{"connection":{"host":"imap.example.com","port":123,"security":"Starttls"},"user":"alice"}],"imap_user":"","imap_password":"foo","smtp":[{"connection":{"host":"smtp.example.com","port":456,"security":"Tls"},"user":"alice@example.org"}],"smtp_user":"","smtp_password":"bar","provider_id":null,"certificate_checks":"Strict","oauth2":false}"#;
|
||||
assert_eq!(
|
||||
t.get_config(Config::ConfiguredImapServers).await?.unwrap(),
|
||||
r#"[{"connection":{"host":"imap.example.com","port":123,"security":"Starttls"},"user":"alice"}]"#
|
||||
t.sql
|
||||
.query_get_value::<String>("SELECT configured_param FROM transports", ())
|
||||
.await?
|
||||
.unwrap(),
|
||||
expected_param
|
||||
);
|
||||
t.set_config(Config::Configured, Some("1")).await?;
|
||||
assert_eq!(t.is_configured().await?, true);
|
||||
let loaded = ConfiguredLoginParam::load(&t).await?.unwrap();
|
||||
assert_eq!(param, loaded);
|
||||
|
||||
// Test that we don't panic on unknown ConfiguredImapCertificateChecks values.
|
||||
// Legacy ConfiguredImapCertificateChecks config is ignored
|
||||
t.set_config(Config::ConfiguredImapCertificateChecks, Some("999"))
|
||||
.await?;
|
||||
assert!(ConfiguredLoginParam::load(&t).await.is_ok());
|
||||
|
||||
// Test that we don't panic on unknown ConfiguredImapCertificateChecks values.
|
||||
let wrong_param = expected_param.replace("Strict", "Stricct");
|
||||
assert_ne!(expected_param, wrong_param);
|
||||
t.sql
|
||||
.execute("UPDATE transports SET configured_param=?", (wrong_param,))
|
||||
.await?;
|
||||
assert!(ConfiguredLoginParam::load(&t).await.is_err());
|
||||
|
||||
Ok(())
|
||||
@@ -989,7 +1054,8 @@ mod tests {
|
||||
t.set_config(Config::Configured, Some("1")).await?;
|
||||
t.set_config(Config::ConfiguredProvider, Some("posteo"))
|
||||
.await?;
|
||||
t.set_config(Config::ConfiguredAddr, Some("alice@posteo.at"))
|
||||
t.sql
|
||||
.set_raw_config(Config::ConfiguredAddr.as_ref(), Some("alice@posteo.at"))
|
||||
.await?;
|
||||
t.set_config(Config::ConfiguredMailServer, Some("posteo.de"))
|
||||
.await?;
|
||||
@@ -1063,12 +1129,68 @@ mod tests {
|
||||
oauth2: false,
|
||||
};
|
||||
|
||||
let loaded = ConfiguredLoginParam::load_legacy(&t).await?.unwrap();
|
||||
assert_eq!(loaded, param);
|
||||
|
||||
migrate_configured_login_param(&t).await;
|
||||
let loaded = ConfiguredLoginParam::load(&t).await?.unwrap();
|
||||
assert_eq!(loaded, param);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_empty_server_list_legacy() -> Result<()> {
|
||||
// Find a provider that does not have server list set.
|
||||
//
|
||||
// There is at least one such provider in the provider database.
|
||||
let (domain, provider) = crate::provider::data::PROVIDER_DATA
|
||||
.iter()
|
||||
.find(|(_domain, provider)| provider.server.is_empty())
|
||||
.unwrap();
|
||||
|
||||
let t = TestContext::new().await;
|
||||
|
||||
let addr = format!("alice@{domain}");
|
||||
|
||||
t.set_config(Config::Configured, Some("1")).await?;
|
||||
t.set_config(Config::ConfiguredProvider, Some(provider.id))
|
||||
.await?;
|
||||
t.sql
|
||||
.set_raw_config(Config::ConfiguredAddr.as_ref(), Some(&addr))
|
||||
.await?;
|
||||
t.set_config(Config::ConfiguredMailPw, Some("foobarbaz"))
|
||||
.await?;
|
||||
t.set_config(Config::ConfiguredImapCertificateChecks, Some("1"))
|
||||
.await?; // Strict
|
||||
t.set_config(Config::ConfiguredSendPw, Some("foobarbaz"))
|
||||
.await?;
|
||||
t.set_config(Config::ConfiguredSmtpCertificateChecks, Some("1"))
|
||||
.await?; // Strict
|
||||
t.set_config(Config::ConfiguredServerFlags, Some("0"))
|
||||
.await?;
|
||||
|
||||
let loaded = ConfiguredLoginParam::load_legacy(&t).await?.unwrap();
|
||||
assert_eq!(loaded.provider, Some(*provider));
|
||||
assert_eq!(loaded.imap.is_empty(), false);
|
||||
assert_eq!(loaded.smtp.is_empty(), false);
|
||||
|
||||
migrate_configured_login_param(&t).await;
|
||||
|
||||
let loaded = ConfiguredLoginParam::load(&t).await?.unwrap();
|
||||
assert_eq!(loaded.provider, Some(*provider));
|
||||
assert_eq!(loaded.imap.is_empty(), false);
|
||||
assert_eq!(loaded.smtp.is_empty(), false);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn migrate_configured_login_param(t: &TestContext) {
|
||||
t.sql.execute("DROP TABLE transports;", ()).await.unwrap();
|
||||
t.sql.set_raw_config_int("dbversion", 130).await.unwrap();
|
||||
t.sql.run_migrations(t).await.log_err(t).ok();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_empty_server_list() -> Result<()> {
|
||||
// Find a provider that does not have server list set.
|
||||
@@ -1083,25 +1205,41 @@ mod tests {
|
||||
|
||||
let addr = format!("alice@{domain}");
|
||||
|
||||
t.set_config(Config::Configured, Some("1")).await?;
|
||||
t.set_config(Config::ConfiguredProvider, Some(provider.id))
|
||||
.await?;
|
||||
t.set_config(Config::ConfiguredAddr, Some(&addr)).await?;
|
||||
t.set_config(Config::ConfiguredMailPw, Some("foobarbaz"))
|
||||
.await?;
|
||||
t.set_config(Config::ConfiguredImapCertificateChecks, Some("1"))
|
||||
.await?; // Strict
|
||||
t.set_config(Config::ConfiguredSendPw, Some("foobarbaz"))
|
||||
.await?;
|
||||
t.set_config(Config::ConfiguredSmtpCertificateChecks, Some("1"))
|
||||
.await?; // Strict
|
||||
t.set_config(Config::ConfiguredServerFlags, Some("0"))
|
||||
.await?;
|
||||
ConfiguredLoginParam {
|
||||
addr: addr.clone(),
|
||||
imap: vec![ConfiguredServerLoginParam {
|
||||
connection: ConnectionCandidate {
|
||||
host: "example.org".to_string(),
|
||||
port: 100,
|
||||
security: ConnectionSecurity::Tls,
|
||||
},
|
||||
user: addr.clone(),
|
||||
}],
|
||||
imap_user: addr.clone(),
|
||||
imap_password: "foobarbaz".to_string(),
|
||||
smtp: vec![ConfiguredServerLoginParam {
|
||||
connection: ConnectionCandidate {
|
||||
host: "example.org".to_string(),
|
||||
port: 100,
|
||||
security: ConnectionSecurity::Tls,
|
||||
},
|
||||
user: addr.clone(),
|
||||
}],
|
||||
smtp_user: addr.clone(),
|
||||
smtp_password: "foobarbaz".to_string(),
|
||||
provider: Some(provider),
|
||||
certificate_checks: ConfiguredCertificateChecks::Automatic,
|
||||
oauth2: false,
|
||||
}
|
||||
.save_to_transports_table(&t, &EnteredLoginParam::default())
|
||||
.await?;
|
||||
|
||||
let loaded = ConfiguredLoginParam::load(&t).await?.unwrap();
|
||||
assert_eq!(loaded.provider, Some(*provider));
|
||||
assert_eq!(loaded.imap.is_empty(), false);
|
||||
assert_eq!(loaded.smtp.is_empty(), false);
|
||||
assert_eq!(t.get_configured_provider().await?, Some(*provider));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -893,10 +893,7 @@ async fn test_dont_remove_self() -> Result<()> {
|
||||
let mime_message = MimeMessage::from_bytes(alice, sent.payload.as_bytes(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
mime_message.get_header(HeaderDef::ChatGroupPastMembers),
|
||||
None
|
||||
);
|
||||
assert!(!mime_message.header_exists(HeaderDef::ChatGroupPastMembers));
|
||||
assert_eq!(
|
||||
mime_message.chat_group_member_timestamps().unwrap().len(),
|
||||
1 // There is a timestamp for Bob, not for Alice
|
||||
|
||||
@@ -57,6 +57,10 @@ pub(crate) struct MimeMessage {
|
||||
/// Message headers.
|
||||
headers: HashMap<String, String>,
|
||||
|
||||
#[cfg(test)]
|
||||
/// Names of removed (ignored) headers. Used by `header_exists()` needed for tests.
|
||||
headers_removed: HashSet<String>,
|
||||
|
||||
/// List of addresses from the `To` and `Cc` headers.
|
||||
///
|
||||
/// Addresses are normalized and lowercase.
|
||||
@@ -236,6 +240,7 @@ impl MimeMessage {
|
||||
let mut hop_info = parse_receive_headers(&mail.get_headers());
|
||||
|
||||
let mut headers = Default::default();
|
||||
let mut headers_removed = HashSet::<String>::new();
|
||||
let mut recipients = Default::default();
|
||||
let mut past_members = Default::default();
|
||||
let mut from = Default::default();
|
||||
@@ -253,7 +258,12 @@ impl MimeMessage {
|
||||
&mut chat_disposition_notification_to,
|
||||
&mail.headers,
|
||||
);
|
||||
headers.retain(|k, _| !is_hidden(k));
|
||||
headers.retain(|k, _| {
|
||||
!is_hidden(k) || {
|
||||
headers_removed.insert(k.clone());
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
// Parse hidden headers.
|
||||
let mimetype = mail.ctype.mimetype.parse::<Mime>()?;
|
||||
@@ -298,9 +308,11 @@ impl MimeMessage {
|
||||
// Overwrite Message-ID with X-Microsoft-Original-Message-ID.
|
||||
// However if we later find Message-ID in the protected part,
|
||||
// it will overwrite both.
|
||||
if let Some(microsoft_message_id) =
|
||||
headers.remove(HeaderDef::XMicrosoftOriginalMessageId.get_headername())
|
||||
{
|
||||
if let Some(microsoft_message_id) = remove_header(
|
||||
&mut headers,
|
||||
HeaderDef::XMicrosoftOriginalMessageId.get_headername(),
|
||||
&mut headers_removed,
|
||||
) {
|
||||
headers.insert(
|
||||
HeaderDef::MessageId.get_headername().to_string(),
|
||||
microsoft_message_id,
|
||||
@@ -309,7 +321,7 @@ impl MimeMessage {
|
||||
|
||||
// Remove headers that are allowed _only_ in the encrypted+signed part. It's ok to leave
|
||||
// them in signed-only emails, but has no value currently.
|
||||
Self::remove_secured_headers(&mut headers);
|
||||
Self::remove_secured_headers(&mut headers, &mut headers_removed);
|
||||
|
||||
let mut from = from.context("No from in message")?;
|
||||
let private_keyring = load_self_secret_keyring(context).await?;
|
||||
@@ -442,7 +454,7 @@ impl MimeMessage {
|
||||
HeaderDef::ChatEdit,
|
||||
HeaderDef::ChatUserAvatar,
|
||||
] {
|
||||
headers.remove(h.get_headername());
|
||||
remove_header(&mut headers, h.get_headername(), &mut headers_removed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,7 +518,7 @@ impl MimeMessage {
|
||||
}
|
||||
}
|
||||
if signatures.is_empty() {
|
||||
Self::remove_secured_headers(&mut headers);
|
||||
Self::remove_secured_headers(&mut headers, &mut headers_removed);
|
||||
|
||||
// If it is not a read receipt, degrade encryption.
|
||||
if let (Some(peerstate), Ok(mail)) = (&mut peerstate, mail) {
|
||||
@@ -530,6 +542,9 @@ impl MimeMessage {
|
||||
let mut parser = MimeMessage {
|
||||
parts: Vec::new(),
|
||||
headers,
|
||||
#[cfg(test)]
|
||||
headers_removed,
|
||||
|
||||
recipients,
|
||||
past_members,
|
||||
list_post,
|
||||
@@ -931,6 +946,16 @@ impl MimeMessage {
|
||||
.map(|s| s.as_str())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// Returns whether the header exists in any part of the parsed message.
|
||||
///
|
||||
/// Use this to check for header absense. Header presense should be checked using
|
||||
/// `get_header(...).is_some()` as it also checks that the header isn't ignored.
|
||||
pub(crate) fn header_exists(&self, headerdef: HeaderDef) -> bool {
|
||||
let hname = headerdef.get_headername();
|
||||
self.headers.contains_key(hname) || self.headers_removed.contains(hname)
|
||||
}
|
||||
|
||||
/// Returns `Chat-Group-ID` header value if it is a valid group ID.
|
||||
pub fn get_chat_group_id(&self) -> Option<&str> {
|
||||
self.get_header(HeaderDef::ChatGroupId)
|
||||
@@ -1526,14 +1551,17 @@ impl MimeMessage {
|
||||
.and_then(|msgid| parse_message_id(msgid).ok())
|
||||
}
|
||||
|
||||
fn remove_secured_headers(headers: &mut HashMap<String, String>) {
|
||||
headers.remove("secure-join-fingerprint");
|
||||
headers.remove("secure-join-auth");
|
||||
headers.remove("chat-verified");
|
||||
headers.remove("autocrypt-gossip");
|
||||
fn remove_secured_headers(
|
||||
headers: &mut HashMap<String, String>,
|
||||
removed: &mut HashSet<String>,
|
||||
) {
|
||||
remove_header(headers, "secure-join-fingerprint", removed);
|
||||
remove_header(headers, "secure-join-auth", removed);
|
||||
remove_header(headers, "chat-verified", removed);
|
||||
remove_header(headers, "autocrypt-gossip", removed);
|
||||
|
||||
// Secure-Join is secured unless it is an initial "vc-request"/"vg-request".
|
||||
if let Some(secure_join) = headers.remove("secure-join") {
|
||||
if let Some(secure_join) = remove_header(headers, "secure-join", removed) {
|
||||
if secure_join == "vc-request" || secure_join == "vg-request" {
|
||||
headers.insert("secure-join".to_string(), secure_join);
|
||||
}
|
||||
@@ -1861,6 +1889,19 @@ impl MimeMessage {
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_header(
|
||||
headers: &mut HashMap<String, String>,
|
||||
key: &str,
|
||||
removed: &mut HashSet<String>,
|
||||
) -> Option<String> {
|
||||
if let Some((k, v)) = headers.remove_entry(key) {
|
||||
removed.insert(k);
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses `Autocrypt-Gossip` headers from the email and applies them to peerstates.
|
||||
/// Params:
|
||||
/// from: The address which sent the message currently being parsed
|
||||
|
||||
@@ -5,6 +5,7 @@ pub(crate) mod data;
|
||||
use anyhow::Result;
|
||||
use deltachat_contact_tools::EmailAddress;
|
||||
use hickory_resolver::{config, Resolver, TokioResolver};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
@@ -37,7 +38,19 @@ pub enum Protocol {
|
||||
}
|
||||
|
||||
/// Socket security.
|
||||
#[derive(Debug, Default, Display, PartialEq, Eq, Copy, Clone, FromPrimitive, ToPrimitive)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Default,
|
||||
Display,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Copy,
|
||||
Clone,
|
||||
FromPrimitive,
|
||||
ToPrimitive,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
#[repr(u8)]
|
||||
pub enum Socket {
|
||||
/// Unspecified socket security, select automatically.
|
||||
|
||||
@@ -10,7 +10,7 @@ use deltachat_contact_tools::{addr_normalize, may_be_valid_addr, ContactAddress}
|
||||
use percent_encoding::{percent_decode_str, percent_encode, NON_ALPHANUMERIC};
|
||||
use serde::Deserialize;
|
||||
|
||||
use self::dclogin_scheme::configure_from_login_qr;
|
||||
pub(crate) use self::dclogin_scheme::configure_from_login_qr;
|
||||
use crate::chat::ChatIdBlocked;
|
||||
use crate::config::Config;
|
||||
use crate::constants::Blocked;
|
||||
|
||||
@@ -3113,7 +3113,6 @@ Message with references."#;
|
||||
async fn test_rfc1847_encapsulation() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
alice.configure_addr("alice@example.org").await;
|
||||
|
||||
// Alice sends an Autocrypt message to Bob so Bob gets Alice's key.
|
||||
let chat_alice = alice.create_chat(&bob).await;
|
||||
|
||||
@@ -120,7 +120,7 @@ async fn test_setup_contact_ex(case: SetupContactCase) {
|
||||
assert!(!msg.was_encrypted());
|
||||
assert_eq!(msg.get_header(HeaderDef::SecureJoin).unwrap(), "vc-request");
|
||||
assert!(msg.get_header(HeaderDef::SecureJoinInvitenumber).is_some());
|
||||
assert!(msg.get_header(HeaderDef::AutoSubmitted).is_none());
|
||||
assert!(!msg.header_exists(HeaderDef::AutoSubmitted));
|
||||
|
||||
// Step 3: Alice receives vc-request, sends vc-auth-required
|
||||
alice.recv_msg_trash(&sent).await;
|
||||
@@ -452,7 +452,7 @@ async fn test_setup_contact_bob_knows_alice() -> Result<()> {
|
||||
.expect("Error looking up contact")
|
||||
.expect("Contact not found");
|
||||
let contact_alice = Contact::get_by_id(&bob.ctx, contact_alice_id).await?;
|
||||
assert_eq!(contact_bob.is_verified(&bob.ctx).await?, false);
|
||||
assert_eq!(contact_alice.is_verified(&bob.ctx).await?, false);
|
||||
|
||||
// Step 7: Bob receives vc-contact-confirm
|
||||
bob.recv_msg_trash(&sent).await;
|
||||
@@ -523,7 +523,7 @@ async fn test_secure_join() -> Result<()> {
|
||||
assert!(!msg.was_encrypted());
|
||||
assert_eq!(msg.get_header(HeaderDef::SecureJoin).unwrap(), "vg-request");
|
||||
assert!(msg.get_header(HeaderDef::SecureJoinInvitenumber).is_some());
|
||||
assert!(msg.get_header(HeaderDef::AutoSubmitted).is_none());
|
||||
assert!(!msg.header_exists(HeaderDef::AutoSubmitted));
|
||||
|
||||
// Old Delta Chat core sent `Secure-Join-Group` header in `vg-request`,
|
||||
// but it was only used by Alice in `vg-request-with-auth`.
|
||||
@@ -531,7 +531,7 @@ async fn test_secure_join() -> Result<()> {
|
||||
// and it is deprecated.
|
||||
// Now `Secure-Join-Group` header
|
||||
// is only sent in `vg-request-with-auth` for compatibility.
|
||||
assert!(msg.get_header(HeaderDef::SecureJoinGroup).is_none());
|
||||
assert!(!msg.header_exists(HeaderDef::SecureJoinGroup));
|
||||
|
||||
// Step 3: Alice receives vg-request, sends vg-auth-required
|
||||
alice.recv_msg_trash(&sent).await;
|
||||
@@ -606,7 +606,7 @@ async fn test_secure_join() -> Result<()> {
|
||||
// Formally this message is auto-submitted, but as the member addition is a result of an
|
||||
// explicit user action, the Auto-Submitted header shouldn't be present. Otherwise it would
|
||||
// be strange to have it in "member-added" messages of verified groups only.
|
||||
assert!(msg.get_header(HeaderDef::AutoSubmitted).is_none());
|
||||
assert!(!msg.header_exists(HeaderDef::AutoSubmitted));
|
||||
// This is a two-member group, but Alice must Autocrypt-gossip to her other devices.
|
||||
assert!(msg.get_header(HeaderDef::AutocryptGossip).is_some());
|
||||
|
||||
@@ -636,7 +636,7 @@ async fn test_secure_join() -> Result<()> {
|
||||
.expect("Error looking up contact")
|
||||
.expect("Contact not found");
|
||||
let contact_alice = Contact::get_by_id(&bob.ctx, contact_alice_id).await?;
|
||||
assert_eq!(contact_bob.is_verified(&bob.ctx).await?, false);
|
||||
assert_eq!(contact_alice.is_verified(&bob.ctx).await?, false);
|
||||
|
||||
// Step 7: Bob receives vg-member-added
|
||||
bob.recv_msg(&sent).await;
|
||||
|
||||
@@ -5,9 +5,11 @@ use deltachat_contact_tools::EmailAddress;
|
||||
use rusqlite::OptionalExtension;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::configure::EnteredLoginParam;
|
||||
use crate::constants::ShowEmails;
|
||||
use crate::context::Context;
|
||||
use crate::imap;
|
||||
use crate::login_param::ConfiguredLoginParam;
|
||||
use crate::message::MsgId;
|
||||
use crate::provider::get_provider_by_domain;
|
||||
use crate::sql::Sql;
|
||||
@@ -1186,6 +1188,42 @@ CREATE INDEX gossip_timestamp_index ON gossip_timestamp (chat_id, fingerprint);
|
||||
.await?;
|
||||
}
|
||||
|
||||
inc_and_check(&mut migration_version, 131)?;
|
||||
if dbversion < migration_version {
|
||||
let entered_param = EnteredLoginParam::load(context).await?;
|
||||
let configured_param = ConfiguredLoginParam::load_legacy(context).await?;
|
||||
|
||||
sql.execute_migration_transaction(
|
||||
|transaction| {
|
||||
transaction.execute(
|
||||
"CREATE TABLE transports (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
addr TEXT NOT NULL,
|
||||
entered_param TEXT NOT NULL,
|
||||
configured_param TEXT NOT NULL,
|
||||
UNIQUE(addr)
|
||||
)",
|
||||
(),
|
||||
)?;
|
||||
if let Some(configured_param) = configured_param {
|
||||
transaction.execute(
|
||||
"INSERT INTO transports (addr, entered_param, configured_param)
|
||||
VALUES (?, ?, ?)",
|
||||
(
|
||||
configured_param.addr.clone(),
|
||||
serde_json::to_string(&entered_param)?,
|
||||
configured_param.into_json()?,
|
||||
),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
migration_version,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let new_version = sql
|
||||
.get_raw_config_int(VERSION_CFG)
|
||||
.await?
|
||||
@@ -1230,6 +1268,21 @@ impl Sql {
|
||||
}
|
||||
|
||||
async fn execute_migration(&self, query: &str, version: i32) -> Result<()> {
|
||||
self.execute_migration_transaction(
|
||||
|transaction| {
|
||||
transaction.execute_batch(query)?;
|
||||
Ok(())
|
||||
},
|
||||
version,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn execute_migration_transaction(
|
||||
&self,
|
||||
migration: impl Send + FnOnce(&mut rusqlite::Transaction) -> Result<()>,
|
||||
version: i32,
|
||||
) -> Result<()> {
|
||||
self.transaction(move |transaction| {
|
||||
let curr_version: String = transaction.query_row(
|
||||
"SELECT IFNULL(value, ?) FROM config WHERE keyname=?;",
|
||||
@@ -1239,7 +1292,7 @@ impl Sql {
|
||||
let curr_version: i32 = curr_version.parse()?;
|
||||
ensure!(curr_version < version, "Db version must be increased");
|
||||
Self::set_db_version_trans(transaction, version)?;
|
||||
transaction.execute_batch(query)?;
|
||||
migration(transaction)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
|
||||
@@ -477,15 +477,11 @@ impl TestContext {
|
||||
/// The context will be configured but the key will not be pre-generated so if a key is
|
||||
/// used the fingerprint will be different every time.
|
||||
pub async fn configure_addr(&self, addr: &str) {
|
||||
self.ctx.set_config(Config::Addr, Some(addr)).await.unwrap();
|
||||
self.ctx
|
||||
.set_config(Config::ConfiguredAddr, Some(addr))
|
||||
.await
|
||||
.unwrap();
|
||||
self.ctx
|
||||
.set_config(Config::Configured, Some("1"))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if let Some(name) = addr.split('@').next() {
|
||||
self.set_name(name);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user