diff --git a/src/SharpCompress/Common/ChecksumValidationStream.cs b/src/SharpCompress/Common/ChecksumValidationStream.cs index 1b14cc9d..2aea94b1 100644 --- a/src/SharpCompress/Common/ChecksumValidationStream.cs +++ b/src/SharpCompress/Common/ChecksumValidationStream.cs @@ -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 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 diff --git a/src/SharpCompress/Common/GZip/GZipChecksumValidationStream.cs b/src/SharpCompress/Common/GZip/GZipChecksumValidationStream.cs index 512d7cc2..6763ec11 100644 --- a/src/SharpCompress/Common/GZip/GZipChecksumValidationStream.cs +++ b/src/SharpCompress/Common/GZip/GZipChecksumValidationStream.cs @@ -54,7 +54,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 +65,10 @@ internal sealed class GZipChecksumValidationStream : Stream public override int Read(Span buffer) { var read = _source.Read(buffer); - UpdateAndValidateAtEof(buffer[..read], read); + if (!buffer.IsEmpty) + { + UpdateAndValidateAtEof(buffer[..read], read); + } return read; } #endif @@ -93,7 +99,10 @@ 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) + { + UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read); + } return read; } @@ -104,7 +113,10 @@ internal sealed class GZipChecksumValidationStream : Stream ) { var read = await _source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); - UpdateAndValidateAtEof(buffer.Span[..read], read); + if (!buffer.IsEmpty) + { + UpdateAndValidateAtEof(buffer.Span[..read], read); + } return read; } #endif diff --git a/tests/SharpCompress.Test/Streams/ChecksumReadRegressionTests.cs b/tests/SharpCompress.Test/Streams/ChecksumReadRegressionTests.cs new file mode 100644 index 00000000..a6f3237d --- /dev/null +++ b/tests/SharpCompress.Test/Streams/ChecksumReadRegressionTests.cs @@ -0,0 +1,40 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Common; +using SharpCompress.Common.GZip; +using SharpCompress.Crypto; +using Xunit; + +namespace SharpCompress.Test.Streams; + +public class ChecksumReadRegressionTests +{ + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task Zero_Length_Read_Does_Not_Validate(bool gzip) + { + byte[] payload = [1, 2, 3]; + var crc = Crc32Stream.Compute(payload); + using var input = new MemoryStream(payload); + using var raw = new MemoryStream(); + using Stream stream = gzip + ? new GZipChecksumValidationStream(input, raw, "test", crc, 3) + : new ChecksumValidationStream( + input, + new ChecksumDescriptor(ChecksumKind.Crc32, crc, true), + "test" + ); + Assert.Equal(0, stream.Read(Array.Empty(), 0, 0)); + Assert.Equal(0, await stream.ReadAsync(Array.Empty(), 0, 0, CancellationToken.None)); +#if !LEGACY_DOTNET + Assert.Equal(0, stream.Read(Span.Empty)); + Assert.Equal(0, await stream.ReadAsync(Memory.Empty, CancellationToken.None)); +#endif + using var output = new MemoryStream(); + await stream.CopyToAsync(output, 81920, CancellationToken.None); + Assert.Equal(payload, output.ToArray()); + } +}