using System;
using System.IO;
using System.Security.Cryptography;
using ICSharpCode.SharpZipLib.Encryption;
namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams
{
///
/// A special stream deflating or compressing the bytes that are
/// written to it. It uses a Deflater to perform actual deflating.
/// Authors of the original java version : Tom Tromey, Jochen Hoenicke
///
public class DeflaterOutputStream : Stream
{
#region Constructors
///
/// Creates a new DeflaterOutputStream with a default Deflater and default buffer size.
///
///
/// the output stream where deflated output should be written.
///
public DeflaterOutputStream(Stream baseOutputStream)
: this(baseOutputStream, new Deflater(), 512)
{
}
///
/// Creates a new DeflaterOutputStream with the given Deflater and
/// default buffer size.
///
///
/// the output stream where deflated output should be written.
///
///
/// the underlying deflater.
///
public DeflaterOutputStream(Stream baseOutputStream, Deflater deflater)
: this(baseOutputStream, deflater, 512)
{
}
///
/// Creates a new DeflaterOutputStream with the given Deflater and
/// buffer size.
///
///
/// The output stream where deflated output is written.
///
///
/// The underlying deflater to use
///
///
/// The buffer size in bytes to use when deflating (minimum value 512)
///
///
/// bufsize is less than or equal to zero.
///
///
/// baseOutputStream does not support writing
///
///
/// deflater instance is null
///
public DeflaterOutputStream(Stream baseOutputStream, Deflater deflater, int bufferSize)
{
if (baseOutputStream == null) {
throw new ArgumentNullException("nameof(baseOutputStream)");
}
if (baseOutputStream.CanWrite == false) {
throw new ArgumentException("Must support writing", "nameof(baseOutputStream)");
}
if (deflater == null) {
throw new ArgumentNullException("nameof(deflater)");
}
if (bufferSize < 512) {
throw new ArgumentOutOfRangeException("nameof(bufferSize)");
}
baseOutputStream_ = baseOutputStream;
buffer_ = new byte[bufferSize];
deflater_ = deflater;
}
#endregion
#region Public API
///
/// Finishes the stream by calling finish() on the deflater.
///
///
/// Not all input is deflated
///
public virtual void Finish()
{
deflater_.Finish();
while (!deflater_.IsFinished) {
int len = deflater_.Deflate(buffer_, 0, buffer_.Length);
if (len <= 0) {
break;
}
if (cryptoTransform_ != null) {
EncryptBlock(buffer_, 0, len);
}
baseOutputStream_.Write(buffer_, 0, len);
}
if (!deflater_.IsFinished) {
throw new SharpZipBaseException("Can't deflate all input?");
}
baseOutputStream_.Flush();
if (cryptoTransform_ != null) {
if (cryptoTransform_ is ZipAESTransform) {
AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode();
}
cryptoTransform_.Dispose();
cryptoTransform_ = null;
}
}
///
/// Gets or sets a flag indicating ownership of underlying stream.
/// When the flag is true will close the underlying stream also.
///
/// The default value is true.
private bool isStreamOwner = true;
public bool IsStreamOwner
{
get { return isStreamOwner;}
set { isStreamOwner = value; }
}
///
/// Allows client to determine if an entry can be patched after its added
///
public bool CanPatchEntries {
get {
return baseOutputStream_.CanSeek;
}
}
#endregion
#region Encryption
string password;
ICryptoTransform cryptoTransform_;
///
/// Returns the 10 byte AUTH CODE to be appended immediately following the AES data stream.
///
protected byte[] AESAuthCode;
///
/// Get/set the password used for encryption.
///
/// When set to null or if the password is empty no encryption is performed
public string Password {
get {
return password;
}
set {
if ((value != null) && (value.Length == 0)) {
password = null;
} else {
password = value;
}
}
}
///
/// Encrypt a block of data
///
///
/// Data to encrypt. NOTE the original contents of the buffer are lost
///
///
/// Offset of first byte in buffer to encrypt
///
///
/// Number of bytes in buffer to encrypt
///
protected void EncryptBlock(byte[] buffer, int offset, int length)
{
cryptoTransform_.TransformBlock(buffer, 0, length, buffer, 0);
}
///
/// Initializes encryption keys based on given .
///
/// The password.
protected void InitializePassword(string password)
{
var pkManaged = new PkzipClassicManaged();
byte[] key = PkzipClassic.GenerateKeys(ZipConstants.ConvertToArray(password));
cryptoTransform_ = pkManaged.CreateEncryptor(key, null);
}
///
/// Initializes encryption keys based on given password.
///
protected void InitializeAESPassword(ZipEntry entry, string rawPassword,
out byte[] salt, out byte[] pwdVerifier)
{
salt = new byte[entry.AESSaltLen];
// Salt needs to be cryptographically random, and unique per file
if (_aesRnd == null)
_aesRnd = RandomNumberGenerator.Create();
_aesRnd.GetBytes(salt);
int blockSize = entry.AESKeySize / 8; // bits to bytes
cryptoTransform_ = new ZipAESTransform(rawPassword, salt, blockSize, true);
pwdVerifier = ((ZipAESTransform)cryptoTransform_).PwdVerifier;
}
#endregion
#region Deflation Support
///
/// Deflates everything in the input buffers. This will call
/// def.deflate()
until all bytes from the input buffers
/// are processed.
///
protected void Deflate()
{
while (!deflater_.IsNeedingInput) {
int deflateCount = deflater_.Deflate(buffer_, 0, buffer_.Length);
if (deflateCount <= 0) {
break;
}
if (cryptoTransform_ != null) {
EncryptBlock(buffer_, 0, deflateCount);
}
baseOutputStream_.Write(buffer_, 0, deflateCount);
}
if (!deflater_.IsNeedingInput) {
throw new SharpZipBaseException("DeflaterOutputStream can't deflate all input?");
}
}
#endregion
#region Stream Overrides
///
/// Gets value indicating stream can be read from
///
public override bool CanRead {
get {
return false;
}
}
///
/// Gets a value indicating if seeking is supported for this stream
/// This property always returns false
///
public override bool CanSeek {
get {
return false;
}
}
///
/// Get value indicating if this stream supports writing
///
public override bool CanWrite {
get {
return baseOutputStream_.CanWrite;
}
}
///
/// Get current length of stream
///
public override long Length {
get {
return baseOutputStream_.Length;
}
}
///
/// Gets the current position within the stream.
///
/// Any attempt to set position
public override long Position {
get {
return baseOutputStream_.Position;
}
set {
throw new NotSupportedException("Position property not supported");
}
}
///
/// Sets the current position of this stream to the given value. Not supported by this class!
///
/// The offset relative to the to seek.
/// The to seek from.
/// The new position in the stream.
/// Any access
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException("DeflaterOutputStream Seek not supported");
}
///
/// Sets the length of this stream to the given value. Not supported by this class!
///
/// The new stream length.
/// Any access
public override void SetLength(long value)
{
throw new NotSupportedException("DeflaterOutputStream SetLength not supported");
}
///
/// Read a byte from stream advancing position by one
///
/// The byte read cast to an int. THe value is -1 if at the end of the stream.
/// Any access
public override int ReadByte()
{
throw new NotSupportedException("DeflaterOutputStream ReadByte not supported");
}
///
/// Read a block of bytes from stream
///
/// The buffer to store read data in.
/// The offset to start storing at.
/// The maximum number of bytes to read.
/// The actual number of bytes read. Zero if end of stream is detected.
/// Any access
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotSupportedException("DeflaterOutputStream Read not supported");
}
///
/// Flushes the stream by calling Flush on the deflater and then
/// on the underlying stream. This ensures that all bytes are flushed.
///
public override void Flush()
{
deflater_.Flush();
Deflate();
baseOutputStream_.Flush();
}
///
/// Calls and closes the underlying
/// stream when is true.
///
protected override void Dispose(bool disposing)
{
if (!isClosed_) {
isClosed_ = true;
try {
Finish();
if (cryptoTransform_ != null) {
GetAuthCodeIfAES();
cryptoTransform_.Dispose();
cryptoTransform_ = null;
}
} finally {
if (IsStreamOwner) {
baseOutputStream_.Dispose();
}
}
}
}
private void GetAuthCodeIfAES()
{
if (cryptoTransform_ is ZipAESTransform) {
AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode();
}
}
///
/// Writes a single byte to the compressed output stream.
///
///
/// The byte value.
///
public override void WriteByte(byte value)
{
byte[] b = new byte[1];
b[0] = value;
Write(b, 0, 1);
}
///
/// Writes bytes from an array to the compressed stream.
///
///
/// The byte array
///
///
/// The offset into the byte array where to start.
///
///
/// The number of bytes to write.
///
public override void Write(byte[] buffer, int offset, int count)
{
deflater_.SetInput(buffer, offset, count);
Deflate();
}
#endregion
#region Instance Fields
///
/// This buffer is used temporarily to retrieve the bytes from the
/// deflater and write them to the underlying output stream.
///
byte[] buffer_;
///
/// The deflater which is used to deflate the stream.
///
protected Deflater deflater_;
///
/// Base stream the deflater depends on.
///
protected Stream baseOutputStream_;
bool isClosed_;
#endregion
#region Static Fields
// Static to help ensure that multiple files within a zip will get different random salt
private static RandomNumberGenerator _aesRnd = RandomNumberGenerator.Create();
#endregion
}
}