rc_zip/parse/
local_headers.rs

1use std::borrow::Cow;
2
3use crate::{
4    encoding::{detect_utf8, Encoding},
5    error::{Error, FormatError, UnsupportedError},
6    parse::{Method, MsdosTimestamp, Version},
7};
8
9use ownable::{IntoOwned, ToOwned};
10use tracing::trace;
11use winnow::{
12    binary::{le_u16, le_u32, le_u64, le_u8},
13    combinator::opt,
14    error::{ContextError, ErrMode, ErrorKind, FromExternalError},
15    seq,
16    token::{literal, take},
17    PResult, Parser, Partial,
18};
19
20use super::{zero_datetime, Entry, ExtraField, ExtraFieldSettings, Mode};
21
22#[derive(Debug, ToOwned, IntoOwned)]
23/// 4.3.7 Local file header
24pub struct LocalFileHeader<'a> {
25    /// version needed to extract
26    pub reader_version: Version,
27
28    /// general purpose bit flag
29    pub flags: u16,
30
31    /// compression method
32    pub method: Method,
33
34    /// last mod file datetime
35    pub modified: MsdosTimestamp,
36
37    /// crc-32
38    pub crc32: u32,
39
40    /// compressed size
41    pub compressed_size: u32,
42
43    /// uncompressed size
44    pub uncompressed_size: u32,
45
46    /// file name
47    pub name: Cow<'a, [u8]>,
48
49    /// extra field
50    pub extra: Cow<'a, [u8]>,
51
52    /// method-specific fields
53    pub method_specific: MethodSpecific,
54}
55
56#[derive(Debug, ToOwned, IntoOwned)]
57/// Method-specific properties following the local file header
58pub enum MethodSpecific {
59    /// No method-specific properties
60    None,
61
62    /// LZMA properties
63    Lzma(LzmaProperties),
64}
65
66impl<'a> LocalFileHeader<'a> {
67    /// The signature for a local file header
68    pub const SIGNATURE: &'static str = "PK\x03\x04";
69
70    /// Parser for the local file header
71    pub fn parser(i: &mut Partial<&'a [u8]>) -> PResult<Self> {
72        let _ = literal(Self::SIGNATURE).parse_next(i)?;
73
74        let reader_version = Version::parser.parse_next(i)?;
75        let flags = le_u16.parse_next(i)?;
76        let method = le_u16.parse_next(i).map(Method::from)?;
77        let modified = MsdosTimestamp::parser.parse_next(i)?;
78        let crc32 = le_u32.parse_next(i)?;
79        let compressed_size = le_u32.parse_next(i)?;
80        let uncompressed_size = le_u32.parse_next(i)?;
81
82        let name_len = le_u16.parse_next(i)?;
83        let extra_len = le_u16.parse_next(i)?;
84
85        let name = take(name_len).parse_next(i).map(Cow::Borrowed)?;
86        let extra = take(extra_len).parse_next(i).map(Cow::Borrowed)?;
87
88        let method_specific = match method {
89            Method::Lzma => {
90                let lzma_properties = LzmaProperties::parser.parse_next(i)?;
91                if let Err(e) = lzma_properties.error_if_unsupported() {
92                    return Err(ErrMode::Cut(ContextError::from_external_error(
93                        i,
94                        ErrorKind::Verify,
95                        e,
96                    )));
97                }
98                MethodSpecific::Lzma(lzma_properties)
99            }
100            _ => MethodSpecific::None,
101        };
102
103        Ok(Self {
104            reader_version,
105            flags,
106            method,
107            modified,
108            crc32,
109            compressed_size,
110            uncompressed_size,
111            name,
112            extra,
113            method_specific,
114        })
115    }
116
117    /// Check for the presence of the bit flag that indicates a data descriptor
118    /// is present after the file data.
119    pub fn has_data_descriptor(&self) -> bool {
120        // 4.3.9.1 This descriptor MUST exist if bit 3 of the general
121        // purpose bit flag is set (see below).
122        self.flags & 0b1000 != 0
123    }
124
125    /// Converts the local file header into an entry.
126    pub fn as_entry(&self) -> Result<Entry, Error> {
127        // see APPNOTE 4.4.4: Bit 11 is the language encoding flag (EFS)
128        let has_utf8_flag = self.flags & 0x800 == 0;
129        let encoding = if has_utf8_flag && detect_utf8(&self.name[..]).0 {
130            Encoding::Utf8
131        } else {
132            Encoding::Cp437
133        };
134        let name = encoding.decode(&self.name[..])?;
135
136        let mut entry = Entry {
137            name,
138            method: self.method,
139            comment: Default::default(),
140            modified: self.modified.to_datetime().unwrap_or_else(zero_datetime),
141            created: None,
142            accessed: None,
143            header_offset: 0,
144            reader_version: self.reader_version,
145            flags: self.flags,
146            uid: None,
147            gid: None,
148            crc32: self.crc32,
149            compressed_size: self.compressed_size as _,
150            uncompressed_size: self.uncompressed_size as _,
151            mode: Mode(0),
152        };
153
154        if entry.name.ends_with('/') {
155            // believe it or not, this is straight from the APPNOTE
156            entry.mode |= Mode::DIR
157        };
158
159        let mut slice = Partial::new(&self.extra[..]);
160        let settings = ExtraFieldSettings {
161            compressed_size_u32: self.compressed_size,
162            uncompressed_size_u32: self.uncompressed_size,
163            header_offset_u32: 0,
164        };
165
166        while !slice.is_empty() {
167            match ExtraField::mk_parser(settings).parse_next(&mut slice) {
168                Ok(ef) => {
169                    entry.set_extra_field(&ef);
170                }
171                Err(e) => {
172                    trace!("extra field error: {:#?}", e);
173                    return Err(FormatError::InvalidExtraField.into());
174                }
175            }
176        }
177
178        Ok(entry)
179    }
180}
181
182/// 4.3.9  Data descriptor:
183#[derive(Debug)]
184pub struct DataDescriptorRecord {
185    /// CRC32 checksum
186    pub crc32: u32,
187    /// Compressed size
188    pub compressed_size: u64,
189    /// Uncompressed size
190    pub uncompressed_size: u64,
191}
192
193impl DataDescriptorRecord {
194    const SIGNATURE: &'static str = "PK\x07\x08";
195
196    /// Create a parser for the data descriptor record.
197    pub fn mk_parser(is_zip64: bool) -> impl FnMut(&mut Partial<&'_ [u8]>) -> PResult<Self> {
198        move |i| {
199            // From appnote.txt:
200            //
201            // 4.3.9.3 Although not originally assigned a signature, the value
202            // 0x08074b50 has commonly been adopted as a signature value for the
203            // data descriptor record.  Implementers SHOULD be aware that ZIP files
204            // MAY be encountered with or without this signature marking data
205            // descriptors and SHOULD account for either case when reading ZIP files
206            // to ensure compatibility.
207            let _ = opt(literal(Self::SIGNATURE)).parse_next(i)?;
208
209            if is_zip64 {
210                seq! {Self {
211                    crc32: le_u32,
212                    compressed_size: le_u64,
213                    uncompressed_size: le_u64,
214                }}
215                .parse_next(i)
216            } else {
217                seq! {Self {
218                    crc32: le_u32,
219                    compressed_size: le_u32.map(|x| x as u64),
220                    uncompressed_size: le_u32.map(|x| x as u64),
221                }}
222                .parse_next(i)
223            }
224        }
225    }
226}
227
228/// 5.8.5 LZMA Properties header
229#[derive(Debug, ToOwned, IntoOwned)]
230pub struct LzmaProperties {
231    /// major version
232    pub major: u8,
233    /// minor version
234    pub minor: u8,
235    /// properties size
236    pub properties_size: u16,
237}
238
239impl LzmaProperties {
240    /// Parser for the LZMA properties header.
241    pub fn parser(i: &mut Partial<&'_ [u8]>) -> PResult<Self> {
242        // Note: the actual properties (5 bytes, contains dictionary size,
243        // and various other settings) is not actually read, because lzma-rs
244        // reads those properties itself.
245
246        seq! {Self {
247            major: le_u8,
248            minor: le_u8,
249            properties_size: le_u16,
250        }}
251        .parse_next(i)
252    }
253
254    /// Check if the LZMA version is supported.
255    pub fn error_if_unsupported(&self) -> Result<(), Error> {
256        if (self.major, self.minor) != (2, 0) {
257            return Err(Error::Unsupported(
258                UnsupportedError::LzmaVersionUnsupported {
259                    minor: self.minor,
260                    major: self.major,
261                },
262            ));
263        }
264
265        const LZMA_PROPERTIES_SIZE: u16 = 5;
266        if self.properties_size != LZMA_PROPERTIES_SIZE {
267            return Err(Error::Unsupported(
268                UnsupportedError::LzmaPropertiesHeaderWrongSize {
269                    expected: 5,
270                    actual: self.properties_size,
271                },
272            ));
273        }
274
275        Ok(())
276    }
277}