diff --git a/docs/API.md b/docs/API.md index 79745918..db807f55 100644 --- a/docs/API.md +++ b/docs/API.md @@ -132,6 +132,15 @@ using (var archive = ZipArchive.OpenArchive("file.zip")) var firstEntry = archive.Entries.First(); firstEntry.WriteToFile(@"C:\output\file.txt"); + // Extract single entry to a stream with extraction options + using (var outputStream = File.Create(@"C:\output\file.txt")) + { + firstEntry.WriteTo( + outputStream, + new ExtractionOptions { CheckCrc = false } + ); + } + // Get entry stream using (var stream = entry.OpenEntryStream()) { @@ -154,6 +163,15 @@ await using (var asyncArchive = await ZipArchive.OpenAsyncArchive("file.zip")) { await foreach (var entry in asyncArchive.EntriesAsync) { + await using (var outputStream = File.Create(@"C:\output\" + entry.Key)) + { + await entry.WriteToAsync( + outputStream, + new ExtractionOptions { CheckCrc = false }, + cancellationToken: cancellationToken + ); + } + using (var stream = await entry.OpenEntryStreamAsync(cancellationToken)) { // ... diff --git a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs index a9c3a054..502a419e 100644 --- a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs +++ b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs @@ -18,7 +18,19 @@ public static class IArchiveEntryExtensions /// The stream to write the entry content to. /// Optional progress reporter for tracking extraction progress. public void WriteTo(Stream streamToWriteTo, IProgress? progress = null) => - archiveEntry.WriteTo(streamToWriteTo, null, progress: progress); + archiveEntry.WriteTo(streamToWriteTo, bufferSize: null, progress: progress); + + /// + /// Extract entry to the specified stream. + /// + /// The stream to write the entry content to. + /// Options for configuring extraction behavior. + /// Optional progress reporter for tracking extraction progress. + public void WriteTo( + Stream streamToWriteTo, + ExtractionOptions options, + IProgress? progress = null + ) => archiveEntry.WriteTo(streamToWriteTo, options.BufferSize, options, progress); private void WriteTo( Stream streamToWriteTo, @@ -50,8 +62,7 @@ public static class IArchiveEntryExtensions Stream streamToWriteTo, IProgress? progress = null, CancellationToken cancellationToken = default - ) - { + ) => await archiveEntry .WriteToAsync( streamToWriteTo, @@ -60,7 +71,29 @@ public static class IArchiveEntryExtensions cancellationToken: cancellationToken ) .ConfigureAwait(false); - } + + /// + /// Extract entry to the specified stream asynchronously. + /// + /// The stream to write the entry content to. + /// Options for configuring extraction behavior. + /// Optional progress reporter for tracking extraction progress. + /// Cancellation token. + public async ValueTask WriteToAsync( + Stream streamToWriteTo, + ExtractionOptions options, + IProgress? progress = null, + CancellationToken cancellationToken = default + ) => + await archiveEntry + .WriteToAsync( + streamToWriteTo, + options.BufferSize, + options, + progress, + cancellationToken + ) + .ConfigureAwait(false); private async ValueTask WriteToAsync( Stream streamToWriteTo, diff --git a/src/SharpCompress/Common/ChecksumValidationStream.cs b/src/SharpCompress/Common/ChecksumValidationStream.cs index e3c4e657..1b14cc9d 100644 --- a/src/SharpCompress/Common/ChecksumValidationStream.cs +++ b/src/SharpCompress/Common/ChecksumValidationStream.cs @@ -56,20 +56,7 @@ internal sealed class ChecksumValidationStream : Stream } #endif - public override int ReadByte() - { - var value = _stream.ReadByte(); - if (value == -1) - { - Validate(); - } - else - { - UpdateChecksum([(byte)value]); - } - - return value; - } + public override int ReadByte() => throw new NotSupportedException(); public override async Task ReadAsync( byte[] buffer, diff --git a/tests/SharpCompress.Test/Zip/ZipCrcExtractionTests.cs b/tests/SharpCompress.Test/Zip/ZipCrcExtractionTests.cs index 14a55fc6..3020e70b 100644 --- a/tests/SharpCompress.Test/Zip/ZipCrcExtractionTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipCrcExtractionTests.cs @@ -45,6 +45,34 @@ public class ZipCrcExtractionTests : ArchiveTests Assert.Equal(EntryData, File.ReadAllBytes(destination)); } + [Fact] + public void Zip_Archive_WriteTo_Throws_On_Crc_Mismatch_When_Enabled() + { + using var zipStream = CreateZipWithInvalidCrc(useDataDescriptor: false); + using var archive = ZipArchive.OpenArchive(zipStream); + var entry = archive.Entries.Single(e => !e.IsDirectory); + using var destination = new MemoryStream(); + + var exception = Assert.Throws(() => + entry.WriteTo(destination, new ExtractionOptions { CheckCrc = true }) + ); + + Assert.Contains(EntryName, exception.Message); + } + + [Fact] + public void Zip_Archive_WriteTo_Skips_Crc_Mismatch_When_Disabled() + { + using var zipStream = CreateZipWithInvalidCrc(useDataDescriptor: false); + using var archive = ZipArchive.OpenArchive(zipStream); + var entry = archive.Entries.Single(e => !e.IsDirectory); + using var destination = new MemoryStream(); + + entry.WriteTo(destination, new ExtractionOptions { CheckCrc = false }); + + Assert.Equal(EntryData, destination.ToArray()); + } + [Fact] public void Zip_Reader_WriteEntryToFile_Throws_On_Crc_Mismatch() { @@ -88,6 +116,34 @@ public class ZipCrcExtractionTests : ArchiveTests Assert.Contains(EntryName, exception.Message); } + [Fact] + public async Task Zip_Archive_WriteToAsync_Throws_On_Crc_Mismatch_When_Enabled() + { + using var zipStream = CreateZipWithInvalidCrc(useDataDescriptor: false); + using var archive = ZipArchive.OpenArchive(zipStream); + var entry = archive.Entries.Single(e => !e.IsDirectory); + await using var destination = new MemoryStream(); + + var exception = await Assert.ThrowsAsync(async () => + await entry.WriteToAsync(destination, new ExtractionOptions { CheckCrc = true }) + ); + + Assert.Contains(EntryName, exception.Message); + } + + [Fact] + public async Task Zip_Archive_WriteToAsync_Skips_Crc_Mismatch_When_Disabled() + { + using var zipStream = CreateZipWithInvalidCrc(useDataDescriptor: false); + using var archive = ZipArchive.OpenArchive(zipStream); + var entry = archive.Entries.Single(e => !e.IsDirectory); + await using var destination = new MemoryStream(); + + await entry.WriteToAsync(destination, new ExtractionOptions { CheckCrc = false }); + + Assert.Equal(EntryData, destination.ToArray()); + } + [Fact] public async Task Zip_Reader_WriteEntryToFileAsync_Throws_On_Crc_Mismatch() {