diff --git a/src/pool.rs b/src/pool.rs index 6b50ebd..fb26c39 100644 --- a/src/pool.rs +++ b/src/pool.rs @@ -302,8 +302,12 @@ impl> Read for PoolReturnRead { #[cfg(test)] mod tests { - use crate::stream::remote_addr_for_test; + use std::io; + + use crate::response::Compression; + use crate::stream::{remote_addr_for_test, Stream}; use crate::ReadWrite; + use chunked_transfer::Decoder as ChunkDecoder; use super::*; @@ -437,4 +441,55 @@ mod tests { assert_eq!(agent.state.pool.len(), 1); } + + // Test that a stream gets returned to the pool if it is gzip encoded and the gzip + // decoder reads the exact amount from a chunked stream, not past the 0. This + // happens because gzip has built-in knowledge of the length to read. + #[test] + fn read_exact_chunked_gzip() { + let gz_body = vec![ + b'E', b'\r', b'\n', // 14 first chunk + 0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0xCB, 0x48, 0xCD, 0xC9, + b'\r', b'\n', // + b'E', b'\r', b'\n', // 14 second chunk + 0xC9, 0x57, 0x28, 0xCF, 0x2F, 0xCA, 0x49, 0x51, 0xC8, 0x18, 0xBC, 0x6C, 0x00, 0xA5, + b'\r', b'\n', // + b'7', b'\r', b'\n', // 7 third chunk + 0x5C, 0x7C, 0xEF, 0xA7, 0x00, 0x00, 0x00, // + b'\r', b'\n', // + // end + b'0', b'\r', b'\n', // + b'\r', b'\n', // + ]; + + println!("{:?}", gz_body); + + impl ReadWrite for io::Cursor> { + fn socket(&self) -> Option<&std::net::TcpStream> { + None + } + } + + impl From>> for Stream { + fn from(c: io::Cursor>) -> Self { + Stream::new(c, "1.1.1.1:8080".parse().unwrap()) + } + } + + let agent = Agent::new(); + let url = Url::parse("https://example.com").unwrap(); + + assert_eq!(agent.state.pool.len(), 0); + + let chunked = ChunkDecoder::new(io::Cursor::new(gz_body)); + let pool_return_read: Box<(dyn Read + Send + Sync + 'static)> = + Box::new(PoolReturnRead::new(&agent, &url, chunked)); + + let compression = Compression::Gzip; + let mut stream = compression.wrap_reader(pool_return_read); + + io::copy(&mut stream, &mut io::sink()).unwrap(); + + assert_eq!(agent.state.pool.len(), 1); + } } diff --git a/src/response.rs b/src/response.rs index 7f66744..7335417 100644 --- a/src/response.rs +++ b/src/response.rs @@ -571,7 +571,7 @@ impl Response { } #[derive(Copy, Clone, Debug, PartialEq, Eq)] -enum Compression { +pub(crate) enum Compression { #[cfg(feature = "brotli")] Brotli, #[cfg(feature = "gzip")] @@ -592,7 +592,7 @@ impl Compression { /// Wrap the raw reader with a decompressing reader #[allow(unused_variables)] // when no features enabled, reader is unused (unreachable) - fn wrap_reader( + pub(crate) fn wrap_reader( self, reader: Box, ) -> Box {