diff options
Diffstat (limited to 'src/devices/file/bedrock_file_path.rs')
-rw-r--r-- | src/devices/file/bedrock_file_path.rs | 165 |
1 files changed, 121 insertions, 44 deletions
diff --git a/src/devices/file/bedrock_file_path.rs b/src/devices/file/bedrock_file_path.rs index c169a62..e083853 100644 --- a/src/devices/file/bedrock_file_path.rs +++ b/src/devices/file/bedrock_file_path.rs @@ -3,17 +3,13 @@ use super::*; use std::cmp::Ordering; use std::ffi::OsString; -#[cfg(target_family = "unix")] -use std::os::unix::ffi::OsStringExt; -#[cfg(target_family = "windows")] -use std::os::windows::ffi::OsStringExt; - #[derive(Clone)] pub struct BedrockFilePath { base: PathBuf, relative: PathBuf, bytes: Vec<u8>, + entry_type: Option<EntryType>, } impl BedrockFilePath { @@ -21,8 +17,9 @@ impl BedrockFilePath { let base = base.to_path_buf(); let relative = buffer_to_path(buffer)?; let bytes = path_to_bytes(&relative)?; - assert_path_is_safe(&relative)?; - Some(Self { base, relative, bytes }) + let entry_type = get_entry_type(base.join(&relative)); + assert_path_is_safe(&relative, &base)?; + Some(Self { base, relative, bytes, entry_type }) } /// Construct an instance from an absolute path and a prefix of that path. @@ -30,8 +27,9 @@ impl BedrockFilePath { let base = base.to_path_buf(); let relative = path.strip_prefix(&base).ok()?.to_path_buf(); let bytes = path_to_bytes(&relative)?; - assert_path_is_safe(&relative)?; - Some( Self { base, relative, bytes } ) + let entry_type = get_entry_type(base.join(&relative)); + assert_path_is_safe(&relative, &base)?; + Some( Self { base, relative, bytes, entry_type } ) } /// Get the base path used by this path. @@ -59,32 +57,60 @@ impl BedrockFilePath { self.base.join(&self.relative) } - /// Get a path which represents the parent of this path. - pub fn parent(&self) -> Option<Self> { - let relative = self.relative.parent()?.to_path_buf(); - let base = self.base.clone(); - let bytes = path_to_bytes(&relative)?; - Some( Self { base, relative, bytes } ) + /// Get the entry type of this path. + pub fn entry_type(&self) -> Option<EntryType> { + self.entry_type } - pub fn directory_listing(&self) -> Option<DirectoryListing> { - DirectoryListing::from_path(&self) + /// Get a path which represents the parent of this path. + pub fn parent(&self) -> Option<Self> { + #[cfg(target_family = "unix")] { + Self::from_path(self.relative.parent()?, &self.base) + } + #[cfg(target_family = "windows")] { + if self.base.components().count() != 0 { + // Sandboxed path, cannot ascend to a virtual root directory. + Self::from_path(self.relative.parent()?, &self.base) + } else { + // Unsandboxed path, we can ascend to a virtual root directory. + match self.relative.parent() { + // Ascend to concrete parent directory. + Some(parent) => Self::from_path(parent, &self.base), + // Ascend into a virtual root directory. + None => { + if self.relative.components().count() != 0 { + // Ascend from concrete path to virtual root. + let blank = PathBuf::from(""); + BedrockFilePath::from_path(&blank, &blank) + } else { + // Cannot ascend above the virtual root. + None + } + }, + } + } + } } - /// Returns true if a dot character directly follows the final - /// forward-slash character in the relative path. - pub fn filename_is_dot_prefixed(&self) -> bool { - let bytes = self.as_bytes(); - let mut dot_i = 0; - for (i, b) in bytes.iter().enumerate() { - if *b == b'/' { - // Guaranteed to be a valid index, bytes is null-terminated. - dot_i = i + 1; - } else if *b == 0x00 { - break; + /// Returns true if the file would be hidden by the default file browser. + pub fn is_hidden(&self) -> bool { + #[cfg(target_family = "unix")] { + if let Some(stem) = self.relative.file_stem() { + if let Some(string) = stem.to_str() { + return string.starts_with('.'); + } + } + } + #[cfg(target_family = "windows")] { + use std::os::windows::fs::MetadataExt; + // See https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants + // const FILE_ATTRIBUTE_HIDDEN: u32 = 0x00000002; + // const FILE_ATTRIBUTE_NOT_CONTENT_INDEXED: u32 = 0x00002000; + if let Ok(metadata) = std::fs::metadata(self.as_path()) { + return metadata.file_attributes() & 0x2002 != 0; } } - return bytes[dot_i] == b'.'; + return false; } } @@ -108,11 +134,19 @@ fn buffer_to_path(bytes: [u8; 256]) -> Option<PathBuf> { let slice = &bytes[1..null_i?]; #[cfg(target_family = "unix")] { + use std::os::unix::ffi::OsStringExt; let vec = Vec::from(slice); return Some(OsString::from_vec(vec).into()) } #[cfg(target_family = "windows")] { - let string = String::from_utf8_lossy(slice); + use std::os::windows::ffi::OsStringExt; + let mut string = String::from_utf8_lossy(slice).to_string(); + // Convert drive-current-directory paths to drive-root paths. This is + // needed because the paths C: and C:/ point to separate directories, + // but trailing forward-slashes are optional in Bedrock. + if string.ends_with(':') { + string.push('/'); + } let utf16: Vec<u16> = string.replace(r"/", r"\").encode_utf16().collect(); return Some(OsString::from_wide(&utf16).into()) } @@ -122,14 +156,15 @@ fn buffer_to_path(bytes: [u8; 256]) -> Option<PathBuf> { /// /// A byte path contains at most 255 bytes, and is not null-terminated. fn path_to_bytes(path: &Path) -> Option<Vec<u8>> { - let mut string = String::from("/"); #[cfg(target_family = "unix")] - string.push_str(path.as_os_str().to_str()?); + let string = path.as_os_str().to_str()?.to_string(); #[cfg(target_family = "windows")] - string.push_str(&path.as_os_str().to_str()?.replace(r"\", r"/")); + let string = path.as_os_str().to_str()?.replace(r"\", r"/"); - // Remove all trailing forward-slash characters. - let slice = string.trim_end_matches('/').as_bytes(); + // Remove any trailing forward-slash and add a leading forward-slash. + let mut prefixed_string = String::from("/"); + prefixed_string.push_str(string.trim_end_matches('/')); + let slice = prefixed_string.as_bytes(); // Error if bytes does not fit into a CircularPathBuffer. if slice.len() > 255 { return None; } @@ -137,24 +172,63 @@ fn path_to_bytes(path: &Path) -> Option<Vec<u8>> { Some(Vec::from(slice)) } -/// Returns true if a path contains only normal components. -fn assert_path_is_safe(relative: &Path) -> Option<()> { - // Error if path contains special components. - for component in relative.components() { - match component { - Component::Normal(_) => continue, - _ => return None, +/// Returns true if a relative path can be safely attached to a base without +/// breaking out of the sandbox. +fn assert_path_is_safe(relative: &Path, base: &Path) -> Option<()> { + #[cfg(target_family = "unix")] { + // Error if path contains special components. + for component in relative.components() { + match component { + Component::Normal(_) => continue, + _ => return None, + } + } + } + #[cfg(target_family = "windows")] { + // If the base path is empty, the relative path needs to be able to + // contain the prefix and root element. If the base path is not + // empty, the relative path must not contain these elements else + // they will override the base path when joined. + if base.components().count() != 0 { + for component in relative.components() { + match component { + Component::Normal(_) => continue, + _ => return None, + } + } } } return Some(()); } +fn get_entry_type(absolute: PathBuf) -> Option<EntryType> { + #[cfg(target_family = "windows")] { + // If path is empty, this is a virtual root directory. + if absolute.components().count() == 0 { + return Some(EntryType::Directory) + } + } + let metadata = std::fs::metadata(absolute).ok()?; + if metadata.is_file() { + Some(EntryType::File) + } else if metadata.is_dir() { + Some(EntryType::Directory) + } else { + None + } +} + +impl std::fmt::Debug for BedrockFilePath { + fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + self.as_path().fmt(f) + } +} // --------------------------------------------------------------------------- impl PartialEq for BedrockFilePath { fn eq(&self, other: &Self) -> bool { - self.bytes == other.bytes + self.bytes == other.bytes && self.entry_type == other.entry_type } } @@ -168,7 +242,10 @@ impl PartialOrd for BedrockFilePath { impl Ord for BedrockFilePath { fn cmp(&self, other: &Self) -> Ordering { - compare_ascii_slices(&self.bytes, &other.bytes) + match self.entry_type.cmp(&other.entry_type) { + Ordering::Equal => compare_ascii_slices(&self.bytes, &other.bytes), + ordering => ordering, + } } } |