#[cfg(feature = "std")]
use std::path::{Path, PathBuf};
#[derive(Debug)]
#[cfg_attr(feature = "std", derive(thiserror::Error))]
#[non_exhaustive]
pub enum Error {
#[cfg_attr(feature = "std", error("no file info"))]
NoFileInfo,
#[cfg_attr(feature = "std", error("invalid path"))]
InvalidPath,
#[cfg_attr(feature = "std", error("duplicate file paths"))]
DuplicatePaths,
#[cfg_attr(feature = "std", error("invalid file info"))]
InvalidFileInfo,
#[cfg_attr(feature = "std", error("unknown meta version"))]
UnknownMetaversion,
#[cfg_attr(feature = "std", error("missing information"))]
MissingInfo,
}
#[cfg(feature = "std")]
impl From<Error> for std::io::Error {
fn from(error: Error) -> Self {
match error {
Error::NoFileInfo
| Error::InvalidPath
| Error::DuplicatePaths
| Error::InvalidFileInfo
| Error::UnknownMetaversion
| Error::MissingInfo => std::io::Error::new(std::io::ErrorKind::InvalidData, error),
}
}
}
#[cfg(feature = "std")]
fn validate_path<P: AsRef<Path>>(path: P) -> Result<(), Error> {
use std::path::Component;
let path = path.as_ref();
let mut is_not_empty = false;
for c in path.components() {
match c {
Component::Normal(_) => {
is_not_empty = true;
}
Component::ParentDir => {
return Err(Error::InvalidPath);
}
Component::CurDir | Component::RootDir | Component::Prefix(_) => {
return Err(Error::InvalidPath)
}
}
}
if is_not_empty {
Ok(())
} else {
Err(Error::InvalidPath)
}
}
#[cfg(feature = "std")]
fn check_info(info: &super::Info<'_>) -> Result<(), Error> {
use super::MetaVersion;
match info.meta_version() {
MetaVersion::V1 => {}
MetaVersion::V2 => {
todo!()
}
MetaVersion::Unknown(_) => return Err(Error::UnknownMetaversion),
}
let base_name = info.name;
let piece_len = u64::from(info.piece_length());
let pieces = info.pieces().ok_or(Error::MissingInfo)?;
if let Some(files) = &info.files {
if info.length.is_some() {
return Err(Error::InvalidFileInfo);
}
let mut paths = Vec::with_capacity(files.len());
for path in files.iter().map(super::File::to_path_buf) {
validate_path(&path)?;
paths.push(path);
}
let dir_path = PathBuf::from(base_name);
validate_path(dir_path)?;
paths.sort();
paths.dedup();
if paths.len() != files.len() {
return Err(Error::DuplicatePaths);
}
let total_len = files.iter().fold(0, |acc, file| acc + file.length());
let expected_num_of_pieces = total_len / piece_len + u64::from(total_len % piece_len != 0);
if expected_num_of_pieces != u64::try_from(pieces.len()).unwrap() {
return Err(Error::InvalidFileInfo);
}
Ok(())
} else if let Some(total_len) = info.length {
let path = PathBuf::from(base_name);
validate_path(path)?;
let expected_num_of_pieces = total_len / piece_len + u64::from(total_len % piece_len != 0);
if expected_num_of_pieces != u64::try_from(pieces.len()).unwrap() {
return Err(Error::InvalidFileInfo);
}
Ok(())
} else {
Err(Error::NoFileInfo)
}
}
#[cfg(feature = "std")]
pub fn check(metainfo: &super::Metainfo<'_>) -> Result<(), Error> {
check_info(&metainfo.info)
}