diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index abc3f0ef0..759ad65ff 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -6784,8 +6784,8 @@ void dc_event_unref(dc_event_t* event); * UI should update the list. * * The event is emitted when the transports are modified on another device - * using the JSON-RPC calls `add_or_update_transport`, `add_transport_from_qr`, `delete_transport` - * or `set_config(configured_addr)`. + * using the JSON-RPC calls `add_or_update_transport`, `add_transport_from_qr`, `delete_transport`, + * `set_transport_unpublished` or `set_config(configured_addr)`. */ #define DC_EVENT_TRANSPORTS_MODIFIED 2600 diff --git a/deltachat-jsonrpc/src/api.rs b/deltachat-jsonrpc/src/api.rs index 3bf828850..7a41792fd 100644 --- a/deltachat-jsonrpc/src/api.rs +++ b/deltachat-jsonrpc/src/api.rs @@ -68,6 +68,7 @@ use self::types::{ }, }; use crate::api::types::chat_list::{get_chat_list_item_by_id, ChatListItemFetchResult}; +use crate::api::types::login_param::Transport; use crate::api::types::qr::{QrObject, SecurejoinSource, SecurejoinUiPath}; #[derive(Debug)] @@ -528,6 +529,7 @@ 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. + /// - [Self::set_transport_unpublished()] to set whether contacts see this transport. async fn add_or_update_transport( &self, account_id: u32, @@ -553,7 +555,23 @@ impl CommandApi { /// Returns the list of all email accounts that are used as a transport in the current profile. /// Use [Self::add_or_update_transport()] to add or change a transport /// and [Self::delete_transport()] to delete a transport. + /// Use [Self::list_transports_ex()] to additionally query + /// whether the transports are marked as 'unpublished'. async fn list_transports(&self, account_id: u32) -> Result> { + let ctx = self.get_context(account_id).await?; + let res = ctx + .list_transports() + .await? + .into_iter() + .map(|t| t.param.into()) + .collect(); + Ok(res) + } + + /// Returns the list of all email accounts that are used as a transport in the current profile. + /// Use [Self::add_or_update_transport()] to add or change a transport + /// and [Self::delete_transport()] to delete a transport. + async fn list_transports_ex(&self, account_id: u32) -> Result> { let ctx = self.get_context(account_id).await?; let res = ctx .list_transports() @@ -571,6 +589,26 @@ impl CommandApi { ctx.delete_transport(&addr).await } + /// Change whether the transport is unpublished. + /// + /// Unpublished transports are not advertised to contacts, + /// and self-sent messages are not sent there, + /// so that we don't cause extra messages to the corresponding inbox, + /// but can still receive messages from contacts who don't know the new relay addresses yet. + /// + /// The default is true, but when updating, + /// existing secondary transports are set to unpublished, + /// so that an existing transport address doesn't suddenly get spammed with a lot of messages. + async fn set_transport_unpublished( + &self, + account_id: u32, + addr: String, + unpublished: bool, + ) -> Result<()> { + let ctx = self.get_context(account_id).await?; + ctx.set_transport_unpublished(&addr, unpublished).await + } + /// Signal an ongoing process to stop. async fn stop_ongoing_process(&self, account_id: u32) -> Result<()> { let ctx = self.get_context(account_id).await?; diff --git a/deltachat-jsonrpc/src/api/types/login_param.rs b/deltachat-jsonrpc/src/api/types/login_param.rs index 6036709cd..71b42add6 100644 --- a/deltachat-jsonrpc/src/api/types/login_param.rs +++ b/deltachat-jsonrpc/src/api/types/login_param.rs @@ -4,6 +4,16 @@ use serde::Deserialize; use serde::Serialize; use yerpc::TypeDef; +#[derive(Serialize, TypeDef, schemars::JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct Transport { + /// The login data entered by the user. + pub param: EnteredLoginParam, + /// Whether this transport is set to 'unpublished'. + /// See `set_transport_unpublished` / `setTransportUnpublished` for details. + pub is_unpublished: bool, +} + /// Login parameters entered by the user. /// /// Usually it will be enough to only set `addr` and `password`, @@ -56,6 +66,15 @@ pub struct EnteredLoginParam { pub oauth2: Option, } +impl From for Transport { + fn from(transport: dc::Transport) -> Self { + Transport { + param: transport.param.into(), + is_unpublished: transport.is_unpublished, + } + } +} + impl From for EnteredLoginParam { fn from(param: dc::EnteredLoginParam) -> Self { let imap_security: Socket = param.imap.security.into(); diff --git a/src/chat.rs b/src/chat.rs index 2dd153570..47c67154e 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -2737,7 +2737,6 @@ async fn prepare_send_msg( chat_id.unarchive_if_not_muted(context, msg.state).await?; } chat.prepare_msg_raw(context, msg, update_msg_id).await?; - let row_ids = create_send_msg_jobs(context, msg) .await .context("Failed to create send jobs")?; @@ -2844,19 +2843,12 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) - let lowercase_from = from.to_lowercase(); recipients.retain(|x| x.to_lowercase() != lowercase_from); - if context.get_config_bool(Config::BccSelf).await? - || msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage + + // Default Webxdc integrations are hidden messages and must not be sent out: + if (msg.param.get_int(Param::WebxdcIntegration).is_some() && msg.hidden) + // This may happen eg. for groups with only SELF and bcc_self disabled: + || (!context.get_config_bool(Config::BccSelf).await? && recipients.is_empty()) { - smtp::add_self_recipients(context, &mut recipients, needs_encryption).await?; - } - - // Default Webxdc integrations are hidden messages and must not be sent out - if msg.param.get_int(Param::WebxdcIntegration).is_some() && msg.hidden { - recipients.clear(); - } - - if recipients.is_empty() { - // may happen eg. for groups with only SELF and bcc_self disabled info!( context, "Message {} has no recipient, skipping smtp-send.", msg.id @@ -2895,6 +2887,12 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) - ); } + if context.get_config_bool(Config::BccSelf).await? + || msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage + { + smtp::add_self_recipients(context, &mut recipients, rendered_msg.is_encrypted).await?; + } + if needs_encryption && !rendered_msg.is_encrypted { /* unrecoverable */ message::set_msg_failed( diff --git a/src/config.rs b/src/config.rs index f524efb64..36fdc8572 100644 --- a/src/config.rs +++ b/src/config.rs @@ -837,7 +837,7 @@ impl Context { // which only fetches from the primary transport. transaction .execute( - "UPDATE transports SET add_timestamp=? WHERE addr=?", + "UPDATE transports SET add_timestamp=?, is_published=1 WHERE addr=?", (time(), addr), ) .context( @@ -974,6 +974,21 @@ impl Context { .await } + /// Returns all published self addresses, newest first. + /// See `[Context::set_transport_unpublished]` + pub(crate) async fn get_published_self_addrs(&self) -> Result> { + self.sql + .query_map_vec( + "SELECT addr FROM transports WHERE is_published=1 ORDER BY add_timestamp DESC", + (), + |row| { + let addr: String = row.get(0)?; + Ok(addr) + }, + ) + .await + } + /// Returns all secondary self addresses. pub(crate) async fn get_secondary_self_addrs(&self) -> Result> { self.sql.query_map_vec("SELECT addr FROM transports WHERE addr NOT IN (SELECT value FROM config WHERE keyname='configured_addr')", (), |row| { @@ -982,6 +997,23 @@ impl Context { }).await } + /// Returns all published secondary self addresses. + /// See `[Context::set_transport_unpublished]` + pub(crate) async fn get_published_secondary_self_addrs(&self) -> Result> { + self.sql + .query_map_vec( + "SELECT addr FROM transports + WHERE is_published=1 + AND addr NOT IN (SELECT value FROM config WHERE keyname='configured_addr')", + (), + |row| { + let addr: String = row.get(0)?; + Ok(addr) + }, + ) + .await + } + /// Returns the primary self address. /// Returns an error if no self addr is configured. pub async fn get_primary_self_addr(&self) -> Result { diff --git a/src/configure.rs b/src/configure.rs index 099f1ecac..6f4fce871 100644 --- a/src/configure.rs +++ b/src/configure.rs @@ -28,8 +28,8 @@ use crate::constants::NON_ALPHANUMERIC_WITHOUT_DOT; use crate::context::Context; use crate::imap::Imap; use crate::log::warn; -use crate::login_param::EnteredCertificateChecks; pub use crate::login_param::EnteredLoginParam; +use crate::login_param::{EnteredCertificateChecks, Transport}; use crate::message::Message; use crate::net::proxy::ProxyConfig; use crate::oauth2::get_oauth2_addr; @@ -110,6 +110,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. + /// - [Self::set_transport_unpublished()] to set whether contacts see this transport. pub async fn add_or_update_transport(&self, param: &mut EnteredLoginParam) -> Result<()> { self.stop_io().await; let result = self.add_transport_inner(param).await; @@ -188,14 +189,22 @@ impl Context { /// Returns the list of all email accounts that are used as a transport in the current profile. /// 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> { + pub async fn list_transports(&self) -> Result> { let transports = self .sql - .query_map_vec("SELECT entered_param FROM transports", (), |row| { - let entered_param: String = row.get(0)?; - let transport: EnteredLoginParam = serde_json::from_str(&entered_param)?; - Ok(transport) - }) + .query_map_vec( + "SELECT entered_param, is_published FROM transports", + (), + |row| { + let param: String = row.get(0)?; + let param: EnteredLoginParam = serde_json::from_str(¶m)?; + let is_published: bool = row.get(1)?; + Ok(Transport { + param, + is_unpublished: !is_published, + }) + }, + ) .await?; Ok(transports) @@ -261,6 +270,40 @@ impl Context { Ok(()) } + /// Change whether the transport is unpublished. + /// + /// Unpublished transports are not advertised to contacts, + /// and self-sent messages are not sent there, + /// so that we don't cause extra messages to the corresponding inbox, + /// but can still receive messages from contacts who don't know the new relay addresses yet. + /// + /// The default is true, but when updating, + /// existing secondary transports are set to unpublished, + /// so that an existing transport address doesn't suddenly get spammed with a lot of messages. + pub async fn set_transport_unpublished(&self, addr: &str, unpublished: bool) -> Result<()> { + // We need to update the timestamp so that the key's timestamp changes + // and is recognized as newer by our peers + self.sql + .transaction(|trans| { + let primary_addr: String = trans.query_row( + "SELECT value FROM config WHERE keyname='configured_addr'", + (), + |row| row.get(0), + )?; + if primary_addr == addr && unpublished { + bail!("Can't set primary relay as unpublished"); + } + trans.execute( + "UPDATE transports SET is_published=?, add_timestamp=? WHERE addr=?", + (!unpublished, time(), addr), + )?; + Ok(()) + }) + .await?; + send_sync_transports(self).await?; + Ok(()) + } + async fn inner_configure(&self, param: &EnteredLoginParam) -> Result<()> { info!(self, "Configure ..."); diff --git a/src/key.rs b/src/key.rs index 101c03518..3e50f922d 100644 --- a/src/key.rs +++ b/src/key.rs @@ -296,7 +296,7 @@ pub(crate) async fn load_self_public_key_opt(context: &Context) -> Result