use crate::ArchiveError;
use crate::Result;
use crate::formats::common;
use crate::inspection::manifest::ManifestEntryType;
use crate::security::validator::NodeKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SevenZClass {
AntiItem,
Present(SevenZKind),
Unsupported(UnsupportedEntry),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SevenZKind {
File,
Directory,
ReparsePoint,
UnixSymlink,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NativeKind {
File,
Directory,
}
impl NativeKind {
const fn noun(self) -> &'static str {
match self {
Self::File => "file",
Self::Directory => "directory",
}
}
const fn opposite(self) -> &'static str {
match self {
Self::File => "directory",
Self::Directory => "regular file",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KindSource {
UnixMode,
WindowsAttribute,
}
impl KindSource {
const fn noun(self) -> &'static str {
match self {
Self::UnixMode => "Unix mode",
Self::WindowsAttribute => "Windows attributes",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnsupportedEntry {
Special(common::SpecialFile),
UndefinedType(u8),
KindContradiction {
native: NativeKind,
claimed_by: KindSource,
},
}
impl UnsupportedEntry {
fn refusal(self, name: &str) -> ArchiveError {
let reason = match self {
Self::Special(special) => {
format!("{} entries not supported: {name}", special.noun())
}
Self::UndefinedType(bits) => {
format!("7z entry has an undefined Unix file type ({bits:#x}): {name}")
}
Self::KindContradiction { native, claimed_by } => format!(
"7z entry is a {} but its {} says {}: {name}",
native.noun(),
claimed_by.noun(),
native.opposite()
),
};
ArchiveError::violation(reason)
}
}
impl SevenZClass {
pub(crate) fn of(entry: &sevenz_rust2::ArchiveEntry) -> Self {
use common::UnixFileType;
if entry.is_anti_item {
return Self::AntiItem;
}
if is_windows_reparse_point(entry) {
return Self::Present(SevenZKind::ReparsePoint);
}
let mode_type = unix_file_type(entry);
if mode_type == Some(UnixFileType::Symlink) {
return Self::Present(SevenZKind::UnixSymlink);
}
let native = if entry.is_directory() {
NativeKind::Directory
} else {
NativeKind::File
};
let contradiction = |claimed_by| {
Self::Unsupported(UnsupportedEntry::KindContradiction { native, claimed_by })
};
match (mode_type, native) {
(Some(UnixFileType::Special(special)), _) => {
return Self::Unsupported(UnsupportedEntry::Special(special));
}
(Some(UnixFileType::Undefined(bits)), _) => {
return Self::Unsupported(UnsupportedEntry::UndefinedType(bits));
}
(Some(UnixFileType::Regular), NativeKind::Directory)
| (Some(UnixFileType::Directory), NativeKind::File) => {
return contradiction(KindSource::UnixMode);
}
_ => {}
}
if native == NativeKind::File && claims_windows_directory(entry) {
return contradiction(KindSource::WindowsAttribute);
}
Self::Present(match native {
NativeKind::Directory => SevenZKind::Directory,
NativeKind::File => SevenZKind::File,
})
}
pub(crate) fn present(self, name: &str) -> Result<Option<SevenZKind>> {
match self {
Self::AntiItem => Ok(None),
Self::Present(kind) => Ok(Some(kind)),
Self::Unsupported(unsupported) => Err(unsupported.refusal(name)),
}
}
}
impl SevenZKind {
pub(crate) const fn node_kind(self) -> NodeKind {
match self {
Self::Directory => NodeKind::Directory,
Self::File | Self::ReparsePoint | Self::UnixSymlink => NodeKind::NonDirectory,
}
}
pub(crate) const fn manifest_type(self) -> ManifestEntryType {
match self {
Self::File => ManifestEntryType::File,
Self::Directory => ManifestEntryType::Directory,
Self::ReparsePoint | Self::UnixSymlink => ManifestEntryType::Symlink,
}
}
}
pub(super) fn reparse_point_refusal(name: &str) -> ArchiveError {
ArchiveError::violation(format!(
"symlink detected in 7z archive: {name} (Windows reparse point attribute set). \
Its target cannot be read due to sevenz-rust2 API limitations."
))
}
pub(super) fn abort_with(
pending_error: &mut Option<ArchiveError>,
error: ArchiveError,
) -> sevenz_rust2::Error {
*pending_error = Some(error);
sevenz_rust2::Error::Other("extraction aborted by security policy".into())
}
pub(super) fn is_windows_reparse_point(entry: &sevenz_rust2::ArchiveEntry) -> bool {
const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x0000_0400;
entry.has_windows_attributes && (entry.windows_attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0
}
pub(super) fn unix_file_type(entry: &sevenz_rust2::ArchiveEntry) -> Option<common::UnixFileType> {
const UNIX_EXTENSION: u32 = 0x8000;
(entry.has_windows_attributes && entry.windows_attributes & UNIX_EXTENSION != 0)
.then(|| common::UnixFileType::from_mode(entry.windows_attributes >> 16))
.flatten()
}
pub(super) fn claims_windows_directory(entry: &sevenz_rust2::ArchiveEntry) -> bool {
const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x0000_0010;
entry.has_windows_attributes && entry.windows_attributes & FILE_ATTRIBUTE_DIRECTORY != 0
}