diff --git a/deltachat-jsonrpc/src/api.rs b/deltachat-jsonrpc/src/api.rs index f2432c1be6..bf22251698 100644 --- a/deltachat-jsonrpc/src/api.rs +++ b/deltachat-jsonrpc/src/api.rs @@ -2227,6 +2227,9 @@ impl CommandApi { /// Get blob encoded as base64 from a webxdc message /// /// path is the path of the file within webxdc archive + /// + /// If the file is `icon.png` or `icon.jpg`, + /// loading it may fail if dimensions are unexpectedly large. async fn get_webxdc_blob( &self, account_id: u32, diff --git a/src/webxdc.rs b/src/webxdc.rs index 9ca1e0f278..f26c23b6f1 100644 --- a/src/webxdc.rs +++ b/src/webxdc.rs @@ -20,6 +20,7 @@ mod maps_integration; use std::cmp::max; use std::collections::HashMap; +use std::io::Cursor; use std::path::Path; use anyhow::{Context as _, Result, anyhow, bail, ensure, format_err}; @@ -27,6 +28,7 @@ use anyhow::{Context as _, Result, anyhow, bail, ensure, format_err}; use async_zip::tokio::read::seek::ZipFileReader as SeekZipFileReader; use deltachat_contact_tools::sanitize_bidi_characters; use deltachat_derive::FromSql; +use image::{ImageFormat, ImageReader}; use mail_builder::mime::MimePart; use rusqlite::OptionalExtension; use serde::{Deserialize, Serialize}; @@ -875,6 +877,9 @@ impl Message { /// Currently, this works only if the message is an webxdc instance. /// /// `name` is the filename within the archive, e.g. `index.html`. + /// + /// If the file is `icon.png` or `icon.jpg`, + /// loading it may fail if dimensions are unexpectedly large. pub async fn get_webxdc_blob(&self, context: &Context, name: &str) -> Result> { ensure!(self.viewtype == Viewtype::Webxdc, "No webxdc instance."); @@ -903,7 +908,25 @@ impl Message { )); } - get_blob(&mut archive, name).await + let blob = get_blob(&mut archive, name).await?; + if name == "icon.png" || name == "icon.jpg" { + let image_reader = ImageReader::new(Cursor::new(&blob)) + .with_guessed_format() + .context("Reading from Cursor must never fail")?; + match image_reader.format() { + None => bail!("Unable to determine image format"), + Some(ImageFormat::Png) | Some(ImageFormat::Jpeg) => { + // We accept PNG named icon.jpg and JPEG named icon.png + // to avoid incompatibilities, but no unexpected formats like GIF. + } + Some(format) => bail!("Unexpected icon format {format:?}"), + } + let (width, height) = image_reader + .into_dimensions() + .context("Failed to determine icon dimensions")?; + ensure!(width <= 4096 && height <= 4096, "Icon is too large"); + } + Ok(blob) } /// Return info from manifest.toml or from fallbacks. diff --git a/src/webxdc/webxdc_tests.rs b/src/webxdc/webxdc_tests.rs index 9a3fb43264..79bdb49d95 100644 --- a/src/webxdc/webxdc_tests.rs +++ b/src/webxdc/webxdc_tests.rs @@ -1080,6 +1080,60 @@ async fn test_get_webxdc_blob() -> Result<()> { Ok(()) } +/// Tests that valid webxdc icon can be loaded. +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_get_webxdc_blob_icon() -> Result<()> { + let mut tcm = TestContextManager::new(); + let alice = &tcm.alice().await; + let chat_id = create_group(alice, "chat").await?; + + { + let mut instance = create_webxdc_instance( + alice, + "with-png-icon.xdc", + include_bytes!("../../test-data/webxdc/with-png-icon.xdc"), + )?; + send_msg(alice, chat_id, &mut instance).await?; + let buf = instance.get_webxdc_blob(alice, "icon.png").await?; + assert_eq!(buf.len(), 103); + } + + { + let mut instance = create_webxdc_instance( + alice, + "with-jpg-icon.xdc", + include_bytes!("../../test-data/webxdc/with-jpg-icon.xdc"), + )?; + send_msg(alice, chat_id, &mut instance).await?; + let buf = instance.get_webxdc_blob(alice, "icon.jpg").await?; + assert_eq!(buf.len(), 286); + } + + { + // Webxdc with icon.png than is in fact a text file. + let mut instance = create_webxdc_instance( + alice, + "with-broken-png-icon.xdc", + include_bytes!("../../test-data/webxdc/with-broken-png-icon.xdc"), + )?; + send_msg(alice, chat_id, &mut instance).await?; + assert!(instance.get_webxdc_blob(alice, "icon.png").await.is_err()); + } + + { + // Webxdc with icon.png than is a 9999x9999 PNG image. + let mut instance = create_webxdc_instance( + alice, + "with-too-large-png-icon.xdc", + include_bytes!("../../test-data/webxdc/with-too-large-png-icon.xdc"), + )?; + send_msg(alice, chat_id, &mut instance).await?; + assert!(instance.get_webxdc_blob(alice, "icon.png").await.is_err()); + } + + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_get_webxdc_blob_default_icon() -> Result<()> { let t = TestContext::new_alice().await; diff --git a/test-data/webxdc/with-broken-png-icon.xdc b/test-data/webxdc/with-broken-png-icon.xdc new file mode 100644 index 0000000000..4b7733b0fa Binary files /dev/null and b/test-data/webxdc/with-broken-png-icon.xdc differ diff --git a/test-data/webxdc/with-too-large-png-icon.xdc b/test-data/webxdc/with-too-large-png-icon.xdc new file mode 100644 index 0000000000..132f37b7ac Binary files /dev/null and b/test-data/webxdc/with-too-large-png-icon.xdc differ