Merge pull request #1488 from adamhathcock/fix/1485-solid-7z-deflate

Fix solid 7z Deflate extraction corruption
This commit is contained in:
Adam Hathcock
2026-11-03 09:25:49 +00:00
committed by GitHub
5 changed files with 137 additions and 5 deletions

View File

@@ -188,8 +188,12 @@ public partial class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, Sev
);
}
return CreateEntryStream(
new ReadOnlySubStream(_currentFolderStream, entry.Size, leaveOpen: true)
// Entry boundaries do not end a solid folder's decoder. Its buffered input must
// remain intact until the reader advances to another folder or is disposed.
return new EntryStream(
this,
new ReadOnlySubStream(_currentFolderStream, entry.Size, leaveOpen: true),
rewindDecoderOnDispose: false
);
}

View File

@@ -31,7 +31,7 @@ public partial class EntryStream
}
//Need a safe standard approach to this - it's okay for compression to overreads. Handling needs to be standardised
if (_stream is IStreamStack ss)
if (_rewindDecoderOnDispose && _stream is IStreamStack ss)
{
if (ss.BaseStream() is SharpCompress.Compressors.Deflate.DeflateStream deflateStream)
{

View File

@@ -12,13 +12,15 @@ public partial class EntryStream : Stream
{
private readonly IReader _reader;
private readonly Stream _stream;
private readonly bool _rewindDecoderOnDispose;
private bool _completed;
private bool _isDisposed;
internal EntryStream(IReader reader, Stream stream)
internal EntryStream(IReader reader, Stream stream, bool rewindDecoderOnDispose = true)
{
_reader = reader;
_stream = stream;
_rewindDecoderOnDispose = rewindDecoderOnDispose;
}
/// <summary>
@@ -54,7 +56,7 @@ public partial class EntryStream : Stream
}
//Need a safe standard approach to this - it's okay for compression to overreads. Handling needs to be standardised
if (_stream is IStreamStack ss)
if (_rewindDecoderOnDispose && _stream is IStreamStack ss)
{
if (
ss.GetStream<SharpCompress.Compressors.Deflate.DeflateStream>()

View File

@@ -0,0 +1,126 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Archives.SevenZip;
using SharpCompress.Readers;
using Xunit;
namespace SharpCompress.Test.SevenZip;
public class SevenZipDeflateSolidTests : TestBase
{
private static readonly int[] Sizes = [1000, 70000, 100000, 200000, 500000, 1048576, 3000000];
// Generated with libarchive bsdtar --format 7zip --options compression=deflate.
// Each file repeats a 4096-byte xorshift sequence with a different starting offset.
private static byte[] CreateExpected(int index)
{
var block = new byte[4096];
uint state = 0x12345678;
for (var i = 0; i < block.Length; i++)
{
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
block[i] = (byte)state;
}
var data = new byte[Sizes[index]];
for (var i = 0; i < data.Length; i++)
{
data[i] = block[(i + index * 997) % block.Length];
}
return data;
}
[Theory]
[InlineData(512, 0)]
[InlineData(4096, 1)]
[InlineData(65536, 2)]
[InlineData(131072, 0)]
[InlineData(300000, 0)]
public void SevenZipArchive_Solid_Deflate_ExtractAllEntries(int bufferSize, int firstEntryMode)
{
using var archive = SevenZipArchive.OpenArchive(
Path.Combine(TEST_ARCHIVES_PATH, "7Zip.solid.Deflate.7z")
);
Assert.True(archive.IsSolid);
using var reader = archive.ExtractAllEntries();
var index = 0;
while (reader.MoveToNextEntry())
{
Assert.Equal($"{index}.bin", reader.Entry.Key);
Assert.Equal(Sizes[index], reader.Entry.Size);
if (index == 0 && firstEntryMode == 2)
{
index++;
continue;
}
using var stream = reader.OpenEntryStream();
if (index == 0 && firstEntryMode == 1)
{
Assert.True(stream.Read(new byte[17], 0, 17) > 0);
}
else
{
using var output = new MemoryStream();
stream.CopyTo(output, bufferSize);
Assert.Equal(CreateExpected(index), output.ToArray());
}
index++;
}
Assert.Equal(Sizes.Length, index);
}
[Theory]
[InlineData(512, 0)]
[InlineData(4096, 1)]
[InlineData(65536, 2)]
[InlineData(131072, 0)]
[InlineData(300000, 0)]
public async Task SevenZipArchive_Solid_Deflate_ExtractAllEntries_Async(
int bufferSize,
int firstEntryMode
)
{
await using var archive = await SevenZipArchive.OpenAsyncArchive(
Path.Combine(TEST_ARCHIVES_PATH, "7Zip.solid.Deflate.7z"),
cancellationToken: CancellationToken.None
);
Assert.True(await archive.IsSolidAsync());
await using var reader = await archive.ExtractAllEntriesAsync();
var index = 0;
while (await reader.MoveToNextEntryAsync(CancellationToken.None))
{
Assert.Equal($"{index}.bin", reader.Entry.Key);
Assert.Equal(Sizes[index], reader.Entry.Size);
if (index == 0 && firstEntryMode == 2)
{
index++;
continue;
}
#if LEGACY_DOTNET
using var stream = await reader.OpenEntryStreamAsync(CancellationToken.None);
#else
await using var stream = await reader.OpenEntryStreamAsync(CancellationToken.None);
#endif
if (index == 0 && firstEntryMode == 1)
{
Assert.True(
await stream.ReadAsync(new byte[17], 0, 17, CancellationToken.None) > 0
);
}
else
{
using var output = new MemoryStream();
await stream.CopyToAsync(output, bufferSize, CancellationToken.None);
Assert.Equal(CreateExpected(index), output.ToArray());
}
index++;
}
Assert.Equal(Sizes.Length, index);
}
}

Binary file not shown.