diff --git a/deltachat-jsonrpc/src/api/types/webxdc.rs b/deltachat-jsonrpc/src/api/types/webxdc.rs index e942d7be2f..28d27a47ad 100644 --- a/deltachat-jsonrpc/src/api/types/webxdc.rs +++ b/deltachat-jsonrpc/src/api/types/webxdc.rs @@ -57,6 +57,7 @@ impl WebxdcMessageInfo { document, summary, source_code_url, + request_integration: _, internet_access, self_addr, send_update_interval, diff --git a/src/chat.rs b/src/chat.rs index f68c292db4..46d8e3a6ee 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -2202,7 +2202,9 @@ impl Chat { msg.id = MsgId::new(u32::try_from(raw_id)?); maybe_set_logging_xdc(context, msg, self.id).await?; - context.update_webxdc_integration_database(msg).await?; + context + .update_webxdc_integration_database(msg, context) + .await?; } context.scheduler.interrupt_ephemeral_task().await; Ok(msg.id) @@ -2977,8 +2979,8 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) - recipients.push(from); } - // Webxdc integrations are messages, however, shipped with main app and must not be sent out - if msg.param.get_int(Param::WebxdcIntegration).is_some() { + // 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(); } diff --git a/src/webxdc.rs b/src/webxdc.rs index 5c0b01535a..c4abad2297 100644 --- a/src/webxdc.rs +++ b/src/webxdc.rs @@ -74,6 +74,9 @@ pub struct WebxdcManifest { /// Optional URL of webxdc source code. pub source_code_url: Option, + /// Set to "map" to request integration. + pub request_integration: Option, + /// If the webxdc requests network access. pub request_internet_access: Option, } @@ -100,6 +103,9 @@ pub struct WebxdcInfo { /// URL of webxdc source code or an empty string. pub source_code_url: String, + /// Set to "map" to request integration, otherwise an empty string. + pub request_integration: String, + /// If the webxdc is allowed to access the network. /// It should request access, be encrypted /// and sent to self for this. @@ -920,6 +926,9 @@ impl Message { } } + let request_integration = manifest.request_integration.unwrap_or_default(); + let is_integrated = self.is_set_as_webxdc_integration(context).await?; + let internet_access = manifest.request_internet_access.unwrap_or_default() && self.chat_id.is_self_talk(context).await.unwrap_or_default() && self.get_showpadlock(); @@ -944,7 +953,12 @@ impl Message { .get(Param::WebxdcDocument) .unwrap_or_default() .to_string(), - summary: if internet_access { + summary: if is_integrated { + "🌍 Used as map. Delete to use default. Do not enter sensitive data".to_string() + } else if request_integration == "map" { + "🌏 To use as map, forward to \"Saved Messages\" again. Do not enter sensitive data" + .to_string() + } else if internet_access { "Dev Mode: Do not enter sensitive data!".to_string() } else { self.param @@ -957,6 +971,7 @@ impl Message { } else { "".to_string() }, + request_integration, internet_access, self_addr, send_update_interval: context.ratelimit.read().await.update_interval(), diff --git a/src/webxdc/integration.rs b/src/webxdc/integration.rs index 500cd36bf6..b07600def6 100644 --- a/src/webxdc/integration.rs +++ b/src/webxdc/integration.rs @@ -57,14 +57,34 @@ impl Context { } // Check if a Webxdc shall be used as an integration and remember that. - pub(crate) async fn update_webxdc_integration_database(&self, msg: &Message) -> Result<()> { - if msg.viewtype == Viewtype::Webxdc && msg.param.get_int(Param::WebxdcIntegration).is_some() - { - self.set_config_internal( - Config::WebxdcIntegration, - Some(&msg.id.to_u32().to_string()), - ) - .await?; + pub(crate) async fn update_webxdc_integration_database( + &self, + msg: &mut Message, + context: &Context, + ) -> Result<()> { + if msg.viewtype == Viewtype::Webxdc { + let is_integration = if msg.param.get_int(Param::WebxdcIntegration).is_some() { + true + } else if msg.chat_id.is_self_talk(context).await? { + let info = msg.get_webxdc_info(context).await?; + if info.request_integration == "map" { + msg.param.set_int(Param::WebxdcIntegration, 1); + msg.update_param(context).await?; + true + } else { + false + } + } else { + false + }; + + if is_integration { + self.set_config_internal( + Config::WebxdcIntegration, + Some(&msg.id.to_u32().to_string()), + ) + .await?; + } } Ok(()) } @@ -101,11 +121,26 @@ impl Message { None } } + + // Check if the message is an actually used as Webxdc integration. + pub(crate) async fn is_set_as_webxdc_integration(&self, context: &Context) -> Result { + if let Some(integration_id) = context + .get_config_parsed::(Config::WebxdcIntegration) + .await? + { + Ok(integration_id == self.id.to_u32()) + } else { + Ok(false) + } + } } #[cfg(test)] mod tests { use crate::config::Config; + use crate::context::Context; + use crate::message; + use crate::message::{Message, Viewtype}; use crate::test_utils::TestContext; use anyhow::Result; use std::time::Duration; @@ -126,4 +161,65 @@ mod tests { Ok(()) } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_overwrite_default_integration() -> Result<()> { + let t = TestContext::new_alice().await; + let self_chat = &t.get_self_chat().await; + assert!(t.init_webxdc_integration(None).await?.is_none()); + + async fn assert_integration(t: &Context, name: &str) -> Result<()> { + let integration_id = t.init_webxdc_integration(None).await?.unwrap(); + let integration = Message::load_from_db(t, integration_id).await?; + let integration_info = integration.get_webxdc_info(t).await?; + assert_eq!(integration_info.name, name); + Ok(()) + } + + // set default integration + let bytes = include_bytes!("../../test-data/webxdc/with-manifest-and-png-icon.xdc"); + let file = t.get_blobdir().join("maps.xdc"); + tokio::fs::write(&file, bytes).await.unwrap(); + t.set_webxdc_integration(file.to_str().unwrap()).await?; + assert_integration(&t, "with some icon").await?; + + // send a maps.xdc with insufficient manifest + let mut msg = Message::new(Viewtype::Webxdc); + msg.set_file_from_bytes( + &t, + "mapstest.xdc", + include_bytes!("../../test-data/webxdc/mapstest-integration-unset.xdc"), + None, + ) + .await?; + t.send_msg(self_chat.id, &mut msg).await; + assert_integration(&t, "with some icon").await?; // still the default integration + + // send a maps.xdc with manifest including the line `request_integration = "map"` + let mut msg = Message::new(Viewtype::Webxdc); + msg.set_file_from_bytes( + &t, + "mapstest.xdc", + include_bytes!("../../test-data/webxdc/mapstest-integration-set.xdc"), + None, + ) + .await?; + let sent = t.send_msg(self_chat.id, &mut msg).await; + let info = msg.get_webxdc_info(&t).await?; + assert!(info.summary.contains("Used as map")); + assert_integration(&t, "Maps Test 2").await?; + + // when maps.xdc is received on another device, the integration is not accepted (needs to be forwarded again) + let t2 = TestContext::new_alice().await; + let msg2 = t2.recv_msg(&sent).await; + let info = msg2.get_webxdc_info(&t2).await?; + assert!(info.summary.contains("To use as map,")); + assert!(t2.init_webxdc_integration(None).await?.is_none()); + + // deleting maps.xdc removes the user's integration - the UI will go back to default calling set_webxdc_integration() then + message::delete_msgs(&t, &[msg.id]).await?; + assert!(t.init_webxdc_integration(None).await?.is_none()); + + Ok(()) + } } diff --git a/src/webxdc/maps_integration.rs b/src/webxdc/maps_integration.rs index 67ff755607..a1d2161f0f 100644 --- a/src/webxdc/maps_integration.rs +++ b/src/webxdc/maps_integration.rs @@ -181,7 +181,7 @@ mod tests { async fn test_maps_integration() -> Result<()> { let t = TestContext::new_alice().await; - let bytes = include_bytes!("../../test-data/webxdc/mapstest.xdc"); + let bytes = include_bytes!("../../test-data/webxdc/mapstest-integration-set.xdc"); let file = t.get_blobdir().join("maps.xdc"); tokio::fs::write(&file, bytes).await.unwrap(); t.set_webxdc_integration(file.to_str().unwrap()).await?; @@ -199,7 +199,7 @@ mod tests { let integration = Message::load_from_db(&t, integration_id).await?; let info = integration.get_webxdc_info(&t).await?; - assert_eq!(info.name, "Maps Test"); + assert_eq!(info.name, "Maps Test 2"); assert_eq!(info.internet_access, true); t.send_webxdc_status_update( diff --git a/test-data/webxdc/mapstest-integration-set.xdc b/test-data/webxdc/mapstest-integration-set.xdc new file mode 100644 index 0000000000..e25819ce95 Binary files /dev/null and b/test-data/webxdc/mapstest-integration-set.xdc differ diff --git a/test-data/webxdc/mapstest.xdc b/test-data/webxdc/mapstest-integration-unset.xdc similarity index 100% rename from test-data/webxdc/mapstest.xdc rename to test-data/webxdc/mapstest-integration-unset.xdc