zip-core 0.0.4

zip implementation independent structs and helpers
Documentation
//! `zip-core::structs` contains common zip records/structs enum, according to the standart file <https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT>
//!
//! They are optimized for Rust usage, e.g. `enums` with valid Compression
//! Methods or structs that don't contain "length"-fields, as that can be
//! determined via Vec::len()

pub struct VersionMadeBy {
    pub compatibility_information: CompatibleFileAttributeInformation,
    pub version: ZipVersion,
}

/// Part of version made by
///
/// see [4.4.2.2](https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT)
#[derive(Debug, Clone, PartialEq)]
pub enum CompatibleFileAttributeInformation {
    /// MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)
    MsDosAndOs2,
    Amiga,
    OpenVms,
    Unix,
    VmCms,
    AtariSt,
    Os2,
    Macintosh,
    ZSystem,
    CpM,
    WindowsNtfs,
    Mvs,
    Vse,
    AcornRisc,
    VFat,
    AlternateMvs,
    BeOs,
    Tandem,
    Os400,
    OsX,
}

impl TryFrom<u8> for CompatibleFileAttributeInformation {
    type Error = u8;

    /// in case of an unknown number, we return the number itself
    /// Note: an unknown number might be a incompatiblity in the zip or just
    /// that this crate hasn't been updated to the latest APPNOTE.TXT
    fn try_from(value: u8) -> Result<Self, Self::Error> {
        use CompatibleFileAttributeInformation::*;
        match value {
            0 => Ok(MsDosAndOs2),
            1 => Ok(Amiga),
            2 => Ok(OpenVms),
            3 => Ok(Unix),
            4 => Ok(VmCms),
            5 => Ok(AtariSt),
            6 => Ok(Os2),
            7 => Ok(Macintosh),
            8 => Ok(ZSystem),
            9 => Ok(CpM),
            10 => Ok(WindowsNtfs),
            11 => Ok(Mvs),
            12 => Ok(Vse),
            13 => Ok(AcornRisc),
            14 => Ok(VFat),
            15 => Ok(AlternateMvs),
            16 => Ok(BeOs),
            17 => Ok(Tandem),
            18 => Ok(Os400),
            19 => Ok(OsX),
            other => Err(other),
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct ZipVersion {
    pub major_version: u8,
    pub minor_version: u8,
}

/// stored in u8 in 4.4.2.2
impl From<u8> for ZipVersion {
    fn from(value: u8) -> Self {
        let major_version = value / 10;
        let minor_version = value.rem_euclid(10);
        Self {
            major_version,
            minor_version,
        }
    }
}

/// stored in u18 in 4.4.3.2
impl From<u16> for ZipVersion {
    fn from(value: u16) -> Self {
        let version = value.to_le_bytes();
        Self {
            major_version: version[0],
            minor_version: version[1],
        }
    }
}

impl ZipVersion {
    pub fn new(major_version: u8, minor_version: u8) -> Self {
        Self {
            major_version,
            minor_version,
        }
    }
}

/// see [4.4.3.2](https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT)
#[derive(Debug, Clone, PartialEq)]
pub enum MinimumFeatureVersion {
    Default,
    VolumeLabel,
    Folder,
    CompressedUsingDeflate,
    EncryptedUsingPkWare,
    CompressedUsingDeflate64,
    CompressedUsingPkwareDclImplode,
    PatchDataSet,
    UsesZip64FormatExtentions,
    CompressedUsingBzip2,
    EncryptedUsingDes,
    EncryptedUsing3Des,
    EncryptedUsingOriginalRc2,
    EncryptedUsingRc4,
    EncryptedUsingAes,
    EncryptedUsingCorrectedRc2,
    EncryptedUsingCorrectedRc2_64,
    EncryptedUsingNonOaepKeyWrapping,
    CentralDirectoryEncryption,
    CompressedUsingLzma,
    CompressedUsingPpmd,
    EncryptedUsingBlowfish,
    EncryptedUsingTwofish,
}

impl From<MinimumFeatureVersion> for ZipVersion {
    fn from(value: MinimumFeatureVersion) -> Self {
        use MinimumFeatureVersion::*;
        match value {
            Default => ZipVersion::new(1, 0),
            VolumeLabel => ZipVersion::new(1, 1),
            Folder => ZipVersion::new(2, 0),
            CompressedUsingDeflate => ZipVersion::new(2, 0),
            EncryptedUsingPkWare => ZipVersion::new(2, 0),
            CompressedUsingDeflate64 => ZipVersion::new(2, 1),
            CompressedUsingPkwareDclImplode => ZipVersion::new(2, 5),
            PatchDataSet => ZipVersion::new(2, 7),
            UsesZip64FormatExtentions => ZipVersion::new(4, 5),
            CompressedUsingBzip2 => ZipVersion::new(4, 7),
            EncryptedUsingDes => ZipVersion::new(5, 0),
            EncryptedUsing3Des => ZipVersion::new(5, 0),
            EncryptedUsingOriginalRc2 => ZipVersion::new(5, 0),
            EncryptedUsingRc4 => ZipVersion::new(5, 0),
            EncryptedUsingAes => ZipVersion::new(5, 1),
            EncryptedUsingCorrectedRc2 => ZipVersion::new(5, 1),
            EncryptedUsingCorrectedRc2_64 => ZipVersion::new(5, 2),
            EncryptedUsingNonOaepKeyWrapping => ZipVersion::new(6, 1),
            CentralDirectoryEncryption => ZipVersion::new(6, 2),
            CompressedUsingLzma => ZipVersion::new(6, 3),
            CompressedUsingPpmd => ZipVersion::new(6, 3),
            EncryptedUsingBlowfish => ZipVersion::new(6, 3),
            EncryptedUsingTwofish => ZipVersion::new(6, 3),
        }
    }
}

/// see [4.4.5](https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT)
#[derive(Debug, Clone, PartialEq)]
pub enum CompressionMethod {
    Stored,
    Shrunk,
    ReducedWithFactor1,
    ReducedWithFactor2,
    ReducedWithFactor3,
    ReducedWithFactor4,
    Imploded,
    Deflated,
    EnhancedDeflatingUsingDeflate64,
    PkWareDataCompressionLIbraryImploding,
    Bzip2,
    Lzma,
    IbmZOsCmpsc,
    /// Ibm Terse new, for old see PkWareDataCompressionLIbraryImploding
    IbmTerse,
    IbmLz77z,
    Zstdandart,
    Mp3,
    Xz,
    Jpeg,
    WavPack,
    PpmdV1R1,
    Aex,
}

impl CompressionMethod {
    pub const fn is_deprecated(&self) -> bool {
        use CompressionMethod::*;
        matches!(
            self,
            Shrunk | ReducedWithFactor1 | ReducedWithFactor2 | ReducedWithFactor3 | ReducedWithFactor4 | Imploded
        )
    }
}

impl TryFrom<u16> for CompressionMethod {
    type Error = u16;

    /// in case of an unknown number, we return the number itself
    /// Note: an unknown number might be a incompatiblity in the zip or just
    /// that this crate hasn't been updated to the latest APPNOTE.TXT
    fn try_from(value: u16) -> Result<Self, Self::Error> {
        use CompressionMethod::*;
        match value {
            0 => Ok(Stored),
            1 => Ok(Shrunk),
            2 => Ok(ReducedWithFactor1),
            3 => Ok(ReducedWithFactor2),
            4 => Ok(ReducedWithFactor3),
            5 => Ok(ReducedWithFactor4),
            6 => Ok(Imploded),
            8 => Ok(Deflated),
            9 => Ok(EnhancedDeflatingUsingDeflate64),
            10 => Ok(PkWareDataCompressionLIbraryImploding),
            12 => Ok(Bzip2),
            14 => Ok(Lzma),
            16 => Ok(IbmZOsCmpsc),
            18 => Ok(IbmTerse),
            19 => Ok(IbmLz77z),
            93 => Ok(Zstdandart),
            94 => Ok(Mp3),
            95 => Ok(Xz),
            96 => Ok(Jpeg),
            97 => Ok(WavPack),
            98 => Ok(PpmdV1R1),
            99 => Ok(Aex),
            other => Err(other),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn order_zip_version() {
        let v1_0 = ZipVersion::new(1, 0);
        let v1_1 = ZipVersion::new(1, 1);
        let v2_0 = ZipVersion::new(2, 0);
        let v2_1 = ZipVersion::new(2, 1);
        assert_eq!(v1_0, v1_0);
        assert_ne!(v1_0, v1_1);
        assert_ne!(v1_1, v2_1);

        assert!(v1_1 > v1_0);
        assert!(v2_0 > v1_0);
        assert!(v2_0 > v1_1);
        assert!(v2_1 > v2_0);
        assert!(v2_0 <= v2_0);
        assert!(v2_0 < v2_1);
        assert!(v1_1 < v2_1);
        assert!(v1_0 < v2_1);
        assert_eq!(v1_0.max(v1_1), v1_1);
    }

    #[test]
    fn parse_zip_version() {
        assert_eq!(ZipVersion::from(30u8), ZipVersion::new(3, 0));
        assert_eq!(ZipVersion::from(19u8), ZipVersion::new(1, 9));
        assert_eq!(ZipVersion::from(123u8), ZipVersion::new(12, 3));
        assert_eq!(ZipVersion::from(1u16), ZipVersion::new(1, 0));
        assert_eq!(ZipVersion::from(258u16), ZipVersion::new(2, 1));
        assert_eq!(ZipVersion::from(259u16), ZipVersion::new(3, 1));
    }

    #[test]
    fn parse_compatible_file_attribute_infromation() {
        use CompatibleFileAttributeInformation::*;
        assert_eq!(CompatibleFileAttributeInformation::try_from(0).unwrap(), MsDosAndOs2);
        assert_eq!(CompatibleFileAttributeInformation::try_from(19).unwrap(), OsX);
        assert_eq!(CompatibleFileAttributeInformation::try_from(123), Err(123));

        // just check it doesn't panic
        assert!(CompatibleFileAttributeInformation::try_from(1).is_ok());
        assert!(CompatibleFileAttributeInformation::try_from(2).is_ok());
        assert!(CompatibleFileAttributeInformation::try_from(3).is_ok());
        assert!(CompatibleFileAttributeInformation::try_from(4).is_ok());
        assert!(CompatibleFileAttributeInformation::try_from(5).is_ok());
        assert!(CompatibleFileAttributeInformation::try_from(6).is_ok());
        assert!(CompatibleFileAttributeInformation::try_from(7).is_ok());
        assert!(CompatibleFileAttributeInformation::try_from(8).is_ok());
        assert!(CompatibleFileAttributeInformation::try_from(9).is_ok());
        assert!(CompatibleFileAttributeInformation::try_from(10).is_ok());
        assert!(CompatibleFileAttributeInformation::try_from(11).is_ok());
        assert!(CompatibleFileAttributeInformation::try_from(12).is_ok());
        assert!(CompatibleFileAttributeInformation::try_from(13).is_ok());
        assert!(CompatibleFileAttributeInformation::try_from(14).is_ok());
        assert!(CompatibleFileAttributeInformation::try_from(15).is_ok());
        assert!(CompatibleFileAttributeInformation::try_from(16).is_ok());
        assert!(CompatibleFileAttributeInformation::try_from(17).is_ok());
        assert!(CompatibleFileAttributeInformation::try_from(18).is_ok());
        assert!(CompatibleFileAttributeInformation::try_from(20).is_err());
        assert!(CompatibleFileAttributeInformation::try_from(21).is_err());
        assert!(CompatibleFileAttributeInformation::try_from(22).is_err());
        assert!(CompatibleFileAttributeInformation::try_from(23).is_err());
        assert!(CompatibleFileAttributeInformation::try_from(55).is_err());
        assert!(CompatibleFileAttributeInformation::try_from(200).is_err());
        assert!(CompatibleFileAttributeInformation::try_from(255).is_err());
    }

    #[test]
    fn parse_minimum_feature_version() {
        use MinimumFeatureVersion::*;
        assert_eq!(ZipVersion::from(Default), ZipVersion::new(1, 0));
        assert_eq!(ZipVersion::from(CompressedUsingDeflate64), ZipVersion::new(2, 1));
        assert_eq!(ZipVersion::from(UsesZip64FormatExtentions), ZipVersion::new(4, 5));
        assert_eq!(ZipVersion::from(EncryptedUsingDes), ZipVersion::new(5, 0));
        assert_eq!(ZipVersion::from(CentralDirectoryEncryption), ZipVersion::new(6, 2));
        assert_eq!(ZipVersion::from(CompressedUsingLzma), ZipVersion::new(6, 3));
    }

    #[test]
    fn parse_compression_method() {
        use CompressionMethod::*;
        assert_eq!(CompressionMethod::try_from(0).unwrap(), Stored);
        assert_eq!(CompressionMethod::try_from(9).unwrap(), EnhancedDeflatingUsingDeflate64);
        assert_eq!(CompressionMethod::try_from(15), Err(15));
        assert_eq!(CompressionMethod::try_from(93).unwrap(), Zstdandart);
        assert_eq!(CompressionMethod::try_from(200), Err(200));

        // just check it doesn't panic
        assert!(CompressionMethod::try_from(1).is_ok());
        assert!(CompressionMethod::try_from(2).is_ok());
        assert!(CompressionMethod::try_from(3).is_ok());
        assert!(CompressionMethod::try_from(4).is_ok());
        assert!(CompressionMethod::try_from(5).is_ok());
        assert!(CompressionMethod::try_from(6).is_ok());
        assert!(CompressionMethod::try_from(7).is_err());
        assert!(CompressionMethod::try_from(8).is_ok());
        assert!(CompressionMethod::try_from(9).is_ok());
        assert!(CompressionMethod::try_from(10).is_ok());
        assert!(CompressionMethod::try_from(11).is_err());
        assert!(CompressionMethod::try_from(12).is_ok());
        assert!(CompressionMethod::try_from(13).is_err());
        assert!(CompressionMethod::try_from(14).is_ok());
        assert!(CompressionMethod::try_from(16).is_ok());
        assert!(CompressionMethod::try_from(17).is_err());
        assert!(CompressionMethod::try_from(18).is_ok());
        assert!(CompressionMethod::try_from(19).is_ok());
        assert!(CompressionMethod::try_from(20).is_err());
        assert!(CompressionMethod::try_from(21).is_err());
        assert!(CompressionMethod::try_from(22).is_err());
        assert!(CompressionMethod::try_from(92).is_err());
        assert!(CompressionMethod::try_from(93).is_ok());
        assert!(CompressionMethod::try_from(94).is_ok());
        assert!(CompressionMethod::try_from(95).is_ok());
        assert!(CompressionMethod::try_from(96).is_ok());
        assert!(CompressionMethod::try_from(97).is_ok());
        assert!(CompressionMethod::try_from(98).is_ok());
        assert!(CompressionMethod::try_from(99).is_ok());
        assert!(CompressionMethod::try_from(100).is_err());
        assert!(CompressionMethod::try_from(200).is_err());
        assert!(CompressionMethod::try_from(255).is_err());

        assert!(!Stored.is_deprecated());
        assert!(ReducedWithFactor3.is_deprecated());
    }
}