mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-14 06:22:24 +00:00
craft-aaf: decode relative media locator escapes (#1101)
* craft-aaf: decode relative media locator escapes * Preserve literal percent escapes in relative paths
This commit is contained in:
1 parent
8958340761
commit
d6134e51cb
4 files changed
+44
-7
No files matched your search
@@ -526,7 +526,7 @@ impl R<'_> {
|
||||
DataKind::Sound => CKind::Sound,
|
||||
_ => kind,
|
||||
};
|
||||
let path = d.objs(pid::LOCATOR).iter().find_map(|l| l.string(pid::URL_STRING)).map(|u| craft_timeline::path::file_url_to_path(&u));
|
||||
let path = d.objs(pid::LOCATOR).iter().find_map(|l| l.string(pid::URL_STRING)).map(|u| locator_path(&u));
|
||||
let file_rate = rate_of(d, pid::SAMPLE_RATE).unwrap_or(rate);
|
||||
let length = d
|
||||
.i64(pid::FILE_LENGTH)
|
||||
@@ -721,3 +721,20 @@ fn markers_of(seg: &Obj, rate: (i64, i64)) -> Vec<CMarker> {
|
||||
fn hex(b: &[u8]) -> String {
|
||||
b.iter().map(|x| format!("{x:02x}")).collect()
|
||||
}
|
||||
|
||||
fn locator_path(url: &str) -> String {
|
||||
if url.contains("://") { craft_timeline::path::file_url_to_path(url) } else { craft_timeline::path::decode_uri_path(url) }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod locator_tests {
|
||||
use super::locator_path;
|
||||
|
||||
#[test]
|
||||
fn decodes_relative_locator_escapes_without_losing_remote_authorities() {
|
||||
assert_eq!(locator_path("Audio%20Files/Di%C3%A1logo%20%231.wav"), "Audio Files/Diálogo #1.wav");
|
||||
assert_eq!(locator_path("file:///tmp/a%20b.wav"), "/tmp/a b.wav");
|
||||
assert_eq!(locator_path("file://server/share/a.wav"), "//server/share/a.wav");
|
||||
assert_eq!(locator_path("https://example.com/a%20b.wav"), "https://example.com/a%20b.wav");
|
||||
}
|
||||
}
|
||||
@@ -472,3 +472,12 @@ fn audio_tracks_keep_their_names() {
|
||||
assert_eq!(back.compositions[0].tracks[1].name, name);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relative_media_locator_round_trips_escaped_characters() {
|
||||
let mut doc = document();
|
||||
doc.sources[0].path = Some("Audio Files/100%20 #1.wav".into());
|
||||
let bytes = written(&doc, false);
|
||||
let got = read_clean(&bytes);
|
||||
assert!(got.sources.iter().any(|s| s.path.as_deref() == Some("Audio Files/100%20 #1.wav")));
|
||||
}
|
||||
@@ -793,7 +793,11 @@ fn descriptor(w: &mut W, s: &Source) -> Obj {
|
||||
d.set(pid::FILE_LENGTH, Value::Data(len.to_le_bytes().to_vec()));
|
||||
d.set(pid::CONTAINER_FORMAT, weak(path::CONTAINER_DEFS, container));
|
||||
if let Some(p) = &s.path {
|
||||
let url = if craft_timeline::path::is_absolute(p) { craft_timeline::path::path_to_file_url(p, false) } else { p.clone() };
|
||||
let url = if craft_timeline::path::is_absolute(p) {
|
||||
craft_timeline::path::path_to_file_url(p, false)
|
||||
} else {
|
||||
craft_timeline::path::encode_uri_path(p)
|
||||
};
|
||||
d.set(pid::LOCATOR, Value::StrongVec(vec![Obj::new(cls::NETWORK_LOCATOR).data_prop(pid::URL_STRING, utf16z(&url))]));
|
||||
}
|
||||
d
|
||||
|
||||
@@ -33,7 +33,14 @@ fn percent_decode(s: &str) -> String {
|
||||
String::from_utf8(out).unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned())
|
||||
}
|
||||
|
||||
fn percent_encode_path(s: &str) -> String {
|
||||
/// Decode percent escapes in a URI path, including a relative media locator.
|
||||
/// Invalid escapes remain literal and invalid UTF-8 is replaced.
|
||||
pub fn decode_uri_path(path: &str) -> String {
|
||||
percent_decode(path)
|
||||
}
|
||||
|
||||
/// Percent-encode a URI path while preserving path separators.
|
||||
pub fn encode_uri_path(s: &str) -> String {
|
||||
let mut out = String::with_capacity(s.len());
|
||||
for &c in s.as_bytes() {
|
||||
if c.is_ascii_alphanumeric() || b"/-._~!$&'()*+,;=:@".contains(&c) {
|
||||
@@ -82,11 +89,11 @@ pub fn path_to_file_url(path: &str, localhost: bool) -> String {
|
||||
};
|
||||
let host = if localhost { "localhost" } else { "" };
|
||||
if is_windows_abs(&p) {
|
||||
format!("file://{host}/{}", percent_encode_path(&p))
|
||||
format!("file://{host}/{}", encode_uri_path(&p))
|
||||
} else if let Some(unc) = p.strip_prefix("//") {
|
||||
format!("file://{}", percent_encode_path(unc))
|
||||
format!("file://{}", encode_uri_path(unc))
|
||||
} else {
|
||||
format!("file://{host}{}", percent_encode_path(&p))
|
||||
format!("file://{host}{}", encode_uri_path(&p))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +171,7 @@ pub fn resolve_url(reference: &str, base_dir: Option<&str>) -> String {
|
||||
|
||||
/// A relative path written as a URL (the inverse of [`resolve_url`]'s decoding).
|
||||
pub fn relative_url(path: &str) -> String {
|
||||
percent_encode_path(path)
|
||||
encode_uri_path(path)
|
||||
}
|
||||
|
||||
/// Resolve a media reference from a document: file URLs become paths, relative paths are joined to
|
||||
|
||||
Reference in new issue
Block a user