diff --git a/src/SharpCompress/Writers/SevenZip/SevenZipWriter.Async.cs b/src/SharpCompress/Writers/SevenZip/SevenZipWriter.Async.cs index 9b24371d..9cb57f6a 100644 --- a/src/SharpCompress/Writers/SevenZip/SevenZipWriter.Async.cs +++ b/src/SharpCompress/Writers/SevenZip/SevenZipWriter.Async.cs @@ -43,6 +43,8 @@ public partial class SevenZipWriter private async ValueTask FinalizeArchiveAsync() { + // Reserve the signature even when no file data was written. + await EnsurePlaceholderWrittenAsync(CancellationToken.None).ConfigureAwait(false); var output = OutputStream.NotNull(); // Current position = end of packed data streams diff --git a/src/SharpCompress/Writers/SevenZip/SevenZipWriter.cs b/src/SharpCompress/Writers/SevenZip/SevenZipWriter.cs index f29bc20d..cfc96cd1 100644 --- a/src/SharpCompress/Writers/SevenZip/SevenZipWriter.cs +++ b/src/SharpCompress/Writers/SevenZip/SevenZipWriter.cs @@ -193,6 +193,8 @@ public partial class SevenZipWriter : AbstractWriter private void FinalizeArchive() { + // Directory-only and empty archives never pass through Write(). + EnsurePlaceholderWritten(); var output = OutputStream.NotNull(); // Current position = end of packed data streams diff --git a/tests/SharpCompress.Test/SevenZip/SevenZipWriterStreamlessTests.cs b/tests/SharpCompress.Test/SevenZip/SevenZipWriterStreamlessTests.cs new file mode 100644 index 00000000..858bcca6 --- /dev/null +++ b/tests/SharpCompress.Test/SevenZip/SevenZipWriterStreamlessTests.cs @@ -0,0 +1,89 @@ +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Archives.SevenZip; +using SharpCompress.Writers.SevenZip; +using Xunit; + +namespace SharpCompress.Test.SevenZip; + +public class SevenZipWriterStreamlessTests +{ + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task SevenZipWriter_Streamless_ClosesOwnedStream(bool async) + { + using var output = new MemoryStream(); + var writer = new SevenZipWriter( + output, + new SevenZipWriterOptions { LeaveStreamOpen = false } + ); + writer.WriteDirectory("dir", null); + if (async) + { + await writer.DisposeAsync(); + } + else + { + writer.Dispose(); + } + Assert.False(output.CanRead); + using var input = new MemoryStream(output.ToArray()); + using var archive = SevenZipArchive.OpenArchive(input); + Assert.True(archive.Entries.Single().IsDirectory); + } + + [Theory] + [InlineData(false, false, 0)] + [InlineData(false, true, 0)] + [InlineData(true, false, 0)] + [InlineData(true, true, 0)] + [InlineData(false, false, 2)] + [InlineData(false, true, 2)] + [InlineData(true, false, 2)] + [InlineData(true, true, 2)] + public async Task SevenZipWriter_Streamless_RoundTrip( + bool async, + bool compressHeader, + int directories + ) + { + using var output = new MemoryStream(); + var writer = new SevenZipWriter( + output, + new SevenZipWriterOptions { CompressHeader = compressHeader } + ); + for (var i = 0; i < directories; i++) + { + if (async) + { + await writer.WriteDirectoryAsync($"dir{i}", null, CancellationToken.None); + } + else + { + writer.WriteDirectory($"dir{i}", null); + } + } + if (async) + { + await writer.DisposeAsync(); + } + else + { + writer.Dispose(); + } + + Assert.True(output.CanRead); + var bytes = output.ToArray(); + var offset = System.BitConverter.ToUInt64(bytes, 12); + var size = System.BitConverter.ToUInt64(bytes, 20); + Assert.Equal((ulong)bytes.Length, 32 + offset + size); + output.Position = 0; + using var archive = SevenZipArchive.OpenArchive(output); + var entries = archive.Entries.ToList(); + Assert.Equal(directories, entries.Count); + Assert.All(entries, entry => Assert.True(entry.IsDirectory)); + } +}