From 4bfeb7889da4039050a1807a78b9b39a3150634d Mon Sep 17 00:00:00 2001 From: Martin Ball <98639962+benandez@users.noreply.github.com> Date: Thu, 22 Oct 2026 14:28:42 +0100 Subject: [PATCH 1/2] Fixes #8519 quick export honours extension (#8535) * fix(engine): Quick Export writes the format its path names (#8519) Quick Export as PNG renamed to photo.jpeg in the save dialog wrote PNG bytes under a JPEG name, which upload checks refuse and file managers don't preview. The path's extension (.jpg/.jpeg/.png/.gif/.webp) now picks the format; any other extension is refused with a pointer to Export As; no extension keeps Export Preferences' format. Co-Authored-By: Claude Opus 5.5 (1M context) * contributors: add benandez's real name Co-Authored-By: Claude Opus 5.5 (1M context) --------- Co-authored-by: Claude Opus 5.5 (1M context) --- contributors/people.toml | 3 +++ crates/engine/src/web_cmds.rs | 8 ++++++++ crates/engine/src/web_cmds/tests.rs | 21 +++++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/contributors/people.toml b/contributors/people.toml index b3cab8e04f..42470ea227 100644 --- a/contributors/people.toml +++ b/contributors/people.toml @@ -191,3 +191,6 @@ sync_github_name = true [people.ha1des] sync_github_name = true + +[people.benandez] +real_name = "Martin Ball" diff --git a/crates/engine/src/web_cmds.rs b/crates/engine/src/web_cmds.rs index f77a020819..9ccb5e02be 100644 --- a/crates/engine/src/web_cmds.rs +++ b/crates/engine/src/web_cmds.rs @@ -1086,6 +1086,14 @@ fn quick_export(s: &mut Session, p: &Value) -> Result { target } }; + // The name typed in the save dialog wins over Export Preferences' format: Quick Export as PNG + // renamed to `photo.jpeg` must write a JPEG, not PNG bytes upload checks refuse (#8519). + let fmt = match crate::file_cmds::extension(&path).as_deref() { + None => fmt, + Some("jpg" | "jpeg") => "jpg".into(), + Some(e @ ("png" | "gif" | "webp")) => e.into(), + Some(e) => return Err(bad(cmd, format!("Quick Export writes PNG, JPEG, GIF or WebP, not .{e}; use Export As"))), + }; let d = s.active().ok_or(EngineError::NoDocument)?; let doc = d.doc.clone(); let metadata = serde_json::to_value(prefs.metadata).ok().and_then(|v| v.as_str().map(str::to_string)).unwrap_or_default(); diff --git a/crates/engine/src/web_cmds/tests.rs b/crates/engine/src/web_cmds/tests.rs index ea8fe40177..cf45dbb368 100644 --- a/crates/engine/src/web_cmds/tests.rs +++ b/crates/engine/src/web_cmds/tests.rs @@ -466,6 +466,27 @@ fn quick_export_never_replaces_the_documents_own_file_unasked() { s.execute("prefs.reset", json!({"path": "export"})).unwrap(); } +/// #8519: Quick Export as PNG renamed to `photo.jpeg` in the save dialog wrote PNG bytes under a +/// JPEG name, which upload checks refuse. A Quick Export extension picks the format; another +/// image extension is refused; no extension keeps Export Preferences' format. +#[test] +fn quick_export_writes_the_format_its_path_names() { + let dir = tmp("quick_ext"); + let mut s = session(8); + s.execute("file.export.exportPreferences", json!({"quickExportFormat": "png"})).unwrap(); + for (name, format, magic) in [("a.jpeg", "jpg", &b"\xFF\xD8\xFF"[..]), ("b.JPG", "jpg", b"\xFF\xD8\xFF"), ("c.gif", "gif", b"GIF8"), ("d.webp", "webp", b"RIFF"), ("e", "png", b"\x89PNG")] { + let path = format!("{dir}/{name}"); + let r = s.execute("file.export.quickExport", json!({"path": path})).unwrap(); + assert_eq!(r["format"], json!(format), "{name}"); + assert!(std::fs::read(&path).unwrap().starts_with(magic), "{name} holds {format} data"); + } + let tif = format!("{dir}/f.tif"); + let e = s.execute("file.export.quickExport", json!({"path": tif})).unwrap_err().to_string(); + assert!(e.contains("Export As"), "{e}"); + assert!(!std::path::Path::new(&tif).exists()); + s.execute("prefs.reset", json!({"path": "export"})).unwrap(); +} + #[test] fn same_file_sees_through_other_names_for_one_file() { let dir = tmp("same_file"); From 95e59a99c09ba41240bede8463d81c406eb84d9c Mon Sep 17 00:00:00 2001 From: aiongg Date: Thu, 22 Oct 2026 13:52:23 +0000 Subject: [PATCH 2/2] Format the quick export test (#8535) --- crates/engine/src/web_cmds/tests.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/web_cmds/tests.rs b/crates/engine/src/web_cmds/tests.rs index cf45dbb368..7eab0759da 100644 --- a/crates/engine/src/web_cmds/tests.rs +++ b/crates/engine/src/web_cmds/tests.rs @@ -474,7 +474,13 @@ fn quick_export_writes_the_format_its_path_names() { let dir = tmp("quick_ext"); let mut s = session(8); s.execute("file.export.exportPreferences", json!({"quickExportFormat": "png"})).unwrap(); - for (name, format, magic) in [("a.jpeg", "jpg", &b"\xFF\xD8\xFF"[..]), ("b.JPG", "jpg", b"\xFF\xD8\xFF"), ("c.gif", "gif", b"GIF8"), ("d.webp", "webp", b"RIFF"), ("e", "png", b"\x89PNG")] { + for (name, format, magic) in [ + ("a.jpeg", "jpg", &b"\xFF\xD8\xFF"[..]), + ("b.JPG", "jpg", b"\xFF\xD8\xFF"), + ("c.gif", "gif", b"GIF8"), + ("d.webp", "webp", b"RIFF"), + ("e", "png", b"\x89PNG"), + ] { let path = format!("{dir}/{name}"); let r = s.execute("file.export.quickExport", json!({"path": path})).unwrap(); assert_eq!(r["format"], json!(format), "{name}");