Merge remote-tracking branch 'origin/release' into fix/1439-sevenzip-password-exceptions

# Conflicts:
#	docs/API.md
This commit is contained in:
Adam Hathcock
2026-10-12 17:10:32 +01:00
47 changed files with 1919 additions and 327 deletions

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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.");
}
}
}

View File

@@ -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;

View File

@@ -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}.");

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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);

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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
{

View File

@@ -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