rc_zip/parse/
central_directory_file_header.rs1use std::borrow::Cow;
2
3use ownable::{IntoOwned, ToOwned};
4use tracing::trace;
5use winnow::{
6 binary::{le_u16, le_u32},
7 prelude::PResult,
8 token::{literal, take},
9 Parser, Partial,
10};
11
12use crate::{
13 encoding::detect_utf8,
14 encoding::Encoding,
15 error::{Error, FormatError},
16 parse::{
17 zero_datetime, Entry, ExtraField, ExtraFieldSettings, HostSystem, Mode, MsdosMode,
18 MsdosTimestamp, UnixMode, Version,
19 },
20};
21
22use super::Method;
23
24#[derive(IntoOwned, ToOwned)]
26pub struct CentralDirectoryFileHeader<'a> {
27 pub creator_version: Version,
29
30 pub reader_version: Version,
32
33 pub flags: u16,
35
36 pub method: Method,
38
39 pub modified: MsdosTimestamp,
41
42 pub crc32: u32,
44
45 pub compressed_size: u32,
47
48 pub uncompressed_size: u32,
50
51 pub disk_nbr_start: u16,
53
54 pub internal_attrs: u16,
56
57 pub external_attrs: u32,
59
60 pub header_offset: u32,
62
63 pub name: Cow<'a, [u8]>,
65
66 pub extra: Cow<'a, [u8]>,
68
69 pub comment: Cow<'a, [u8]>,
71}
72
73impl<'a> CentralDirectoryFileHeader<'a> {
74 const SIGNATURE: &'static str = "PK\x01\x02";
75
76 pub fn parser(i: &mut Partial<&'a [u8]>) -> PResult<Self> {
78 _ = literal(Self::SIGNATURE).parse_next(i)?;
79 let creator_version = Version::parser.parse_next(i)?;
80 let reader_version = Version::parser.parse_next(i)?;
81 let flags = le_u16.parse_next(i)?;
82 let method = Method::parser.parse_next(i)?;
83 let modified = MsdosTimestamp::parser.parse_next(i)?;
84 let crc32 = le_u32.parse_next(i)?;
85 let compressed_size = le_u32.parse_next(i)?;
86 let uncompressed_size = le_u32.parse_next(i)?;
87 let name_len = le_u16.parse_next(i)?;
88 let extra_len = le_u16.parse_next(i)?;
89 let comment_len = le_u16.parse_next(i)?;
90 let disk_nbr_start = le_u16.parse_next(i)?;
91 let internal_attrs = le_u16.parse_next(i)?;
92 let external_attrs = le_u32.parse_next(i)?;
93 let header_offset = le_u32.parse_next(i)?;
94
95 let name = take(name_len).parse_next(i)?;
96 let extra = take(extra_len).parse_next(i)?;
97 let comment = take(comment_len).parse_next(i)?;
98
99 Ok(Self {
100 creator_version,
101 reader_version,
102 flags,
103 method,
104 modified,
105 crc32,
106 compressed_size,
107 uncompressed_size,
108 disk_nbr_start,
109 internal_attrs,
110 external_attrs,
111 header_offset,
112 name: Cow::Borrowed(name),
113 extra: Cow::Borrowed(extra),
114 comment: Cow::Borrowed(comment),
115 })
116 }
117}
118
119impl CentralDirectoryFileHeader<'_> {
120 pub fn is_non_utf8(&self) -> bool {
122 let (valid1, require1) = detect_utf8(&self.name[..]);
123 let (valid2, require2) = detect_utf8(&self.comment[..]);
124 if !valid1 || !valid2 {
125 return true;
127 }
128
129 if !require1 && !require2 {
130 return false;
132 }
133
134 self.flags & 0x800 == 0
139 }
140
141 pub fn as_entry(&self, encoding: Encoding, global_offset: u64) -> Result<Entry, Error> {
144 let mut entry = Entry {
145 name: encoding.decode(&self.name[..])?,
146 method: self.method,
147 comment: encoding.decode(&self.comment[..])?,
148 modified: self.modified.to_datetime().unwrap_or_else(zero_datetime),
149 created: None,
150 accessed: None,
151 header_offset: (self.header_offset as u64)
152 .checked_add(global_offset)
153 .ok_or(FormatError::InvalidHeaderOffset)?,
154 reader_version: self.reader_version,
155 flags: self.flags,
156 uid: None,
157 gid: None,
158 crc32: self.crc32,
159 compressed_size: self.compressed_size as _,
160 uncompressed_size: self.uncompressed_size as _,
161 mode: Mode(0),
162 };
163
164 entry.mode = match self.creator_version.host_system {
165 HostSystem::Unix | HostSystem::Osx => UnixMode(self.external_attrs >> 16).into(),
166 HostSystem::WindowsNtfs | HostSystem::Vfat | HostSystem::MsDos => {
167 MsdosMode(self.external_attrs).into()
168 }
169 _ => Mode(0),
170 };
171 if entry.name.ends_with('/') {
172 entry.mode |= Mode::DIR
174 };
175
176 let settings = ExtraFieldSettings {
177 uncompressed_size_u32: self.uncompressed_size,
178 compressed_size_u32: self.compressed_size,
179 header_offset_u32: self.header_offset,
180 };
181
182 let mut slice = Partial::new(&self.extra[..]);
183 while !slice.is_empty() {
184 match ExtraField::mk_parser(settings).parse_next(&mut slice) {
185 Ok(ef) => {
186 entry.set_extra_field(&ef);
187 }
188 Err(e) => {
189 trace!("extra field error: {:#?}", e);
190 return Err(FormatError::InvalidExtraField.into());
191 }
192 }
193 }
194
195 Ok(entry)
196 }
197}