mod components;
use core::fmt;
use core::hash::Hasher;
pub use components::*;
use super::constants::*;
use crate::common::CheckedPathError;
use crate::no_std_compat::*;
use crate::typed::{TypedPath, TypedPathBuf};
use crate::{private, Components, Encoding, Path, PathBuf};
pub type UnixPath = Path<UnixEncoding>;
pub type UnixPathBuf = PathBuf<UnixEncoding>;
#[derive(Copy, Clone)]
pub struct UnixEncoding;
impl private::Sealed for UnixEncoding {}
impl<'a> Encoding<'a> for UnixEncoding {
type Components = UnixComponents<'a>;
fn label() -> &'static str {
"unix"
}
fn components(path: &'a [u8]) -> Self::Components {
UnixComponents::new(path)
}
fn hash<H: Hasher>(path: &[u8], h: &mut H) {
let mut component_start = 0;
let mut bytes_hashed = 0;
for i in 0..path.len() {
let is_sep = path[i] == SEPARATOR as u8;
if is_sep {
if i > component_start {
let to_hash = &path[component_start..i];
h.write(to_hash);
bytes_hashed += to_hash.len();
}
component_start = i + 1;
let tail = &path[component_start..];
component_start += match tail {
[b'.'] => 1,
[b'.', sep, ..] if *sep == SEPARATOR as u8 => 1,
_ => 0,
};
}
}
if component_start < path.len() {
let to_hash = &path[component_start..];
h.write(to_hash);
bytes_hashed += to_hash.len();
}
h.write_usize(bytes_hashed);
}
fn push(current_path: &mut Vec<u8>, path: &[u8]) {
if path.is_empty() {
return;
}
if Self::components(path).is_absolute() {
current_path.clear();
} else if !current_path.is_empty() && !current_path.ends_with(&[SEPARATOR as u8]) {
current_path.push(SEPARATOR as u8);
}
current_path.extend_from_slice(path);
}
fn push_checked(current_path: &mut Vec<u8>, path: &[u8]) -> Result<(), CheckedPathError> {
let mut normal_cnt = 0;
for component in UnixPath::new(path).components() {
match component {
UnixComponent::RootDir => return Err(CheckedPathError::UnexpectedRoot),
UnixComponent::ParentDir if normal_cnt == 0 => {
return Err(CheckedPathError::PathTraversalAttack)
}
UnixComponent::ParentDir => normal_cnt -= 1,
UnixComponent::Normal(bytes) => {
for b in bytes {
if DISALLOWED_FILENAME_BYTES.contains(b) {
return Err(CheckedPathError::InvalidFilename);
}
}
normal_cnt += 1;
}
_ => continue,
}
}
Self::push(current_path, path);
Ok(())
}
}
impl fmt::Debug for UnixEncoding {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UnixEncoding").finish()
}
}
impl fmt::Display for UnixEncoding {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "UnixEncoding")
}
}
impl<T> Path<T>
where
T: for<'enc> Encoding<'enc>,
{
pub fn has_unix_encoding(&self) -> bool {
T::label() == UnixEncoding::label()
}
pub fn with_unix_encoding(&self) -> PathBuf<UnixEncoding> {
self.with_encoding()
}
pub fn with_unix_encoding_checked(&self) -> Result<PathBuf<UnixEncoding>, CheckedPathError> {
self.with_encoding_checked()
}
}
impl UnixPath {
pub fn to_typed_path(&self) -> TypedPath {
TypedPath::unix(self)
}
pub fn to_typed_path_buf(&self) -> TypedPathBuf {
TypedPathBuf::from_unix(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn push_should_replace_current_path_with_provided_path_if_provided_path_is_absolute() {
let mut current_path = vec![];
UnixEncoding::push(&mut current_path, b"/abc");
assert_eq!(current_path, b"/abc");
let mut current_path = b"some/path".to_vec();
UnixEncoding::push(&mut current_path, b"/abc");
assert_eq!(current_path, b"/abc");
let mut current_path = b"/some/path/".to_vec();
UnixEncoding::push(&mut current_path, b"/abc");
assert_eq!(current_path, b"/abc");
}
#[test]
fn push_should_append_path_to_current_path_with_a_separator_if_provided_path_is_relative() {
let mut current_path = vec![];
UnixEncoding::push(&mut current_path, b"abc");
assert_eq!(current_path, b"abc");
let mut current_path = b"some/path".to_vec();
UnixEncoding::push(&mut current_path, b"abc");
assert_eq!(current_path, b"some/path/abc");
let mut current_path = b"some/path/".to_vec();
UnixEncoding::push(&mut current_path, b"abc");
assert_eq!(current_path, b"some/path/abc");
}
#[test]
fn push_checked_should_fail_if_providing_an_absolute_path() {
let mut current_path = vec![];
assert_eq!(
UnixEncoding::push_checked(&mut current_path, b"/abc"),
Err(CheckedPathError::UnexpectedRoot)
);
assert_eq!(current_path, b"");
let mut current_path = b"some/path".to_vec();
assert_eq!(
UnixEncoding::push_checked(&mut current_path, b"/abc"),
Err(CheckedPathError::UnexpectedRoot)
);
assert_eq!(current_path, b"some/path");
let mut current_path = b"/some/path/".to_vec();
assert_eq!(
UnixEncoding::push_checked(&mut current_path, b"/abc"),
Err(CheckedPathError::UnexpectedRoot)
);
assert_eq!(current_path, b"/some/path/");
}
#[test]
fn push_checked_should_fail_if_providing_a_path_with_disallowed_filename_bytes() {
let mut current_path = vec![];
assert_eq!(
UnixEncoding::push_checked(&mut current_path, b"some/inva\0lid/path"),
Err(CheckedPathError::InvalidFilename)
);
assert_eq!(current_path, b"");
let mut current_path = b"some/path".to_vec();
assert_eq!(
UnixEncoding::push_checked(&mut current_path, b"some/inva\0lid/path"),
Err(CheckedPathError::InvalidFilename)
);
assert_eq!(current_path, b"some/path");
let mut current_path = b"/some/path/".to_vec();
assert_eq!(
UnixEncoding::push_checked(&mut current_path, b"some/inva\0lid/path"),
Err(CheckedPathError::InvalidFilename)
);
assert_eq!(current_path, b"/some/path/");
}
#[test]
fn push_checked_should_fail_if_providing_a_path_that_would_escape_the_current_path() {
let mut current_path = vec![];
assert_eq!(
UnixEncoding::push_checked(&mut current_path, b".."),
Err(CheckedPathError::PathTraversalAttack)
);
assert_eq!(current_path, b"");
let mut current_path = b"some/path".to_vec();
assert_eq!(
UnixEncoding::push_checked(&mut current_path, b".."),
Err(CheckedPathError::PathTraversalAttack)
);
assert_eq!(current_path, b"some/path");
let mut current_path = b"/some/path/".to_vec();
assert_eq!(
UnixEncoding::push_checked(&mut current_path, b".."),
Err(CheckedPathError::PathTraversalAttack)
);
assert_eq!(current_path, b"/some/path/");
}
#[test]
fn push_checked_should_append_path_to_current_path_with_a_separator_if_does_not_violate_rules()
{
let mut current_path = vec![];
assert_eq!(
UnixEncoding::push_checked(&mut current_path, b"abc/../def/."),
Ok(()),
);
assert_eq!(current_path, b"abc/../def/.");
let mut current_path = b"some/path".to_vec();
assert_eq!(
UnixEncoding::push_checked(&mut current_path, b"abc/../def/."),
Ok(()),
);
assert_eq!(current_path, b"some/path/abc/../def/.");
let mut current_path = b"/some/path/".to_vec();
assert_eq!(
UnixEncoding::push_checked(&mut current_path, b"abc/../def/."),
Ok(()),
);
assert_eq!(current_path, b"/some/path/abc/../def/.");
}
}