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)]
23pub struct LocalFileHeader<'a> {
25 pub reader_version: Version,
27
28 pub flags: u16,
30
31 pub method: Method,
33
34 pub modified: MsdosTimestamp,
36
37 pub crc32: u32,
39
40 pub compressed_size: u32,
42
43 pub uncompressed_size: u32,
45
46 pub name: Cow<'a, [u8]>,
48
49 pub extra: Cow<'a, [u8]>,
51
52 pub method_specific: MethodSpecific,
54}
55
56#[derive(Debug, ToOwned, IntoOwned)]
57pub enum MethodSpecific {
59 None,
61
62 Lzma(LzmaProperties),
64}
65
66impl<'a> LocalFileHeader<'a> {
67 pub const SIGNATURE: &'static str = "PK\x03\x04";
69
70 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 pub fn has_data_descriptor(&self) -> bool {
120 self.flags & 0b1000 != 0
123 }
124
125 pub fn as_entry(&self) -> Result<Entry, Error> {
127 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 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#[derive(Debug)]
184pub struct DataDescriptorRecord {
185 pub crc32: u32,
187 pub compressed_size: u64,
189 pub uncompressed_size: u64,
191}
192
193impl DataDescriptorRecord {
194 const SIGNATURE: &'static str = "PK\x07\x08";
195
196 pub fn mk_parser(is_zip64: bool) -> impl FnMut(&mut Partial<&'_ [u8]>) -> PResult<Self> {
198 move |i| {
199 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#[derive(Debug, ToOwned, IntoOwned)]
230pub struct LzmaProperties {
231 pub major: u8,
233 pub minor: u8,
235 pub properties_size: u16,
237}
238
239impl LzmaProperties {
240 pub fn parser(i: &mut Partial<&'_ [u8]>) -> PResult<Self> {
242 seq! {Self {
247 major: le_u8,
248 minor: le_u8,
249 properties_size: le_u16,
250 }}
251 .parse_next(i)
252 }
253
254 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}