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 compress_data(
198    _input: Vec<u8>,
199    _format: CompressionFormat,
200) -> Result<Vec<u8>, CompressError> {
201    unimplemented!();
202}
203
204/// Decompress data using the specified format
205#[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/// Represents a zip item
226#[allow(missing_debug_implementations)]
227pub struct ZipItem<'a> {
228    /// The file name
229    pub filename: String,
230    /// The file comment
231    pub comment: String,
232    /// If the user wants to read the contents of the file, they can use this function and it will unzip it
233    pub read: Box<dyn Fn() -> Result<Vec<u8>, CompressError> + Send + Sync + 'a>,
234}
235
236/// Iterates through the items in a zip file
237pub 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    // Read end central directory
243    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    // Read central directory
251    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        // Find local entry location
259        let local_entry_at = reader.uint32_le(Some(42 + at)) as usize;
260
261        // Read buffers, move at to after entry, and store where we were
262        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        // >> Start reading entry
275        at = local_entry_at as u64;
276
277        // This is the local entry (filename + extra fields) length, which we skip
278        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 = usize::max(search - 65516, 2); // Sub 2**256 - 20 (max comment length)
306    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}