sit-rs 0.3.0

Rust-native extraction for StuffIt Expander archive files
Documentation
use crate::structs::{Algorithm, Version};
use fourcc::FourCC;
use std::{fmt, io};

#[derive(Debug)]
/// Reasons why files can be ruled out as valid StuffIt archives
pub enum InvalidFileReason {
    InvalidHeader,
    CreatorCode(FourCC),
    FileMagic(FourCC),
}

impl fmt::Display for InvalidFileReason {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            InvalidFileReason::CreatorCode(code) => f.write_fmt(format_args!(
                "Creator code is '{}' instead of 'rLau'",
                code.name()
            )),
            InvalidFileReason::FileMagic(code) => f.write_fmt(format_args!(
                "File code is '{}' but should be one of 'SIT!', 'SITD', 'SIT2' or 'SIT5'",
                code.name()
            )),
            InvalidFileReason::InvalidHeader => {
                f.write_str("Could not find valid archive header at the given position")
            }
        }
    }
}

#[derive(Debug)]
/// Archive features that are known to exist but are not implemented yet
pub enum UnsupportedFeature {
    Algorithm(Algorithm),
    Version(Version),
    Encryption,
}

impl fmt::Display for UnsupportedFeature {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            UnsupportedFeature::Algorithm(algo) => {
                f.write_fmt(format_args!("Algorithm {algo} is not implemented yet"))
            }
            UnsupportedFeature::Version(version) => {
                f.write_fmt(format_args!("The version {version} is not implemented yet"))
            }
            UnsupportedFeature::Encryption => f.write_str("Encryption is not implemented yet"),
        }
    }
}

impl From<Algorithm> for UnsupportedFeature {
    fn from(val: Algorithm) -> Self {
        UnsupportedFeature::Algorithm(val)
    }
}

impl From<Version> for UnsupportedFeature {
    fn from(val: Version) -> Self {
        UnsupportedFeature::Version(val)
    }
}

#[derive(Debug)]
pub enum ChecksumLocation {
    /// Checksum for the archive header is invalid
    ArchiveHeader,
    /// Checksum for an entry header does not match
    EntryHeader,
    /// Checksum for an entry's data is corrupt
    DataFork,
    /// Checksum for an entry's resource fork is corrupt
    ResourceFork,
    DataStream,
}

impl fmt::Display for ChecksumLocation {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ChecksumLocation::ArchiveHeader => f.write_str("archive header"),
            ChecksumLocation::EntryHeader => f.write_str("entry header"),
            ChecksumLocation::DataFork => f.write_str("data fork"),
            ChecksumLocation::ResourceFork => f.write_str("resource fork"),
            ChecksumLocation::DataStream => f.write_str("data stream"),
        }
    }
}

#[derive(Debug, thiserror::Error)]
/// Common error for working with the sit crate
pub enum Error {
    #[error(transparent)]
    Io(io::Error),

    #[error(transparent)]
    BinRW(#[from] binrw::Error),

    #[error("The file is not a Stuffit archive ({0})")]
    InvalidFile(InvalidFileReason),

    #[error("The entry uses unsupported features ({0})")]
    UnsupportedFeature(UnsupportedFeature),

    #[error("One or more checksums indicate archive corruption")]
    ChecksumMismatch(ChecksumLocation),

    #[error("The compressed data stream could not read")]
    InvalidDataStream,
}

impl From<Error> for binrw::Error {
    fn from(val: Error) -> Self {
        match val {
            Error::Io(error) => binrw::Error::Io(error),
            Error::BinRW(error) => error,
            err => binrw::Error::Custom {
                pos: 0,
                err: Box::new(err),
            },
        }
    }
}

impl From<Error> for io::Error {
    fn from(val: Error) -> Self {
        match val {
            Error::Io(error) => error,
            Error::BinRW(error) => io::Error::other(Box::new(error)),
            err => io::Error::other(Box::new(err)),
        }
    }
}

impl From<sit_algos::huffman::Error> for Error {
    fn from(_: sit_algos::huffman::Error) -> Self {
        Self::InvalidDataStream
    }
}

impl From<sit_algos::huffman_fixed::Error> for Error {
    fn from(_: sit_algos::huffman_fixed::Error) -> Self {
        Self::InvalidDataStream
    }
}

impl From<sit_algos::arsenic::Error> for Error {
    fn from(_: sit_algos::arsenic::Error) -> Self {
        Self::InvalidDataStream
    }
}

impl From<io::Error> for Error {
    fn from(e: io::Error) -> Self {
        match e {
            e if e.kind() == io::ErrorKind::Other => match e.downcast() {
                Ok(e) => e,
                Err(e) => Error::Io(e),
            },
            e => Error::Io(e),
        }
    }
}

#[derive(thiserror::Error, Debug)]
/// Error when trying to extract a specifc item form the archive
pub enum ExtractionError {
    #[error("The requested entry does not exist in the sit archive")]
    ItemNotFound,
    #[error(transparent)]
    Error(#[from] Error),
}

impl From<io::Error> for ExtractionError {
    fn from(value: io::Error) -> Self {
        Self::Error(value.into())
    }
}

impl From<ExtractionError> for io::Error {
    fn from(value: ExtractionError) -> Self {
        match value {
            ExtractionError::ItemNotFound => {
                io::Error::other("Requested file was not found in the archive")
            }
            other => other.into(),
        }
    }
}