mod decode;
#[cfg(feature = "std")]
mod reader;
mod stream;
mod tables;
#[cfg(all(test, feature = "alloc"))]
mod tests;
mod wrapper;
#[cfg(feature = "std")]
pub use reader::Reader;
pub use stream::{Decoder, Lengths, Progress, Scanner};
pub const MIN_BUFFER_LEN: usize = stream::WINDOW + 1;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::fmt;
#[cfg(feature = "alloc")]
use crate::Format;
#[cfg(feature = "alloc")]
use decode::Status;
#[cfg(feature = "alloc")]
use tables::Tables;
#[cfg(feature = "alloc")]
use wrapper::{body_start, Check};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Error {
BadData,
ChecksumMismatch,
InsufficientSpace,
TrailingData,
Truncated,
PresetDictionary,
BufferFull,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Error::BadData => "invalid compressed data",
Error::ChecksumMismatch => "checksum mismatch in compressed data",
Error::InsufficientSpace => "decompressed data longer than the limit",
Error::TrailingData => "unexpected data after the compressed stream",
Error::Truncated => "compressed data ended early",
Error::PresetDictionary => "zlib stream needs a preset dictionary",
Error::BufferFull => "the buffer has no room for the stream to go on",
})
}
}
impl core::error::Error for Error {}
#[cfg(feature = "std")]
impl From<Error> for std::io::Error {
fn from(e: Error) -> Self {
let kind = match e {
Error::Truncated => std::io::ErrorKind::UnexpectedEof,
Error::BufferFull => std::io::ErrorKind::InvalidInput,
_ => std::io::ErrorKind::InvalidData,
};
std::io::Error::new(kind, e)
}
}
#[cfg(feature = "alloc")]
pub(crate) fn decompress(format: Format, input: &[u8], max_len: usize) -> Result<Vec<u8>, Error> {
VecDecoder {
format,
tables: Tables::new(),
}
.decompress(input, max_len)
}
#[cfg(feature = "alloc")]
struct VecDecoder {
format: Format,
tables: Tables,
}
#[cfg(feature = "alloc")]
impl VecDecoder {
fn decompress(&mut self, input: &[u8], max_len: usize) -> Result<Vec<u8>, Error> {
let mut out = Vec::new();
let mut rest = input;
loop {
let consumed = self.decompress_append(rest, &mut out, max_len)?;
rest = &rest[consumed..];
if rest.is_empty() {
return Ok(out);
}
if self.format != Format::Gzip {
return Err(Error::TrailingData);
}
}
}
fn decompress_append(
&mut self,
input: &[u8],
out: &mut Vec<u8>,
max_len: usize,
) -> Result<usize, Error> {
let guess = match (self.format, input.len().checked_sub(4)) {
(Format::Gzip, Some(i)) => u32::from_le_bytes(input[i..].try_into().unwrap()) as usize,
_ => input.len().saturating_mul(4).max(1024),
};
self.decompress_append_from(input, out, max_len, guess)
}
fn decompress_append_from(
&mut self,
input: &[u8],
out: &mut Vec<u8>,
max_len: usize,
guess: usize,
) -> Result<usize, Error> {
let out_start = out.len();
let r = body_start(self.format, input).and_then(|start| {
let consumed = self.inflate_growing(&input[start..], out, out_start, max_len, guess)?;
let mut check = Check::new(self.format);
check.update(&out[out_start..]);
check.verify_at(input, start + consumed)
});
if r.is_err() {
out.truncate(out_start);
}
r
}
fn inflate_growing(
&mut self,
input: &[u8],
out: &mut Vec<u8>,
start: usize,
max_len: usize,
guess: usize,
) -> Result<usize, Error> {
let room = max_len - start;
let mut len = guess.min(room);
let mut resume = None;
loop {
if out.capacity() == 0 {
*out = alloc::vec![0; len];
} else {
out.resize(start + len, 0);
}
match decode::run::<true, true>(
&mut self.tables,
input,
&mut out[start..],
len,
resume.as_ref(),
false,
)? {
Status::Done(consumed, written) => {
out.truncate(start + written);
return Ok(consumed);
}
Status::Full(_) if len == room => {
return Err(Error::InsufficientSpace);
}
Status::Full(r) => {
len = next_len(len, r.in_pos(), input.len()).min(room);
resume = Some(r);
}
}
}
}
}
#[cfg(feature = "alloc")]
fn next_len(len: usize, in_pos: usize, in_len: usize) -> usize {
let rest = (in_len - in_pos) as u128;
let estimate = if in_pos == 0 {
len
} else {
usize::try_from(len as u128 * rest / in_pos as u128).unwrap_or(usize::MAX)
};
let extra = estimate.saturating_add(estimate / 8).max(len / 4).max(1024);
len.saturating_add(extra)
}