1pub fn safe_relative(name: &str) -> Option<String> {
10 if name.starts_with('/') || name.starts_with('\\') {
11 return None;
12 }
13 let mut segments: Vec<&str> = Vec::new();
14 for segment in name.split(['/', '\\']) {
15 match segment {
16 "" | "." => continue,
17 ".." => return None,
18 _ if segment.contains(':') || segment.contains('\0') => return None,
19 _ if segment.ends_with('.') || segment.ends_with(' ') => return None,
20 _ if is_reserved_device(segment) => return None,
21 _ => segments.push(segment),
22 }
23 }
24 if segments.is_empty() {
25 return None;
26 }
27 Some(segments.join("/"))
28}
29
30fn is_reserved_device(segment: &str) -> bool {
32 const RESERVED: [&str; 22] = [
33 "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8",
34 "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
35 ];
36 let stem = segment.split('.').next().unwrap_or(segment);
37 RESERVED
38 .iter()
39 .any(|reserved| stem.eq_ignore_ascii_case(reserved))
40}
41
42pub fn decode_name(raw: &[u8], flags: u16) -> String {
45 const UTF8_FLAG: u16 = 1 << 11;
46 if flags & UTF8_FLAG != 0 {
47 if let Ok(text) = std::str::from_utf8(raw) {
48 return text.to_string();
49 }
50 }
51 raw.iter()
52 .map(|&byte| {
53 if byte < 0x80 {
54 byte as char
55 } else {
56 CP437_HIGH[(byte - 0x80) as usize]
57 }
58 })
59 .collect()
60}
61
62#[rustfmt::skip]
64const CP437_HIGH: [char; 128] = [
65 'Ç','ü','é','â','ä','à','å','ç','ê','ë','è','ï','î','ì','Ä','Å',
66 'É','æ','Æ','ô','ö','ò','û','ù','ÿ','Ö','Ü','¢','£','¥','₧','ƒ',
67 'á','í','ó','ú','ñ','Ñ','ª','º','¿','⌐','¬','½','¼','¡','«','»',
68 '░','▒','▓','│','┤','╡','╢','╖','╕','╣','║','╗','╝','╜','╛','┐',
69 '└','┴','┬','├','─','┼','╞','╟','╚','╔','╩','╦','╠','═','╬','╧',
70 '╨','╤','╥','╙','╘','╒','╓','╫','╪','┘','┌','█','▄','▌','▐','▀',
71 'α','ß','Γ','π','Σ','σ','µ','τ','Φ','Θ','Ω','δ','∞','φ','ε','∩',
72 '≡','±','≥','≤','⌠','⌡','÷','≈','°','∙','·','√','ⁿ','²','■',' ',
73];