From 11f6f411469a0b1781424c9e93d18da54aad1cad Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 6 Oct 2024 18:50:11 +0100 Subject: [PATCH] Better support for volume labels. They can contain spaces! But not . --- src/fat/mod.rs | 2 +- src/filesystem/filename.rs | 161 ++++++++++++++++++------------------- 2 files changed, 81 insertions(+), 82 deletions(-) diff --git a/src/fat/mod.rs b/src/fat/mod.rs index 35641cb..dcc5ffb 100644 --- a/src/fat/mod.rs +++ b/src/fat/mod.rs @@ -139,7 +139,7 @@ mod test { "#; let results = [ Expected::Short(DirEntry { - name: ShortFileName::create_from_str_mixed_case("boot").unwrap(), + name: ShortFileName::create_volume_label_from_str("boot").unwrap(), mtime: Timestamp::from_calendar(2015, 11, 21, 19, 35, 18).unwrap(), ctime: Timestamp::from_calendar(2015, 11, 21, 19, 35, 18).unwrap(), attributes: Attributes::create_from_fat(Attributes::VOLUME), diff --git a/src/filesystem/filename.rs b/src/filesystem/filename.rs index 4cb763f..0e46dd3 100644 --- a/src/filesystem/filename.rs +++ b/src/filesystem/filename.rs @@ -40,6 +40,15 @@ impl ToShortFileName for &str { } } +/// How filenames should be handled +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum FilenameKind { + /// Stored mixed case + VolumeLabel, + /// Converted to upper case + DirectoryEntry, +} + /// An MS-DOS 8.3 filename. 7-bit ASCII only. All lower-case is converted to /// upper-case by default. #[cfg_attr(feature = "defmt-log", derive(defmt::Format))] @@ -82,6 +91,26 @@ impl ShortFileName { /// Create a new MS-DOS 8.3 space-padded file name as stored in the directory entry. pub fn create_from_str(name: &str) -> Result { + Self::create_from_str_inner(name, FilenameKind::DirectoryEntry) + } + + /// Create a new Volume Label. + pub fn create_volume_label_from_str(name: &str) -> Result { + Self::create_from_str_inner(name, FilenameKind::VolumeLabel) + } + + /// Create a new MS-DOS 8.3 space-padded file name as stored in the + /// directory entry. + /// + /// The Unicode string must contain only code points from ISO-8859-1 (i.e. + /// Code Points \U+0000 to \U+00FF). + /// + /// Convert the filename to upper case if specified by the `case_handling` + /// argument. + fn create_from_str_inner( + name: &str, + case_handling: FilenameKind, + ) -> Result { let mut sfn = ShortFileName { contents: [b' '; Self::FILENAME_MAX_LEN], }; @@ -98,92 +127,42 @@ impl ShortFileName { let mut idx = 0; let mut seen_dot = false; - for ch in name.bytes() { + for ch in name.chars() { match ch { // Microsoft say these are the invalid characters - 0x00..=0x1F - | 0x20 - | 0x22 - | 0x2A - | 0x2B - | 0x2C - | 0x2F - | 0x3A - | 0x3B - | 0x3C - | 0x3D - | 0x3E - | 0x3F - | 0x5B - | 0x5C - | 0x5D - | 0x7C => { + '\u{0000}'..='\u{001F}' + | '"' + | '*' + | '+' + | ',' + | '/' + | ':' + | ';' + | '<' + | '=' + | '>' + | '?' + | '[' + | '\\' + | ']' + | '|' => { return Err(FilenameError::InvalidCharacter); } - // Denotes the start of the file extension - b'.' => { - if (1..=Self::FILENAME_BASE_MAX_LEN).contains(&idx) { - idx = Self::FILENAME_BASE_MAX_LEN; - seen_dot = true; - } else { - return Err(FilenameError::MisplacedPeriod); - } - } - _ => { - let ch = ch.to_ascii_uppercase(); - if seen_dot { - if (Self::FILENAME_BASE_MAX_LEN..Self::FILENAME_MAX_LEN).contains(&idx) { - sfn.contents[idx] = ch; - } else { - return Err(FilenameError::NameTooLong); - } - } else if idx < Self::FILENAME_BASE_MAX_LEN { - sfn.contents[idx] = ch; - } else { - return Err(FilenameError::NameTooLong); - } - idx += 1; + ' ' if case_handling == FilenameKind::DirectoryEntry => { + // can't have spaces in regular file names + return Err(FilenameError::InvalidCharacter); } - } - } - if idx == 0 { - return Err(FilenameError::FilenameEmpty); - } - Ok(sfn) - } - - /// Create a new MS-DOS 8.3 space-padded file name as stored in the directory entry. - /// Use this for volume labels with mixed case. - pub fn create_from_str_mixed_case(name: &str) -> Result { - let mut sfn = ShortFileName { - contents: [b' '; Self::FILENAME_MAX_LEN], - }; - let mut idx = 0; - let mut seen_dot = false; - for ch in name.bytes() { - match ch { - // Microsoft say these are the invalid characters - 0x00..=0x1F - | 0x20 - | 0x22 - | 0x2A - | 0x2B - | 0x2C - | 0x2F - | 0x3A - | 0x3B - | 0x3C - | 0x3D - | 0x3E - | 0x3F - | 0x5B - | 0x5C - | 0x5D - | 0x7C => { + x if x > '\u{00FF}' => { + // We only handle ISO-8859-1 which is Unicode Code Points + // \U+0000 to \U+00FF. This is above that. return Err(FilenameError::InvalidCharacter); } - // Denotes the start of the file extension - b'.' => { + '.' if case_handling == FilenameKind::VolumeLabel => { + // not allowed + return Err(FilenameError::MisplacedPeriod); + } + '.' => { + // Denotes the start of the file extension if (1..=Self::FILENAME_BASE_MAX_LEN).contains(&idx) { idx = Self::FILENAME_BASE_MAX_LEN; seen_dot = true; @@ -192,14 +171,23 @@ impl ShortFileName { } } _ => { + let b = if case_handling == FilenameKind::DirectoryEntry { + ch.to_ascii_uppercase() as u8 + } else { + ch as u8 + }; if seen_dot { if (Self::FILENAME_BASE_MAX_LEN..Self::FILENAME_MAX_LEN).contains(&idx) { - sfn.contents[idx] = ch; + sfn.contents[idx] = b; } else { return Err(FilenameError::NameTooLong); } } else if idx < Self::FILENAME_BASE_MAX_LEN { - sfn.contents[idx] = ch; + sfn.contents[idx] = b; + } else if idx < Self::FILENAME_MAX_LEN + && case_handling == FilenameKind::VolumeLabel + { + sfn.contents[idx] = b; } else { return Err(FilenameError::NameTooLong); } @@ -337,6 +325,17 @@ mod test { assert!(ShortFileName::create_from_str("123456789").is_err()); assert!(ShortFileName::create_from_str("12345678.ABCD").is_err()); } + + #[test] + fn volume_name() { + let sfn = ShortFileName { + contents: *b"Hello \xA399 ", + }; + assert_eq!( + sfn, + ShortFileName::create_volume_label_from_str("Hello £99").unwrap() + ) + } } // ****************************************************************************