BZip2, GZip and XZ done

This commit is contained in:
Adam Hathcock
2026-06-15 09:59:48 +01:00
parent e9e05520d6
commit 808524d917
8 changed files with 335 additions and 7 deletions

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Common.Options;
@@ -86,6 +87,17 @@ public abstract class Entry : IEntry
internal virtual ChecksumDescriptor Checksum => default;
internal virtual Stream WrapWithChecksumValidation(Stream source, ExtractionOptions options)
{
var checksum = Checksum;
if (!checksum.IsAvailable)
{
return source;
}
return new ChecksumValidationStream(source, checksum, Key);
}
/// <summary>
/// Entry file attribute.
/// </summary>

View File

@@ -0,0 +1,165 @@
using System;
using System.Buffers.Binary;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Crypto;
namespace SharpCompress.Common.GZip;
internal sealed class GZipChecksumValidationStream : Stream
{
private readonly Stream _source;
private readonly Stream _rawStream;
private readonly string _entryName;
private readonly uint? _expectedCrc;
private readonly uint? _expectedSize;
private readonly uint[] _crc32Table;
private uint _seed = Crc32Stream.DEFAULT_SEED;
private uint _size;
private bool _validated;
internal GZipChecksumValidationStream(
Stream source,
Stream rawStream,
string? entryName,
uint? expectedCrc,
uint? expectedSize
)
{
_source = source;
_rawStream = rawStream;
_entryName = string.IsNullOrEmpty(entryName) ? "Entry" : entryName!;
_expectedCrc = expectedCrc;
_expectedSize = expectedSize;
_crc32Table = Crc32Stream.InitializeTable(Crc32Stream.DEFAULT_POLYNOMIAL);
}
public override bool CanRead => _source.CanRead;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => _source.Length;
public override long Position
{
get => _source.Position;
set => throw new NotSupportedException();
}
public override void Flush() => _source.Flush();
public override Task FlushAsync(CancellationToken cancellationToken) =>
_source.FlushAsync(cancellationToken);
public override int Read(byte[] buffer, int offset, int count)
{
var read = _source.Read(buffer, offset, count);
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
return read;
}
#if !LEGACY_DOTNET
public override int Read(Span<byte> buffer)
{
var read = _source.Read(buffer);
UpdateAndValidateAtEof(buffer[..read], read);
return read;
}
#endif
public override int ReadByte()
{
var value = _source.ReadByte();
if (value == -1)
{
Validate();
}
else
{
_seed = Crc32Stream.CalculateCrc(_crc32Table, _seed, (byte)value);
_size++;
}
return value;
}
public override async Task<int> ReadAsync(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken
)
{
var read = await _source
.ReadAsync(buffer, offset, count, cancellationToken)
.ConfigureAwait(false);
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
return read;
}
#if !LEGACY_DOTNET
public override async ValueTask<int> ReadAsync(
Memory<byte> buffer,
CancellationToken cancellationToken = default
)
{
var read = await _source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
UpdateAndValidateAtEof(buffer.Span[..read], read);
return read;
}
#endif
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count) =>
throw new NotSupportedException();
private void UpdateAndValidateAtEof(ReadOnlySpan<byte> buffer, int read)
{
if (read > 0)
{
_seed = Crc32Stream.CalculateCrc(_crc32Table, _seed, buffer);
_size += unchecked((uint)read);
return;
}
Validate();
}
private void Validate()
{
if (_validated)
{
return;
}
_validated = true;
var expectedCrc = _expectedCrc;
var expectedSize = _expectedSize;
if (!expectedCrc.HasValue || !expectedSize.HasValue)
{
Span<byte> trailer = stackalloc byte[8];
_rawStream.ReadFully(trailer);
expectedCrc = BinaryPrimitives.ReadUInt32LittleEndian(trailer);
expectedSize = BinaryPrimitives.ReadUInt32LittleEndian(trailer[4..]);
}
var actualCrc = ~_seed;
if (actualCrc != expectedCrc.Value)
{
throw new InvalidFormatException(
$"CRC mismatch for entry '{_entryName}'. Expected 0x{expectedCrc.Value:X8}, actual 0x{actualCrc:X8}."
);
}
if (_size != expectedSize.Value)
{
throw new InvalidFormatException(
$"Size mismatch for entry '{_entryName}'. Expected {expectedSize.Value}, actual {_size}."
);
}
}
}

View File

@@ -20,6 +20,9 @@ public partial class GZipEntry : Entry
public override long Crc => _filePart?.Crc ?? 0;
internal override Stream WrapWithChecksumValidation(Stream source, ExtractionOptions options) =>
_filePart?.WrapWithChecksumValidation(source, Key) ?? source;
public override string? Key => _filePart?.FilePartName;
public override string? LinkTarget => null;

View File

@@ -5,6 +5,7 @@ using System.IO;
using SharpCompress.Common.Tar.Headers;
using SharpCompress.Compressors;
using SharpCompress.Compressors.Deflate;
using SharpCompress.IO;
using SharpCompress.Providers;
namespace SharpCompress.Common.GZip;
@@ -48,7 +49,7 @@ internal sealed partial class GZipFilePart : FilePart
)
: base(archiveEncoding)
{
_stream = stream;
_stream = SharpCompressStream.Create(stream);
_compressionProviders = compressionProviders;
}
@@ -66,6 +67,9 @@ internal sealed partial class GZipFilePart : FilePart
return _compressionProviders.CreateDecompressStream(CompressionType.Deflate, _stream);
}
internal Stream WrapWithChecksumValidation(Stream source, string? entryName) =>
new GZipChecksumValidationStream(source, _stream, entryName, Crc, UncompressedSize);
internal override Stream GetRawStream() => _stream;
private void ReadTrailer()

View File

@@ -11,13 +11,10 @@ internal static partial class IEntryExtensions
ExtractionOptions? options
)
{
if (options?.CheckCrc != false && entry is Entry typedEntry)
options ??= new ExtractionOptions();
if (options.CheckCrc && entry is Entry typedEntry)
{
var checksum = typedEntry.Checksum;
if (checksum.IsAvailable)
{
return new ChecksumValidationStream(source, checksum, entry.Key);
}
return typedEntry.WrapWithChecksumValidation(source, options);
}
return source;