gistools/util/compression/
mod.rs

1/// Flate decompression (gzip, inflate, inflate-raw)
2pub mod fflate;
3/// LZW decompression
4pub 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/// Handles compression errors
26#[derive(Debug, PartialEq)]
27pub enum CompressError {
28    /// Brotli not implemented
29    UnimplementedBrotli,
30    /// Zstd not implemented
31    UnimplementedZstd,
32    /// Errors from the FFlate library
33    FFlate(FFlateError),
34    ///  Zipped Folder has a bad format
35    BadZipFormat,
36    ///  Zipped Folder has multiple disks
37    ZipMultiDiskNotSupported,
38    /// Invalid compression method
39    InvalidCompressionMethod,
40    /// Read error
41    ReadError,
42    /// Write error
43    WriteError,
44    /// Other
45    Other,
46}
47impl From<FFlateError> for CompressError {
48    fn from(err: FFlateError) -> Self {
49        CompressError::FFlate(err)
50    }
51}
52
53/// Compression formats
54#[derive(Debug, Default, Clone, Copy, PartialEq)]
55pub enum CompressionFormat {
56    /// No compression
57    #[default]
58    None = 1,
59    /// Gzip
60    Gzip = 2,
61    /// Brotli
62    Brotli = 3,
63    /// Zstd
64    Zstd = 4,
65    /// Deflate
66    Deflate = 5,
67    /// Deflate raw
68    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/// Compresses data using the specified format
130#[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/// Decompress data using the specified format
162#[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        // CompressionFormat::Zstd => {
186        //     panic!("zstd not implemented");
187        //     // let mut decoder = StreamingDecoder::new(input).unwrap();
188        //     // decoder.read_to_end(&mut output).unwrap();
189        // }
190    }
191
192    Ok(output)
193}
194
195/// Decompress data using the specified format
196#[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/// Represents a zip item
217#[allow(missing_debug_implementations)]
218pub struct ZipItem<'a> {
219    /// The file name
220    pub filename: String,
221    /// The file comment
222    pub comment: String,
223    /// If the user wants to read the contents of the file, they can use this function and it will unzip it
224    pub read: Box<dyn Fn() -> Result<Vec<u8>, CompressError> + Send + Sync + 'a>,
225}
226
227/// Iterates through the items in a zip file
228pub 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    // Read end central directory
234    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    // Read central directory
242    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        // Find local entry location
250        let local_entry_at = reader.uint32_le(Some(42 + at)) as usize;
251
252        // Read buffers, move at to after entry, and store where we were
253        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        // >> Start reading entry
266        at = local_entry_at as u64;
267
268        // This is the local entry (filename + extra fields) length, which we skip
269        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 = usize::max(search - 65516, 2); // Sub 2**256 - 20 (max comment length)
297    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}