Skip to content

feat(zip): ZipOutputStream async support #574

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Oct 9, 2021
3 changes: 3 additions & 0 deletions src/ICSharpCode.SharpZipLib/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("ICSharpCode.SharpZipLib.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b9a14ea8fc9d7599e0e82a1292a23103f0210e2f928a0f466963af23fffadba59dcc8c9e26ecd114d7c0b4179e4bc93b1656b7ee2d4a67dd7c1992653e0d9cc534f7914b6f583b022e0a7aa8a430f407932f9a6806f0fc64d61e78d5ae01aa8f8233196719d44da2c50a2d1cfa3f7abb7487b3567a4f0456aa6667154c6749b1")]
130 changes: 130 additions & 0 deletions src/ICSharpCode.SharpZipLib/Core/ByteOrderUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using CT = System.Threading.CancellationToken;

// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable InconsistentNaming

namespace ICSharpCode.SharpZipLib.Core
{
internal static class ByteOrderStreamExtensions
{
internal static byte[] SwappedBytes(ushort value) => new[] {(byte)value, (byte)(value >> 8)};
internal static byte[] SwappedBytes(short value) => new[] {(byte)value, (byte)(value >> 8)};
internal static byte[] SwappedBytes(uint value) => new[] {(byte)value, (byte)(value >> 8), (byte)(value >> 16), (byte)(value >> 24)};
internal static byte[] SwappedBytes(int value) => new[] {(byte)value, (byte)(value >> 8), (byte)(value >> 16), (byte)(value >> 24)};

internal static byte[] SwappedBytes(long value) => new[] {
(byte)value, (byte)(value >> 8), (byte)(value >> 16), (byte)(value >> 24),
(byte)(value >> 32), (byte)(value >> 40), (byte)(value >> 48), (byte)(value >> 56)
};

internal static byte[] SwappedBytes(ulong value) => new[] {
(byte)value, (byte)(value >> 8), (byte)(value >> 16), (byte)(value >> 24),
(byte)(value >> 32), (byte)(value >> 40), (byte)(value >> 48), (byte)(value >> 56)
};

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static long SwappedS64(byte[] bytes) => (
(long)bytes[0] << 0 | (long)bytes[1] << 8 | (long)bytes[2] << 16 | (long)bytes[3] << 24 |
(long)bytes[4] << 32 | (long)bytes[5] << 40 | (long)bytes[6] << 48 | (long)bytes[7] << 56);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static ulong SwappedU64(byte[] bytes) => (
(ulong)bytes[0] << 0 | (ulong)bytes[1] << 8 | (ulong)bytes[2] << 16 | (ulong)bytes[3] << 24 |
(ulong)bytes[4] << 32 | (ulong)bytes[5] << 40 | (ulong)bytes[6] << 48 | (ulong)bytes[7] << 56);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int SwappedS32(byte[] bytes) => bytes[0] | bytes[1] << 8 | bytes[2] << 16 | bytes[3] << 24;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static uint SwappedU32(byte[] bytes) => (uint) SwappedS32(bytes);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static short SwappedS16(byte[] bytes) => (short)(bytes[0] | bytes[1] << 8);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static ushort SwappedU16(byte[] bytes) => (ushort) SwappedS16(bytes);

internal static byte[] ReadBytes(this Stream stream, int count)
{
var bytes = new byte[count];
var remaining = count;
while (remaining > 0)
{
var bytesRead = stream.Read(bytes, count - remaining, remaining);
if (bytesRead < 1) throw new EndOfStreamException();
remaining -= bytesRead;
}

return bytes;
}

/// <summary> Read an unsigned short in little endian byte order. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ReadLEShort(this Stream stream) => SwappedS16(ReadBytes(stream, 2));

/// <summary> Read an int in little endian byte order. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ReadLEInt(this Stream stream) => SwappedS32(ReadBytes(stream, 4));

/// <summary> Read a long in little endian byte order. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static long ReadLELong(this Stream stream) => SwappedS64(ReadBytes(stream, 8));

/// <summary> Write an unsigned short in little endian byte order. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteLEShort(this Stream stream, int value) => stream.Write(SwappedBytes(value), 0, 2);

/// <inheritdoc cref="WriteLEShort"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static async Task WriteLEShortAsync(this Stream stream, int value, CT ct)
=> await stream.WriteAsync(SwappedBytes(value), 0, 2, ct);

/// <summary> Write a ushort in little endian byte order. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteLEUshort(this Stream stream, ushort value) => stream.Write(SwappedBytes(value), 0, 2);

/// <inheritdoc cref="WriteLEUshort"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static async Task WriteLEUshortAsync(this Stream stream, ushort value, CT ct)
=> await stream.WriteAsync(SwappedBytes(value), 0, 2, ct);

/// <summary> Write an int in little endian byte order. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteLEInt(this Stream stream, int value) => stream.Write(SwappedBytes(value), 0, 4);

/// <inheritdoc cref="WriteLEInt"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static async Task WriteLEIntAsync(this Stream stream, int value, CT ct)
=> await stream.WriteAsync(SwappedBytes(value), 0, 4, ct);

/// <summary> Write a uint in little endian byte order. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteLEUint(this Stream stream, uint value) => stream.Write(SwappedBytes(value), 0, 4);

/// <inheritdoc cref="WriteLEUint"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static async Task WriteLEUintAsync(this Stream stream, uint value, CT ct)
=> await stream.WriteAsync(SwappedBytes(value), 0, 4, ct);

/// <summary> Write a long in little endian byte order. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteLELong(this Stream stream, long value) => stream.Write(SwappedBytes(value), 0, 8);

/// <inheritdoc cref="WriteLELong"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static async Task WriteLELongAsync(this Stream stream, long value, CT ct)
=> await stream.WriteAsync(SwappedBytes(value), 0, 8, ct);

/// <summary> Write a ulong in little endian byte order. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteLEUlong(this Stream stream, ulong value) => stream.Write(SwappedBytes(value), 0, 8);

/// <inheritdoc cref="WriteLEUlong"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static async Task WriteLEUlongAsync(this Stream stream, ulong value, CT ct)
=> await stream.WriteAsync(SwappedBytes(value), 0, 8, ct);
}
}
37 changes: 24 additions & 13 deletions src/ICSharpCode.SharpZipLib/Core/StreamUtils.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace ICSharpCode.SharpZipLib.Core
{
/// <summary>
/// Provides simple <see cref="Stream"/>" utilities.
/// </summary>
public sealed class StreamUtils
public static class StreamUtils
{
/// <summary>
/// Read from a <see cref="Stream"/> ensuring all the required data is read.
/// </summary>
/// <param name="stream">The stream to read.</param>
/// <param name="buffer">The buffer to fill.</param>
/// <seealso cref="ReadFully(Stream,byte[],int,int)"/>
static public void ReadFully(Stream stream, byte[] buffer)
public static void ReadFully(Stream stream, byte[] buffer)
{
ReadFully(stream, buffer, 0, buffer.Length);
}
Expand All @@ -29,7 +31,7 @@ static public void ReadFully(Stream stream, byte[] buffer)
/// <exception cref="ArgumentNullException">Required parameter is null</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> and or <paramref name="count"/> are invalid.</exception>
/// <exception cref="EndOfStreamException">End of stream is encountered before all the data has been read.</exception>
static public void ReadFully(Stream stream, byte[] buffer, int offset, int count)
public static void ReadFully(Stream stream, byte[] buffer, int offset, int count)
{
if (stream == null)
{
Expand Down Expand Up @@ -73,7 +75,7 @@ static public void ReadFully(Stream stream, byte[] buffer, int offset, int count
/// <param name="count">The number of bytes of data to store.</param>
/// <exception cref="ArgumentNullException">Required parameter is null</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> and or <paramref name="count"/> are invalid.</exception>
static public int ReadRequestedBytes(Stream stream, byte[] buffer, int offset, int count)
public static int ReadRequestedBytes(Stream stream, byte[] buffer, int offset, int count)
{
if (stream == null)
{
Expand Down Expand Up @@ -118,7 +120,7 @@ static public int ReadRequestedBytes(Stream stream, byte[] buffer, int offset, i
/// <param name="source">The stream to source data from.</param>
/// <param name="destination">The stream to write data to.</param>
/// <param name="buffer">The buffer to use during copying.</param>
static public void Copy(Stream source, Stream destination, byte[] buffer)
public static void Copy(Stream source, Stream destination, byte[] buffer)
{
if (source == null)
{
Expand Down Expand Up @@ -169,7 +171,7 @@ static public void Copy(Stream source, Stream destination, byte[] buffer)
/// <param name="sender">The source for this event.</param>
/// <param name="name">The name to use with the event.</param>
/// <remarks>This form is specialised for use within #Zip to support events during archive operations.</remarks>
static public void Copy(Stream source, Stream destination,
public static void Copy(Stream source, Stream destination,
byte[] buffer, ProgressHandler progressHandler, TimeSpan updateInterval, object sender, string name)
{
Copy(source, destination, buffer, progressHandler, updateInterval, sender, name, -1);
Expand All @@ -188,7 +190,7 @@ static public void Copy(Stream source, Stream destination,
/// <param name="fixedTarget">A predetermined fixed target value to use with progress updates.
/// If the value is negative the target is calculated by looking at the stream.</param>
/// <remarks>This form is specialised for use within #Zip to support events during archive operations.</remarks>
static public void Copy(Stream source, Stream destination,
public static void Copy(Stream source, Stream destination,
byte[] buffer,
ProgressHandler progressHandler, TimeSpan updateInterval,
object sender, string name, long fixedTarget)
Expand Down Expand Up @@ -272,13 +274,22 @@ static public void Copy(Stream source, Stream destination,
progressHandler(sender, args);
}
}

/// <summary>
/// Initialise an instance of <see cref="StreamUtils"></see>
/// </summary>
private StreamUtils()

internal static async Task WriteProcToStreamAsync(this Stream targetStream, MemoryStream bufferStream, Action<Stream> writeProc, CancellationToken ct)
{
// Do nothing.
bufferStream.SetLength(0);
writeProc(bufferStream);
bufferStream.Position = 0;
await bufferStream.CopyToAsync(targetStream, 81920, ct);
bufferStream.SetLength(0);
}

internal static async Task WriteProcToStreamAsync(this Stream targetStream, Action<Stream> writeProc, CancellationToken ct)
{
using (var ms = new MemoryStream())
{
await WriteProcToStreamAsync(targetStream, ms, writeProc, ct);
}
}
}
}
2 changes: 1 addition & 1 deletion src/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@ Please see https://github.com/icsharpcode/SharpZipLib/wiki/Release-1.3.3 for mor
<PackagePath>images</PackagePath>
</None>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -105,10 +107,7 @@ public virtual void Finish()
break;
}

if (cryptoTransform_ != null)
{
EncryptBlock(buffer_, 0, len);
}
EncryptBlock(buffer_, 0, len);

baseOutputStream_.Write(buffer_, 0, len);
}
Expand All @@ -131,6 +130,47 @@ public virtual void Finish()
}
}

/// <summary>
/// Finishes the stream by calling finish() on the deflater.
/// </summary>
/// <param name="ct">The <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <exception cref="SharpZipBaseException">
/// Not all input is deflated
/// </exception>
public virtual async Task FinishAsync(CancellationToken ct)
{
deflater_.Finish();
while (!deflater_.IsFinished)
{
int len = deflater_.Deflate(buffer_, 0, buffer_.Length);
if (len <= 0)
{
break;
}

EncryptBlock(buffer_, 0, len);

await baseOutputStream_.WriteAsync(buffer_, 0, len, ct);
}

if (!deflater_.IsFinished)
{
throw new SharpZipBaseException("Can't deflate all input?");
}

await baseOutputStream_.FlushAsync(ct);

if (cryptoTransform_ != null)
{
if (cryptoTransform_ is ZipAESTransform)
{
AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode();
}
cryptoTransform_.Dispose();
cryptoTransform_ = null;
}
}

/// <summary>
/// Gets or sets a flag indicating ownership of underlying stream.
/// When the flag is true <see cref="Stream.Dispose()" /> will close the underlying stream also.
Expand Down Expand Up @@ -177,6 +217,7 @@ public bool CanPatchEntries
/// </param>
protected void EncryptBlock(byte[] buffer, int offset, int length)
{
if(cryptoTransform_ is null) return;
cryptoTransform_.TransformBlock(buffer, 0, length, buffer, 0);
}

Expand Down Expand Up @@ -204,10 +245,8 @@ private void Deflate(bool flushing)
{
break;
}
if (cryptoTransform_ != null)
{
EncryptBlock(buffer_, 0, deflateCount);
}

EncryptBlock(buffer_, 0, deflateCount);

baseOutputStream_.Write(buffer_, 0, deflateCount);
}
Expand Down Expand Up @@ -369,6 +408,38 @@ protected override void Dispose(bool disposing)
}
}

#if NETSTANDARD2_1
/// <summary>
/// Calls <see cref="FinishAsync"/> and closes the underlying
/// stream when <see cref="IsStreamOwner"></see> is true.
/// </summary>
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

/// <summary>
/// Get the Auth code for AES encrypted entries
/// </summary>
Expand Down
Loading