diff --git a/src/SharpCompress/Common/Lzw/LzwFilePart.cs b/src/SharpCompress/Common/Lzw/LzwFilePart.cs index 707d16cd..087ab3aa 100644 --- a/src/SharpCompress/Common/Lzw/LzwFilePart.cs +++ b/src/SharpCompress/Common/Lzw/LzwFilePart.cs @@ -36,8 +36,15 @@ internal sealed partial class LzwFilePart : FilePart private static string? DeriveFileName(Stream stream) { + // Unwrap SharpCompressStream to get to the underlying FileStream + var unwrappedStream = stream; + if (stream is SharpCompress.IO.IStreamStack streamStack) + { + unwrappedStream = streamStack.BaseStream(); + } + // Try to derive filename from FileStream - if (stream is FileStream fileStream && !string.IsNullOrEmpty(fileStream.Name)) + if (unwrappedStream is FileStream fileStream && !string.IsNullOrEmpty(fileStream.Name)) { var fileName = Path.GetFileName(fileStream.Name); // Strip .Z extension if present 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/Lzw/LzwReaderAsyncTests.cs b/tests/SharpCompress.Test/Lzw/LzwReaderAsyncTests.cs index b9083138..6ecc6047 100644 --- a/tests/SharpCompress.Test/Lzw/LzwReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/Lzw/LzwReaderAsyncTests.cs @@ -1,4 +1,7 @@ +using System.IO; using SharpCompress.Common; +using SharpCompress.Readers; +using SharpCompress.Readers.Lzw; using Xunit; namespace SharpCompress.Test.Lzw; @@ -12,4 +15,29 @@ public class LzwReaderAsyncTests : ReaderTests { await ReadAsync("Tar.tar.Z", CompressionType.Lzw); } + + [Fact] + public async System.Threading.Tasks.Task Lzw_Reader_Plain_Z_File_Async() + { + // Test async reading of a plain .Z file (not tar-wrapped) using LzwReader directly + using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "large_test.txt.Z")); + using var reader = LzwReader.OpenReader(stream); + + Assert.Equal(ArchiveType.Lzw, reader.ArchiveType); + Assert.True(reader.MoveToNextEntry()); + + var entry = reader.Entry; + Assert.NotNull(entry); + Assert.Equal(CompressionType.Lzw, entry.CompressionType); + + // When opened as FileStream, key should be derived from filename + Assert.Equal("large_test.txt", entry.Key); + + // Decompress asynchronously + using var entryStream = reader.OpenEntryStream(); + using var ms = new MemoryStream(); + await entryStream.CopyToAsync(ms); + + Assert.Equal(22300, ms.Length); + } } diff --git a/tests/SharpCompress.Test/Lzw/LzwReaderTests.cs b/tests/SharpCompress.Test/Lzw/LzwReaderTests.cs index 4c7f2d6d..f75fd666 100644 --- a/tests/SharpCompress.Test/Lzw/LzwReaderTests.cs +++ b/tests/SharpCompress.Test/Lzw/LzwReaderTests.cs @@ -46,4 +46,46 @@ public class LzwReaderTests : ReaderTests Assert.NotNull(reader.Entry); Assert.Equal(CompressionType.Lzw, reader.Entry.CompressionType); } + + [Fact] + public void Lzw_Reader_Plain_Z_File() + { + // Test with a plain .Z file (not tar-wrapped) using LzwReader directly + using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "large_test.txt.Z")); + using var reader = LzwReader.OpenReader(stream); + + Assert.True(reader.MoveToNextEntry()); + var entry = reader.Entry; + Assert.NotNull(entry); + Assert.Equal(CompressionType.Lzw, entry.CompressionType); + + // Entry key should be "large_test.txt" (stripped .Z extension) when opened via FileStream + Assert.Equal("large_test.txt", entry.Key); + + // Decompress and verify content + using var entryStream = reader.OpenEntryStream(); + using var ms = new MemoryStream(); + entryStream.CopyTo(ms); + var decompressed = System.Text.Encoding.UTF8.GetString(ms.ToArray()); + + Assert.Equal(22300, ms.Length); + Assert.Contains("This is a test file for LZW compression testing", decompressed); + } + + [Fact] + public void Lzw_Reader_Factory_Detects_Plain_Z_File() + { + // Test that ReaderFactory correctly identifies a plain .Z file (not tar-wrapped) + using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "large_test.txt.Z")); + using var reader = ReaderFactory.OpenReader(stream); + + // Should detect as Lzw archive (not Tar) + Assert.Equal(ArchiveType.Lzw, reader.ArchiveType); + Assert.True(reader.MoveToNextEntry()); + Assert.NotNull(reader.Entry); + Assert.Equal(CompressionType.Lzw, reader.Entry.CompressionType); + + // When opened via ReaderFactory with a non-FileStream, key defaults to "data" + Assert.NotNull(reader.Entry.Key); + } } diff --git a/tests/TestArchives/Archives/large_test.txt.Z b/tests/TestArchives/Archives/large_test.txt.Z new file mode 100644 index 00000000..64ae2af1 Binary files /dev/null and b/tests/TestArchives/Archives/large_test.txt.Z differ