From e8ee2264a9ef05c77a67439063c63b2692fce3bb Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Mon, 3 Aug 2026 15:18:44 +0100 Subject: [PATCH 1/3] converted a lot of zip items to sync method generator --- docs/SYNC_METHOD_GENERATION.md | 41 ++- .../Common/Zip/Headers/ZipFileEntry.Async.cs | 3 +- .../Common/Zip/Headers/ZipFileEntry.cs | 12 - .../PkwareTraditionalCryptoStream.Async.cs | 2 + .../Zip/PkwareTraditionalCryptoStream.cs | 43 --- .../Common/Zip/SeekableZipFilePart.Async.cs | 2 + .../Common/Zip/SeekableZipFilePart.cs | 14 - .../Common/Zip/StreamingZipFilePart.Async.cs | 8 +- .../Common/Zip/StreamingZipFilePart.cs | 24 +- .../Common/Zip/WinzipAesCryptoStream.Async.cs | 1 + .../Common/Zip/WinzipAesCryptoStream.cs | 19 -- .../Common/Zip/ZipFilePart.Async.cs | 3 + src/SharpCompress/Common/Zip/ZipFilePart.cs | 127 -------- .../Deflate/DeflateStream.Async.cs | 39 +++ .../Compressors/Deflate/DeflateStream.cs | 88 ------ .../Compressors/Deflate/ZlibBaseStream.cs | 290 +----------------- .../Compressors/Deflate/ZlibStream.Async.cs | 27 ++ .../Compressors/Deflate/ZlibStream.cs | 84 ----- .../Deflate64/Deflate64Stream.Async.cs | 1 + .../Compressors/Deflate64/Deflate64Stream.cs | 47 --- .../Providers/CompressionProviderRegistry.cs | 113 +------ .../Writers/Zip/ZipWriter.Async.cs | 21 +- src/SharpCompress/Writers/Zip/ZipWriter.cs | 67 ---- .../Writers/Zip/ZipWritingStream.cs | 150 +-------- src/SharpCompress/packages.lock.json | 2 +- .../OptionsUsabilityTests.cs | 15 + 26 files changed, 165 insertions(+), 1078 deletions(-) diff --git a/docs/SYNC_METHOD_GENERATION.md b/docs/SYNC_METHOD_GENERATION.md index aac905ef..9bf9ca1b 100644 --- a/docs/SYNC_METHOD_GENERATION.md +++ b/docs/SYNC_METHOD_GENERATION.md @@ -25,7 +25,13 @@ the verification workflow, and the record of what is blocked and why. | 0 | `Archives/IArchiveEntryExtensions` (5 methods) | done — `c303856c` | | 1 | `Compressors/Filters/Filter` + 6 XZ branch filters + `Lzma2Filter` (9 methods, 147 lines) | done — verified identical on net48 + net10.0 | | 2 | `IO/` leaf stream shims + `Common/EntryStream` (17 methods, 190 lines) | done — verified identical on net48 + net10.0 | -| 3–12 | below | to do | +| 3–5 | below | to do | +| 6 | ZIP parts & header factories | 6a done; header factories remain | +| 7 | Concrete writers | ZIP done; Tar, SevenZip, and remaining writers to do | +| 8 | 7-Zip | to do | +| 9 | Streaming compressors | Deflate, Zlib, and Deflate64 done; remaining compressors to do | +| 10–11 | below | to do | +| 12 | Providers | registry done; provider implementations remain | | Rar reader unification | below | to do, needs its own design review | --- @@ -207,16 +213,17 @@ the async one `new byte[4]`, and the method is 4 lines — `SYNC_ONLY` would be `Arj/HuffmanTree`, `Arj/BitReader`, `Arj/LhaStream` (11 pairs, all ≥0.99), `Arj/LHDecoderStream`, `Squeezed/BitReader`. Best win per line of review — pure parsing, all clean. -### 6 — Zip parts & header factories · ~250 lines · med risk +### 6 — Zip parts & header factories · ~250 lines · med risk · **in progress** 6a: `ZipFilePart` (incl. `GetCryptoStreamAsync`), `SeekableZipFilePart`, `StreamingZipFilePart`, -`GZipFilePart`, `Zip/Headers/ZipFileEntry`, `PkwareTraditionalCryptoStream`, `WinzipAesCryptoStream`. +`Zip/Headers/ZipFileEntry`, `PkwareTraditionalCryptoStream`, and `WinzipAesCryptoStream` — **done**. +`GZipFilePart` was completed in the prior GZip batch. 6b: `ZipHeaderFactory` (`LoadHeaderAsync` identical), `SeekableZipHeaderFactory`. -### 7 — Concrete writers · ~250 lines · med risk +### 7 — Concrete writers · ~250 lines · med risk · **in progress** -`ZipWriter`, `ZipWritingStream` (`GetWriteStreamAsync` is a 131-line duplicate), `TarWriter`, -`SevenZipWriter`, `GZipWriter`. +`ZipWriter` and `ZipWritingStream` (`GetWriteStreamAsync` is a 131-line duplicate) — **done**. +`TarWriter` and `SevenZipWriter` remain. `GZipWriter` was completed in the prior GZip batch. This is the **reachable Writers win**: `AbstractWriter` already implements *both* `IWriter` and `IAsyncWriter` (`AbstractWriter.cs:11`), so `ZipWriter.Async.cs:52 @@ -233,14 +240,15 @@ attempt `IWriterExtensions` (see Blocked). Clean: these use the sync `DataReader`, not an async-only reader type. Keep the file split (`ArchiveReader.cs` is 1,377 lines). -### 9 — Streaming compressors · ~300 lines · med-high risk +### 9 — Streaming compressors · ~300 lines · med-high risk · **in progress** -`Deflate64Stream`, `Deflate/ZlibBaseStream` (`ReadAsync` 192 lines, `WriteAsync`, `FlushAsync`), -`DeflateStream`, `GZipStream`, `ZlibStream`, `ZStandard/*`, `Reduce`, `Explode`, `Lzw/LzwStream` -(`ReadAsync` 208 lines), `RLE90`, `Shrink`, `ArcLzw`. +`Deflate64Stream`, `Deflate/ZlibBaseStream` (`ReadAsync`, `WriteAsync`, `FlushAsync`), +`DeflateStream`, and `ZlibStream` — **done**. `GZipStream` was completed in the prior GZip batch. +`ZStandard/*`, `Reduce`, `Explode`, `Lzw/LzwStream` (`ReadAsync` 208 lines), `RLE90`, `Shrink`, +and `ArcLzw` remain. -- `ZlibBaseStream` needs `partial` added. -- `[SkipSyncVersion]` on all four Deflate-family `DisposeAsync`. +- `ZlibBaseStream` is partial. Deflate-family `DisposeAsync`, byte methods, unmatched memory + overloads, GZip-header parsing, and finalization remain hand-written. - `RunLength90Stream` is the canonical `SYNC_ONLY` case (see below). - `GZipCompressionProvider.cs:41` returns `new ValueTask(...)` from a non-`async` method — not a Zomp rewrite; make it `async` first. (Non-`async` methods that just *return* an `XAsync(...)` @@ -264,11 +272,12 @@ generated `ReadByte()`/`WriteByte()` are emitted without `override` and would hi Fully clean — these use raw `Stream`, **not** the async reader types. The least readable diffs in the repo, so do them last of the mechanical work. -### 12 — Providers · ~80 lines · low risk +### 12 — Providers · ~80 lines · low risk · **in progress** -`CompressionProviderRegistry` (4 identical pairs), `Default/*` providers (`XStream.CreateAsync(...)` -→ `Create(...)` maps cleanly), `CompressionProviderBase`, -`ContextRequiredDecompressionProviderBase`. Blocked on the `GZipCompressionProvider` rewrite above. +`CompressionProviderRegistry` (4 identical pairs) — **done**. `Default/*` providers +(`XStream.CreateAsync(...)` → `Create(...)` maps cleanly), `CompressionProviderBase`, and +`ContextRequiredDecompressionProviderBase` remain. The `GZipCompressionProvider` rewrite was +completed in the prior GZip batch. --- diff --git a/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.Async.cs b/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.Async.cs index f541fc8d..e43af8c7 100644 --- a/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.Async.cs +++ b/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.Async.cs @@ -7,6 +7,7 @@ namespace SharpCompress.Common.Zip.Headers; internal abstract partial class ZipFileEntry { + [Zomp.SyncMethodGenerator.CreateSyncVersion] internal async ValueTask ComposeEncryptionDataAsync( Stream archiveStream, CancellationToken cancellationToken = default @@ -15,7 +16,7 @@ internal abstract partial class ZipFileEntry ThrowHelper.ThrowIfNull(archiveStream); var buffer = new byte[12]; - await archiveStream.ReadFullyAsync(buffer, 0, 12, cancellationToken).ConfigureAwait(false); + await archiveStream.ReadFullyAsync(buffer, cancellationToken).ConfigureAwait(false); var encryptionData = PkwareTraditionalEncryptionData.ForRead(Password!, this, buffer); diff --git a/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs b/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs index e7f0b042..68fa65d3 100644 --- a/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs +++ b/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs @@ -42,18 +42,6 @@ internal abstract partial class ZipFileEntry(ZipHeaderType type, IArchiveEncodin public string? Password { get; set; } - internal PkwareTraditionalEncryptionData ComposeEncryptionData(Stream archiveStream) - { - ThrowHelper.ThrowIfNull(archiveStream); - - var buffer = new byte[12]; - archiveStream.ReadFully(buffer); - - var encryptionData = PkwareTraditionalEncryptionData.ForRead(Password!, this, buffer); - - return encryptionData; - } - internal WinzipAesEncryptionData? WinzipAesEncryptionData { get; set; } /// diff --git a/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.Async.cs b/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.Async.cs index 32a0a7bf..63abcf4d 100644 --- a/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.Async.cs +++ b/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.Async.cs @@ -8,6 +8,7 @@ namespace SharpCompress.Common.Zip; internal partial class PkwareTraditionalCryptoStream { + [Zomp.SyncMethodGenerator.CreateSyncVersion] public override async Task ReadAsync( byte[] buffer, int offset, @@ -59,6 +60,7 @@ internal partial class PkwareTraditionalCryptoStream } #endif + [Zomp.SyncMethodGenerator.CreateSyncVersion] public override async Task WriteAsync( byte[] buffer, int offset, diff --git a/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.cs b/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.cs index 2efd036e..9545d8de 100644 --- a/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.cs +++ b/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.cs @@ -41,49 +41,6 @@ internal partial class PkwareTraditionalCryptoStream : Stream set => throw new NotSupportedException(); } - public override int Read(byte[] buffer, int offset, int count) - { - if (_mode == CryptoMode.Encrypt) - { - throw new NotSupportedException("This stream does not encrypt via Read()"); - } - - ThrowHelper.ThrowIfNull(buffer); - - var temp = new byte[count]; - var readBytes = _stream.Read(temp, 0, count); - var decrypted = _encryptor.Decrypt(temp, readBytes); - Buffer.BlockCopy(decrypted, 0, buffer, offset, readBytes); - return readBytes; - } - - public override void Write(byte[] buffer, int offset, int count) - { - if (_mode == CryptoMode.Decrypt) - { - throw new NotSupportedException("This stream does not Decrypt via Write()"); - } - - if (count == 0) - { - return; - } - - byte[] plaintext; - if (offset != 0) - { - plaintext = new byte[count]; - Buffer.BlockCopy(buffer, offset, plaintext, 0, count); - } - else - { - plaintext = buffer; - } - - var encrypted = _encryptor.Encrypt(plaintext, count); - _stream.Write(encrypted, 0, encrypted.Length); - } - public override void Flush() { } public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); diff --git a/src/SharpCompress/Common/Zip/SeekableZipFilePart.Async.cs b/src/SharpCompress/Common/Zip/SeekableZipFilePart.Async.cs index 8fff8436..da9ea359 100644 --- a/src/SharpCompress/Common/Zip/SeekableZipFilePart.Async.cs +++ b/src/SharpCompress/Common/Zip/SeekableZipFilePart.Async.cs @@ -7,6 +7,7 @@ namespace SharpCompress.Common.Zip; internal partial class SeekableZipFilePart { + [Zomp.SyncMethodGenerator.CreateSyncVersion] internal override async ValueTask GetCompressedStreamAsync( CancellationToken cancellationToken = default ) @@ -19,6 +20,7 @@ internal partial class SeekableZipFilePart return await base.GetCompressedStreamAsync(cancellationToken).ConfigureAwait(false); } + [Zomp.SyncMethodGenerator.CreateSyncVersion] private async ValueTask LoadLocalHeaderAsync(CancellationToken cancellationToken = default) => Header = await _headerFactory .GetLocalHeaderAsync(BaseStream, (DirectoryEntryHeader)Header) diff --git a/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs b/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs index f9c59dfd..bed94da3 100644 --- a/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs @@ -1,6 +1,5 @@ using System.IO; using SharpCompress.Common.Zip.Headers; -using SharpCompress.Compressors; using SharpCompress.Providers; namespace SharpCompress.Common.Zip; @@ -18,19 +17,6 @@ internal partial class SeekableZipFilePart : ZipFilePart ) : base(header, stream, compressionProviders) => _headerFactory = headerFactory; - internal override Stream GetCompressedStream() - { - if (!_isLocalHeaderLoaded) - { - LoadLocalHeader(); - _isLocalHeaderLoaded = true; - } - return base.GetCompressedStream(); - } - - private void LoadLocalHeader() => - Header = _headerFactory.GetLocalHeader(BaseStream, (DirectoryEntryHeader)Header); - protected override Stream CreateBaseStream() { BaseStream.Position = Header.DataStartPosition.NotNull(); diff --git a/src/SharpCompress/Common/Zip/StreamingZipFilePart.Async.cs b/src/SharpCompress/Common/Zip/StreamingZipFilePart.Async.cs index dbd62841..fa372826 100644 --- a/src/SharpCompress/Common/Zip/StreamingZipFilePart.Async.cs +++ b/src/SharpCompress/Common/Zip/StreamingZipFilePart.Async.cs @@ -7,6 +7,7 @@ namespace SharpCompress.Common.Zip; internal sealed partial class StreamingZipFilePart { + [Zomp.SyncMethodGenerator.CreateSyncVersion] internal override async ValueTask GetCompressedStreamAsync( CancellationToken cancellationToken = default ) @@ -15,17 +16,18 @@ internal sealed partial class StreamingZipFilePart { return Stream.Null; } - _decompressionStream = await CreateDecompressionStreamAsync( + var decompressionStream = await CreateDecompressionStreamAsync( await GetCryptoStreamAsync(CreateBaseStream(), cancellationToken) .ConfigureAwait(false), Header.CompressionMethod, cancellationToken ) .ConfigureAwait(false); + _decompressionStream = decompressionStream; if (LeaveStreamOpen) { - return SharpCompressStream.CreateNonDisposing(_decompressionStream); + return SharpCompressStream.CreateNonDisposing(decompressionStream); } - return _decompressionStream; + return decompressionStream; } } diff --git a/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs b/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs index e8ef7c55..9240e16c 100644 --- a/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs @@ -1,6 +1,5 @@ using System.IO; using SharpCompress.Common.Zip.Headers; -using SharpCompress.Compressors; using SharpCompress.IO; using SharpCompress.Providers; @@ -19,23 +18,6 @@ internal sealed partial class StreamingZipFilePart : ZipFilePart protected override Stream CreateBaseStream() => Header.PackedStream.NotNull(); - internal override Stream GetCompressedStream() - { - if (!Header.HasData) - { - return Stream.Null; - } - _decompressionStream = CreateDecompressionStream( - GetCryptoStream(CreateBaseStream()), - Header.CompressionMethod - ); - if (LeaveStreamOpen) - { - return SharpCompressStream.CreateNonDisposing(_decompressionStream); - } - return _decompressionStream; - } - internal BinaryReader FixStreamedFileLocation(ref Stream stream) { if (Header.IsDirectory) @@ -45,12 +27,12 @@ internal sealed partial class StreamingZipFilePart : ZipFilePart if (Header.HasData && !Skipped) { - _decompressionStream ??= GetCompressedStream(); + var decompressionStream = _decompressionStream ??= GetCompressedStream().NotNull(); - _decompressionStream.Skip(); + decompressionStream.Skip(); // If we had TotalIn / TotalOut we could have used them - Header.CompressedSize = _decompressionStream.Position; + Header.CompressedSize = decompressionStream.Position; Skipped = true; } diff --git a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.Async.cs b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.Async.cs index d68b8a87..04863349 100644 --- a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.Async.cs +++ b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.Async.cs @@ -32,6 +32,7 @@ internal partial class WinzipAesCryptoStream } #endif + [Zomp.SyncMethodGenerator.CreateSyncVersion] public override async Task ReadAsync( byte[] buffer, int offset, diff --git a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs index 9d269473..f6343c02 100644 --- a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs +++ b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs @@ -104,25 +104,6 @@ internal partial class WinzipAesCryptoStream : Stream public override void Flush() { } - public override int Read(byte[] buffer, int offset, int count) - { - if (_totalBytesLeftToRead == 0) - { - return 0; - } - var bytesToRead = count; - if (count > _totalBytesLeftToRead) - { - bytesToRead = (int)_totalBytesLeftToRead; - } - var read = _stream.Read(buffer, offset, bytesToRead); - _totalBytesLeftToRead -= read; - - ReadTransformBlocks(buffer, offset, read); - - return read; - } - private void FillCounterOut() { // update the counter diff --git a/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs b/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs index 29f523a6..2902de0b 100644 --- a/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs +++ b/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs @@ -13,6 +13,7 @@ namespace SharpCompress.Common.Zip; internal abstract partial class ZipFilePart { + [Zomp.SyncMethodGenerator.CreateSyncVersion] internal override async ValueTask GetCompressedStreamAsync( CancellationToken cancellationToken = default ) @@ -35,6 +36,7 @@ internal abstract partial class ZipFilePart return decompressionStream; } + [Zomp.SyncMethodGenerator.CreateSyncVersion] protected async ValueTask GetCryptoStreamAsync( Stream plainStream, CancellationToken cancellationToken = default @@ -242,6 +244,7 @@ internal abstract partial class ZipFilePart } } + [Zomp.SyncMethodGenerator.CreateSyncVersion] private async ValueTask CreateWinzipAesDecompressionStreamAsync( Stream stream, CancellationToken cancellationToken = default diff --git a/src/SharpCompress/Common/Zip/ZipFilePart.cs b/src/SharpCompress/Common/Zip/ZipFilePart.cs index de182f37..8597b825 100644 --- a/src/SharpCompress/Common/Zip/ZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/ZipFilePart.cs @@ -1,19 +1,6 @@ using System; -using System.Buffers.Binary; using System.IO; -using System.Linq; using SharpCompress.Common.Zip.Headers; -using SharpCompress.Compressors; -using SharpCompress.Compressors.BZip2; -using SharpCompress.Compressors.Deflate; -using SharpCompress.Compressors.Deflate64; -using SharpCompress.Compressors.Explode; -using SharpCompress.Compressors.LZMA; -using SharpCompress.Compressors.PPMd; -using SharpCompress.Compressors.Reduce; -using SharpCompress.Compressors.Shrink; -using SharpCompress.Compressors.Xz; -using SharpCompress.Compressors.ZStandard; using SharpCompress.IO; using SharpCompress.Providers; @@ -41,23 +28,6 @@ internal abstract partial class ZipFilePart : FilePart internal override string? FilePartName => Header.Name; - internal override Stream GetCompressedStream() - { - if (!Header.HasData) - { - return Stream.Null; - } - var decompressionStream = CreateDecompressionStream( - GetCryptoStream(CreateBaseStream()), - Header.CompressionMethod - ); - if (LeaveStreamOpen) - { - return SharpCompressStream.CreateNonDisposing(decompressionStream); - } - return decompressionStream; - } - internal override Stream GetRawStream() { if (!Header.HasData) @@ -192,101 +162,4 @@ internal abstract partial class ZipFilePart : FilePart // For simple methods, use the basic decompress return providers.CreateDecompressStream(compressionType, stream, context); } - - private Stream CreateWinzipAesDecompressionStream(Stream stream) - { - var data = Header.Extra.SingleOrDefault(x => x.Type == ExtraDataType.WinZipAes); - if (data is null) - { - throw new InvalidFormatException("No Winzip AES extra data found."); - } - if (data.Length != 7) - { - throw new InvalidFormatException("Winzip data length is not 7."); - } - var compressedMethod = BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes); - - if (compressedMethod != 0x01 && compressedMethod != 0x02) - { - throw new InvalidFormatException( - "Unexpected vendor version number for WinZip AES metadata" - ); - } - - var vendorId = BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(2)); - if (vendorId != 0x4541) - { - throw new InvalidFormatException("Unexpected vendor ID for WinZip AES metadata"); - } - return CreateDecompressionStream( - stream, - (ZipCompressionMethod)BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(5)) - ); - } - - protected Stream GetCryptoStream(Stream plainStream) - { - var isFileEncrypted = FlagUtility.HasFlag(Header.Flags, HeaderFlags.Encrypted); - - if (Header.CompressedSize == 0 && isFileEncrypted) - { - throw new NotSupportedException("Cannot encrypt file with unknown size at start."); - } - - if ( - Header.CompressedSize == 0 - && FlagUtility.HasFlag(Header.Flags, HeaderFlags.UsePostDataDescriptor) - ) - { - plainStream = SharpCompressStream.CreateNonDisposing(plainStream); //make sure AES doesn't close - } - else - { - plainStream = new ReadOnlySubStream(plainStream, Header.CompressedSize); //make sure AES doesn't close - } - - if (isFileEncrypted) - { - switch (Header.CompressionMethod) - { - case ZipCompressionMethod.None: - case ZipCompressionMethod.Shrink: - case ZipCompressionMethod.Reduce1: - case ZipCompressionMethod.Reduce2: - case ZipCompressionMethod.Reduce3: - case ZipCompressionMethod.Reduce4: - case ZipCompressionMethod.Deflate: - case ZipCompressionMethod.Deflate64: - case ZipCompressionMethod.BZip2: - case ZipCompressionMethod.LZMA: - case ZipCompressionMethod.PPMd: - { - return new PkwareTraditionalCryptoStream( - plainStream, - Header.ComposeEncryptionData(plainStream), - CryptoMode.Decrypt - ); - } - - case ZipCompressionMethod.WinzipAes: - { - if (Header.WinzipAesEncryptionData != null) - { - return new WinzipAesCryptoStream( - plainStream, - Header.WinzipAesEncryptionData, - Header.CompressedSize - 10 - ); - } - return plainStream; - } - - default: - { - throw new ArchiveOperationException("Header.CompressionMethod is invalid"); - } - } - } - return plainStream; - } } diff --git a/src/SharpCompress/Compressors/Deflate/DeflateStream.Async.cs b/src/SharpCompress/Compressors/Deflate/DeflateStream.Async.cs index 454ba022..ae6343cd 100644 --- a/src/SharpCompress/Compressors/Deflate/DeflateStream.Async.cs +++ b/src/SharpCompress/Compressors/Deflate/DeflateStream.Async.cs @@ -29,6 +29,10 @@ public partial class DeflateStream #endif } + /// + /// Flush the stream. + /// + [Zomp.SyncMethodGenerator.CreateSyncVersion] public override async Task FlushAsync(CancellationToken cancellationToken) { if (_disposed) @@ -38,6 +42,24 @@ public partial class DeflateStream await _baseStream.FlushAsync(cancellationToken).ConfigureAwait(false); } + /// + /// Read data from the stream. + /// + /// + /// + /// If you wish to use the DeflateStream to compress data while reading, create it with + /// CompressionMode.Compress and an uncompressed data stream. Reading then returns + /// compressed data. With CompressionMode.Decompress, reading returns decompressed data. + /// + /// + /// A DeflateStream can be used for Read() or Write(), but not both. + /// + /// + /// The buffer into which the read data should be placed. + /// The offset within that data array to put the first byte read. + /// The number of bytes to read. + /// The number of bytes actually read. + [Zomp.SyncMethodGenerator.CreateSyncVersion] public override async Task ReadAsync( byte[] buffer, int offset, @@ -68,6 +90,23 @@ public partial class DeflateStream } #endif + /// + /// Write data to the stream. + /// + /// + /// + /// With CompressionMode.Compress, written uncompressed data is compressed to the + /// destination. With CompressionMode.Decompress, written compressed data is decompressed + /// to the destination. + /// + /// + /// A DeflateStream can be used for Read() or Write(), but not both. + /// + /// + /// The buffer holding data to write to the stream. + /// The offset within that data array to find the first byte to write. + /// The number of bytes to write. + [Zomp.SyncMethodGenerator.CreateSyncVersion] public override async Task WriteAsync( byte[] buffer, int offset, diff --git a/src/SharpCompress/Compressors/Deflate/DeflateStream.cs b/src/SharpCompress/Compressors/Deflate/DeflateStream.cs index 13f3b356..47ab0c7f 100644 --- a/src/SharpCompress/Compressors/Deflate/DeflateStream.cs +++ b/src/SharpCompress/Compressors/Deflate/DeflateStream.cs @@ -27,8 +27,6 @@ using System; using System.IO; using System.Text; -using System.Threading; -using System.Threading.Tasks; using SharpCompress.Common; using SharpCompress.IO; @@ -270,54 +268,6 @@ public partial class DeflateStream : Stream, IStreamStack Stream IStreamStack.BaseStream() => _baseStream; - /// - /// Flush the stream. - /// - public override void Flush() - { - if (_disposed) - { - throw new ObjectDisposedException("DeflateStream"); - } - _baseStream.Flush(); - } - - /// - /// Read data from the stream. - /// - /// - /// - /// - /// If you wish to use the DeflateStream to compress data while - /// reading, you can create a DeflateStream with - /// CompressionMode.Compress, providing an uncompressed data stream. - /// Then call Read() on that DeflateStream, and the data read will be - /// compressed as you read. If you wish to use the DeflateStream to - /// decompress data while reading, you can create a DeflateStream with - /// CompressionMode.Decompress, providing a readable compressed data - /// stream. Then call Read() on that DeflateStream, and the data read - /// will be decompressed as you read. - /// - /// - /// - /// A DeflateStream can be used for Read() or Write(), but not both. - /// - /// - /// - /// The buffer into which the read data should be placed. - /// the offset within that data array to put the first byte read. - /// the number of bytes to read. - /// the number of bytes actually read - public override int Read(byte[] buffer, int offset, int count) - { - if (_disposed) - { - throw new ObjectDisposedException("DeflateStream"); - } - - return _baseStream.Read(buffer, offset, count); - } - public override int ReadByte() { if (_disposed) @@ -341,44 +291,6 @@ public partial class DeflateStream : Stream, IStreamStack /// this is irrelevant, since it will always throw! public override void SetLength(long value) => throw new NotSupportedException(); - /// - /// Write data to the stream. - /// - /// - /// - /// - /// If you wish to use the DeflateStream to compress data while - /// writing, you can create a DeflateStream with - /// CompressionMode.Compress, and a writable output stream. Then call - /// Write() on that DeflateStream, providing uncompressed data - /// as input. The data sent to the output stream will be the compressed form - /// of the data written. If you wish to use the DeflateStream to - /// decompress data while writing, you can create a DeflateStream with - /// CompressionMode.Decompress, and a writable output stream. Then - /// call Write() on that stream, providing previously compressed - /// data. The data sent to the output stream will be the decompressed form of - /// the data written. - /// - /// - /// - /// A DeflateStream can be used for Read() or Write(), - /// but not both. - /// - /// - /// - /// - /// The buffer holding data to write to the stream. - /// the offset within that data array to find the first byte to write. - /// the number of bytes to write. - public override void Write(byte[] buffer, int offset, int count) - { - if (_disposed) - { - throw new ObjectDisposedException("DeflateStream"); - } - _baseStream.Write(buffer, offset, count); - } - public override void WriteByte(byte value) { if (_disposed) diff --git a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs index 04ac63cf..4815ec33 100644 --- a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs +++ b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs @@ -47,7 +47,7 @@ internal enum ZlibStreamFlavor GZIP = 1952, } -internal class ZlibBaseStream : Stream, IStreamStack +internal partial class ZlibBaseStream : Stream, IStreamStack { Stream IStreamStack.BaseStream() => _stream; @@ -139,58 +139,7 @@ internal class ZlibBaseStream : Stream, IStreamStack private byte[] workingBuffer => _workingBuffer ??= ArrayPool.Shared.Rent(_bufferSize); - public override void Write(byte[] buffer, int offset, int count) - { - // workitem 7159 - // calculate the CRC on the unccompressed data (before writing) - if (crc != null) - { - crc.SlurpBlock(buffer, offset, count); - } - - if (_streamMode == StreamMode.Undefined) - { - _streamMode = StreamMode.Writer; - } - else if (_streamMode != StreamMode.Writer) - { - throw new ZlibException("Cannot Write after Reading."); - } - - if (count == 0) - { - return; - } - - // first reference of z property will initialize the private var _z - z.InputBuffer = buffer; - _z.NextIn = offset; - _z.AvailableBytesIn = count; - var done = false; - do - { - _z.OutputBuffer = workingBuffer; - _z.NextOut = 0; - _z.AvailableBytesOut = _workingBuffer.Length; - var rc = (_wantCompress) ? _z.Deflate(_flushMode) : _z.Inflate(_flushMode); - if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) - { - throw new ZlibException((_wantCompress ? "de" : "in") + "flating: " + _z.Message); - } - - //if (_workingBuffer.Length - _z.AvailableBytesOut > 0) - _stream.Write(_workingBuffer, 0, _workingBuffer.Length - _z.AvailableBytesOut); - - done = _z.AvailableBytesIn == 0 && _z.AvailableBytesOut != 0; - - // If GZIP and de-compress, we're done when 8 bytes remain. - if (_flavor == ZlibStreamFlavor.GZIP && !_wantCompress) - { - done = (_z.AvailableBytesIn == 8 && _z.AvailableBytesOut != 0); - } - } while (!done); - } - + [Zomp.SyncMethodGenerator.CreateSyncVersion] public override async Task WriteAsync( byte[] buffer, int offset, @@ -607,27 +556,7 @@ internal class ZlibBaseStream : Stream, IStreamStack _workingBuffer = null; } - public override void Flush() - { - // Only flush the underlying stream when in write mode - // Flushing input streams during read operations is not meaningful - // and can cause issues with forward-only/non-seekable streams - if (_streamMode == StreamMode.Writer) - { - _stream.Flush(); - } - else if (z.AvailableBytesIn > 0) - { - // Rewind the underlying stream by the number of unconsumed bytes in the buffer - // This handles the case where the decompressor over-read past the end of the entry - if (_stream is IStreamStack stack) - { - stack.RewindBytes(z.AvailableBytesIn); - } - z.AvailableBytesIn = 0; - } - } - + [Zomp.SyncMethodGenerator.CreateSyncVersion] public override async Task FlushAsync(CancellationToken cancellationToken) { // Only flush the underlying stream when in write mode @@ -670,31 +599,7 @@ internal class ZlibBaseStream : Stream, IStreamStack private bool nomoreinput; private bool isDisposed; - private string ReadZeroTerminatedString() - { - var list = new List(); - var done = false; - do - { - // workitem 7740 - var n = _stream.Read(_buf1, 0, 1); - if (n != 1) - { - throw new ZlibException("Unexpected EOF reading GZIP header."); - } - if (_buf1[0] == 0) - { - done = true; - } - else - { - list.Add(_buf1[0]); - } - } while (!done); - var buffer = list.ToArray(); - return _encoding.GetString(buffer, 0, buffer.Length); - } - + [Zomp.SyncMethodGenerator.CreateSyncVersion] private async ValueTask ReadZeroTerminatedStringAsync( CancellationToken cancellationToken ) @@ -844,192 +749,7 @@ internal class ZlibBaseStream : Stream, IStreamStack return totalBytesRead; } - public override Int32 Read(Byte[] buffer, Int32 offset, Int32 count) - { - // According to MS documentation, any implementation of the IO.Stream.Read function must: - // (a) throw an exception if offset & count reference an invalid part of the buffer, - // or if count < 0, or if buffer is null - // (b) return 0 only upon EOF, or if count = 0 - // (c) if not EOF, then return at least 1 byte, up to bytes - - if (_streamMode == StreamMode.Undefined) - { - if (!_stream.CanRead) - { - throw new ZlibException("The stream is not readable."); - } - - // for the first read, set up some controls. - _streamMode = StreamMode.Reader; - - // (The first reference to _z goes through the private accessor which - // may initialize it.) - z.AvailableBytesIn = 0; - if (_flavor == ZlibStreamFlavor.GZIP) - { - _gzipHeaderByteCount = _ReadAndValidateGzipHeader(); - - // workitem 8501: handle edge case (decompress empty stream) - if (_gzipHeaderByteCount == 0) - { - return 0; - } - } - } - - if (_streamMode != StreamMode.Reader) - { - throw new ZlibException("Cannot Read after Writing."); - } - - var rc = 0; - - // set up the output of the deflate/inflate codec: - _z.OutputBuffer = buffer; - _z.NextOut = offset; - _z.AvailableBytesOut = count; - - if (count == 0) - { - return 0; - } - if (nomoreinput && _wantCompress) - { - // no more input data available; therefore we flush to - // try to complete the read - rc = _z.Deflate(FlushType.Finish); - - if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) - { - throw new ZlibException( - String.Format( - Constants.DefaultCultureInfo, - "Deflating: rc={0} msg={1}", - rc, - _z.Message - ) - ); - } - - rc = (count - _z.AvailableBytesOut); - - // calculate CRC after reading - if (crc != null) - { - crc.SlurpBlock(buffer, offset, rc); - } - - return rc; - } - ThrowHelper.ThrowIfNull(buffer); - ThrowHelper.ThrowIfNegative(count); - ThrowHelper.ThrowIfLessThan(offset, buffer.GetLowerBound(0)); - if ((offset + count) > buffer.GetLength(0)) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - // This is necessary in case _workingBuffer has been resized. (new byte[]) - // (The first reference to _workingBuffer goes through the private accessor which - // may initialize it.) - _z.InputBuffer = workingBuffer; - - do - { - // need data in _workingBuffer in order to deflate/inflate. Here, we check if we have any. - if ((_z.AvailableBytesIn == 0) && (!nomoreinput)) - { - // No data available, so try to Read data from the captive stream. - _z.NextIn = 0; - _z.AvailableBytesIn = _stream.Read(_workingBuffer, 0, _workingBuffer.Length); - if (_z.AvailableBytesIn == 0) - { - nomoreinput = true; - } - } - - // we have data in InputBuffer; now compress or decompress as appropriate - rc = (_wantCompress) ? _z.Deflate(_flushMode) : _z.Inflate(_flushMode); - - if (nomoreinput && (rc == ZlibConstants.Z_BUF_ERROR)) - { - return 0; - } - - if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) - { - throw new ZlibException( - String.Format( - Constants.DefaultCultureInfo, - "{0}flating: rc={1} msg={2}", - (_wantCompress ? "de" : "in"), - rc, - _z.Message - ) - ); - } - - if ( - (nomoreinput || rc == ZlibConstants.Z_STREAM_END) && (_z.AvailableBytesOut == count) - ) - { - break; // nothing more to read - } - } //while (_z.AvailableBytesOut == count && rc == ZlibConstants.Z_OK); - while (_z.AvailableBytesOut > 0 && !nomoreinput && rc == ZlibConstants.Z_OK); - - // workitem 8557 - // is there more room in output? - if (_z.AvailableBytesOut > 0) - { - if (rc == ZlibConstants.Z_OK && _z.AvailableBytesIn == 0) - { - // deferred - } - - // are we completely done reading? - if (nomoreinput) - { - // and in compression? - if (_wantCompress) - { - // no more input data available; therefore we flush to - // try to complete the read - rc = _z.Deflate(FlushType.Finish); - - if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) - { - throw new ZlibException( - String.Format( - Constants.DefaultCultureInfo, - "Deflating: rc={0} msg={1}", - rc, - _z.Message - ) - ); - } - } - } - } - - rc = (count - _z.AvailableBytesOut); - - // calculate CRC after reading - if (crc != null) - { - crc.SlurpBlock(buffer, offset, rc); - } - - if (rc == ZlibConstants.Z_STREAM_END && z.AvailableBytesIn != 0 && !_wantCompress) - { - //rewind the buffer - this.RewindBytes(z.AvailableBytesIn); - z.AvailableBytesIn = 0; - } - - return rc; - } - + [Zomp.SyncMethodGenerator.CreateSyncVersion] public override async Task ReadAsync( byte[] buffer, int offset, diff --git a/src/SharpCompress/Compressors/Deflate/ZlibStream.Async.cs b/src/SharpCompress/Compressors/Deflate/ZlibStream.Async.cs index 0dee2197..058bd28e 100644 --- a/src/SharpCompress/Compressors/Deflate/ZlibStream.Async.cs +++ b/src/SharpCompress/Compressors/Deflate/ZlibStream.Async.cs @@ -8,6 +8,10 @@ namespace SharpCompress.Compressors.Deflate; public partial class ZlibStream { + /// + /// Flush the stream. + /// + [Zomp.SyncMethodGenerator.CreateSyncVersion] public override async Task FlushAsync(CancellationToken cancellationToken) { if (_disposed) @@ -33,6 +37,18 @@ public partial class ZlibStream } #endif + /// + /// Read data from the stream. + /// + /// + /// A ZlibStream compresses or decompresses according to its + /// CompressionMode. It can be used for Read() or Write(), but not both. + /// + /// The buffer into which the read data should be placed. + /// The offset within that data array to put the first byte read. + /// The number of bytes to read. + /// The number of bytes actually read. + [Zomp.SyncMethodGenerator.CreateSyncVersion] public override async Task ReadAsync( byte[] buffer, int offset, @@ -63,6 +79,17 @@ public partial class ZlibStream } #endif + /// + /// Write data to the stream. + /// + /// + /// A ZlibStream compresses or decompresses according to its + /// CompressionMode. It can be used for Read() or Write(), but not both. + /// + /// The buffer holding data to write to the stream. + /// The offset within that data array to find the first byte to write. + /// The number of bytes to write. + [Zomp.SyncMethodGenerator.CreateSyncVersion] public override async Task WriteAsync( byte[] buffer, int offset, diff --git a/src/SharpCompress/Compressors/Deflate/ZlibStream.cs b/src/SharpCompress/Compressors/Deflate/ZlibStream.cs index c4652d44..580961d7 100644 --- a/src/SharpCompress/Compressors/Deflate/ZlibStream.cs +++ b/src/SharpCompress/Compressors/Deflate/ZlibStream.cs @@ -28,8 +28,6 @@ using System; using System.IO; using System.Text; -using System.Threading; -using System.Threading.Tasks; using SharpCompress.Common; namespace SharpCompress.Compressors.Deflate; @@ -231,53 +229,6 @@ public partial class ZlibStream : Stream } } - /// - /// Flush the stream. - /// - public override void Flush() - { - if (_disposed) - { - throw new ObjectDisposedException("ZlibStream"); - } - _baseStream.Flush(); - } - - /// - /// Read data from the stream. - /// - /// - /// - /// - /// - /// If you wish to use the ZlibStream to compress data while reading, - /// you can create a ZlibStream with CompressionMode.Compress, - /// providing an uncompressed data stream. Then call Read() on that - /// ZlibStream, and the data read will be compressed. If you wish to - /// use the ZlibStream to decompress data while reading, you can create - /// a ZlibStream with CompressionMode.Decompress, providing a - /// readable compressed data stream. Then call Read() on that - /// ZlibStream, and the data will be decompressed as it is read. - /// - /// - /// - /// A ZlibStream can be used for Read() or Write(), but - /// not both. - /// - /// - /// - /// The buffer into which the read data should be placed. - /// the offset within that data array to put the first byte read. - /// the number of bytes to read. - public override int Read(byte[] buffer, int offset, int count) - { - if (_disposed) - { - throw new ObjectDisposedException("ZlibStream"); - } - return _baseStream.Read(buffer, offset, count); - } - public override int ReadByte() { if (_disposed) @@ -297,41 +248,6 @@ public partial class ZlibStream : Stream /// public override void SetLength(long value) => throw new NotSupportedException(); - /// - /// Write data to the stream. - /// - /// - /// - /// - /// - /// If you wish to use the ZlibStream to compress data while writing, - /// you can create a ZlibStream with CompressionMode.Compress, - /// and a writable output stream. Then call Write() on that - /// ZlibStream, providing uncompressed data as input. The data sent to - /// the output stream will be the compressed form of the data written. If you - /// wish to use the ZlibStream to decompress data while writing, you - /// can create a ZlibStream with CompressionMode.Decompress, and a - /// writable output stream. Then call Write() on that stream, - /// providing previously compressed data. The data sent to the output stream - /// will be the decompressed form of the data written. - /// - /// - /// - /// A ZlibStream can be used for Read() or Write(), but not both. - /// - /// - /// The buffer holding data to write to the stream. - /// the offset within that data array to find the first byte to write. - /// the number of bytes to write. - public override void Write(byte[] buffer, int offset, int count) - { - if (_disposed) - { - throw new ObjectDisposedException("ZlibStream"); - } - _baseStream.Write(buffer, offset, count); - } - public override void WriteByte(byte value) { if (_disposed) diff --git a/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.Async.cs b/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.Async.cs index 64652872..dbb8afa6 100644 --- a/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.Async.cs +++ b/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.Async.cs @@ -10,6 +10,7 @@ namespace SharpCompress.Compressors.Deflate64; public sealed partial class Deflate64Stream { + [Zomp.SyncMethodGenerator.CreateSyncVersion] public override async Task ReadAsync( byte[] buffer, int offset, diff --git a/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs b/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs index 9e9ff7db..8d06436f 100644 --- a/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs +++ b/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs @@ -5,10 +5,7 @@ using System; using System.IO; using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; using SharpCompress.Common; -using SharpCompress.Common.Zip; namespace SharpCompress.Compressors.Deflate64; @@ -69,50 +66,6 @@ public sealed partial class Deflate64Stream : Stream public override void SetLength(long value) => throw new NotSupportedException("Deflate64: not supported"); - public override int Read(byte[] buffer, int offset, int count) - { - ValidateParameters(buffer, offset, count); - EnsureNotDisposed(); - - int bytesRead; - var currentOffset = offset; - var remainingCount = count; - - while (true) - { - bytesRead = _inflater.Inflate(buffer, currentOffset, remainingCount); - currentOffset += bytesRead; - remainingCount -= bytesRead; - - if (remainingCount == 0) - { - break; - } - - if (_inflater.Finished()) - { - // if we finished decompressing, we can't have anything left in the outputwindow. - break; - } - - var bytes = _stream.Read(_buffer, 0, _buffer.Length); - if (bytes <= 0) - { - break; - } - else if (bytes > _buffer.Length) - { - // The stream is either malicious or poorly implemented and returned a number of - // bytes larger than the buffer supplied to it. - throw new InvalidFormatException("Deflate64: invalid data"); - } - - _inflater.SetInput(_buffer, 0, bytes); - } - - return count - remainingCount; - } - private void ValidateParameters(byte[] array, int offset, int count) { ThrowHelper.ThrowIfNull(array); diff --git a/src/SharpCompress/Providers/CompressionProviderRegistry.cs b/src/SharpCompress/Providers/CompressionProviderRegistry.cs index ea0e217a..289487a0 100644 --- a/src/SharpCompress/Providers/CompressionProviderRegistry.cs +++ b/src/SharpCompress/Providers/CompressionProviderRegistry.cs @@ -26,7 +26,7 @@ namespace SharpCompress.Providers; /// }; /// /// -public sealed class CompressionProviderRegistry +public sealed partial class CompressionProviderRegistry { /// /// The default registry using SharpCompress internal implementations. @@ -61,103 +61,11 @@ public sealed class CompressionProviderRegistry /// The compression type. /// The destination stream. /// The compression level. - /// A compression stream. - /// If no provider is registered for the type. - /// If the provider does not support compression. - public Stream CreateCompressStream(CompressionType type, Stream destination, int level) - { - var provider = GetProvider(type); - if (provider is null) - { - throw new ArchiveOperationException( - $"No compression provider registered for type: {type}" - ); - } - return provider.CreateCompressStream(destination, level); - } - - /// - /// Creates a decompression stream for the specified type. - /// - /// The compression type. - /// The source stream. - /// A decompression stream. - /// If no provider is registered for the type. - /// If the provider does not support decompression. - public Stream CreateDecompressStream(CompressionType type, Stream source) - { - var provider = GetProvider(type); - if (provider is null) - { - throw new ArchiveOperationException( - $"No compression provider registered for type: {type}" - ); - } - return provider.CreateDecompressStream(source); - } - - /// - /// Creates a compression stream for the specified type with context. - /// - /// The compression type. - /// The destination stream. - /// The compression level. - /// Context information for the compression. - /// A compression stream. - /// If no provider is registered for the type. - /// If the provider does not support compression. - public Stream CreateCompressStream( - CompressionType type, - Stream destination, - int level, - CompressionContext context - ) - { - var provider = GetProvider(type); - if (provider is null) - { - throw new ArchiveOperationException( - $"No compression provider registered for type: {type}" - ); - } - return provider.CreateCompressStream(destination, level, context); - } - - /// - /// Creates a decompression stream for the specified type with context. - /// - /// The compression type. - /// The source stream. - /// Context information for the decompression. - /// A decompression stream. - /// If no provider is registered for the type. - /// If the provider does not support decompression. - public Stream CreateDecompressStream( - CompressionType type, - Stream source, - CompressionContext context - ) - { - var provider = GetProvider(type); - if (provider is null) - { - throw new ArchiveOperationException( - $"No compression provider registered for type: {type}" - ); - } - return provider.CreateDecompressStream(source, context); - } - - /// - /// Asynchronously creates a compression stream for the specified type. - /// - /// The compression type. - /// The destination stream. - /// The compression level. /// Cancellation token. - /// A task containing the compression stream. + /// A compression stream. /// If no provider is registered for the type. /// If the provider does not support compression. + [Zomp.SyncMethodGenerator.CreateSyncVersion] public ValueTask CreateCompressStreamAsync( CompressionType type, Stream destination, @@ -176,14 +84,15 @@ public sealed class CompressionProviderRegistry } /// - /// Asynchronously creates a decompression stream for the specified type. + /// Creates a decompression stream for the specified type. /// /// The compression type. /// The source stream. /// Cancellation token. - /// A task containing the decompression stream. + /// A decompression stream. /// If no provider is registered for the type. /// If the provider does not support decompression. + [Zomp.SyncMethodGenerator.CreateSyncVersion] public ValueTask CreateDecompressStreamAsync( CompressionType type, Stream source, @@ -201,16 +110,17 @@ public sealed class CompressionProviderRegistry } /// - /// Asynchronously creates a compression stream for the specified type with context. + /// Creates a compression stream for the specified type with context. /// /// The compression type. /// The destination stream. /// The compression level. /// Context information for the compression. /// Cancellation token. - /// A task containing the compression stream. + /// A compression stream. /// If no provider is registered for the type. /// If the provider does not support compression. + [Zomp.SyncMethodGenerator.CreateSyncVersion] public ValueTask CreateCompressStreamAsync( CompressionType type, Stream destination, @@ -230,15 +140,16 @@ public sealed class CompressionProviderRegistry } /// - /// Asynchronously creates a decompression stream for the specified type with context. + /// Creates a decompression stream for the specified type with context. /// /// The compression type. /// The source stream. /// Context information for the decompression. /// Cancellation token. - /// A task containing the decompression stream. + /// A decompression stream. /// If no provider is registered for the type. /// If the provider does not support decompression. + [Zomp.SyncMethodGenerator.CreateSyncVersion] public ValueTask CreateDecompressStreamAsync( CompressionType type, Stream source, diff --git a/src/SharpCompress/Writers/Zip/ZipWriter.Async.cs b/src/SharpCompress/Writers/Zip/ZipWriter.Async.cs index b934c6e1..ec0d9ca4 100644 --- a/src/SharpCompress/Writers/Zip/ZipWriter.Async.cs +++ b/src/SharpCompress/Writers/Zip/ZipWriter.Async.cs @@ -47,8 +47,9 @@ public partial class ZipWriter } /// - /// Asynchronously writes an entry to the ZIP archive. + /// Writes an entry to the ZIP archive. /// + [Zomp.SyncMethodGenerator.CreateSyncVersion] public override async ValueTask WriteAsync( string filename, Stream source, @@ -56,7 +57,9 @@ public partial class ZipWriter CancellationToken cancellationToken = default ) { +#if !SYNC_ONLY cancellationToken.ThrowIfCancellationRequested(); +#endif await WriteAsync( filename, source, @@ -67,8 +70,9 @@ public partial class ZipWriter } /// - /// Asynchronously writes an entry to the ZIP archive with specified options. + /// Writes an entry to the ZIP archive with specified options. /// + [Zomp.SyncMethodGenerator.CreateSyncVersion] public async ValueTask WriteAsync( string entryPath, Stream source, @@ -76,7 +80,9 @@ public partial class ZipWriter CancellationToken cancellationToken = default ) { +#if !SYNC_ONLY cancellationToken.ThrowIfCancellationRequested(); +#endif await using var output = await WriteToStreamAsync( entryPath, zipWriterEntryOptions, @@ -84,7 +90,9 @@ public partial class ZipWriter ) .ConfigureAwait(false); var progressStream = WrapWithProgress(source, entryPath); - await progressStream.CopyToAsync(output, 81920, cancellationToken).ConfigureAwait(false); + await progressStream + .CopyToAsync(output, WriterOptions.BufferSize, cancellationToken) + .ConfigureAwait(false); } private async ValueTask WriteToStreamAsync( @@ -168,16 +176,18 @@ public partial class ZipWriter } /// - /// Asynchronously writes a directory entry to the ZIP archive. - /// Uses synchronous implementation for directory entries as they are lightweight. + /// Writes a directory entry to the ZIP archive. /// + [Zomp.SyncMethodGenerator.CreateSyncVersion] public override async ValueTask WriteDirectoryAsync( string directoryName, DateTime? modificationTime, CancellationToken cancellationToken = default ) { +#if !SYNC_ONLY cancellationToken.ThrowIfCancellationRequested(); +#endif var normalizedName = NormalizeDirectoryName(directoryName); if (string.IsNullOrEmpty(normalizedName)) @@ -190,6 +200,7 @@ public partial class ZipWriter .ConfigureAwait(false); } + [Zomp.SyncMethodGenerator.CreateSyncVersion] private async ValueTask WriteDirectoryEntryAsync( string directoryPath, ZipWriterEntryOptions options, diff --git a/src/SharpCompress/Writers/Zip/ZipWriter.cs b/src/SharpCompress/Writers/Zip/ZipWriter.cs index 8775253c..f9640c32 100644 --- a/src/SharpCompress/Writers/Zip/ZipWriter.cs +++ b/src/SharpCompress/Writers/Zip/ZipWriter.cs @@ -77,20 +77,6 @@ public partial class ZipWriter : AbstractWriter _ => throw new InvalidFormatException("Invalid compression method: " + compressionType), }; - public override void Write(string filename, Stream source, DateTime? modificationTime) => - Write( - filename, - source, - new ZipWriterEntryOptions() { ModificationDateTime = modificationTime } - ); - - public void Write(string entryPath, Stream source, ZipWriterEntryOptions zipWriterEntryOptions) - { - using var output = WriteToStream(entryPath, zipWriterEntryOptions); - var progressStream = WrapWithProgress(source, entryPath); - progressStream.CopyTo(output, WriterOptions.BufferSize); - } - public Stream WriteToStream(string entryPath, ZipWriterEntryOptions options) { options.ValidateWithFallback(compressionType, compressionLevel); @@ -162,59 +148,6 @@ public partial class ZipWriter : AbstractWriter return directoryName; } - public override void WriteDirectory(string directoryName, DateTime? modificationTime) - { - var normalizedName = NormalizeDirectoryName(directoryName); - if (string.IsNullOrEmpty(normalizedName)) - { - return; // Skip empty or root directory - } - - var options = new ZipWriterEntryOptions { ModificationDateTime = modificationTime }; - WriteDirectoryEntry(normalizedName, options); - } - - // WriteDirectoryAsync moved to ZipWriter.Async.cs - - private void WriteDirectoryEntry(string directoryPath, ZipWriterEntryOptions options) - { - var compression = ZipCompressionMethod.None; - - options.ModificationDateTime ??= DateTime.Now; - options.EntryComment ??= string.Empty; - - var entry = new ZipCentralDirectoryEntry( - compression, - directoryPath, - (ulong)streamPosition, - WriterOptions.ArchiveEncoding - ) - { - Comment = options.EntryComment, - ModificationTime = options.ModificationDateTime, - Crc = 0, - Compressed = 0, - Decompressed = 0, - }; - - // Use the archive default setting for zip64 and allow overrides - var useZip64 = isZip64; - if (options.EnableZip64.HasValue) - { - useZip64 = options.EnableZip64.Value; - } - - var headersize = (uint)WriteHeader( - directoryPath, - options, - entry, - useZip64, - usesDataDescriptor: false - ); - streamPosition += headersize; - entries.Add(entry); - } - private int WriteHeader( string filename, ZipWriterEntryOptions zipWriterEntryOptions, diff --git a/src/SharpCompress/Writers/Zip/ZipWritingStream.cs b/src/SharpCompress/Writers/Zip/ZipWritingStream.cs index ad7c7473..e84b5155 100644 --- a/src/SharpCompress/Writers/Zip/ZipWritingStream.cs +++ b/src/SharpCompress/Writers/Zip/ZipWritingStream.cs @@ -15,7 +15,7 @@ namespace SharpCompress.Writers.Zip; public partial class ZipWriter { - internal class ZipWritingStream : Stream + internal partial class ZipWritingStream : Stream { private readonly CRC32 crc = new(); private readonly ZipCentralDirectoryEntry entry; @@ -98,114 +98,7 @@ public partial class ZipWriter set => throw new NotSupportedException(); } - private Stream GetWriteStream(Stream writeStream) - { - counting = new CountingStream(SharpCompressStream.CreateNonDisposing(writeStream)); - Stream output = counting; - - var providers = writer.WriterOptions.Providers; - - switch (zipCompressionMethod) - { - case ZipCompressionMethod.None: - { - return output; - } - case ZipCompressionMethod.Deflate: - { - return providers.CreateCompressStream( - CompressionType.Deflate, - counting, - compressionLevel - ); - } - case ZipCompressionMethod.BZip2: - { - return providers.CreateCompressStream( - CompressionType.BZip2, - counting, - compressionLevel - ); - } - case ZipCompressionMethod.LZMA: - { - var compressingProvider = providers.GetCompressingProvider( - CompressionType.LZMA - ); - if (compressingProvider is null) - { - throw new ArchiveOperationException("LZMA compression provider not found."); - } - - var context = new CompressionContext { CanSeek = originalStream.CanSeek }; - compressionProviderHooks = compressingProvider; - compressionContext = context; - - var preData = compressingProvider.GetPreCompressionData(context); - if (preData is not null) - { - counting.Write(preData, 0, preData.Length); - } - - var lzmaStream = compressingProvider.CreateCompressStream( - counting, - compressionLevel, - context - ); - - var props = compressingProvider.GetCompressionProperties(lzmaStream, context); - if (props is not null) - { - counting.Write(props, 0, props.Length); - } - - return lzmaStream; - } - case ZipCompressionMethod.PPMd: - { - var compressingProvider = providers.GetCompressingProvider( - CompressionType.PPMd - ); - if (compressingProvider is null) - { - throw new ArchiveOperationException("PPMd compression provider not found."); - } - - var context = new CompressionContext - { - CanSeek = originalStream.CanSeek, - FormatOptions = writer.PpmdProperties, - }; - compressionProviderHooks = compressingProvider; - compressionContext = context; - - var preData = compressingProvider.GetPreCompressionData(context); - if (preData is not null) - { - counting.Write(preData, 0, preData.Length); - } - - return compressingProvider.CreateCompressStream( - counting, - compressionLevel, - context - ); - } - case ZipCompressionMethod.ZStandard: - { - return providers.CreateCompressStream( - CompressionType.ZStandard, - counting, - compressionLevel - ); - } - default: - { - throw new NotSupportedException("CompressionMethod: " + zipCompressionMethod); - } - } - } - + [Zomp.SyncMethodGenerator.CreateSyncVersion] private async ValueTask GetWriteStreamAsync( Stream writeStream, CancellationToken cancellationToken @@ -459,17 +352,7 @@ public partial class ZipWriter public override void SetLength(long value) => throw new NotSupportedException(); - public override void Write(byte[] buffer, int offset, int count) - { - CheckWriteLimits(count); - - decompressed += (uint)count; - crc.SlurpBlock(buffer, offset, count); - writeStream.Write(buffer, offset, count); - - CheckPostWriteLimits(); - } - + [Zomp.SyncMethodGenerator.CreateSyncVersion] public override async Task WriteAsync( byte[] buffer, int offset, @@ -477,7 +360,9 @@ public partial class ZipWriter CancellationToken cancellationToken ) { +#if !SYNC_ONLY cancellationToken.ThrowIfCancellationRequested(); +#endif CheckWriteLimits(count); decompressed += (uint)count; @@ -556,30 +441,7 @@ public partial class ZipWriter } } - private void WritePostCompressionData() - { - if ( - compressionProviderHooks is null - || compressionContext is null - || counting is null - || zipCompressionMethod == ZipCompressionMethod.None - ) - { - return; - } - - var postData = compressionProviderHooks.GetPostCompressionData( - writeStream, - compressionContext - ); - if (postData is null || postData.Length == 0) - { - return; - } - - counting.Write(postData, 0, postData.Length); - } - + [Zomp.SyncMethodGenerator.CreateSyncVersion] private async ValueTask WritePostCompressionDataAsync(CancellationToken cancellationToken) { if ( diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index 78c97f48..99915dcb 100644 --- a/src/SharpCompress/packages.lock.json +++ b/src/SharpCompress/packages.lock.json @@ -538,4 +538,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/SharpCompress.Test/OptionsUsabilityTests.cs b/tests/SharpCompress.Test/OptionsUsabilityTests.cs index c8b6ba86..0226aed8 100644 --- a/tests/SharpCompress.Test/OptionsUsabilityTests.cs +++ b/tests/SharpCompress.Test/OptionsUsabilityTests.cs @@ -235,6 +235,21 @@ public class OptionsUsabilityTests : TestBase Assert.Equal(23, source.CopyBufferSize); } + [Fact] + public async Task ZipWriter_Uses_WriterOptions_BufferSize_Async() + { + await using var source = new TrackingReadStream(new byte[100]); + await using var destination = new MemoryStream(); + await using var writer = new ZipWriter( + destination, + new ZipWriterOptions(CompressionType.None) { BufferSize = 29 } + ); + + await writer.WriteAsync("buffer-size.txt", source, DateTime.Now); + + Assert.Equal(29, source.CopyBufferSize); + } + [Fact] public void TarWriter_Uses_WriterOptions_BufferSize_ForTransfer() { From 409ab426d20232763e2ce2584fef02ee8b74e8d9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Aug 2026 07:18:53 +0000 Subject: [PATCH 2/3] Initial plan From e578c8a9b8f271a3b071e6a8e6046605bf737e8e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Aug 2026 07:21:40 +0000 Subject: [PATCH 3/3] Fix formatting: correct indentation of helper methods in TarFactory.cs Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- src/SharpCompress/Factories/TarFactory.cs | 45 ++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/SharpCompress/Factories/TarFactory.cs b/src/SharpCompress/Factories/TarFactory.cs index 7c88b1d6..38dd7ab9 100644 --- a/src/SharpCompress/Factories/TarFactory.cs +++ b/src/SharpCompress/Factories/TarFactory.cs @@ -296,11 +296,53 @@ public class TarFactory #region IReaderFactory + /// + /// Checks whether is backed by a genuinely seekable stream. + /// Note: always reports for + /// non-passthrough instances (it simulates seeking via its ring buffer even over a + /// non-seekable underlying stream), so it must be unwrapped to check the real stream. + /// + private static bool IsGenuinelySeekable(Stream stream) + { + while (stream is SharpCompressStream scs) + { + stream = scs.BaseStream(); + } + return stream.CanSeek; + } + + /// + /// Creates a new wrapper around with + /// its own independent recording/rewind scope, isolated from any recording session the caller may + /// already have in progress on itself. + /// + /// + /// When the stream is genuinely seekable, a is used: + /// it delegates recording/rewinding to the underlying stream's native and + /// never allocates a rewind ring buffer. For non-seekable streams, a ring-buffered + /// is required to support rewinding during format detection. + /// + private static SharpCompressStream CreateNestedRecordingStream(Stream stream) + { + if (!IsGenuinelySeekable(stream)) + { + return new SharpCompressStream(stream); + } + + // Unwrap buffered SharpCompressStream wrappers so we use the real stream's native Seek. + while (stream is SharpCompressStream scs && !scs.IsPassthrough) + { + stream = scs.BaseStream(); + } + + return new SeekableSharpCompressStream(stream, leaveStreamOpen: false); + } + /// public IReader OpenReader(Stream stream, ReaderOptions? options) { options ??= ReaderOptions.ForExternalStream; - var sharpCompressStream = new SharpCompressStream(stream); + var sharpCompressStream = CreateNestedRecordingStream(stream); sharpCompressStream.StartRecording(TarWrapper.MaximumRewindBufferSize); foreach (var wrapper in TarWrapper.Wrappers) { @@ -315,6 +357,7 @@ public class TarFactory ); if (TarArchive.IsTarFile(decompressedStream)) { + sharpCompressStream.Rewind(); sharpCompressStream.StopRecording(); return new TarReader(sharpCompressStream, options, wrapper.CompressionType); }