gistools/util/compression/
mod.rs1pub mod fflate;
3pub mod lzw;
5
6use crate::parsers::{BufferReader, Reader};
7use alloc::{
8 boxed::Box,
9 string::{String, ToString},
10 vec::Vec,
11};
12use core::result::Result;
13pub use fflate::*;
14#[cfg(feature = "std")]
15use flate2::{
16 Compression,
17 read::{DeflateDecoder, GzDecoder, ZlibDecoder},
18 write::{DeflateEncoder, GzEncoder, ZlibEncoder},
19};
20pub use lzw::*;
21use s2_tilejson::Encoding;
22#[cfg(feature = "std")]
23use std::io::{Read, Write};
24
25#[derive(Debug, PartialEq)]
27pub enum CompressError {
28 UnimplementedBrotli,
30 UnimplementedZstd,
32 FFlate(FFlateError),
34 BadZipFormat,
36 ZipMultiDiskNotSupported,
38 InvalidCompressionMethod,
40 ReadError,
42 WriteError,
44 Other,
46}
47impl From<FFlateError> for CompressError {
48 fn from(err: FFlateError) -> Self {
49 CompressError::FFlate(err)
50 }
51}
52
53#[derive(Debug, Default, Clone, Copy, PartialEq)]
55pub enum CompressionFormat {
56 #[default]
58 None = 1,
59 Gzip = 2,
61 Brotli = 3,
63 Zstd = 4,
65 Deflate = 5,
67 DeflateRaw = 6,
69}
70impl From<u8> for CompressionFormat {
71 fn from(value: u8) -> Self {
72 match value {
73 2 => CompressionFormat::Gzip,
74 3 => CompressionFormat::Brotli,
75 4 => CompressionFormat::Zstd,
76 5 => CompressionFormat::Deflate,
77 6 => CompressionFormat::DeflateRaw,
78 _ => CompressionFormat::None,
79 }
80 }
81}
82impl From<&Encoding> for CompressionFormat {
83 fn from(encoding: &Encoding) -> Self {
84 match encoding {
85 Encoding::Gzip => CompressionFormat::Gzip,
86 Encoding::Brotli => CompressionFormat::Brotli,
87 Encoding::Zstd => CompressionFormat::Zstd,
88 _ => CompressionFormat::None,
89 }
90 }
91}
92impl From<CompressionFormat> for u8 {
93 fn from(compression: CompressionFormat) -> Self {
94 match compression {
95 CompressionFormat::None => 1,
96 CompressionFormat::Gzip => 2,
97 CompressionFormat::Brotli => 3,
98 CompressionFormat::Zstd => 4,
99 CompressionFormat::Deflate => 5,
100 CompressionFormat::DeflateRaw => 6,
101 }
102 }
103}
104impl From<&str> for CompressionFormat {
105 fn from(s: &str) -> Self {
106 match s {
107 "gzip" => CompressionFormat::Gzip,
108 "deflate" => CompressionFormat::Deflate,
109 "deflate-raw" => CompressionFormat::DeflateRaw,
110 "brotli" => CompressionFormat::Brotli,
111 "zstd" => CompressionFormat::Zstd,
112 _ => CompressionFormat::None,
113 }
114 }
115}
116impl From<CompressionFormat> for String {
117 fn from(s: CompressionFormat) -> Self {
118 match s {
119 CompressionFormat::None => "none".into(),
120 CompressionFormat::Gzip => "gzip".into(),
121 CompressionFormat::Deflate => "deflate".into(),
122 CompressionFormat::DeflateRaw => "deflate-raw".into(),
123 CompressionFormat::Brotli => "brotli".into(),
124 CompressionFormat::Zstd => "zstd".into(),
125 }
126 }
127}
128
129#[cfg(feature = "std")]
131pub fn compress_data(input: Vec<u8>, format: CompressionFormat) -> Result<Vec<u8>, CompressError> {
132 let mut output = Vec::new();
133
134 match format {
135 CompressionFormat::None => output = input,
136 CompressionFormat::Gzip => {
137 let mut encoder = GzEncoder::new(&mut output, Compression::default());
138 encoder.write_all(&input).map_err(|_| CompressError::WriteError)?;
139 encoder.finish().map_err(|_| CompressError::WriteError)?;
140 }
141 CompressionFormat::Deflate => {
142 let mut encoder = DeflateEncoder::new(&mut output, Compression::default());
143 encoder.write_all(&input).map_err(|_| CompressError::WriteError)?;
144 encoder.finish().map_err(|_| CompressError::WriteError)?;
145 }
146 CompressionFormat::DeflateRaw => {
147 let mut encoder = ZlibEncoder::new(&mut output, Compression::default());
148 encoder.write_all(&input).map_err(|_| CompressError::WriteError)?;
149 encoder.finish().map_err(|_| CompressError::WriteError)?;
150 }
151 CompressionFormat::Brotli => {
152 let mut encoder = brotli::CompressorWriter::new(&mut output, 4096, 11, 22);
153 encoder.write_all(&input).map_err(|_| CompressError::WriteError)?;
154 }
155 CompressionFormat::Zstd => return Err(CompressError::UnimplementedZstd),
156 }
157
158 Ok(output)
159}
160
161#[cfg(feature = "std")]
163pub fn decompress_data(input: &[u8], format: CompressionFormat) -> Result<Vec<u8>, CompressError> {
164 let mut output = Vec::new();
165
166 match format {
167 CompressionFormat::None => output.extend_from_slice(input),
168 CompressionFormat::Gzip => {
169 let mut decoder = GzDecoder::new(input);
170 decoder.read_to_end(&mut output).map_err(|_| CompressError::ReadError)?;
171 }
172 CompressionFormat::Deflate => {
173 let mut decoder = DeflateDecoder::new(input);
174 decoder.read_to_end(&mut output).map_err(|_| CompressError::ReadError)?;
175 }
176 CompressionFormat::DeflateRaw => {
177 let mut decoder = ZlibDecoder::new(input);
178 decoder.read_to_end(&mut output).map_err(|_| CompressError::ReadError)?;
179 }
180 CompressionFormat::Brotli => {
181 let mut decoder = brotli::Decompressor::new(input, 4096);
182 _ = decoder.read_to_end(&mut output);
183 }
184 CompressionFormat::Zstd => return Err(CompressError::UnimplementedZstd),
185 }
191
192 Ok(output)
193}
194
195#[cfg(not(feature = "std"))]
197pub fn compress_data(
198 _input: Vec<u8>,
199 _format: CompressionFormat,
200) -> Result<Vec<u8>, CompressError> {
201 unimplemented!();
202}
203
204#[cfg(not(feature = "std"))]
206pub fn decompress_data(input: &[u8], format: CompressionFormat) -> Result<Vec<u8>, CompressError> {
207 let mut output = Vec::new();
208
209 match format {
210 CompressionFormat::None => output.extend_from_slice(input),
211 CompressionFormat::Gzip | CompressionFormat::Deflate | CompressionFormat::DeflateRaw => {
212 output = decompress_fflate(input, None)?;
213 }
214 CompressionFormat::Brotli => {
215 return Err(CompressError::UnimplementedBrotli);
216 }
217 CompressionFormat::Zstd => {
218 return Err(CompressError::UnimplementedZstd);
219 }
220 }
221
222 Ok(output)
223}
224
225#[allow(missing_debug_implementations)]
227pub struct ZipItem<'a> {
228 pub filename: String,
230 pub comment: String,
232 pub read: Box<dyn Fn() -> Result<Vec<u8>, CompressError> + Send + Sync + 'a>,
234}
235
236pub fn iter_zip_folder(raw: &[u8]) -> Result<Vec<ZipItem<'_>>, CompressError> {
238 let mut at = find_end_central_directory(raw)? as u64;
239 let mut items = Vec::new();
240 let reader: BufferReader = raw.into();
241
242 let file_count = reader.uint16_le(Some(10 + at));
244 if file_count != reader.uint16_le(Some(8 + at)) {
245 return Err(CompressError::ZipMultiDiskNotSupported);
246 }
247 let central_directory_start = reader.uint32_le(Some(16 + at));
248 at = central_directory_start as u64;
249
250 for _ in 0..file_count {
252 let compression_method = reader.uint16_le(Some(10 + at)) as usize;
253 let filename_length = reader.uint16_le(Some(28 + at)) as usize;
254 let extra_fields_length = reader.uint16_le(Some(30 + at)) as usize;
255 let comment_length = reader.uint16_le(Some(32 + at)) as usize;
256 let compressed_size = reader.uint32_le(Some(20 + at)) as usize;
257
258 let local_entry_at = reader.uint32_le(Some(42 + at)) as usize;
260
261 let filename =
263 String::from_utf8_lossy(&raw[(at + 46) as usize..(at as usize + 46 + filename_length)])
264 .to_string();
265 let comment = String::from_utf8_lossy(
266 &raw[at as usize + 46 + filename_length + extra_fields_length
267 ..at as usize + 46 + filename_length + extra_fields_length + comment_length],
268 )
269 .to_string();
270
271 let central_entry_size = 46 + filename_length + extra_fields_length + comment_length;
272 let next_central_directory_entry = at + central_entry_size as u64;
273
274 at = local_entry_at as u64;
276
277 let bytes_start = at as usize
279 + 30
280 + reader.uint16_le(Some(26 + at)) as usize
281 + reader.uint16_le(Some(28 + at)) as usize;
282 let bytes_end = bytes_start + compressed_size;
283 let bytes = &raw[bytes_start..bytes_end];
284
285 let read_fn = Box::new(move || {
286 if compression_method & 8 > 0 {
287 decompress_fflate(bytes, None).map_err(|_| CompressError::ReadError)
288 } else if compression_method > 0 {
289 Err(CompressError::InvalidCompressionMethod)
290 } else {
291 Ok(bytes.to_vec())
292 }
293 });
294
295 items.push(ZipItem { filename, comment, read: read_fn });
296
297 at = next_central_directory_entry;
298 }
299
300 Ok(items)
301}
302
303fn find_end_central_directory(raw: &[u8]) -> Result<usize, CompressError> {
304 let mut search = raw.len() - 20;
305 let bounds = if search > 65516 { usize::max(search - 65516, 2) } else { 2 };
307
308 while search > bounds {
309 if raw[search..search + 4] == [0x50, 0x4b, 0x05, 0x06] {
310 return Ok(search);
311 }
312
313 search = raw[..search].iter().rposition(|&x| x == 0x50).unwrap_or(bounds);
314 }
315
316 Err(CompressError::BadZipFormat)
317}