zip_format/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5
6macro_rules! gen {
7    {
8        $($n:ident $p:ident)*;
9        $(
10            $name:ident : $magic:literal{
11                $($field:ident:$t:ident,)*
12            }
13        )*
14    } => {
15        $(
16            #[derive(Copy, Clone, PartialEq, Eq)]
17            pub struct $n([u8; core::mem::size_of::<$p>()]);
18            impl $n {
19                pub fn get(&self) -> $p {
20                    $p::from_le_bytes(self.0)
21                }
22                #[allow(unused)]
23                const fn new(n: $p) -> Self {
24                    Self(n.to_le_bytes())
25                }
26            }
27            impl fmt::Debug for $n {
28                fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
29                    self.get().fmt(f)
30                }
31            }
32        )*
33        mod types {
34            pub use super::{CompressionMethod, $($n),*};
35        }
36        $(
37            #[repr(C)]
38            #[derive(Debug)]
39            pub struct $name {
40                $(pub $field: types::$t),*
41            }
42            impl $name {
43                pub fn as_prefix(bytes: &[u8]) -> Option<&Self> {
44                    (bytes.len() >= core::mem::size_of::<Self>() + 4)
45                        .then(|| unsafe { &*(bytes.as_ptr() as *const (U32, Self)) })
46                        .filter(|p| p.0.get() == $magic)
47                        .map(|p| &p.1)
48                }
49                pub fn as_suffix(bytes: &[u8]) -> Option<&Self> {
50                    bytes.get(bytes.len() - core::mem::size_of::<Self>() - 4..).and_then(Self::as_prefix)
51                }
52            }
53        )*
54    }
55}
56gen! {
57    U16 u16 U32 u32 U64 u64;
58    
59    Footer: 0x06054b50 {
60        disk_number: U16,
61        directory_start_disk: U16,
62        entries_on_this_disk: U16,
63        entries: U16,
64        directory_size: U32,
65        offset_from_start: U32,
66        comment_length: U16,
67    }
68    FooterLocator: 0x07064b50 {
69        directory_start_disk: U32,
70        footer_offset: U64,
71        disk_count: U32,
72    }
73    FooterV2: 0x06064b50 {
74        footer_size: U64,
75        made_by: U16,
76        required_version: U16,
77        disk_number: U32,
78        directory_start_disk: U32,
79        entries_on_this_disk: U64,
80        entries: U64,
81        directory_size: U64,
82        offset_from_start: U64,
83    }
84    DirectoryEntry: 0x02014b50 {
85        made_by: U16,
86        required_version: U16,
87        flags: U16,
88        method: CompressionMethod,
89        modified_time: U16,
90        modified_date: U16,
91        crc32: U32,
92        compressed_size: U32,
93        uncompressed_size: U32,
94        name_len: U16,
95        metadata_len: U16,
96        comment_len: U16,
97    
98        disk_number: U16,
99        internal_attrs: U16,
100        external_attrs: U32,
101        offset_from_start: U32,
102    }
103    Header: 0x04034b50 {
104        required_version: U16,
105        flags: U16,
106        method: CompressionMethod,
107        modified_time: U16,
108        modified_date: U16,
109        crc32: U32,
110        compressed_size: U32,
111        uncompressed_size: U32,
112        name_len: U16,
113        metadata_len: U16,
114    }
115}
116
117#[repr(transparent)]
118#[derive(Copy, Clone, PartialEq, Eq)]
119pub struct CompressionMethod(U16);
120impl CompressionMethod {
121    pub const STORED: Self = Self(U16::new(0));
122    pub const DEFLATE: Self = Self(U16::new(8));
123}
124
125impl fmt::Debug for CompressionMethod {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        write!(f, "CompressionMethod({})", self.0.get())
128    }
129}
130/// Each method will be converted to a description that fits in the sentence:
131/// `"this zip file was stored using {method}"`
132/// 
133/// I *think* you'd call it a noun phrase, though I've never been great at
134/// linguistics.
135impl fmt::Display for CompressionMethod {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        f.write_str(match self.0.get() {
138            0 => "no compression",
139            
140            1 => "a deprecated method (shrunk)",
141            2 => "a deprecated method (reduced with factor 1)",
142            3 => "a deprecated method (reduced with factor 2)",
143            4 => "a deprecated method (reduced with factor 3)",
144            5 => "a deprecated method (reduced with factor 4)",
145            6 => "a deprecated method (imploded)",
146
147            7  => "a reserved method (PKWARE tokenizing algorithm)",
148            11 => "a reserved method (11)",
149            13 => "a reserved method (13)",
150            15 => "a reserved method (15)",
151            17 => "a reserved method (17)",
152
153            8  => "deflate compression",
154            9  => "deflate64 compression",
155            10 => "PKWARE TERSE",
156            12 => "BZIP2 compression",
157            14 => "LZMA compression",
158            16 => "IBM z/OS CMPSC compression",
159            18 => "IBM TERSE",
160            19 => "LZ77 compression",
161
162            20 => "a deprecated method (zstd)",
163
164            93 => "zstd compression",
165            94 => "MP3 encoding",
166            95 => "XZ compression",
167            96 => "JPEG encoding",
168            97 => "WavPack compression",
169            98 => "PPMd compression",
170
171            99 => "AE-x encryption",
172
173            v => return write!(f, "an unknown method ({v})"),
174        })
175    }
176}