Edit: Edit Original opens only regular files with a footage media extension

A folder or bundle, a link to another kind of file, or a file whose extension
the footage importer doesn't take is refused with a message instead of being
handed to the system's default app.
This commit is contained in:
aiongg
2026-10-16 16:14:02 +00:00
parent 299223f112
commit 1064911fc4
+70 -3
View File
@@ -808,8 +808,28 @@ fn original_path(s: &Session) -> Option<String> {
}
}
/// Whether Edit Original may hand `path` to the system's default app: a regular file (after
/// following links) whose name, and the name it links to, have a media extension the footage
/// importer takes. Anything else (a folder or app bundle, a script, a shortcut) is refused.
pub(crate) fn openable_original(path: &str) -> std::result::Result<(), String> {
let media = |p: &std::path::Path| {
let ext = p.extension().and_then(|e| e.to_str()).map(str::to_ascii_lowercase).unwrap_or_default();
crate::media_browser::kind_of(&p.to_string_lossy()).is_some_and(|k| k != "project") || matches!(ext.as_str(), "pdf" | "ai" | "eps")
};
let name = std::path::Path::new(path).file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_else(|| path.to_string());
let real = std::fs::canonicalize(path).map_err(|e| format!("cannot open “{name}”: {e}"))?;
if !real.is_file() {
return Err(format!("“{name}” is not a file"));
}
if !media(std::path::Path::new(path)) || !media(&real) {
return Err(format!("Edit Original opens media files only; “{name}” is not one"));
}
Ok(())
}
fn edit_original(s: &mut Session, _: &Value) -> Result<Value> {
let path = original_path(s).ok_or_else(|| super::bad("edit.editOriginal", "select a footage item or layer"))?;
openable_original(&path).map_err(|e| super::bad("edit.editOriginal", e))?;
let url = super::layer_menu::file_url(&path);
s.events.push(crate::Event::OpenUrl(url.clone()));
Ok(json!({"url": url}))
@@ -952,20 +972,67 @@ fn always_ok(_: &Session) -> std::result::Result<(), String> {
mod tests {
use serde_json::json;
fn tmp(name: &str) -> std::path::PathBuf {
let d = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../target/test-out").join(name);
let _ = std::fs::remove_dir_all(&d);
std::fs::create_dir_all(&d).unwrap();
d
}
/// Edit Original opens the footage as a well-formed `file://` URL, whatever its name contains.
#[test]
fn edit_original_opens_an_encoded_file_url() {
let clip = tmp("edit-original-url").join("take #1.mov");
std::fs::write(&clip, b"").unwrap();
let clip = clip.to_string_lossy().into_owned();
let url = super::super::layer_menu::file_url(&clip);
assert!(url.ends_with("/take%20%231.mov"), "{url}");
let mut s = crate::Session::default();
let id = s
.edit("add", None, |p, _| {
let f = effectcraft_project::Footage { path: "/clips/take #1.mov".into(), ..Default::default() };
let f = effectcraft_project::Footage { path: clip.clone(), ..Default::default() };
Ok(p.add_item("take", effectcraft_color::Label::Lavender, None, effectcraft_project::ItemKind::Footage(f)))
})
.unwrap();
s.state.project_selection = vec![id];
s.drain_events();
let r = s.execute("edit.editOriginal", json!({})).unwrap();
assert_eq!(r["url"], "file:///clips/take%20%231.mov");
assert!(s.drain_events().iter().any(|e| matches!(e, crate::Event::OpenUrl(u) if u == "file:///clips/take%20%231.mov")));
assert_eq!(r["url"], url.as_str());
assert!(s.drain_events().iter().any(|e| matches!(e, crate::Event::OpenUrl(u) if *u == url)));
}
/// Only regular media files reach the system opener.
#[test]
fn edit_original_opens_media_files_only() {
let d = tmp("edit-original-kinds");
for f in ["clip.mov", "Still.PNG", "run.bat", "app.desktop", "clip.mov.lnk"] {
std::fs::write(d.join(f), b"").unwrap();
}
for f in ["Tool.app", "folder.mov"] {
std::fs::create_dir_all(d.join(f)).unwrap();
}
let ok = |f: &str| super::openable_original(&d.join(f).to_string_lossy()).is_ok();
assert!(ok("clip.mov") && ok("Still.PNG"));
for f in ["run.bat", "app.desktop", "clip.mov.lnk", "Tool.app", "folder.mov", "missing.mov", ""] {
assert!(!ok(f), "{f}");
}
#[cfg(unix)]
{
std::os::unix::fs::symlink(d.join("app.desktop"), d.join("link.png")).unwrap();
assert!(!ok("link.png"), "a media name linking to another kind of file");
}
// The command refuses without opening anything.
let mut s = crate::Session::default();
let path = d.join("run.bat").to_string_lossy().into_owned();
let id = s
.edit("add", None, |p, _| {
let f = effectcraft_project::Footage { path, ..Default::default() };
Ok(p.add_item("bat", effectcraft_color::Label::Lavender, None, effectcraft_project::ItemKind::Footage(f)))
})
.unwrap();
s.state.project_selection = vec![id];
s.drain_events();
assert!(s.execute("edit.editOriginal", json!({})).is_err());
assert!(!s.drain_events().iter().any(|e| matches!(e, crate::Event::OpenUrl(_))));
}
}