From 2089117ec0f8d5b7fed0f8075d6ae6de7b2d6420 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Tue, 3 Nov 2026 12:58:58 +0000 Subject: [PATCH] address review: limit sanitized names to 255 bytes --- crates/project/src/render_queue.rs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/crates/project/src/render_queue.rs b/crates/project/src/render_queue.rs index f7759cff4..e7d887262 100644 --- a/crates/project/src/render_queue.rs +++ b/crates/project/src/render_queue.rs @@ -1529,9 +1529,15 @@ pub fn numbered_template(template: &str, n: u32) -> String { /// Make a name safe as a file name component. pub fn sanitize(name: &str) -> String { - // At most a file name's worth (255 characters): a template repeating a long name token - // must not expand to gigabytes. - let s: String = name.chars().take(255).map(|c| if matches!(c, '/' | '\\' | ':' | '*' | '?' | '"' | '<' | '>' | '|') { '_' } else { c }).collect(); + // At most a file name's worth (255 UTF-8 bytes, whole characters only): a template repeating + // a long name token must not expand to gigabytes. + let mut s = String::new(); + for c in name.chars() { + if s.len() + c.len_utf8() > 255 { + break; + } + s.push(if matches!(c, '/' | '\\' | ':' | '*' | '?' | '"' | '<' | '>' | '|') { '_' } else { c }); + } // `.` and `..` would name the folder itself or its parent when the token is a path component. if !s.is_empty() && s.chars().all(|c| c == '.') { "_".repeat(s.len()) } else { s } } @@ -1540,6 +1546,14 @@ pub fn sanitize(name: &str) -> String { mod tests { use super::*; + #[test] + fn sanitize_limits_the_name_in_bytes() { + // 255 two-byte characters: 127 fit (254 bytes); the 128th would pass the limit. + let s = sanitize(&"é".repeat(255)); + assert_eq!(s.len(), 254); + assert!(s.chars().all(|c| c == 'é')); + } + #[test] fn templates_and_sequences() { let v = TemplateVars { comp_name: "Main/Title", project_name: "P", width: 1920, height: 1080, frame_rate: 29.97, start_frame: 0, end_frame: 9 };