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 decompress_data(input: &[u8], format: CompressionFormat) -> Result<Vec<u8>, CompressError> {
198 let mut output = Vec::new();
199
200 match format {
201 CompressionFormat::None => output.extend_from_slice(input),
202 CompressionFormat::Gzip | CompressionFormat::Deflate | CompressionFormat::DeflateRaw => {
203 output = decompress_fflate(input, None)?;
204 }
205 CompressionFormat::Brotli => {
206 return Err(CompressError::UnimplementedBrotli);
207 }
208 CompressionFormat::Zstd => {
209 return Err(CompressError::UnimplementedZstd);
210 }
211 }
212
213 Ok(output)
214}
215
216#[allow(missing_debug_implementations)]
218pub struct ZipItem<'a> {
219 pub filename: String,
221 pub comment: String,
223 pub read: Box<dyn Fn() -> Result<Vec<u8>, CompressError> + Send + Sync + 'a>,
225}
226
227pub fn iter_zip_folder(raw: &[u8]) -> Result<Vec<ZipItem<'_>>, CompressError> {
229 let mut at = find_end_central_directory(raw)? as u64;
230 let mut items = Vec::new();
231 let reader: BufferReader = raw.into();
232
233 let file_count = reader.uint16_le(Some(10 + at));
235 if file_count != reader.uint16_le(Some(8 + at)) {
236 return Err(CompressError::ZipMultiDiskNotSupported);
237 }
238 let central_directory_start = reader.uint32_le(Some(16 + at));
239 at = central_directory_start as u64;
240
241 for _ in 0..file_count {
243 let compression_method = reader.uint16_le(Some(10 + at)) as usize;
244 let filename_length = reader.uint16_le(Some(28 + at)) as usize;
245 let extra_fields_length = reader.uint16_le(Some(30 + at)) as usize;
246 let comment_length = reader.uint16_le(Some(32 + at)) as usize;
247 let compressed_size = reader.uint32_le(Some(20 + at)) as usize;
248
249 let local_entry_at = reader.uint32_le(Some(42 + at)) as usize;
251
252 let filename =
254 String::from_utf8_lossy(&raw[(at + 46) as usize..(at as usize + 46 + filename_length)])
255 .to_string();
256 let comment = String::from_utf8_lossy(
257 &raw[at as usize + 46 + filename_length + extra_fields_length
258 ..at as usize + 46 + filename_length + extra_fields_length + comment_length],
259 )
260 .to_string();
261
262 let central_entry_size = 46 + filename_length + extra_fields_length + comment_length;
263 let next_central_directory_entry = at + central_entry_size as u64;
264
265 at = local_entry_at as u64;
267
268 let bytes_start = at as usize
270 + 30
271 + reader.uint16_le(Some(26 + at)) as usize
272 + reader.uint16_le(Some(28 + at)) as usize;
273 let bytes_end = bytes_start + compressed_size;
274 let bytes = &raw[bytes_start..bytes_end];
275
276 let read_fn = Box::new(move || {
277 if compression_method & 8 > 0 {
278 decompress_fflate(bytes, None).map_err(|_| CompressError::ReadError)
279 } else if compression_method > 0 {
280 Err(CompressError::InvalidCompressionMethod)
281 } else {
282 Ok(bytes.to_vec())
283 }
284 });
285
286 items.push(ZipItem { filename, comment, read: read_fn });
287
288 at = next_central_directory_entry;
289 }
290
291 Ok(items)
292}
293
294fn find_end_central_directory(raw: &[u8]) -> Result<usize, CompressError> {
295 let mut search = raw.len() - 20;
296 let bounds = if search > 65516 { usize::max(search - 65516, 2) } else { 2 };
298
299 while search > bounds {
300 if raw[search..search + 4] == [0x50, 0x4b, 0x05, 0x06] {
301 return Ok(search);
302 }
303
304 search = raw[..search].iter().rposition(|&x| x == 0x50).unwrap_or(bounds);
305 }
306
307 Err(CompressError::BadZipFormat)
308}