Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions deltachat-jsonrpc/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
25 changes: 24 additions & 1 deletion src/webxdc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ 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};

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};
Expand Down Expand Up @@ -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<Vec<u8>> {
ensure!(self.viewtype == Viewtype::Webxdc, "No webxdc instance.");

Expand Down Expand Up @@ -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")?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this result in a fail for any unintendedly wrong named icon? So a png that has a jpg ending would result in no icon at all? (while before this change it worked)
I saw image::guess_format in blob.rs - which seems to not rely on the file name. Maybe that would also work here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed to guess_format, but added a check to reject everything that is not PNG and JPEG so we don't have icons in arbitrary formats, especially GIF which may be animated and WebP which may be not supported everywhere anyway.

ensure!(width <= 4096 && height <= 4096, "Icon is too large");
}
Ok(blob)
}

/// Return info from manifest.toml or from fallbacks.
Expand Down
54 changes: 54 additions & 0 deletions src/webxdc/webxdc_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Binary file added test-data/webxdc/with-broken-png-icon.xdc
Binary file not shown.
Binary file added test-data/webxdc/with-too-large-png-icon.xdc
Binary file not shown.