1use crate::error::{Error, Result};
19use crate::manifest::{StreamRecipe, StreamSegment, ZipMemberMeta, ZipRecipe};
20use preflate_rs::PreflateConfig;
21use serde_bytes::ByteBuf;
22
23pub const ZIP_EXTENSIONS: &[&str] = &["zip", "docx", "xlsx", "pptx", "epub", "jar", "odt", "ods", "odp", "xpi", "whl"];
24
25pub const KIND_STORED: u8 = 0;
26pub const KIND_DEFLATE: u8 = 1;
27pub const KIND_RAW: u8 = 2;
28
29const SIG_LOCAL: u32 = 0x0403_4b50;
30const SIG_CENTRAL: u32 = 0x0201_4b50;
31const SIG_EOCD: u32 = 0x0605_4b50;
32const SIG_ZIP64_LOCATOR: u32 = 0x0706_4b50;
33const SIG_DESCRIPTOR: u32 = 0x0807_4b50;
34
35pub const PREFLATE_PLAINTEXT_LIMIT: usize = 256 << 20;
37
38pub fn is_zip_like(name: &str) -> bool {
39 let ext = name.rsplit('.').next().unwrap_or("").to_ascii_lowercase();
40 ZIP_EXTENSIONS.contains(&ext.as_str())
41}
42
43fn u16_at(b: &[u8], i: usize) -> u16 {
44 u16::from_le_bytes([b[i], b[i + 1]])
45}
46
47fn u32_at(b: &[u8], i: usize) -> u32 {
48 u32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]])
49}
50
51struct CentralRecord {
52 bytes_start: usize,
53 bytes_end: usize,
54 flags: u16,
55 method: u16,
56 csize: u32,
57 usize_: u32,
58 local_off: u32,
59 name: String,
60}
61
62pub fn explode(data: &[u8]) -> Option<(ZipRecipe, Vec<Vec<u8>>)> {
65 if data.len() < 22 {
66 return None;
67 }
68 let min_start = data.len().saturating_sub(22 + 65_535);
70 let mut eocd_off = None;
71 let mut i = data.len() - 22;
72 loop {
73 if u32_at(data, i) == SIG_EOCD {
74 let clen = u16_at(data, i + 20) as usize;
75 if i + 22 + clen == data.len() {
76 eocd_off = Some(i);
77 break;
78 }
79 }
80 if i == min_start {
81 break;
82 }
83 i -= 1;
84 }
85 let eocd_off = eocd_off?;
86 if eocd_off >= 20 && u32_at(data, eocd_off - 20) == SIG_ZIP64_LOCATOR {
87 return None; }
89 let disk = u16_at(data, eocd_off + 4);
90 let cd_disk = u16_at(data, eocd_off + 6);
91 let entries = u16_at(data, eocd_off + 10) as usize;
92 let cd_size = u32_at(data, eocd_off + 12) as usize;
93 let cd_off = u32_at(data, eocd_off + 16) as usize;
94 if disk != 0 || cd_disk != 0 || entries == 0xFFFF || cd_off + cd_size != eocd_off {
95 return None;
96 }
97
98 let mut central = Vec::with_capacity(entries);
100 let mut p = cd_off;
101 for _ in 0..entries {
102 if p + 46 > eocd_off || u32_at(data, p) != SIG_CENTRAL {
103 return None;
104 }
105 let flags = u16_at(data, p + 8);
106 let method = u16_at(data, p + 10);
107 let csize = u32_at(data, p + 20);
108 let usize_ = u32_at(data, p + 24);
109 let nlen = u16_at(data, p + 28) as usize;
110 let elen = u16_at(data, p + 30) as usize;
111 let clen = u16_at(data, p + 32) as usize;
112 let local_off = u32_at(data, p + 42);
113 let end = p + 46 + nlen + elen + clen;
114 if end > eocd_off || csize == u32::MAX || usize_ == u32::MAX || local_off == u32::MAX {
115 return None;
116 }
117 let name = String::from_utf8_lossy(&data[p + 46..p + 46 + nlen]).to_string();
118 central.push(CentralRecord { bytes_start: p, bytes_end: end, flags, method, csize, usize_, local_off, name });
119 p = end;
120 }
121 if p != eocd_off {
122 return None;
123 }
124
125 let mut order: Vec<usize> = (0..central.len()).collect();
127 order.sort_by_key(|&k| central[k].local_off);
128 let mut members: Vec<Option<ZipMemberMeta>> = (0..central.len()).map(|_| None).collect();
129 let mut parts: Vec<Option<Vec<u8>>> = (0..central.len()).map(|_| None).collect();
130 let mut cursor = 0usize;
131 let config = PreflateConfig { plain_text_limit: PREFLATE_PLAINTEXT_LIMIT, verify_compression: true, ..PreflateConfig::default() };
132 for &k in &order {
133 let c = ¢ral[k];
134 let lo = c.local_off as usize;
135 if lo != cursor || lo + 30 > cd_off || u32_at(data, lo) != SIG_LOCAL {
136 return None;
137 }
138 let nlen = u16_at(data, lo + 26) as usize;
139 let elen = u16_at(data, lo + 28) as usize;
140 let data_start = lo + 30 + nlen + elen;
141 let data_end = data_start + c.csize as usize;
142 if data_end > cd_off {
143 return None;
144 }
145 let payload = &data[data_start..data_end];
146 let mut desc_end = data_end;
148 if c.flags & 0x0008 != 0 {
149 if data_end + 16 <= cd_off && u32_at(data, data_end) == SIG_DESCRIPTOR {
150 desc_end = data_end + 16;
151 } else if data_end + 12 <= cd_off {
152 desc_end = data_end + 12;
153 } else {
154 return None;
155 }
156 }
157 let encrypted = c.flags & 0x0001 != 0;
158 let (kind, plain, corrections) = if encrypted {
159 (KIND_RAW, payload.to_vec(), Vec::new())
160 } else {
161 match c.method {
162 0 => (KIND_STORED, payload.to_vec(), Vec::new()),
163 8 => match preflate_rs::preflate_whole_deflate_stream(payload, &config) {
164 Ok((res, text)) if res.compressed_size == payload.len() && text.text().len() == c.usize_ as usize => (KIND_DEFLATE, text.text().to_vec(), res.corrections),
165 _ => (KIND_RAW, payload.to_vec(), Vec::new()),
166 },
167 _ => (KIND_RAW, payload.to_vec(), Vec::new()),
168 }
169 };
170 members[k] = Some(ZipMemberMeta {
171 name: c.name.clone(),
172 kind,
173 local_header: ByteBuf::from(data[lo..data_start].to_vec()),
174 descriptor: ByteBuf::from(data[data_end..desc_end].to_vec()),
175 central: ByteBuf::from(data[c.bytes_start..c.bytes_end].to_vec()),
176 corrections: ByteBuf::from(corrections),
177 size: plain.len() as u64,
178 chunks: Vec::new(),
179 });
180 parts[k] = Some(plain);
181 cursor = desc_end;
182 }
183 if cursor != cd_off {
184 return None; }
186 let members: Vec<ZipMemberMeta> = members.into_iter().map(|m| m.unwrap()).collect();
187 let parts: Vec<Vec<u8>> = parts.into_iter().map(|p| p.unwrap()).collect();
188 Some((ZipRecipe { local_order: order.iter().map(|&k| k as u32).collect(), eocd: ByteBuf::from(data[eocd_off..].to_vec()), members }, parts))
189}
190
191pub fn rebuild(recipe: &ZipRecipe, parts: &[Vec<u8>]) -> Result<Vec<u8>> {
193 if recipe.members.len() != parts.len() || recipe.local_order.len() != parts.len() {
194 return Err(Error::Corrupt("zip recipe/member count mismatch".into()));
195 }
196 let mut out = Vec::new();
197 for &k in &recipe.local_order {
198 let m = recipe.members.get(k as usize).ok_or_else(|| Error::Corrupt("zip local order out of range".into()))?;
199 out.extend_from_slice(&m.local_header);
200 match m.kind {
201 KIND_STORED | KIND_RAW => out.extend_from_slice(&parts[k as usize]),
202 KIND_DEFLATE => {
203 let stream = preflate_rs::recreate_whole_deflate_stream(&parts[k as usize], &m.corrections).map_err(|e| Error::Corrupt(format!("preflate recreate {}: {e}", m.name)))?;
204 out.extend_from_slice(&stream);
205 }
206 other => return Err(Error::Corrupt(format!("unknown zip member kind {other}"))),
207 }
208 out.extend_from_slice(&m.descriptor);
209 }
210 for m in &recipe.members {
211 out.extend_from_slice(&m.central);
212 }
213 out.extend_from_slice(&recipe.eocd);
214 Ok(out)
215}
216
217pub fn try_bitexact(data: &[u8]) -> Option<(ZipRecipe, Vec<Vec<u8>>)> {
219 let (recipe, parts) = explode(data)?;
220 match rebuild(&recipe, &parts) {
221 Ok(out) if out == data => Some((recipe, parts)),
222 _ => None,
223 }
224}
225
226pub const STREAM_EXTENSIONS: &[&str] = &["pdf", "jpg", "jpeg", "jpe"];
233
234pub fn is_stream_container(name: &str) -> bool {
235 let ext = name.rsplit('.').next().unwrap_or("").to_ascii_lowercase();
236 STREAM_EXTENSIONS.contains(&ext.as_str())
237}
238
239pub const SEG_LITERAL: u8 = 0;
240pub const SEG_ZLIB: u8 = 1;
241pub const SEG_JPEG: u8 = 2;
243
244pub const LEPTON_MAX_INPUT: usize = 64 << 20;
246
247#[cfg(feature = "lepton")]
248fn lepton_features() -> lepton_jpeg::EnabledFeatures {
249 lepton_jpeg::EnabledFeatures::compat_lepton_vector_write()
250}
251
252#[cfg(feature = "lepton")]
255pub fn jpeg_to_lepton(jpeg: &[u8]) -> Option<Vec<u8>> {
256 if jpeg.len() < 4 || jpeg.len() > LEPTON_MAX_INPUT || jpeg[..3] != [0xFF, 0xD8, 0xFF] {
257 return None;
258 }
259 let pool = lepton_jpeg::SingleThreadPool {};
260 match lepton_jpeg::catch_unwind_result(|| lepton_jpeg::encode_lepton_verify(jpeg, &lepton_features(), &pool)) {
261 Ok((out, _metrics)) if out.len() < jpeg.len() => Some(out),
262 _ => None,
263 }
264}
265
266#[cfg(feature = "lepton")]
267pub fn lepton_to_jpeg(lepton: &[u8], expected_len: u64) -> Result<Vec<u8>> {
268 let pool = lepton_jpeg::SingleThreadPool {};
269 let mut out = Vec::with_capacity(expected_len as usize);
270 let mut reader = std::io::Cursor::new(lepton);
271 lepton_jpeg::catch_unwind_result(|| lepton_jpeg::decode_lepton(&mut reader, &mut out, &lepton_features(), &pool)).map_err(|e| Error::Corrupt(format!("lepton decode: {e}")))?;
272 Ok(out)
273}
274
275#[cfg(not(feature = "lepton"))]
277pub fn jpeg_to_lepton(_jpeg: &[u8]) -> Option<Vec<u8>> {
278 None
279}
280
281#[cfg(not(feature = "lepton"))]
282pub fn lepton_to_jpeg(_lepton: &[u8], _expected_len: u64) -> Result<Vec<u8>> {
283 Err(Error::Invalid("this build has no Lepton support (Cargo feature `lepton`): JPEG segments of this archive cannot be decoded".into()))
284}
285
286fn adler32(data: &[u8]) -> u32 {
287 let (mut a, mut b) = (1u32, 0u32);
288 for chunk in data.chunks(5552) {
289 for &x in chunk {
290 a += x as u32;
291 b += a;
292 }
293 a %= 65521;
294 b %= 65521;
295 }
296 (b << 16) | a
297}
298
299fn find(hay: &[u8], needle: &[u8], from: usize) -> Option<usize> {
300 if needle.is_empty() || from >= hay.len() {
301 return None;
302 }
303 hay[from..].windows(needle.len()).position(|w| w == needle).map(|p| p + from)
304}
305
306fn literal_segment(bytes: &[u8]) -> StreamSegment {
307 StreamSegment { kind: SEG_LITERAL, header: ByteBuf::new(), trailer: ByteBuf::new(), corrections: ByteBuf::new(), size: bytes.len() as u64, chunks: Vec::new() }
308}
309
310pub fn explode_streams(data: &[u8], jpeg: bool) -> Option<(StreamRecipe, Vec<Vec<u8>>)> {
315 if data.len() >= 4 && data[..3] == [0xFF, 0xD8, 0xFF] {
316 if !jpeg {
317 return None;
318 }
319 let lepton = jpeg_to_lepton(data)?;
320 let seg = StreamSegment {
321 kind: SEG_JPEG,
322 header: ByteBuf::new(),
323 trailer: ByteBuf::from((data.len() as u64).to_le_bytes().to_vec()),
324 corrections: ByteBuf::new(),
325 size: lepton.len() as u64,
326 chunks: Vec::new(),
327 };
328 return Some((StreamRecipe { segments: vec![seg] }, vec![lepton]));
329 }
330 let config = PreflateConfig { plain_text_limit: PREFLATE_PLAINTEXT_LIMIT, verify_compression: true, ..PreflateConfig::default() };
331 let mut segments = Vec::new();
332 let mut parts = Vec::new();
333 let mut lit_start = 0usize;
334 let mut pos = 0usize;
335 let mut inverted = 0usize;
336 while let Some(s) = find(data, b"stream", pos) {
337 let mut data_start = s + 6;
339 if s >= 3 && &data[s - 3..s] == b"end" {
340 pos = s + 6;
341 continue;
342 }
343 if data.get(data_start) == Some(&b'\r') {
344 data_start += 1;
345 }
346 if data.get(data_start) == Some(&b'\n') {
347 data_start += 1;
348 } else {
349 pos = s + 6;
350 continue;
351 }
352 let Some(end) = find(data, b"endstream", data_start) else { break };
353 let payload = &data[data_start..end];
354 let mut consumed: Option<(usize, StreamSegment, Vec<u8>)> = None;
356 if payload.len() > 6 && payload[0] & 0x0F == 8 && ((payload[0] as u16) << 8 | payload[1] as u16).is_multiple_of(31) && payload[1] & 0x20 == 0 {
357 if let Ok((res, text)) = preflate_rs::preflate_whole_deflate_stream(&payload[2..], &config) {
358 let zlen = 2 + res.compressed_size + 4;
359 if zlen <= payload.len() {
360 let trailer = &payload[2 + res.compressed_size..zlen];
361 if u32::from_be_bytes([trailer[0], trailer[1], trailer[2], trailer[3]]) == adler32(text.text()) {
362 let seg = StreamSegment {
363 kind: SEG_ZLIB,
364 header: ByteBuf::from(payload[..2].to_vec()),
365 trailer: ByteBuf::from(payload[zlen - 4..zlen].to_vec()),
366 corrections: ByteBuf::from(res.corrections),
367 size: text.text().len() as u64,
368 chunks: Vec::new(),
369 };
370 consumed = Some((zlen, seg, text.text().to_vec()));
371 }
372 }
373 }
374 } else if jpeg && payload.len() > 4 && payload[..3] == [0xFF, 0xD8, 0xFF] {
375 let mut jlen = payload.len();
377 while jlen > 2 && (payload[jlen - 1] == b'\n' || payload[jlen - 1] == b'\r') {
378 jlen -= 1;
379 }
380 if payload[jlen - 2..jlen] == [0xFF, 0xD9] {
381 if let Some(lepton) = jpeg_to_lepton(&payload[..jlen]) {
382 let seg = StreamSegment {
383 kind: SEG_JPEG,
384 header: ByteBuf::new(),
385 trailer: ByteBuf::from((jlen as u64).to_le_bytes().to_vec()),
386 corrections: ByteBuf::new(),
387 size: lepton.len() as u64,
388 chunks: Vec::new(),
389 };
390 consumed = Some((jlen, seg, lepton));
391 }
392 }
393 }
394 if let Some((zlen, seg, plain)) = consumed {
395 if data_start > lit_start {
396 let lit = data[lit_start..data_start].to_vec();
397 segments.push(literal_segment(&lit));
398 parts.push(lit);
399 }
400 segments.push(seg);
401 parts.push(plain);
402 lit_start = data_start + zlen;
403 inverted += 1;
404 }
405 pos = end + 9;
406 }
407 if inverted == 0 {
408 return None;
409 }
410 if data.len() > lit_start {
411 let lit = data[lit_start..].to_vec();
412 segments.push(literal_segment(&lit));
413 parts.push(lit);
414 }
415 Some((StreamRecipe { segments }, parts))
416}
417
418pub fn rebuild_streams(recipe: &StreamRecipe, parts: &[Vec<u8>]) -> Result<Vec<u8>> {
419 if recipe.segments.len() != parts.len() {
420 return Err(Error::Corrupt("stream recipe/segment count mismatch".into()));
421 }
422 let mut out = Vec::new();
423 for (seg, part) in recipe.segments.iter().zip(parts) {
424 match seg.kind {
425 SEG_LITERAL => out.extend_from_slice(part),
426 SEG_ZLIB => {
427 out.extend_from_slice(&seg.header);
428 let stream = preflate_rs::recreate_whole_deflate_stream(part, &seg.corrections).map_err(|e| Error::Corrupt(format!("preflate recreate stream: {e}")))?;
429 out.extend_from_slice(&stream);
430 out.extend_from_slice(&seg.trailer);
431 }
432 SEG_JPEG => {
433 let expected = if seg.trailer.len() == 8 { u64::from_le_bytes(seg.trailer[..8].try_into().unwrap()) } else { 0 };
434 let jpeg = lepton_to_jpeg(part, expected)?;
435 if expected != 0 && jpeg.len() as u64 != expected {
436 return Err(Error::Corrupt("lepton decode: unexpected JPEG length".into()));
437 }
438 out.extend_from_slice(&jpeg);
439 }
440 other => return Err(Error::Corrupt(format!("unknown stream segment kind {other}"))),
441 }
442 }
443 Ok(out)
444}
445
446pub fn try_bitexact_streams(data: &[u8], jpeg: bool) -> Option<(StreamRecipe, Vec<Vec<u8>>)> {
447 let (recipe, parts) = explode_streams(data, jpeg)?;
448 match rebuild_streams(&recipe, &parts) {
449 Ok(out) if out == data => Some((recipe, parts)),
450 _ => None,
451 }
452}
453
454pub fn kinds(recipe: &ZipRecipe) -> (usize, usize, usize) {
456 let mut s = (0, 0, 0);
457 for m in &recipe.members {
458 match m.kind {
459 KIND_STORED => s.0 += 1,
460 KIND_DEFLATE => s.1 += 1,
461 _ => s.2 += 1,
462 }
463 }
464 s
465}