use alloc::borrow::ToOwned;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt::Display;
use core::str::FromStr;
use spin::Lazy;
use super::error::SfsError;
use crate::error::Error;
use crate::fs::error::FsError;
use crate::path::{Path, PathError, UnixStr};
pub static ROOT_NAME_STRING: Lazy<NameString> = Lazy::new(|| NameString(String::from("/")));
#[must_use]
pub const fn is_forbidden_character(c: &u8) -> bool {
matches!(c, 0x00..0x20 | 0x80..=0x9F | b'"' | b'*' | b':' | b'<' | b'>' | b'?' | b'\\' | 0x7F | 0xA0)
}
#[must_use]
pub fn is_valid_name_string(str: &[u8]) -> bool {
if str.len() <= 1 {
return false;
}
for c in str.iter().rev().skip(1).rev() {
if is_forbidden_character(c) {
return false;
}
}
str.last().is_some_and(|c| *c == b'\0')
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NameString(String);
impl NameString {
pub fn new(str: Vec<u8>) -> Result<Self, Error<SfsError>> {
if is_valid_name_string(&str)
&& let Ok(inner) = String::from_utf8(str.clone())
{
Ok(Self(unsafe { inner.strip_suffix('\0').unwrap_unchecked().to_owned() }))
} else {
Err(Error::Fs(FsError::Implementation(SfsError::InvalidNameString(str))))
}
}
pub fn new_from_start(seq: &[u8]) -> Result<Self, Error<SfsError>> {
seq.iter().position(|byte| *byte == 0).map_or_else(
|| {
let mut seq_with_nul = seq.to_vec();
seq_with_nul.push(0);
Self::new(seq_with_nul)
},
|idx| Self::new(seq[..=idx].to_vec()),
)
}
pub fn join(&mut self, other: &Self) {
self.0.push_str(&other.0);
}
}
impl FromStr for NameString {
type Err = Error<SfsError>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut bytes = s.as_bytes().to_vec();
bytes.push(b'\0');
if is_valid_name_string(&bytes) {
Ok(Self(s.to_owned()))
} else {
Err(Error::Fs(FsError::Implementation(SfsError::InvalidNameString(bytes))))
}
}
}
impl From<NameString> for UnixStr<'_> {
fn from(value: NameString) -> Self {
let full_path = format!("/{}", value.0);
unsafe { UnixStr::from_str(&full_path).unwrap_unchecked() }
}
}
impl From<NameString> for Path<'_> {
fn from(value: NameString) -> Self {
UnixStr::from(value).into()
}
}
impl TryFrom<Path<'_>> for NameString {
type Error = Error<SfsError>;
fn try_from(path: Path<'_>) -> Result<Self, Self::Error> {
if !path.is_absolute() {
return Err(Error::Path(PathError::AbsolutePathRequired(path.to_string())));
}
let path_str = path.to_string();
let stripped_path = unsafe { path_str.strip_prefix("/").unwrap_unchecked() };
let bytes = stripped_path.as_bytes();
if bytes.is_empty() || bytes.iter().any(is_forbidden_character) {
Err(Error::Fs(FsError::Implementation(SfsError::InvalidNameString(bytes.to_vec()))))
} else {
Ok(Self(stripped_path.to_owned()))
}
}
}
impl Display for NameString {
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(fmt, "{}", UnixStr::from(self.clone()))
}
}
#[cfg(test)]
mod test {
use core::str::FromStr;
use super::NameString;
use crate::path::Path;
#[test]
fn name_string_creation() {
assert!(NameString::new(b"".to_vec()).is_err());
assert!(NameString::new(b"\0".to_vec()).is_err());
assert!(NameString::new(b"foo\0".to_vec()).is_ok());
assert!(NameString::new(b"foo".to_vec()).is_err());
assert!(NameString::new(b"foo*.txt\0".to_vec()).is_err());
assert!(NameString::from_str("foo").is_ok());
assert!(NameString::from_str("foo.txt").is_ok());
assert!(NameString::from_str("foo/bar").is_ok());
assert!(NameString::from_str("foo/bar/baz.txt").is_ok());
assert!(NameString::from_str("foo.txt\0").is_err());
assert!(NameString::from_str("foo:txt").is_err());
assert!(NameString::from_str("foo*txt").is_err());
}
#[test]
fn name_string_to_path() {
assert_eq!(Path::from(NameString::from_str("foo.txt").unwrap()), Path::from_str("/foo.txt").unwrap());
assert_eq!(Path::from(NameString::from_str("foo/bar.txt").unwrap()), Path::from_str("/foo/bar.txt").unwrap());
}
#[test]
fn path_to_name_string() {
assert_eq!(
NameString::try_from(Path::from_str("/foo.txt").unwrap()).unwrap(),
NameString::from_str("foo.txt").unwrap()
);
assert_eq!(
NameString::try_from(Path::from_str("/foo/bar.txt").unwrap()).unwrap(),
NameString::from_str("foo/bar.txt").unwrap()
);
}
#[test]
fn name_string_from_start() {
assert_eq!(
NameString::new_from_start(b"foo.txt\0*:zeaqqdqs#").unwrap(),
NameString::from_str("foo.txt").unwrap()
);
assert_eq!(NameString::new_from_start(b"foo.txttoto").unwrap(), NameString::from_str("foo.txttoto").unwrap());
assert!(NameString::new_from_start(b"foo.txt*\0:zeaqqdqs#").is_err());
}
}