From ee98b5565b0df3d91ed972bdb02358388f64d685 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 15:49:14 +0000 Subject: [PATCH] Fix XZStream to support non-seekable streams Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- src/SharpCompress/Compressors/Xz/XZStream.cs | 17 +++++++++++++-- src/SharpCompress/packages.lock.json | 12 +++++------ .../Xz/XZStreamAsyncTests.cs | 20 ++++++++++++++++++ tests/SharpCompress.Test/Xz/XZStreamTests.cs | 21 +++++++++++++++++++ 4 files changed, 62 insertions(+), 8 deletions(-) diff --git a/src/SharpCompress/Compressors/Xz/XZStream.cs b/src/SharpCompress/Compressors/Xz/XZStream.cs index 489b449a..dff04464 100644 --- a/src/SharpCompress/Compressors/Xz/XZStream.cs +++ b/src/SharpCompress/Compressors/Xz/XZStream.cs @@ -5,6 +5,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using SharpCompress.Common; +using SharpCompress.IO; namespace SharpCompress.Compressors.Xz; @@ -12,9 +13,21 @@ namespace SharpCompress.Compressors.Xz; public sealed partial class XZStream : XZReadOnlyStream { public XZStream(Stream baseStream) - : base(baseStream) + : base(WrapIfNeeded(baseStream)) { - _baseStream = baseStream; + _baseStream = BaseStream; + } + + private static Stream WrapIfNeeded(Stream stream) + { + // XZ format requires position tracking for padding calculations. + // For non-seekable streams, wrap with SharpCompressStream which provides + // limited backward seeking via a ring buffer. + if (!stream.CanSeek) + { + return SharpCompressStream.Create(stream); + } + return stream; } protected override void Dispose(bool disposing) diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index 29e7a1bd..27e9e496 100644 --- a/src/SharpCompress/packages.lock.json +++ b/src/SharpCompress/packages.lock.json @@ -216,9 +216,9 @@ "net10.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "kICGrGYEzCNI3wPzfEXcwNHgTvlvVn9yJDhSdRK+oZQy4jvYH529u7O0xf5ocQKzOMjfS07+3z9PKRIjrFMJDA==" + "requested": "[10.0.2, )", + "resolved": "10.0.2", + "contentHash": "sXdDtMf2qcnbygw9OdE535c2lxSxrZP8gO4UhDJ0xiJbl1wIqXS1OTcTDFTIJPOFd6Mhcm8gPEthqWGUxBsTqw==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", @@ -264,9 +264,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.22, )", - "resolved": "8.0.22", - "contentHash": "MhcMithKEiyyNkD2ZfbDZPmcOdi0GheGfg8saEIIEfD/fol3iHmcV8TsZkD4ZYz5gdUuoX4YtlVySUU7Sxl9SQ==" + "requested": "[8.0.23, )", + "resolved": "8.0.23", + "contentHash": "GqHiB1HbbODWPbY/lc5xLQH8siEEhNA0ptpJCC6X6adtAYNEzu5ZlqV3YHA3Gh7fuEwgA8XqVwMtH2KNtuQM1Q==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", diff --git a/tests/SharpCompress.Test/Xz/XZStreamAsyncTests.cs b/tests/SharpCompress.Test/Xz/XZStreamAsyncTests.cs index a2276602..96074f64 100644 --- a/tests/SharpCompress.Test/Xz/XZStreamAsyncTests.cs +++ b/tests/SharpCompress.Test/Xz/XZStreamAsyncTests.cs @@ -34,4 +34,24 @@ public class XzStreamAsyncTests : XzTestsBase var uncompressed = await sr.ReadToEndAsync().ConfigureAwait(false); Assert.Equal(OriginalIndexed, uncompressed); } + + [Fact] + public async ValueTask CanReadNonSeekableStreamAsync() + { + var nonSeekable = new ForwardOnlyStream(new MemoryStream(Compressed)); + var xz = new XZStream(nonSeekable); + using var sr = new StreamReader(new AsyncOnlyStream(xz)); + var uncompressed = await sr.ReadToEndAsync().ConfigureAwait(false); + Assert.Equal(Original, uncompressed); + } + + [Fact] + public async ValueTask CanReadNonSeekableEmptyStreamAsync() + { + var nonSeekable = new ForwardOnlyStream(new MemoryStream(CompressedEmpty)); + var xz = new XZStream(nonSeekable); + using var sr = new StreamReader(new AsyncOnlyStream(xz)); + var uncompressed = await sr.ReadToEndAsync().ConfigureAwait(false); + Assert.Equal(OriginalEmpty, uncompressed); + } } diff --git a/tests/SharpCompress.Test/Xz/XZStreamTests.cs b/tests/SharpCompress.Test/Xz/XZStreamTests.cs index 02c5020c..8b175a8c 100644 --- a/tests/SharpCompress.Test/Xz/XZStreamTests.cs +++ b/tests/SharpCompress.Test/Xz/XZStreamTests.cs @@ -1,5 +1,6 @@ using System.IO; using SharpCompress.Compressors.Xz; +using SharpCompress.Test.Mocks; using Xunit; namespace SharpCompress.Test.Xz; @@ -32,4 +33,24 @@ public class XzStreamTests : XzTestsBase var uncompressed = sr.ReadToEnd(); Assert.Equal(OriginalIndexed, uncompressed); } + + [Fact] + public void CanReadNonSeekableStream() + { + var nonSeekable = new ForwardOnlyStream(new MemoryStream(Compressed)); + var xz = new XZStream(nonSeekable); + using var sr = new StreamReader(xz); + var uncompressed = sr.ReadToEnd(); + Assert.Equal(Original, uncompressed); + } + + [Fact] + public void CanReadNonSeekableEmptyStream() + { + var nonSeekable = new ForwardOnlyStream(new MemoryStream(CompressedEmpty)); + var xz = new XZStream(nonSeekable); + using var sr = new StreamReader(xz); + var uncompressed = sr.ReadToEnd(); + Assert.Equal(OriginalEmpty, uncompressed); + } }