summaryrefslogtreecommitdiff
path: root/src/devices/file/bedrock_file_path.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/file/bedrock_file_path.rs')
-rw-r--r--src/devices/file/bedrock_file_path.rs165
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,
+ }
}
}