mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-24 10:10:05 -06:00
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:
@@ -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,
|
||||
|
||||
@@ -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..]) {
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
Reference in New Issue
Block a user