mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-04-05 21:51:09 +00:00
@@ -11,5 +11,6 @@
|
|||||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||||
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
||||||
|
<NoWarn>${NoWarn};IDE0051</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ public static class Constants
|
|||||||
/// by rewinding and re-reading the same data.
|
/// by rewinding and re-reading the same data.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// <b>Default:</b> 81920 bytes (81KB) - sufficient for typical format detection.
|
/// <b>Default:</b> 163840 bytes (160KB) - sized to cover ZStandard's worst-case
|
||||||
|
/// first block on a tar archive (~131KB including frame header overhead).
|
||||||
|
/// ZStandard blocks can be up to 128KB, exceeding the previous 81KB default.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// <b>Typical usage:</b> 500-1000 bytes for most archives
|
/// <b>Typical usage:</b> 500-1000 bytes for most archives
|
||||||
@@ -39,7 +41,7 @@ public static class Constants
|
|||||||
/// </list>
|
/// </list>
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static int RewindableBufferSize { get; set; } = 81920;
|
public static int RewindableBufferSize { get; set; } = 163840;
|
||||||
|
|
||||||
public static CultureInfo DefaultCultureInfo { get; set; } = CultureInfo.InvariantCulture;
|
public static CultureInfo DefaultCultureInfo { get; set; } = CultureInfo.InvariantCulture;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,55 +86,34 @@ internal partial class WinzipAesCryptoStream
|
|||||||
private void ReadTransformBlocks(Span<byte> buffer, int count)
|
private void ReadTransformBlocks(Span<byte> buffer, int count)
|
||||||
{
|
{
|
||||||
var posn = 0;
|
var posn = 0;
|
||||||
var last = count;
|
var remaining = count;
|
||||||
|
|
||||||
while (posn < buffer.Length && posn < last)
|
while (posn < buffer.Length && remaining > 0)
|
||||||
{
|
{
|
||||||
var n = ReadTransformOneBlock(buffer, posn, last);
|
var n = ReadTransformOneBlock(buffer, posn, remaining);
|
||||||
posn += n;
|
posn += n;
|
||||||
|
remaining -= n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int ReadTransformOneBlock(Span<byte> buffer, int offset, int last)
|
private int ReadTransformOneBlock(Span<byte> buffer, int offset, int remaining)
|
||||||
{
|
{
|
||||||
if (_isFinalBlock)
|
if (_counterOutOffset == BLOCK_SIZE_IN_BYTES)
|
||||||
{
|
{
|
||||||
throw new ArchiveOperationException();
|
FillCounterOut();
|
||||||
}
|
}
|
||||||
|
|
||||||
var bytesRemaining = last - offset;
|
var bytesToXor = Math.Min(BLOCK_SIZE_IN_BYTES - _counterOutOffset, remaining);
|
||||||
var bytesToRead =
|
XorInPlace(buffer, offset, bytesToXor, _counterOutOffset);
|
||||||
(bytesRemaining > BLOCK_SIZE_IN_BYTES) ? BLOCK_SIZE_IN_BYTES : bytesRemaining;
|
_counterOutOffset += bytesToXor;
|
||||||
|
return bytesToXor;
|
||||||
// update the counter
|
|
||||||
System.Buffers.Binary.BinaryPrimitives.WriteInt32LittleEndian(_counter, _nonce++);
|
|
||||||
|
|
||||||
// Determine if this is the final block
|
|
||||||
if ((bytesToRead == bytesRemaining) && (_totalBytesLeftToRead == 0))
|
|
||||||
{
|
|
||||||
_counterOut = _transform.TransformFinalBlock(_counter, 0, BLOCK_SIZE_IN_BYTES);
|
|
||||||
_isFinalBlock = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_transform.TransformBlock(
|
|
||||||
_counter,
|
|
||||||
0, // offset
|
|
||||||
BLOCK_SIZE_IN_BYTES,
|
|
||||||
_counterOut,
|
|
||||||
0
|
|
||||||
); // offset
|
|
||||||
}
|
|
||||||
|
|
||||||
XorInPlace(buffer, offset, bytesToRead);
|
|
||||||
return bytesToRead;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void XorInPlace(Span<byte> buffer, int offset, int count)
|
private void XorInPlace(Span<byte> buffer, int offset, int count, int counterOffset)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < count; i++)
|
for (var i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
buffer[offset + i] = (byte)(_counterOut[i] ^ buffer[offset + i]);
|
buffer[offset + i] = (byte)(_counterOut[counterOffset + i] ^ buffer[offset + i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ internal partial class WinzipAesCryptoStream : Stream
|
|||||||
private readonly ICryptoTransform _transform;
|
private readonly ICryptoTransform _transform;
|
||||||
private int _nonce = 1;
|
private int _nonce = 1;
|
||||||
private byte[] _counterOut = new byte[BLOCK_SIZE_IN_BYTES];
|
private byte[] _counterOut = new byte[BLOCK_SIZE_IN_BYTES];
|
||||||
private bool _isFinalBlock;
|
private int _counterOutOffset = BLOCK_SIZE_IN_BYTES;
|
||||||
private long _totalBytesLeftToRead;
|
private long _totalBytesLeftToRead;
|
||||||
private bool _isDisposed;
|
private bool _isDisposed;
|
||||||
|
|
||||||
@@ -123,58 +123,45 @@ internal partial class WinzipAesCryptoStream : Stream
|
|||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int ReadTransformOneBlock(byte[] buffer, int offset, int last)
|
private void FillCounterOut()
|
||||||
{
|
{
|
||||||
if (_isFinalBlock)
|
|
||||||
{
|
|
||||||
throw new ArchiveOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
var bytesRemaining = last - offset;
|
|
||||||
var bytesToRead =
|
|
||||||
(bytesRemaining > BLOCK_SIZE_IN_BYTES) ? BLOCK_SIZE_IN_BYTES : bytesRemaining;
|
|
||||||
|
|
||||||
// update the counter
|
// update the counter
|
||||||
BinaryPrimitives.WriteInt32LittleEndian(_counter, _nonce++);
|
BinaryPrimitives.WriteInt32LittleEndian(_counter, _nonce++);
|
||||||
|
_transform.TransformBlock(
|
||||||
// Determine if this is the final block
|
_counter,
|
||||||
if ((bytesToRead == bytesRemaining) && (_totalBytesLeftToRead == 0))
|
0, // offset
|
||||||
{
|
BLOCK_SIZE_IN_BYTES,
|
||||||
_counterOut = _transform.TransformFinalBlock(_counter, 0, BLOCK_SIZE_IN_BYTES);
|
_counterOut,
|
||||||
_isFinalBlock = true;
|
0
|
||||||
}
|
); // offset
|
||||||
else
|
_counterOutOffset = 0;
|
||||||
{
|
|
||||||
_transform.TransformBlock(
|
|
||||||
_counter,
|
|
||||||
0, // offset
|
|
||||||
BLOCK_SIZE_IN_BYTES,
|
|
||||||
_counterOut,
|
|
||||||
0
|
|
||||||
); // offset
|
|
||||||
}
|
|
||||||
|
|
||||||
XorInPlace(buffer, offset, bytesToRead);
|
|
||||||
return bytesToRead;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void XorInPlace(byte[] buffer, int offset, int count)
|
private void XorInPlace(byte[] buffer, int offset, int count, int counterOffset)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < count; i++)
|
for (var i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
buffer[offset + i] = (byte)(_counterOut[i] ^ buffer[offset + i]);
|
buffer[offset + i] = (byte)(_counterOut[counterOffset + i] ^ buffer[offset + i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReadTransformBlocks(byte[] buffer, int offset, int count)
|
private void ReadTransformBlocks(byte[] buffer, int offset, int count)
|
||||||
{
|
{
|
||||||
var posn = offset;
|
var posn = offset;
|
||||||
var last = count + offset;
|
var remaining = count;
|
||||||
|
|
||||||
while (posn < buffer.Length && posn < last)
|
while (posn < buffer.Length && remaining > 0)
|
||||||
{
|
{
|
||||||
var n = ReadTransformOneBlock(buffer, posn, last);
|
if (_counterOutOffset == BLOCK_SIZE_IN_BYTES)
|
||||||
posn += n;
|
{
|
||||||
|
FillCounterOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytesToXor = Math.Min(BLOCK_SIZE_IN_BYTES - _counterOutOffset, remaining);
|
||||||
|
XorInPlace(buffer, posn, bytesToXor, _counterOutOffset);
|
||||||
|
_counterOutOffset += bytesToXor;
|
||||||
|
posn += bytesToXor;
|
||||||
|
remaining -= bytesToXor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,10 +48,8 @@ internal abstract partial class ZipFilePart
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(
|
Header.CompressedSize == 0
|
||||||
Header.CompressedSize == 0
|
&& FlagUtility.HasFlag(Header.Flags, HeaderFlags.UsePostDataDescriptor)
|
||||||
&& FlagUtility.HasFlag(Header.Flags, HeaderFlags.UsePostDataDescriptor)
|
|
||||||
) || Header.IsZip64
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
plainStream = SharpCompressStream.CreateNonDisposing(plainStream); //make sure AES doesn't close
|
plainStream = SharpCompressStream.CreateNonDisposing(plainStream); //make sure AES doesn't close
|
||||||
|
|||||||
@@ -234,10 +234,8 @@ internal abstract partial class ZipFilePart : FilePart
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(
|
Header.CompressedSize == 0
|
||||||
Header.CompressedSize == 0
|
&& FlagUtility.HasFlag(Header.Flags, HeaderFlags.UsePostDataDescriptor)
|
||||||
&& FlagUtility.HasFlag(Header.Flags, HeaderFlags.UsePostDataDescriptor)
|
|
||||||
) || Header.IsZip64
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
plainStream = SharpCompressStream.CreateNonDisposing(plainStream); //make sure AES doesn't close
|
plainStream = SharpCompressStream.CreateNonDisposing(plainStream); //make sure AES doesn't close
|
||||||
|
|||||||
@@ -73,6 +73,10 @@ public partial class ArcLzwStream : Stream
|
|||||||
|
|
||||||
if (useCrunched)
|
if (useCrunched)
|
||||||
{
|
{
|
||||||
|
if (input.Length == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("ArcLzwStream: compressed data is empty");
|
||||||
|
}
|
||||||
if (input[0] != BITS)
|
if (input[0] != BITS)
|
||||||
{
|
{
|
||||||
throw new InvalidFormatException($"File packed with {input[0]}, expected {BITS}.");
|
throw new InvalidFormatException($"File packed with {input[0]}, expected {BITS}.");
|
||||||
@@ -129,6 +133,10 @@ public partial class ArcLzwStream : Stream
|
|||||||
|
|
||||||
while (code >= 256)
|
while (code >= 256)
|
||||||
{
|
{
|
||||||
|
if (code >= suffix.Length)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("ArcLzwStream: code out of range");
|
||||||
|
}
|
||||||
stack.Push(suffix[code]);
|
stack.Push(suffix[code]);
|
||||||
code = prefix[code];
|
code = prefix[code];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,6 +237,10 @@ internal partial class CBZip2InputStream
|
|||||||
|
|
||||||
/* Now the selectors */
|
/* Now the selectors */
|
||||||
nGroups = await BsRAsync(3, cancellationToken).ConfigureAwait(false);
|
nGroups = await BsRAsync(3, cancellationToken).ConfigureAwait(false);
|
||||||
|
if (nGroups < 2 || nGroups > BZip2Constants.N_GROUPS)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: invalid number of Huffman trees");
|
||||||
|
}
|
||||||
nSelectors = await BsRAsync(15, cancellationToken).ConfigureAwait(false);
|
nSelectors = await BsRAsync(15, cancellationToken).ConfigureAwait(false);
|
||||||
for (i = 0; i < nSelectors; i++)
|
for (i = 0; i < nSelectors; i++)
|
||||||
{
|
{
|
||||||
@@ -244,6 +248,10 @@ internal partial class CBZip2InputStream
|
|||||||
while (await BsRAsync(1, cancellationToken).ConfigureAwait(false) == 1)
|
while (await BsRAsync(1, cancellationToken).ConfigureAwait(false) == 1)
|
||||||
{
|
{
|
||||||
j++;
|
j++;
|
||||||
|
if (j >= nGroups)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: invalid selector MTF value");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (i < BZip2Constants.MAX_SELECTORS)
|
if (i < BZip2Constants.MAX_SELECTORS)
|
||||||
{
|
{
|
||||||
@@ -266,6 +274,10 @@ internal partial class CBZip2InputStream
|
|||||||
for (i = 0; i < nSelectors; i++)
|
for (i = 0; i < nSelectors; i++)
|
||||||
{
|
{
|
||||||
v = selectorMtf[i];
|
v = selectorMtf[i];
|
||||||
|
if (v >= nGroups)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: selector MTF value out of range");
|
||||||
|
}
|
||||||
tmp = pos[v];
|
tmp = pos[v];
|
||||||
while (v > 0)
|
while (v > 0)
|
||||||
{
|
{
|
||||||
@@ -374,6 +386,10 @@ internal partial class CBZip2InputStream
|
|||||||
while (zvec > limit[zt][zn])
|
while (zvec > limit[zt][zn])
|
||||||
{
|
{
|
||||||
zn++;
|
zn++;
|
||||||
|
if (zn >= BZip2Constants.MAX_CODE_LEN)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: Huffman code too long");
|
||||||
|
}
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
while (bsLive < 1)
|
while (bsLive < 1)
|
||||||
@@ -405,7 +421,14 @@ internal partial class CBZip2InputStream
|
|||||||
}
|
}
|
||||||
zvec = (zvec << 1) | zj;
|
zvec = (zvec << 1) | zj;
|
||||||
}
|
}
|
||||||
nextSym = perm[zt][zvec - basev[zt][zn]];
|
{
|
||||||
|
int permIdx = zvec - basev[zt][zn];
|
||||||
|
if (permIdx < 0 || permIdx >= perm[zt].Length)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: invalid Huffman symbol");
|
||||||
|
}
|
||||||
|
nextSym = perm[zt][permIdx];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
@@ -448,6 +471,10 @@ internal partial class CBZip2InputStream
|
|||||||
while (zvec > limit[zt][zn])
|
while (zvec > limit[zt][zn])
|
||||||
{
|
{
|
||||||
zn++;
|
zn++;
|
||||||
|
if (zn >= BZip2Constants.MAX_CODE_LEN)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: Huffman code too long");
|
||||||
|
}
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
while (bsLive < 1)
|
while (bsLive < 1)
|
||||||
@@ -479,7 +506,14 @@ internal partial class CBZip2InputStream
|
|||||||
}
|
}
|
||||||
zvec = (zvec << 1) | zj;
|
zvec = (zvec << 1) | zj;
|
||||||
}
|
}
|
||||||
nextSym = perm[zt][zvec - basev[zt][zn]];
|
{
|
||||||
|
int permIdx = zvec - basev[zt][zn];
|
||||||
|
if (permIdx < 0 || permIdx >= perm[zt].Length)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: invalid Huffman symbol");
|
||||||
|
}
|
||||||
|
nextSym = perm[zt][permIdx];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} while (nextSym == BZip2Constants.RUNA || nextSym == BZip2Constants.RUNB);
|
} while (nextSym == BZip2Constants.RUNA || nextSym == BZip2Constants.RUNB);
|
||||||
|
|
||||||
@@ -550,6 +584,10 @@ internal partial class CBZip2InputStream
|
|||||||
while (zvec > limit[zt][zn])
|
while (zvec > limit[zt][zn])
|
||||||
{
|
{
|
||||||
zn++;
|
zn++;
|
||||||
|
if (zn >= BZip2Constants.MAX_CODE_LEN)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: Huffman code too long");
|
||||||
|
}
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
while (bsLive < 1)
|
while (bsLive < 1)
|
||||||
@@ -581,7 +619,14 @@ internal partial class CBZip2InputStream
|
|||||||
}
|
}
|
||||||
zvec = (zvec << 1) | zj;
|
zvec = (zvec << 1) | zj;
|
||||||
}
|
}
|
||||||
nextSym = perm[zt][zvec - basev[zt][zn]];
|
{
|
||||||
|
int permIdx = zvec - basev[zt][zn];
|
||||||
|
if (permIdx < 0 || permIdx >= perm[zt].Length)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: invalid Huffman symbol");
|
||||||
|
}
|
||||||
|
nextSym = perm[zt][permIdx];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -605,10 +650,18 @@ internal partial class CBZip2InputStream
|
|||||||
for (i = 0; i <= last; i++)
|
for (i = 0; i <= last; i++)
|
||||||
{
|
{
|
||||||
ch = ll8[i];
|
ch = ll8[i];
|
||||||
|
if (cftab[ch] < 0 || cftab[ch] >= tt.Length)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: block data out of bounds");
|
||||||
|
}
|
||||||
tt[cftab[ch]] = i;
|
tt[cftab[ch]] = i;
|
||||||
cftab[ch]++;
|
cftab[ch]++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (origPtr < 0 || origPtr >= tt.Length)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: origPtr out of bounds");
|
||||||
|
}
|
||||||
tPos = tt[origPtr];
|
tPos = tt[origPtr];
|
||||||
|
|
||||||
count = 0;
|
count = 0;
|
||||||
@@ -806,6 +859,10 @@ internal partial class CBZip2InputStream
|
|||||||
int v;
|
int v;
|
||||||
while (bsLive < n)
|
while (bsLive < n)
|
||||||
{
|
{
|
||||||
|
if (bsStream is null)
|
||||||
|
{
|
||||||
|
CompressedStreamEOF();
|
||||||
|
}
|
||||||
int zzi;
|
int zzi;
|
||||||
int thech = '\0';
|
int thech = '\0';
|
||||||
var b = ArrayPool<byte>.Shared.Rent(1);
|
var b = ArrayPool<byte>.Shared.Rent(1);
|
||||||
@@ -858,7 +915,10 @@ internal partial class CBZip2InputStream
|
|||||||
cbZip2InputStream.ll8 = null;
|
cbZip2InputStream.ll8 = null;
|
||||||
cbZip2InputStream.tt = null;
|
cbZip2InputStream.tt = null;
|
||||||
cbZip2InputStream.BsSetStream(zStream);
|
cbZip2InputStream.BsSetStream(zStream);
|
||||||
await cbZip2InputStream.InitializeAsync(true, cancellationToken).ConfigureAwait(false);
|
if (!await cbZip2InputStream.InitializeAsync(true, cancellationToken).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("Not a valid BZip2 stream");
|
||||||
|
}
|
||||||
await cbZip2InputStream.InitBlockAsync(cancellationToken).ConfigureAwait(false);
|
await cbZip2InputStream.InitBlockAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await cbZip2InputStream.SetupBlockAsync(cancellationToken).ConfigureAwait(false);
|
await cbZip2InputStream.SetupBlockAsync(cancellationToken).ConfigureAwait(false);
|
||||||
return cbZip2InputStream;
|
return cbZip2InputStream;
|
||||||
|
|||||||
@@ -179,7 +179,10 @@ internal partial class CBZip2InputStream : Stream
|
|||||||
cbZip2InputStream.ll8 = null;
|
cbZip2InputStream.ll8 = null;
|
||||||
cbZip2InputStream.tt = null;
|
cbZip2InputStream.tt = null;
|
||||||
cbZip2InputStream.BsSetStream(zStream);
|
cbZip2InputStream.BsSetStream(zStream);
|
||||||
cbZip2InputStream.Initialize(true);
|
if (!cbZip2InputStream.Initialize(true))
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("Not a valid BZip2 stream");
|
||||||
|
}
|
||||||
cbZip2InputStream.InitBlock();
|
cbZip2InputStream.InitBlock();
|
||||||
cbZip2InputStream.SetupBlock();
|
cbZip2InputStream.SetupBlock();
|
||||||
return cbZip2InputStream;
|
return cbZip2InputStream;
|
||||||
@@ -403,6 +406,10 @@ internal partial class CBZip2InputStream : Stream
|
|||||||
int v;
|
int v;
|
||||||
while (bsLive < n)
|
while (bsLive < n)
|
||||||
{
|
{
|
||||||
|
if (bsStream is null)
|
||||||
|
{
|
||||||
|
CompressedStreamEOF();
|
||||||
|
}
|
||||||
int zzi;
|
int zzi;
|
||||||
int thech = '\0';
|
int thech = '\0';
|
||||||
try
|
try
|
||||||
@@ -477,6 +484,10 @@ internal partial class CBZip2InputStream : Stream
|
|||||||
}
|
}
|
||||||
for (i = 0; i < alphaSize; i++)
|
for (i = 0; i < alphaSize; i++)
|
||||||
{
|
{
|
||||||
|
if (length[i] >= BZip2Constants.MAX_CODE_LEN)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: invalid Huffman code length");
|
||||||
|
}
|
||||||
basev[length[i] + 1]++;
|
basev[length[i] + 1]++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -553,6 +564,10 @@ internal partial class CBZip2InputStream : Stream
|
|||||||
|
|
||||||
/* Now the selectors */
|
/* Now the selectors */
|
||||||
nGroups = BsR(3);
|
nGroups = BsR(3);
|
||||||
|
if (nGroups < 2 || nGroups > BZip2Constants.N_GROUPS)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: invalid number of Huffman trees");
|
||||||
|
}
|
||||||
nSelectors = BsR(15);
|
nSelectors = BsR(15);
|
||||||
for (i = 0; i < nSelectors; i++)
|
for (i = 0; i < nSelectors; i++)
|
||||||
{
|
{
|
||||||
@@ -560,6 +575,10 @@ internal partial class CBZip2InputStream : Stream
|
|||||||
while (BsR(1) == 1)
|
while (BsR(1) == 1)
|
||||||
{
|
{
|
||||||
j++;
|
j++;
|
||||||
|
if (j >= nGroups)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: invalid selector MTF value");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (i < BZip2Constants.MAX_SELECTORS)
|
if (i < BZip2Constants.MAX_SELECTORS)
|
||||||
{
|
{
|
||||||
@@ -582,6 +601,10 @@ internal partial class CBZip2InputStream : Stream
|
|||||||
for (i = 0; i < nSelectors; i++)
|
for (i = 0; i < nSelectors; i++)
|
||||||
{
|
{
|
||||||
v = selectorMtf[i];
|
v = selectorMtf[i];
|
||||||
|
if (v >= nGroups)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: selector MTF value out of range");
|
||||||
|
}
|
||||||
tmp = pos[v];
|
tmp = pos[v];
|
||||||
while (v > 0)
|
while (v > 0)
|
||||||
{
|
{
|
||||||
@@ -689,6 +712,10 @@ internal partial class CBZip2InputStream : Stream
|
|||||||
while (zvec > limit[zt][zn])
|
while (zvec > limit[zt][zn])
|
||||||
{
|
{
|
||||||
zn++;
|
zn++;
|
||||||
|
if (zn >= BZip2Constants.MAX_CODE_LEN)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: Huffman code too long");
|
||||||
|
}
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
while (bsLive < 1)
|
while (bsLive < 1)
|
||||||
@@ -717,7 +744,14 @@ internal partial class CBZip2InputStream : Stream
|
|||||||
}
|
}
|
||||||
zvec = (zvec << 1) | zj;
|
zvec = (zvec << 1) | zj;
|
||||||
}
|
}
|
||||||
nextSym = perm[zt][zvec - basev[zt][zn]];
|
{
|
||||||
|
int permIdx = zvec - basev[zt][zn];
|
||||||
|
if (permIdx < 0 || permIdx >= perm[zt].Length)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: invalid Huffman symbol");
|
||||||
|
}
|
||||||
|
nextSym = perm[zt][permIdx];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
@@ -760,6 +794,10 @@ internal partial class CBZip2InputStream : Stream
|
|||||||
while (zvec > limit[zt][zn])
|
while (zvec > limit[zt][zn])
|
||||||
{
|
{
|
||||||
zn++;
|
zn++;
|
||||||
|
if (zn >= BZip2Constants.MAX_CODE_LEN)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: Huffman code too long");
|
||||||
|
}
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
while (bsLive < 1)
|
while (bsLive < 1)
|
||||||
@@ -788,7 +826,14 @@ internal partial class CBZip2InputStream : Stream
|
|||||||
}
|
}
|
||||||
zvec = (zvec << 1) | zj;
|
zvec = (zvec << 1) | zj;
|
||||||
}
|
}
|
||||||
nextSym = perm[zt][zvec - basev[zt][zn]];
|
{
|
||||||
|
int permIdx = zvec - basev[zt][zn];
|
||||||
|
if (permIdx < 0 || permIdx >= perm[zt].Length)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: invalid Huffman symbol");
|
||||||
|
}
|
||||||
|
nextSym = perm[zt][permIdx];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} while (nextSym == BZip2Constants.RUNA || nextSym == BZip2Constants.RUNB);
|
} while (nextSym == BZip2Constants.RUNA || nextSym == BZip2Constants.RUNB);
|
||||||
|
|
||||||
@@ -859,6 +904,10 @@ internal partial class CBZip2InputStream : Stream
|
|||||||
while (zvec > limit[zt][zn])
|
while (zvec > limit[zt][zn])
|
||||||
{
|
{
|
||||||
zn++;
|
zn++;
|
||||||
|
if (zn >= BZip2Constants.MAX_CODE_LEN)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: Huffman code too long");
|
||||||
|
}
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
while (bsLive < 1)
|
while (bsLive < 1)
|
||||||
@@ -883,7 +932,14 @@ internal partial class CBZip2InputStream : Stream
|
|||||||
}
|
}
|
||||||
zvec = (zvec << 1) | zj;
|
zvec = (zvec << 1) | zj;
|
||||||
}
|
}
|
||||||
nextSym = perm[zt][zvec - basev[zt][zn]];
|
{
|
||||||
|
int permIdx = zvec - basev[zt][zn];
|
||||||
|
if (permIdx < 0 || permIdx >= perm[zt].Length)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: invalid Huffman symbol");
|
||||||
|
}
|
||||||
|
nextSym = perm[zt][permIdx];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -907,10 +963,18 @@ internal partial class CBZip2InputStream : Stream
|
|||||||
for (i = 0; i <= last; i++)
|
for (i = 0; i <= last; i++)
|
||||||
{
|
{
|
||||||
ch = ll8[i];
|
ch = ll8[i];
|
||||||
|
if (cftab[ch] < 0 || cftab[ch] >= tt.Length)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: block data out of bounds");
|
||||||
|
}
|
||||||
tt[cftab[ch]] = i;
|
tt[cftab[ch]] = i;
|
||||||
cftab[ch]++;
|
cftab[ch]++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (origPtr < 0 || origPtr >= tt.Length)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("BZip2: origPtr out of bounds");
|
||||||
|
}
|
||||||
tPos = tt[origPtr];
|
tPos = tt[origPtr];
|
||||||
|
|
||||||
count = 0;
|
count = 0;
|
||||||
|
|||||||
@@ -208,6 +208,10 @@ internal sealed class HuffmanTree
|
|||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
|
if (index < 0 || index >= array.Length)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("Deflate64: invalid Huffman data");
|
||||||
|
}
|
||||||
var value = array[index];
|
var value = array[index];
|
||||||
|
|
||||||
if (value == 0)
|
if (value == 0)
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ public partial class ExplodeStream
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
var ex = new ExplodeStream(inStr, compressedSize, uncompressedSize, generalPurposeBitFlag);
|
var ex = new ExplodeStream(inStr, compressedSize, uncompressedSize, generalPurposeBitFlag);
|
||||||
await ex.explode_SetTables_async(cancellationToken).ConfigureAwait(false);
|
if (await ex.explode_SetTables_async(cancellationToken).ConfigureAwait(false) != 0)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("ExplodeStream: invalid Huffman table data");
|
||||||
|
}
|
||||||
ex.explode_var_init();
|
ex.explode_var_init();
|
||||||
return ex;
|
return ex;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,10 @@ public partial class ExplodeStream : Stream
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
var ex = new ExplodeStream(inStr, compressedSize, uncompressedSize, generalPurposeBitFlag);
|
var ex = new ExplodeStream(inStr, compressedSize, uncompressedSize, generalPurposeBitFlag);
|
||||||
ex.explode_SetTables();
|
if (ex.explode_SetTables() != 0)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("ExplodeStream: invalid Huffman table data");
|
||||||
|
}
|
||||||
ex.explode_var_init();
|
ex.explode_var_init();
|
||||||
return ex;
|
return ex;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Buffers;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using SharpCompress.Common;
|
||||||
|
|
||||||
namespace SharpCompress.Compressors.LZMA.LZ;
|
namespace SharpCompress.Compressors.LZMA.LZ;
|
||||||
|
|
||||||
@@ -25,6 +26,10 @@ internal partial class OutWindow : IDisposable
|
|||||||
|
|
||||||
public void Create(int windowSize)
|
public void Create(int windowSize)
|
||||||
{
|
{
|
||||||
|
if (windowSize <= 0)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException($"LZMA: invalid dictionary size {windowSize}");
|
||||||
|
}
|
||||||
if (_windowSize != windowSize)
|
if (_windowSize != windowSize)
|
||||||
{
|
{
|
||||||
if (_buffer is not null)
|
if (_buffer is not null)
|
||||||
|
|||||||
@@ -70,7 +70,15 @@ public partial class LzwStream
|
|||||||
{
|
{
|
||||||
if (!headerParsed)
|
if (!headerParsed)
|
||||||
{
|
{
|
||||||
await ParseHeaderAsync(cancellationToken).ConfigureAwait(false);
|
try
|
||||||
|
{
|
||||||
|
await ParseHeaderAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
eof = true;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eof)
|
if (eof)
|
||||||
@@ -348,6 +356,17 @@ public partial class LzwStream
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (maxBits < LzwConstants.INIT_BITS)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException(
|
||||||
|
"Stream compressed with "
|
||||||
|
+ maxBits
|
||||||
|
+ " bits, but minimum supported is "
|
||||||
|
+ LzwConstants.INIT_BITS
|
||||||
|
+ " bits."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if ((hdr[2] & LzwConstants.RESERVED_MASK) > 0)
|
if ((hdr[2] & LzwConstants.RESERVED_MASK) > 0)
|
||||||
{
|
{
|
||||||
throw new ArchiveException("Unsupported bits set in the header.");
|
throw new ArchiveException("Unsupported bits set in the header.");
|
||||||
|
|||||||
@@ -129,7 +129,15 @@ public partial class LzwStream : Stream
|
|||||||
{
|
{
|
||||||
if (!headerParsed)
|
if (!headerParsed)
|
||||||
{
|
{
|
||||||
ParseHeader();
|
try
|
||||||
|
{
|
||||||
|
ParseHeader();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
eof = true;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eof)
|
if (eof)
|
||||||
@@ -421,6 +429,17 @@ public partial class LzwStream : Stream
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (maxBits < LzwConstants.INIT_BITS)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException(
|
||||||
|
"Stream compressed with "
|
||||||
|
+ maxBits
|
||||||
|
+ " bits, but minimum supported is "
|
||||||
|
+ LzwConstants.INIT_BITS
|
||||||
|
+ " bits."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if ((hdr[2] & LzwConstants.RESERVED_MASK) > 0)
|
if ((hdr[2] & LzwConstants.RESERVED_MASK) > 0)
|
||||||
{
|
{
|
||||||
throw new ArchiveException("Unsupported bits set in the header.");
|
throw new ArchiveException("Unsupported bits set in the header.");
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using SharpCompress.Common;
|
||||||
|
|
||||||
// This is a port of Dmitry Shkarin's PPMd Variant I Revision 1.
|
// This is a port of Dmitry Shkarin's PPMd Variant I Revision 1.
|
||||||
// Ported by Michael Bone (mjbone03@yahoo.com.au).
|
// Ported by Michael Bone (mjbone03@yahoo.com.au).
|
||||||
@@ -253,6 +254,10 @@ internal partial class Model
|
|||||||
_coder.RangeDecoderInitialize(source);
|
_coder.RangeDecoderInitialize(source);
|
||||||
StartModel(properties.ModelOrder, properties.RestorationMethod);
|
StartModel(properties.ModelOrder, properties.RestorationMethod);
|
||||||
_minimumContext = _maximumContext;
|
_minimumContext = _maximumContext;
|
||||||
|
if (_minimumContext == PpmContext.ZERO)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("PPMd: model context not initialized");
|
||||||
|
}
|
||||||
_numberStatistics = _minimumContext.NumberStatistics;
|
_numberStatistics = _minimumContext.NumberStatistics;
|
||||||
return _coder;
|
return _coder;
|
||||||
}
|
}
|
||||||
@@ -268,6 +273,10 @@ internal partial class Model
|
|||||||
await _coder.RangeDecoderInitializeAsync(source, cancellationToken).ConfigureAwait(false);
|
await _coder.RangeDecoderInitializeAsync(source, cancellationToken).ConfigureAwait(false);
|
||||||
StartModel(properties.ModelOrder, properties.RestorationMethod);
|
StartModel(properties.ModelOrder, properties.RestorationMethod);
|
||||||
_minimumContext = _maximumContext;
|
_minimumContext = _maximumContext;
|
||||||
|
if (_minimumContext == PpmContext.ZERO)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("PPMd: model context not initialized");
|
||||||
|
}
|
||||||
_numberStatistics = _minimumContext.NumberStatistics;
|
_numberStatistics = _minimumContext.NumberStatistics;
|
||||||
return _coder;
|
return _coder;
|
||||||
}
|
}
|
||||||
@@ -429,13 +438,16 @@ internal partial class Model
|
|||||||
if (modelOrder < 2)
|
if (modelOrder < 2)
|
||||||
{
|
{
|
||||||
_orderFall = _modelOrder;
|
_orderFall = _modelOrder;
|
||||||
for (
|
if (_maximumContext != PpmContext.ZERO)
|
||||||
var context = _maximumContext;
|
|
||||||
context.Suffix != PpmContext.ZERO;
|
|
||||||
context = context.Suffix
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
_orderFall--;
|
for (
|
||||||
|
var context = _maximumContext;
|
||||||
|
context.Suffix != PpmContext.ZERO;
|
||||||
|
context = context.Suffix
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_orderFall--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using SharpCompress.Common;
|
||||||
using SharpCompress.IO;
|
using SharpCompress.IO;
|
||||||
|
|
||||||
namespace SharpCompress.Compressors.Reduce;
|
namespace SharpCompress.Compressors.Reduce;
|
||||||
@@ -96,6 +97,10 @@ public partial class ReduceStream
|
|||||||
cancellationToken
|
cancellationToken
|
||||||
)
|
)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
if (nextByteIndex >= nextByteTable[outByte].Length)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("ReduceStream: next byte table index out of range");
|
||||||
|
}
|
||||||
outByte = nextByteTable[outByte][nextByteIndex];
|
outByte = nextByteTable[outByte][nextByteIndex];
|
||||||
return outByte;
|
return outByte;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using SharpCompress.Common;
|
||||||
|
|
||||||
namespace SharpCompress.Compressors.Reduce;
|
namespace SharpCompress.Compressors.Reduce;
|
||||||
|
|
||||||
@@ -192,6 +193,10 @@ public partial class ReduceStream : Stream
|
|||||||
return outByte;
|
return outByte;
|
||||||
}
|
}
|
||||||
READBITS(bitCountTable[nextByteTable[outByte].Length], out byte nextByteIndex);
|
READBITS(bitCountTable[nextByteTable[outByte].Length], out byte nextByteIndex);
|
||||||
|
if (nextByteIndex >= nextByteTable[outByte].Length)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("ReduceStream: next byte table index out of range");
|
||||||
|
}
|
||||||
outByte = nextByteTable[outByte][nextByteIndex];
|
outByte = nextByteTable[outByte][nextByteIndex];
|
||||||
return outByte;
|
return outByte;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,10 @@ public partial class SqueezeStream
|
|||||||
huffmanDecoded.WriteByte((byte)i);
|
huffmanDecoded.WriteByte((byte)i);
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
|
else if (i >= numnodes)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("SqueezeStream: invalid Huffman tree node index");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
huffmanDecoded.Position = 0;
|
huffmanDecoded.Position = 0;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using SharpCompress.Common;
|
||||||
using SharpCompress.Compressors.RLE90;
|
using SharpCompress.Compressors.RLE90;
|
||||||
|
|
||||||
namespace SharpCompress.Compressors.Squeezed;
|
namespace SharpCompress.Compressors.Squeezed;
|
||||||
@@ -93,6 +94,10 @@ public partial class SqueezeStream : Stream
|
|||||||
huffmanDecoded.WriteByte((byte)i);
|
huffmanDecoded.WriteByte((byte)i);
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
|
else if (i >= numnodes)
|
||||||
|
{
|
||||||
|
throw new InvalidFormatException("SqueezeStream: invalid Huffman tree node index");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
huffmanDecoded.Position = 0;
|
huffmanDecoded.Position = 0;
|
||||||
|
|||||||
@@ -76,7 +76,8 @@ public sealed record ReaderOptions : IReaderOptions
|
|||||||
/// by rewinding and re-reading the same data.
|
/// by rewinding and re-reading the same data.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// <b>Default:</b> Constants.RewindableBufferSize (81920 bytes / 81KB)
|
/// <b>Default:</b> Constants.RewindableBufferSize (163840 bytes / 160KB) - sized to cover
|
||||||
|
/// ZStandard's worst-case first block on a tar archive (~131KB including header overhead).
|
||||||
/// </para>
|
/// </para>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// <b>Typical usage:</b> 500-1000 bytes for most archives
|
/// <b>Typical usage:</b> 500-1000 bytes for most archives
|
||||||
|
|||||||
134
tests/SharpCompress.Test/MalformedInputTests.cs
Normal file
134
tests/SharpCompress.Test/MalformedInputTests.cs
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
#if !LEGACY_DOTNET
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using AwesomeAssertions;
|
||||||
|
using SharpCompress.Common;
|
||||||
|
using SharpCompress.Readers;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SharpCompress.Test;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that malformed compressed input is handled gracefully, throwing library exceptions
|
||||||
|
/// rather than unhandled IndexOutOfRangeException, DivideByZeroException, or NullReferenceException.
|
||||||
|
/// </summary>
|
||||||
|
public class MalformedInputTests
|
||||||
|
{
|
||||||
|
private static void VerifyMalformedInputThrowsLibraryException(string hex)
|
||||||
|
{
|
||||||
|
var data = Convert.FromHexString(hex);
|
||||||
|
using var ms = new MemoryStream(data);
|
||||||
|
var buf = new byte[4096];
|
||||||
|
|
||||||
|
Action act = () =>
|
||||||
|
{
|
||||||
|
using var reader = ReaderFactory.OpenReader(ms);
|
||||||
|
while (reader.MoveToNextEntry())
|
||||||
|
{
|
||||||
|
if (!reader.Entry.IsDirectory)
|
||||||
|
{
|
||||||
|
using var entryStream = reader.OpenEntryStream();
|
||||||
|
while (entryStream.Read(buf, 0, buf.Length) > 0) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
act.Should()
|
||||||
|
.Throw<Exception>()
|
||||||
|
.And.Should()
|
||||||
|
.BeAssignableTo<SharpCompressException>(
|
||||||
|
"malformed input should throw a library exception, not a raw system exception"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void LzwStream_DivideByZero_ThrowsLibraryException()
|
||||||
|
{
|
||||||
|
// LZW stream with invalid header that would cause DivideByZero on subsequent reads
|
||||||
|
VerifyMalformedInputThrowsLibraryException(
|
||||||
|
"1f9d1a362f20000000130003edd1310a8030f1605ca2b26245c47b97e6d615e29400000000130003edd1310a8030f1605c606060606060606060606060606060606060606060606060007f60606060280000"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void LzwStream_IndexOutOfRange_ThrowsLibraryException()
|
||||||
|
{
|
||||||
|
// LZW stream with maxBits < INIT_BITS causing table size mismatch
|
||||||
|
VerifyMalformedInputThrowsLibraryException(
|
||||||
|
"1f9d0836e1553ac4e1ce9ea227000000000000001070b4058faf051127c54144f8bfe54192e141bab6efe8032c41cd64004aef53da4acc8077a5b26245c47b97e6d615e29400000000000003edd1310a8030f1e2ee66ff535d800000000b00000000"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BZip2_NullRef_InBsR_ThrowsLibraryException()
|
||||||
|
{
|
||||||
|
// BZip2 stream with invalid block size causing null bsStream access
|
||||||
|
VerifyMalformedInputThrowsLibraryException(
|
||||||
|
"425a6857575757575768575757575757fff2fff27c007159425a6857ff0f21007159c1e2d5e2"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BZip2_IndexOutOfRange_InGetAndMoveToFrontDecode_ThrowsLibraryException()
|
||||||
|
{
|
||||||
|
// BZip2 with malformed Huffman tables causing code-too-long or bad perm index
|
||||||
|
VerifyMalformedInputThrowsLibraryException(
|
||||||
|
"425a6839314159265359c1c080e2000001410000100244a000305a6839314159265359c1c080e2000001410000100244a00030cd00c3cd00c34629971772c080e2"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SqueezeStream_IndexOutOfRange_ThrowsLibraryException()
|
||||||
|
{
|
||||||
|
// Squeezed ARC stream with malformed Huffman tree node indices
|
||||||
|
VerifyMalformedInputThrowsLibraryException(
|
||||||
|
"1a041a425a081a0000090000606839425a081730765cbb311042265300040000090000606839425a081730765cbb31104226530053"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ArcLzwStream_IndexOutOfRange_ThrowsLibraryException()
|
||||||
|
{
|
||||||
|
// ARC LZW stream with empty or malformed compressed data
|
||||||
|
VerifyMalformedInputThrowsLibraryException(
|
||||||
|
"1a081a1931081a00000000f9ffffff00000000ddff000000000000000000000000000012006068394200000080c431b37fff531042d9ff"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExplodeStream_IndexOutOfRange_ThrowsLibraryException()
|
||||||
|
{
|
||||||
|
// ZIP entry using Implode/Explode with invalid Huffman tables
|
||||||
|
VerifyMalformedInputThrowsLibraryException(
|
||||||
|
"504b03040a000000060000ff676767676767676767676767676700000000683a36060000676767676767676767676700000000000000000000000000000000000000000000000000000000630000000000800000000000002e7478745554090003a8c8b6696045ac6975780b000104e803000004e803000068656c6c6f0a504b01021e030a0000000000147f6f5c20303a3639314159265359c1c080e2000001410000100244a00030cd00c346299717786975870b000104e8030000780b000104e803000004e8030000504b050600000000010000e74f004040490000000064"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Deflate64_IndexOutOfRange_ThrowsLibraryException()
|
||||||
|
{
|
||||||
|
// ZIP entry using Deflate64 with invalid Huffman data
|
||||||
|
VerifyMalformedInputThrowsLibraryException(
|
||||||
|
"504b03040a00009709001c0068656c6c6f2e807874555409000000000000147f6f5c20303a36060000ff0600000009425a6839314159265359595959595959a481000000000000000000007478925554050001c601003dffff000000000000001e000000001e00000000000000000000e1490000000000"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PPMd_NullRef_ThrowsLibraryException()
|
||||||
|
{
|
||||||
|
// ZIP entry using PPMd with malformed properties triggering uninitialized model access
|
||||||
|
VerifyMalformedInputThrowsLibraryException(
|
||||||
|
"504b03040000007462001c905c206600fa80ffffffffff1f8b0a00000000000003edd1310a80cf0c00090010000b000000e000000000030000002e000000686515e294362f763ac439d493d62a3671081e05c14114b4058faf051127c54144f8bfe541ace141bab6ef643c2ce2000001410000100244a00040cd41bdc76c4aef3977a5b25645c47b97e6d615e294362f763ac439d493d62a367108f1e2ee66ff535efa7f3015e2943601003ac439d493d62a3671081e05c14114b4058faf3a0003edd1310a80cf8597e6d60500140409"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void LZMA_NullRef_ThrowsLibraryException()
|
||||||
|
{
|
||||||
|
// ZIP entry using LZMA with invalid dictionary size (0) causing null window buffer access
|
||||||
|
VerifyMalformedInputThrowsLibraryException(
|
||||||
|
"504b03040a0200000e001c0068646c6c6f2e7478745554ac507578000000000000000000000000000000000000000000e80300000000000068030a0000000000147f040020303a360600002e7478745554090003a8c8b6696045ac69f5780b0006ff1d000908180000e8030000000000a4810000109a9a9a8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b9a0000000000000000000000e80300000000000068030a0000009a9a9a504b03440a6fcb486c6c6f2e74ffff"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
257
tests/SharpCompress.Test/Streams/WinzipAesCryptoStreamTests.cs
Normal file
257
tests/SharpCompress.Test/Streams/WinzipAesCryptoStreamTests.cs
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
using System;
|
||||||
|
using System.Buffers.Binary;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using SharpCompress.Common.Zip;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SharpCompress.Test.Streams;
|
||||||
|
|
||||||
|
public class WinzipAesCryptoStreamTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Read_Decrypts_Data_For_Aligned_Buffer_Size()
|
||||||
|
{
|
||||||
|
const string password = "sample-password";
|
||||||
|
byte[] plainText = Enumerable.Range(0, 64).Select(i => (byte)i).ToArray();
|
||||||
|
byte[] salt = [0x10, 0x21, 0x32, 0x43, 0x54, 0x65, 0x76, 0x87];
|
||||||
|
using var stream = CreateStream(plainText, password, salt);
|
||||||
|
|
||||||
|
byte[] actual = new byte[plainText.Length];
|
||||||
|
int bytesRead = stream.Read(actual, 0, actual.Length);
|
||||||
|
|
||||||
|
Assert.Equal(plainText.Length, bytesRead);
|
||||||
|
Assert.Equal(plainText, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Read_Preserves_Keystream_Between_NonAligned_Reads()
|
||||||
|
{
|
||||||
|
const string password = "sample-password";
|
||||||
|
byte[] plainText = Enumerable.Range(0, 97).Select(i => (byte)i).ToArray();
|
||||||
|
byte[] salt = [0x10, 0x21, 0x32, 0x43, 0x54, 0x65, 0x76, 0x87];
|
||||||
|
using var stream = CreateStream(plainText, password, salt);
|
||||||
|
|
||||||
|
byte[] actual = ReadWithChunkPattern(
|
||||||
|
(buffer, offset, count) => stream.Read(buffer, offset, count),
|
||||||
|
plainText.Length,
|
||||||
|
[13, 5, 29, 7, 43]
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.Equal(plainText, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ReadAsync_Preserves_Keystream_Between_NonAligned_Reads()
|
||||||
|
{
|
||||||
|
const string password = "sample-password";
|
||||||
|
byte[] plainText = Enumerable
|
||||||
|
.Range(0, 113)
|
||||||
|
.Select(i => unchecked((byte)(255 - i)))
|
||||||
|
.ToArray();
|
||||||
|
byte[] salt = [0x91, 0x82, 0x73, 0x64, 0x55, 0x46, 0x37, 0x28];
|
||||||
|
using var stream = CreateStream(plainText, password, salt);
|
||||||
|
|
||||||
|
byte[] actual = await ReadWithChunkPatternAsync(
|
||||||
|
(buffer, offset, count) => stream.ReadAsync(buffer, offset, count),
|
||||||
|
plainText.Length,
|
||||||
|
[11, 3, 17, 5, 41]
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.Equal(plainText, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ReadAsync_Memory_Preserves_Keystream_Between_NonAligned_Reads()
|
||||||
|
{
|
||||||
|
const string password = "sample-password";
|
||||||
|
byte[] plainText = Enumerable
|
||||||
|
.Range(0, 113)
|
||||||
|
.Select(i => unchecked((byte)(255 - i)))
|
||||||
|
.ToArray();
|
||||||
|
byte[] salt = [0x91, 0x82, 0x73, 0x64, 0x55, 0x46, 0x37, 0x28];
|
||||||
|
using var stream = CreateStream(plainText, password, salt);
|
||||||
|
|
||||||
|
byte[] actual = await ReadWithChunkPatternMemoryAsync(
|
||||||
|
stream,
|
||||||
|
plainText.Length,
|
||||||
|
[11, 3, 17, 5, 41]
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.Equal(plainText, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Read_Stops_At_Encrypted_Payload_Length()
|
||||||
|
{
|
||||||
|
const string password = "sample-password";
|
||||||
|
byte[] plainText = Enumerable.Range(0, 31).Select(i => (byte)(i * 3)).ToArray();
|
||||||
|
byte[] salt = [0xA1, 0xB2, 0xC3, 0xD4, 0x01, 0x12, 0x23, 0x34];
|
||||||
|
using var stream = CreateStream(plainText, password, salt);
|
||||||
|
|
||||||
|
byte[] actual = new byte[plainText.Length + 16];
|
||||||
|
int bytesRead = stream.Read(actual, 0, actual.Length);
|
||||||
|
int eofRead = stream.Read(actual, bytesRead, actual.Length - bytesRead);
|
||||||
|
|
||||||
|
Assert.Equal(plainText.Length, bytesRead);
|
||||||
|
Assert.Equal(0, eofRead);
|
||||||
|
Assert.Equal(plainText, actual.Take(bytesRead).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WinzipAesCryptoStream CreateStream(
|
||||||
|
byte[] plainText,
|
||||||
|
string password,
|
||||||
|
byte[] salt
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var encryptionData = CreateEncryptionData(password, salt);
|
||||||
|
byte[] cipherText = EncryptCtr(plainText, encryptionData.KeyBytes);
|
||||||
|
byte[] archiveBytes = cipherText.Concat(new byte[10]).ToArray();
|
||||||
|
return new WinzipAesCryptoStream(
|
||||||
|
new MemoryStream(archiveBytes, writable: false),
|
||||||
|
encryptionData,
|
||||||
|
cipherText.Length
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage(
|
||||||
|
"Security",
|
||||||
|
"CA5379:Rfc2898DeriveBytes might be using a weak hash algorithm",
|
||||||
|
Justification = "WinZip AES interop requires PBKDF2 with SHA-1."
|
||||||
|
)]
|
||||||
|
private static WinzipAesEncryptionData CreateEncryptionData(string password, byte[] salt)
|
||||||
|
{
|
||||||
|
#pragma warning disable SYSLIB0060 // Rfc2898DeriveBytes might be using a weak hash algorithm
|
||||||
|
using var deriveBytes = new Rfc2898DeriveBytes(
|
||||||
|
password,
|
||||||
|
salt,
|
||||||
|
1000,
|
||||||
|
HashAlgorithmName.SHA1
|
||||||
|
);
|
||||||
|
#pragma warning restore SYSLIB0060
|
||||||
|
deriveBytes.GetBytes(16);
|
||||||
|
deriveBytes.GetBytes(16);
|
||||||
|
byte[] passwordVerifyValue = deriveBytes.GetBytes(2);
|
||||||
|
|
||||||
|
return new WinzipAesEncryptionData(
|
||||||
|
WinzipAesKeySize.KeySize128,
|
||||||
|
salt,
|
||||||
|
passwordVerifyValue,
|
||||||
|
password
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] EncryptCtr(byte[] plainText, byte[] keyBytes)
|
||||||
|
{
|
||||||
|
using var aes = Aes.Create();
|
||||||
|
aes.BlockSize = 128;
|
||||||
|
aes.KeySize = keyBytes.Length * 8;
|
||||||
|
aes.Mode = CipherMode.ECB;
|
||||||
|
aes.Padding = PaddingMode.None;
|
||||||
|
|
||||||
|
using var encryptor = aes.CreateEncryptor(keyBytes, new byte[16]);
|
||||||
|
byte[] counter = new byte[16];
|
||||||
|
byte[] counterOut = new byte[16];
|
||||||
|
byte[] cipherText = new byte[plainText.Length];
|
||||||
|
int nonce = 1;
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
while (offset < plainText.Length)
|
||||||
|
{
|
||||||
|
BinaryPrimitives.WriteInt32LittleEndian(counter, nonce++);
|
||||||
|
encryptor.TransformBlock(counter, 0, counter.Length, counterOut, 0);
|
||||||
|
|
||||||
|
int blockLength = Math.Min(counterOut.Length, plainText.Length - offset);
|
||||||
|
for (int i = 0; i < blockLength; i++)
|
||||||
|
{
|
||||||
|
cipherText[offset + i] = (byte)(plainText[offset + i] ^ counterOut[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += blockLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cipherText;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] ReadWithChunkPattern(
|
||||||
|
Func<byte[], int, int, int> read,
|
||||||
|
int totalLength,
|
||||||
|
int[] chunkPattern
|
||||||
|
)
|
||||||
|
{
|
||||||
|
byte[] actual = new byte[totalLength];
|
||||||
|
int offset = 0;
|
||||||
|
int chunkIndex = 0;
|
||||||
|
|
||||||
|
while (offset < totalLength)
|
||||||
|
{
|
||||||
|
int requested = Math.Min(
|
||||||
|
chunkPattern[chunkIndex % chunkPattern.Length],
|
||||||
|
totalLength - offset
|
||||||
|
);
|
||||||
|
int bytesRead = read(actual, offset, requested);
|
||||||
|
Assert.True(bytesRead > 0);
|
||||||
|
offset += bytesRead;
|
||||||
|
chunkIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return actual;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<byte[]> ReadWithChunkPatternAsync(
|
||||||
|
Func<byte[], int, int, Task<int>> readAsync,
|
||||||
|
int totalLength,
|
||||||
|
int[] chunkPattern
|
||||||
|
)
|
||||||
|
{
|
||||||
|
byte[] actual = new byte[totalLength];
|
||||||
|
int offset = 0;
|
||||||
|
int chunkIndex = 0;
|
||||||
|
|
||||||
|
while (offset < totalLength)
|
||||||
|
{
|
||||||
|
int requested = Math.Min(
|
||||||
|
chunkPattern[chunkIndex % chunkPattern.Length],
|
||||||
|
totalLength - offset
|
||||||
|
);
|
||||||
|
int bytesRead = await readAsync(actual, offset, requested);
|
||||||
|
Assert.True(bytesRead > 0);
|
||||||
|
offset += bytesRead;
|
||||||
|
chunkIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return actual;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<byte[]> ReadWithChunkPatternMemoryAsync(
|
||||||
|
Stream stream,
|
||||||
|
int totalLength,
|
||||||
|
int[] chunkPattern
|
||||||
|
)
|
||||||
|
{
|
||||||
|
byte[] actual = new byte[totalLength];
|
||||||
|
int offset = 0;
|
||||||
|
int chunkIndex = 0;
|
||||||
|
|
||||||
|
while (offset < totalLength)
|
||||||
|
{
|
||||||
|
int requested = Math.Min(
|
||||||
|
chunkPattern[chunkIndex % chunkPattern.Length],
|
||||||
|
totalLength - offset
|
||||||
|
);
|
||||||
|
#if NET48
|
||||||
|
int bytesRead = await stream.ReadAsync(actual, offset, requested);
|
||||||
|
#else
|
||||||
|
int bytesRead = await stream.ReadAsync(actual.AsMemory(offset, requested));
|
||||||
|
#endif
|
||||||
|
Assert.True(bytesRead > 0);
|
||||||
|
offset += bytesRead;
|
||||||
|
chunkIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return actual;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@ namespace SharpCompress.Test.Zip;
|
|||||||
|
|
||||||
public class ZipArchiveTests : ArchiveTests
|
public class ZipArchiveTests : ArchiveTests
|
||||||
{
|
{
|
||||||
|
private const long GeneratedZip64EntrySize = (long)uint.MaxValue + 1;
|
||||||
|
|
||||||
public ZipArchiveTests() => UseExtensionInsteadOfNameToVerify = true;
|
public ZipArchiveTests() => UseExtensionInsteadOfNameToVerify = true;
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -516,6 +518,53 @@ public class ZipArchiveTests : ArchiveTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Zip_Zstandard_WinzipAES_Mixed_ArchiveFileRead()
|
||||||
|
{
|
||||||
|
using var archive = ZipArchive.OpenArchive(
|
||||||
|
Path.Combine(TEST_ARCHIVES_PATH, "Zip.zstd.WinzipAES.mixed.zip"),
|
||||||
|
new ReaderOptions { Password = "test" }
|
||||||
|
);
|
||||||
|
|
||||||
|
VerifyMixedZstandardArchive(archive);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Zip_Zstandard_WinzipAES_Mixed_ArchiveStreamRead()
|
||||||
|
{
|
||||||
|
using var stream = File.OpenRead(
|
||||||
|
Path.Combine(TEST_ARCHIVES_PATH, "Zip.zstd.WinzipAES.mixed.zip")
|
||||||
|
);
|
||||||
|
using var archive = ZipArchive.OpenArchive(stream, new ReaderOptions { Password = "test" });
|
||||||
|
|
||||||
|
VerifyMixedZstandardArchive(archive);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact(Explicit = true)]
|
||||||
|
[Trait("zip64", "generated")]
|
||||||
|
public void Zip_Zip64_GeneratedArchive_StreamIsBoundedToEntryLength()
|
||||||
|
{
|
||||||
|
var zipPath = Path.Combine(SCRATCH2_FILES_PATH, "generated.zip64.large.zip");
|
||||||
|
CreateZip64Archive(zipPath);
|
||||||
|
|
||||||
|
using var archive = ZipArchive.OpenArchive(zipPath);
|
||||||
|
var entries = archive
|
||||||
|
.Entries.Where(x => !x.IsDirectory)
|
||||||
|
.OrderByDescending(x => x.Size)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
Assert.Equal(2, entries.Length);
|
||||||
|
Assert.Equal(GeneratedZip64EntrySize, entries[0].Size);
|
||||||
|
Assert.Equal(1, entries[1].Size);
|
||||||
|
|
||||||
|
using var firstStream = entries[0].OpenEntryStream();
|
||||||
|
Assert.Equal(entries[0].Size, CountBytes(firstStream));
|
||||||
|
|
||||||
|
using var secondStream = entries[1].OpenEntryStream();
|
||||||
|
Assert.Equal(0x42, secondStream.ReadByte());
|
||||||
|
Assert.Equal(-1, secondStream.ReadByte());
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Zip_Pkware_CompressionType()
|
public void Zip_Pkware_CompressionType()
|
||||||
{
|
{
|
||||||
@@ -910,4 +959,89 @@ public class ZipArchiveTests : ArchiveTests
|
|||||||
entries[0].WriteTo(outStream);
|
entries[0].WriteTo(outStream);
|
||||||
Assert.Equal(0, outStream.Length);
|
Assert.Equal(0, outStream.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void VerifyMixedZstandardArchive(IArchive archive)
|
||||||
|
{
|
||||||
|
var entries = archive.Entries.Where(x => !x.IsDirectory).ToArray();
|
||||||
|
Assert.Equal(4, entries.Length);
|
||||||
|
Assert.Equal(2, entries.Count(x => x.IsEncrypted));
|
||||||
|
Assert.Equal(
|
||||||
|
[".signature", "encrypted-zstd-entry.bin", "plain-zstd-entry.bin", "tables.db"],
|
||||||
|
entries.Select(x => x.Key.NotNull()).OrderBy(x => x).ToArray()
|
||||||
|
);
|
||||||
|
Assert.All(
|
||||||
|
entries,
|
||||||
|
entry => Assert.Equal(CompressionType.ZStandard, entry.CompressionType)
|
||||||
|
);
|
||||||
|
|
||||||
|
var expectedSizes = new long[] { 160, 64 * 1024, 64 * 1024, 192 * 1024 };
|
||||||
|
Assert.Equal(expectedSizes, entries.Select(x => x.Size).OrderBy(x => x).ToArray());
|
||||||
|
|
||||||
|
foreach (var entry in entries)
|
||||||
|
{
|
||||||
|
using var entryStream = entry.OpenEntryStream();
|
||||||
|
using var target = new MemoryStream();
|
||||||
|
entryStream.CopyTo(target);
|
||||||
|
Assert.Equal(entry.Size, target.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long CountBytes(Stream stream)
|
||||||
|
{
|
||||||
|
var buffer = new byte[8 * 1024 * 1024];
|
||||||
|
long total = 0;
|
||||||
|
int read;
|
||||||
|
while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
|
||||||
|
{
|
||||||
|
total += read;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CreateZip64Archive(string path)
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
var writerOptions = new ZipWriterOptions(CompressionType.None) { UseZip64 = true };
|
||||||
|
using var fileStream = File.OpenWrite(path);
|
||||||
|
using var zipWriter = (ZipWriter)
|
||||||
|
WriterFactory.OpenWriter(fileStream, ArchiveType.Zip, writerOptions);
|
||||||
|
|
||||||
|
using (
|
||||||
|
var largeEntryStream = zipWriter.WriteToStream(
|
||||||
|
"large-entry.bin",
|
||||||
|
new ZipWriterEntryOptions()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
WriteZeroes(largeEntryStream, GeneratedZip64EntrySize);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (
|
||||||
|
var trailingEntryStream = zipWriter.WriteToStream(
|
||||||
|
"trailing-entry.bin",
|
||||||
|
new ZipWriterEntryOptions()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
trailingEntryStream.WriteByte(0x42);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteZeroes(Stream stream, long length)
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[8 * 1024 * 1024];
|
||||||
|
long remaining = length;
|
||||||
|
|
||||||
|
while (remaining > 0)
|
||||||
|
{
|
||||||
|
int chunk = (int)Math.Min(buffer.Length, remaining);
|
||||||
|
stream.Write(buffer, 0, chunk);
|
||||||
|
remaining -= chunk;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
59
tests/SharpCompress.Test/Zip/ZipFilePartTests.cs
Normal file
59
tests/SharpCompress.Test/Zip/ZipFilePartTests.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using System.IO;
|
||||||
|
using SharpCompress.Common;
|
||||||
|
using SharpCompress.Common.Zip;
|
||||||
|
using SharpCompress.Common.Zip.Headers;
|
||||||
|
using SharpCompress.IO;
|
||||||
|
using SharpCompress.Providers;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SharpCompress.Test.Zip;
|
||||||
|
|
||||||
|
public class ZipFilePartTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void GetCryptoStream_Bounds_Known_Size_Zip64_Entries()
|
||||||
|
{
|
||||||
|
var header = new DirectoryEntryHeader(new ArchiveEncoding())
|
||||||
|
{
|
||||||
|
Name = "entry.bin",
|
||||||
|
CompressionMethod = ZipCompressionMethod.None,
|
||||||
|
CompressedSize = uint.MaxValue,
|
||||||
|
UncompressedSize = uint.MaxValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
using var backingStream = new MemoryStream([1, 2, 3, 4, 5], writable: false);
|
||||||
|
var part = new TestZipFilePart(header, backingStream);
|
||||||
|
|
||||||
|
using var cryptoStream = part.OpenCryptoStream();
|
||||||
|
|
||||||
|
Assert.IsType<ReadOnlySubStream>(cryptoStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetCryptoStream_Leaves_DataDescriptor_Entries_Unbounded_When_Size_Is_Unknown()
|
||||||
|
{
|
||||||
|
var header = new DirectoryEntryHeader(new ArchiveEncoding())
|
||||||
|
{
|
||||||
|
Name = "entry.bin",
|
||||||
|
CompressionMethod = ZipCompressionMethod.None,
|
||||||
|
CompressedSize = 0,
|
||||||
|
UncompressedSize = 0,
|
||||||
|
Flags = HeaderFlags.UsePostDataDescriptor,
|
||||||
|
};
|
||||||
|
|
||||||
|
using var backingStream = new MemoryStream([1, 2, 3, 4, 5], writable: false);
|
||||||
|
var part = new TestZipFilePart(header, backingStream);
|
||||||
|
|
||||||
|
using var cryptoStream = part.OpenCryptoStream();
|
||||||
|
|
||||||
|
Assert.IsNotType<ReadOnlySubStream>(cryptoStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TestZipFilePart(ZipFileEntry header, Stream stream)
|
||||||
|
: ZipFilePart(header, stream, CompressionProviderRegistry.Default)
|
||||||
|
{
|
||||||
|
public Stream OpenCryptoStream() => GetCryptoStream(CreateBaseStream());
|
||||||
|
|
||||||
|
protected override Stream CreateBaseStream() => BaseStream;
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
tests/TestArchives/Archives/Zip.zstd.WinzipAES.mixed.zip
Normal file
BIN
tests/TestArchives/Archives/Zip.zstd.WinzipAES.mixed.zip
Normal file
Binary file not shown.
Reference in New Issue
Block a user