pub mod fflate;
pub mod lzw;
use crate::parsers::{BufferReader, Reader};
use alloc::{
boxed::Box,
string::{String, ToString},
vec::Vec,
};
use core::result::Result;
pub use fflate::*;
#[cfg(feature = "std")]
use flate2::{
Compression,
read::{DeflateDecoder, GzDecoder, ZlibDecoder},
write::{DeflateEncoder, GzEncoder, ZlibEncoder},
};
pub use lzw::*;
use s2_tilejson::Encoding;
#[cfg(feature = "std")]
use std::io::{Read, Write};
#[derive(Debug, PartialEq)]
pub enum CompressError {
UnimplementedBrotli,
UnimplementedZstd,
FFlate(FFlateError),
BadZipFormat,
ZipMultiDiskNotSupported,
InvalidCompressionMethod,
ReadError,
WriteError,
Other,
}
impl From<FFlateError> for CompressError {
fn from(err: FFlateError) -> Self {
CompressError::FFlate(err)
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub enum CompressionFormat {
#[default]
None = 1,
Gzip = 2,
Brotli = 3,
Zstd = 4,
Deflate = 5,
DeflateRaw = 6,
}
impl From<u8> for CompressionFormat {
fn from(value: u8) -> Self {
match value {
2 => CompressionFormat::Gzip,
3 => CompressionFormat::Brotli,
4 => CompressionFormat::Zstd,
5 => CompressionFormat::Deflate,
6 => CompressionFormat::DeflateRaw,
_ => CompressionFormat::None,
}
}
}
impl From<&Encoding> for CompressionFormat {
fn from(encoding: &Encoding) -> Self {
match encoding {
Encoding::Gzip => CompressionFormat::Gzip,
Encoding::Brotli => CompressionFormat::Brotli,
Encoding::Zstd => CompressionFormat::Zstd,
_ => CompressionFormat::None,
}
}
}
impl From<CompressionFormat> for u8 {
fn from(compression: CompressionFormat) -> Self {
match compression {
CompressionFormat::None => 1,
CompressionFormat::Gzip => 2,
CompressionFormat::Brotli => 3,
CompressionFormat::Zstd => 4,
CompressionFormat::Deflate => 5,
CompressionFormat::DeflateRaw => 6,
}
}
}
impl From<&str> for CompressionFormat {
fn from(s: &str) -> Self {
match s {
"gzip" => CompressionFormat::Gzip,
"deflate" => CompressionFormat::Deflate,
"deflate-raw" => CompressionFormat::DeflateRaw,
"brotli" => CompressionFormat::Brotli,
"zstd" => CompressionFormat::Zstd,
_ => CompressionFormat::None,
}
}
}
impl From<CompressionFormat> for String {
fn from(s: CompressionFormat) -> Self {
match s {
CompressionFormat::None => "none".into(),
CompressionFormat::Gzip => "gzip".into(),
CompressionFormat::Deflate => "deflate".into(),
CompressionFormat::DeflateRaw => "deflate-raw".into(),
CompressionFormat::Brotli => "brotli".into(),
CompressionFormat::Zstd => "zstd".into(),
}
}
}
#[cfg(feature = "std")]
pub fn compress_data(input: Vec<u8>, format: CompressionFormat) -> Result<Vec<u8>, CompressError> {
let mut output = Vec::new();
match format {
CompressionFormat::None => output = input,
CompressionFormat::Gzip => {
let mut encoder = GzEncoder::new(&mut output, Compression::default());
encoder.write_all(&input).map_err(|_| CompressError::WriteError)?;
encoder.finish().map_err(|_| CompressError::WriteError)?;
}
CompressionFormat::Deflate => {
let mut encoder = DeflateEncoder::new(&mut output, Compression::default());
encoder.write_all(&input).map_err(|_| CompressError::WriteError)?;
encoder.finish().map_err(|_| CompressError::WriteError)?;
}
CompressionFormat::DeflateRaw => {
let mut encoder = ZlibEncoder::new(&mut output, Compression::default());
encoder.write_all(&input).map_err(|_| CompressError::WriteError)?;
encoder.finish().map_err(|_| CompressError::WriteError)?;
}
CompressionFormat::Brotli => {
let mut encoder = brotli::CompressorWriter::new(&mut output, 4096, 11, 22);
encoder.write_all(&input).map_err(|_| CompressError::WriteError)?;
}
CompressionFormat::Zstd => return Err(CompressError::UnimplementedZstd),
}
Ok(output)
}
#[cfg(feature = "std")]
pub fn decompress_data(input: &[u8], format: CompressionFormat) -> Result<Vec<u8>, CompressError> {
let mut output = Vec::new();
match format {
CompressionFormat::None => output.extend_from_slice(input),
CompressionFormat::Gzip => {
let mut decoder = GzDecoder::new(input);
decoder.read_to_end(&mut output).map_err(|_| CompressError::ReadError)?;
}
CompressionFormat::Deflate => {
let mut decoder = DeflateDecoder::new(input);
decoder.read_to_end(&mut output).map_err(|_| CompressError::ReadError)?;
}
CompressionFormat::DeflateRaw => {
let mut decoder = ZlibDecoder::new(input);
decoder.read_to_end(&mut output).map_err(|_| CompressError::ReadError)?;
}
CompressionFormat::Brotli => {
let mut decoder = brotli::Decompressor::new(input, 4096);
_ = decoder.read_to_end(&mut output);
}
CompressionFormat::Zstd => return Err(CompressError::UnimplementedZstd),
}
Ok(output)
}
#[cfg(not(feature = "std"))]
pub fn compress_data(
_input: Vec<u8>,
_format: CompressionFormat,
) -> Result<Vec<u8>, CompressError> {
unimplemented!();
}
#[cfg(not(feature = "std"))]
pub fn decompress_data(input: &[u8], format: CompressionFormat) -> Result<Vec<u8>, CompressError> {
let mut output = Vec::new();
match format {
CompressionFormat::None => output.extend_from_slice(input),
CompressionFormat::Gzip | CompressionFormat::Deflate | CompressionFormat::DeflateRaw => {
output = decompress_fflate(input, None)?;
}
CompressionFormat::Brotli => {
return Err(CompressError::UnimplementedBrotli);
}
CompressionFormat::Zstd => {
return Err(CompressError::UnimplementedZstd);
}
}
Ok(output)
}
#[allow(missing_debug_implementations)]
pub struct ZipItem<'a> {
pub filename: String,
pub comment: String,
pub read: Box<dyn Fn() -> Result<Vec<u8>, CompressError> + Send + Sync + 'a>,
}
pub fn iter_zip_folder(raw: &[u8]) -> Result<Vec<ZipItem<'_>>, CompressError> {
let mut at = find_end_central_directory(raw)? as u64;
let mut items = Vec::new();
let reader: BufferReader = raw.into();
let file_count = reader.uint16_le(Some(10 + at));
if file_count != reader.uint16_le(Some(8 + at)) {
return Err(CompressError::ZipMultiDiskNotSupported);
}
let central_directory_start = reader.uint32_le(Some(16 + at));
at = central_directory_start as u64;
for _ in 0..file_count {
let compression_method = reader.uint16_le(Some(10 + at)) as usize;
let filename_length = reader.uint16_le(Some(28 + at)) as usize;
let extra_fields_length = reader.uint16_le(Some(30 + at)) as usize;
let comment_length = reader.uint16_le(Some(32 + at)) as usize;
let compressed_size = reader.uint32_le(Some(20 + at)) as usize;
let local_entry_at = reader.uint32_le(Some(42 + at)) as usize;
let filename =
String::from_utf8_lossy(&raw[(at + 46) as usize..(at as usize + 46 + filename_length)])
.to_string();
let comment = String::from_utf8_lossy(
&raw[at as usize + 46 + filename_length + extra_fields_length
..at as usize + 46 + filename_length + extra_fields_length + comment_length],
)
.to_string();
let central_entry_size = 46 + filename_length + extra_fields_length + comment_length;
let next_central_directory_entry = at + central_entry_size as u64;
at = local_entry_at as u64;
let bytes_start = at as usize
+ 30
+ reader.uint16_le(Some(26 + at)) as usize
+ reader.uint16_le(Some(28 + at)) as usize;
let bytes_end = bytes_start + compressed_size;
let bytes = &raw[bytes_start..bytes_end];
let read_fn = Box::new(move || {
if compression_method & 8 > 0 {
decompress_fflate(bytes, None).map_err(|_| CompressError::ReadError)
} else if compression_method > 0 {
Err(CompressError::InvalidCompressionMethod)
} else {
Ok(bytes.to_vec())
}
});
items.push(ZipItem { filename, comment, read: read_fn });
at = next_central_directory_entry;
}
Ok(items)
}
fn find_end_central_directory(raw: &[u8]) -> Result<usize, CompressError> {
let mut search = raw.len() - 20;
let bounds = if search > 65516 { usize::max(search - 65516, 2) } else { 2 };
while search > bounds {
if raw[search..search + 4] == [0x50, 0x4b, 0x05, 0x06] {
return Ok(search);
}
search = raw[..search].iter().rposition(|&x| x == 0x50).unwrap_or(bounds);
}
Err(CompressError::BadZipFormat)
}