#[cfg(test)]
mod tests;
use std::{
fmt,
io::{self, BufRead, Read},
};
#[derive(Debug)]
pub enum GzipReadError<E> {
Input(io::Error),
Decode(io::Error),
InputLimit {
limit: u64,
},
DecodedLimit {
limit: u64,
},
TrailingData,
Consumer(E),
}
impl<E: fmt::Display> fmt::Display for GzipReadError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Input(source) => write!(f, "gzip input failed: {source}"),
Self::Decode(source) => write!(f, "gzip decoding failed: {source}"),
Self::InputLimit { limit } => write!(f, "gzip input exceeds {limit} bytes"),
Self::DecodedLimit { limit } => write!(f, "gzip payload exceeds {limit} bytes"),
Self::TrailingData => f.write_str("bytes follow the single gzip member"),
Self::Consumer(source) => write!(f, "gzip consumer failed: {source}"),
}
}
}
impl<E: std::error::Error + 'static> std::error::Error for GzipReadError<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Input(source) | Self::Decode(source) => Some(source),
Self::Consumer(source) => Some(source),
_ => None,
}
}
}
pub fn with_gzip_reader<R: BufRead, T, E>(
input: R,
max_compressed_bytes: u64,
max_decoded_bytes: u64,
consume: impl FnOnce(&mut dyn Read) -> Result<T, E>,
) -> Result<T, GzipReadError<E>> {
let mut reader = Payload {
decoder: flate2::bufread::GzDecoder::new(Input {
inner: input,
remaining: max_compressed_bytes,
limit: max_compressed_bytes,
failure: None,
}),
remaining: max_decoded_bytes,
limit: max_decoded_bytes,
failure: None,
};
let result = consume(&mut reader);
if let Some(error) = reader.take_failure() {
return Err(error);
}
let result = result.map_err(GzipReadError::Consumer)?;
let mut scratch = [0; 16 * 1024];
loop {
match reader.read(&mut scratch) {
Ok(0) => break,
Ok(_) => {}
Err(error) => {
return Err(reader
.take_failure()
.unwrap_or(GzipReadError::Decode(error)));
}
}
}
loop {
match reader.decoder.get_mut().fill_buf() {
Ok([]) => return Ok(result),
Ok(_) => return Err(GzipReadError::TrailingData),
Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
Err(error) => return Err(reader.take_failure().unwrap_or(GzipReadError::Input(error))),
}
}
}
enum Failure {
Input(io::Error),
Decode(io::Error),
InputLimit(u64),
DecodedLimit(u64),
}
impl Failure {
fn into_error<E>(self) -> GzipReadError<E> {
match self {
Self::Input(source) => GzipReadError::Input(source),
Self::Decode(source) => GzipReadError::Decode(source),
Self::InputLimit(limit) => GzipReadError::InputLimit { limit },
Self::DecodedLimit(limit) => GzipReadError::DecodedLimit { limit },
}
}
}
fn failed_read() -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, "bounded gzip traversal failed")
}
struct Input<R> {
inner: R,
remaining: u64,
limit: u64,
failure: Option<Failure>,
}
impl<R: BufRead> Read for Input<R> {
fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
if buffer.is_empty() {
return Ok(0);
}
let available = self.fill_buf()?;
let count = available.len().min(buffer.len());
buffer[..count].copy_from_slice(&available[..count]);
self.consume(count);
Ok(count)
}
}
impl<R: BufRead> BufRead for Input<R> {
fn fill_buf(&mut self) -> io::Result<&[u8]> {
if self.failure.is_some() {
return Err(failed_read());
}
let available = match self.inner.fill_buf() {
Ok(bytes) => bytes,
Err(error) if error.kind() == io::ErrorKind::Interrupted => return Err(error),
Err(error) => {
self.failure = Some(Failure::Input(error));
return Err(failed_read());
}
};
if self.remaining == 0 && !available.is_empty() {
self.failure = Some(Failure::InputLimit(self.limit));
return Err(failed_read());
}
let count = available
.len()
.min(usize::try_from(self.remaining).unwrap_or(usize::MAX));
Ok(&available[..count])
}
fn consume(&mut self, amount: usize) {
self.inner.consume(amount);
self.remaining -= amount as u64;
}
}
struct Payload<R: BufRead> {
decoder: flate2::bufread::GzDecoder<Input<R>>,
remaining: u64,
limit: u64,
failure: Option<Failure>,
}
impl<R: BufRead> Payload<R> {
fn take_failure<E>(&mut self) -> Option<GzipReadError<E>> {
self.failure
.take()
.or_else(|| self.decoder.get_mut().failure.take())
.map(Failure::into_error)
}
}
impl<R: BufRead> Read for Payload<R> {
fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
if buffer.is_empty() {
return Ok(0);
}
if self.failure.is_some() || self.decoder.get_mut().failure.is_some() {
return Err(failed_read());
}
loop {
let mut probe = [0];
let allowance = buffer
.len()
.min(usize::try_from(self.remaining).unwrap_or(usize::MAX));
let output = if allowance == 0 {
&mut probe[..]
} else {
&mut buffer[..allowance]
};
match self.decoder.read(output) {
Ok(count) if count > allowance => {
self.failure = Some(Failure::DecodedLimit(self.limit));
return Err(failed_read());
}
Ok(count) => {
self.remaining -= count as u64;
return Ok(count);
}
Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
Err(error) => {
self.failure = Some(
self.decoder
.get_mut()
.failure
.take()
.unwrap_or(Failure::Decode(error)),
);
return Err(failed_read());
}
}
}
}
}