Merge pull request #1401 from adamhathcock/adam/zip-sync-generator

This commit is contained in:
Adam Hathcock
2026-08-03 21:05:08 +01:00
committed by GitHub
26 changed files with 165 additions and 1078 deletions

View File

@@ -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 |
| 312 | below | to do |
| 35 | 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 |
| 1011 | 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<Stream>(...)` 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.
---

View File

@@ -7,6 +7,7 @@ namespace SharpCompress.Common.Zip.Headers;
internal abstract partial class ZipFileEntry
{
[Zomp.SyncMethodGenerator.CreateSyncVersion]
internal async ValueTask<PkwareTraditionalEncryptionData> 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);

View File

@@ -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; }
/// <summary>

View File

@@ -8,6 +8,7 @@ namespace SharpCompress.Common.Zip;
internal partial class PkwareTraditionalCryptoStream
{
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public override async Task<int> 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,

View File

@@ -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();

View File

@@ -7,6 +7,7 @@ namespace SharpCompress.Common.Zip;
internal partial class SeekableZipFilePart
{
[Zomp.SyncMethodGenerator.CreateSyncVersion]
internal override async ValueTask<Stream?> 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)

View File

@@ -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();

View File

@@ -7,6 +7,7 @@ namespace SharpCompress.Common.Zip;
internal sealed partial class StreamingZipFilePart
{
[Zomp.SyncMethodGenerator.CreateSyncVersion]
internal override async ValueTask<Stream?> 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;
}
}

View File

@@ -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;
}

View File

@@ -32,6 +32,7 @@ internal partial class WinzipAesCryptoStream
}
#endif
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public override async Task<int> ReadAsync(
byte[] buffer,
int offset,

View File

@@ -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

View File

@@ -13,6 +13,7 @@ namespace SharpCompress.Common.Zip;
internal abstract partial class ZipFilePart
{
[Zomp.SyncMethodGenerator.CreateSyncVersion]
internal override async ValueTask<Stream?> GetCompressedStreamAsync(
CancellationToken cancellationToken = default
)
@@ -35,6 +36,7 @@ internal abstract partial class ZipFilePart
return decompressionStream;
}
[Zomp.SyncMethodGenerator.CreateSyncVersion]
protected async ValueTask<Stream> GetCryptoStreamAsync(
Stream plainStream,
CancellationToken cancellationToken = default
@@ -242,6 +244,7 @@ internal abstract partial class ZipFilePart
}
}
[Zomp.SyncMethodGenerator.CreateSyncVersion]
private async ValueTask<Stream> CreateWinzipAesDecompressionStreamAsync(
Stream stream,
CancellationToken cancellationToken = default

View File

@@ -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;
}
}

View File

@@ -29,6 +29,10 @@ public partial class DeflateStream
#endif
}
/// <summary>
/// Flush the stream.
/// </summary>
[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);
}
/// <summary>
/// Read data from the stream.
/// </summary>
/// <remarks>
/// <para>
/// If you wish to use the <c>DeflateStream</c> to compress data while reading, create it with
/// <c>CompressionMode.Compress</c> and an uncompressed data stream. Reading then returns
/// compressed data. With <c>CompressionMode.Decompress</c>, reading returns decompressed data.
/// </para>
/// <para>
/// A <c>DeflateStream</c> can be used for <c>Read()</c> or <c>Write()</c>, but not both.
/// </para>
/// </remarks>
/// <param name="buffer">The buffer into which the read data should be placed.</param>
/// <param name="offset">The offset within that data array to put the first byte read.</param>
/// <param name="count">The number of bytes to read.</param>
/// <returns>The number of bytes actually read.</returns>
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public override async Task<int> ReadAsync(
byte[] buffer,
int offset,
@@ -68,6 +90,23 @@ public partial class DeflateStream
}
#endif
/// <summary>
/// Write data to the stream.
/// </summary>
/// <remarks>
/// <para>
/// With <c>CompressionMode.Compress</c>, written uncompressed data is compressed to the
/// destination. With <c>CompressionMode.Decompress</c>, written compressed data is decompressed
/// to the destination.
/// </para>
/// <para>
/// A <c>DeflateStream</c> can be used for <c>Read()</c> or <c>Write()</c>, but not both.
/// </para>
/// </remarks>
/// <param name="buffer">The buffer holding data to write to the stream.</param>
/// <param name="offset">The offset within that data array to find the first byte to write.</param>
/// <param name="count">The number of bytes to write.</param>
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public override async Task WriteAsync(
byte[] buffer,
int offset,

View File

@@ -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;
/// <summary>
/// Flush the stream.
/// </summary>
public override void Flush()
{
if (_disposed)
{
throw new ObjectDisposedException("DeflateStream");
}
_baseStream.Flush();
}
/// <summary>
/// Read data from the stream.
/// </summary>
/// <remarks>
///
/// <para>
/// If you wish to use the <c>DeflateStream</c> to compress data while
/// reading, you can create a <c>DeflateStream</c> with
/// <c>CompressionMode.Compress</c>, providing an uncompressed data stream.
/// Then call Read() on that <c>DeflateStream</c>, and the data read will be
/// compressed as you read. If you wish to use the <c>DeflateStream</c> to
/// decompress data while reading, you can create a <c>DeflateStream</c> with
/// <c>CompressionMode.Decompress</c>, providing a readable compressed data
/// stream. Then call Read() on that <c>DeflateStream</c>, and the data read
/// will be decompressed as you read.
/// </para>
///
/// <para>
/// A <c>DeflateStream</c> can be used for <c>Read()</c> or <c>Write()</c>, but not both.
/// </para>
///
/// </remarks>
/// <param name="buffer">The buffer into which the read data should be placed.</param>
/// <param name="offset">the offset within that data array to put the first byte read.</param>
/// <param name="count">the number of bytes to read.</param>
/// <returns>the number of bytes actually read</returns>
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
/// <param name="value">this is irrelevant, since it will always throw!</param>
public override void SetLength(long value) => throw new NotSupportedException();
/// <summary>
/// Write data to the stream.
/// </summary>
/// <remarks>
///
/// <para>
/// If you wish to use the <c>DeflateStream</c> to compress data while
/// writing, you can create a <c>DeflateStream</c> with
/// <c>CompressionMode.Compress</c>, and a writable output stream. Then call
/// <c>Write()</c> on that <c>DeflateStream</c>, 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 <c>DeflateStream</c> to
/// decompress data while writing, you can create a <c>DeflateStream</c> with
/// <c>CompressionMode.Decompress</c>, and a writable output stream. Then
/// call <c>Write()</c> on that stream, providing previously compressed
/// data. The data sent to the output stream will be the decompressed form of
/// the data written.
/// </para>
///
/// <para>
/// A <c>DeflateStream</c> can be used for <c>Read()</c> or <c>Write()</c>,
/// but not both.
/// </para>
///
/// </remarks>
///
/// <param name="buffer">The buffer holding data to write to the stream.</param>
/// <param name="offset">the offset within that data array to find the first byte to write.</param>
/// <param name="count">the number of bytes to write.</param>
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)

View File

@@ -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<byte>.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<byte>();
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<string> 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 <count> 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<int> ReadAsync(
byte[] buffer,
int offset,

View File

@@ -8,6 +8,10 @@ namespace SharpCompress.Compressors.Deflate;
public partial class ZlibStream
{
/// <summary>
/// Flush the stream.
/// </summary>
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public override async Task FlushAsync(CancellationToken cancellationToken)
{
if (_disposed)
@@ -33,6 +37,18 @@ public partial class ZlibStream
}
#endif
/// <summary>
/// Read data from the stream.
/// </summary>
/// <remarks>
/// A <c>ZlibStream</c> compresses or decompresses according to its
/// <c>CompressionMode</c>. It can be used for <c>Read()</c> or <c>Write()</c>, but not both.
/// </remarks>
/// <param name="buffer">The buffer into which the read data should be placed.</param>
/// <param name="offset">The offset within that data array to put the first byte read.</param>
/// <param name="count">The number of bytes to read.</param>
/// <returns>The number of bytes actually read.</returns>
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public override async Task<int> ReadAsync(
byte[] buffer,
int offset,
@@ -63,6 +79,17 @@ public partial class ZlibStream
}
#endif
/// <summary>
/// Write data to the stream.
/// </summary>
/// <remarks>
/// A <c>ZlibStream</c> compresses or decompresses according to its
/// <c>CompressionMode</c>. It can be used for <c>Read()</c> or <c>Write()</c>, but not both.
/// </remarks>
/// <param name="buffer">The buffer holding data to write to the stream.</param>
/// <param name="offset">The offset within that data array to find the first byte to write.</param>
/// <param name="count">The number of bytes to write.</param>
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public override async Task WriteAsync(
byte[] buffer,
int offset,

View File

@@ -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
}
}
/// <summary>
/// Flush the stream.
/// </summary>
public override void Flush()
{
if (_disposed)
{
throw new ObjectDisposedException("ZlibStream");
}
_baseStream.Flush();
}
/// <summary>
/// Read data from the stream.
/// </summary>
///
/// <remarks>
///
/// <para>
/// If you wish to use the <c>ZlibStream</c> to compress data while reading,
/// you can create a <c>ZlibStream</c> with <c>CompressionMode.Compress</c>,
/// providing an uncompressed data stream. Then call <c>Read()</c> on that
/// <c>ZlibStream</c>, and the data read will be compressed. If you wish to
/// use the <c>ZlibStream</c> to decompress data while reading, you can create
/// a <c>ZlibStream</c> with <c>CompressionMode.Decompress</c>, providing a
/// readable compressed data stream. Then call <c>Read()</c> on that
/// <c>ZlibStream</c>, and the data will be decompressed as it is read.
/// </para>
///
/// <para>
/// A <c>ZlibStream</c> can be used for <c>Read()</c> or <c>Write()</c>, but
/// not both.
/// </para>
///
/// </remarks>
/// <param name="buffer">The buffer into which the read data should be placed.</param>
/// <param name="offset">the offset within that data array to put the first byte read.</param>
/// <param name="count">the number of bytes to read.</param>
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
/// </summary>
public override void SetLength(long value) => throw new NotSupportedException();
/// <summary>
/// Write data to the stream.
/// </summary>
///
/// <remarks>
///
/// <para>
/// If you wish to use the <c>ZlibStream</c> to compress data while writing,
/// you can create a <c>ZlibStream</c> with <c>CompressionMode.Compress</c>,
/// and a writable output stream. Then call <c>Write()</c> on that
/// <c>ZlibStream</c>, 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 <c>ZlibStream</c> to decompress data while writing, you
/// can create a <c>ZlibStream</c> with <c>CompressionMode.Decompress</c>, and a
/// writable output stream. Then call <c>Write()</c> on that stream,
/// providing previously compressed data. The data sent to the output stream
/// will be the decompressed form of the data written.
/// </para>
///
/// <para>
/// A <c>ZlibStream</c> can be used for <c>Read()</c> or <c>Write()</c>, but not both.
/// </para>
/// </remarks>
/// <param name="buffer">The buffer holding data to write to the stream.</param>
/// <param name="offset">the offset within that data array to find the first byte to write.</param>
/// <param name="count">the number of bytes to write.</param>
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)

View File

@@ -10,6 +10,7 @@ namespace SharpCompress.Compressors.Deflate64;
public sealed partial class Deflate64Stream
{
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public override async Task<int> ReadAsync(
byte[] buffer,
int offset,

View File

@@ -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);

View File

@@ -26,7 +26,7 @@ namespace SharpCompress.Providers;
/// };
/// </code>
/// </remarks>
public sealed class CompressionProviderRegistry
public sealed partial class CompressionProviderRegistry
{
/// <summary>
/// The default registry using SharpCompress internal implementations.
@@ -61,103 +61,11 @@ public sealed class CompressionProviderRegistry
/// <param name="type">The compression type.</param>
/// <param name="destination">The destination stream.</param>
/// <param name="level">The compression level.</param>
/// <returns>A compression stream.</returns>
/// <exception cref="ArchiveOperationException">If no provider is registered for the type.</exception>
/// <exception cref="NotSupportedException">If the provider does not support compression.</exception>
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);
}
/// <summary>
/// Creates a decompression stream for the specified type.
/// </summary>
/// <param name="type">The compression type.</param>
/// <param name="source">The source stream.</param>
/// <returns>A decompression stream.</returns>
/// <exception cref="ArchiveOperationException">If no provider is registered for the type.</exception>
/// <exception cref="NotSupportedException">If the provider does not support decompression.</exception>
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);
}
/// <summary>
/// Creates a compression stream for the specified type with context.
/// </summary>
/// <param name="type">The compression type.</param>
/// <param name="destination">The destination stream.</param>
/// <param name="level">The compression level.</param>
/// <param name="context">Context information for the compression.</param>
/// <returns>A compression stream.</returns>
/// <exception cref="ArchiveOperationException">If no provider is registered for the type.</exception>
/// <exception cref="NotSupportedException">If the provider does not support compression.</exception>
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);
}
/// <summary>
/// Creates a decompression stream for the specified type with context.
/// </summary>
/// <param name="type">The compression type.</param>
/// <param name="source">The source stream.</param>
/// <param name="context">Context information for the decompression.</param>
/// <returns>A decompression stream.</returns>
/// <exception cref="ArchiveOperationException">If no provider is registered for the type.</exception>
/// <exception cref="NotSupportedException">If the provider does not support decompression.</exception>
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);
}
/// <summary>
/// Asynchronously creates a compression stream for the specified type.
/// </summary>
/// <param name="type">The compression type.</param>
/// <param name="destination">The destination stream.</param>
/// <param name="level">The compression level.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task containing the compression stream.</returns>
/// <returns>A compression stream.</returns>
/// <exception cref="ArchiveOperationException">If no provider is registered for the type.</exception>
/// <exception cref="NotSupportedException">If the provider does not support compression.</exception>
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public ValueTask<Stream> CreateCompressStreamAsync(
CompressionType type,
Stream destination,
@@ -176,14 +84,15 @@ public sealed class CompressionProviderRegistry
}
/// <summary>
/// Asynchronously creates a decompression stream for the specified type.
/// Creates a decompression stream for the specified type.
/// </summary>
/// <param name="type">The compression type.</param>
/// <param name="source">The source stream.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task containing the decompression stream.</returns>
/// <returns>A decompression stream.</returns>
/// <exception cref="ArchiveOperationException">If no provider is registered for the type.</exception>
/// <exception cref="NotSupportedException">If the provider does not support decompression.</exception>
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public ValueTask<Stream> CreateDecompressStreamAsync(
CompressionType type,
Stream source,
@@ -201,16 +110,17 @@ public sealed class CompressionProviderRegistry
}
/// <summary>
/// Asynchronously creates a compression stream for the specified type with context.
/// Creates a compression stream for the specified type with context.
/// </summary>
/// <param name="type">The compression type.</param>
/// <param name="destination">The destination stream.</param>
/// <param name="level">The compression level.</param>
/// <param name="context">Context information for the compression.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task containing the compression stream.</returns>
/// <returns>A compression stream.</returns>
/// <exception cref="ArchiveOperationException">If no provider is registered for the type.</exception>
/// <exception cref="NotSupportedException">If the provider does not support compression.</exception>
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public ValueTask<Stream> CreateCompressStreamAsync(
CompressionType type,
Stream destination,
@@ -230,15 +140,16 @@ public sealed class CompressionProviderRegistry
}
/// <summary>
/// Asynchronously creates a decompression stream for the specified type with context.
/// Creates a decompression stream for the specified type with context.
/// </summary>
/// <param name="type">The compression type.</param>
/// <param name="source">The source stream.</param>
/// <param name="context">Context information for the decompression.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task containing the decompression stream.</returns>
/// <returns>A decompression stream.</returns>
/// <exception cref="ArchiveOperationException">If no provider is registered for the type.</exception>
/// <exception cref="NotSupportedException">If the provider does not support decompression.</exception>
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public ValueTask<Stream> CreateDecompressStreamAsync(
CompressionType type,
Stream source,

View File

@@ -47,8 +47,9 @@ public partial class ZipWriter
}
/// <summary>
/// Asynchronously writes an entry to the ZIP archive.
/// Writes an entry to the ZIP archive.
/// </summary>
[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
}
/// <summary>
/// Asynchronously writes an entry to the ZIP archive with specified options.
/// Writes an entry to the ZIP archive with specified options.
/// </summary>
[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<ZipWritingStream> WriteToStreamAsync(
@@ -168,16 +176,18 @@ public partial class ZipWriter
}
/// <summary>
/// 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.
/// </summary>
[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,

View File

@@ -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,

View File

@@ -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<Stream> 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 (

View File

@@ -538,4 +538,4 @@
}
}
}
}
}

View File

@@ -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()
{