mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-11-15 17:02:03 +00:00
Merge remote-tracking branch 'origin/release' into fix/1439-sevenzip-password-exceptions
# Conflicts: # docs/API.md
This commit is contained in:
@@ -28,11 +28,32 @@ internal abstract class ArchiveVolumeFactory
|
||||
);
|
||||
}
|
||||
|
||||
if (item != null && item.Exists)
|
||||
return item is null ? null : ResolveFilePart(item);
|
||||
}
|
||||
|
||||
internal static FileInfo? ResolveFilePart(FileInfo item)
|
||||
{
|
||||
if (item.Exists)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
|
||||
return null;
|
||||
if (!item.Directory!.Exists)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
FileInfo? match = null;
|
||||
foreach (var file in item.Directory.EnumerateFiles())
|
||||
{
|
||||
if (!string.Equals(file.Name, item.Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (match is not null)
|
||||
{
|
||||
throw new InvalidFormatException($"Ambiguous archive volume name: {item.Name}");
|
||||
}
|
||||
match = file;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,11 +61,6 @@ internal static class RarArchiveVolumeFactory
|
||||
}
|
||||
}
|
||||
|
||||
if (item != null && item.Exists)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
|
||||
return null; //no more items
|
||||
return item is null ? null : ArchiveVolumeFactory.ResolveFilePart(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ public partial class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, Sev
|
||||
.GroupBy(x => x.FilePart.Folder)
|
||||
.Any(folder => folder.Skip(1).Any());
|
||||
|
||||
public override bool IsEncrypted => Entries.First(x => !x.IsDirectory).IsEncrypted;
|
||||
public override bool IsEncrypted => Entries.Any(x => x.IsEncrypted);
|
||||
|
||||
public override long TotalSize =>
|
||||
_database?._packSizes.Aggregate(0L, (total, packSize) => total + packSize) ?? 0;
|
||||
|
||||
@@ -32,11 +32,6 @@ internal static class ZipArchiveVolumeFactory
|
||||
return ArchiveVolumeFactory.GetFilePart(index, part1);
|
||||
}
|
||||
|
||||
if (item != null && item.Exists)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
|
||||
return null; //no more items
|
||||
return item is null ? null : ArchiveVolumeFactory.ResolveFilePart(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ public abstract partial class AceHeader
|
||||
// First two bytes are typically 0x60 0xEA (signature bytes)
|
||||
// At offset 7, there should be "**ACE**" (7 bytes)
|
||||
var bytes = new byte[14];
|
||||
if (stream.Read(bytes, 0, 14) != 14)
|
||||
if (!stream.ReadFully(bytes))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class ArcEntryHeader
|
||||
public ArcEntryHeader? ReadHeader(Stream stream)
|
||||
{
|
||||
byte[] headerBytes = new byte[29];
|
||||
if (stream.Read(headerBytes, 0, headerBytes.Length) != headerBytes.Length)
|
||||
if (!stream.ReadFully(headerBytes))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -40,11 +40,7 @@ public class ArcEntryHeader
|
||||
)
|
||||
{
|
||||
byte[] headerBytes = new byte[29];
|
||||
if (
|
||||
await stream
|
||||
.ReadAsync(headerBytes, 0, headerBytes.Length, cancellationToken)
|
||||
.ConfigureAwait(false) != headerBytes.Length
|
||||
)
|
||||
if (!await stream.ReadFullyAsync(headerBytes, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public abstract partial class ArjHeader
|
||||
{
|
||||
// check for magic bytes
|
||||
var magic = new byte[2];
|
||||
if (await stream.ReadAsync(magic, 0, 2, cancellationToken).ConfigureAwait(false) != 2)
|
||||
if (!await stream.ReadFullyAsync(magic, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
@@ -33,7 +33,7 @@ public abstract partial class ArjHeader
|
||||
|
||||
// read header_size
|
||||
byte[] headerBytes = new byte[2];
|
||||
await stream.ReadAsync(headerBytes, 0, 2, cancellationToken).ConfigureAwait(false);
|
||||
await stream.ReadExactAsync(headerBytes, 0, 2, cancellationToken).ConfigureAwait(false);
|
||||
var headerSize = (ushort)(headerBytes[0] | headerBytes[1] << 8);
|
||||
if (headerSize < 1)
|
||||
{
|
||||
@@ -41,16 +41,13 @@ public abstract partial class ArjHeader
|
||||
}
|
||||
|
||||
var body = new byte[headerSize];
|
||||
var read = await stream
|
||||
.ReadAsync(body, 0, headerSize, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (read < headerSize)
|
||||
if (!await stream.ReadFullyAsync(body, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
byte[] crc = new byte[4];
|
||||
await stream.ReadFullyAsync(crc, 0, 4, cancellationToken).ConfigureAwait(false);
|
||||
await stream.ReadExactAsync(crc, 0, 4, cancellationToken).ConfigureAwait(false);
|
||||
var checksum = Crc32Stream.Compute(body);
|
||||
// Compute the hash value
|
||||
if (checksum != BitConverter.ToUInt32(crc, 0))
|
||||
@@ -70,10 +67,7 @@ public abstract partial class ArjHeader
|
||||
|
||||
while (true)
|
||||
{
|
||||
int bytesRead = await reader
|
||||
.ReadAsync(buffer, 0, 2, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (bytesRead < 2)
|
||||
if (!await reader.ReadFullyAsync(buffer, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
throw new IncompleteArchiveException(
|
||||
"Unexpected end of stream while reading extended header size."
|
||||
@@ -87,10 +81,7 @@ public abstract partial class ArjHeader
|
||||
}
|
||||
|
||||
byte[] header = new byte[extHeaderSize];
|
||||
bytesRead = await reader
|
||||
.ReadAsync(header, 0, extHeaderSize, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (bytesRead < extHeaderSize)
|
||||
if (!await reader.ReadFullyAsync(header, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
throw new IncompleteArchiveException(
|
||||
"Unexpected end of stream while reading extended header data."
|
||||
@@ -98,10 +89,7 @@ public abstract partial class ArjHeader
|
||||
}
|
||||
|
||||
byte[] crcextended = new byte[4];
|
||||
bytesRead = await reader
|
||||
.ReadAsync(crcextended, 0, 4, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (bytesRead < 4)
|
||||
if (!await reader.ReadFullyAsync(crcextended, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
throw new IncompleteArchiveException(
|
||||
"Unexpected end of stream while reading extended header CRC."
|
||||
@@ -130,7 +118,7 @@ public abstract partial class ArjHeader
|
||||
)
|
||||
{
|
||||
var bytes = new byte[2];
|
||||
if (await stream.ReadAsync(bytes, 0, 2, cancellationToken).ConfigureAwait(false) != 2)
|
||||
if (!await stream.ReadFullyAsync(bytes, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public abstract partial class ArjHeader
|
||||
{
|
||||
// check for magic bytes
|
||||
var magic = new byte[2];
|
||||
if (stream.Read(magic) != 2)
|
||||
if (!stream.ReadFully(magic))
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
@@ -49,7 +49,7 @@ public abstract partial class ArjHeader
|
||||
|
||||
// read header_size
|
||||
byte[] headerBytes = new byte[2];
|
||||
stream.Read(headerBytes, 0, 2);
|
||||
stream.ReadExact(headerBytes, 0, 2);
|
||||
var headerSize = (ushort)(headerBytes[0] | headerBytes[1] << 8);
|
||||
if (headerSize < 1)
|
||||
{
|
||||
@@ -57,14 +57,13 @@ public abstract partial class ArjHeader
|
||||
}
|
||||
|
||||
var body = new byte[headerSize];
|
||||
var read = stream.Read(body, 0, headerSize);
|
||||
if (read < headerSize)
|
||||
if (!stream.ReadFully(body))
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
byte[] crc = new byte[4];
|
||||
read = stream.Read(crc, 0, 4);
|
||||
stream.ReadExact(crc, 0, 4);
|
||||
var checksum = Crc32Stream.Compute(body);
|
||||
// Compute the hash value
|
||||
if (checksum != BitConverter.ToUInt32(crc, 0))
|
||||
@@ -83,8 +82,7 @@ public abstract partial class ArjHeader
|
||||
|
||||
while (true)
|
||||
{
|
||||
int bytesRead = reader.Read(buffer, 0, 2);
|
||||
if (bytesRead < 2)
|
||||
if (!reader.ReadFully(buffer))
|
||||
{
|
||||
throw new IncompleteArchiveException(
|
||||
"Unexpected end of stream while reading extended header size."
|
||||
@@ -98,8 +96,7 @@ public abstract partial class ArjHeader
|
||||
}
|
||||
|
||||
byte[] header = new byte[extHeaderSize];
|
||||
bytesRead = reader.Read(header, 0, extHeaderSize);
|
||||
if (bytesRead < extHeaderSize)
|
||||
if (!reader.ReadFully(header))
|
||||
{
|
||||
throw new IncompleteArchiveException(
|
||||
"Unexpected end of stream while reading extended header data."
|
||||
@@ -107,8 +104,7 @@ public abstract partial class ArjHeader
|
||||
}
|
||||
|
||||
byte[] crc = new byte[4];
|
||||
bytesRead = reader.Read(crc, 0, 4);
|
||||
if (bytesRead < 4)
|
||||
if (!reader.ReadFully(crc))
|
||||
{
|
||||
throw new IncompleteArchiveException(
|
||||
"Unexpected end of stream while reading extended header CRC."
|
||||
@@ -147,7 +143,7 @@ public abstract partial class ArjHeader
|
||||
public static bool IsArchive(Stream stream)
|
||||
{
|
||||
var bytes = new byte[2];
|
||||
if (stream.Read(bytes, 0, 2) != 2)
|
||||
if (!stream.ReadFully(bytes))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,10 @@ internal sealed class ChecksumValidationStream : Stream
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var read = _stream.Read(buffer, offset, count);
|
||||
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
|
||||
if (count != 0)
|
||||
{
|
||||
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
@@ -51,7 +54,10 @@ internal sealed class ChecksumValidationStream : Stream
|
||||
public override int Read(Span<byte> buffer)
|
||||
{
|
||||
var read = _stream.Read(buffer);
|
||||
UpdateAndValidateAtEof(buffer[..read], read);
|
||||
if (!buffer.IsEmpty)
|
||||
{
|
||||
UpdateAndValidateAtEof(buffer[..read], read);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
#endif
|
||||
@@ -68,7 +74,10 @@ internal sealed class ChecksumValidationStream : Stream
|
||||
var read = await _stream
|
||||
.ReadAsync(buffer, offset, count, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
|
||||
if (count != 0)
|
||||
{
|
||||
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
@@ -79,7 +88,10 @@ internal sealed class ChecksumValidationStream : Stream
|
||||
)
|
||||
{
|
||||
var read = await _stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||
UpdateAndValidateAtEof(buffer.Span[..read], read);
|
||||
if (!buffer.IsEmpty)
|
||||
{
|
||||
UpdateAndValidateAtEof(buffer.Span[..read], read);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -18,13 +18,15 @@ internal sealed class GZipChecksumValidationStream : Stream
|
||||
private uint _seed = Crc32Stream.DEFAULT_SEED;
|
||||
private uint _size;
|
||||
private bool _validated;
|
||||
private readonly Func<Stream?>? _bufferedInput;
|
||||
|
||||
internal GZipChecksumValidationStream(
|
||||
Stream source,
|
||||
Stream rawStream,
|
||||
string? entryName,
|
||||
uint? expectedCrc,
|
||||
uint? expectedSize
|
||||
uint? expectedSize,
|
||||
Func<Stream?>? bufferedInput = null
|
||||
)
|
||||
{
|
||||
_source = source;
|
||||
@@ -32,6 +34,7 @@ internal sealed class GZipChecksumValidationStream : Stream
|
||||
_entryName = string.IsNullOrEmpty(entryName) ? "Entry" : entryName!;
|
||||
_expectedCrc = expectedCrc;
|
||||
_expectedSize = expectedSize;
|
||||
_bufferedInput = bufferedInput;
|
||||
_crc32Table = Crc32Stream.InitializeTable(Crc32Stream.DEFAULT_POLYNOMIAL);
|
||||
}
|
||||
|
||||
@@ -54,7 +57,10 @@ internal sealed class GZipChecksumValidationStream : Stream
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var read = _source.Read(buffer, offset, count);
|
||||
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
|
||||
if (count != 0)
|
||||
{
|
||||
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
@@ -62,7 +68,10 @@ internal sealed class GZipChecksumValidationStream : Stream
|
||||
public override int Read(Span<byte> buffer)
|
||||
{
|
||||
var read = _source.Read(buffer);
|
||||
UpdateAndValidateAtEof(buffer[..read], read);
|
||||
if (!buffer.IsEmpty)
|
||||
{
|
||||
UpdateAndValidateAtEof(buffer[..read], read);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
#endif
|
||||
@@ -93,7 +102,17 @@ internal sealed class GZipChecksumValidationStream : Stream
|
||||
var read = await _source
|
||||
.ReadAsync(buffer, offset, count, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
|
||||
if (count != 0)
|
||||
{
|
||||
if (read == 0)
|
||||
{
|
||||
await ValidateAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
|
||||
}
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
@@ -104,7 +123,17 @@ internal sealed class GZipChecksumValidationStream : Stream
|
||||
)
|
||||
{
|
||||
var read = await _source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||
UpdateAndValidateAtEof(buffer.Span[..read], read);
|
||||
if (!buffer.IsEmpty)
|
||||
{
|
||||
if (read == 0)
|
||||
{
|
||||
await ValidateAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateAndValidateAtEof(buffer.Span[..read], read);
|
||||
}
|
||||
}
|
||||
return read;
|
||||
}
|
||||
#endif
|
||||
@@ -135,31 +164,77 @@ internal sealed class GZipChecksumValidationStream : Stream
|
||||
return;
|
||||
}
|
||||
|
||||
_validated = true;
|
||||
|
||||
var expectedCrc = _expectedCrc;
|
||||
var expectedSize = _expectedSize;
|
||||
if (!expectedCrc.HasValue || !expectedSize.HasValue)
|
||||
{
|
||||
Span<byte> trailer = stackalloc byte[8];
|
||||
_rawStream.ReadFully(trailer);
|
||||
var bufferedCount = ReadBufferedTrailer(trailer);
|
||||
if (!_rawStream.ReadFully(trailer.Slice(bufferedCount)))
|
||||
{
|
||||
throw new IncompleteArchiveException("Unexpected end of GZIP trailer.");
|
||||
}
|
||||
expectedCrc = BinaryPrimitives.ReadUInt32LittleEndian(trailer);
|
||||
expectedSize = BinaryPrimitives.ReadUInt32LittleEndian(trailer[4..]);
|
||||
}
|
||||
|
||||
ValidateValues(expectedCrc.Value, expectedSize.Value);
|
||||
}
|
||||
|
||||
private async ValueTask ValidateAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_validated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var expectedCrc = _expectedCrc;
|
||||
var expectedSize = _expectedSize;
|
||||
if (!expectedCrc.HasValue || !expectedSize.HasValue)
|
||||
{
|
||||
var trailer = new byte[8];
|
||||
var bufferedCount = ReadBufferedTrailer(trailer);
|
||||
if (
|
||||
!await _rawStream
|
||||
.ReadFullyAsync(
|
||||
trailer,
|
||||
bufferedCount,
|
||||
trailer.Length - bufferedCount,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
throw new IncompleteArchiveException("Unexpected end of GZIP trailer.");
|
||||
}
|
||||
expectedCrc = BinaryPrimitives.ReadUInt32LittleEndian(trailer);
|
||||
expectedSize = BinaryPrimitives.ReadUInt32LittleEndian(trailer.AsSpan(4));
|
||||
}
|
||||
ValidateValues(expectedCrc.Value, expectedSize.Value);
|
||||
}
|
||||
|
||||
private int ReadBufferedTrailer(Span<byte> trailer)
|
||||
{
|
||||
// The inflater may have read ahead beyond the final compressed bit.
|
||||
using var buffered = _bufferedInput?.Invoke();
|
||||
return buffered?.Read(trailer) ?? 0;
|
||||
}
|
||||
|
||||
private void ValidateValues(uint expectedCrc, uint expectedSize)
|
||||
{
|
||||
var actualCrc = ~_seed;
|
||||
if (actualCrc != expectedCrc.Value)
|
||||
if (actualCrc != expectedCrc)
|
||||
{
|
||||
throw new InvalidFormatException(
|
||||
$"CRC mismatch for entry '{_entryName}'. Expected 0x{expectedCrc.Value:X8}, actual 0x{actualCrc:X8}."
|
||||
$"CRC mismatch for entry '{_entryName}'. Expected 0x{expectedCrc:X8}, actual 0x{actualCrc:X8}."
|
||||
);
|
||||
}
|
||||
|
||||
if (_size != expectedSize.Value)
|
||||
if (_size != expectedSize)
|
||||
{
|
||||
throw new InvalidFormatException(
|
||||
$"Size mismatch for entry '{_entryName}'. Expected {expectedSize.Value}, actual {_size}."
|
||||
$"Size mismatch for entry '{_entryName}'. Expected {expectedSize}, actual {_size}."
|
||||
);
|
||||
}
|
||||
_validated = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,10 @@ internal sealed partial class GZipFilePart
|
||||
{
|
||||
// Read and potentially verify the GZIP trailer: CRC32 and size mod 2^32
|
||||
var trailer = new byte[8];
|
||||
_ = await _stream.ReadFullyAsync(trailer, 0, 8, cancellationToken).ConfigureAwait(false);
|
||||
if (!await _stream.ReadFullyAsync(trailer, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
throw new ZlibException("Unexpected end-of-file reading GZIP trailer.");
|
||||
}
|
||||
|
||||
Crc = BinaryPrimitives.ReadUInt32LittleEndian(trailer);
|
||||
UncompressedSize = BinaryPrimitives.ReadUInt32LittleEndian(trailer.AsSpan().Slice(4));
|
||||
@@ -64,7 +67,11 @@ internal sealed partial class GZipFilePart
|
||||
return;
|
||||
}
|
||||
|
||||
if (n != 10)
|
||||
if (
|
||||
!await _stream
|
||||
.ReadFullyAsync(header, n, header.Length - n, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
throw new ZlibException("Not a valid GZIP stream.");
|
||||
}
|
||||
@@ -80,7 +87,10 @@ internal sealed partial class GZipFilePart
|
||||
{
|
||||
// read and discard extra field
|
||||
var lengthField = new byte[2];
|
||||
_ = await _stream.ReadAsync(lengthField, 0, 2, cancellationToken).ConfigureAwait(false);
|
||||
if (!await _stream.ReadFullyAsync(lengthField, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
throw new ZlibException("Unexpected end-of-file reading GZIP header.");
|
||||
}
|
||||
|
||||
var extraLength = lengthField[0] + (lengthField[1] * 256);
|
||||
var extra = new byte[extraLength];
|
||||
@@ -101,8 +111,11 @@ internal sealed partial class GZipFilePart
|
||||
}
|
||||
if ((header[3] & 0x02) == 0x02)
|
||||
{
|
||||
var buf = new byte[1];
|
||||
_ = await _stream.ReadAsync(buf, 0, 1, cancellationToken).ConfigureAwait(false); // CRC16, ignore
|
||||
var headerCrc = new byte[2];
|
||||
if (!await _stream.ReadFullyAsync(headerCrc, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
throw new ZlibException("Unexpected end-of-file reading GZIP header CRC.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,8 +153,9 @@ internal sealed partial class GZipFilePart
|
||||
)
|
||||
{
|
||||
// GZip uses Deflate compression
|
||||
return await _compressionProviders
|
||||
_decompressor = await _compressionProviders
|
||||
.CreateDecompressStreamAsync(CompressionType.Deflate, _stream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
return _decompressor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ internal sealed partial class GZipFilePart : FilePart
|
||||
private string? _name;
|
||||
private readonly Stream _stream;
|
||||
private readonly CompressionProviderRegistry _compressionProviders;
|
||||
private Stream? _decompressor;
|
||||
|
||||
internal static GZipFilePart Create(
|
||||
Stream stream,
|
||||
@@ -64,11 +65,22 @@ internal sealed partial class GZipFilePart : FilePart
|
||||
internal override Stream GetCompressedStream()
|
||||
{
|
||||
//GZip uses Deflate compression, at this point we need a deflate stream
|
||||
return _compressionProviders.CreateDecompressStream(CompressionType.Deflate, _stream);
|
||||
_decompressor = _compressionProviders.CreateDecompressStream(
|
||||
CompressionType.Deflate,
|
||||
_stream
|
||||
);
|
||||
return _decompressor;
|
||||
}
|
||||
|
||||
internal Stream WrapWithChecksumValidation(Stream source, string? entryName) =>
|
||||
new GZipChecksumValidationStream(source, _stream, entryName, Crc, UncompressedSize);
|
||||
new GZipChecksumValidationStream(
|
||||
source,
|
||||
_stream,
|
||||
entryName,
|
||||
Crc,
|
||||
UncompressedSize,
|
||||
() => (_decompressor as DeflateStream)?.InputBuffer
|
||||
);
|
||||
|
||||
internal override Stream GetRawStream() => _stream;
|
||||
|
||||
@@ -76,7 +88,10 @@ internal sealed partial class GZipFilePart : FilePart
|
||||
{
|
||||
// Read and potentially verify the GZIP trailer: CRC32 and size mod 2^32
|
||||
Span<byte> trailer = stackalloc byte[8];
|
||||
_stream.ReadFully(trailer);
|
||||
if (!_stream.ReadFully(trailer))
|
||||
{
|
||||
throw new ZlibException("Unexpected end-of-file reading GZIP trailer.");
|
||||
}
|
||||
|
||||
Crc = BinaryPrimitives.ReadUInt32LittleEndian(trailer);
|
||||
UncompressedSize = BinaryPrimitives.ReadUInt32LittleEndian(trailer.Slice(4));
|
||||
@@ -94,7 +109,7 @@ internal sealed partial class GZipFilePart : FilePart
|
||||
return;
|
||||
}
|
||||
|
||||
if (n != 10)
|
||||
if (!_stream.ReadFully(header.Slice(n)))
|
||||
{
|
||||
throw new ZlibException("Not a valid GZIP stream.");
|
||||
}
|
||||
@@ -109,7 +124,10 @@ internal sealed partial class GZipFilePart : FilePart
|
||||
if ((header[3] & 0x04) == 0x04)
|
||||
{
|
||||
// read and discard extra field
|
||||
n = _stream.Read(header.Slice(0, 2)); // 2-byte length field
|
||||
if (!_stream.ReadFully(header.Slice(0, 2)))
|
||||
{
|
||||
throw new ZlibException("Unexpected end-of-file reading GZIP header.");
|
||||
}
|
||||
|
||||
var extraLength = header[0] + (header[1] * 256);
|
||||
var extra = new byte[extraLength];
|
||||
@@ -130,7 +148,11 @@ internal sealed partial class GZipFilePart : FilePart
|
||||
}
|
||||
if ((header[3] & 0x02) == 0x02)
|
||||
{
|
||||
_stream.ReadByte(); // CRC16, ignore
|
||||
Span<byte> headerCrc = stackalloc byte[2];
|
||||
if (!_stream.ReadFully(headerCrc))
|
||||
{
|
||||
throw new ZlibException("Unexpected end-of-file reading GZIP header CRC.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ internal static class EncryptionFlagsV5
|
||||
internal static class EncryptionConstV5
|
||||
{
|
||||
public const int VERSION = 0;
|
||||
public const uint CRYPT5_KDF_LG2_COUNT_MAX = 0x24;
|
||||
public const uint CRYPT5_KDF_LG2_COUNT_MAX = 24;
|
||||
public const int SIZE_SALT30 = 0x08;
|
||||
public const int SIZE_SALT50 = 0x10;
|
||||
public const int SIZE_INITV = 0x10;
|
||||
|
||||
@@ -24,7 +24,7 @@ internal class Rar5CryptoInfo
|
||||
encryptionFlags,
|
||||
EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK
|
||||
);
|
||||
cryptoInfo.LG2Count = reader.ReadRarVIntByte(1);
|
||||
cryptoInfo.LG2Count = reader.ReadByte();
|
||||
|
||||
if (cryptoInfo.LG2Count > EncryptionConstV5.CRYPT5_KDF_LG2_COUNT_MAX)
|
||||
{
|
||||
@@ -78,10 +78,9 @@ internal class Rar5CryptoInfo
|
||||
encryptionFlags,
|
||||
EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK
|
||||
);
|
||||
cryptoInfo.LG2Count = (int)
|
||||
await reader
|
||||
.ReadRarVIntUInt32Async(cancellationToken: CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
cryptoInfo.LG2Count = await reader
|
||||
.ReadByteAsync(CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
if (cryptoInfo.LG2Count > EncryptionConstV5.CRYPT5_KDF_LG2_COUNT_MAX)
|
||||
{
|
||||
throw new CryptographicException($"Unsupported LG2 count of {cryptoInfo.LG2Count}.");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
|
||||
namespace SharpCompress.Common.Rar;
|
||||
|
||||
@@ -12,6 +13,12 @@ internal sealed class Rar5KeyCache
|
||||
|
||||
internal List<byte[]> GetKeys(string password, byte[] salt, int lg2Count)
|
||||
{
|
||||
// Metadata can be mutated after parsing; validate before C# masks the shift count.
|
||||
if (lg2Count < 0 || lg2Count > EncryptionConstV5.CRYPT5_KDF_LG2_COUNT_MAX)
|
||||
{
|
||||
throw new CryptographicException($"Unsupported LG2 count of {lg2Count}.");
|
||||
}
|
||||
|
||||
lock (entries)
|
||||
{
|
||||
foreach (var (cachedPassword, cachedSalt, cachedCount, cachedKeys) in entries)
|
||||
|
||||
@@ -40,8 +40,11 @@ internal sealed class RarCryptoWrapper : Stream
|
||||
Span<byte> cipherText = stackalloc byte[16];
|
||||
for (var i = 0; i < alignedSize / 16; i++)
|
||||
{
|
||||
//long ax = System.currentTimeMillis();
|
||||
_actualStream.Read(cipherText);
|
||||
if (!_actualStream.ReadFully(cipherText))
|
||||
{
|
||||
// Preserve the synchronous API's malformed-archive exception type.
|
||||
throw new InvalidFormatException("Unexpected end of encrypted stream");
|
||||
}
|
||||
|
||||
var readBytes = _rijndael.ProcessBlock(cipherText);
|
||||
foreach (var readByte in readBytes)
|
||||
@@ -49,11 +52,11 @@ internal sealed class RarCryptoWrapper : Stream
|
||||
_data.Enqueue(readByte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
buffer[offset + i] = _data.Dequeue();
|
||||
}
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
buffer[offset + i] = _data.Dequeue();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
@@ -97,7 +100,7 @@ internal sealed class RarCryptoWrapper : Stream
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (EndOfStreamException e)
|
||||
catch (IncompleteArchiveException e)
|
||||
{
|
||||
throw new InvalidFormatException("Unexpected end of encrypted stream", e);
|
||||
}
|
||||
|
||||
@@ -119,7 +119,5 @@ internal class SevenZipFilePart : FilePart
|
||||
};
|
||||
}
|
||||
|
||||
internal bool IsEncrypted =>
|
||||
!Header.IsDir
|
||||
&& Folder?._coders.FindIndex(c => c._methodId._id == CMethodId.K_AES_ID) != -1;
|
||||
internal bool IsEncrypted => !Header.IsDir && Folder?.IsEncrypted() == true;
|
||||
}
|
||||
|
||||
@@ -260,48 +260,48 @@ internal sealed partial class TarHeader
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Check header checksum
|
||||
if (!checkChecksum(buffer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Name = ArchiveEncoding.Decode(buffer, 0, 100).TrimNulls();
|
||||
EntryType = entryType;
|
||||
Size = ReadSize(buffer);
|
||||
LinkName = null;
|
||||
|
||||
// for symlinks, additionally read the linkname
|
||||
if (entryType == EntryType.SymLink || entryType == EntryType.HardLink)
|
||||
{
|
||||
LinkName = ArchiveEncoding.Decode(buffer, 157, 100).TrimNulls();
|
||||
}
|
||||
|
||||
Mode = ReadAsciiInt64Base8(buffer, 100, 7);
|
||||
UserId = ReadAsciiInt64Base8oldGnu(buffer, 108, 7);
|
||||
GroupId = ReadAsciiInt64Base8oldGnu(buffer, 116, 7);
|
||||
|
||||
var unixTimeStamp = ReadAsciiInt64Base8(buffer, 136, 11);
|
||||
|
||||
LastModifiedTime = EPOCH.AddSeconds(unixTimeStamp).ToLocalTime();
|
||||
Magic = ArchiveEncoding.Decode(buffer, 257, 6).TrimNulls();
|
||||
|
||||
if (!string.IsNullOrEmpty(Magic) && "ustar".Equals(Magic, StringComparison.Ordinal))
|
||||
{
|
||||
var namePrefix = ArchiveEncoding.Decode(buffer, 345, 157).TrimNulls();
|
||||
|
||||
if (!string.IsNullOrEmpty(namePrefix))
|
||||
{
|
||||
Name = namePrefix + "/" + Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
|
||||
// Check header checksum
|
||||
if (!checkChecksum(buffer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Name = ArchiveEncoding.Decode(buffer, 0, 100).TrimNulls();
|
||||
EntryType = entryType;
|
||||
Size = ReadSize(buffer);
|
||||
LinkName = null;
|
||||
|
||||
// for symlinks, additionally read the linkname
|
||||
if (entryType == EntryType.SymLink || entryType == EntryType.HardLink)
|
||||
{
|
||||
LinkName = ArchiveEncoding.Decode(buffer, 157, 100).TrimNulls();
|
||||
}
|
||||
|
||||
Mode = ReadAsciiInt64Base8(buffer, 100, 7);
|
||||
UserId = ReadAsciiInt64Base8oldGnu(buffer, 108, 7);
|
||||
GroupId = ReadAsciiInt64Base8oldGnu(buffer, 116, 7);
|
||||
|
||||
var unixTimeStamp = ReadAsciiInt64Base8(buffer, 136, 11);
|
||||
|
||||
LastModifiedTime = EPOCH.AddSeconds(unixTimeStamp).ToLocalTime();
|
||||
Magic = ArchiveEncoding.Decode(buffer, 257, 6).TrimNulls();
|
||||
|
||||
if (!string.IsNullOrEmpty(Magic) && "ustar".Equals(Magic, StringComparison.Ordinal))
|
||||
{
|
||||
var namePrefix = ArchiveEncoding.Decode(buffer, 345, 157).TrimNulls();
|
||||
|
||||
if (!string.IsNullOrEmpty(namePrefix))
|
||||
{
|
||||
Name = namePrefix + "/" + Name;
|
||||
}
|
||||
}
|
||||
|
||||
pendingMetadata.ApplyTo(this);
|
||||
|
||||
if (entryType == EntryType.Directory)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -17,21 +19,57 @@ internal partial class WinzipAesCryptoStream
|
||||
return;
|
||||
}
|
||||
_isDisposed = true;
|
||||
// Read out last 10 auth bytes asynchronously
|
||||
byte[] authBytes = ArrayPool<byte>.Shared.Rent(10);
|
||||
try
|
||||
{
|
||||
await _stream.ReadFullyAsync(authBytes, 0, 10).ConfigureAwait(false);
|
||||
if (!_authRead)
|
||||
{
|
||||
await DrainAndVerifyAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(authBytes);
|
||||
_authentication.Dispose();
|
||||
_transform.Dispose();
|
||||
_cipher.Dispose();
|
||||
await _stream.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
await base.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
#endif
|
||||
|
||||
private async ValueTask VerifyAuthenticationAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_authenticationFailure?.Throw();
|
||||
if (_authRead)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_authRead = true;
|
||||
var tag = new byte[10];
|
||||
try
|
||||
{
|
||||
await _stream
|
||||
.ReadExactAsync(tag, 0, tag.Length, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
ValidateAuthentication(tag);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_authenticationFailure = ExceptionDispatchInfo.Capture(exception);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask DrainAndVerifyAsync()
|
||||
{
|
||||
var buffer = new byte[4096];
|
||||
while (_totalBytesLeftToRead > 0)
|
||||
{
|
||||
await ReadAsync(buffer, 0, buffer.Length, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
await VerifyAuthenticationAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
@@ -39,8 +77,15 @@ internal partial class WinzipAesCryptoStream
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
_authenticationFailure?.Throw();
|
||||
if (count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (_totalBytesLeftToRead == 0)
|
||||
{
|
||||
await VerifyAuthenticationAsync(cancellationToken).ConfigureAwait(false);
|
||||
return 0;
|
||||
}
|
||||
var bytesToRead = count;
|
||||
@@ -51,7 +96,17 @@ internal partial class WinzipAesCryptoStream
|
||||
var read = await _stream
|
||||
.ReadAsync(buffer, offset, bytesToRead, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (read == 0)
|
||||
{
|
||||
_authRead = true;
|
||||
throw new IncompleteArchiveException("Unexpected end of WinZip AES ciphertext.");
|
||||
}
|
||||
_authentication.TransformBlock(buffer, offset, read, null, 0);
|
||||
_totalBytesLeftToRead -= read;
|
||||
if (_totalBytesLeftToRead == 0)
|
||||
{
|
||||
await VerifyAuthenticationAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
ReadTransformBlocks(buffer, offset, read);
|
||||
|
||||
@@ -64,56 +119,22 @@ internal partial class WinzipAesCryptoStream
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (_totalBytesLeftToRead == 0)
|
||||
if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)buffer, out var segment))
|
||||
{
|
||||
return 0;
|
||||
return await ReadAsync(segment.Array!, segment.Offset, segment.Count, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
var bytesToRead = buffer.Length;
|
||||
if (buffer.Length > _totalBytesLeftToRead)
|
||||
var rented = ArrayPool<byte>.Shared.Rent(buffer.Length);
|
||||
try
|
||||
{
|
||||
bytesToRead = (int)_totalBytesLeftToRead;
|
||||
var read = await ReadAsync(rented, 0, buffer.Length, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
rented.AsSpan(0, read).CopyTo(buffer.Span);
|
||||
return read;
|
||||
}
|
||||
var read = await _stream
|
||||
.ReadAsync(buffer.Slice(0, bytesToRead), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
_totalBytesLeftToRead -= read;
|
||||
|
||||
ReadTransformBlocks(buffer.Span, read);
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
private void ReadTransformBlocks(Span<byte> buffer, int count)
|
||||
{
|
||||
var posn = 0;
|
||||
var remaining = count;
|
||||
|
||||
while (posn < buffer.Length && remaining > 0)
|
||||
finally
|
||||
{
|
||||
var n = ReadTransformOneBlock(buffer, posn, remaining);
|
||||
posn += n;
|
||||
remaining -= n;
|
||||
}
|
||||
}
|
||||
|
||||
private int ReadTransformOneBlock(Span<byte> buffer, int offset, int remaining)
|
||||
{
|
||||
if (_counterOutOffset == BLOCK_SIZE_IN_BYTES)
|
||||
{
|
||||
FillCounterOut();
|
||||
}
|
||||
|
||||
var bytesToXor = Math.Min(BLOCK_SIZE_IN_BYTES - _counterOutOffset, remaining);
|
||||
XorInPlace(buffer, offset, bytesToXor, _counterOutOffset);
|
||||
_counterOutOffset += bytesToXor;
|
||||
return bytesToXor;
|
||||
}
|
||||
|
||||
private void XorInPlace(Span<byte> buffer, int offset, int count, int counterOffset)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
buffer[offset + i] = (byte)(_counterOut[counterOffset + i] ^ buffer[offset + i]);
|
||||
ArrayPool<byte>.Shared.Return(rented, clearArray: true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Common.Zip;
|
||||
|
||||
@@ -14,6 +13,9 @@ internal partial class WinzipAesCryptoStream : Stream
|
||||
private readonly byte[] _counter = new byte[BLOCK_SIZE_IN_BYTES];
|
||||
private readonly Stream _stream;
|
||||
private readonly ICryptoTransform _transform;
|
||||
private readonly HMACSHA1 _authentication;
|
||||
private bool _authRead;
|
||||
private ExceptionDispatchInfo? _authenticationFailure;
|
||||
private int _nonce = 1;
|
||||
private byte[] _counterOut = new byte[BLOCK_SIZE_IN_BYTES];
|
||||
private int _counterOutOffset = BLOCK_SIZE_IN_BYTES;
|
||||
@@ -28,6 +30,14 @@ internal partial class WinzipAesCryptoStream : Stream
|
||||
{
|
||||
_stream = stream;
|
||||
_totalBytesLeftToRead = length;
|
||||
if (length < 0)
|
||||
{
|
||||
throw new InvalidFormatException("Invalid WinZip AES payload size.");
|
||||
}
|
||||
// PBKDF2's second key is the authentication key, not the CTR initialization vector.
|
||||
#pragma warning disable CA5350 // WinZip AES mandates HMAC-SHA1-80 for interoperability.
|
||||
_authentication = new HMACSHA1(winzipAesEncryptionData.IvBytes);
|
||||
#pragma warning restore CA5350
|
||||
|
||||
_cipher = CreateCipher(winzipAesEncryptionData);
|
||||
|
||||
@@ -69,45 +79,85 @@ internal partial class WinzipAesCryptoStream : Stream
|
||||
_isDisposed = true;
|
||||
if (disposing)
|
||||
{
|
||||
// Read out last 10 auth bytes - catch exceptions for async-only streams
|
||||
if (Utility.UseSyncOverAsyncDispose())
|
||||
try
|
||||
{
|
||||
var ten = ArrayPool<byte>.Shared.Rent(10);
|
||||
try
|
||||
if (!_authRead && Utility.UseSyncOverAsyncDispose())
|
||||
{
|
||||
#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits
|
||||
#pragma warning disable CA2012
|
||||
_stream.ReadFullyAsync(ten, 0, 10).GetAwaiter().GetResult();
|
||||
DrainAndVerifyAsync().GetAwaiter().GetResult();
|
||||
#pragma warning restore CA2012
|
||||
#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits
|
||||
}
|
||||
finally
|
||||
else if (!_authRead)
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(ten);
|
||||
var buffer = new byte[4096];
|
||||
while (_totalBytesLeftToRead > 0)
|
||||
{
|
||||
Read(buffer, 0, buffer.Length);
|
||||
}
|
||||
VerifyAuthentication();
|
||||
}
|
||||
}
|
||||
else
|
||||
finally
|
||||
{
|
||||
Span<byte> ten = stackalloc byte[10];
|
||||
_stream.ReadFully(ten);
|
||||
_authentication.Dispose();
|
||||
_transform.Dispose();
|
||||
_cipher.Dispose();
|
||||
_stream.Dispose();
|
||||
}
|
||||
_stream.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private async ValueTask ReadAuthBytesAsync()
|
||||
private void VerifyAuthentication()
|
||||
{
|
||||
byte[] authBytes = new byte[10];
|
||||
await _stream.ReadFullyAsync(authBytes, 0, 10).ConfigureAwait(false);
|
||||
_authenticationFailure?.Throw();
|
||||
if (_authRead)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_authRead = true;
|
||||
var tag = new byte[10];
|
||||
try
|
||||
{
|
||||
_stream.ReadExact(tag, 0, tag.Length);
|
||||
ValidateAuthentication(tag);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_authenticationFailure = ExceptionDispatchInfo.Capture(exception);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateAuthentication(byte[] tag)
|
||||
{
|
||||
_authentication.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
|
||||
var expected = _authentication.Hash!;
|
||||
var difference = 0;
|
||||
for (var i = 0; i < tag.Length; i++)
|
||||
{
|
||||
difference |= tag[i] ^ expected[i];
|
||||
}
|
||||
if (difference != 0)
|
||||
{
|
||||
throw new InvalidFormatException("WinZip AES authentication failed.");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush() { }
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
_authenticationFailure?.Throw();
|
||||
if (count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (_totalBytesLeftToRead == 0)
|
||||
{
|
||||
VerifyAuthentication();
|
||||
return 0;
|
||||
}
|
||||
var bytesToRead = count;
|
||||
@@ -116,7 +166,17 @@ internal partial class WinzipAesCryptoStream : Stream
|
||||
bytesToRead = (int)_totalBytesLeftToRead;
|
||||
}
|
||||
var read = _stream.Read(buffer, offset, bytesToRead);
|
||||
if (read == 0)
|
||||
{
|
||||
_authRead = true;
|
||||
throw new IncompleteArchiveException("Unexpected end of WinZip AES ciphertext.");
|
||||
}
|
||||
_authentication.TransformBlock(buffer, offset, read, null, 0);
|
||||
_totalBytesLeftToRead -= read;
|
||||
if (_totalBytesLeftToRead == 0)
|
||||
{
|
||||
VerifyAuthentication();
|
||||
}
|
||||
|
||||
ReadTransformBlocks(buffer, offset, read);
|
||||
|
||||
|
||||
@@ -160,8 +160,8 @@ internal partial class ZipHeaderFactory
|
||||
|
||||
var salt = new byte[WinzipAesEncryptionData.KeyLengthInBytes(keySize) / 2];
|
||||
var passwordVerifyValue = new byte[2];
|
||||
stream.Read(salt, 0, salt.Length);
|
||||
stream.Read(passwordVerifyValue, 0, 2);
|
||||
stream.ReadExact(salt, 0, salt.Length);
|
||||
stream.ReadExact(passwordVerifyValue, 0, 2);
|
||||
entryHeader.WinzipAesEncryptionData = new WinzipAesEncryptionData(
|
||||
keySize,
|
||||
salt,
|
||||
|
||||
@@ -327,7 +327,7 @@ internal class ZlibBaseStream : Stream, IStreamStack
|
||||
if (!_wantCompress)
|
||||
{
|
||||
// workitem 8501: handle edge case (decompress empty stream)
|
||||
if (_z.TotalBytesOut == 0L)
|
||||
if (_z.TotalBytesOut == 0L && !_readCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -336,23 +336,14 @@ internal class ZlibBaseStream : Stream, IStreamStack
|
||||
Span<byte> trailer = stackalloc byte[8];
|
||||
|
||||
// workitem 8679
|
||||
if (_z.AvailableBytesIn != 8)
|
||||
if (_z.AvailableBytesIn < 8)
|
||||
{
|
||||
// Make sure we have read to the end of the stream
|
||||
_z.InputBuffer.AsSpan(_z.NextIn, _z.AvailableBytesIn).CopyTo(trailer);
|
||||
var bytesNeeded = 8 - _z.AvailableBytesIn;
|
||||
var bytesRead = _stream.Read(
|
||||
trailer.Slice(_z.AvailableBytesIn, bytesNeeded)
|
||||
);
|
||||
if (bytesNeeded != bytesRead)
|
||||
if (!_stream.ReadFully(trailer.Slice(_z.AvailableBytesIn, bytesNeeded)))
|
||||
{
|
||||
throw new ZlibException(
|
||||
String.Format(
|
||||
Constants.DefaultCultureInfo,
|
||||
"Protocol error. AvailableBytesIn={0}, expected 8",
|
||||
_z.AvailableBytesIn + bytesRead
|
||||
)
|
||||
);
|
||||
throw new ZlibException("Unexpected end-of-file reading GZIP trailer.");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -360,34 +351,7 @@ internal class ZlibBaseStream : Stream, IStreamStack
|
||||
_z.InputBuffer.AsSpan(_z.NextIn, trailer.Length).CopyTo(trailer);
|
||||
}
|
||||
|
||||
var crc32_expected = BinaryPrimitives.ReadInt32LittleEndian(trailer);
|
||||
var crc32_actual = crc.Crc32Result;
|
||||
var isize_expected = BinaryPrimitives.ReadInt32LittleEndian(trailer.Slice(4));
|
||||
var isize_actual = (Int32)(_z.TotalBytesOut & 0x00000000FFFFFFFF);
|
||||
|
||||
if (crc32_actual != crc32_expected)
|
||||
{
|
||||
throw new ZlibException(
|
||||
String.Format(
|
||||
Constants.DefaultCultureInfo,
|
||||
"Bad CRC32 in GZIP stream. (actual({0:X8})!=expected({1:X8}))",
|
||||
crc32_actual,
|
||||
crc32_expected
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (isize_actual != isize_expected)
|
||||
{
|
||||
throw new ZlibException(
|
||||
String.Format(
|
||||
Constants.DefaultCultureInfo,
|
||||
"Bad size in GZIP stream. (actual({0})!=expected({1}))",
|
||||
isize_actual,
|
||||
isize_expected
|
||||
)
|
||||
);
|
||||
}
|
||||
ValidateGzipTrailer(trailer);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -477,7 +441,7 @@ internal class ZlibBaseStream : Stream, IStreamStack
|
||||
if (!_wantCompress)
|
||||
{
|
||||
// workitem 8501: handle edge case (decompress empty stream)
|
||||
if (_z.TotalBytesOut == 0L)
|
||||
if (_z.TotalBytesOut == 0L && !_readCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -486,15 +450,30 @@ internal class ZlibBaseStream : Stream, IStreamStack
|
||||
byte[] trailer = new byte[8];
|
||||
|
||||
// workitem 8679
|
||||
if (_z.AvailableBytesIn != 8)
|
||||
if (_z.AvailableBytesIn < 8)
|
||||
{
|
||||
// Make sure we have read to the end of the stream
|
||||
_z.InputBuffer.AsSpan(_z.NextIn, _z.AvailableBytesIn).CopyTo(trailer);
|
||||
var bytesNeeded = 8 - _z.AvailableBytesIn;
|
||||
var bytesRead = await _stream
|
||||
.ReadAsync(trailer, _z.AvailableBytesIn, bytesNeeded, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (
|
||||
!await _stream
|
||||
.ReadFullyAsync(
|
||||
trailer,
|
||||
_z.AvailableBytesIn,
|
||||
bytesNeeded,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
throw new ZlibException("Unexpected end-of-file reading GZIP trailer.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_z.InputBuffer.AsSpan(_z.NextIn, trailer.Length).CopyTo(trailer);
|
||||
}
|
||||
ValidateGzipTrailer(trailer);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -504,6 +483,36 @@ internal class ZlibBaseStream : Stream, IStreamStack
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateGzipTrailer(ReadOnlySpan<byte> trailer)
|
||||
{
|
||||
var expectedCrc = BinaryPrimitives.ReadInt32LittleEndian(trailer);
|
||||
var actualCrc = crc.Crc32Result;
|
||||
if (actualCrc != expectedCrc)
|
||||
{
|
||||
throw new ZlibException(
|
||||
String.Format(
|
||||
Constants.DefaultCultureInfo,
|
||||
"Bad CRC32 in GZIP stream. (actual({0:X8})!=expected({1:X8}))",
|
||||
actualCrc,
|
||||
expectedCrc
|
||||
)
|
||||
);
|
||||
}
|
||||
var expectedSize = BinaryPrimitives.ReadInt32LittleEndian(trailer.Slice(4));
|
||||
var actualSize = unchecked((int)_z.TotalBytesOut);
|
||||
if (actualSize != expectedSize)
|
||||
{
|
||||
throw new ZlibException(
|
||||
String.Format(
|
||||
Constants.DefaultCultureInfo,
|
||||
"Bad size in GZIP stream. (actual({0})!=expected({1}))",
|
||||
actualSize,
|
||||
expectedSize
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void end()
|
||||
{
|
||||
if (z is null)
|
||||
@@ -668,6 +677,7 @@ internal class ZlibBaseStream : Stream, IStreamStack
|
||||
#endif
|
||||
|
||||
private bool nomoreinput;
|
||||
private bool _readCompleted;
|
||||
private bool isDisposed;
|
||||
|
||||
private string ReadZeroTerminatedString()
|
||||
@@ -736,7 +746,7 @@ internal class ZlibBaseStream : Stream, IStreamStack
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (n != 10)
|
||||
if (!_stream.ReadFully(header.Slice(n)))
|
||||
{
|
||||
throw new ZlibException("Not a valid GZIP stream.");
|
||||
}
|
||||
@@ -748,21 +758,23 @@ internal class ZlibBaseStream : Stream, IStreamStack
|
||||
|
||||
var timet = BinaryPrimitives.ReadInt32LittleEndian(header.Slice(4));
|
||||
_GzipMtime = TarHeader.EPOCH.AddSeconds(timet);
|
||||
totalBytesRead += n;
|
||||
totalBytesRead += header.Length;
|
||||
if ((header[3] & 0x04) == 0x04)
|
||||
{
|
||||
// read and discard extra field
|
||||
n = _stream.Read(header.Slice(0, 2)); // 2-byte length field
|
||||
totalBytesRead += n;
|
||||
|
||||
var extraLength = header[0] + (header[1] * 256);
|
||||
var extra = new byte[extraLength];
|
||||
n = _stream.Read(extra, 0, extra.Length);
|
||||
if (n != extraLength)
|
||||
if (!_stream.ReadFully(header.Slice(0, 2)))
|
||||
{
|
||||
throw new ZlibException("Unexpected end-of-file reading GZIP header.");
|
||||
}
|
||||
totalBytesRead += n;
|
||||
totalBytesRead += 2;
|
||||
|
||||
var extraLength = header[0] + (header[1] * 256);
|
||||
var extra = new byte[extraLength];
|
||||
if (!_stream.ReadFully(extra))
|
||||
{
|
||||
throw new ZlibException("Unexpected end-of-file reading GZIP header.");
|
||||
}
|
||||
totalBytesRead += extraLength;
|
||||
}
|
||||
if ((header[3] & 0x08) == 0x08)
|
||||
{
|
||||
@@ -774,7 +786,12 @@ internal class ZlibBaseStream : Stream, IStreamStack
|
||||
}
|
||||
if ((header[3] & 0x02) == 0x02)
|
||||
{
|
||||
Read(_buf1, 0, 1); // CRC16, ignore
|
||||
Span<byte> headerCrc = stackalloc byte[2];
|
||||
if (!_stream.ReadFully(headerCrc))
|
||||
{
|
||||
throw new ZlibException("Unexpected end-of-file reading GZIP header CRC.");
|
||||
}
|
||||
totalBytesRead += 2;
|
||||
}
|
||||
|
||||
return totalBytesRead;
|
||||
@@ -796,7 +813,11 @@ internal class ZlibBaseStream : Stream, IStreamStack
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (n != 10)
|
||||
if (
|
||||
!await _stream
|
||||
.ReadFullyAsync(header, n, header.Length - n, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
throw new ZlibException("Not a valid GZIP stream.");
|
||||
}
|
||||
@@ -808,23 +829,25 @@ internal class ZlibBaseStream : Stream, IStreamStack
|
||||
|
||||
var timet = BinaryPrimitives.ReadInt32LittleEndian(header.AsSpan(4));
|
||||
_GzipMtime = TarHeader.EPOCH.AddSeconds(timet);
|
||||
totalBytesRead += n;
|
||||
totalBytesRead += header.Length;
|
||||
if ((header[3] & 0x04) == 0x04)
|
||||
{
|
||||
// read and discard extra field
|
||||
n = await _stream.ReadAsync(header, 0, 2, cancellationToken).ConfigureAwait(false); // 2-byte length field
|
||||
totalBytesRead += n;
|
||||
|
||||
var extraLength = header[0] + (header[1] * 256);
|
||||
var extra = new byte[extraLength];
|
||||
n = await _stream
|
||||
.ReadAsync(extra, 0, extra.Length, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (n != extraLength)
|
||||
if (
|
||||
!await _stream.ReadFullyAsync(header, 0, 2, cancellationToken).ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
throw new ZlibException("Unexpected end-of-file reading GZIP header.");
|
||||
}
|
||||
totalBytesRead += n;
|
||||
totalBytesRead += 2;
|
||||
|
||||
var extraLength = header[0] + (header[1] * 256);
|
||||
var extra = new byte[extraLength];
|
||||
if (!await _stream.ReadFullyAsync(extra, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
throw new ZlibException("Unexpected end-of-file reading GZIP header.");
|
||||
}
|
||||
totalBytesRead += extraLength;
|
||||
}
|
||||
if ((header[3] & 0x08) == 0x08)
|
||||
{
|
||||
@@ -838,7 +861,12 @@ internal class ZlibBaseStream : Stream, IStreamStack
|
||||
}
|
||||
if ((header[3] & 0x02) == 0x02)
|
||||
{
|
||||
await _stream.ReadAsync(_buf1, 0, 1, cancellationToken).ConfigureAwait(false); // CRC16, ignore
|
||||
var headerCrc = new byte[2];
|
||||
if (!await _stream.ReadFullyAsync(headerCrc, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
throw new ZlibException("Unexpected end-of-file reading GZIP header CRC.");
|
||||
}
|
||||
totalBytesRead += 2;
|
||||
}
|
||||
|
||||
return totalBytesRead;
|
||||
@@ -889,7 +917,7 @@ internal class ZlibBaseStream : Stream, IStreamStack
|
||||
_z.NextOut = offset;
|
||||
_z.AvailableBytesOut = count;
|
||||
|
||||
if (count == 0)
|
||||
if (count == 0 || _readCompleted)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -1012,6 +1040,7 @@ internal class ZlibBaseStream : Stream, IStreamStack
|
||||
}
|
||||
}
|
||||
|
||||
_readCompleted = !_wantCompress && rc == ZlibConstants.Z_STREAM_END;
|
||||
rc = (count - _z.AvailableBytesOut);
|
||||
|
||||
// calculate CRC after reading
|
||||
@@ -1020,12 +1049,8 @@ internal class ZlibBaseStream : Stream, IStreamStack
|
||||
crc.SlurpBlock(buffer, offset, rc);
|
||||
}
|
||||
|
||||
if (rc == ZlibConstants.Z_STREAM_END && z.AvailableBytesIn != 0 && !_wantCompress)
|
||||
{
|
||||
//rewind the buffer
|
||||
this.Rewind(z.AvailableBytesIn);
|
||||
z.AvailableBytesIn = 0;
|
||||
}
|
||||
// Preserve read-ahead bytes for trailer validation or the entry stream's Flush.
|
||||
// rc is now an output byte count, not a codec status.
|
||||
|
||||
return rc;
|
||||
}
|
||||
@@ -1081,7 +1106,7 @@ internal class ZlibBaseStream : Stream, IStreamStack
|
||||
_z.NextOut = offset;
|
||||
_z.AvailableBytesOut = count;
|
||||
|
||||
if (count == 0)
|
||||
if (count == 0 || _readCompleted)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -1206,6 +1231,7 @@ internal class ZlibBaseStream : Stream, IStreamStack
|
||||
}
|
||||
}
|
||||
|
||||
_readCompleted = !_wantCompress && rc == ZlibConstants.Z_STREAM_END;
|
||||
rc = (count - _z.AvailableBytesOut);
|
||||
|
||||
// calculate CRC after reading
|
||||
@@ -1214,12 +1240,7 @@ internal class ZlibBaseStream : Stream, IStreamStack
|
||||
crc.SlurpBlock(buffer, offset, rc);
|
||||
}
|
||||
|
||||
if (rc == ZlibConstants.Z_STREAM_END && z.AvailableBytesIn != 0 && !_wantCompress)
|
||||
{
|
||||
//rewind the buffer
|
||||
this.Rewind(z.AvailableBytesIn);
|
||||
z.AvailableBytesIn = 0;
|
||||
}
|
||||
// Preserve read-ahead bytes, as in the synchronous path.
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ internal sealed partial class AesDecoderStream : DecoderStream2
|
||||
public AesDecoderStream(Stream input, byte[] info, IPasswordProvider pass, long limit)
|
||||
{
|
||||
var password = pass.CryptoGetTextPassword();
|
||||
if (password == null)
|
||||
if (password is null)
|
||||
{
|
||||
throw new SharpCompress.Common.CryptographicException(
|
||||
"Encrypted 7Zip archive has no password specified."
|
||||
@@ -43,7 +43,7 @@ internal sealed partial class AesDecoderStream : DecoderStream2
|
||||
|
||||
var passwordBytes = Encoding.Unicode.GetBytes(password);
|
||||
var key = InitKey(numCyclesPower, salt, passwordBytes);
|
||||
if (key == null)
|
||||
if (key is null)
|
||||
{
|
||||
throw new ArchiveOperationException("Initialized with null key");
|
||||
}
|
||||
@@ -147,13 +147,24 @@ internal sealed partial class AesDecoderStream : DecoderStream2
|
||||
|
||||
private void Init(byte[] info, out int numCyclesPower, out byte[] salt, out byte[] iv)
|
||||
{
|
||||
if (info.Length == 0)
|
||||
{
|
||||
throw new InvalidFormatException("Missing 7z AES properties.");
|
||||
}
|
||||
var bt = info[0];
|
||||
numCyclesPower = bt & 0x3F;
|
||||
|
||||
// Check before the no-salt/no-IV return, and before starting expensive key derivation.
|
||||
// 0x3F is the format's direct-key mode, not an iteration exponent.
|
||||
if (numCyclesPower > 24 && numCyclesPower != 0x3F)
|
||||
{
|
||||
throw new NotSupportedException("Unsupported 7z AES KDF count.");
|
||||
}
|
||||
|
||||
if ((bt & 0xC0) == 0)
|
||||
{
|
||||
salt = Array.Empty<byte>();
|
||||
iv = Array.Empty<byte>();
|
||||
iv = new byte[16];
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -183,11 +194,6 @@ internal sealed partial class AesDecoderStream : DecoderStream2
|
||||
{
|
||||
iv[i] = info[i + saltSize + 2];
|
||||
}
|
||||
|
||||
if (numCyclesPower > 24)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
private byte[]? InitKey(int mNumCyclesPower, byte[] salt, byte[] pass)
|
||||
|
||||
@@ -129,11 +129,7 @@ public sealed partial class LZipStream
|
||||
var header = ArrayPool<byte>.Shared.Rent(6);
|
||||
try
|
||||
{
|
||||
var n = await stream.ReadAsync(header, 0, 6, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// TODO: Handle reading only part of the header?
|
||||
|
||||
if (n != 6)
|
||||
if (!await stream.ReadFullyAsync(header, 0, 6, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -148,13 +144,7 @@ public sealed partial class LZipStream
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
var basePower = header[5] & 0x1F;
|
||||
var subtractionNumerator = (header[5] & 0xE0) >> 5;
|
||||
if (basePower < 4 || basePower > 30)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return (1 << basePower) - (subtractionNumerator * (1 << (basePower - 4)));
|
||||
return DecodeDictionarySize(header[5]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -253,11 +253,7 @@ public sealed partial class LZipStream : Stream, IFinishable
|
||||
{
|
||||
// Read the header
|
||||
Span<byte> header = stackalloc byte[6];
|
||||
var n = stream.Read(header);
|
||||
|
||||
// TODO: Handle reading only part of the header?
|
||||
|
||||
if (n != 6)
|
||||
if (!stream.ReadFully(header))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -272,9 +268,18 @@ public sealed partial class LZipStream : Stream, IFinishable
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
var basePower = header[5] & 0x1F;
|
||||
var subtractionNumerator = (header[5] & 0xE0) >> 5;
|
||||
return (1 << basePower) - (subtractionNumerator * (1 << (basePower - 4)));
|
||||
return DecodeDictionarySize(header[5]);
|
||||
}
|
||||
|
||||
private static int DecodeDictionarySize(byte encoded)
|
||||
{
|
||||
var basePower = encoded & 0x1F;
|
||||
if (basePower < 12 || basePower > 29)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
var size = (1 << basePower) - ((encoded >> 5) * (1 << (basePower - 4)));
|
||||
return size >= 4096 ? size : 0;
|
||||
}
|
||||
|
||||
// Async methods moved to LZipStream.Async.cs
|
||||
|
||||
Reference in New Issue
Block a user