use anyhow::{bail, Result}; /// Prüft, ob ein Name Path-Traversal-Muster oder Pfadtrennzeichen enthält (S-02). pub fn is_path_traversal(name: &str) -> bool { name.is_empty() || name == "." || name == ".." || name.contains('/') || name.contains('\\') || name.contains('\0') } /// Validiert einen Datei- oder Verzeichnisnamen gegen Path-Traversal, Null-Bytes, /// unzulässige Steuerzeichen und Windows-reservierte Gerätenamen (S-08, R-03). pub fn validate_node_name(name: &str) -> Result<()> { if name.is_empty() { bail!("Dateiname darf nicht leer sein."); } if name == "." || name == ".." { bail!( "Ungültiger Dateiname (Verzeichnisreferenz verboten): '{}'", name ); } if name.contains('/') || name.contains('\\') || name.contains('\0') { bail!( "Dateiname enthält unzulässige Trennzeichen oder Null-Bytes: '{}'", name ); } for c in name.chars() { if (c as u32) < 0x20 { bail!("Dateiname enthält Steuerzeichen: '{}'", name); } } // Windows reservierte Gerätenamen (CON, PRN, AUX, NUL, COM1..9, LPT1..9) let base_name = if let Some(dot_idx) = name.find('.') { &name[..dot_idx] } else { name }; let base_upper = base_name.to_ascii_uppercase(); let reserved = [ "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", ]; if reserved.contains(&base_upper.as_str()) { bail!( "Dateiname '{}' kollidiert mit einem reservierten Windows-Gerätenamen.", name ); } Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_validate_node_name() { assert!(validate_node_name("test.txt").is_ok()); assert!(validate_node_name("").is_err()); assert!(validate_node_name(".").is_err()); assert!(validate_node_name("..").is_err()); assert!(validate_node_name("foo/bar").is_err()); assert!(validate_node_name("foo\\bar").is_err()); assert!(validate_node_name("CON").is_err()); assert!(validate_node_name("aux.txt").is_err()); assert!(validate_node_name("nul").is_err()); assert!(validate_node_name("com1.log").is_err()); assert!(validate_node_name("line\nbreak").is_err()); assert!(validate_node_name("null\0byte").is_err()); } }