Skip to content

Commit e1e1a91

Browse files
piksel0xced
andauthored
feat(zip): ZipOutputStream async support (#574)
Co-authored-by: Cédric Luthi <[email protected]>
1 parent 6f7d973 commit e1e1a91

17 files changed

+1360
-1093
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
using System.Runtime.CompilerServices;
2+
3+
[assembly: InternalsVisibleTo("ICSharpCode.SharpZipLib.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b9a14ea8fc9d7599e0e82a1292a23103f0210e2f928a0f466963af23fffadba59dcc8c9e26ecd114d7c0b4179e4bc93b1656b7ee2d4a67dd7c1992653e0d9cc534f7914b6f583b022e0a7aa8a430f407932f9a6806f0fc64d61e78d5ae01aa8f8233196719d44da2c50a2d1cfa3f7abb7487b3567a4f0456aa6667154c6749b1")]
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
using System.IO;
2+
using System.Runtime.CompilerServices;
3+
using System.Threading.Tasks;
4+
using CT = System.Threading.CancellationToken;
5+
6+
// ReSharper disable MemberCanBePrivate.Global
7+
// ReSharper disable InconsistentNaming
8+
9+
namespace ICSharpCode.SharpZipLib.Core
10+
{
11+
internal static class ByteOrderStreamExtensions
12+
{
13+
internal static byte[] SwappedBytes(ushort value) => new[] {(byte)value, (byte)(value >> 8)};
14+
internal static byte[] SwappedBytes(short value) => new[] {(byte)value, (byte)(value >> 8)};
15+
internal static byte[] SwappedBytes(uint value) => new[] {(byte)value, (byte)(value >> 8), (byte)(value >> 16), (byte)(value >> 24)};
16+
internal static byte[] SwappedBytes(int value) => new[] {(byte)value, (byte)(value >> 8), (byte)(value >> 16), (byte)(value >> 24)};
17+
18+
internal static byte[] SwappedBytes(long value) => new[] {
19+
(byte)value, (byte)(value >> 8), (byte)(value >> 16), (byte)(value >> 24),
20+
(byte)(value >> 32), (byte)(value >> 40), (byte)(value >> 48), (byte)(value >> 56)
21+
};
22+
23+
internal static byte[] SwappedBytes(ulong value) => new[] {
24+
(byte)value, (byte)(value >> 8), (byte)(value >> 16), (byte)(value >> 24),
25+
(byte)(value >> 32), (byte)(value >> 40), (byte)(value >> 48), (byte)(value >> 56)
26+
};
27+
28+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
29+
internal static long SwappedS64(byte[] bytes) => (
30+
(long)bytes[0] << 0 | (long)bytes[1] << 8 | (long)bytes[2] << 16 | (long)bytes[3] << 24 |
31+
(long)bytes[4] << 32 | (long)bytes[5] << 40 | (long)bytes[6] << 48 | (long)bytes[7] << 56);
32+
33+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
34+
internal static ulong SwappedU64(byte[] bytes) => (
35+
(ulong)bytes[0] << 0 | (ulong)bytes[1] << 8 | (ulong)bytes[2] << 16 | (ulong)bytes[3] << 24 |
36+
(ulong)bytes[4] << 32 | (ulong)bytes[5] << 40 | (ulong)bytes[6] << 48 | (ulong)bytes[7] << 56);
37+
38+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
39+
internal static int SwappedS32(byte[] bytes) => bytes[0] | bytes[1] << 8 | bytes[2] << 16 | bytes[3] << 24;
40+
41+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
42+
internal static uint SwappedU32(byte[] bytes) => (uint) SwappedS32(bytes);
43+
44+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
45+
internal static short SwappedS16(byte[] bytes) => (short)(bytes[0] | bytes[1] << 8);
46+
47+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
48+
internal static ushort SwappedU16(byte[] bytes) => (ushort) SwappedS16(bytes);
49+
50+
internal static byte[] ReadBytes(this Stream stream, int count)
51+
{
52+
var bytes = new byte[count];
53+
var remaining = count;
54+
while (remaining > 0)
55+
{
56+
var bytesRead = stream.Read(bytes, count - remaining, remaining);
57+
if (bytesRead < 1) throw new EndOfStreamException();
58+
remaining -= bytesRead;
59+
}
60+
61+
return bytes;
62+
}
63+
64+
/// <summary> Read an unsigned short in little endian byte order. </summary>
65+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
66+
public static int ReadLEShort(this Stream stream) => SwappedS16(ReadBytes(stream, 2));
67+
68+
/// <summary> Read an int in little endian byte order. </summary>
69+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
70+
public static int ReadLEInt(this Stream stream) => SwappedS32(ReadBytes(stream, 4));
71+
72+
/// <summary> Read a long in little endian byte order. </summary>
73+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
74+
public static long ReadLELong(this Stream stream) => SwappedS64(ReadBytes(stream, 8));
75+
76+
/// <summary> Write an unsigned short in little endian byte order. </summary>
77+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
78+
public static void WriteLEShort(this Stream stream, int value) => stream.Write(SwappedBytes(value), 0, 2);
79+
80+
/// <inheritdoc cref="WriteLEShort"/>
81+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
82+
public static async Task WriteLEShortAsync(this Stream stream, int value, CT ct)
83+
=> await stream.WriteAsync(SwappedBytes(value), 0, 2, ct);
84+
85+
/// <summary> Write a ushort in little endian byte order. </summary>
86+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
87+
public static void WriteLEUshort(this Stream stream, ushort value) => stream.Write(SwappedBytes(value), 0, 2);
88+
89+
/// <inheritdoc cref="WriteLEUshort"/>
90+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
91+
public static async Task WriteLEUshortAsync(this Stream stream, ushort value, CT ct)
92+
=> await stream.WriteAsync(SwappedBytes(value), 0, 2, ct);
93+
94+
/// <summary> Write an int in little endian byte order. </summary>
95+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
96+
public static void WriteLEInt(this Stream stream, int value) => stream.Write(SwappedBytes(value), 0, 4);
97+
98+
/// <inheritdoc cref="WriteLEInt"/>
99+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
100+
public static async Task WriteLEIntAsync(this Stream stream, int value, CT ct)
101+
=> await stream.WriteAsync(SwappedBytes(value), 0, 4, ct);
102+
103+
/// <summary> Write a uint in little endian byte order. </summary>
104+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
105+
public static void WriteLEUint(this Stream stream, uint value) => stream.Write(SwappedBytes(value), 0, 4);
106+
107+
/// <inheritdoc cref="WriteLEUint"/>
108+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
109+
public static async Task WriteLEUintAsync(this Stream stream, uint value, CT ct)
110+
=> await stream.WriteAsync(SwappedBytes(value), 0, 4, ct);
111+
112+
/// <summary> Write a long in little endian byte order. </summary>
113+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
114+
public static void WriteLELong(this Stream stream, long value) => stream.Write(SwappedBytes(value), 0, 8);
115+
116+
/// <inheritdoc cref="WriteLELong"/>
117+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
118+
public static async Task WriteLELongAsync(this Stream stream, long value, CT ct)
119+
=> await stream.WriteAsync(SwappedBytes(value), 0, 8, ct);
120+
121+
/// <summary> Write a ulong in little endian byte order. </summary>
122+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
123+
public static void WriteLEUlong(this Stream stream, ulong value) => stream.Write(SwappedBytes(value), 0, 8);
124+
125+
/// <inheritdoc cref="WriteLEUlong"/>
126+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
127+
public static async Task WriteLEUlongAsync(this Stream stream, ulong value, CT ct)
128+
=> await stream.WriteAsync(SwappedBytes(value), 0, 8, ct);
129+
}
130+
}

src/ICSharpCode.SharpZipLib/Core/StreamUtils.cs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
using System;
22
using System.IO;
3+
using System.Threading;
4+
using System.Threading.Tasks;
35

46
namespace ICSharpCode.SharpZipLib.Core
57
{
68
/// <summary>
79
/// Provides simple <see cref="Stream"/>" utilities.
810
/// </summary>
9-
public sealed class StreamUtils
11+
public static class StreamUtils
1012
{
1113
/// <summary>
1214
/// Read from a <see cref="Stream"/> ensuring all the required data is read.
1315
/// </summary>
1416
/// <param name="stream">The stream to read.</param>
1517
/// <param name="buffer">The buffer to fill.</param>
1618
/// <seealso cref="ReadFully(Stream,byte[],int,int)"/>
17-
static public void ReadFully(Stream stream, byte[] buffer)
19+
public static void ReadFully(Stream stream, byte[] buffer)
1820
{
1921
ReadFully(stream, buffer, 0, buffer.Length);
2022
}
@@ -29,7 +31,7 @@ static public void ReadFully(Stream stream, byte[] buffer)
2931
/// <exception cref="ArgumentNullException">Required parameter is null</exception>
3032
/// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> and or <paramref name="count"/> are invalid.</exception>
3133
/// <exception cref="EndOfStreamException">End of stream is encountered before all the data has been read.</exception>
32-
static public void ReadFully(Stream stream, byte[] buffer, int offset, int count)
34+
public static void ReadFully(Stream stream, byte[] buffer, int offset, int count)
3335
{
3436
if (stream == null)
3537
{
@@ -73,7 +75,7 @@ static public void ReadFully(Stream stream, byte[] buffer, int offset, int count
7375
/// <param name="count">The number of bytes of data to store.</param>
7476
/// <exception cref="ArgumentNullException">Required parameter is null</exception>
7577
/// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> and or <paramref name="count"/> are invalid.</exception>
76-
static public int ReadRequestedBytes(Stream stream, byte[] buffer, int offset, int count)
78+
public static int ReadRequestedBytes(Stream stream, byte[] buffer, int offset, int count)
7779
{
7880
if (stream == null)
7981
{
@@ -118,7 +120,7 @@ static public int ReadRequestedBytes(Stream stream, byte[] buffer, int offset, i
118120
/// <param name="source">The stream to source data from.</param>
119121
/// <param name="destination">The stream to write data to.</param>
120122
/// <param name="buffer">The buffer to use during copying.</param>
121-
static public void Copy(Stream source, Stream destination, byte[] buffer)
123+
public static void Copy(Stream source, Stream destination, byte[] buffer)
122124
{
123125
if (source == null)
124126
{
@@ -169,7 +171,7 @@ static public void Copy(Stream source, Stream destination, byte[] buffer)
169171
/// <param name="sender">The source for this event.</param>
170172
/// <param name="name">The name to use with the event.</param>
171173
/// <remarks>This form is specialised for use within #Zip to support events during archive operations.</remarks>
172-
static public void Copy(Stream source, Stream destination,
174+
public static void Copy(Stream source, Stream destination,
173175
byte[] buffer, ProgressHandler progressHandler, TimeSpan updateInterval, object sender, string name)
174176
{
175177
Copy(source, destination, buffer, progressHandler, updateInterval, sender, name, -1);
@@ -188,7 +190,7 @@ static public void Copy(Stream source, Stream destination,
188190
/// <param name="fixedTarget">A predetermined fixed target value to use with progress updates.
189191
/// If the value is negative the target is calculated by looking at the stream.</param>
190192
/// <remarks>This form is specialised for use within #Zip to support events during archive operations.</remarks>
191-
static public void Copy(Stream source, Stream destination,
193+
public static void Copy(Stream source, Stream destination,
192194
byte[] buffer,
193195
ProgressHandler progressHandler, TimeSpan updateInterval,
194196
object sender, string name, long fixedTarget)
@@ -272,13 +274,22 @@ static public void Copy(Stream source, Stream destination,
272274
progressHandler(sender, args);
273275
}
274276
}
275-
276-
/// <summary>
277-
/// Initialise an instance of <see cref="StreamUtils"></see>
278-
/// </summary>
279-
private StreamUtils()
277+
278+
internal static async Task WriteProcToStreamAsync(this Stream targetStream, MemoryStream bufferStream, Action<Stream> writeProc, CancellationToken ct)
280279
{
281-
// Do nothing.
280+
bufferStream.SetLength(0);
281+
writeProc(bufferStream);
282+
bufferStream.Position = 0;
283+
await bufferStream.CopyToAsync(targetStream, 81920, ct);
284+
bufferStream.SetLength(0);
285+
}
286+
287+
internal static async Task WriteProcToStreamAsync(this Stream targetStream, Action<Stream> writeProc, CancellationToken ct)
288+
{
289+
using (var ms = new MemoryStream())
290+
{
291+
await WriteProcToStreamAsync(targetStream, ms, writeProc, ct);
292+
}
282293
}
283294
}
284295
}

src/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,5 @@ Please see https://github.com/icsharpcode/SharpZipLib/wiki/Release-1.3.3 for mor
4040
<PackagePath>images</PackagePath>
4141
</None>
4242
</ItemGroup>
43-
43+
4444
</Project>

src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using System;
33
using System.IO;
44
using System.Security.Cryptography;
5+
using System.Threading;
6+
using System.Threading.Tasks;
57

68
namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams
79
{
@@ -105,10 +107,7 @@ public virtual void Finish()
105107
break;
106108
}
107109

108-
if (cryptoTransform_ != null)
109-
{
110-
EncryptBlock(buffer_, 0, len);
111-
}
110+
EncryptBlock(buffer_, 0, len);
112111

113112
baseOutputStream_.Write(buffer_, 0, len);
114113
}
@@ -131,6 +130,47 @@ public virtual void Finish()
131130
}
132131
}
133132

133+
/// <summary>
134+
/// Finishes the stream by calling finish() on the deflater.
135+
/// </summary>
136+
/// <param name="ct">The <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
137+
/// <exception cref="SharpZipBaseException">
138+
/// Not all input is deflated
139+
/// </exception>
140+
public virtual async Task FinishAsync(CancellationToken ct)
141+
{
142+
deflater_.Finish();
143+
while (!deflater_.IsFinished)
144+
{
145+
int len = deflater_.Deflate(buffer_, 0, buffer_.Length);
146+
if (len <= 0)
147+
{
148+
break;
149+
}
150+
151+
EncryptBlock(buffer_, 0, len);
152+
153+
await baseOutputStream_.WriteAsync(buffer_, 0, len, ct);
154+
}
155+
156+
if (!deflater_.IsFinished)
157+
{
158+
throw new SharpZipBaseException("Can't deflate all input?");
159+
}
160+
161+
await baseOutputStream_.FlushAsync(ct);
162+
163+
if (cryptoTransform_ != null)
164+
{
165+
if (cryptoTransform_ is ZipAESTransform)
166+
{
167+
AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode();
168+
}
169+
cryptoTransform_.Dispose();
170+
cryptoTransform_ = null;
171+
}
172+
}
173+
134174
/// <summary>
135175
/// Gets or sets a flag indicating ownership of underlying stream.
136176
/// When the flag is true <see cref="Stream.Dispose()" /> will close the underlying stream also.
@@ -177,6 +217,7 @@ public bool CanPatchEntries
177217
/// </param>
178218
protected void EncryptBlock(byte[] buffer, int offset, int length)
179219
{
220+
if(cryptoTransform_ is null) return;
180221
cryptoTransform_.TransformBlock(buffer, 0, length, buffer, 0);
181222
}
182223

@@ -204,10 +245,8 @@ private void Deflate(bool flushing)
204245
{
205246
break;
206247
}
207-
if (cryptoTransform_ != null)
208-
{
209-
EncryptBlock(buffer_, 0, deflateCount);
210-
}
248+
249+
EncryptBlock(buffer_, 0, deflateCount);
211250

212251
baseOutputStream_.Write(buffer_, 0, deflateCount);
213252
}
@@ -369,6 +408,38 @@ protected override void Dispose(bool disposing)
369408
}
370409
}
371410

411+
#if NETSTANDARD2_1
412+
/// <summary>
413+
/// Calls <see cref="FinishAsync"/> and closes the underlying
414+
/// stream when <see cref="IsStreamOwner"></see> is true.
415+
/// </summary>
416+
public override async ValueTask DisposeAsync()
417+
{
418+
if (!isClosed_)
419+
{
420+
isClosed_ = true;
421+
422+
try
423+
{
424+
await FinishAsync(CancellationToken.None);
425+
if (cryptoTransform_ != null)
426+
{
427+
GetAuthCodeIfAES();
428+
cryptoTransform_.Dispose();
429+
cryptoTransform_ = null;
430+
}
431+
}
432+
finally
433+
{
434+
if (IsStreamOwner)
435+
{
436+
await baseOutputStream_.DisposeAsync();
437+
}
438+
}
439+
}
440+
}
441+
#endif
442+
372443
/// <summary>
373444
/// Get the Auth code for AES encrypted entries
374445
/// </summary>

0 commit comments

Comments
 (0)