use std::{
fmt::Debug,
fs::File,
path::{Path, PathBuf},
};
use crate::{
Error,
identifiers::{Context, Os, Purpose, SegmentPath, Technology},
load_path::LoadPath,
util::symlinks::{PathType, ResolvedSymlink, resolve_symlink},
};
#[derive(Clone, Debug)]
pub struct Verifier {
voa_location: VoaLocation,
canonicalized: PathBuf,
}
impl Verifier {
pub(crate) fn new(voa_location: VoaLocation, canonicalized: PathBuf) -> Self {
Self {
voa_location,
canonicalized,
}
}
pub fn voa_location(&self) -> &VoaLocation {
&self.voa_location
}
pub fn canonicalized(&self) -> &Path {
&self.canonicalized
}
pub(crate) fn filename(&self) -> Option<&std::ffi::OsStr> {
self.canonicalized.file_name()
}
pub fn open(&self) -> Result<File, Error> {
File::open(&self.canonicalized).map_err(|source| Error::IoPath {
path: self.canonicalized.clone(),
context: "opening the file for reading",
source,
})
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct VoaLocation {
load_path: LoadPath,
os: Os,
purpose: Purpose,
context: Context,
technology: Technology,
}
impl VoaLocation {
pub(crate) fn new(
load_path: LoadPath,
os: Os,
purpose: Purpose,
context: Context,
technology: Technology,
) -> Self {
Self {
load_path,
os,
purpose,
context,
technology,
}
}
pub fn load_path(&self) -> &LoadPath {
&self.load_path
}
pub fn os(&self) -> &Os {
&self.os
}
pub fn purpose(&self) -> &Purpose {
&self.purpose
}
pub fn context(&self) -> &Context {
&self.context
}
pub fn technology(&self) -> &Technology {
&self.technology
}
pub(crate) fn check_and_canonicalize(
&self,
legal_symlink_paths: &[&LoadPath],
) -> Result<PathBuf, Error> {
let base_path = self
.load_path()
.path()
.canonicalize()
.map_err(|source| Error::IoPath {
path: self.load_path.path.clone(),
context: "canonicalizing",
source,
})?;
let mut path = Self::append(&base_path, &self.os().path_segment()?, legal_symlink_paths)?;
path = Self::append(&path, &self.purpose().path_segment()?, legal_symlink_paths)?;
path = Self::append(&path, &self.context().path_segment()?, legal_symlink_paths)?;
path = Self::append(
&path,
&self.technology().path_segment()?,
legal_symlink_paths,
)?;
Ok(path)
}
fn append(
current_path: &Path,
segment: &SegmentPath,
legal_symlink_paths: &[&LoadPath],
) -> Result<PathBuf, Error> {
let mut buf = current_path.join(segment);
if buf.is_symlink() {
buf = match resolve_symlink(&buf, legal_symlink_paths, PathType::Dir)? {
ResolvedSymlink::Dir(dir) => dir,
ResolvedSymlink::File(path) => {
return Err(Error::IllegalSymlink {
path,
context: "Unexpected file",
});
}
ResolvedSymlink::Masked => {
return Err(Error::IllegalSymlink {
path: buf,
context: "Illegal masking symlink from directory",
});
}
};
}
if buf.is_dir() {
Ok(buf)
} else {
Err(Error::ExpectedDirectory { path: buf })
}
}
}