diff --git a/src/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.csproj b/src/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.csproj
index 03dcc861d..0bfe98b80 100644
--- a/src/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.csproj
+++ b/src/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.csproj
@@ -1,7 +1,7 @@
- netstandard2;net45
+ netstandard2.0;netstandard2.1;net45
True
../../assets/ICSharpCode.SharpZipLib.snk
true
@@ -40,5 +40,5 @@ Please see https://github.com/icsharpcode/SharpZipLib/wiki/Release-1.3.1 for mor
images
-
+
diff --git a/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs b/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs
index 03cac7358..f39442285 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs
@@ -2,6 +2,8 @@
using System;
using System.IO;
using System.Security.Cryptography;
+using System.Threading;
+using System.Threading.Tasks;
namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams
{
@@ -131,6 +133,50 @@ public virtual void Finish()
}
}
+ ///
+ /// Finishes the stream by calling finish() on the deflater.
+ ///
+ /// The that can be used to cancel the operation.
+ ///
+ /// Not all input is deflated
+ ///
+ public virtual async Task FinishAsync(CancellationToken cancellationToken)
+ {
+ deflater_.Finish();
+ while (!deflater_.IsFinished)
+ {
+ int len = deflater_.Deflate(buffer_, 0, buffer_.Length);
+ if (len <= 0)
+ {
+ break;
+ }
+
+ if (cryptoTransform_ != null)
+ {
+ EncryptBlock(buffer_, 0, len);
+ }
+
+ await baseOutputStream_.WriteAsync(buffer_, 0, len, cancellationToken);
+ }
+
+ if (!deflater_.IsFinished)
+ {
+ throw new SharpZipBaseException("Can't deflate all input?");
+ }
+
+ await baseOutputStream_.FlushAsync(cancellationToken);
+
+ 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.
@@ -419,6 +465,38 @@ protected override void Dispose(bool disposing)
}
}
+#if NETSTANDARD2_1
+ ///
+ /// Calls and closes the underlying
+ /// stream when is true.
+ ///
+ public override async ValueTask DisposeAsync()
+ {
+ if (!isClosed_)
+ {
+ isClosed_ = true;
+
+ try
+ {
+ await FinishAsync(CancellationToken.None);
+ if (cryptoTransform_ != null)
+ {
+ GetAuthCodeIfAES();
+ cryptoTransform_.Dispose();
+ cryptoTransform_ = null;
+ }
+ }
+ finally
+ {
+ if (IsStreamOwner)
+ {
+ await baseOutputStream_.DisposeAsync();
+ }
+ }
+ }
+ }
+#endif
+
///
/// Get the Auth code for AES encrypted entries
///
diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipHelperStream.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipHelperStream.cs
index dd7d25d94..f9c277ff2 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/ZipHelperStream.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipHelperStream.cs
@@ -1,5 +1,7 @@
using System;
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
namespace ICSharpCode.SharpZipLib.Zip
{
@@ -361,6 +363,41 @@ public void WriteZip64EndOfCentralDirectory(long noOfEntries, long sizeEntries,
WriteLEInt(1);
}
+ ///
+ /// Write Zip64 end of central directory records (File header and locator).
+ ///
+ /// The number of entries in the central directory.
+ /// The size of entries in the central directory.
+ /// The offset of the central directory.
+ /// The that can be used to cancel the operation.
+ public async Task WriteZip64EndOfCentralDirectoryAsync(long noOfEntries, long sizeEntries, long centralDirOffset, CancellationToken cancellationToken)
+ {
+ long centralSignatureOffset = centralDirOffset + sizeEntries;
+ await WriteLEIntAsync(ZipConstants.Zip64CentralFileHeaderSignature, cancellationToken);
+ await WriteLELongAsync(44, cancellationToken); // Size of this record (total size of remaining fields in header or full size - 12)
+ await WriteLEShortAsync(ZipConstants.VersionMadeBy, cancellationToken); // Version made by
+ await WriteLEShortAsync(ZipConstants.VersionZip64, cancellationToken); // Version to extract
+ await WriteLEIntAsync(0, cancellationToken); // Number of this disk
+ await WriteLEIntAsync(0, cancellationToken); // number of the disk with the start of the central directory
+ await WriteLELongAsync(noOfEntries, cancellationToken); // No of entries on this disk
+ await WriteLELongAsync(noOfEntries, cancellationToken); // Total No of entries in central directory
+ await WriteLELongAsync(sizeEntries, cancellationToken); // Size of the central directory
+ await WriteLELongAsync(centralDirOffset, cancellationToken); // offset of start of central directory
+ // zip64 extensible data sector not catered for here (variable size)
+
+ // Write the Zip64 end of central directory locator
+ await WriteLEIntAsync(ZipConstants.Zip64CentralDirLocatorSignature, cancellationToken);
+
+ // no of the disk with the start of the zip64 end of central directory
+ await WriteLEIntAsync(0, cancellationToken);
+
+ // relative offset of the zip64 end of central directory record
+ await WriteLELongAsync(centralSignatureOffset, cancellationToken);
+
+ // total number of disks
+ await WriteLEIntAsync(1, cancellationToken);
+ }
+
///
/// Write the required records to end the central directory.
///
@@ -431,6 +468,77 @@ public void WriteEndOfCentralDirectory(long noOfEntries, long sizeEntries,
}
}
+ ///
+ /// Write the required records to end the central directory.
+ ///
+ /// The number of entries in the directory.
+ /// The size of the entries in the directory.
+ /// The start of the central directory.
+ /// The archive comment. (This can be null).
+ /// The that can be used to cancel the operation.
+ public async Task WriteEndOfCentralDirectoryAsync(long noOfEntries, long sizeEntries,
+ long startOfCentralDirectory, byte[] comment, CancellationToken cancellationToken)
+ {
+ if ((noOfEntries >= 0xffff) ||
+ (startOfCentralDirectory >= 0xffffffff) ||
+ (sizeEntries >= 0xffffffff))
+ {
+ await WriteZip64EndOfCentralDirectoryAsync(noOfEntries, sizeEntries, startOfCentralDirectory, cancellationToken);
+ }
+
+ await WriteLEIntAsync(ZipConstants.EndOfCentralDirectorySignature, cancellationToken);
+
+ // TODO: ZipFile Multi disk handling not done
+ await WriteLEShortAsync(0, cancellationToken); // number of this disk
+ await WriteLEShortAsync(0, cancellationToken); // no of disk with start of central dir
+
+ // Number of entries
+ if (noOfEntries >= 0xffff)
+ {
+ await WriteLEUshortAsync(0xffff, cancellationToken); // Zip64 marker
+ await WriteLEUshortAsync(0xffff, cancellationToken);
+ }
+ else
+ {
+ await WriteLEShortAsync((short)noOfEntries, cancellationToken); // entries in central dir for this disk
+ await WriteLEShortAsync((short)noOfEntries, cancellationToken); // total entries in central directory
+ }
+
+ // Size of the central directory
+ if (sizeEntries >= 0xffffffff)
+ {
+ await WriteLEUintAsync(0xffffffff, cancellationToken); // Zip64 marker
+ }
+ else
+ {
+ await WriteLEIntAsync((int)sizeEntries, cancellationToken);
+ }
+
+ // offset of start of central directory
+ if (startOfCentralDirectory >= 0xffffffff)
+ {
+ await WriteLEUintAsync(0xffffffff, cancellationToken); // Zip64 marker
+ }
+ else
+ {
+ await WriteLEIntAsync((int)startOfCentralDirectory, cancellationToken);
+ }
+
+ int commentLength = (comment != null) ? comment.Length : 0;
+
+ if (commentLength > 0xffff)
+ {
+ throw new ZipException(string.Format("Comment length({0}) is too long can only be 64K", commentLength));
+ }
+
+ await WriteLEShortAsync(commentLength, cancellationToken);
+
+ if (commentLength > 0)
+ {
+ await WriteAsync(comment, 0, comment.Length, cancellationToken);
+ }
+ }
+
#region LE value reading/writing
///
@@ -495,6 +603,16 @@ public void WriteLEShort(int value)
stream_.WriteByte((byte)((value >> 8) & 0xff));
}
+ ///
+ /// Write an unsigned short in little endian byte order.
+ ///
+ /// The value to write.
+ /// The that can be used to cancel the operation.
+ public async Task WriteLEShortAsync(int value, CancellationToken cancellationToken)
+ {
+ await stream_.WriteAsync(new[] { (byte)(value & 0xff), (byte)((value >> 8) & 0xff) }, 0, 2, cancellationToken);
+ }
+
///
/// Write a ushort in little endian byte order.
///
@@ -505,6 +623,16 @@ public void WriteLEUshort(ushort value)
stream_.WriteByte((byte)(value >> 8));
}
+ ///
+ /// Write a ushort in little endian byte order.
+ ///
+ /// The value to write.
+ /// The that can be used to cancel the operation.
+ public async Task WriteLEUshortAsync(ushort value, CancellationToken cancellationToken)
+ {
+ await stream_.WriteAsync(new[] { (byte)(value & 0xff), (byte)(value >> 8) }, 0, 2, cancellationToken);
+ }
+
///
/// Write an int in little endian byte order.
///
@@ -515,6 +643,17 @@ public void WriteLEInt(int value)
WriteLEShort(value >> 16);
}
+ ///
+ /// Write an int in little endian byte order.
+ ///
+ /// The value to write.
+ /// The that can be used to cancel the operation.
+ public async Task WriteLEIntAsync(int value, CancellationToken cancellationToken)
+ {
+ await WriteLEShortAsync(value, cancellationToken);
+ await WriteLEShortAsync(value >> 16, cancellationToken);
+ }
+
///
/// Write a uint in little endian byte order.
///
@@ -525,6 +664,17 @@ public void WriteLEUint(uint value)
WriteLEUshort((ushort)(value >> 16));
}
+ ///
+ /// Write a uint in little endian byte order.
+ ///
+ /// The value to write.
+ /// The that can be used to cancel the operation.
+ public async Task WriteLEUintAsync(uint value, CancellationToken cancellationToken)
+ {
+ await WriteLEUshortAsync((ushort)(value & 0xffff), cancellationToken);
+ await WriteLEUshortAsync((ushort)(value >> 16), cancellationToken);
+ }
+
///
/// Write a long in little endian byte order.
///
@@ -535,6 +685,17 @@ public void WriteLELong(long value)
WriteLEInt((int)(value >> 32));
}
+ ///
+ /// Write a long in little endian byte order.
+ ///
+ /// The value to write.
+ /// The that can be used to cancel the operation.
+ public async Task WriteLELongAsync(long value, CancellationToken cancellationToken)
+ {
+ await WriteLEIntAsync((int)value, cancellationToken);
+ await WriteLEIntAsync((int)(value >> 32), cancellationToken);
+ }
+
///
/// Write a ulong in little endian byte order.
///
@@ -545,6 +706,17 @@ public void WriteLEUlong(ulong value)
WriteLEUint((uint)(value >> 32));
}
+ ///
+ /// Write a ulong in little endian byte order.
+ ///
+ /// The value to write.
+ /// The that can be used to cancel the operation.
+ public async Task WriteLEUlongAsync(ulong value, CancellationToken cancellationToken)
+ {
+ await WriteLEUintAsync((uint)(value & 0xffffffff), cancellationToken);
+ await WriteLEUintAsync((uint)(value >> 32), cancellationToken);
+ }
+
#endregion LE value reading/writing
///
diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs
index b9131d040..a0d9886bf 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs
@@ -6,6 +6,8 @@
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
+using System.Threading;
+using System.Threading.Tasks;
namespace ICSharpCode.SharpZipLib.Zip
{
@@ -166,6 +168,17 @@ private void WriteLeShort(int value)
}
}
+ ///
+ /// Write an unsigned short in little endian byte order.
+ ///
+ private async Task WriteLeShortAsync(int value, CancellationToken cancellationToken)
+ {
+ unchecked
+ {
+ await baseOutputStream_.WriteAsync(new[] {(byte)(value & 0xff), (byte)((value >> 8) & 0xff)}, 0, 2, cancellationToken);
+ }
+ }
+
///
/// Write an int in little endian byte order.
///
@@ -178,6 +191,15 @@ private void WriteLeInt(int value)
}
}
+ ///
+ /// Write an int in little endian byte order.
+ ///
+ private async Task WriteLeIntAsync(int value, CancellationToken cancellationToken)
+ {
+ await WriteLeShortAsync(value, cancellationToken);
+ await WriteLeShortAsync(value >> 16, cancellationToken);
+ }
+
///
/// Write an int in little endian byte order.
///
@@ -190,6 +212,18 @@ private void WriteLeLong(long value)
}
}
+ ///
+ /// Write an int in little endian byte order.
+ ///
+ private async Task WriteLeLongAsync(long value, CancellationToken cancellationToken)
+ {
+ unchecked
+ {
+ await WriteLeIntAsync((int)value, cancellationToken);
+ await WriteLeIntAsync((int)(value >> 32), cancellationToken);
+ }
+ }
+
// Apply any configured transforms/cleaning to the name of the supplied entry.
private void TransformEntryName(ZipEntry entry)
{
@@ -457,56 +491,479 @@ public void PutNextEntry(ZipEntry entry)
sizePatchPos += baseOutputStream_.Position;
}
- if (extra.Length > 0)
+ if (extra.Length > 0)
+ {
+ baseOutputStream_.Write(extra, 0, extra.Length);
+ }
+
+ offset += ZipConstants.LocalHeaderBaseSize + name.Length + extra.Length;
+ // Fix offsetOfCentraldir for AES
+ if (entry.AESKeySize > 0)
+ offset += entry.AESOverheadSize;
+
+ // Activate the entry.
+ curEntry = entry;
+ crc.Reset();
+ if (method == CompressionMethod.Deflated)
+ {
+ deflater_.Reset();
+ deflater_.SetLevel(compressionLevel);
+ }
+ size = 0;
+
+ if (entry.IsCrypted)
+ {
+ if (entry.AESKeySize > 0)
+ {
+ WriteAESHeader(entry);
+ }
+ else
+ {
+ if (entry.Crc < 0)
+ { // so testing Zip will says its ok
+ WriteEncryptionHeader(entry.DosTime << 16);
+ }
+ else
+ {
+ WriteEncryptionHeader(entry.Crc);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Starts a new Zip entry. It automatically closes the previous
+ /// entry if present.
+ /// All entry elements bar name are optional, but must be correct if present.
+ /// If the compression method is stored and the output is not patchable
+ /// the compression for that entry is automatically changed to deflate level 0
+ ///
+ ///
+ /// the entry.
+ ///
+ /// The that can be used to cancel the operation.
+ ///
+ /// if entry passed is null.
+ ///
+ ///
+ /// if an I/O error occured.
+ ///
+ ///
+ /// if stream was finished
+ ///
+ ///
+ /// Too many entries in the Zip file
+ /// Entry name is too long
+ /// Finish has already been called
+ ///
+ ///
+ /// The Compression method specified for the entry is unsupported.
+ ///
+ public async Task PutNextEntryAsync(ZipEntry entry, CancellationToken cancellationToken = default)
+ {
+ if (entry == null)
+ {
+ throw new ArgumentNullException(nameof(entry));
+ }
+
+ if (entries == null)
+ {
+ throw new InvalidOperationException("ZipOutputStream was finished");
+ }
+
+ if (curEntry != null)
+ {
+ await CloseEntryAsync(cancellationToken);
+ }
+
+ if (entries.Count == int.MaxValue)
+ {
+ throw new ZipException("Too many entries for Zip file");
+ }
+
+ CompressionMethod method = entry.CompressionMethod;
+
+ // Check that the compression is one that we support
+ if (method != CompressionMethod.Deflated && method != CompressionMethod.Stored)
+ {
+ throw new NotImplementedException("Compression method not supported");
+ }
+
+ // A password must have been set in order to add AES encrypted entries
+ if (entry.AESKeySize > 0 && string.IsNullOrEmpty(this.Password))
+ {
+ throw new InvalidOperationException("The Password property must be set before AES encrypted entries can be added");
+ }
+
+ int compressionLevel = defaultCompressionLevel;
+
+ // Clear flags that the library manages internally
+ entry.Flags &= (int)GeneralBitFlags.UnicodeText;
+ patchEntryHeader = false;
+
+ bool headerInfoAvailable;
+
+ // No need to compress - definitely no data.
+ if (entry.Size == 0)
+ {
+ entry.CompressedSize = entry.Size;
+ entry.Crc = 0;
+ method = CompressionMethod.Stored;
+ headerInfoAvailable = true;
+ }
+ else
+ {
+ headerInfoAvailable = (entry.Size >= 0) && entry.HasCrc && entry.CompressedSize >= 0;
+
+ // Switch to deflation if storing isnt possible.
+ if (method == CompressionMethod.Stored)
+ {
+ if (!headerInfoAvailable)
+ {
+ if (!CanPatchEntries)
+ {
+ // Can't patch entries so storing is not possible.
+ method = CompressionMethod.Deflated;
+ compressionLevel = 0;
+ }
+ }
+ else // entry.size must be > 0
+ {
+ entry.CompressedSize = entry.Size;
+ headerInfoAvailable = entry.HasCrc;
+ }
+ }
+ }
+
+ if (headerInfoAvailable == false)
+ {
+ if (CanPatchEntries == false)
+ {
+ // Only way to record size and compressed size is to append a data descriptor
+ // after compressed data.
+
+ // Stored entries of this form have already been converted to deflating.
+ entry.Flags |= 8;
+ }
+ else
+ {
+ patchEntryHeader = true;
+ }
+ }
+
+ if (Password != null)
+ {
+ entry.IsCrypted = true;
+ if (entry.Crc < 0)
+ {
+ // Need to append a data descriptor as the crc isnt available for use
+ // with encryption, the date is used instead. Setting the flag
+ // indicates this to the decompressor.
+ entry.Flags |= 8;
+ }
+ }
+
+ entry.Offset = offset;
+ entry.CompressionMethod = (CompressionMethod)method;
+
+ curMethod = method;
+ sizePatchPos = -1;
+
+ if ((useZip64_ == UseZip64.On) || ((entry.Size < 0) && (useZip64_ == UseZip64.Dynamic)))
+ {
+ entry.ForceZip64();
+ }
+
+ // Write the local file header
+ await WriteLeIntAsync(ZipConstants.LocalHeaderSignature, cancellationToken);
+
+ await WriteLeShortAsync(entry.Version, cancellationToken);
+ await WriteLeShortAsync(entry.Flags, cancellationToken);
+ await WriteLeShortAsync((byte)entry.CompressionMethodForHeader, cancellationToken);
+ await WriteLeIntAsync((int)entry.DosTime, cancellationToken);
+
+ // TODO: Refactor header writing. Its done in several places.
+ if (headerInfoAvailable)
+ {
+ await WriteLeIntAsync((int)entry.Crc, cancellationToken);
+ if (entry.LocalHeaderRequiresZip64)
+ {
+ await WriteLeIntAsync(-1, cancellationToken);
+ await WriteLeIntAsync(-1, cancellationToken);
+ }
+ else
+ {
+ await WriteLeIntAsync((int)entry.CompressedSize + entry.EncryptionOverheadSize, cancellationToken);
+ await WriteLeIntAsync((int)entry.Size, cancellationToken);
+ }
+ }
+ else
+ {
+ if (patchEntryHeader)
+ {
+ crcPatchPos = baseOutputStream_.Position;
+ }
+ await WriteLeIntAsync(0, cancellationToken); // Crc
+
+ if (patchEntryHeader)
+ {
+ sizePatchPos = baseOutputStream_.Position;
+ }
+
+ // For local header both sizes appear in Zip64 Extended Information
+ if (entry.LocalHeaderRequiresZip64 || patchEntryHeader)
+ {
+ await WriteLeIntAsync(-1, cancellationToken);
+ await WriteLeIntAsync(-1, cancellationToken);
+ }
+ else
+ {
+ await WriteLeIntAsync(0, cancellationToken); // Compressed size
+ await WriteLeIntAsync(0, cancellationToken); // Uncompressed size
+ }
+ }
+
+ // Apply any required transforms to the entry name, and then convert to byte array format.
+ TransformEntryName(entry);
+ byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name);
+
+ if (name.Length > 0xFFFF)
+ {
+ throw new ZipException("Entry name too long.");
+ }
+
+ var ed = new ZipExtraData(entry.ExtraData);
+
+ if (entry.LocalHeaderRequiresZip64)
+ {
+ ed.StartNewEntry();
+ if (headerInfoAvailable)
+ {
+ ed.AddLeLong(entry.Size);
+ ed.AddLeLong(entry.CompressedSize + entry.EncryptionOverheadSize);
+ }
+ else
+ {
+ ed.AddLeLong(-1);
+ ed.AddLeLong(-1);
+ }
+ ed.AddNewEntry(1);
+
+ if (!ed.Find(1))
+ {
+ throw new ZipException("Internal error cant find extra data");
+ }
+
+ if (patchEntryHeader)
+ {
+ sizePatchPos = ed.CurrentReadIndex;
+ }
+ }
+ else
+ {
+ ed.Delete(1);
+ }
+
+ if (entry.AESKeySize > 0)
+ {
+ AddExtraDataAES(entry, ed);
+ }
+ byte[] extra = ed.GetEntryData();
+
+ await WriteLeShortAsync(name.Length, cancellationToken);
+ await WriteLeShortAsync(extra.Length, cancellationToken);
+
+ if (name.Length > 0)
+ {
+ await baseOutputStream_.WriteAsync(name, 0, name.Length, cancellationToken);
+ }
+
+ if (entry.LocalHeaderRequiresZip64 && patchEntryHeader)
+ {
+ sizePatchPos += baseOutputStream_.Position;
+ }
+
+ if (extra.Length > 0)
+ {
+ await baseOutputStream_.WriteAsync(extra, 0, extra.Length, cancellationToken);
+ }
+
+ offset += ZipConstants.LocalHeaderBaseSize + name.Length + extra.Length;
+ // Fix offsetOfCentraldir for AES
+ if (entry.AESKeySize > 0)
+ offset += entry.AESOverheadSize;
+
+ // Activate the entry.
+ curEntry = entry;
+ crc.Reset();
+ if (method == CompressionMethod.Deflated)
+ {
+ deflater_.Reset();
+ deflater_.SetLevel(compressionLevel);
+ }
+ size = 0;
+
+ if (entry.IsCrypted)
+ {
+ if (entry.AESKeySize > 0)
+ {
+ await WriteAESHeaderAsync(entry, cancellationToken);
+ }
+ else
+ {
+ if (entry.Crc < 0)
+ { // so testing Zip will says its ok
+ await WriteEncryptionHeaderAsync(entry.DosTime << 16, cancellationToken);
+ }
+ else
+ {
+ await WriteEncryptionHeaderAsync(entry.Crc, cancellationToken);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Closes the current entry, updating header and footer information as required
+ ///
+ ///
+ /// An I/O error occurs.
+ ///
+ ///
+ /// No entry is active.
+ ///
+ public void CloseEntry()
+ {
+ if (curEntry == null)
+ {
+ throw new InvalidOperationException("No open entry");
+ }
+
+ long csize = size;
+
+ // First finish the deflater, if appropriate
+ if (curMethod == CompressionMethod.Deflated)
+ {
+ if (size >= 0)
+ {
+ base.Finish();
+ csize = deflater_.TotalOut;
+ }
+ else
+ {
+ deflater_.Reset();
+ }
+ }
+ else if (curMethod == CompressionMethod.Stored)
+ {
+ // This is done by Finsh() for Deflated entries, but we need to do it
+ // ourselves for Stored ones
+ base.GetAuthCodeIfAES();
+ }
+
+ // Write the AES Authentication Code (a hash of the compressed and encrypted data)
+ if (curEntry.AESKeySize > 0)
+ {
+ baseOutputStream_.Write(AESAuthCode, 0, 10);
+ }
+
+ if (curEntry.Size < 0)
+ {
+ curEntry.Size = size;
+ }
+ else if (curEntry.Size != size)
+ {
+ throw new ZipException("size was " + size + ", but I expected " + curEntry.Size);
+ }
+
+ if (curEntry.CompressedSize < 0)
+ {
+ curEntry.CompressedSize = csize;
+ }
+ else if (curEntry.CompressedSize != csize)
+ {
+ throw new ZipException("compressed size was " + csize + ", but I expected " + curEntry.CompressedSize);
+ }
+
+ if (curEntry.Crc < 0)
+ {
+ curEntry.Crc = crc.Value;
+ }
+ else if (curEntry.Crc != crc.Value)
+ {
+ throw new ZipException("crc was " + crc.Value + ", but I expected " + curEntry.Crc);
+ }
+
+ offset += csize;
+
+ if (curEntry.IsCrypted)
+ {
+ curEntry.CompressedSize += curEntry.EncryptionOverheadSize;
+ }
+
+ // Patch the header if possible
+ if (patchEntryHeader)
{
- baseOutputStream_.Write(extra, 0, extra.Length);
- }
+ patchEntryHeader = false;
- offset += ZipConstants.LocalHeaderBaseSize + name.Length + extra.Length;
- // Fix offsetOfCentraldir for AES
- if (entry.AESKeySize > 0)
- offset += entry.AESOverheadSize;
+ long curPos = baseOutputStream_.Position;
+ baseOutputStream_.Seek(crcPatchPos, SeekOrigin.Begin);
+ WriteLeInt((int)curEntry.Crc);
- // Activate the entry.
- curEntry = entry;
- crc.Reset();
- if (method == CompressionMethod.Deflated)
- {
- deflater_.Reset();
- deflater_.SetLevel(compressionLevel);
+ if (curEntry.LocalHeaderRequiresZip64)
+ {
+ if (sizePatchPos == -1)
+ {
+ throw new ZipException("Entry requires zip64 but this has been turned off");
+ }
+
+ baseOutputStream_.Seek(sizePatchPos, SeekOrigin.Begin);
+ WriteLeLong(curEntry.Size);
+ WriteLeLong(curEntry.CompressedSize);
+ }
+ else
+ {
+ WriteLeInt((int)curEntry.CompressedSize);
+ WriteLeInt((int)curEntry.Size);
+ }
+ baseOutputStream_.Seek(curPos, SeekOrigin.Begin);
}
- size = 0;
- if (entry.IsCrypted)
+ // Add data descriptor if flagged as required
+ if ((curEntry.Flags & 8) != 0)
{
- if (entry.AESKeySize > 0)
+ WriteLeInt(ZipConstants.DataDescriptorSignature);
+ WriteLeInt(unchecked((int)curEntry.Crc));
+
+ if (curEntry.LocalHeaderRequiresZip64)
{
- WriteAESHeader(entry);
+ WriteLeLong(curEntry.CompressedSize);
+ WriteLeLong(curEntry.Size);
+ offset += ZipConstants.Zip64DataDescriptorSize;
}
else
{
- if (entry.Crc < 0)
- { // so testing Zip will says its ok
- WriteEncryptionHeader(entry.DosTime << 16);
- }
- else
- {
- WriteEncryptionHeader(entry.Crc);
- }
+ WriteLeInt((int)curEntry.CompressedSize);
+ WriteLeInt((int)curEntry.Size);
+ offset += ZipConstants.DataDescriptorSize;
}
}
+
+ entries.Add(curEntry);
+ curEntry = null;
}
///
/// Closes the current entry, updating header and footer information as required
///
+ /// The that can be used to cancel the operation.
///
/// An I/O error occurs.
///
///
/// No entry is active.
///
- public void CloseEntry()
+ public async Task CloseEntryAsync(CancellationToken cancellationToken)
{
if (curEntry == null)
{
@@ -520,7 +977,7 @@ public void CloseEntry()
{
if (size >= 0)
{
- base.Finish();
+ await base.FinishAsync(cancellationToken);
csize = deflater_.TotalOut;
}
else
@@ -538,7 +995,7 @@ public void CloseEntry()
// Write the AES Authentication Code (a hash of the compressed and encrypted data)
if (curEntry.AESKeySize > 0)
{
- baseOutputStream_.Write(AESAuthCode, 0, 10);
+ await baseOutputStream_.WriteAsync(AESAuthCode, 0, 10, cancellationToken);
}
if (curEntry.Size < 0)
@@ -582,7 +1039,7 @@ public void CloseEntry()
long curPos = baseOutputStream_.Position;
baseOutputStream_.Seek(crcPatchPos, SeekOrigin.Begin);
- WriteLeInt((int)curEntry.Crc);
+ await WriteLeIntAsync((int)curEntry.Crc, cancellationToken);
if (curEntry.LocalHeaderRequiresZip64)
{
@@ -592,13 +1049,13 @@ public void CloseEntry()
}
baseOutputStream_.Seek(sizePatchPos, SeekOrigin.Begin);
- WriteLeLong(curEntry.Size);
- WriteLeLong(curEntry.CompressedSize);
+ await WriteLeLongAsync(curEntry.Size, cancellationToken);
+ await WriteLeLongAsync(curEntry.CompressedSize, cancellationToken);
}
else
{
- WriteLeInt((int)curEntry.CompressedSize);
- WriteLeInt((int)curEntry.Size);
+ await WriteLeIntAsync((int)curEntry.CompressedSize, cancellationToken);
+ await WriteLeIntAsync((int)curEntry.Size, cancellationToken);
}
baseOutputStream_.Seek(curPos, SeekOrigin.Begin);
}
@@ -606,19 +1063,19 @@ public void CloseEntry()
// Add data descriptor if flagged as required
if ((curEntry.Flags & 8) != 0)
{
- WriteLeInt(ZipConstants.DataDescriptorSignature);
- WriteLeInt(unchecked((int)curEntry.Crc));
+ await WriteLeIntAsync(ZipConstants.DataDescriptorSignature, cancellationToken);
+ await WriteLeIntAsync(unchecked((int)curEntry.Crc), cancellationToken);
if (curEntry.LocalHeaderRequiresZip64)
{
- WriteLeLong(curEntry.CompressedSize);
- WriteLeLong(curEntry.Size);
+ await WriteLeLongAsync(curEntry.CompressedSize, cancellationToken);
+ await WriteLeLongAsync(curEntry.Size, cancellationToken);
offset += ZipConstants.Zip64DataDescriptorSize;
}
else
{
- WriteLeInt((int)curEntry.CompressedSize);
- WriteLeInt((int)curEntry.Size);
+ await WriteLeIntAsync((int)curEntry.CompressedSize, cancellationToken);
+ await WriteLeIntAsync((int)curEntry.Size, cancellationToken);
offset += ZipConstants.DataDescriptorSize;
}
}
@@ -645,6 +1102,24 @@ private void WriteEncryptionHeader(long crcValue)
baseOutputStream_.Write(cryptBuffer, 0, cryptBuffer.Length);
}
+ private async Task WriteEncryptionHeaderAsync(long crcValue, CancellationToken cancellationToken)
+ {
+ offset += ZipConstants.CryptoHeaderSize;
+
+ InitializePassword(Password);
+
+ byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize];
+ using (var rng = new RNGCryptoServiceProvider())
+ {
+ rng.GetBytes(cryptBuffer);
+ }
+
+ cryptBuffer[11] = (byte)(crcValue >> 24);
+
+ EncryptBlock(cryptBuffer, 0, cryptBuffer.Length);
+ await baseOutputStream_.WriteAsync(cryptBuffer, 0, cryptBuffer.Length, cancellationToken);
+ }
+
private static void AddExtraDataAES(ZipEntry entry, ZipExtraData extraData)
{
// Vendor Version: AE-1 IS 1. AE-2 is 2. With AE-2 no CRC is required and 0 is stored.
@@ -683,6 +1158,28 @@ private void WriteAESHeader(ZipEntry entry)
baseOutputStream_.Write(pwdVerifier, 0, pwdVerifier.Length);
}
+ // Replaces WriteEncryptionHeader for AES
+ //
+ private async Task WriteAESHeaderAsync(ZipEntry entry, CancellationToken cancellationToken)
+ {
+ byte[] salt;
+ byte[] pwdVerifier;
+ InitializeAESPassword(entry, Password, out salt, out pwdVerifier);
+ // File format for AES:
+ // Size (bytes) Content
+ // ------------ -------
+ // Variable Salt value
+ // 2 Password verification value
+ // Variable Encrypted file data
+ // 10 Authentication code
+ //
+ // Value in the "compressed size" fields of the local file header and the central directory entry
+ // is the total size of all the items listed above. In other words, it is the total size of the
+ // salt value, password verification value, encrypted data, and authentication code.
+ await baseOutputStream_.WriteAsync(salt, 0, salt.Length, cancellationToken);
+ await baseOutputStream_.WriteAsync(pwdVerifier, 0, pwdVerifier.Length, cancellationToken);
+ }
+
///
/// Writes the given buffer to the current entry.
///
@@ -925,6 +1422,179 @@ public override void Finish()
entries = null;
}
+ ///
+ /// Finishes the stream. This will write the central directory at the
+ /// end of the zip file and flush the stream.
+ ///
+ /// The that can be used to cancel the operation.
+ ///
+ /// This is automatically called when the stream is closed.
+ ///
+ ///
+ /// An I/O error occurs.
+ ///
+ ///
+ /// Comment exceeds the maximum length
+ /// Entry name exceeds the maximum length
+ ///
+ public override async Task FinishAsync(CancellationToken cancellationToken)
+ {
+ if (entries == null)
+ {
+ return;
+ }
+
+ if (curEntry != null)
+ {
+ await CloseEntryAsync(cancellationToken);
+ }
+
+ long numEntries = entries.Count;
+ long sizeEntries = 0;
+
+ foreach (ZipEntry entry in entries)
+ {
+ await WriteLeIntAsync(ZipConstants.CentralHeaderSignature, cancellationToken);
+ await WriteLeShortAsync((entry.HostSystem << 8) | entry.VersionMadeBy, cancellationToken);
+ await WriteLeShortAsync(entry.Version, cancellationToken);
+ await WriteLeShortAsync(entry.Flags, cancellationToken);
+ await WriteLeShortAsync((short)entry.CompressionMethodForHeader, cancellationToken);
+ await WriteLeIntAsync((int)entry.DosTime, cancellationToken);
+ await WriteLeIntAsync((int)entry.Crc, cancellationToken);
+
+ if (entry.IsZip64Forced() ||
+ (entry.CompressedSize >= uint.MaxValue))
+ {
+ await WriteLeIntAsync(-1, cancellationToken);
+ }
+ else
+ {
+ await WriteLeIntAsync((int)entry.CompressedSize, cancellationToken);
+ }
+
+ if (entry.IsZip64Forced() ||
+ (entry.Size >= uint.MaxValue))
+ {
+ await WriteLeIntAsync(-1, cancellationToken);
+ }
+ else
+ {
+ await WriteLeIntAsync((int)entry.Size, cancellationToken);
+ }
+
+ byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name);
+
+ if (name.Length > 0xffff)
+ {
+ throw new ZipException("Name too long.");
+ }
+
+ var ed = new ZipExtraData(entry.ExtraData);
+
+ if (entry.CentralHeaderRequiresZip64)
+ {
+ ed.StartNewEntry();
+ if (entry.IsZip64Forced() ||
+ (entry.Size >= 0xffffffff))
+ {
+ ed.AddLeLong(entry.Size);
+ }
+
+ if (entry.IsZip64Forced() ||
+ (entry.CompressedSize >= 0xffffffff))
+ {
+ ed.AddLeLong(entry.CompressedSize);
+ }
+
+ if (entry.Offset >= 0xffffffff)
+ {
+ ed.AddLeLong(entry.Offset);
+ }
+
+ ed.AddNewEntry(1);
+ }
+ else
+ {
+ ed.Delete(1);
+ }
+
+ if (entry.AESKeySize > 0)
+ {
+ AddExtraDataAES(entry, ed);
+ }
+ byte[] extra = ed.GetEntryData();
+
+ byte[] entryComment =
+ (entry.Comment != null) ?
+ ZipStrings.ConvertToArray(entry.Flags, entry.Comment) :
+ new byte[0];
+
+ if (entryComment.Length > 0xffff)
+ {
+ throw new ZipException("Comment too long.");
+ }
+
+ await WriteLeShortAsync(name.Length, cancellationToken);
+ await WriteLeShortAsync(extra.Length, cancellationToken);
+ await WriteLeShortAsync(entryComment.Length, cancellationToken);
+ await WriteLeShortAsync(0, cancellationToken); // disk number
+ await WriteLeShortAsync(0, cancellationToken); // internal file attributes
+ // external file attributes
+
+ if (entry.ExternalFileAttributes != -1)
+ {
+ await WriteLeIntAsync(entry.ExternalFileAttributes, cancellationToken);
+ }
+ else
+ {
+ if (entry.IsDirectory)
+ { // mark entry as directory (from nikolam.AT.perfectinfo.com)
+ await WriteLeIntAsync(16, cancellationToken);
+ }
+ else
+ {
+ await WriteLeIntAsync(0, cancellationToken);
+ }
+ }
+
+ if (entry.Offset >= uint.MaxValue)
+ {
+ await WriteLeIntAsync(-1, cancellationToken);
+ }
+ else
+ {
+ await WriteLeIntAsync((int)entry.Offset, cancellationToken);
+ }
+
+ if (name.Length > 0)
+ {
+ await baseOutputStream_.WriteAsync(name, 0, name.Length, cancellationToken);
+ }
+
+ if (extra.Length > 0)
+ {
+ await baseOutputStream_.WriteAsync(extra, 0, extra.Length, cancellationToken);
+ }
+
+ if (entryComment.Length > 0)
+ {
+ await baseOutputStream_.WriteAsync(entryComment, 0, entryComment.Length, cancellationToken);
+ }
+
+ sizeEntries += ZipConstants.CentralHeaderBaseSize + name.Length + extra.Length + entryComment.Length;
+ }
+
+#if NETSTANDARD2_1
+ await
+#endif
+ using (ZipHelperStream zhs = new ZipHelperStream(baseOutputStream_))
+ {
+ await zhs.WriteEndOfCentralDirectoryAsync(numEntries, sizeEntries, offset, zipComment, cancellationToken);
+ }
+
+ entries = null;
+ }
+
///
/// Flushes the stream by calling Flush on the deflater stream unless
/// the current compression method is . Then it flushes the underlying output stream.