use std::path::Component;
use std::path::Path;
use hf_hub::repository::RepoTreeEntry;
use crate::error::CanddingError;
use crate::error::Result;
pub(super) struct ListedFile {
pub path: String,
pub size: u64,
pub oid: String,
pub in_git: bool,
}
pub(super) fn files(entries: Vec<RepoTreeEntry>) -> Vec<ListedFile> {
entries
.into_iter()
.filter_map(|entry| match entry {
RepoTreeEntry::File {
path,
size,
oid,
lfs,
xet_hash,
..
} => Some(ListedFile {
path,
size,
oid,
in_git: lfs.is_none() && xet_hash.is_none(),
}),
RepoTreeEntry::Directory { .. } => None,
})
.collect()
}
pub(super) fn check_commit(id: &str, commit: &str) -> Result<()> {
let hex = commit
.bytes()
.all(|byte| matches!(byte, b'0'..=b'9' | b'a'..=b'f'));
if commit.len() == 40 && hex {
return Ok(());
}
Err(unsafe_path(
id,
commit,
"is not a commit of 40 lowercase hexadecimal characters",
))
}
pub(super) fn check_paths(id: &str, files: &[ListedFile]) -> Result<()> {
match files
.iter()
.find_map(|file| problem(&file.path).map(|reason| (file, reason)))
{
Some((file, reason)) => Err(unsafe_path(id, &file.path, reason)),
None => Ok(()),
}
}
pub(super) fn problem(path: &str) -> Option<&'static str> {
if Path::new(path).has_root() {
Some("is absolute")
} else if path.contains('\\') {
Some("contains a backslash")
} else if path.split('/').any(str::is_empty) {
Some("has an empty component")
} else if path.split('/').any(|part| part == "..") {
Some("has a `..` component")
} else if path.split('/').any(|part| part == ".") {
Some("has a `.` component")
} else if !Path::new(path)
.components()
.all(|part| matches!(part, Component::Normal(_)))
{
Some("has a component that is not a plain name")
} else {
None
}
}
fn unsafe_path(id: &str, path: &str, reason: &str) -> CanddingError {
CanddingError::UnsafeHubPath {
id: id.to_string(),
path: path.to_string(),
reason: reason.to_string(),
}
}