From cf5512520242a1f5a5bf4b75b1b3fd633f96bc6b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 10:58:07 +0000 Subject: [PATCH 1/4] Initial plan From 51e22cea71e38c33dd61bcd1f8b9b964032322b6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:02:53 +0000 Subject: [PATCH 2/4] Initial plan for fixing GZip non-seekable stream support Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- src/SharpCompress/packages.lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index 9ebb1677..b85a38f7 100644 --- a/src/SharpCompress/packages.lock.json +++ b/src/SharpCompress/packages.lock.json @@ -335,9 +335,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.17, )", - "resolved": "8.0.17", - "contentHash": "x5/y4l8AtshpBOrCZdlE4txw8K3e3s9meBFeZeR3l8hbbku2V7kK6ojhXvrbjg1rk3G+JqL1BI26gtgc1ZrdUw==" + "requested": "[8.0.20, )", + "resolved": "8.0.20", + "contentHash": "Rhcto2AjGvTO62+/VTmBpumBOmqIGp7nYEbTbmEXkCq4yPGxV8whju3/HsIA/bKyo2+DggaYk5+/8sxb1AbPTw==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", From 4067b6ed2c8284dc4d4467dca7bfc0bcb3d84a67 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:08:38 +0000 Subject: [PATCH 3/4] Fix GZip extraction for non-seekable streams - Modified GZipFilePart to only access stream.Position when stream.CanSeek is true - Modified GZipArchiveEntry.OpenEntryStream to check CanSeek before accessing Position - Added test case GZip_Archive_NonSeekableStream to verify non-seekable stream support - All existing tests pass Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- .../Archives/GZip/GZipArchiveEntry.cs | 5 +- src/SharpCompress/Common/GZip/GZipFilePart.cs | 6 +- .../GZip/GZipArchiveTests.cs | 57 +++++++++++++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/SharpCompress/Archives/GZip/GZipArchiveEntry.cs b/src/SharpCompress/Archives/GZip/GZipArchiveEntry.cs index 1cc115e4..62e4760b 100644 --- a/src/SharpCompress/Archives/GZip/GZipArchiveEntry.cs +++ b/src/SharpCompress/Archives/GZip/GZipArchiveEntry.cs @@ -15,9 +15,10 @@ public class GZipArchiveEntry : GZipEntry, IArchiveEntry { //this is to reset the stream to be read multiple times var part = (GZipFilePart)Parts.Single(); - if (part.GetRawStream().Position != part.EntryStartPosition) + var rawStream = part.GetRawStream(); + if (rawStream.CanSeek && rawStream.Position != part.EntryStartPosition) { - part.GetRawStream().Position = part.EntryStartPosition; + rawStream.Position = part.EntryStartPosition; } return Parts.Single().GetCompressedStream().NotNull(); } diff --git a/src/SharpCompress/Common/GZip/GZipFilePart.cs b/src/SharpCompress/Common/GZip/GZipFilePart.cs index 4a1c9515..d3c614f9 100644 --- a/src/SharpCompress/Common/GZip/GZipFilePart.cs +++ b/src/SharpCompress/Common/GZip/GZipFilePart.cs @@ -24,8 +24,12 @@ internal sealed class GZipFilePart : FilePart stream.Position = stream.Length - 8; ReadTrailer(); stream.Position = position; + EntryStartPosition = position; + } + else + { + EntryStartPosition = 0; } - EntryStartPosition = stream.Position; } internal long EntryStartPosition { get; } diff --git a/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs b/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs index 6a2b9795..c8a87327 100644 --- a/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using System.Linq; using SharpCompress.Archives; @@ -124,4 +125,60 @@ public class GZipArchiveTests : ArchiveTests using var archive = GZipArchive.Open(stream); Assert.Equal(archive.Type, ArchiveType.GZip); } + + [Fact] + public void GZip_Archive_NonSeekableStream() + { + // Test that GZip extraction works with non-seekable streams (like HttpBaseStream) + using var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); + var buffer = new MemoryStream(); + fileStream.CopyTo(buffer); + buffer.Position = 0; + + // Create a non-seekable wrapper around the MemoryStream + using var nonSeekableStream = new NonSeekableStream(buffer); + using var reader = SharpCompress.Readers.GZip.GZipReader.Open(nonSeekableStream); + + // Verify we can move to the first entry and read it without exceptions + Assert.True(reader.MoveToNextEntry()); + Assert.NotNull(reader.Entry); + + // Extract and verify the entry can be read + using var outputStream = new MemoryStream(); + reader.WriteEntryTo(outputStream); + + Assert.True(outputStream.Length > 0); + } + + // Helper class to simulate a non-seekable stream like HttpBaseStream + private class NonSeekableStream : Stream + { + private readonly Stream _baseStream; + + public NonSeekableStream(Stream baseStream) => _baseStream = baseStream; + + public override bool CanRead => _baseStream.CanRead; + public override bool CanSeek => false; // Simulate non-seekable stream + public override bool CanWrite => false; + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override void Flush() => _baseStream.Flush(); + + public override int Read(byte[] buffer, int offset, int count) => + _baseStream.Read(buffer, offset, count); + + 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(); + } } From 1a767105e6cd95dd7ae2e3ca2c3bb2612b05c5a5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:09:52 +0000 Subject: [PATCH 4/4] Add explanatory comment for EntryStartPosition initialization Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- src/SharpCompress/Common/GZip/GZipFilePart.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/SharpCompress/Common/GZip/GZipFilePart.cs b/src/SharpCompress/Common/GZip/GZipFilePart.cs index d3c614f9..690aad80 100644 --- a/src/SharpCompress/Common/GZip/GZipFilePart.cs +++ b/src/SharpCompress/Common/GZip/GZipFilePart.cs @@ -28,6 +28,8 @@ internal sealed class GZipFilePart : FilePart } else { + // For non-seekable streams, we can't read the trailer or track position. + // Set to 0 since the stream will be read sequentially from its current position. EntryStartPosition = 0; } }