use super::ValidationError;
use super::utf8::Utf8Bytes;
#[cfg(unix)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PathBytes<const MAX_LEN: usize = 4096> {
utf8: Utf8Bytes<MAX_LEN>,
}
#[cfg(unix)]
impl<const MAX_LEN: usize> PathBytes<MAX_LEN> {
pub fn from_slice(bytes: &[u8]) -> Result<Self, ValidationError> {
let len = bytes.len();
if len > MAX_LEN {
return Err(ValidationError::TooLong {
max: MAX_LEN,
actual: len,
});
}
let mut fixed = [0u8; MAX_LEN];
fixed[..len].copy_from_slice(bytes);
let utf8 = Utf8Bytes::new(fixed, len)?;
#[cfg(kani)]
{
let has_null: bool = kani::any();
if has_null {
return Err(ValidationError::PathContainsNull);
}
}
#[cfg(not(kani))]
{
if has_null_byte(utf8.as_str()) {
return Err(ValidationError::PathContainsNull);
}
}
Ok(Self { utf8 })
}
pub fn new(bytes: Vec<u8>) -> Result<Self, ValidationError> {
Self::from_slice(&bytes)
}
pub fn as_str(&self) -> &str {
self.utf8.as_str()
}
pub fn as_bytes(&self) -> &[u8] {
self.utf8.as_str().as_bytes()
}
pub fn len(&self) -> usize {
self.utf8.len()
}
pub fn is_empty(&self) -> bool {
self.utf8.is_empty()
}
pub fn is_absolute(&self) -> bool {
is_absolute(self.as_str())
}
pub fn is_relative(&self) -> bool {
!self.is_absolute()
}
pub fn is_root(&self) -> bool {
self.as_str() == "/"
}
}
impl<const MAX_LEN: usize> std::fmt::Display for PathBytes<MAX_LEN> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.utf8)
}
}
pub fn has_null_byte(s: &str) -> bool {
let bytes = s.as_bytes();
let len = bytes.len();
let mut i = 0;
while i < len {
if bytes[i] == 0 {
return true;
}
i += 1;
}
false
}
#[cfg(unix)]
pub fn is_absolute(path: &str) -> bool {
let bytes = path.as_bytes();
!bytes.is_empty() && bytes[0] == b'/'
}
#[cfg(unix)]
pub fn is_relative(path: &str) -> bool {
!is_absolute(path)
}
#[cfg(unix)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PathAbsolute<const MAX_LEN: usize = 4096>(PathBytes<MAX_LEN>);
#[cfg(unix)]
impl<const MAX_LEN: usize> PathAbsolute<MAX_LEN> {
pub fn from_slice(bytes: &[u8]) -> Result<Self, ValidationError> {
let path = PathBytes::from_slice(bytes)?;
#[cfg(kani)]
{
let is_rel: bool = kani::any();
if is_rel {
return Err(ValidationError::PathNotAbsolute(String::new()));
}
}
#[cfg(not(kani))]
{
if !path.is_absolute() {
return Err(ValidationError::PathNotAbsolute(path.to_string()));
}
}
Ok(Self(path))
}
pub fn new(bytes: Vec<u8>) -> Result<Self, ValidationError> {
Self::from_slice(&bytes)
}
pub fn get(&self) -> &PathBytes<MAX_LEN> {
&self.0
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn into_inner(self) -> PathBytes<MAX_LEN> {
self.0
}
}
#[cfg(unix)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PathRelative<const MAX_LEN: usize = 4096>(PathBytes<MAX_LEN>);
#[cfg(unix)]
impl<const MAX_LEN: usize> PathRelative<MAX_LEN> {
pub fn from_slice(bytes: &[u8]) -> Result<Self, ValidationError> {
let path = PathBytes::from_slice(bytes)?;
#[cfg(kani)]
{
let is_abs: bool = kani::any();
if is_abs {
return Err(ValidationError::PathNotRelative(String::new()));
}
}
#[cfg(not(kani))]
{
if !path.is_relative() {
return Err(ValidationError::PathNotRelative(path.to_string()));
}
}
Ok(Self(path))
}
pub fn new(bytes: Vec<u8>) -> Result<Self, ValidationError> {
Self::from_slice(&bytes)
}
pub fn get(&self) -> &PathBytes<MAX_LEN> {
&self.0
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn into_inner(self) -> PathBytes<MAX_LEN> {
self.0
}
}
#[cfg(unix)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PathNonEmpty<const MAX_LEN: usize = 4096>(PathBytes<MAX_LEN>);
#[cfg(unix)]
impl<const MAX_LEN: usize> PathNonEmpty<MAX_LEN> {
pub fn from_slice(bytes: &[u8]) -> Result<Self, ValidationError> {
let path = PathBytes::from_slice(bytes)?;
if path.is_empty() {
return Err(ValidationError::EmptyString);
}
Ok(Self(path))
}
pub fn new(bytes: Vec<u8>) -> Result<Self, ValidationError> {
Self::from_slice(&bytes)
}
pub fn get(&self) -> &PathBytes<MAX_LEN> {
&self.0
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn into_inner(self) -> PathBytes<MAX_LEN> {
self.0
}
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
#[test]
fn test_valid_unix_path() {
let bytes = b"/home/user/file.txt".to_vec();
let path = PathBytes::<4096>::new(bytes);
assert!(path.is_ok());
assert_eq!(path.unwrap().as_str(), "/home/user/file.txt");
}
#[test]
fn test_path_with_null_rejected() {
let bytes = b"/home/\0user/file.txt".to_vec();
let path = PathBytes::<4096>::new(bytes);
assert!(path.is_err());
}
#[test]
fn test_invalid_utf8_rejected() {
let bytes = vec![0xFF, 0xFE]; let path = PathBytes::<4096>::new(bytes);
assert!(path.is_err());
}
#[test]
fn test_absolute_path_detection() {
let bytes = b"/home/user".to_vec();
let path = PathBytes::<4096>::new(bytes).unwrap();
assert!(path.is_absolute());
assert!(!path.is_relative());
}
#[test]
fn test_relative_path_detection() {
let bytes = b"home/user".to_vec();
let path = PathBytes::<4096>::new(bytes).unwrap();
assert!(path.is_relative());
assert!(!path.is_absolute());
}
#[test]
fn test_root_path() {
let bytes = b"/".to_vec();
let path = PathBytes::<4096>::new(bytes).unwrap();
assert!(path.is_root());
assert!(path.is_absolute());
}
#[test]
fn test_path_absolute_construction() {
let bytes = b"/home/user".to_vec();
let abs = PathAbsolute::<4096>::new(bytes);
assert!(abs.is_ok());
let bytes = b"home/user".to_vec();
let abs = PathAbsolute::<4096>::new(bytes);
assert!(abs.is_err());
}
#[test]
fn test_path_relative_construction() {
let bytes = b"home/user".to_vec();
let rel = PathRelative::<4096>::new(bytes);
assert!(rel.is_ok());
let bytes = b"/home/user".to_vec();
let rel = PathRelative::<4096>::new(bytes);
assert!(rel.is_err());
}
#[test]
fn test_path_nonempty_construction() {
let bytes = b"/home".to_vec();
let nonempty = PathNonEmpty::<4096>::new(bytes);
assert!(nonempty.is_ok());
let bytes = b"".to_vec();
let nonempty = PathNonEmpty::<4096>::new(bytes);
assert!(nonempty.is_err());
}
#[test]
fn test_special_paths() {
let bytes = b".".to_vec();
let path = PathBytes::<4096>::new(bytes).unwrap();
assert!(path.is_relative());
let bytes = b"..".to_vec();
let path = PathBytes::<4096>::new(bytes).unwrap();
assert!(path.is_relative());
let bytes = b"../parent".to_vec();
let path = PathBytes::<4096>::new(bytes).unwrap();
assert!(path.is_relative());
}
}