1use crate::cfg_feature_alloc;
9use core::fmt::{Display, Formatter};
10
11pub static USUALLY_PROHIBITED_FS_CHARS: &[char; 9] =
15 &['<', '>', ':', '"', '/', '\\', '|', '?', '*'];
16
17pub static FATXX_PROHIBITED_FS_CHARS: &[char; 18] = &[
21 '"', '*', '/', ':', '<', '>', '?', '\\', '|', '+', ',', '.', ';', '=', '[', ']', '!', '@',
22];
23
24pub static WINDOWS_PROHIBITED_FILE_NAMES: &[&str; 45] = &[
28 "CON", "PRN", "AUX", "CLOCK$", "LST", "KEYBD$", "SCREEN$", "$IDLE$", "CONFIG$", "NUL", "COM0",
29 "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2",
30 "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", "$Mft", "$MftMirr", "$LogFile",
31 "$Volume", "$AttrDef", "$Bitmap", "$Boot", "$BadClus", "$Secure", "$Upcase", "$Extend",
32 "$Quota", "$ObjId", "$Reparse", "$Extend",
33];
34
35pub static USUALLY_SAFE_FS_CHARS: &[char; 83] = &[
40 ' ', '!', '#', '%', '&', '(', ')', '+', ',', '-', '.', '0', '1', '2', '3', '4', '5', '6', '7',
41 '8', '9', '=', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
42 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', ']', '^', '_', '`', 'a', 'b', 'c',
43 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
44 'w', 'x', 'y', 'z', '{', '}', '~',
45];
46
47#[derive(Debug, Copy, Clone, Eq, PartialEq)]
50pub enum FilenameError {
51 StartsWithWindowsProhibited(&'static str),
52 ContainsUsuallyInvalidChar(char),
53 EndsWithInvalidCharacter(char),
54}
55impl Display for FilenameError {
56 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
57 match self {
58 FilenameError::StartsWithWindowsProhibited(pro) => {
59 write!(f, "Filename starts with windows prohibited word: {pro}")
60 }
61 FilenameError::ContainsUsuallyInvalidChar(chr) => {
62 write!(
63 f,
64 "Filename contains a usually invalid character: {chr}(0x{:02X})",
65 *chr as u16
66 )
67 }
68 FilenameError::EndsWithInvalidCharacter(chr) => {
69 write!(
70 f,
71 "Filename ends with a usually invalid character: {chr}(0x{:02X})",
72 *chr as u16
73 )
74 }
75 }
76 }
77}
78impl core::error::Error for FilenameError {}
79
80cfg_feature_alloc! {
81 extern crate alloc;
82 pub fn clean_filename<T: AsRef<str>>(val: &T) -> alloc::string::String {
85 let input = val.as_ref();
86 let mut out = alloc::string::String::with_capacity(input.len());
87 for v in input.chars() {
88 if USUALLY_SAFE_FS_CHARS.binary_search(&v).is_ok() {
89 out.push(v);
90 }
91 }
92 out
93 }
94}
95
96pub fn is_filename_probably_valid<T: AsRef<str>>(val: &T) -> Result<(), FilenameError> {
100 let input = val.as_ref();
101 for invalid in WINDOWS_PROHIBITED_FILE_NAMES {
102 if input.starts_with(invalid) {
103 return Err(FilenameError::StartsWithWindowsProhibited(invalid));
104 }
105 }
106 for v in input.chars() {
107 let vi = v as u32;
108 if !(0x20..=0x7E).contains(&vi) || USUALLY_PROHIBITED_FS_CHARS.binary_search(&v).is_ok() {
109 return Err(FilenameError::ContainsUsuallyInvalidChar(v));
110 }
111 }
112 Ok(())
113}