| 1 | use std::io; |
| 2 | |
| 3 | /// Observed directory metadata, not a stable notebook identity or a file snapshot. |
| 4 | #[derive(Debug, Clone, PartialEq, Eq)] |
| 5 | pub struct DirectoryEntry { |
| 6 | pub name: String, |
| 7 | pub size: u64, |
| 8 | /// LastWriteTime, FILETIME. |
| 9 | pub modified: u64, |
| 10 | /// MS-FSCC file attributes; directory is 0x10 and reparse point is 0x400. |
| 11 | pub attributes: u32, |
| 12 | } |
| 13 | |
| 14 | pub(super) fn decode(mut bytes: &[u8]) -> io::Result<Vec<DirectoryEntry>> { |
| 15 | if bytes.len() > 65536 { |
| 16 | return Err(io::ErrorKind::InvalidData.into()); |
| 17 | } |
| 18 | let mut entries = Vec::new(); |
| 19 | loop { |
| 20 | if bytes.len() < 64 { |
| 21 | return Err(io::ErrorKind::InvalidData.into()); |
| 22 | } |
| 23 | let next = u32::from_le_bytes(bytes[..4].try_into().unwrap()) as usize; |
| 24 | let length = u32::from_le_bytes(bytes[60..64].try_into().unwrap()) as usize; |
| 25 | let end = 64usize |
| 26 | .checked_add(length) |
| 27 | .ok_or(io::ErrorKind::InvalidData)?; |
| 28 | if length == 0 |
| 29 | || !length.is_multiple_of(2) |
| 30 | || end > bytes.len() |
| 31 | || (next != 0 && (next < end || !next.is_multiple_of(8) || next >= bytes.len())) |
| 32 | || (next == 0 && bytes.len() - end > 7) |
| 33 | { |
| 34 | return Err(io::ErrorKind::InvalidData.into()); |
| 35 | } |
| 36 | let units: Vec<_> = bytes[64..end] |
| 37 | .chunks_exact(2) |
| 38 | .map(|unit| u16::from_le_bytes([unit[0], unit[1]])) |
| 39 | .collect(); |
| 40 | let name = String::from_utf16(&units).map_err(|_| io::ErrorKind::InvalidData)?; |
| 41 | if name.contains(['\0', '/', '\\']) { |
| 42 | return Err(io::ErrorKind::InvalidData.into()); |
| 43 | } |
| 44 | let size = i64::from_le_bytes(bytes[40..48].try_into().unwrap()); |
| 45 | entries.push(DirectoryEntry { |
| 46 | name, |
| 47 | size: size.try_into().map_err(|_| io::ErrorKind::InvalidData)?, |
| 48 | modified: u64::from_le_bytes(bytes[24..32].try_into().unwrap()), |
| 49 | attributes: u32::from_le_bytes(bytes[56..60].try_into().unwrap()), |
| 50 | }); |
| 51 | if next == 0 { |
| 52 | return Ok(entries); |
| 53 | } |
| 54 | bytes = &bytes[next..]; |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | #[cfg(test)] |
| 59 | mod tests { |
| 60 | use super::*; |
| 61 | |
| 62 | fn record(name: &str, size: u64, attributes: u32) -> Vec<u8> { |
| 63 | let mut bytes = vec![0; 64]; |
| 64 | bytes[24..32].copy_from_slice(&(size + 7).to_le_bytes()); |
| 65 | bytes[40..48].copy_from_slice(&size.to_le_bytes()); |
| 66 | bytes[56..60].copy_from_slice(&attributes.to_le_bytes()); |
| 67 | let name: Vec<_> = name.encode_utf16().flat_map(u16::to_le_bytes).collect(); |
| 68 | bytes[60..64].copy_from_slice(&(name.len() as u32).to_le_bytes()); |
| 69 | bytes.extend(name); |
| 70 | bytes |
| 71 | } |
| 72 | |
| 73 | #[test] |
| 74 | fn directory_records_preserve_names_sizes_and_attributes() { |
| 75 | let mut bytes = Vec::new(); |
| 76 | let mut expected = Vec::new(); |
| 77 | for i in 0..300u32 { |
| 78 | let name = format!("Section {i} 🦀 e\u{301}.one"); |
| 79 | let size = u64::from(i) * 100_000_000; |
| 80 | let attributes = if i % 3 == 0 { 0x410 } else { 0x20 }; |
| 81 | let mut entry = record(&name, size, attributes); |
| 82 | if i != 299 { |
| 83 | entry.resize(entry.len().next_multiple_of(8), 0xa5); |
| 84 | let length = entry.len() as u32; |
| 85 | entry[..4].copy_from_slice(&length.to_le_bytes()); |
| 86 | } |
| 87 | bytes.extend(entry); |
| 88 | expected.push(DirectoryEntry { |
| 89 | name, |
| 90 | size, |
| 91 | modified: size + 7, |
| 92 | attributes, |
| 93 | }); |
| 94 | } |
| 95 | assert_eq!(decode(&bytes).unwrap(), expected); |
| 96 | for end in 0..bytes.len() { |
| 97 | assert!(decode(&bytes[..end]).is_err(), "accepted prefix {end}"); |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | #[test] |
| 102 | fn invalid_directory_records_never_become_partial_results() { |
| 103 | assert!(decode(&vec![0; 65537]).is_err()); |
| 104 | for name in ["", "bad\0name", "a/b", "a\\b"] { |
| 105 | assert!(decode(&record(name, 0, 0)).is_err()); |
| 106 | } |
| 107 | let valid = record("a.one", 12, 0x20); |
| 108 | for (at, value) in [ |
| 109 | (0, 8), |
| 110 | (0, 65), |
| 111 | (0, 72), |
| 112 | (0, u32::MAX), |
| 113 | (60, 0), |
| 114 | (60, 1), |
| 115 | (60, u32::MAX), |
| 116 | (44, u32::MAX), |
| 117 | ] { |
| 118 | let mut bytes = valid.clone(); |
| 119 | bytes[at..at + 4].copy_from_slice(&value.to_le_bytes()); |
| 120 | assert!(decode(&bytes).is_err(), "offset={at} value={value}"); |
| 121 | } |
| 122 | let mut bytes = valid.clone(); |
| 123 | bytes[64..66].copy_from_slice(&0xd800u16.to_le_bytes()); |
| 124 | assert!(decode(&bytes).is_err()); |
| 125 | let mut bytes = valid; |
| 126 | bytes.extend_from_slice(&[0; 8]); |
| 127 | assert!(decode(&bytes).is_err()); |
| 128 | for name in [".", ".."] { |
| 129 | assert_eq!(decode(&record(name, 0, 0x10)).unwrap()[0].name, name); |
| 130 | } |
| 131 | } |
| 132 | } |