use std::cmp::Ordering;
use std::io;
use std::path::{Component, Path, PathBuf};
const MAX_NAME_LEN: usize = 31;
pub fn compare_names(name1: &str, name2: &str) -> Ordering {
match name1.encode_utf16().count().cmp(&name2.encode_utf16().count()) {
Ordering::Equal => name1.to_uppercase().cmp(&name2.to_uppercase()),
other => other,
}
}
pub fn validate_name(name: &str) -> io::Result<Vec<u16>> {
let name_utf16: Vec<u16> =
name.encode_utf16().take(MAX_NAME_LEN + 1).collect();
if name_utf16.len() > MAX_NAME_LEN {
invalid_input!(
"Object name cannot be more than {} UTF-16 code units \
(was {})",
MAX_NAME_LEN,
name.encode_utf16().count()
);
}
for &chr in &['/', '\\', ':', '!'] {
if name.contains(chr) {
invalid_input!("Object name cannot contain {} character", chr);
}
}
Ok(name_utf16)
}
pub fn name_chain_from_path(path: &Path) -> io::Result<Vec<&str>> {
let mut names: Vec<&str> = Vec::new();
for component in path.components() {
match component {
Component::Prefix(_) => {
invalid_input!("Invalid path (must not have prefix)");
}
Component::RootDir => names.clear(),
Component::CurDir => {}
Component::ParentDir => {
if names.pop().is_none() {
invalid_input!("Invalid path (must be within root)");
}
}
Component::Normal(osstr) => match osstr.to_str() {
Some(name) => names.push(name),
None => invalid_input!("Non UTF-8 path"),
},
}
}
Ok(names)
}
pub fn path_from_name_chain(names: &[&str]) -> PathBuf {
let mut path = PathBuf::from("/");
for name in names {
path.push(name);
}
path
}
#[cfg(test)]
mod tests {
use super::{
compare_names, name_chain_from_path, path_from_name_chain,
validate_name,
};
use std::cmp::Ordering;
use std::path::{Path, PathBuf};
#[test]
fn name_ordering() {
assert_eq!(compare_names("foobar", "FOOBAR"), Ordering::Equal);
assert_eq!(compare_names("foo", "barfoo"), Ordering::Less);
assert_eq!(compare_names("Foo", "bar"), Ordering::Greater);
}
#[test]
fn short_name_is_valid() {
assert_eq!(
validate_name("Foobar").unwrap(),
vec![70, 111, 111, 98, 97, 114]
);
}
#[test]
#[should_panic(
expected = "Object name cannot be more than 31 UTF-16 code \
units (was 35)"
)]
fn long_name_is_invalid() {
validate_name("ThisNameIsMostDefinitelyMuchTooLong").unwrap();
}
#[test]
#[should_panic(expected = "Object name cannot contain / character")]
fn name_with_slash_is_invalid() {
validate_name("foo/bar").unwrap();
}
#[test]
fn absolute_path_is_valid() {
assert_eq!(
name_chain_from_path(&Path::new("/foo/bar/baz/")).unwrap(),
vec!["foo", "bar", "baz"]
);
}
#[test]
fn relative_path_is_valid() {
assert_eq!(
name_chain_from_path(&Path::new("foo/bar/baz")).unwrap(),
vec!["foo", "bar", "baz"]
);
}
#[test]
fn path_with_parents_is_valid() {
assert_eq!(
name_chain_from_path(&Path::new("foo/bar/../baz")).unwrap(),
vec!["foo", "baz"]
);
}
#[test]
#[should_panic(expected = "Invalid path (must be within root)")]
fn parent_of_root_is_invalid() {
name_chain_from_path(&Path::new("foo/../../baz")).unwrap();
}
#[test]
fn canonical_path_is_absolute() {
let path = Path::new("foo/bar/../baz");
let names = name_chain_from_path(&path).unwrap();
assert_eq!(path_from_name_chain(&names), PathBuf::from("/foo/baz"));
}
}