diff --git a/src/webxdc.rs b/src/webxdc.rs index 9ac6a1d2e..dca2fb3bf 100644 --- a/src/webxdc.rs +++ b/src/webxdc.rs @@ -847,8 +847,11 @@ fn parse_webxdc_manifest(bytes: &[u8]) -> Result { } async fn get_blob(archive: &mut SeekZipFileReader>, name: &str) -> Result> { - let (i, _) = + let (i, entry) = find_zip_entry(archive.file(), name).ok_or_else(|| anyhow!("no entry found for {name}"))?; + if entry.dir()? { + bail!("'{name}' is a directory not a file.") + } let mut reader = archive.reader_with_entry(i).await?; let mut buf = Vec::new(); reader.read_to_end_checked(&mut buf).await?; @@ -903,7 +906,28 @@ impl Message { )); } - get_blob(&mut archive, name).await + let result = get_blob(&mut archive, name).await; + // not found and no extension, then assume directory and try index.html + // this mimics how webservers behave. + if result.is_err() && !name.contains('.') { + let base = if name.ends_with('/') { + name.to_string() + } else { + format!("{name}/") + }; + // ignore first slash. So that requesting "" for index.html works + let base = base.trim_start_matches('/'); + let fallbacks = [format!("{base}index.html"), format!("{base}index.htm")]; + for fallback in &fallbacks { + let result = get_blob(&mut archive, fallback).await; + if result.is_ok() { + return result; + } + } + result // return orginal error to the path that was requested, not the fallback + } else { + result + } } /// Return info from manifest.toml or from fallbacks. diff --git a/src/webxdc/webxdc_tests.rs b/src/webxdc/webxdc_tests.rs index 12f0b9e13..0ed00c1ce 100644 --- a/src/webxdc/webxdc_tests.rs +++ b/src/webxdc/webxdc_tests.rs @@ -1144,6 +1144,49 @@ async fn test_get_webxdc_blob_with_subdirs() -> Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_get_webxdc_blob_indexhtml_fallback() -> Result<()> { + let t = &TestContext::new_alice().await; + let chat_id = create_group(t, "foo").await?; + let instance = { + let mut instance = create_webxdc_instance( + t, + "indexhtml-fallback.xdc", + include_bytes!("../../test-data/webxdc/indexhtml-fallback.xdc"), + )?; + let instance_msg_id = send_msg(t, chat_id, &mut instance).await?; + assert_eq!(instance.viewtype, Viewtype::Webxdc); + Message::load_from_db(t, instance_msg_id).await? + }; + + // "../" links that go back should work + assert!(instance.get_webxdc_blob(t, "").await.is_ok()); + // test falling back to index.html + assert!(instance.get_webxdc_blob(t, "/alpha").await.is_ok()); + assert!(instance.get_webxdc_blob(t, "/alpha/").await.is_ok()); + // test falling back to index.htm + assert!(instance.get_webxdc_blob(t, "/beta").await.is_ok()); + assert!(instance.get_webxdc_blob(t, "/beta/").await.is_ok()); + // test that original error is still there when there is no index.htm(l) file + assert!(instance.get_webxdc_blob(t, "/control").await.is_err()); + println!("{:?}", instance.get_webxdc_blob(t, "/control/").await); + println!( + "{:?}", + instance.get_webxdc_blob(t, "/control/were.html").await + ); + + assert!(instance.get_webxdc_blob(t, "/control/").await.is_err()); + assert!( + !instance + .get_webxdc_blob(t, "/control/") + .await + .expect_err("error expected because there is no index.html") + .to_string() + .contains("control/index.html") + ); + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_parse_webxdc_manifest() -> Result<()> { let result = parse_webxdc_manifest(r#"key = syntax error"#.as_bytes()); diff --git a/test-data/webxdc/indexhtml-fallback.xdc b/test-data/webxdc/indexhtml-fallback.xdc new file mode 100644 index 000000000..fb5069fe3 Binary files /dev/null and b/test-data/webxdc/indexhtml-fallback.xdc differ