use crate::{PrayError, PrayResult};
use std::fs;
use std::path::{Component, Path, PathBuf};
pub fn find_prayspec_file(root: &Path) -> PrayResult<PathBuf> {
let mut prayspec_files = Vec::new();
for entry in fs::read_dir(root)? {
let entry = entry?;
let path = entry.path();
if path.extension().and_then(|value| value.to_str()) == Some("prayspec") {
prayspec_files.push(path);
}
}
match prayspec_files.len() {
1 => Ok(prayspec_files.remove(0)),
0 => Err(PrayError::Resolution(format!(
"no prayspec file found in {:?}",
root
))),
_ => Err(PrayError::Resolution(format!(
"multiple prayspec files found in {:?}",
root
))),
}
}
pub fn validate_package_relative_path(path: &Path) -> PrayResult<()> {
if path.is_absolute() {
return Err(PrayError::Integrity(format!(
"package path must be relative: {}",
path.display()
)));
}
for component in path.components() {
match component {
Component::Normal(_) | Component::CurDir => {}
_ => {
return Err(PrayError::Integrity(format!(
"package path escapes package root: {}",
path.display()
)));
}
}
}
Ok(())
}
pub fn sanitize_relative_path(path: &str) -> PrayResult<PathBuf> {
let path = path.trim_start_matches('/');
let mut relative = PathBuf::new();
for component in Path::new(path).components() {
match component {
Component::Normal(part) => relative.push(part),
Component::CurDir => {}
_ => {
return Err(PrayError::Resolution(format!(
"invalid relative path: {path}"
)));
}
}
}
if relative.as_os_str().is_empty() {
return Err(PrayError::Resolution(format!(
"invalid relative path: {path}"
)));
}
Ok(relative)
}
pub fn remove_path_if_exists(path: &Path) -> PrayResult<()> {
match fs::symlink_metadata(path) {
Ok(metadata) if metadata.is_dir() => {
fs::remove_dir_all(path)?;
Ok(())
}
Ok(_) => {
fs::remove_file(path)?;
Ok(())
}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(error) => Err(error.into()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanitize_relative_path_rejects_parent_dir() {
let error = sanitize_relative_path("../escape.praypkg").expect_err("parent dir");
assert!(error.to_string().contains("invalid relative path"));
}
#[test]
fn sanitize_relative_path_accepts_nested_artifact() {
let path = sanitize_relative_path("v1/artifacts/sample/base/1.0.0/package.praypkg")
.expect("nested path");
assert_eq!(
path,
PathBuf::from("v1/artifacts/sample/base/1.0.0/package.praypkg")
);
}
}