From dedb17be4eb1a970594ae8acaa1c983f0513871c Mon Sep 17 00:00:00 2001 From: sgasho Date: Sat, 14 Jun 2025 10:05:58 +0900 Subject: [PATCH 1/2] fix: accept an empty gzip as a valid input --- src/codec/gzip/decoder.rs | 28 +++++++++++++++++----------- src/codec/gzip/header.rs | 7 +++++++ src/util.rs | 4 ++++ tests/gzip.rs | 28 ++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/codec/gzip/decoder.rs b/src/codec/gzip/decoder.rs index 4bb42606..3e2d0921 100644 --- a/src/codec/gzip/decoder.rs +++ b/src/codec/gzip/decoder.rs @@ -5,9 +5,9 @@ use crate::{ }, util::PartialBuffer, }; -use std::io::{Error, ErrorKind, Result}; - use flate2::Crc; +use std::io::{Error, ErrorKind, Read, Result}; +use std::ops::Deref; #[derive(Debug)] enum State { @@ -163,16 +163,22 @@ impl Decode for GzipDecoder { fn finish( &mut self, - _output: &mut PartialBuffer + AsMut<[u8]>>, + output: &mut PartialBuffer + AsMut<[u8]>>, ) -> Result { - // Because of the footer we have to have already flushed all the data out before we get here - if let State::Done = self.state { - Ok(true) - } else { - Err(Error::new( - ErrorKind::UnexpectedEof, - "unexpected end of file", - )) + match &mut self.state { + State::Done => return Ok(true), + State::Header(parser) => { + // In this case, the input was an empty gzip. Exit successfully with an empty gzip. + if parser.has_no_content() { + return Ok(true); + } + } + _ => {} } + + Err(Error::new( + ErrorKind::UnexpectedEof, + "unexpected end of file", + )) } } diff --git a/src/codec/gzip/header.rs b/src/codec/gzip/header.rs index ebe482af..28e108f0 100644 --- a/src/codec/gzip/header.rs +++ b/src/codec/gzip/header.rs @@ -161,4 +161,11 @@ impl Parser { }; } } + + pub(super) fn has_no_content(&self) -> bool { + match &self.state { + State::Fixed(data) => data.buffer().iter().all(|&b| b == 0), + _ => false, + } + } } diff --git a/src/util.rs b/src/util.rs index a00bce2d..c34611c7 100644 --- a/src/util.rs +++ b/src/util.rs @@ -20,6 +20,10 @@ impl> PartialBuffer { &self.buffer.as_ref()[self.index..] } + pub(crate) fn buffer(&self) -> &[u8] { + self.buffer.as_ref() + } + pub(crate) fn advance(&mut self, amount: usize) { self.index += amount; } diff --git a/tests/gzip.rs b/tests/gzip.rs index d9cd4eff..fc51167b 100644 --- a/tests/gzip.rs +++ b/tests/gzip.rs @@ -6,6 +6,9 @@ test_cases!(gzip); #[allow(unused)] use utils::{algos::gzip::sync, InputStream}; +#[allow(unused)] +use ntest::assert_true; + #[cfg(feature = "futures-io")] use utils::algos::gzip::futures::bufread; @@ -51,3 +54,28 @@ fn gzip_bufread_chunks_decompress_with_extra_header() { assert_eq!(output, &[1, 2, 3, 4, 5, 6][..]); } + +#[test] +#[ntest::timeout(1000)] +#[cfg(feature = "futures-io")] +fn gzip_empty() { + let bytes = Vec::new(); + + let input = InputStream::from(bytes.chunks(2)); + let output = bufread::decompress(bufread::from(&input)); + + assert_eq!(output, &[][..]); +} + +#[test] +#[ntest::timeout(1000)] +#[cfg(feature = "futures-io")] +fn invalid_gzip() { + let bytes = [0, 0, 0, 1, 0, 0]; + + let input = InputStream::from(bytes.chunks(2)); + + let result = std::panic::catch_unwind(|| bufread::decompress(bufread::from(&input))); + + assert_true!(result.is_err()); +} From ccdb74d0266f4a969334b16de8fa205b9edee010 Mon Sep 17 00:00:00 2001 From: sgasho Date: Sun, 15 Jun 2025 00:18:17 +0900 Subject: [PATCH 2/2] refactor: simplify expressions --- src/codec/gzip/decoder.rs | 20 +++++++------------- src/codec/gzip/header.rs | 5 +---- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/codec/gzip/decoder.rs b/src/codec/gzip/decoder.rs index 3e2d0921..ee5715b6 100644 --- a/src/codec/gzip/decoder.rs +++ b/src/codec/gzip/decoder.rs @@ -166,19 +166,13 @@ impl Decode for GzipDecoder { output: &mut PartialBuffer + AsMut<[u8]>>, ) -> Result { match &mut self.state { - State::Done => return Ok(true), - State::Header(parser) => { - // In this case, the input was an empty gzip. Exit successfully with an empty gzip. - if parser.has_no_content() { - return Ok(true); - } - } - _ => {} + State::Done => Ok(true), + // In this case, the input was an empty gzip. Exit successfully with an empty gzip. + State::Header(parser) if parser.has_no_content() => Ok(true), + _ => Err(Error::new( + ErrorKind::UnexpectedEof, + "unexpected end of file", + )), } - - Err(Error::new( - ErrorKind::UnexpectedEof, - "unexpected end of file", - )) } } diff --git a/src/codec/gzip/header.rs b/src/codec/gzip/header.rs index 28e108f0..70b1399f 100644 --- a/src/codec/gzip/header.rs +++ b/src/codec/gzip/header.rs @@ -163,9 +163,6 @@ impl Parser { } pub(super) fn has_no_content(&self) -> bool { - match &self.state { - State::Fixed(data) => data.buffer().iter().all(|&b| b == 0), - _ => false, - } + matches!(&self.state, State::Fixed(data) if data.buffer().iter().all(|&b| b == 0)) } }