Fix XZBlock exception when accessing Position on non-seekable streams

XZBlock accesses stream.Position in its constructor and SkipPadding(),
which throws NotSupportedException for non-seekable streams (e.g., HTTP).

Fix: XZStream now auto-wraps non-seekable streams with
SharpCompressStream.Create() to provide position tracking via ring buffer.

Add tests for raw non-seekable streams (sync and async).

Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-23 21:11:00 +00:00
parent 2d0bc1633c
commit e5ef31bdaf
3 changed files with 42 additions and 1 deletions

View File

@@ -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,7 +13,7 @@ namespace SharpCompress.Compressors.Xz;
public sealed partial class XZStream : XZReadOnlyStream
{
public XZStream(Stream baseStream)
: base(baseStream) { }
: base(baseStream.CanSeek ? baseStream : SharpCompressStream.Create(baseStream)) { }
protected override void Dispose(bool disposing)
{

View File

@@ -55,4 +55,24 @@ public class XzStreamAsyncTests : XzTestsBase
var uncompressed = await sr.ReadToEndAsync().ConfigureAwait(false);
Assert.Equal(OriginalEmpty, uncompressed);
}
[Fact]
public async ValueTask CanReadRawNonSeekableStreamAsync()
{
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 CanReadRawNonSeekableEmptyStreamAsync()
{
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);
}
}

View File

@@ -54,4 +54,24 @@ public class XzStreamTests : XzTestsBase
var uncompressed = sr.ReadToEnd();
Assert.Equal(OriginalEmpty, uncompressed);
}
[Fact]
public void CanReadRawNonSeekableStream()
{
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 CanReadRawNonSeekableEmptyStream()
{
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);
}
}