diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs index de0adc98b..710e490c1 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs @@ -164,6 +164,12 @@ protected override EntryStream GetEntryStream() var folder = entry.FilePart.Folder; + // If folder is null (empty stream entry), return empty stream + if (folder is null) + { + return CreateEntryStream(Stream.Null); + } + // Check if we're starting a new folder - dispose old folder stream if needed if (folder != _currentFolder) { diff --git a/src/SharpCompress/IO/SharpCompressStream.cs b/src/SharpCompress/IO/SharpCompressStream.cs index b47b70313..a7c09072a 100644 --- a/src/SharpCompress/IO/SharpCompressStream.cs +++ b/src/SharpCompress/IO/SharpCompressStream.cs @@ -206,8 +206,22 @@ public override void Flush() throw new NotSupportedException(); } - public override long Length => - _isPassthrough ? stream.Length : throw new NotSupportedException(); + public override long Length + { + get + { + if (_isPassthrough) + { + return stream.Length; + } + + if (_ringBuffer is not null) + { + return _ringBuffer.Length; + } + throw new NotSupportedException(); + } + } public override long Position { diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index 29e7a1bd5..27e9e4968 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/SevenZip/SevenZipArchiveTests.cs b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs index ea9316e57..0b77c0400 100644 --- a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs +++ b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs @@ -4,6 +4,7 @@ using SharpCompress.Archives; using SharpCompress.Archives.SevenZip; using SharpCompress.Common; +using SharpCompress.Common.SevenZip; using SharpCompress.Factories; using SharpCompress.Readers; using Xunit; @@ -344,4 +345,53 @@ public void SevenZipArchive_Solid_VerifyStreamReuse() // The critical check: within a single folder, the stream should NEVER be recreated Assert.Equal(0, streamRecreationsWithinFolder); // Folder stream should remain the same for all entries in the same folder } + + [Fact] + public void SevenZipArchive_EmptyStream_WriteToDirectory() + { + // This test specifically verifies that archives with empty-stream entries + // (files with size 0 and no compressed data) can be extracted without throwing + // NullReferenceException. This was previously failing because the folder was null + // for empty-stream entries. + var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.EmptyStream.7z"); + using var archive = SevenZipArchive.OpenArchive(testArchive); + + var emptyStreamFileCount = 0; + foreach (var entry in archive.Entries) + { + if (!entry.IsDirectory) + { + // Verify this is actually an empty-stream entry (HasStream == false) + var sevenZipEntry = entry as SevenZipEntry; + if (sevenZipEntry?.FilePart.Header.HasStream == false) + { + emptyStreamFileCount++; + } + + // This should not throw NullReferenceException + entry.WriteToDirectory(SCRATCH_FILES_PATH); + } + } + + // Ensure we actually tested empty-stream entries + Assert.True( + emptyStreamFileCount > 0, + "Test archive should contain at least one empty-stream entry" + ); + + // Verify that empty files were created + var extractedFiles = Directory.GetFiles( + SCRATCH_FILES_PATH, + "*", + SearchOption.AllDirectories + ); + Assert.NotEmpty(extractedFiles); + + // All extracted files should be empty (0 bytes) + foreach (var file in extractedFiles) + { + var fileInfo = new FileInfo(file); + Assert.Equal(0, fileInfo.Length); + } + } } diff --git a/tests/TestArchives/Archives/7Zip.EmptyStream.7z b/tests/TestArchives/Archives/7Zip.EmptyStream.7z new file mode 100644 index 000000000..c2d9bfa43 Binary files /dev/null and b/tests/TestArchives/Archives/7Zip.EmptyStream.7z differ