fix(interchange): avoid UTF-8 slice panics in file URLs

Use a checked localhost prefix probe so Unicode file URLs retain their media paths without slicing inside a UTF-8 character. Add public converter, resolver and EDL import regressions.

Fixes #1591

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Paul
2026-10-14 16:24:29 -06:00
committed by GitHub
co-authored by Codex
parent 39a8b65e33
commit 0b432e3834
3 changed files with 50 additions and 1 deletions
+4
View File
@@ -24,6 +24,10 @@ read off files it exports, never from the application itself.
The other formats' editions are listed in the module docs (`edl`, `fcp7`, `fcpxml`, `otio`, `ale`).
**Invariant — Unicode media URLs:** file URL prefix probes must not panic when their byte boundary
falls inside a UTF-8 character. Timeline importers use the shared converter; `tests/file_urls.rs`
checks expected Unicode paths through the public converter, resolver and EDL importer.
## AAF and OMF
`comp` holds a format-neutral mob-style model (compositions of slots whose sequences of fillers,
+1 -1
View File
@@ -65,7 +65,7 @@ pub fn file_url_to_path(url: &str) -> String {
return url.to_string();
}
let rest = &url[7..];
let rest = if rest.len() >= 9 && rest[..9].eq_ignore_ascii_case("localhost") { &rest[9..] } else { rest };
let rest = if rest.get(..9).is_some_and(|s| s.eq_ignore_ascii_case("localhost")) { &rest[9..] } else { rest };
let decoded = percent_decode(rest);
// file:///C:/x -> C:/x
if decoded.len() >= 3 && decoded.starts_with('/') && is_windows_abs(&decoded[1..]) {
+45
View File
@@ -0,0 +1,45 @@
//! Media URL handling at the public interchange boundary.
use filmcraft_interchange::{Format, file_url_to_path, import, resolve_path};
use filmcraft_project::MediaRef;
#[test]
fn unicode_file_urls_do_not_panic_or_lose_the_path() {
for (url, path) in [("file://srv/日本語.mov", "//srv/日本語.mov"), ("file://localhost/media/漢字.mov", "/media/漢字.mov")] {
let parsed = std::panic::catch_unwind(|| file_url_to_path(url));
assert!(parsed.is_ok(), "file URL panicked: {url}");
assert_eq!(parsed.unwrap(), path);
assert_eq!(resolve_path(url, Some("/project")), path);
}
for path in ["/media/漢字.mov", "/日本語.mov", "//srv/日本語.mov"] {
let url = format!("file://{path}");
let parsed = std::panic::catch_unwind(|| file_url_to_path(&url));
assert!(parsed.is_ok(), "file URL panicked: {url}");
assert_eq!(parsed.unwrap(), path);
assert_eq!(resolve_path(&url, Some("/project")), path);
}
}
#[test]
fn edl_import_keeps_unicode_file_url_media() {
let doc = "TITLE: Unicode media
FCM: NON-DROP FRAME
001 AX V C 00:00:00:00 00:00:01:00 00:00:00:00 00:00:01:00
* SOURCE FILE: file:///日本語.mov
";
let parsed = std::panic::catch_unwind(|| import(doc.as_bytes(), Format::Edl, None));
assert!(parsed.is_ok(), "importing a Unicode file URL panicked");
let (imported, report) = parsed.unwrap().unwrap();
let paths: Vec<_> = imported
.project
.items
.values()
.filter_map(|item| item.as_media())
.filter_map(|media| match &media.media {
MediaRef::File { path } => Some(path.as_str()),
_ => None,
})
.collect();
assert_eq!(paths, ["/日本語.mov"]);
assert!(!report.has_warnings(), "{report}");
}