//
// Copyright (c) 2008-2011, Kenneth Bell
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
using System;
using System.IO;
using System.IO.Compression;
using DiscUtils.Streams;
namespace DiscUtils.Compression
{
///
/// Implementation of the Zlib compression algorithm.
///
/// Only decompression is currently implemented.
public class ZlibStream : Stream
{
private readonly Adler32 _adler32;
private readonly DeflateStream _deflateStream;
private readonly CompressionMode _mode;
private readonly Stream _stream;
///
/// Initializes a new instance of the ZlibStream class.
///
/// The stream to compress of decompress.
/// Whether to compress or decompress.
/// Whether closing this stream should leave stream open.
public ZlibStream(Stream stream, CompressionMode mode, bool leaveOpen)
{
_stream = stream;
_mode = mode;
if (mode == CompressionMode.Decompress)
{
// We just sanity check against expected header values...
byte[] headerBuffer = StreamUtilities.ReadExact(stream, 2);
ushort header = EndianUtilities.ToUInt16BigEndian(headerBuffer, 0);
if (header % 31 != 0)
{
throw new IOException("Invalid Zlib header found");
}
if ((header & 0x0F00) != 8 << 8)
{
throw new NotSupportedException("Zlib compression not using DEFLATE algorithm");
}
if ((header & 0x0020) != 0)
{
throw new NotSupportedException("Zlib compression using preset dictionary");
}
}
else
{
ushort header =
(8 << 8) // DEFLATE
| (7 << 12) // 32K window size
| 0x80; // Default algorithm
header |= (ushort)(31 - header % 31);
byte[] headerBuffer = new byte[2];
EndianUtilities.WriteBytesBigEndian(header, headerBuffer, 0);
stream.Write(headerBuffer, 0, 2);
}
_deflateStream = new DeflateStream(stream, mode, leaveOpen);
_adler32 = new Adler32();
}
///
/// Gets whether the stream can be read.
///
public override bool CanRead
{
get { return _deflateStream.CanRead; }
}
///
/// Gets whether the stream pointer can be changed.
///
public override bool CanSeek
{
get { return false; }
}
///
/// Gets whether the stream can be written to.
///
public override bool CanWrite
{
get { return _deflateStream.CanWrite; }
}
///
/// Gets the length of the stream.
///
public override long Length
{
get { throw new NotSupportedException(); }
}
///
/// Gets and sets the stream position.
///
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
///
/// Closes the stream.
///
protected override void Dispose(bool disposing)
{
if (_mode == CompressionMode.Decompress)
{
// Can only check Adler checksum on seekable streams. Since DeflateStream
// aggresively caches input, it normally has already consumed the footer.
if (_stream.CanSeek)
{
_stream.Seek(-4, SeekOrigin.End);
byte[] footerBuffer = StreamUtilities.ReadExact(_stream, 4);
if (EndianUtilities.ToInt32BigEndian(footerBuffer, 0) != _adler32.Value)
{
throw new InvalidDataException("Corrupt decompressed data detected");
}
}
_deflateStream.Dispose();
}
else
{
_deflateStream.Dispose();
byte[] footerBuffer = new byte[4];
EndianUtilities.WriteBytesBigEndian(_adler32.Value, footerBuffer, 0);
_stream.Write(footerBuffer, 0, 4);
}
base.Dispose(disposing);
}
///
/// Flushes the stream.
///
public override void Flush()
{
_deflateStream.Flush();
}
///
/// Reads data from the stream.
///
/// The buffer to populate.
/// The first byte to write.
/// The number of bytes requested.
/// The number of bytes read.
public override int Read(byte[] buffer, int offset, int count)
{
CheckParams(buffer, offset, count);
int numRead = _deflateStream.Read(buffer, offset, count);
_adler32.Process(buffer, offset, numRead);
return numRead;
}
///
/// Seeks to a new position.
///
/// Relative position to seek to.
/// The origin of the seek.
/// The new position.
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
///
/// Changes the length of the stream.
///
/// The new desired length of the stream.
public override void SetLength(long value)
{
throw new NotSupportedException();
}
///
/// Writes data to the stream.
///
/// Buffer containing the data to write.
/// Offset of the first byte to write.
/// Number of bytes to write.
public override void Write(byte[] buffer, int offset, int count)
{
CheckParams(buffer, offset, count);
_adler32.Process(buffer, offset, count);
_deflateStream.Write(buffer, offset, count);
}
private static void CheckParams(byte[] buffer, int offset, int count)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (offset < 0 || offset > buffer.Length)
{
throw new ArgumentException("Offset outside of array bounds", nameof(offset));
}
if (count < 0 || offset + count > buffer.Length)
{
throw new ArgumentException("Array index out of bounds", nameof(count));
}
}
}
}