use std::cmp;
use std::io;
use std::io::prelude::*;
use super::{corrupt, GzBuilder, GzHeader, GzHeaderParser};
use crate::crc::{Crc, CrcWriter};
use crate::zio;
use crate::{Compress, Compression, Decompress, Status};
#[derive(Debug)]
pub struct GzEncoder<W: Write> {
inner: zio::Writer<W, Compress>,
crc: Crc,
crc_bytes_written: usize,
header: Vec<u8>,
}
pub fn gz_encoder<W: Write>(header: Vec<u8>, w: W, lvl: Compression) -> GzEncoder<W> {
GzEncoder {
inner: zio::Writer::new(w, Compress::new(lvl, false)),
crc: Crc::new(),
header,
crc_bytes_written: 0,
}
}
impl<W: Write> GzEncoder<W> {
pub fn new(w: W, level: Compression) -> GzEncoder<W> {
GzBuilder::new().write(w, level)
}
pub fn get_ref(&self) -> &W {
self.inner.get_ref()
}
pub fn get_mut(&mut self) -> &mut W {
self.inner.get_mut()
}
pub fn try_finish(&mut self) -> io::Result<()> {
self.write_header()?;
self.inner.finish()?;
while self.crc_bytes_written < 8 {
let (sum, amt) = (self.crc.sum(), self.crc.amount());
let buf = [
sum as u8,
(sum >> 8) as u8,
(sum >> 16) as u8,
(sum >> 24) as u8,
amt as u8,
(amt >> 8) as u8,
(amt >> 16) as u8,
(amt >> 24) as u8,
];
let inner = self.inner.get_mut();
let n = inner.write(&buf[self.crc_bytes_written..])?;
self.crc_bytes_written += n;
}
Ok(())
}
pub fn finish(mut self) -> io::Result<W> {
self.try_finish()?;
Ok(self.inner.take_inner())
}
fn write_header(&mut self) -> io::Result<()> {
while !self.header.is_empty() {
let n = self.inner.get_mut().write(&self.header)?;
self.header.drain(..n);
}
Ok(())
}
}
impl<W: Write> Write for GzEncoder<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
assert_eq!(self.crc_bytes_written, 0);
self.write_header()?;
let n = self.inner.write(buf)?;
self.crc.update(&buf[..n]);
Ok(n)
}
fn flush(&mut self) -> io::Result<()> {
assert_eq!(self.crc_bytes_written, 0);
self.write_header()?;
self.inner.flush()
}
}
impl<R: Read + Write> Read for GzEncoder<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.get_mut().read(buf)
}
}
impl<W: Write> Drop for GzEncoder<W> {
fn drop(&mut self) {
if self.inner.is_present() {
let _ = self.try_finish();
}
}
}
#[derive(Debug)]
pub struct GzDecoder<W: Write> {
inner: zio::Writer<CrcWriter<W>, Decompress>,
crc_bytes: Vec<u8>,
header_parser: GzHeaderParser,
}
const CRC_BYTES_LEN: usize = 8;
impl<W: Write> GzDecoder<W> {
pub fn new(w: W) -> GzDecoder<W> {
GzDecoder {
inner: zio::Writer::new(CrcWriter::new(w), Decompress::new(false)),
crc_bytes: Vec::with_capacity(CRC_BYTES_LEN),
header_parser: GzHeaderParser::new(),
}
}
pub fn header(&self) -> Option<&GzHeader> {
self.header_parser.header()
}
pub fn get_ref(&self) -> &W {
self.inner.get_ref().get_ref()
}
pub fn get_mut(&mut self) -> &mut W {
self.inner.get_mut().get_mut()
}
pub fn try_finish(&mut self) -> io::Result<()> {
self.finish_and_check_crc()?;
Ok(())
}
pub fn finish(mut self) -> io::Result<W> {
self.finish_and_check_crc()?;
Ok(self.inner.take_inner().into_inner())
}
fn finish_and_check_crc(&mut self) -> io::Result<()> {
self.inner.finish()?;
if self.crc_bytes.len() != 8 {
return Err(corrupt());
}
let crc = (self.crc_bytes[0] as u32)
| ((self.crc_bytes[1] as u32) << 8)
| ((self.crc_bytes[2] as u32) << 16)
| ((self.crc_bytes[3] as u32) << 24);
let amt = (self.crc_bytes[4] as u32)
| ((self.crc_bytes[5] as u32) << 8)
| ((self.crc_bytes[6] as u32) << 16)
| ((self.crc_bytes[7] as u32) << 24);
if crc != self.inner.get_ref().crc().sum() {
return Err(corrupt());
}
if amt != self.inner.get_ref().crc().amount() {
return Err(corrupt());
}
Ok(())
}
}
impl<W: Write> Write for GzDecoder<W> {
fn write(&mut self, mut buf: &[u8]) -> io::Result<usize> {
let buflen = buf.len();
if self.header().is_none() {
match self.header_parser.parse(&mut buf) {
Err(err) => {
if err.kind() == io::ErrorKind::UnexpectedEof {
Ok(buflen)
} else {
Err(err)
}
}
Ok(_) => {
debug_assert!(self.header().is_some());
let n = buflen - buf.len();
Ok(n)
}
}
} else {
let (n, status) = self.inner.write_with_status(buf)?;
if status == Status::StreamEnd && n < buf.len() && self.crc_bytes.len() < 8 {
let remaining = buf.len() - n;
let crc_bytes = cmp::min(remaining, CRC_BYTES_LEN - self.crc_bytes.len());
self.crc_bytes.extend(&buf[n..n + crc_bytes]);
return Ok(n + crc_bytes);
}
Ok(n)
}
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
impl<W: Read + Write> Read for GzDecoder<W> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.get_mut().get_mut().read(buf)
}
}
#[derive(Debug)]
pub struct MultiGzDecoder<W: Write> {
inner: GzDecoder<W>,
}
impl<W: Write> MultiGzDecoder<W> {
pub fn new(w: W) -> MultiGzDecoder<W> {
MultiGzDecoder {
inner: GzDecoder::new(w),
}
}
pub fn header(&self) -> Option<&GzHeader> {
self.inner.header()
}
pub fn get_ref(&self) -> &W {
self.inner.get_ref()
}
pub fn get_mut(&mut self) -> &mut W {
self.inner.get_mut()
}
pub fn try_finish(&mut self) -> io::Result<()> {
self.inner.try_finish()
}
pub fn finish(self) -> io::Result<W> {
self.inner.finish()
}
}
impl<W: Write> Write for MultiGzDecoder<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if buf.is_empty() {
Ok(0)
} else {
match self.inner.write(buf) {
Ok(0) => {
self.inner.try_finish()?;
let w = self.inner.inner.take_inner().into_inner();
self.inner = GzDecoder::new(w);
self.inner.write(buf)
}
res => res,
}
}
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
#[cfg(test)]
mod tests {
use super::*;
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World";
#[test]
fn decode_writer_one_chunk() {
let mut e = GzEncoder::new(Vec::new(), Compression::default());
e.write_all(STR.as_ref()).unwrap();
let bytes = e.finish().unwrap();
let mut writer = Vec::new();
let mut decoder = GzDecoder::new(writer);
let n = decoder.write(&bytes[..]).unwrap();
decoder.write_all(&bytes[n..]).unwrap();
decoder.try_finish().unwrap();
writer = decoder.finish().unwrap();
let return_string = String::from_utf8(writer).expect("String parsing error");
assert_eq!(return_string, STR);
}
#[test]
fn decode_writer_partial_header() {
let mut e = GzEncoder::new(Vec::new(), Compression::default());
e.write_all(STR.as_ref()).unwrap();
let bytes = e.finish().unwrap();
let mut writer = Vec::new();
let mut decoder = GzDecoder::new(writer);
assert_eq!(decoder.write(&bytes[..5]).unwrap(), 5);
let n = decoder.write(&bytes[5..]).unwrap();
if n < bytes.len() - 5 {
decoder.write_all(&bytes[n + 5..]).unwrap();
}
writer = decoder.finish().unwrap();
let return_string = String::from_utf8(writer).expect("String parsing error");
assert_eq!(return_string, STR);
}
#[test]
fn decode_writer_partial_header_filename() {
let filename = "test.txt";
let mut e = GzBuilder::new()
.filename(filename)
.read(STR.as_bytes(), Compression::default());
let mut bytes = Vec::new();
e.read_to_end(&mut bytes).unwrap();
let mut writer = Vec::new();
let mut decoder = GzDecoder::new(writer);
assert_eq!(decoder.write(&bytes[..12]).unwrap(), 12);
let n = decoder.write(&bytes[12..]).unwrap();
if n < bytes.len() - 12 {
decoder.write_all(&bytes[n + 12..]).unwrap();
}
assert_eq!(
decoder.header().unwrap().filename().unwrap(),
filename.as_bytes()
);
writer = decoder.finish().unwrap();
let return_string = String::from_utf8(writer).expect("String parsing error");
assert_eq!(return_string, STR);
}
#[test]
fn decode_writer_partial_header_comment() {
let comment = "test comment";
let mut e = GzBuilder::new()
.comment(comment)
.read(STR.as_bytes(), Compression::default());
let mut bytes = Vec::new();
e.read_to_end(&mut bytes).unwrap();
let mut writer = Vec::new();
let mut decoder = GzDecoder::new(writer);
assert_eq!(decoder.write(&bytes[..12]).unwrap(), 12);
let n = decoder.write(&bytes[12..]).unwrap();
if n < bytes.len() - 12 {
decoder.write_all(&bytes[n + 12..]).unwrap();
}
assert_eq!(
decoder.header().unwrap().comment().unwrap(),
comment.as_bytes()
);
writer = decoder.finish().unwrap();
let return_string = String::from_utf8(writer).expect("String parsing error");
assert_eq!(return_string, STR);
}
#[test]
fn decode_writer_exact_header() {
let mut e = GzEncoder::new(Vec::new(), Compression::default());
e.write_all(STR.as_ref()).unwrap();
let bytes = e.finish().unwrap();
let mut writer = Vec::new();
let mut decoder = GzDecoder::new(writer);
assert_eq!(decoder.write(&bytes[..10]).unwrap(), 10);
decoder.write_all(&bytes[10..]).unwrap();
writer = decoder.finish().unwrap();
let return_string = String::from_utf8(writer).expect("String parsing error");
assert_eq!(return_string, STR);
}
#[test]
fn decode_writer_partial_crc() {
let mut e = GzEncoder::new(Vec::new(), Compression::default());
e.write_all(STR.as_ref()).unwrap();
let bytes = e.finish().unwrap();
let mut writer = Vec::new();
let mut decoder = GzDecoder::new(writer);
let l = bytes.len() - 5;
let n = decoder.write(&bytes[..l]).unwrap();
decoder.write_all(&bytes[n..]).unwrap();
writer = decoder.finish().unwrap();
let return_string = String::from_utf8(writer).expect("String parsing error");
assert_eq!(return_string, STR);
}
#[test]
fn decode_multi_writer() {
let mut e = GzEncoder::new(Vec::new(), Compression::default());
e.write_all(STR.as_ref()).unwrap();
let bytes = e.finish().unwrap().repeat(2);
let mut writer = Vec::new();
let mut decoder = MultiGzDecoder::new(writer);
let mut count = 0;
while count < bytes.len() {
let n = decoder.write(&bytes[count..]).unwrap();
assert!(n != 0);
count += n;
}
writer = decoder.finish().unwrap();
let return_string = String::from_utf8(writer).expect("String parsing error");
let expected = STR.repeat(2);
assert_eq!(return_string, expected);
}
#[test]
fn decode_extra_data() {
let compressed = {
let mut e = GzEncoder::new(Vec::new(), Compression::default());
e.write_all(STR.as_ref()).unwrap();
let mut b = e.finish().unwrap();
b.push(b'x');
b
};
let mut writer = Vec::new();
let mut decoder = GzDecoder::new(writer);
let mut consumed_bytes = 0;
loop {
let n = decoder.write(&compressed[consumed_bytes..]).unwrap();
if n == 0 {
break;
}
consumed_bytes += n;
}
writer = decoder.finish().unwrap();
let actual = String::from_utf8(writer).expect("String parsing error");
assert_eq!(actual, STR);
assert_eq!(&compressed[consumed_bytes..], b"x");
}
}