< Summary

Line coverage
40%
Covered lines: 844
Uncovered lines: 1219
Coverable lines: 2063
Total lines: 3583
Line coverage: 40.9%
Branch coverage
28%
Covered branches: 222
Total branches: 777
Branch coverage: 28.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
File 1: OpenAsync(...)100%11100%
File 1: OpenAsync(...)100%110%
File 1: OpenAsync(...)0%440%
File 1: OpenAsync(...)50%4475%
File 1: OpenAsyncCore(...)16.66%242424%
File 1: GetOffsetOfCompressedDataAsync(...)75%4483.33%
File 1: ReadEncryptionSaltIfNeededAsync(...)50%8866.66%
File 1: OpenInReadModeAsync(...)42.85%141455.88%
File 1: OpenInReadModeAsyncCore(System.Boolean,System.Nullable`1<System.IO.Compression.WinZipAesKeyMaterial>,System.Nullable`1<System.IO.Compression.ZipCryptoKeys>,System.Byte,System.Threading.CancellationToken)33.33%6666.66%
File 1: OpenInUpdateModeAsync(...)0%10100%
File 1: OpenInUpdateModeForReadAsync(...)0%220%
File 1: GetUncompressedDataAsync(...)0%16160%
File 1: WriteAndFinishLocalEntryAsync(...)100%11100%
File 1: WriteCentralDirectoryFileHeaderAsync(...)41.66%121265.38%
File 1: LoadLocalHeaderExtraFieldIfNeededAsync(...)0%220%
File 1: LoadCompressedBytesIfNeededAsync(...)0%660%
File 1: GetIsOpenableAsync(...)100%110%
File 1: ThrowIfNotOpenableAsync(...)50%2271.42%
File 1: OpenInUpdateModeWithPasswordAsync(...)0%22220%
File 1: CastToStreamAsync(...)100%110%
File 1: DecryptAndStoreForUpdateWithZipCryptoAsync(...)100%110%
File 1: DecryptAndStoreForUpdateWithAesAsync(...)0%220%
File 1: StoreDecryptedDataForUpdateAsync(...)0%12120%
File 1: IsOpenableAsync(...)50%161655.55%
File 1: WriteLocalFileHeaderAsync(...)40%101073.91%
File 1: WriteLocalFileHeaderAndDataIfNeededAsync(...)8.82%68685.84%
File 1: WriteCrcAndSizesInLocalHeaderAsync(...)50%6647.82%
File 1: WriteDataDescriptorAsync(...)100%110%
File 1: UnloadStreamsAsync()50%2262.5%
File 1: CloseStreamsAsync()50%2250%
File 2: .ctor(...)41.66%121274.57%
File 2: .ctor(...)0%220%
File 2: .ctor(...)50%8894.73%
File 2: ToString()100%110%
File 2: Delete()0%660%
File 2: Open()100%11100%
File 2: Open(...)50%4471.42%
File 2: Open(...)100%110%
File 2: Open(...)0%440%
File 2: InferAccessFromMode()75%4483.33%
File 2: ValidateAccessForMode(...)0%12120%
File 2: OpenCore(...)16.66%121229.41%
File 2: DecodeEntryString(...)50%66100%
File 2: .cctor()100%110%
File 2: GetOffsetOfCompressedData()75%4481.81%
File 2: ReadEncryptionSaltIfNeeded()50%8866.66%
File 2: GetUncompressedData(...)0%880%
File 2: WriteAndFinishLocalEntry(...)100%11100%
File 2: WriteCentralDirectoryFileHeaderInitialize(...)39.28%282860%
File 2: WriteCentralDirectoryFileHeaderPrepare(...)50%44100%
File 2: WriteCentralDirectoryFileHeader(...)41.66%121272.72%
File 2: LoadLocalHeaderExtraFieldIfNeeded()0%220%
File 2: LoadCompressedBytesIfNeededInitialize(...)0%220%
File 2: LoadCompressedBytesIfNeeded()0%660%
File 2: ThrowIfNotOpenable(...)50%2260%
File 2: DetectEntryNameVersion()50%2250%
File 2: GetDataCompressor(...)66.66%121288.46%
File 2: CalculateZipCryptoCheckByte()0%220%
File 2: GetAesKeySizeBits(...)75%4488.88%
File 2: WrapWithDecryptionIfNeeded(...)40%101053.84%
File 2: GetDataDecompressor(...)33.33%6642.85%
File 2: OpenInReadMode(...)50%22100%
File 2: OpenInReadModeGetDataCompressor(...)50%2272.72%
File 2: BuildDecompressionPipeline(...)50%6685.71%
File 2: OpenInWriteMode()50%2271.42%
File 2: OpenInWriteModeCore()42.85%141472.34%
File 2: OpenInUpdateMode(...)0%16160%
File 2: OpenInUpdateModeForRead()0%220%
File 2: MarkAsModified()0%220%
File 2: SetupEncryptionKeyMaterial(...)0%440%
File 2: PrepareEncryption(...)50%8855.55%
File 2: CreateAesExtraField()66.66%6693.75%
File 2: IsOpenable(...)50%161657.69%
File 2: IsOpenableInitialVerifications(...)58.33%121259.09%
File 2: IsOpenableFinalVerifications(...)33.33%121240%
File 2: MapCompressionLevel(...)25%121257.14%
File 2: MapDeflateCompressionOption(...)22.22%9975%
File 2: WriteLocalFileHeaderInitialize(...)35.71%424245.19%
File 2: WriteLocalFileHeaderPrepare(...)50%22100%
File 2: WriteLocalFileHeader(...)40%101084.21%
File 2: WriteLocalFileHeaderAndDataIfNeeded(...)13.04%46465.55%
File 2: WriteCrcAndSizesInLocalHeader(...)50%6645.45%
File 2: WriteCrcAndSizesInLocalHeaderInitialize(...)50%66100%
File 2: WriteCrcAndSizesInLocalHeaderPrepareForZip64PretendStreaming(...)100%110%
File 2: WriteCrcAndSizesInLocalHeaderPrepareFor32bitValuesWriting(...)50%4482.35%
File 2: WriteCrcAndSizesInLocalHeaderPrepareForWritingWhenZip64HeaderUsed(...)100%110%
File 2: WriteCrcAndSizesInLocalHeaderPrepareForWritingDataDescriptor(...)0%220%
File 2: WriteDataDescriptor()100%110%
File 2: PrepareToWriteDataDescriptor(...)0%440%
File 2: UnloadStreams()50%22100%
File 2: CloseStreams()50%22100%
File 2: VersionToExtractAtLeast(...)100%44100%
File 2: ThrowIfInvalidArchive()50%2266.66%
File 2: GetFileName_Windows(...)50%22100%
File 2: GetFileName_Unix(...)0%220%
File 2: .ctor(...)100%11100%
File 2: ThrowIfDisposed()50%2260%
File 2: Read(...)100%110%
File 2: ReadAsync(...)100%110%
File 2: ReadAsync(...)100%110%
File 2: Seek(...)100%110%
File 2: SetLength(...)100%110%
File 2: Write(...)50%4486.66%
File 2: Write(...)0%440%
File 2: WriteByte(...)100%110%
File 2: WriteAsync(...)100%110%
File 2: WriteAsync(...)50%22100%
File 2: Core(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)50%22100%
File 2: Flush()100%110%
File 2: FlushAsync(...)100%110%
File 2: Dispose(...)50%121268%
File 2: DisposeAsync()40%101060.71%
File 2: .cctor()100%110%
File 2: Compare(...)0%12120%
File 3: ParseFileName(...)50%22100%

File(s)

D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\ZipArchiveEntry.Async.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers.Binary;
 5using System.Diagnostics;
 6using System.Threading;
 7using System.Threading.Tasks;
 8
 9namespace System.IO.Compression;
 10
 11// The disposable fields that this class owns get disposed when the ZipArchive it belongs to gets disposed
 12public partial class ZipArchiveEntry
 13{
 14    /// <summary>
 15    /// Asynchronously opens the entry. If the archive that the entry belongs to was opened in Read mode, the returned s
 16    /// </summary>
 17    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 18    /// <returns>A Stream that represents the contents of the entry.</returns>
 19    /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been deleted f
 20    /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be read. -
 21    /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exceptio
 22    public Task<Stream> OpenAsync(CancellationToken cancellationToken = default)
 8623    {
 8624        cancellationToken.ThrowIfCancellationRequested();
 8625        ThrowIfInvalidArchive();
 8626        return OpenAsyncCore(InferAccessFromMode(), default, cancellationToken);
 8627    }
 28
 29    /// <summary>
 30    /// Asynchronously opens the entry with the specified access mode. This allows for more granular control over the re
 31    /// </summary>
 32    /// <param name="access">The file access mode for the returned stream.</param>
 33    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 34    /// <returns>A <see cref="Task{Stream}"/> that represents the asynchronous open operation.</returns>
 35    /// <remarks>
 36    /// <para>The allowed <paramref name="access"/> values depend on the <see cref="ZipArchiveMode"/>:</para>
 37    /// <list type="bullet">
 38    /// <item><description><see cref="ZipArchiveMode.Read"/>: Only <see cref="FileAccess.Read"/> is allowed.</descriptio
 39    /// <item><description><see cref="ZipArchiveMode.Create"/>: <see cref="FileAccess.Write"/> and <see cref="FileAccess
 40    /// <item><description><see cref="ZipArchiveMode.Update"/>: All values are allowed. <see cref="FileAccess.Read"/> pr
 41    /// </list>
 42    /// </remarks>
 43    /// <exception cref="ArgumentOutOfRangeException"><paramref name="access"/> is not a valid <see cref="FileAccess"/> 
 44    /// <exception cref="InvalidOperationException">The requested access is not compatible with the archive's open mode.
 45    /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been deleted f
 46    /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be read. -
 47    /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exceptio
 48    public Task<Stream> OpenAsync(FileAccess access, CancellationToken cancellationToken = default)
 049    {
 050        cancellationToken.ThrowIfCancellationRequested();
 051        ThrowIfInvalidArchive();
 052        ValidateAccessForMode(access);
 053        return OpenAsyncCore(access, default, cancellationToken);
 054    }
 55
 56    /// <summary>
 57    /// Asynchronously opens the entry with the specified access mode and password for decrypting encrypted entries.
 58    /// </summary>
 59    /// <param name="access">The file access mode for the returned stream.</param>
 60    /// <param name="password">The password used to decrypt the encrypted entry.</param>
 61    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 62    /// <returns>A <see cref="Task{Stream}"/> that represents the asynchronous open operation.</returns>
 63    /// <remarks>
 64    /// <para>The allowed <paramref name="access"/> values depend on the <see cref="ZipArchiveMode"/>:</para>
 65    /// <list type="bullet">
 66    /// <item><description><see cref="ZipArchiveMode.Read"/>: Only <see cref="FileAccess.Read"/> is allowed.</descriptio
 67    /// <item><description><see cref="ZipArchiveMode.Create"/>: <see cref="FileAccess.Write"/> and <see cref="FileAccess
 68    /// <item><description><see cref="ZipArchiveMode.Update"/>: All values are allowed for encrypted entries.</descripti
 69    /// </list>
 70    /// </remarks>
 71    /// <exception cref="ArgumentOutOfRangeException"><paramref name="access"/> is not a valid <see cref="FileAccess"/> 
 72    /// <exception cref="InvalidOperationException">The requested access is not compatible with the archive's open mode.
 73    /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been deleted f
 74    /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exceptio
 75    public Task<Stream> OpenAsync(FileAccess access, ReadOnlySpan<char> password, CancellationToken cancellationToken = 
 076    {
 077        cancellationToken.ThrowIfCancellationRequested();
 078        ThrowIfInvalidArchive();
 079        ValidateAccessForMode(access);
 80
 081        if (IsEncrypted && password.IsEmpty)
 082        {
 083            throw new ArgumentException(SR.PasswordRequired, nameof(password));
 84        }
 85
 086        return OpenAsyncCore(access, password, cancellationToken);
 087    }
 88
 89    /// <summary>
 90    /// Asynchronously opens the entry and uses the specified password to decrypt it if it is encrypted.
 91    /// If the archive that the entry belongs to was opened in Read mode, the returned stream will be readable, and it m
 92    /// </summary>
 93    /// <param name="password">The password used to decrypt the encrypted entry.</param>
 94    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 95    /// <returns>A task whose result is a stream that represents the contents of the entry.</returns>
 96    /// <remarks>
 97    /// <para>If the entry is not encrypted, <paramref name="password" /> is ignored.</para>
 98    /// </remarks>
 99    /// <exception cref="ArgumentException">The entry is encrypted and <paramref name="password" /> is empty.</exception
 100    /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been deleted f
 101    /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be read. -
 102    /// <exception cref="ObjectDisposedException">The <see cref="ZipArchive" /> that this entry belongs to has been disp
 103    public Task<Stream> OpenAsync(ReadOnlySpan<char> password, CancellationToken cancellationToken = default)
 172104    {
 172105        cancellationToken.ThrowIfCancellationRequested();
 172106        ThrowIfInvalidArchive();
 107
 172108        if (IsEncrypted && password.IsEmpty)
 0109        {
 0110            throw new ArgumentException(SR.PasswordRequired, nameof(password));
 111        }
 112
 172113        return OpenAsyncCore(InferAccessFromMode(), password, cancellationToken);
 172114    }
 115
 116    private Task<Stream> OpenAsyncCore(FileAccess access, ReadOnlySpan<char> password, CancellationToken cancellationTok
 258117    {
 258118        bool usePassword = IsEncrypted && !password.IsEmpty;
 119
 258120        switch (_archive.Mode)
 121        {
 122            case ZipArchiveMode.Read:
 172123                return OpenInReadModeAsync(checkOpenable: true, password, cancellationToken);
 124            case ZipArchiveMode.Create:
 86125                return Task.FromResult<Stream>(OpenInWriteMode());
 126            case ZipArchiveMode.Update:
 127            default:
 0128                Debug.Assert(_archive.Mode == ZipArchiveMode.Update);
 129                // Encrypted entries always require a password for re-encryption,
 130                // even when discarding existing content (write-only access).
 0131                if (IsEncrypted && password.IsEmpty && access != FileAccess.Read)
 0132                {
 0133                    throw new ArgumentException(SR.PasswordRequired, nameof(password));
 134                }
 0135                return access switch
 0136                {
 0137                    // Reads in Update mode must observe content written earlier in this session and
 0138                    // treat a newly created entry as empty. Only an unmodified entry that already
 0139                    // exists in the archive can be read directly without loading it into memory.
 0140                    FileAccess.Read => _storedUncompressedData is not null || !_originallyInArchive
 0141                        ? OpenInUpdateModeForReadAsync(cancellationToken)
 0142                        : OpenInReadModeAsync(checkOpenable: true, password, cancellationToken),
 0143                    FileAccess.Write => usePassword
 0144                        ? OpenInUpdateModeWithPasswordAsync(loadExistingContent: false, password, cancellationToken)
 0145                        : CastToStreamAsync(OpenInUpdateModeAsync(loadExistingContent: false, cancellationToken)),
 0146                    _ => usePassword
 0147                        ? OpenInUpdateModeWithPasswordAsync(loadExistingContent: true, password, cancellationToken)
 0148                        : CastToStreamAsync(OpenInUpdateModeAsync(loadExistingContent: true, cancellationToken)),
 0149                };
 150        }
 258151    }
 152
 153    internal async Task<long> GetOffsetOfCompressedDataAsync(CancellationToken cancellationToken)
 430154    {
 430155        cancellationToken.ThrowIfCancellationRequested();
 430156        if (_storedOffsetOfCompressedData == null)
 86157        {
 158            // Seek to local header
 86159            _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
 160
 161            // Skip the local file header to get to the compressed data
 162            // TrySkipBlockAsync handles both AES and non-AES cases correctly
 86163            if (!await ZipLocalFileHeader.TrySkipBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(fa
 0164            {
 0165                throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
 166            }
 167
 86168            _storedOffsetOfCompressedData = _archive.ArchiveStream.Position;
 86169        }
 430170        return _storedOffsetOfCompressedData.Value;
 430171    }
 172
 173    /// <summary>
 174    /// Asynchronously reads and caches the AES encryption salt from the local file data area.
 175    /// Called during central directory parsing for AES-encrypted entries so that
 176    /// the salt is available for key derivation without additional I/O at open time.
 177    /// </summary>
 178    internal async Task ReadEncryptionSaltIfNeededAsync(CancellationToken cancellationToken)
 86179    {
 86180        if (!IsAesEncrypted || !_originallyInArchive || OperatingSystem.IsBrowser())
 0181        {
 0182            return;
 183        }
 184
 185        // A corrupt central directory can point the local header offset past the end of the
 186        // archive. Seeking there throws ArgumentOutOfRangeException on some streams (e.g.
 187        // MemoryStream). Mirror the check in IsOpenableInitialVerifications and defer the error
 188        // to when the entry is actually opened, same as for non AES encrypted entries.
 86189        if (_offsetOfLocalHeader > _archive.ArchiveStream.Length)
 0190        {
 0191            return;
 192        }
 193
 86194        long savedPosition = _archive.ArchiveStream.Position;
 195        try
 86196        {
 86197            long offset = await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false);
 86198            _archive.ArchiveStream.Seek(offset, SeekOrigin.Begin);
 199
 86200            int keySizeBits = GetAesKeySizeBits(Encryption);
 86201            int saltSize = WinZipAesStream.GetSaltSize(keySizeBits);
 86202            _aesSalt = new byte[saltSize];
 86203            await _archive.ArchiveStream.ReadExactlyAsync(_aesSalt, cancellationToken).ConfigureAwait(false);
 86204        }
 0205        catch (Exception ex) when (ex is InvalidDataException or EndOfStreamException)
 0206        {
 207            // These are the only exceptions GetOffsetOfCompressedDataAsync() and ReadExactlyAsync()
 208            // can throw for corrupt or truncated data. Swallow them here and defer the error
 209            // to when the entry is actually opened.
 0210            _aesSalt = null;
 0211        }
 212        finally
 86213        {
 86214            _archive.ArchiveStream.Seek(savedPosition, SeekOrigin.Begin);
 86215        }
 86216    }
 217
 218    private Task<Stream> OpenInReadModeAsync(bool checkOpenable, ReadOnlySpan<char> password, CancellationToken cancella
 172219    {
 172220        cancellationToken.ThrowIfCancellationRequested();
 221
 222        // Derive key material from the password span before entering the async core.
 172223        WinZipAesKeyMaterial? aesKeys = null;
 172224        ZipCryptoKeys? zipCryptoKeys = null;
 172225        byte zipCryptoCheckByte = 0;
 226
 172227        if (IsEncrypted)
 172228        {
 172229            if (Encryption == ZipEncryptionMethod.Unknown)
 0230            {
 0231                throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 232            }
 233
 172234            if (password.IsEmpty)
 0235            {
 0236                throw new InvalidDataException(SR.PasswordRequired);
 237            }
 238
 172239            if (IsAesEncrypted)
 172240            {
 172241                if (OperatingSystem.IsBrowser())
 0242                {
 0243                    throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnBrowser);
 244                }
 245
 172246                if (_aesSalt is null)
 0247                {
 0248                    throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
 249                }
 250
 172251                int keySizeBits = GetAesKeySizeBits(Encryption);
 172252                aesKeys = WinZipAesStream.CreateKey(password, _aesSalt, keySizeBits);
 172253            }
 0254            else if (IsZipCryptoEncrypted)
 0255            {
 0256                zipCryptoCheckByte = CalculateZipCryptoCheckByte();
 0257                zipCryptoKeys = ZipCryptoStream.CreateKey(password);
 0258            }
 259            else
 0260            {
 0261                throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 262            }
 172263        }
 264
 172265        return OpenInReadModeAsyncCore(checkOpenable, aesKeys, zipCryptoKeys, zipCryptoCheckByte, cancellationToken);
 266
 267        async Task<Stream> OpenInReadModeAsyncCore(bool checkOpenable, WinZipAesKeyMaterial? aesKeys, ZipCryptoKeys? zip
 172268        {
 172269            if (checkOpenable)
 172270            {
 172271                await ThrowIfNotOpenableAsync(needToUncompress: true, needToLoadIntoMemory: false, cancellationToken).Co
 172272            }
 273
 172274            long offset = await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false);
 172275            Stream compressedStream = new SubReadStream(_archive.ArchiveStream, offset, _compressedSize);
 276
 277            Stream streamToDecompress;
 172278            if (aesKeys is not null)
 172279            {
 172280                streamToDecompress = await WinZipAesStream.CreateAsync(
 172281                    baseStream: compressedStream,
 172282                    keyMaterial: aesKeys.Value,
 172283                    totalStreamSize: _compressedSize,
 172284                    encrypting: false,
 172285                    cancellationToken: cancellationToken).ConfigureAwait(false);
 86286            }
 0287            else if (zipCryptoKeys is not null)
 0288            {
 0289                streamToDecompress = await ZipCryptoStream.CreateAsync(
 0290                    compressedStream, zipCryptoKeys.Value, zipCryptoCheckByte,
 0291                    encrypting: false, cancellationToken).ConfigureAwait(false);
 0292            }
 293            else
 0294            {
 0295                streamToDecompress = compressedStream;
 0296            }
 297
 86298            return BuildDecompressionPipeline(streamToDecompress);
 86299        }
 172300    }
 301
 302    private async Task<WrappedStream> OpenInUpdateModeAsync(bool loadExistingContent, CancellationToken cancellationToke
 0303    {
 0304        cancellationToken.ThrowIfCancellationRequested();
 0305        if (_currentlyOpenForWrite)
 0306        {
 0307            throw new IOException(SR.UpdateModeOneStream);
 308        }
 309
 0310        if (Encryption == ZipEncryptionMethod.Unknown)
 0311        {
 0312            throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 313        }
 314
 0315        if (loadExistingContent)
 0316        {
 0317            await ThrowIfNotOpenableAsync(needToUncompress: true, needToLoadIntoMemory: true, cancellationToken).Configu
 0318        }
 319
 0320        _currentlyOpenForWrite = true;
 321
 0322        if (loadExistingContent)
 0323        {
 0324            _storedUncompressedData = await GetUncompressedDataAsync(cancellationToken).ConfigureAwait(false);
 0325        }
 326        else
 0327        {
 0328            _storedUncompressedData?.Dispose();
 0329            _storedUncompressedData = new MemoryStream();
 0330            MarkAsModified();
 0331        }
 332
 0333        _storedUncompressedData.Seek(0, SeekOrigin.Begin);
 334
 0335        return new WrappedStream(_storedUncompressedData, this,
 0336            onClosed: thisRef => thisRef!._currentlyOpenForWrite = false,
 0337            notifyEntryOnWrite: true);
 0338    }
 339
 340    // Returns a read-only view over the entry's current uncompressed buffer in Update mode.
 341    // It reflects modifications made earlier in the current session (and is empty for a freshly created
 342    // entry), unlike OpenInReadModeAsync which reads the original bytes directly from the archive. The stream
 343    // shares the underlying buffer with the entry rather than copying it, so it is not isolated from later
 344    // in-place writes; callers are expected to finish reading before reopening the entry for writing.
 345    private async Task<Stream> OpenInUpdateModeForReadAsync(CancellationToken cancellationToken)
 0346    {
 0347        if (_currentlyOpenForWrite)
 0348        {
 0349            throw new IOException(SR.UpdateModeOneStream);
 350        }
 351
 0352        MemoryStream uncompressedData = await GetUncompressedDataAsync(cancellationToken).ConfigureAwait(false);
 0353        return new MemoryStream(uncompressedData.GetBuffer(), 0, (int)uncompressedData.Length, writable: false);
 0354    }
 355
 356    private async Task<MemoryStream> GetUncompressedDataAsync(CancellationToken cancellationToken)
 0357    {
 0358        cancellationToken.ThrowIfCancellationRequested();
 0359        if (_storedUncompressedData is null)
 0360        {
 361            // MemoryStream is backed by a single byte[] and cannot grow beyond Array.MaxLength.
 362            // Validate up front before attempting the (int) cast.
 0363            if ((ulong)_uncompressedSize > (ulong)Array.MaxLength)
 0364            {
 0365                _currentlyOpenForWrite = false;
 0366                throw new InvalidDataException(SR.EntryUncompressedSizeTooLargeForUpdateMode);
 367            }
 368
 369
 0370            _storedUncompressedData = new MemoryStream((int)_uncompressedSize);
 371
 0372            if (_originallyInArchive)
 0373            {
 0374                Stream decompressor = await OpenInReadModeAsync(checkOpenable: false, default, cancellationToken).Config
 375
 0376                await using (decompressor)
 0377                {
 378                    try
 0379                    {
 0380                        await decompressor.CopyToAsync(_storedUncompressedData, cancellationToken).ConfigureAwait(false)
 0381                    }
 0382                    catch (InvalidDataException)
 0383                    {
 0384                        await _storedUncompressedData.DisposeAsync().ConfigureAwait(false);
 0385                        _storedUncompressedData = null;
 0386                        _currentlyOpenForWrite = false;
 0387                        _everOpenedForWrite = false;
 0388                        _derivedZipCryptoKeyMaterial = null;
 0389                        _derivedAesKeyMaterial = null;
 0390                        throw;
 391                    }
 0392                }
 0393            }
 0394        }
 395
 0396        return _storedUncompressedData;
 0397    }
 398
 399    // does almost everything you need to do to forget about this entry
 400    // writes the local header/data, gets rid of all the data,
 401    // closes all of the streams except for the very outermost one that
 402    // the user holds on to and is responsible for closing
 403    //
 404    // after calling this, and only after calling this can we be guaranteed
 405    // that we are reading to write the central directory
 406    //
 407    // should only throw an exception in extremely exceptional cases because it is called from dispose
 408    internal async Task WriteAndFinishLocalEntryAsync(bool forceWrite, CancellationToken cancellationToken)
 86409    {
 86410        cancellationToken.ThrowIfCancellationRequested();
 86411        await CloseStreamsAsync().ConfigureAwait(false);
 86412        await WriteLocalFileHeaderAndDataIfNeededAsync(forceWrite, cancellationToken).ConfigureAwait(false);
 86413        await UnloadStreamsAsync().ConfigureAwait(false);
 86414    }
 415
 416    // should only throw an exception in extremely exceptional cases because it is called from dispose
 417    internal async Task WriteCentralDirectoryFileHeaderAsync(bool forceWrite, CancellationToken cancellationToken)
 86418    {
 86419        cancellationToken.ThrowIfCancellationRequested();
 420
 86421        if (WriteCentralDirectoryFileHeaderInitialize(forceWrite, out Zip64ExtraField? zip64ExtraField, out uint compres
 86422        {
 86423            byte[] cdStaticHeader = new byte[ZipCentralDirectoryFileHeader.BlockConstantSectionSize];
 86424            WriteCentralDirectoryFileHeaderPrepare(cdStaticHeader, compressedSizeTruncated, uncompressedSizeTruncated, e
 425
 86426            await _archive.ArchiveStream.WriteAsync(cdStaticHeader, cancellationToken).ConfigureAwait(false);
 86427            await _archive.ArchiveStream.WriteAsync(_storedEntryNameBytes, cancellationToken).ConfigureAwait(false);
 428
 429            // Write zip64ExtraField first if we decided we need it
 86430            if (zip64ExtraField != null)
 0431            {
 0432                await zip64ExtraField.WriteBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(false);
 0433            }
 434
 435            // Write WinZip AES extra field AFTER Zip64 (matching sync version order)
 436            // Must match the exact check used in the sync version WriteCentralDirectoryFileHeader
 86437            if (UseAesEncryption)
 86438            {
 86439                await CreateAesExtraField().WriteBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(fa
 440
 441                // write extra fields excluding existing AES extra field (and any malformed trailing data).
 86442                await ZipGenericExtraField.WriteAllBlocksExcludingTagAsync(_cdUnknownExtraFields, _cdTrailingExtraFieldD
 86443            }
 444            else
 0445            {
 446                // write extra fields (and any malformed trailing data).
 0447                await ZipGenericExtraField.WriteAllBlocksAsync(_cdUnknownExtraFields, _cdTrailingExtraFieldData ?? Array
 0448            }
 449
 86450            if (_fileComment.Length > 0)
 0451            {
 0452                await _archive.ArchiveStream.WriteAsync(_fileComment, cancellationToken).ConfigureAwait(false);
 0453            }
 86454        }
 86455    }
 456    internal async Task LoadLocalHeaderExtraFieldIfNeededAsync(CancellationToken cancellationToken)
 0457    {
 0458        cancellationToken.ThrowIfCancellationRequested();
 459        // we should have made this exact call in _archive.Init through ThrowIfOpenable
 0460        Debug.Assert(await GetIsOpenableAsync(false, true, cancellationToken).ConfigureAwait(false));
 461
 462        // load local header's extra fields. it will be null if we couldn't read for some reason
 0463        if (_originallyInArchive)
 0464        {
 0465            _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
 0466            (_lhUnknownExtraFields, _lhTrailingExtraFieldData) = await ZipLocalFileHeader.GetExtraFieldsAsync(_archive.A
 0467        }
 0468    }
 469
 470    internal async Task LoadCompressedBytesIfNeededAsync(CancellationToken cancellationToken)
 0471    {
 0472        cancellationToken.ThrowIfCancellationRequested();
 473        // we should have made this exact call in _archive.Init through ThrowIfOpenable
 0474        Debug.Assert(await GetIsOpenableAsync(false, true, cancellationToken).ConfigureAwait(false));
 475
 0476        if (!_everOpenedForWrite && _originallyInArchive)
 0477        {
 0478            _compressedBytes = LoadCompressedBytesIfNeededInitialize(out int maxSingleBufferSize);
 479
 0480            _archive.ArchiveStream.Seek(await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false), S
 481
 0482            for (int i = 0; i < _compressedBytes.Length - 1; i++)
 0483            {
 0484                await _archive.ArchiveStream.ReadAtLeastAsync(_compressedBytes[i], maxSingleBufferSize, throwOnEndOfStre
 0485            }
 0486            await _archive.ArchiveStream.ReadAtLeastAsync(_compressedBytes[_compressedBytes.Length - 1], (int)(_compress
 0487        }
 0488    }
 489
 490    private async Task<bool> GetIsOpenableAsync(bool needToUncompress, bool needToLoadIntoMemory, CancellationToken canc
 0491    {
 0492        cancellationToken.ThrowIfCancellationRequested();
 0493        (bool result, _) = await IsOpenableAsync(needToUncompress, needToLoadIntoMemory, cancellationToken).ConfigureAwa
 0494        return result;
 0495    }
 496
 497    internal async Task ThrowIfNotOpenableAsync(bool needToUncompress, bool needToLoadIntoMemory, CancellationToken canc
 172498    {
 172499        cancellationToken.ThrowIfCancellationRequested();
 172500        (bool openable, string? message) = await IsOpenableAsync(needToUncompress, needToLoadIntoMemory, cancellationTok
 172501        if (!openable)
 0502        {
 0503            throw new InvalidDataException(message);
 504        }
 172505    }
 506
 507    /// <summary>
 508    /// Accepts a <see cref="ReadOnlySpan{T}"/> password, derives decryption and re-encryption keys (CPU-only),
 509    /// and delegates all I/O to fully async helper methods.
 510    /// </summary>
 511    private Task<Stream> OpenInUpdateModeWithPasswordAsync(bool loadExistingContent, ReadOnlySpan<char> password, Cancel
 0512    {
 0513        if (_currentlyOpenForWrite)
 0514        {
 0515            throw new IOException(SR.UpdateModeOneStream);
 516        }
 517
 0518        if (Encryption == ZipEncryptionMethod.Unknown)
 0519        {
 0520            throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 521        }
 522
 523        // Encrypted entries always require a password for re-encryption,
 524        // even when discarding existing content (write-only access).
 0525        if (IsEncrypted && password.IsEmpty)
 0526        {
 0527            throw new ArgumentException(SR.PasswordRequired, nameof(password));
 528        }
 529
 530        // Derive re-encryption key material while the password span is still valid.
 0531        if (IsEncrypted)
 0532        {
 0533            SetupEncryptionKeyMaterial(password);
 0534        }
 535
 0536        if (loadExistingContent && IsEncrypted)
 0537        {
 0538            if (IsAesEncrypted)
 0539            {
 0540                if (OperatingSystem.IsBrowser())
 0541                {
 0542                    throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnBrowser);
 543                }
 544
 0545                if (_aesSalt is null)
 0546                {
 0547                    throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
 548                }
 549
 0550                int keySizeBits = GetAesKeySizeBits(Encryption);
 0551                WinZipAesKeyMaterial aesKeys = WinZipAesStream.CreateKey(password, _aesSalt, keySizeBits);
 0552                return DecryptAndStoreForUpdateWithAesAsync(aesKeys, cancellationToken);
 553            }
 554
 0555            if (IsZipCryptoEncrypted)
 0556            {
 0557                byte checkByte = CalculateZipCryptoCheckByte();
 0558                ZipCryptoKeys keys = ZipCryptoStream.CreateKey(password);
 0559                return DecryptAndStoreForUpdateWithZipCryptoAsync(keys, checkByte, cancellationToken);
 560            }
 561
 0562            throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 563        }
 564
 0565        return CastToStreamAsync(OpenInUpdateModeAsync(loadExistingContent, cancellationToken));
 0566    }
 567
 0568    private static async Task<Stream> CastToStreamAsync(Task<WrappedStream> task) => await task.ConfigureAwait(false);
 569
 570    private async Task<Stream> DecryptAndStoreForUpdateWithZipCryptoAsync(ZipCryptoKeys keys, byte checkByte, Cancellati
 0571    {
 0572        await ThrowIfNotOpenableAsync(needToUncompress: true, needToLoadIntoMemory: true, cancellationToken).ConfigureAw
 573
 0574        long offset = await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false);
 0575        Stream compressedStream = new SubReadStream(_archive.ArchiveStream, offset, _compressedSize);
 576
 0577        Stream decrypted = await ZipCryptoStream.CreateAsync(compressedStream, keys, checkByte, encrypting: false, cance
 578
 0579        return await StoreDecryptedDataForUpdateAsync(decrypted, cancellationToken).ConfigureAwait(false);
 0580    }
 581
 582    private async Task<Stream> DecryptAndStoreForUpdateWithAesAsync(WinZipAesKeyMaterial aesKeys, CancellationToken canc
 0583    {
 0584        if (OperatingSystem.IsBrowser())
 0585        {
 0586            throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnBrowser);
 587        }
 588
 0589        await ThrowIfNotOpenableAsync(needToUncompress: true, needToLoadIntoMemory: true, cancellationToken).ConfigureAw
 590
 0591        long offset = await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false);
 0592        Stream compressedStream = new SubReadStream(_archive.ArchiveStream, offset, _compressedSize);
 593
 0594        Stream decrypted = await WinZipAesStream.CreateAsync(
 0595            baseStream: compressedStream,
 0596            keyMaterial: aesKeys,
 0597            totalStreamSize: _compressedSize,
 0598            encrypting: false,
 0599            cancellationToken: cancellationToken).ConfigureAwait(false);
 600
 0601        return await StoreDecryptedDataForUpdateAsync(decrypted, cancellationToken).ConfigureAwait(false);
 0602    }
 603
 604    /// <summary>
 605    /// Decompresses a decrypted stream and stores the result in memory for update mode.
 606    /// </summary>
 607    private async Task<Stream> StoreDecryptedDataForUpdateAsync(Stream decryptedStream, CancellationToken cancellationTo
 0608    {
 0609        _currentlyOpenForWrite = true;
 610
 0611        if (_uncompressedSize > Array.MaxLength)
 0612        {
 0613            throw new InvalidDataException(SR.EntryTooLarge);
 614        }
 615
 0616        _storedUncompressedData = new MemoryStream((int)_uncompressedSize);
 617
 0618        Stream decompressed = BuildDecompressionPipeline(decryptedStream);
 619
 0620        await using (decompressed)
 0621        {
 622            try
 0623            {
 0624                await decompressed.CopyToAsync(_storedUncompressedData, cancellationToken).ConfigureAwait(false);
 0625            }
 0626            catch (InvalidDataException)
 0627            {
 0628                await _storedUncompressedData.DisposeAsync().ConfigureAwait(false);
 0629                _storedUncompressedData = null;
 0630                _currentlyOpenForWrite = false;
 0631                _everOpenedForWrite = false;
 0632                _derivedZipCryptoKeyMaterial = null;
 0633                _derivedAesKeyMaterial = null;
 0634                throw;
 635            }
 0636        }
 637
 0638        _storedUncompressedData.Seek(0, SeekOrigin.Begin);
 639
 0640        return new WrappedStream(_storedUncompressedData, this,
 0641            onClosed: thisRef => thisRef!._currentlyOpenForWrite = false,
 0642            notifyEntryOnWrite: true);
 0643    }
 644
 645    private async Task<(bool, string?)> IsOpenableAsync(bool needToUncompress, bool needToLoadIntoMemory, CancellationTo
 172646    {
 172647        cancellationToken.ThrowIfCancellationRequested();
 172648        string? message = null;
 649
 172650        if (!_originallyInArchive)
 0651        {
 0652            return (true, message);
 653        }
 654
 172655        if (!IsOpenableInitialVerifications(needToUncompress, out message))
 0656        {
 0657            return (false, message);
 658        }
 659
 172660        if (!IsEncrypted && !await ZipLocalFileHeader.TrySkipBlockAsync(_archive.ArchiveStream, cancellationToken).Confi
 0661        {
 0662            message = SR.LocalFileHeaderCorrupt;
 0663            return (false, message);
 664        }
 172665        else if (IsEncrypted && (ushort)_headerCompressionMethod == WinZipAesMethod)
 172666        {
 172667            _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
 668            // AES case - skip the local file header and validate it exists.
 669            // The AES metadata (encryption strength, actual compression method) was already
 670            // parsed from the central directory in the constructor.
 172671            if (!await ZipLocalFileHeader.TrySkipBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(fa
 0672            {
 0673                message = SR.LocalFileHeaderCorrupt;
 0674                return (false, message);
 675            }
 172676        }
 677
 678        // when this property gets called, some duplicated work
 172679        long offsetOfCompressedData = await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false);
 172680        if (!IsOpenableFinalVerifications(needToLoadIntoMemory, offsetOfCompressedData, out message))
 0681        {
 0682            return (false, message);
 683        }
 684
 172685        return (true, message);
 172686    }
 687
 688    // return value is true if we allocated an extra field for 64 bit headers, un/compressed size
 689    private async Task<bool> WriteLocalFileHeaderAsync(bool isEmptyFile, bool forceWrite, bool preserveDataDescriptor, C
 86690    {
 86691        cancellationToken.ThrowIfCancellationRequested();
 692
 86693        if (WriteLocalFileHeaderInitialize(isEmptyFile, forceWrite, preserveDataDescriptor, out Zip64ExtraField? zip64Ex
 86694        {
 86695            byte[] lfStaticHeader = new byte[ZipLocalFileHeader.SizeOfLocalHeader];
 86696            WriteLocalFileHeaderPrepare(lfStaticHeader, crc32ToWrite, compressedSizeTruncated, uncompressedSizeTruncated
 697
 698            // write header
 86699            await _archive.ArchiveStream.WriteAsync(lfStaticHeader, cancellationToken).ConfigureAwait(false);
 86700            await _archive.ArchiveStream.WriteAsync(_storedEntryNameBytes, cancellationToken).ConfigureAwait(false);
 701
 702            // Only when handling zip64
 86703            if (zip64ExtraField != null)
 0704            {
 0705                await zip64ExtraField.WriteBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(false);
 0706            }
 707
 708            // Write WinZip AES extra field if using AES encryption
 709            // Must match the exact check used in the sync version WriteLocalFileHeader
 86710            if (UseAesEncryption)
 86711            {
 86712                await CreateAesExtraField().WriteBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(fa
 713
 714                // Write other extra fields, excluding any existing AES extra field to avoid duplication
 86715                await ZipGenericExtraField.WriteAllBlocksExcludingTagAsync(_lhUnknownExtraFields, _lhTrailingExtraFieldD
 86716            }
 717            else
 0718            {
 0719                await ZipGenericExtraField.WriteAllBlocksAsync(_lhUnknownExtraFields, _lhTrailingExtraFieldData ?? Array
 0720            }
 86721        }
 722
 86723        return zip64ExtraField != null;
 86724    }
 725
 726    private async Task WriteLocalFileHeaderAndDataIfNeededAsync(bool forceWrite, CancellationToken cancellationToken)
 86727    {
 86728        cancellationToken.ThrowIfCancellationRequested();
 729
 730        // Check if the entry's stored data was actually modified (StoredData flag is set).
 731        // If _storedUncompressedData is loaded but StoredData is not set, it means the entry
 732        // was opened for update but no writes occurred - we should use the original compressed bytes.
 86733        bool storedDataModified = (Changes & ZipArchive.ChangeState.StoredData) != 0;
 734
 735        // If _storedUncompressedData is loaded but not modified, clear it so we use _compressedBytes
 86736        if (_storedUncompressedData != null && !storedDataModified)
 0737        {
 0738            await _storedUncompressedData.DisposeAsync().ConfigureAwait(false);
 0739            _storedUncompressedData = null;
 0740        }
 741
 742        // _storedUncompressedData gets frozen here, and is what gets written to the file
 86743        if (_storedUncompressedData != null || _compressedBytes != null)
 0744        {
 0745            if (_storedUncompressedData != null)
 0746            {
 0747                _uncompressedSize = _storedUncompressedData.Length;
 748
 749                // Check if we need to re-encrypt with ZipCrypto (only if we have cached key material)
 0750                if (Encryption == ZipEncryptionMethod.ZipCrypto && _derivedZipCryptoKeyMaterial != null)
 0751                {
 752                    // Write local file header first (with encryption flag set)
 753                    // Pass isEmptyFile: false because even empty encrypted files have the 12-byte header
 0754                    await WriteLocalFileHeaderAsync(isEmptyFile: false, forceWrite: true, preserveDataDescriptor: false,
 755
 756                    // Record position before encryption data
 0757                    long startPosition = _archive.ArchiveStream.Position;
 758
 0759                    ushort verifierLow2Bytes = (ushort)ZipHelper.DateTimeToDosTime(_lastModified.DateTime);
 760
 0761                    ZipCryptoStream encryptionStream = ZipCryptoStream.Create(
 0762                        baseStream: _archive.ArchiveStream,
 0763                        keys: _derivedZipCryptoKeyMaterial.Value,
 0764                        passwordVerifierLow2Bytes: verifierLow2Bytes,
 0765                        encrypting: true,
 0766                        crc32: null,
 0767                        leaveOpen: true);
 0768                    await using (encryptionStream.ConfigureAwait(false))
 0769                    {
 770                        // Use GetDataCompressor which handles CRC calculation and compression
 0771                        CheckSumAndSizeWriteStream crcStream = GetDataCompressor(encryptionStream, leaveBackingStreamOpe
 0772                        await using (crcStream.ConfigureAwait(false))
 0773                        {
 0774                            _storedUncompressedData.Seek(0, SeekOrigin.Begin);
 0775                            await _storedUncompressedData.CopyToAsync(crcStream, cancellationToken).ConfigureAwait(false
 0776                        }
 777                        // CRC, uncompressed size are now set by GetDataCompressor callback
 778                        // For empty files, ZipCryptoStream.Dispose() will write the 12-byte header
 0779                    }
 780
 781                    // Calculate compressed size AFTER ZipCryptoStream is disposed
 782                    // (includes 12-byte encryption header + compressed data)
 0783                    _compressedSize = _archive.ArchiveStream.Position - startPosition;
 784
 785                    // Write data descriptor since we used streaming mode
 0786                    await WriteDataDescriptorAsync(cancellationToken).ConfigureAwait(false);
 787
 0788                    await _storedUncompressedData.DisposeAsync().ConfigureAwait(false);
 0789                    _storedUncompressedData = null;
 0790                }
 0791                else if (UseAesEncryption && _derivedAesKeyMaterial != null)
 0792                {
 793
 0794                    if (OperatingSystem.IsBrowser())
 0795                    {
 0796                        throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnBrowser);
 797                    }
 798                    // For AES, we need to:
 799                    // 1. Write header with CompressionMethod = Aes (99)
 800                    // 2. Compress data with actual compression (Deflate/Stored)
 801                    // 3. Keep CompressionMethod = Aes for central directory
 802
 803                    // WriteLocalFileHeaderAsync will set CompressionMethod = Aes
 0804                    bool usedZip64InLH = await WriteLocalFileHeaderAsync(isEmptyFile: false, forceWrite: true, preserveD
 805
 806                    // Record position before encryption data
 0807                    long startPosition = _archive.ArchiveStream.Position;
 808
 0809                    int keySizeBits = GetAesKeySizeBits(Encryption);
 810
 811                    // Determine the actual compression method to use
 812                    // The AES extra field stores the real compression method
 0813                    bool useDeflate = _compressionLevel != CompressionLevel.NoCompression;
 814
 0815                    WinZipAesStream encryptionStream = WinZipAesStream.Create(
 0816                        baseStream: _archive.ArchiveStream,
 0817                        keyMaterial: _derivedAesKeyMaterial.Value,
 0818                        totalStreamSize: -1,
 0819                        encrypting: true,
 0820                        leaveOpen: true);
 821
 0822                    await using (encryptionStream.ConfigureAwait(false))
 0823                    {
 824                        // Only compress/write if there's data
 0825                        if (_storedUncompressedData.Length > 0)
 0826                        {
 827                            // Temporarily set CompressionMethod for GetDataCompressor
 0828                            ZipCompressionMethod savedMethod = CompressionMethod;
 0829                            CompressionMethod = useDeflate ? ZipCompressionMethod.Deflate : ZipCompressionMethod.Stored;
 830
 831                            try
 0832                            {
 0833                                CheckSumAndSizeWriteStream crcStream = GetDataCompressor(encryptionStream, leaveBackingS
 0834                                await using (crcStream.ConfigureAwait(false))
 0835                                {
 0836                                    _storedUncompressedData.Seek(0, SeekOrigin.Begin);
 0837                                    await _storedUncompressedData.CopyToAsync(crcStream, cancellationToken).ConfigureAwa
 0838                                }
 0839                            }
 840                            finally
 0841                            {
 842                                // Restore CompressionMethod - AesCompressionMethodValue is used directly when writing h
 0843                                CompressionMethod = savedMethod;
 0844                            }
 0845                        }
 846                        else
 0847                        {
 848                            // Empty file: CRC is 0, uncompressed size is 0
 0849                            _crc32 = 0;
 0850                            _uncompressedSize = 0;
 0851                        }
 852                        // WinZipAesStream.Dispose() writes salt + verifier + HMAC even for empty files
 0853                    }
 854
 855                    // Calculate compressed size AFTER WinZipAesStream is disposed
 856                    // (includes salt + password verifier + encrypted data + HMAC)
 0857                    _compressedSize = _archive.ArchiveStream.Position - startPosition;
 858
 859                    // Patch CRC and sizes back into the local header
 0860                    await WriteCrcAndSizesInLocalHeaderAsync(usedZip64InLH, cancellationToken).ConfigureAwait(false);
 861
 0862                    await _storedUncompressedData.DisposeAsync().ConfigureAwait(false);
 0863                    _storedUncompressedData = null;
 0864                }
 865                else
 0866                {
 867                    // Non-encrypted: use standard path
 868                    //The compressor fills in CRC and sizes
 869                    //The DirectToArchiveWriterStream writes headers and such
 0870                    DirectToArchiveWriterStream entryWriter = new(GetDataCompressor(_archive.ArchiveStream, true, null, 
 0871                    await using (entryWriter.ConfigureAwait(false))
 0872                    {
 0873                        _storedUncompressedData.Seek(0, SeekOrigin.Begin);
 0874                        await _storedUncompressedData.CopyToAsync(entryWriter, cancellationToken).ConfigureAwait(false);
 0875                    }
 0876                    await _storedUncompressedData.DisposeAsync().ConfigureAwait(false);
 0877                    _storedUncompressedData = null;
 0878                }
 0879            }
 880            else // _compressedBytes path - copying unchanged entry data
 0881            {
 0882                if (_uncompressedSize == 0)
 0883                {
 884                    // reset size to ensure proper central directory size header
 0885                    _compressedSize = 0;
 0886                }
 887
 888                // For unchanged entries, we need to write the header correctly but avoid
 889                // WriteLocalFileHeaderAsync creating NEW encryption structures (which would have
 890                // wrong compression method from _compressionLevel).
 891                // The original AES extra field is preserved in _lhUnknownExtraFields.
 0892                BitFlagValues savedFlags = _generalPurposeBitFlag;
 0893                ZipEncryptionMethod savedEncryption = Encryption;
 0894                ZipCompressionMethod savedCompressionMethod = CompressionMethod;
 895
 896                try
 0897                {
 898                    // For AES entries: set CompressionMethod to Aes so header writes method 99,
 899                    // but clear _encryptionMethod so WriteLocalFileHeaderAsync doesn't create a new
 900                    // AES extra field (the original one in _lhUnknownExtraFields will be used).
 0901                    if (savedEncryption is ZipEncryptionMethod.Aes128 or ZipEncryptionMethod.Aes192 or ZipEncryptionMeth
 0902                    {
 0903                        CompressionMethod = (ZipCompressionMethod)WinZipAesMethod;
 0904                        Encryption = ZipEncryptionMethod.None;
 0905                    }
 906
 0907                    await WriteLocalFileHeaderAsync(isEmptyFile: _uncompressedSize == 0, forceWrite: true, preserveDataD
 908
 909                    // WriteLocalFileHeaderInitialize may have cleared the DataDescriptor flag
 910                    // (because Encryption was temporarily set to None and the stream is seekable).
 911                    // If the original entry had a data descriptor, patch the general-purpose bit
 912                    // flags in the already-written local header to match, so the header on disk
 913                    // is consistent with the data descriptor we conditionally write below.
 0914                    if ((savedFlags & BitFlagValues.DataDescriptor) != 0 &&
 0915                        (_generalPurposeBitFlag & BitFlagValues.DataDescriptor) == 0)
 0916                    {
 0917                        long currentPos = _archive.ArchiveStream.Position;
 0918                        _archive.ArchiveStream.Seek(
 0919                            _offsetOfLocalHeader + ZipLocalFileHeader.FieldLocations.GeneralPurposeBitFlags,
 0920                            SeekOrigin.Begin);
 0921                        byte[] flagBytes = new byte[2];
 0922                        BinaryPrimitives.WriteUInt16LittleEndian(flagBytes, (ushort)savedFlags);
 0923                        await _archive.ArchiveStream.WriteAsync(flagBytes, cancellationToken).ConfigureAwait(false);
 0924                        _archive.ArchiveStream.Seek(currentPos, SeekOrigin.Begin);
 0925                    }
 0926                }
 927                finally
 0928                {
 929                    // Restore original state
 0930                    _generalPurposeBitFlag = savedFlags;
 0931                    Encryption = savedEncryption;
 0932                    CompressionMethod = savedCompressionMethod;
 0933                }
 934
 935                // according to ZIP specs, zero-byte files MUST NOT include file data
 0936                if (_uncompressedSize != 0)
 0937                {
 0938                    Debug.Assert(_compressedBytes != null);
 0939                    foreach (byte[] compressedBytes in _compressedBytes)
 0940                    {
 0941                        await _archive.ArchiveStream.WriteAsync(compressedBytes, cancellationToken).ConfigureAwait(false
 0942                    }
 0943                }
 944
 945                // Write data descriptor if the original entry had one
 0946                if ((savedFlags & BitFlagValues.DataDescriptor) != 0)
 0947                {
 0948                    await WriteDataDescriptorAsync(cancellationToken).ConfigureAwait(false);
 0949                }
 0950            }
 0951        }
 952        else // there is no data in the file (or the data in the file has not been loaded), but if we are in update mode
 86953        {
 86954            if (_archive.Mode == ZipArchiveMode.Update || !_everOpenedForWrite)
 0955            {
 0956                _everOpenedForWrite = true;
 957                // Preserve the data descriptor flag for entries that originally had one,
 958                // since the descriptor bytes remain on disk after the compressed data.
 0959                bool preserveDataDescriptor = _originallyInArchive
 0960                    && (_generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0;
 0961                await WriteLocalFileHeaderAsync(isEmptyFile: _uncompressedSize == 0, forceWrite: forceWrite, preserveDat
 962
 963                // Advance the stream past the compressed data and any trailing data descriptor
 964                // by seeking to the pre-computed end-of-entry boundary.
 0965                if (_endOfLocalEntryData > _archive.ArchiveStream.Position)
 0966                {
 0967                    _archive.ArchiveStream.Seek(_endOfLocalEntryData, SeekOrigin.Begin);
 0968                }
 0969            }
 86970        }
 86971    }
 972
 973    // Using _offsetOfLocalHeader, seeks back to where CRC and sizes should be in the header,
 974    // writes them, then seeks back to where you started
 975    // Assumes that the stream is currently at the end of the data
 976    private async Task WriteCrcAndSizesInLocalHeaderAsync(bool zip64HeaderUsed, CancellationToken cancellationToken)
 86977    {
 86978        cancellationToken.ThrowIfCancellationRequested();
 979        // Buffer has been sized to the largest data payload required: the 64-bit data descriptor.
 86980        byte[] writeBuffer = new byte[Zip64DataDescriptorCrcAndSizesBufferLength];
 981
 86982        WriteCrcAndSizesInLocalHeaderInitialize(zip64HeaderUsed, out long finalPosition, out bool pretendStreaming, out 
 983
 984        // first step is, if we need zip64, but didn't allocate it, pretend we did a stream write, because
 985        // we can't go back and give ourselves the space that the extra field needs.
 986        // we do this by setting the correct property in the bit flag to indicate we have a data descriptor
 987        // and setting the version to Zip64 to indicate that descriptor contains 64-bit values
 86988        if (pretendStreaming)
 0989        {
 0990            WriteCrcAndSizesInLocalHeaderPrepareForZip64PretendStreaming(writeBuffer);
 0991            await _archive.ArchiveStream.WriteAsync(writeBuffer.AsMemory(0, MetadataBufferLength), cancellationToken).Co
 0992        }
 993
 994        // next step is fill out the 32-bit size values in the normal header. we can't assume that
 995        // they are correct. we also write the CRC
 86996        WriteCrcAndSizesInLocalHeaderPrepareFor32bitValuesWriting(pretendStreaming, writeBuffer, compressedSizeTruncated
 86997        await _archive.ArchiveStream.WriteAsync(writeBuffer.AsMemory(0, CrcAndSizesBufferLength), cancellationToken).Con
 998
 999        // next step: if we wrote the 64 bit header initially, a different implementation might
 1000        // try to read it, even if the 32-bit size values aren't masked. thus, we should always put the
 1001        // correct size information in there. note that order of uncomp/comp is switched, and these are
 1002        // 64-bit values
 1003        // also, note that in order for this to be correct, we have to ensure that the zip64 extra field
 1004        // is always the first extra field that is written
 861005        if (zip64HeaderUsed)
 01006        {
 01007            WriteCrcAndSizesInLocalHeaderPrepareForWritingWhenZip64HeaderUsed(writeBuffer);
 01008            await _archive.ArchiveStream.WriteAsync(writeBuffer.AsMemory(0, Zip64SizesBufferLength), cancellationToken).
 01009        }
 1010
 1011        // now go to the where we were. assume that this is the end of the data
 861012        _archive.ArchiveStream.Seek(finalPosition, SeekOrigin.Begin);
 1013
 1014        // if we are pretending we did a stream write, we want to write the data descriptor out
 1015        // the data descriptor can have 32-bit sizes or 64-bit sizes. In this case, we always use
 1016        // 64-bit sizes
 861017        if (pretendStreaming)
 01018        {
 01019            WriteCrcAndSizesInLocalHeaderPrepareForWritingDataDescriptor(writeBuffer);
 01020            await _archive.ArchiveStream.WriteAsync(writeBuffer.AsMemory(0, Zip64DataDescriptorCrcAndSizesBufferLength),
 01021        }
 861022    }
 1023
 1024    private ValueTask WriteDataDescriptorAsync(CancellationToken cancellationToken)
 01025    {
 01026        cancellationToken.ThrowIfCancellationRequested();
 01027        byte[] dataDescriptor = new byte[MaxSizeOfDataDescriptor];
 01028        int bytesToWrite = PrepareToWriteDataDescriptor(dataDescriptor);
 01029        return _archive.ArchiveStream.WriteAsync(dataDescriptor.AsMemory(0, bytesToWrite), cancellationToken);
 01030    }
 1031
 1032    internal async Task UnloadStreamsAsync()
 861033    {
 861034        if (_storedUncompressedData != null)
 01035        {
 01036            await _storedUncompressedData.DisposeAsync().ConfigureAwait(false);
 01037        }
 861038        _compressedBytes = null;
 861039        _outstandingWriteStream = null;
 861040    }
 1041
 1042    private async Task CloseStreamsAsync()
 861043    {
 1044        // if the user left the stream open, close the underlying stream for them
 861045        if (_outstandingWriteStream != null)
 01046        {
 01047            await _outstandingWriteStream.DisposeAsync().ConfigureAwait(false);
 01048        }
 861049    }
 1050}

D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\ZipArchiveEntry.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers.Binary;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.Diagnostics.CodeAnalysis;
 8using System.Text;
 9using System.Threading;
 10using System.Threading.Tasks;
 11
 12using static System.IO.Compression.ZipArchiveEntryConstants;
 13
 14namespace System.IO.Compression
 15{
 16    // The disposable fields that this class owns get disposed when the ZipArchive it belongs to gets disposed
 17    public partial class ZipArchiveEntry
 18    {
 19        private ZipArchive _archive;
 20        private readonly bool _originallyInArchive;
 21        private readonly uint _diskNumberStart;
 22        private readonly ZipVersionMadeByPlatform _versionMadeByPlatform;
 23        private ZipVersionNeededValues _versionMadeBySpecification;
 24        private ZipVersionNeededValues _versionToExtract;
 25        private BitFlagValues _generalPurposeBitFlag;
 26        private ZipCompressionMethod _storedCompressionMethod;
 27        private DateTimeOffset _lastModified;
 28        private long _compressedSize;
 29        private long _uncompressedSize;
 30        private long _offsetOfLocalHeader;
 31        private long? _storedOffsetOfCompressedData;
 32        private long _endOfLocalEntryData;
 33        private uint _crc32;
 34        // An array of buffers, each a maximum of MaxSingleBufferSize in size
 35        private byte[][]? _compressedBytes;
 36        private MemoryStream? _storedUncompressedData;
 37        private bool _currentlyOpenForWrite;
 38        private bool _everOpenedForWrite;
 39        private Stream? _outstandingWriteStream;
 40        private uint _externalFileAttr;
 41        private string _storedEntryName;
 42        private byte[] _storedEntryNameBytes;
 43        // only apply to update mode
 44        private List<ZipGenericExtraField>? _cdUnknownExtraFields;
 45        private byte[]? _cdTrailingExtraFieldData;
 46        private List<ZipGenericExtraField>? _lhUnknownExtraFields;
 47        private byte[]? _lhTrailingExtraFieldData;
 48        private byte[] _fileComment;
 49        private ZipEncryptionMethod _encryptionMethod;
 50        private readonly CompressionLevel _compressionLevel;
 51        private ZipCompressionMethod _headerCompressionMethod;
 52        private ushort? _aeVersion;
 53        // Cached derived key material for encrypted entries to allow updating in place.
 54        // Only one of these is set at a time, depending on the encryption method.
 55        private ZipCryptoKeys? _derivedZipCryptoKeyMaterial;
 56        private WinZipAesKeyMaterial? _derivedAesKeyMaterial;
 57        // Pre-read AES salt from the local file data area during central directory parsing.
 58        // This allows async open methods to derive keys without synchronous I/O.
 59        private byte[]? _aesSalt;
 60        internal const ushort WinZipAesMethod = 99;
 61        // Initializes a ZipArchiveEntry instance for an existing archive entry.
 17262        internal ZipArchiveEntry(ZipArchive archive, ZipCentralDirectoryFileHeader cd)
 17263        {
 17264            _archive = archive;
 65
 17266            _originallyInArchive = true;
 67            // It's possible for the CompressionMethod setter and DetectEntryNameVersion to update this, even without an
 68            // changes. This can occur if a ZipArchive instance runs in Update mode and opens a stream with invalid data
 69            // a situation, both the local file header and the central directory header will be rewritten (to prevent th
 70            // from falling out of sync when the central directory header is rewritten.)
 17271            Changes = ZipArchive.ChangeState.Unchanged;
 72
 17273            _diskNumberStart = cd.DiskNumberStart;
 17274            _versionMadeByPlatform = (ZipVersionMadeByPlatform)cd.VersionMadeByCompatibility;
 17275            _versionMadeBySpecification = (ZipVersionNeededValues)cd.VersionMadeBySpecification;
 17276            _versionToExtract = (ZipVersionNeededValues)cd.VersionNeededToExtract;
 17277            _generalPurposeBitFlag = (BitFlagValues)cd.GeneralPurposeBitFlag;
 78            // Initialize _headerCompressionMethod from the central directory.
 79            // For AES entries, this will be 99 (WinZip AES wrapper indicator) and never changes.
 17280            _headerCompressionMethod = (ZipCompressionMethod)cd.CompressionMethod;
 81            // For AES-encrypted entries, the real compression method is stored in the AES extra field (0x9901)
 82            // Parse it now so that people can see the actual value before opening the entry.
 17283            if (IsEncrypted && cd.AesExtraField.HasValue)
 17284            {
 17285                WinZipAesExtraField aesField = cd.AesExtraField.Value;
 86                // Set the real compression method from the AES extra field
 17287                CompressionMethod = (ZipCompressionMethod)aesField.CompressionMethod;
 88
 89                // Also parse remaining needed metadata now
 17290                _aeVersion = aesField.VendorVersion;
 17291                Encryption = aesField.AesStrength switch
 17292                {
 1093                    1 => ZipEncryptionMethod.Aes128,
 2094                    2 => ZipEncryptionMethod.Aes192,
 14295                    3 => ZipEncryptionMethod.Aes256,
 096                    _ => throw new InvalidDataException(SR.InvalidAesStrength)
 17297                };
 17298            }
 099            else if (IsEncrypted)
 0100            {
 0101                if ((_generalPurposeBitFlag & BitFlagValues.StrongEncryption) != 0)
 0102                {
 0103                    Encryption = ZipEncryptionMethod.Unknown;
 0104                }
 105                else
 0106                {
 107                    // Encrypted but no AES extra field means ZipCrypto
 0108                    Encryption = ZipEncryptionMethod.ZipCrypto;
 0109                }
 0110                CompressionMethod = (ZipCompressionMethod)cd.CompressionMethod;
 0111            }
 112            else
 0113            {
 114                // Non-AES entry: compression method from CD is the real method
 0115                CompressionMethod = (ZipCompressionMethod)cd.CompressionMethod;
 0116            }
 117
 172118            _lastModified = new DateTimeOffset(ZipHelper.DosTimeToDateTime(cd.LastModified));
 172119            _compressedSize = cd.CompressedSize;
 172120            _uncompressedSize = cd.UncompressedSize;
 172121            _externalFileAttr = cd.ExternalFileAttributes;
 172122            _offsetOfLocalHeader = cd.RelativeOffsetOfLocalHeader;
 123            // we don't know this yet: should be _offsetOfLocalHeader + 30 + _storedEntryNameBytes.Length + extrafieldle
 124            // but entryname/extra length could be different in LH
 172125            _storedOffsetOfCompressedData = null;
 172126            _crc32 = cd.Crc32;
 127
 172128            _compressedBytes = null;
 172129            _storedUncompressedData = null;
 172130            _currentlyOpenForWrite = false;
 172131            _everOpenedForWrite = false;
 172132            _outstandingWriteStream = null;
 133
 172134            _storedEntryNameBytes = cd.Filename;
 172135            _storedEntryName = DecodeEntryString(_storedEntryNameBytes);
 172136            DetectEntryNameVersion();
 137
 172138            _lhUnknownExtraFields = null;
 139            // the cd should have this as null if we aren't in Update mode
 172140            _cdUnknownExtraFields = cd.ExtraFields;
 172141            _cdTrailingExtraFieldData = cd.TrailingExtraFieldData;
 142
 172143            _fileComment = cd.FileComment;
 144
 172145            _compressionLevel = MapCompressionLevel(_generalPurposeBitFlag, CompressionMethod);
 172146        }
 147        // Initializes a ZipArchiveEntry instance for a new archive entry with a specified compression level.
 148        internal ZipArchiveEntry(ZipArchive archive, string entryName, CompressionLevel compressionLevel)
 0149            : this(archive, entryName)
 0150        {
 0151            _compressionLevel = compressionLevel;
 0152            if (_compressionLevel == CompressionLevel.NoCompression)
 0153            {
 0154                CompressionMethod = ZipCompressionMethod.Stored;
 0155            }
 0156            _generalPurposeBitFlag = MapDeflateCompressionOption(_generalPurposeBitFlag, _compressionLevel, CompressionM
 0157        }
 158
 159        // Initializes a ZipArchiveEntry instance for a new archive entry.
 172160        internal ZipArchiveEntry(ZipArchive archive, string entryName)
 172161        {
 172162            _archive = archive;
 163
 172164            _originallyInArchive = false;
 165
 172166            _diskNumberStart = 0;
 172167            _versionMadeByPlatform = CurrentZipPlatform;
 172168            _versionMadeBySpecification = ZipVersionNeededValues.Default;
 172169            _versionToExtract = ZipVersionNeededValues.Default; // this must happen before following two assignment
 172170            _compressionLevel = CompressionLevel.Optimal;
 172171            CompressionMethod = ZipCompressionMethod.Deflate;
 172172            _generalPurposeBitFlag = MapDeflateCompressionOption(0, _compressionLevel, CompressionMethod);
 172173            _lastModified = DateTimeOffset.Now;
 174
 172175            _compressedSize = 0; // we don't know these yet
 172176            _uncompressedSize = 0;
 172177            _externalFileAttr = entryName.EndsWith(Path.DirectorySeparatorChar) || entryName.EndsWith(Path.AltDirectoryS
 172178                                        ? DefaultDirectoryExternalAttributes
 172179                                        : DefaultFileExternalAttributes;
 180
 172181            _offsetOfLocalHeader = 0;
 172182            _storedOffsetOfCompressedData = null;
 172183            _crc32 = 0;
 184
 172185            _compressedBytes = null;
 172186            _storedUncompressedData = null;
 172187            _currentlyOpenForWrite = false;
 172188            _everOpenedForWrite = false;
 172189            _outstandingWriteStream = null;
 190
 172191            FullName = entryName;
 192
 172193            _cdUnknownExtraFields = null;
 172194            _lhUnknownExtraFields = null;
 195
 172196            _fileComment = Array.Empty<byte>();
 197
 172198            if (_storedEntryNameBytes.Length > ushort.MaxValue)
 0199            {
 0200                throw new ArgumentException(SR.EntryNamesTooLong);
 201            }
 202
 203            // grab the stream if we're in create mode
 172204            if (_archive.Mode == ZipArchiveMode.Create)
 172205            {
 172206                _archive.AcquireArchiveStream(this);
 172207            }
 208
 172209            Changes = ZipArchive.ChangeState.Unchanged;
 172210        }
 211
 212        /// <summary>
 213        /// The ZipArchive that this entry belongs to. If this entry has been deleted, this will return null.
 214        /// </summary>
 0215        public ZipArchive Archive => _archive;
 216
 217        [CLSCompliant(false)]
 0218        public uint Crc32 => _crc32;
 219
 220        /// <summary>
 221        /// Gets a value that indicates whether the entry is encrypted.
 222        /// </summary>
 2150223        public bool IsEncrypted => (_generalPurposeBitFlag & BitFlagValues.IsEncrypted) != 0;
 224
 225        /// <summary>
 226        /// Gets the encryption method used to encrypt the entry.
 227        /// </summary>
 228        /// <value>
 229        /// <see cref="ZipEncryptionMethod.None"/> if the entry is not encrypted;
 230        /// <see cref="ZipEncryptionMethod.Unknown"/> if the entry uses an unsupported encryption method;
 231        /// otherwise, the specific encryption method (e.g., <see cref="ZipEncryptionMethod.ZipCrypto"/>,
 232        /// <see cref="ZipEncryptionMethod.Aes128"/>, <see cref="ZipEncryptionMethod.Aes192"/>,
 233        /// or <see cref="ZipEncryptionMethod.Aes256"/>).
 234        /// </value>
 172235        public ZipEncryptionMethod EncryptionMethod => _encryptionMethod;
 236
 237        /// <summary>
 238        /// Gets the compression method used to compress the entry.
 239        /// </summary>
 240        public ZipCompressionMethod CompressionMethod
 241        {
 1548242            get => _storedCompressionMethod;
 243            private set
 516244            {
 516245                if (value == ZipCompressionMethod.Deflate)
 344246                {
 344247                    VersionToExtractAtLeast(ZipVersionNeededValues.Deflate);
 344248                }
 172249                else if (value == ZipCompressionMethod.Deflate64)
 0250                {
 0251                    VersionToExtractAtLeast(ZipVersionNeededValues.Deflate64);
 0252                }
 516253                _storedCompressionMethod = value;
 516254            }
 255        }
 256
 257        /// <summary>
 258        /// The compressed size of the entry. If the archive that the entry belongs to is in Create mode, attempts to ge
 259        /// </summary>
 260        /// <exception cref="InvalidOperationException">This property is not available because the entry has been writte
 261        public long CompressedLength
 262        {
 263            get
 0264            {
 0265                if (_everOpenedForWrite)
 0266                {
 0267                    throw new InvalidOperationException(SR.LengthAfterWrite);
 268                }
 0269                return _compressedSize;
 0270            }
 271        }
 272
 273        public int ExternalAttributes
 274        {
 275            get
 0276            {
 0277                return (int)_externalFileAttr;
 0278            }
 279            set
 0280            {
 0281                ThrowIfInvalidArchive();
 0282                _externalFileAttr = (uint)value;
 0283                Changes |= ZipArchive.ChangeState.FixedLengthMetadata;
 0284            }
 285        }
 286
 287        /// <summary>
 288        /// Gets or sets the optional entry comment.
 289        /// </summary>
 290        /// <remarks>
 291        ///The comment encoding is determined by the <c>entryNameEncoding</c> parameter of the <see cref="ZipArchive(Str
 292        /// If the comment byte length is larger than <see cref="ushort.MaxValue"/>, it will be truncated when disposing
 293        /// </remarks>
 294        [AllowNull]
 295        public string Comment
 296        {
 0297            get => DecodeEntryString(_fileComment);
 298            set
 0299            {
 0300                _fileComment = ZipHelper.GetEncodedTruncatedBytesFromString(value, _archive.EntryNameAndCommentEncoding,
 301
 0302                if (isUTF8)
 0303                {
 0304                    _generalPurposeBitFlag |= BitFlagValues.UnicodeFileNameAndComment;
 0305                }
 0306                Changes |= ZipArchive.ChangeState.DynamicLengthMetadata;
 0307            }
 308        }
 309
 310        /// <summary>
 311        /// The relative path of the entry as stored in the Zip archive. Note that Zip archives allow any string to be t
 312        /// </summary>
 313        public string FullName
 314        {
 315            get
 344316            {
 344317                return _storedEntryName;
 344318            }
 319
 320            [MemberNotNull(nameof(_storedEntryNameBytes))]
 321            [MemberNotNull(nameof(_storedEntryName))]
 322            private set
 172323            {
 172324                ArgumentNullException.ThrowIfNull(value, nameof(FullName));
 325
 172326                _storedEntryNameBytes = ZipHelper.GetEncodedTruncatedBytesFromString(
 172327                    value, _archive.EntryNameAndCommentEncoding, 0 /* No truncation */, out bool isUTF8);
 328
 172329                _storedEntryName = value;
 330
 172331                if (isUTF8)
 0332                {
 0333                    _generalPurposeBitFlag |= BitFlagValues.UnicodeFileNameAndComment;
 0334                }
 335                else
 172336                {
 172337                    _generalPurposeBitFlag &= ~BitFlagValues.UnicodeFileNameAndComment;
 172338                }
 339
 172340                DetectEntryNameVersion();
 172341            }
 342        }
 343
 344        /// <summary>
 345        /// Returns the relative path of the entry in the Zip archive, equivalent to <see cref="FullName"/>.
 346        /// </summary>
 0347        public override string ToString() => FullName;
 348
 349        /// <summary>
 350        /// The last write time of the entry as stored in the Zip archive. When setting this property, the DateTime will
 351        /// Zip timestamp format, which supports a resolution of two seconds. If the data in the last write time field i
 352        /// an indicator value of 1980 January 1 at midnight will be returned.
 353        /// </summary>
 354        /// <exception cref="NotSupportedException">An attempt to set this property was made, but the ZipArchive that th
 355        /// opened in read-only mode.</exception>
 356        /// <exception cref="ArgumentOutOfRangeException">An attempt was made to set this property to a value that canno
 357        /// Zip timestamp format. The earliest date/time that can be represented is 1980 January 1 0:00:00 (midnight), a
 358        /// that can be represented is 2107 December 31 23:59:58 (one second before midnight).</exception>
 359        public DateTimeOffset LastWriteTime
 360        {
 361            get
 0362            {
 0363                return _lastModified;
 0364            }
 365            set
 0366            {
 0367                ThrowIfInvalidArchive();
 0368                if (_archive.Mode == ZipArchiveMode.Read)
 0369                {
 0370                    throw new NotSupportedException(SR.ReadOnlyArchive);
 371                }
 0372                if (_archive.Mode == ZipArchiveMode.Create && _everOpenedForWrite)
 0373                {
 0374                    throw new IOException(SR.FrozenAfterWrite);
 375                }
 0376                if (value.DateTime.Year < ZipHelper.ValidZipDate_YearMin || value.DateTime.Year > ZipHelper.ValidZipDate
 0377                {
 0378                    throw new ArgumentOutOfRangeException(nameof(value), SR.DateTimeOutOfRange);
 379                }
 380
 0381                _lastModified = value;
 0382                Changes |= ZipArchive.ChangeState.FixedLengthMetadata;
 0383            }
 384        }
 385
 386        /// <summary>
 387        /// The uncompressed size of the entry. This property is not valid in Create mode, and it is only valid in Updat
 388        /// </summary>
 389        /// <exception cref="InvalidOperationException">This property is not available because the entry has been writte
 390        public long Length
 391        {
 392            get
 0393            {
 0394                if (_everOpenedForWrite)
 0395                {
 0396                    throw new InvalidOperationException(SR.LengthAfterWrite);
 397                }
 0398                return _uncompressedSize;
 0399            }
 400        }
 401
 402        /// <summary>
 403        /// The filename of the entry. This is equivalent to the substring of Fullname that follows the final directory 
 404        /// </summary>
 0405        public string Name => ParseFileName(FullName, _versionMadeByPlatform);
 406
 407        /// <summary>
 408        /// Gets the "version made by" field of the entry as stored in the archive's central directory record.
 409        /// </summary>
 410        /// <remarks>
 411        /// As defined by the ZIP file format specification, the low byte contains the version of the specification used
 412        /// </remarks>
 413        [CLSCompliant(false)]
 0414        public ushort VersionMadeBy => (ushort)(((byte)_versionMadeByPlatform << 8) | (byte)_versionMadeBySpecification)
 415
 1548416        internal ZipArchive.ChangeState Changes { get; private set; }
 417
 172418        internal bool OriginallyInArchive => _originallyInArchive;
 419
 0420        internal long OffsetOfLocalHeader => _offsetOfLocalHeader;
 421
 422        /// <summary>
 423        /// Deletes the entry from the archive.
 424        /// </summary>
 425        /// <exception cref="IOException">The entry is already open for reading or writing.</exception>
 426        /// <exception cref="NotSupportedException">The ZipArchive that this entry belongs to was opened in a mode other
 427        /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exce
 428        public void Delete()
 0429        {
 0430            if (_archive == null)
 0431            {
 0432                return;
 433            }
 434
 0435            if (_currentlyOpenForWrite)
 0436            {
 0437                throw new IOException(SR.DeleteOpenEntry);
 438            }
 439
 0440            if (_archive.Mode != ZipArchiveMode.Update)
 0441            {
 0442                throw new NotSupportedException(SR.DeleteOnlyInUpdate);
 443            }
 444
 0445            _archive.ThrowIfDisposed();
 446
 0447            _archive.RemoveEntry(this);
 0448            _archive = null!;
 0449            UnloadStreams();
 0450        }
 451
 452        /// <summary>
 453        /// Opens the entry. If the archive that the entry belongs to was opened in Read mode, the returned stream will 
 454        /// </summary>
 455        /// <returns>A Stream that represents the contents of the entry.</returns>
 456        /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been delet
 457        /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be rea
 458        /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exce
 459        public Stream Open()
 86460        {
 86461            ThrowIfInvalidArchive();
 86462            return OpenCore(InferAccessFromMode());
 86463        }
 464
 465
 466        /// <summary>
 467        /// Opens the entry for reading or updating with the specified password.
 468        /// If the entry is not encrypted, the password is ignored and the entry is opened normally.
 469        /// </summary>
 470        /// <returns>A Stream that represents the contents of the entry.</returns>
 471        /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been delet
 472        /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be rea
 473        /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exce
 474        /// <exception cref="InvalidOperationException">The requested access is not compatible with the archive's open m
 475        /// <param name="password">The password used to decrypt the entry. If the entry is not encrypted, this parameter
 476        public Stream Open(ReadOnlySpan<char> password)
 172477        {
 172478            ThrowIfInvalidArchive();
 479
 172480            if (IsEncrypted && password.IsEmpty)
 0481            {
 0482                throw new ArgumentException(SR.PasswordRequired, nameof(password));
 483            }
 484
 172485            return OpenCore(InferAccessFromMode(), password);
 86486        }
 487
 488        /// <summary>
 489        /// Opens the entry with the specified access mode. This allows for more granular control over the returned stre
 490        /// </summary>
 491        /// <param name="access">The file access mode for the returned stream.</param>
 492        /// <returns>A <see cref="Stream"/> that represents the contents of the entry with the specified access capabili
 493        /// <remarks>
 494        /// <para>The allowed <paramref name="access"/> values depend on the <see cref="ZipArchiveMode"/>:</para>
 495        /// <list type="bullet">
 496        /// <item><description><see cref="ZipArchiveMode.Read"/>: Only <see cref="FileAccess.Read"/> is allowed.</descri
 497        /// <item><description><see cref="ZipArchiveMode.Create"/>: <see cref="FileAccess.Write"/> and <see cref="FileAc
 498        /// <item><description><see cref="ZipArchiveMode.Update"/>: All values are allowed. <see cref="FileAccess.Read"/
 499        /// </list>
 500        /// </remarks>
 501        /// <exception cref="ArgumentOutOfRangeException"><paramref name="access"/> is not a valid <see cref="FileAccess
 502        /// <exception cref="InvalidOperationException">The requested access is not compatible with the archive's open m
 503        /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been delet
 504        /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be rea
 505        /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exce
 506        public Stream Open(FileAccess access)
 0507        {
 0508            ThrowIfInvalidArchive();
 0509            ValidateAccessForMode(access);
 0510            return OpenCore(access);
 0511        }
 512
 513        public Stream Open(FileAccess access, ReadOnlySpan<char> password)
 0514        {
 0515            ThrowIfInvalidArchive();
 0516            ValidateAccessForMode(access);
 517
 0518            if (IsEncrypted && password.IsEmpty)
 0519            {
 0520                throw new ArgumentException(SR.PasswordRequired, nameof(password));
 521            }
 522
 0523            return OpenCore(access, password);
 0524        }
 525
 516526        private FileAccess InferAccessFromMode() => _archive.Mode switch
 516527        {
 344528            ZipArchiveMode.Read => FileAccess.Read,
 172529            ZipArchiveMode.Create => FileAccess.Write,
 0530            _ => FileAccess.ReadWrite
 516531        };
 532
 533        private void ValidateAccessForMode(FileAccess access)
 0534        {
 0535            if (access is not (FileAccess.Read or FileAccess.Write or FileAccess.ReadWrite))
 0536            {
 0537                throw new ArgumentOutOfRangeException(nameof(access), SR.InvalidFileAccess);
 538            }
 539
 0540            switch (_archive.Mode)
 541            {
 542                case ZipArchiveMode.Read:
 0543                    if (access != FileAccess.Read)
 0544                    {
 0545                        throw new InvalidOperationException(SR.CannotBeWrittenInReadMode);
 546                    }
 0547                    break;
 548                case ZipArchiveMode.Create:
 0549                    if (access == FileAccess.Read)
 0550                    {
 0551                        throw new InvalidOperationException(SR.CannotBeReadInCreateMode);
 552                    }
 0553                    break;
 554            }
 0555        }
 556
 557        private Stream OpenCore(FileAccess access, ReadOnlySpan<char> password = default)
 258558        {
 258559            switch (_archive.Mode)
 560            {
 561                case ZipArchiveMode.Read:
 172562                    return OpenInReadMode(checkOpenable: true, password);
 563                case ZipArchiveMode.Create:
 86564                    return OpenInWriteMode();
 565                case ZipArchiveMode.Update:
 566                default:
 0567                    Debug.Assert(_archive.Mode == ZipArchiveMode.Update);
 0568                    return access switch
 0569                    {
 0570                        // Reads in Update mode must observe content written earlier in this session and
 0571                        // treat a newly created entry as empty. Only an unmodified entry that already
 0572                        // exists in the archive can be read directly without loading it into memory.
 0573                        FileAccess.Read => _storedUncompressedData is not null || !_originallyInArchive
 0574                            ? OpenInUpdateModeForRead()
 0575                            : OpenInReadMode(checkOpenable: true, password),
 0576                        FileAccess.Write => OpenInUpdateMode(loadExistingContent: false, password),
 0577                        _ => OpenInUpdateMode(loadExistingContent: true, password),
 0578                    };
 579            }
 172580        }
 581
 582        private string DecodeEntryString(byte[] entryStringBytes)
 172583        {
 172584            Debug.Assert(entryStringBytes != null);
 585
 172586            Encoding readEntryStringEncoding =
 172587                (_generalPurposeBitFlag & BitFlagValues.UnicodeFileNameAndComment) == BitFlagValues.UnicodeFileNameAndCo
 172588                ? Encoding.UTF8
 172589                : _archive?.EntryNameAndCommentEncoding ?? Encoding.UTF8;
 590
 172591            return readEntryStringEncoding.GetString(entryStringBytes);
 172592        }
 593
 594        // Only allow opening ZipArchives with large ZipArchiveEntries in update mode when running in a 64-bit process.
 595        // This is for compatibility with old behavior that threw an exception for all process bitnesses, because this
 596        // will not work in a 32-bit process.
 0597        private static readonly bool s_allowLargeZipArchiveEntriesInUpdateMode = IntPtr.Size > 4;
 598
 0599        internal bool EverOpenedForWrite => _everOpenedForWrite;
 600
 601        internal long GetOffsetOfCompressedData()
 430602        {
 430603            if (_storedOffsetOfCompressedData == null)
 86604            {
 605                // Seek to local header
 86606                _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
 607
 608                // Skip the local file header to get to the compressed data
 609                // TrySkipBlock handles both AES and non-AES cases correctly
 86610                if (!ZipLocalFileHeader.TrySkipBlock(_archive.ArchiveStream))
 0611                {
 0612                    throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
 613                }
 614
 86615                _storedOffsetOfCompressedData = _archive.ArchiveStream.Position;
 86616            }
 617
 430618            return _storedOffsetOfCompressedData.Value;
 430619        }
 620
 621        /// <summary>
 622        /// Reads and caches the AES encryption salt from the local file data area.
 623        /// Called during central directory parsing for AES-encrypted entries so that
 624        /// the salt is available for key derivation without additional I/O at open time.
 625        /// </summary>
 626        internal void ReadEncryptionSaltIfNeeded()
 86627        {
 86628            if (!IsAesEncrypted || !_originallyInArchive || OperatingSystem.IsBrowser())
 0629            {
 0630                return;
 631            }
 632
 633            // A corrupt central directory can point the local header offset past the end of the
 634            // archive. Seeking there throws ArgumentOutOfRangeException on some streams (e.g.
 635            // MemoryStream). Mirror the check in IsOpenableInitialVerifications and defer the error
 636            // to when the entry is actually opened, same as for non AES encrypted entries.
 86637            if (_offsetOfLocalHeader > _archive.ArchiveStream.Length)
 0638            {
 0639                return;
 640            }
 641
 86642            long savedPosition = _archive.ArchiveStream.Position;
 643            try
 86644            {
 86645                long offset = GetOffsetOfCompressedData();
 86646                _archive.ArchiveStream.Seek(offset, SeekOrigin.Begin);
 647
 86648                int keySizeBits = GetAesKeySizeBits(Encryption);
 86649                int saltSize = WinZipAesStream.GetSaltSize(keySizeBits);
 86650                _aesSalt = new byte[saltSize];
 86651                _archive.ArchiveStream.ReadExactly(_aesSalt);
 86652            }
 0653            catch (Exception ex) when (ex is InvalidDataException or EndOfStreamException)
 0654            {
 655                // These are the only exceptions GetOffsetOfCompressedData() and ReadExactly()
 656                // can throw for corrupt or truncated data. Swallow them here and defer the error
 657                // to when the entry is actually opened.
 0658                _aesSalt = null;
 0659            }
 660            finally
 86661            {
 86662                _archive.ArchiveStream.Seek(savedPosition, SeekOrigin.Begin);
 86663            }
 86664        }
 665
 666        private MemoryStream GetUncompressedData(ReadOnlySpan<char> password = default)
 0667        {
 0668            if (_storedUncompressedData == null)
 0669            {
 670                // this means we have never opened it before
 671
 672
 673                // MemoryStream is backed by a single byte[] and cannot grow beyond Array.MaxLength.
 674                // Validate up front before attempting the (int) cast.
 0675                if ((ulong)_uncompressedSize > (ulong)Array.MaxLength)
 0676                {
 0677                    _currentlyOpenForWrite = false;
 0678                    throw new InvalidDataException(SR.EntryUncompressedSizeTooLargeForUpdateMode);
 679                }
 680
 0681                _storedUncompressedData = new MemoryStream((int)_uncompressedSize);
 682
 0683                if (_originallyInArchive)
 0684                {
 0685                    Stream decompressor = !password.IsEmpty
 0686                        ? OpenInReadMode(checkOpenable: false, password)
 0687                        : OpenInReadMode(checkOpenable: false);
 688
 0689                    using (decompressor)
 0690                    {
 691                        try
 0692                        {
 0693                            decompressor.CopyTo(_storedUncompressedData);
 0694                        }
 0695                        catch (InvalidDataException)
 0696                        {
 697                            // this is the case where the archive say the entry is deflate, but deflateStream
 698                            // throws an InvalidDataException. This property should only be getting accessed in
 699                            // Update mode, so we want to make sure _storedUncompressedData stays null so
 700                            // that later when we dispose the archive, this entry loads the compressedBytes, and
 701                            // copies them straight over
 0702                            _storedUncompressedData.Dispose();
 0703                            _storedUncompressedData = null;
 0704                            _currentlyOpenForWrite = false;
 0705                            _everOpenedForWrite = false;
 0706                            _derivedZipCryptoKeyMaterial = null;
 0707                            _derivedAesKeyMaterial = null;
 0708                            throw;
 709                        }
 0710                    }
 0711                }
 712
 713                // NOTE: CompressionMethod normalization is deferred to MarkAsModified() to avoid
 714                // corrupting entries that are opened in Update mode but not actually written to.
 715                // If we normalized here and the entry wasn't modified, we'd write a header with
 716                // CompressionMethod=Deflate but the original _compressedBytes would still be in
 717                // their original format (e.g., Deflate64), producing an invalid entry.
 0718            }
 719
 0720            return _storedUncompressedData;
 0721        }
 722        // does almost everything you need to do to forget about this entry
 723        // writes the local header/data, gets rid of all the data,
 724        // closes all of the streams except for the very outermost one that
 725        // the user holds on to and is responsible for closing
 726        //
 727        // after calling this, and only after calling this can we be guaranteed
 728        // that we are reading to write the central directory
 729        //
 730        // should only throw an exception in extremely exceptional cases because it is called from dispose
 731        internal void WriteAndFinishLocalEntry(bool forceWrite)
 86732        {
 86733            CloseStreams();
 86734            WriteLocalFileHeaderAndDataIfNeeded(forceWrite);
 86735            UnloadStreams();
 86736        }
 737
 738        private bool WriteCentralDirectoryFileHeaderInitialize(bool forceWrite, out Zip64ExtraField? zip64ExtraField, ou
 172739        {
 740            // This part is simple, because we should definitely know the sizes by this time
 741
 742            // _storedEntryNameBytes only gets set when we read in or call moveTo. MoveTo does a check, and
 743            // reading in should not be able to produce an entryname longer than ushort.MaxValue
 744            // _fileComment only gets set when we read in or set the FileComment property. This performs its own
 745            // length check.
 172746            Debug.Assert(_storedEntryNameBytes.Length <= ushort.MaxValue);
 172747            Debug.Assert(_fileComment.Length <= ushort.MaxValue);
 748
 749            // decide if we need the Zip64 extra field:
 172750            zip64ExtraField = null;
 751
 172752            if (AreSizesTooLarge
 172753#if DEBUG_FORCE_ZIP64
 172754                || _archive._forceZip64
 172755#endif
 172756                )
 0757            {
 0758                compressedSizeTruncated = ZipHelper.Mask32Bit;
 0759                uncompressedSizeTruncated = ZipHelper.Mask32Bit;
 760
 761                // If we have one of the sizes, the other must go in there as speced for LH, but not necessarily for CH,
 0762                zip64ExtraField = new()
 0763                {
 0764                    CompressedSize = _compressedSize,
 0765                    UncompressedSize = _uncompressedSize
 0766                };
 0767            }
 768            else
 172769            {
 172770                compressedSizeTruncated = (uint)_compressedSize;
 172771                uncompressedSizeTruncated = (uint)_uncompressedSize;
 172772            }
 773
 774
 172775            if (IsOffsetTooLarge
 172776#if DEBUG_FORCE_ZIP64
 172777                || _archive._forceZip64
 172778#endif
 172779                )
 0780            {
 0781                offsetOfLocalHeaderTruncated = ZipHelper.Mask32Bit;
 782
 783                // If we already created a zip64 extra field for sizes, add the offset to it
 784                // Otherwise, create a new one for just the offset
 0785                zip64ExtraField ??= new();
 0786                zip64ExtraField.LocalHeaderOffset = _offsetOfLocalHeader;
 0787            }
 788            else
 172789            {
 172790                offsetOfLocalHeaderTruncated = (uint)_offsetOfLocalHeader;
 172791            }
 792
 172793            if (zip64ExtraField != null)
 0794            {
 0795                VersionToExtractAtLeast(ZipVersionNeededValues.Zip64);
 0796            }
 797
 798
 172799            WinZipAesExtraField? aesExtraField = null;
 172800            int aesExtraFieldSize = 0;
 801
 172802            if (UseAesEncryption)
 172803            {
 172804                aesExtraField = CreateAesExtraField();
 172805                aesExtraFieldSize = WinZipAesExtraField.TotalSize;
 172806            }
 807
 808            // determine if we can fit zip64 extra field and original extra fields all in
 809            // When using AES encryption, exclude the AES tag from currExtraFieldDataLength since we're writing a new on
 172810            int currExtraFieldDataLength = UseAesEncryption
 172811                ? ZipGenericExtraField.TotalSizeExcludingTag(_cdUnknownExtraFields, _cdTrailingExtraFieldData?.Length ??
 172812                : ZipGenericExtraField.TotalSize(_cdUnknownExtraFields, _cdTrailingExtraFieldData?.Length ?? 0);
 172813            int bigExtraFieldLength = (zip64ExtraField != null ? zip64ExtraField.TotalSize : 0)
 172814                                      + aesExtraFieldSize
 172815                                      + currExtraFieldDataLength;
 816
 172817            if (bigExtraFieldLength > ushort.MaxValue)
 0818            {
 0819                extraFieldLength = (ushort)((zip64ExtraField != null ? zip64ExtraField.TotalSize : 0) + aesExtraFieldSiz
 0820                _cdUnknownExtraFields = null;
 0821            }
 822            else
 172823            {
 172824                extraFieldLength = (ushort)bigExtraFieldLength;
 172825            }
 826
 172827            if (_originallyInArchive && Changes == ZipArchive.ChangeState.Unchanged && !forceWrite)
 0828            {
 0829                long centralDirectoryHeaderLength = ZipCentralDirectoryFileHeader.FieldLocations.DynamicData
 0830                    + _storedEntryNameBytes.Length
 0831                    + extraFieldLength
 0832                    + _fileComment.Length;
 833
 0834                _archive.ArchiveStream.Seek(centralDirectoryHeaderLength, SeekOrigin.Current);
 835
 0836                return false;
 837            }
 838
 172839            return true;
 172840        }
 841
 842        private void WriteCentralDirectoryFileHeaderPrepare(Span<byte> cdStaticHeader, uint compressedSizeTruncated, uin
 172843        {
 844            // The central directory file header begins with the below constant-length structure:
 845            // Central directory file header signature  (4 bytes)
 846            // Version made by Specification (version)  (1 byte)
 847            // Version made by Compatibility (type)     (1 byte)
 848            // Minimum version needed to extract        (2 bytes)
 849            // General Purpose bit flag                 (2 bytes)
 850            // The Compression method                   (2 bytes)
 851            // File last modification time and date     (4 bytes)
 852            // CRC-32                                   (4 bytes)
 853            // Compressed Size                          (4 bytes)
 854            // Uncompressed Size                        (4 bytes)
 855            // File Name Length                         (2 bytes)
 856            // Extra Field Length                       (2 bytes)
 857            // File Comment Length                      (2 bytes)
 858            // Start Disk Number                        (2 bytes)
 859            // Internal File Attributes                 (2 bytes)
 860            // External File Attributes                 (4 bytes)
 861            // Offset Of Local Header                   (4 bytes)
 862
 172863            ZipCentralDirectoryFileHeader.SignatureConstantBytes.CopyTo(cdStaticHeader[ZipCentralDirectoryFileHeader.Fie
 172864            cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.VersionMadeBySpecification] = (byte)_versionMade
 172865            cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.VersionMadeByCompatibility] = (byte)CurrentZipPl
 172866            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Version
 172867            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.General
 868
 869            // For AES encryption, write compression method 99 (Aes) in the header
 870            // _headerCompressionMethod preserves the original value from the central directory
 172871            ushort compressionMethodToWrite = UseAesEncryption ? (ushort)WinZipAesMethod : (ushort)CompressionMethod;
 172872            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Compres
 873
 172874            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.LastMod
 875            // when using aes encryption, ae-2 standard dictates crc to be 0
 172876            uint crcToWrite = UseAesEncryption ? 0 : _crc32;
 172877            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Crc32..
 172878            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Compres
 172879            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Uncompr
 172880            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Filenam
 172881            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.ExtraFi
 172882            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.FileCom
 172883            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.DiskNum
 172884            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Interna
 172885            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Externa
 172886            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Relativ
 172887        }
 888
 889        // should only throw an exception in extremely exceptional cases because it is called from dispose
 890        internal unsafe void WriteCentralDirectoryFileHeader(bool forceWrite)
 86891        {
 86892            if (WriteCentralDirectoryFileHeaderInitialize(forceWrite, out Zip64ExtraField? zip64ExtraField, out uint com
 86893            {
 86894                Span<byte> cdStaticHeader = stackalloc byte[ZipCentralDirectoryFileHeader.BlockConstantSectionSize];
 86895                WriteCentralDirectoryFileHeaderPrepare(cdStaticHeader, compressedSizeTruncated, uncompressedSizeTruncate
 896
 86897                _archive.ArchiveStream.Write(cdStaticHeader);
 86898                _archive.ArchiveStream.Write(_storedEntryNameBytes);
 899
 900                // only write zip64ExtraField if we decided we need it (it's not null)
 86901                zip64ExtraField?.WriteBlock(_archive.ArchiveStream);
 902
 903                // Write AES extra field if using AES encryption
 86904                if (UseAesEncryption)
 86905                {
 86906                    CreateAesExtraField().WriteBlock(_archive.ArchiveStream);
 907
 908                    // write extra fields excluding existing AES extra field (and any malformed trailing data).
 86909                    ZipGenericExtraField.WriteAllBlocksExcludingTag(_cdUnknownExtraFields, _cdTrailingExtraFieldData ?? 
 86910                }
 911                else
 0912                {
 913                    // write extra fields (and any malformed trailing data).
 0914                    ZipGenericExtraField.WriteAllBlocks(_cdUnknownExtraFields, _cdTrailingExtraFieldData ?? Array.Empty<
 0915                }
 916
 86917                if (_fileComment.Length > 0)
 0918                {
 0919                    _archive.ArchiveStream.Write(_fileComment);
 0920                }
 86921            }
 86922        }
 923
 924        // throws exception if fails, will get called on every relevant entry before closing in update mode
 925        // can throw InvalidDataException
 926        internal void LoadLocalHeaderExtraFieldIfNeeded()
 0927        {
 928            // we should have made this exact call in _archive.Init through ThrowIfOpenable
 0929            Debug.Assert(IsOpenable(false, true, out _));
 930
 931            // load local header's extra fields. it will be null if we couldn't read for some reason
 0932            if (_originallyInArchive)
 0933            {
 0934                _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
 0935                _lhUnknownExtraFields = ZipLocalFileHeader.GetExtraFields(_archive.ArchiveStream, out _lhTrailingExtraFi
 0936            }
 0937        }
 938
 939        private byte[][] LoadCompressedBytesIfNeededInitialize(out int maxSingleBufferSize)
 0940        {
 941            // we know that it is openable at this point
 0942            maxSingleBufferSize = Array.MaxLength;
 943
 0944            byte[][] compressedBytes = new byte[(_compressedSize / maxSingleBufferSize) + 1][];
 0945            for (int i = 0; i < compressedBytes.Length - 1; i++)
 0946            {
 0947                compressedBytes[i] = new byte[maxSingleBufferSize];
 0948            }
 0949            compressedBytes[compressedBytes.Length - 1] = new byte[_compressedSize % maxSingleBufferSize];
 950
 0951            return compressedBytes;
 0952        }
 953
 954        // throws exception if fails, will get called on every relevant entry before closing in update mode
 955        // can throw InvalidDataException
 956        internal void LoadCompressedBytesIfNeeded()
 0957        {
 958            // we should have made this exact call in _archive.Init through ThrowIfOpenable
 0959            Debug.Assert(IsOpenable(false, true, out _));
 960
 0961            if (!_everOpenedForWrite && _originallyInArchive)
 0962            {
 0963                _compressedBytes = LoadCompressedBytesIfNeededInitialize(out int maxSingleBufferSize);
 964
 0965                _archive.ArchiveStream.Seek(GetOffsetOfCompressedData(), SeekOrigin.Begin);
 966
 0967                for (int i = 0; i < _compressedBytes.Length - 1; i++)
 0968                {
 0969                    _archive.ArchiveStream.ReadAtLeast(_compressedBytes[i], maxSingleBufferSize, throwOnEndOfStream: tru
 0970                }
 0971                _archive.ArchiveStream.ReadAtLeast(_compressedBytes[_compressedBytes.Length - 1], (int)(_compressedSize 
 0972            }
 0973        }
 974
 975        internal void ThrowIfNotOpenable(bool needToUncompress, bool needToLoadIntoMemory)
 172976        {
 172977            if (!IsOpenable(needToUncompress, needToLoadIntoMemory, out string? message))
 0978            {
 0979                throw new InvalidDataException(message);
 980            }
 172981        }
 982
 983        private void DetectEntryNameVersion()
 344984        {
 344985            if (ParseFileName(_storedEntryName, _versionMadeByPlatform) == "")
 0986            {
 0987                VersionToExtractAtLeast(ZipVersionNeededValues.ExplicitDirectory);
 0988            }
 344989        }
 990
 991
 992        private CheckSumAndSizeWriteStream GetDataCompressor(Stream backingStream, bool leaveBackingStreamOpen, EventHan
 172993        {
 994            // stream stack: backingStream -> DeflateStream -> CheckSumWriteStream
 995
 996            // By default we compress with deflate, except if compression level
 997            // is set to NoCompression then stored is used.
 998            //
 999            // Stored is also used for empty files, but we can't know at this
 1000            // point if user will write anything to the stream or not. For that
 1001            // reason, we defer the instantiation of the compression stream
 1002            // until the first write to the CheckSumAndSizeWriteStream happens.
 1003            // If the user never writes anything, this will be detected during
 1004            // saving and the compression method in the file header will be
 1005            // changed to Stored.
 1006            //
 1007            // Note: Deflate64 is not supported on all platforms
 1721008            Debug.Assert(CompressionMethod == ZipCompressionMethod.Deflate
 1721009                || CompressionMethod == ZipCompressionMethod.Stored);
 1010            Func<Stream> compressorStreamFactory;
 1011
 1721012            bool isIntermediateStream = true;
 1013
 1721014            switch (CompressionMethod)
 1015            {
 1016                case ZipCompressionMethod.Stored:
 01017                    compressorStreamFactory = () => backingStream;
 01018                    isIntermediateStream = false;
 01019                    break;
 1020                case ZipCompressionMethod.Deflate:
 1021                case ZipCompressionMethod.Deflate64:
 1022                default:
 3441023                    compressorStreamFactory = () => new DeflateStream(backingStream, _compressionLevel, leaveBackingStre
 1721024                    break;
 1025            }
 1721026            bool leaveCompressorStreamOpenOnClose = leaveBackingStreamOpen && !isIntermediateStream;
 1721027            var checkSumStream = new CheckSumAndSizeWriteStream(
 1721028                compressorStreamFactory,
 1721029                streamForPosition ?? backingStream,
 1721030                leaveCompressorStreamOpenOnClose,
 1721031                this,
 1721032                onClose,
 1721033                (long initialPosition, long currentPosition, uint checkSum, Stream backing, ZipArchiveEntry thisRef, Eve
 1721034                {
 1721035                    thisRef._crc32 = checkSum;
 1721036                    thisRef._uncompressedSize = currentPosition;
 1721037                    thisRef._compressedSize = backing.Position - initialPosition;
 1721038                    closeHandler?.Invoke(thisRef, EventArgs.Empty);
 3441039                });
 1040
 1721041            return checkSumStream;
 1721042        }
 1043
 1044        private byte CalculateZipCryptoCheckByte()
 01045        {
 1046            // If data descriptor NOT used, the check byte is the MSB of CRC32
 01047            if ((_generalPurposeBitFlag & BitFlagValues.DataDescriptor) == 0)
 01048            {
 01049                return (byte)((_crc32 >> 24) & 0xFF);
 1050            }
 1051
 1052            // If data descriptor IS used, the check byte is the MSB of the DOS time from the *local* header
 01053            return (byte)((ZipHelper.DateTimeToDosTime(_lastModified.DateTime) >> 8) & 0xFF);
 01054        }
 1055
 01056        private bool IsZipCryptoEncrypted => (_generalPurposeBitFlag & BitFlagValues.IsEncrypted) != 0 && (ushort)_heade
 1057
 18921058        private bool UseAesEncryption => Encryption is ZipEncryptionMethod.Aes128 or ZipEncryptionMethod.Aes192 or ZipEn
 1059
 8601060        private bool IsAesEncrypted => (ushort)_headerCompressionMethod == WinZipAesMethod;
 1061
 1062        private static int GetAesKeySizeBits(ZipEncryptionMethod encryption)
 6881063        {
 1064            // Get number of bits for AES key size based on the encryption method
 1065            // Only possible values are: AES-128 = 128 bits, AES-192 = 192 bits, AES-256 as per the specs
 6881066            return encryption switch
 6881067            {
 401068                ZipEncryptionMethod.Aes128 => 128,
 801069                ZipEncryptionMethod.Aes192 => 192,
 5681070                ZipEncryptionMethod.Aes256 => 256,
 01071                _ => throw new InvalidDataException(SR.InvalidAesStrength)
 6881072            };
 6881073        }
 1074
 1075        /// <summary>
 1076        /// Creates the appropriate decryption stream for an encrypted entry.
 1077        /// For AES entries, uses the salt that was pre-read during central directory parsing.
 1078        /// </summary>
 1079        private Stream WrapWithDecryptionIfNeeded(Stream compressedStream, ReadOnlySpan<char> password)
 1721080        {
 1721081            if (Encryption == ZipEncryptionMethod.Unknown)
 01082            {
 01083                throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 1084            }
 1085
 1721086            if (password.IsEmpty)
 01087            {
 01088                throw new InvalidDataException(SR.PasswordRequired);
 1089            }
 1090
 1721091            if (IsAesEncrypted)
 1721092            {
 1721093                if (_aesSalt is null)
 01094                {
 01095                    throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
 1096                }
 1097
 1721098                int keySizeBits = GetAesKeySizeBits(Encryption);
 1721099                WinZipAesKeyMaterial keyMaterial = WinZipAesStream.CreateKey(password, _aesSalt, keySizeBits);
 1721100                return WinZipAesStream.Create(
 1721101                    baseStream: compressedStream,
 1721102                    keyMaterial: keyMaterial,
 1721103                    totalStreamSize: _compressedSize,
 1721104                    encrypting: false);
 1105            }
 1106
 01107            if (IsZipCryptoEncrypted)
 01108            {
 01109                byte expectedCheckByte = CalculateZipCryptoCheckByte();
 01110                ZipCryptoKeys keyMaterial = ZipCryptoStream.CreateKey(password);
 01111                return ZipCryptoStream.Create(compressedStream, keyMaterial, expectedCheckByte, encrypting: false);
 1112            }
 1113
 01114            throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 861115        }
 1116
 1117        private Stream GetDataDecompressor(Stream compressedStreamToRead)
 1721118        {
 1119            Stream? uncompressedStream;
 1721120            switch (CompressionMethod)
 1121            {
 1122                case ZipCompressionMethod.Deflate:
 1721123                    uncompressedStream = new DeflateStream(compressedStreamToRead, CompressionMode.Decompress, _uncompre
 1721124                    break;
 1125                case ZipCompressionMethod.Deflate64:
 01126                    uncompressedStream = new DeflateManagedStream(compressedStreamToRead, ZipCompressionMethod.Deflate64
 01127                    break;
 1128                case ZipCompressionMethod.Stored:
 01129                    uncompressedStream = compressedStreamToRead;
 01130                    break;
 1131                default:
 1132                    // We should not get here with Aes as CompressionMethod anymore
 1133                    // as it should have been replaced with the actual compression method
 01134                    Debug.Assert((ushort)CompressionMethod != WinZipAesMethod,
 01135                        "AES compression method should have been replaced with actual compression method");
 1136
 1137                    // Fallback to stored if we somehow get here
 01138                    uncompressedStream = compressedStreamToRead;
 01139                    break;
 1140            }
 1141
 1721142            return uncompressedStream;
 1721143        }
 1144
 1145        private Stream OpenInReadMode(bool checkOpenable, ReadOnlySpan<char> password = default)
 1721146        {
 1721147            if (checkOpenable)
 1721148            {
 1721149                ThrowIfNotOpenable(needToUncompress: true, needToLoadIntoMemory: false);
 1721150            }
 1721151            return OpenInReadModeGetDataCompressor(GetOffsetOfCompressedData(), password);
 861152        }
 1153
 1154        private Stream OpenInReadModeGetDataCompressor(long offsetOfCompressedData, ReadOnlySpan<char> password = defaul
 1721155        {
 1721156            Stream compressedStream = new SubReadStream(_archive.ArchiveStream, offsetOfCompressedData, _compressedSize)
 1157            Stream streamToDecompress;
 1158
 1721159            if (IsEncrypted)
 1721160            {
 1721161                streamToDecompress = WrapWithDecryptionIfNeeded(compressedStream, password);
 861162            }
 1163            else
 01164            {
 01165                streamToDecompress = compressedStream;
 01166            }
 1167
 861168            return BuildDecompressionPipeline(streamToDecompress);
 861169        }
 1170
 1171        /// <summary>
 1172        /// Wraps a (possibly decrypted) stream with decompression and CRC validation.
 1173        /// Shared by both sync and async read paths.
 1174        /// </summary>
 1175        private Stream BuildDecompressionPipeline(Stream streamToDecompress)
 1721176        {
 1721177            Stream decompressedStream = GetDataDecompressor(streamToDecompress);
 1178
 1179            // AE-2 encrypted entries store CRC as 0 so skip CRC validation for those.
 1180            // AE-1 version entries store a valid CRC.
 1721181            if (IsAesEncrypted && _aeVersion == 2)
 1721182            {
 1721183                return decompressedStream;
 1184            }
 1185
 01186            return new CrcValidatingReadStream(decompressedStream, _crc32, _uncompressedSize);
 1721187        }
 1188
 1189        private WrappedStream OpenInWriteMode()
 1721190        {
 1721191            if (_everOpenedForWrite)
 01192            {
 01193                throw new IOException(SR.CreateModeWriteOnceAndOneEntryAtATime);
 1194            }
 1195
 1196            // we assume that if another entry grabbed the archive stream, that it set this entry's _everOpenedForWrite 
 1721197            _archive.DebugAssertIsStillArchiveStreamOwner(this);
 1198
 1721199            return OpenInWriteModeCore();
 1721200        }
 1201
 1202        private WrappedStream OpenInWriteModeCore()
 1721203        {
 1721204            _everOpenedForWrite = true;
 1721205            Changes |= ZipArchive.ChangeState.StoredData;
 1206
 1207            // Use the encryption method pre-configured via PrepareEncryption (CreateEntry with password).
 1721208            ZipEncryptionMethod encryptionMethod = Encryption;
 1209
 1210            // Build the stream stack with encryption if needed
 1721211            Stream targetStream = _archive.ArchiveStream;
 1721212            Stream? encryptionStream = null;
 1213
 1721214            if (encryptionMethod == ZipEncryptionMethod.ZipCrypto)
 01215            {
 01216                ZipCryptoKeys keyMaterial = _derivedZipCryptoKeyMaterial
 01217                    ?? throw new InvalidOperationException(SR.EmptyPassword);
 1218
 01219                ushort verifierLow2Bytes = (ushort)ZipHelper.DateTimeToDosTime(_lastModified.DateTime);
 1220
 01221                targetStream = ZipCryptoStream.Create(
 01222                    baseStream: _archive.ArchiveStream,
 01223                    keys: keyMaterial,
 01224                    passwordVerifierLow2Bytes: verifierLow2Bytes,
 01225                    encrypting: true,
 01226                    crc32: null,
 01227                    leaveOpen: true);
 01228                encryptionStream = targetStream;
 01229            }
 1721230            else if (encryptionMethod is ZipEncryptionMethod.Aes256 or ZipEncryptionMethod.Aes192 or ZipEncryptionMethod
 1721231            {
 1721232                WinZipAesKeyMaterial keyMaterial = _derivedAesKeyMaterial
 1721233                    ?? throw new InvalidOperationException(SR.EmptyPassword);
 1234
 1721235                targetStream = WinZipAesStream.Create(
 1721236                    baseStream: _archive.ArchiveStream,
 1721237                    keyMaterial: keyMaterial,
 1721238                    totalStreamSize: -1,
 1721239                    encrypting: true,
 1721240                    leaveOpen: true);
 1721241                encryptionStream = targetStream;
 1721242            }
 1243
 1721244            bool isAesEncryption = encryptionMethod is ZipEncryptionMethod.Aes256 or ZipEncryptionMethod.Aes192 or ZipEn
 1245
 1721246            CheckSumAndSizeWriteStream crcSizeStream = GetDataCompressor(
 1721247                targetStream,
 1721248                leaveBackingStreamOpen: !isAesEncryption,
 1721249                (object? o, EventArgs e) =>
 1721250                {
 1721251                    // release the archive stream
 1721252                    var entry = (ZipArchiveEntry)o!;
 1721253                    entry._archive.ReleaseArchiveStream(entry);
 1721254                    entry._outstandingWriteStream = null;
 1721255                },
 1721256                streamForPosition: encryptionMethod != ZipEncryptionMethod.None ? _archive.ArchiveStream : null);
 1257
 1721258            _outstandingWriteStream = new DirectToArchiveWriterStream(crcSizeStream, this, encryptionMethod, encryptionS
 1259
 1721260            return new WrappedStream(baseStream: _outstandingWriteStream, closeBaseStream: true);
 1721261        }
 1262
 1263        private WrappedStream OpenInUpdateMode(bool loadExistingContent = true, ReadOnlySpan<char> password = default)
 01264        {
 01265            if (_currentlyOpenForWrite)
 01266            {
 01267                throw new IOException(SR.UpdateModeOneStream);
 1268            }
 1269
 01270            if (Encryption == ZipEncryptionMethod.Unknown)
 01271            {
 01272                throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 1273            }
 1274
 1275            // Encrypted entries always require a password for re-encryption,
 1276            // even when discarding existing content (write-only access).
 01277            if (IsEncrypted && password.IsEmpty)
 01278            {
 01279                throw new ArgumentException(SR.PasswordRequired, nameof(password));
 1280            }
 1281
 01282            if (loadExistingContent)
 01283            {
 01284                ThrowIfNotOpenable(needToUncompress: true, needToLoadIntoMemory: true);
 01285            }
 1286
 01287            _currentlyOpenForWrite = true;
 1288
 1289            // Set up re-encryption key material so that the rewritten entry has valid encryption headers.
 01290            if (IsEncrypted)
 01291            {
 01292                SetupEncryptionKeyMaterial(password);
 01293            }
 1294
 01295            if (loadExistingContent)
 01296            {
 01297                _storedUncompressedData = GetUncompressedData(password);
 01298            }
 1299            else
 01300            {
 01301                _storedUncompressedData?.Dispose();
 01302                _storedUncompressedData = new MemoryStream();
 1303                // Opening with loadExistingContent: false discards existing content, which is a modification
 01304                MarkAsModified();
 01305            }
 1306
 01307            _storedUncompressedData.Seek(0, SeekOrigin.Begin);
 1308
 01309            return new WrappedStream(_storedUncompressedData, this,
 01310                onClosed: thisRef => thisRef!._currentlyOpenForWrite = false,
 01311                notifyEntryOnWrite: true);
 01312        }
 1313
 1314        // Returns a read-only, seekable view over the entry's current uncompressed buffer in Update mode.
 1315        // It reflects modifications made earlier in the current session (and is empty for a freshly created
 1316        // entry), unlike OpenInReadMode which reads the original bytes directly from the archive. The stream
 1317        // shares the underlying buffer with the entry rather than copying it, so it is not isolated from later
 1318        // in-place writes; callers are expected to finish reading before reopening the entry for writing.
 1319        private MemoryStream OpenInUpdateModeForRead()
 01320        {
 01321            if (_currentlyOpenForWrite)
 01322                throw new IOException(SR.UpdateModeOneStream);
 1323
 01324            MemoryStream uncompressedData = GetUncompressedData();
 01325            return new MemoryStream(uncompressedData.GetBuffer(), 0, (int)uncompressedData.Length, writable: false);
 01326        }
 1327
 1328        /// <summary>
 1329        /// Marks this entry as modified, indicating that its data has changed and needs to be rewritten.
 1330        /// </summary>
 1331        internal void MarkAsModified()
 01332        {
 01333            _everOpenedForWrite = true;
 01334            Changes |= ZipArchive.ChangeState.StoredData;
 1335
 1336            // Normalize compression method when actually modifying - Deflate64 data will be
 1337            // re-compressed as Deflate since we don't support writing Deflate64.
 01338            if (CompressionMethod != ZipCompressionMethod.Stored)
 01339            {
 01340                CompressionMethod = ZipCompressionMethod.Deflate;
 01341            }
 01342        }
 1343
 1344        /// <summary>
 1345        /// Sets up encryption key material for re-encryption when writing back to the archive.
 1346        /// </summary>
 1347        private void SetupEncryptionKeyMaterial(ReadOnlySpan<char> password)
 01348        {
 1349            // Derive and save key material for re-encryption
 01350            if (IsZipCryptoEncrypted)
 01351            {
 01352                _derivedZipCryptoKeyMaterial = ZipCryptoStream.CreateKey(password);
 01353                Encryption = ZipEncryptionMethod.ZipCrypto;
 01354            }
 01355            else if (UseAesEncryption)
 01356            {
 1357                // Generate new salt and derive key material for AES
 1358                // This ensures each write uses a fresh random salt for security
 01359                int keySizeBits = GetAesKeySizeBits(Encryption);
 01360                _derivedAesKeyMaterial = WinZipAesStream.CreateKey(password, salt: null, keySizeBits);
 1361                // Encryption is already set from constructor (parsed from central directory AES extra field)
 01362            }
 01363        }
 1364
 1365        /// <summary>
 1366        /// Pre-derives encryption key material from a password for use when the entry is later opened for writing.
 1367        /// Called by <see cref="ZipArchive.CreateEntry(string, ReadOnlySpan{char}, ZipEncryptionMethod)"/>.
 1368        /// </summary>
 1369        internal void PrepareEncryption(ReadOnlySpan<char> password, ZipEncryptionMethod encryptionMethod)
 1721370        {
 1721371            if (password.IsEmpty)
 01372            {
 01373                throw new ArgumentException(SR.EmptyPassword, nameof(password));
 1374            }
 1375
 1721376            if (encryptionMethod == ZipEncryptionMethod.ZipCrypto)
 01377            {
 01378                Encryption = encryptionMethod;
 01379                _derivedZipCryptoKeyMaterial = ZipCryptoStream.CreateKey(password);
 01380            }
 1721381            else if (encryptionMethod is ZipEncryptionMethod.Aes128 or ZipEncryptionMethod.Aes192 or ZipEncryptionMethod
 1721382            {
 1383
 1721384                Encryption = encryptionMethod;
 1721385                int keySizeBits = GetAesKeySizeBits(encryptionMethod);
 1721386                _derivedAesKeyMaterial = WinZipAesStream.CreateKey(password, salt: null, keySizeBits);
 1721387            }
 1388            else
 01389            {
 1390                // Covers ZipEncryptionMethod.None, ZipEncryptionMethod.Unknown, and any undefined/out-of-range value.
 01391                throw new ArgumentOutOfRangeException(nameof(encryptionMethod), encryptionMethod, SR.EncryptionNotSpecif
 1392            }
 1721393        }
 1394
 1395        /// <summary>
 1396        /// Creates a WinZip AES extra field for writing to local/central directory headers.
 1397        /// </summary>
 1398        private WinZipAesExtraField CreateAesExtraField()
 6881399        {
 6881400            return new WinZipAesExtraField
 6881401            {
 6881402                VendorVersion = 2,
 6881403                AesStrength = Encryption switch
 6881404                {
 401405                    ZipEncryptionMethod.Aes128 => (byte)1,
 801406                    ZipEncryptionMethod.Aes192 => (byte)2,
 5681407                    ZipEncryptionMethod.Aes256 => (byte)3,
 01408                    _ => throw new InvalidDataException(SR.InvalidAesStrength)
 6881409                },
 6881410                CompressionMethod = _compressionLevel == CompressionLevel.NoCompression
 6881411                    ? (ushort)ZipCompressionMethod.Stored
 6881412                    : (ushort)ZipCompressionMethod.Deflate
 6881413            };
 6881414        }
 1415
 1416        private bool IsOpenable(bool needToUncompress, bool needToLoadIntoMemory, out string? message)
 1721417        {
 1721418            message = null;
 1419
 1721420            if (_originallyInArchive)
 1721421            {
 1721422                if (!IsOpenableInitialVerifications(needToUncompress, out message))
 01423                {
 01424                    return false;
 1425                }
 1426
 1721427                if (!IsEncrypted && !ZipLocalFileHeader.TrySkipBlock(_archive.ArchiveStream))
 01428                {
 01429                    message = SR.LocalFileHeaderCorrupt;
 01430                    return false;
 1431                }
 1721432                else if (IsEncrypted && IsAesEncrypted)
 1721433                {
 1721434                    _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
 1435                    // AES case - skip the local file header and validate it exists.
 1436                    // The AES metadata (encryption strength, actual compression method) was already
 1437                    // parsed from the central directory in the constructor
 1721438                    if (!ZipLocalFileHeader.TrySkipBlock(_archive.ArchiveStream))
 01439                    {
 01440                        message = SR.LocalFileHeaderCorrupt;
 01441                        return false;
 1442                    }
 1721443                }
 1444
 1445                // Pass the detected encryption method to GetOffsetOfCompressedData
 1721446                long offsetOfCompressedData = GetOffsetOfCompressedData();
 1447
 1721448                if (!IsOpenableFinalVerifications(needToLoadIntoMemory, offsetOfCompressedData, out message))
 01449                {
 01450                    return false;
 1451                }
 1452
 1721453                return true;
 1454            }
 1455
 01456            return true;
 1721457        }
 1458
 1459        private bool IsOpenableInitialVerifications(bool needToUncompress, out string? message)
 3441460        {
 3441461            message = null;
 3441462            if (needToUncompress)
 3441463            {
 1464                // For AES-encrypted entries, CompressionMethod now contains the actual compression
 1465                // method (from the AES extra field), not the wrapper value 99. So we can use
 1466                // the same validation logic for both encrypted and non-encrypted entries.
 3441467                if (CompressionMethod != ZipCompressionMethod.Stored &&
 3441468                    CompressionMethod != ZipCompressionMethod.Deflate &&
 3441469                    CompressionMethod != ZipCompressionMethod.Deflate64)
 01470                {
 01471                    message = SR.UnsupportedCompression;
 01472                    return false;
 1473                }
 3441474            }
 3441475            if (_diskNumberStart != _archive.NumberOfThisDisk)
 01476            {
 01477                message = SR.SplitSpanned;
 01478                return false;
 1479            }
 3441480            if (_offsetOfLocalHeader > _archive.ArchiveStream.Length)
 01481            {
 01482                message = SR.LocalFileHeaderCorrupt;
 01483                return false;
 1484            }
 1485
 3441486            _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
 3441487            return true;
 3441488        }
 1489
 1490        private bool IsOpenableFinalVerifications(bool needToLoadIntoMemory, long offsetOfCompressedData, out string? me
 3441491        {
 3441492            message = null;
 1493            // Use an overflow-safe form: corrupt zip64 archives can report compressed sizes / offsets
 1494            // near long.MaxValue, which would wrap around in 'offsetOfCompressedData + _compressedSize'.
 3441495            if (offsetOfCompressedData < 0 ||
 3441496                _compressedSize < 0 ||
 3441497                _compressedSize > _archive.ArchiveStream.Length - offsetOfCompressedData)
 01498            {
 01499                message = SR.LocalFileHeaderCorrupt;
 01500                return false;
 1501            }
 1502            // This limitation originally existed because a) it is unreasonable to load > 4GB into memory
 1503            // but also because the stream reading functions make it hard.  This has been updated to handle
 1504            // this scenario in a 64-bit process using multiple buffers, delivered first as an OOB for
 1505            // compatibility.
 3441506            if (needToLoadIntoMemory)
 01507            {
 01508                if (_compressedSize > int.MaxValue)
 01509                {
 01510                    if (!s_allowLargeZipArchiveEntriesInUpdateMode)
 01511                    {
 01512                        message = SR.EntryTooLarge;
 01513                        return false;
 1514                    }
 01515                }
 01516            }
 1517
 3441518            return true;
 3441519        }
 1520
 3441521        private bool AreSizesTooLarge => _compressedSize > uint.MaxValue || _uncompressedSize > uint.MaxValue;
 1522
 1523        /// <summary>
 1524        /// The position immediately after all entry data (including any trailing data descriptor).
 1525        /// Computed while reading the central directory from the next entry's local header offset
 1526        /// or, for the last entry, the central directory start offset.
 1527        /// </summary>
 1528        internal long EndOfLocalEntryData
 1529        {
 01530            get => _endOfLocalEntryData;
 01531            set => _endOfLocalEntryData = value;
 1532        }
 1533
 1534        private static CompressionLevel MapCompressionLevel(BitFlagValues generalPurposeBitFlag, ZipCompressionMethod co
 1721535        {
 1536            // Information about the Deflate compression option is stored in bits 1 and 2 of the general purpose bit fla
 1537            // If the compression method is not Deflate, the Deflate compression option is invalid - default to NoCompre
 1721538            if (compressionMethod == ZipCompressionMethod.Deflate || compressionMethod == ZipCompressionMethod.Deflate64
 1721539            {
 1721540                return ((int)generalPurposeBitFlag & 0x6) switch
 1721541                {
 1721542                    0 => CompressionLevel.Optimal,
 01543                    2 => CompressionLevel.SmallestSize,
 01544                    4 => CompressionLevel.Fastest,
 01545                    6 => CompressionLevel.Fastest,
 01546                    _ => CompressionLevel.Optimal
 1721547                };
 1548            }
 1549            else
 01550            {
 01551                return CompressionLevel.NoCompression;
 1552            }
 1721553        }
 1554
 1555        private static BitFlagValues MapDeflateCompressionOption(BitFlagValues generalPurposeBitFlag, CompressionLevel c
 1721556        {
 1721557            ushort deflateCompressionOptions = (ushort)(
 1721558                // The Deflate compression level is only valid if the compression method is actually Deflate (or Deflate
 1721559                // value of the two bits is undefined and they should be zeroed out.
 1721560                compressionMethod == ZipCompressionMethod.Deflate || compressionMethod == ZipCompressionMethod.Deflate64
 1721561                    ? compressionLevel switch
 1721562                    {
 1721563                        CompressionLevel.Optimal => 0,
 01564                        CompressionLevel.SmallestSize => 2,
 01565                        CompressionLevel.Fastest => 6,
 01566                        CompressionLevel.NoCompression => 6,
 01567                        _ => 0
 1721568                    }
 1721569                    : 0);
 1570
 1721571            return (BitFlagValues)(((int)generalPurposeBitFlag & ~0x6) | deflateCompressionOptions);
 1721572        }
 1573
 3441574        private bool IsOffsetTooLarge => _offsetOfLocalHeader > uint.MaxValue;
 1575
 1721576        private bool ShouldUseZIP64 => AreSizesTooLarge || IsOffsetTooLarge;
 43001577        internal ZipEncryptionMethod Encryption { get => _encryptionMethod; private set => _encryptionMethod = value; }
 1578
 1579        private unsafe bool WriteLocalFileHeaderInitialize(bool isEmptyFile, bool forceWrite, bool preserveDataDescripto
 1721580        {
 1581            // _entryname only gets set when we read in or call moveTo. MoveTo does a check, and
 1582            // reading in should not be able to produce an entryname longer than ushort.MaxValue
 1721583            Debug.Assert(_storedEntryNameBytes.Length <= ushort.MaxValue);
 1584
 1585            // decide if we need the Zip64 extra field:
 1721586            zip64ExtraField = null;
 1587
 1588            // save offset
 1721589            _offsetOfLocalHeader = _archive.ArchiveStream.Position;
 1590
 1591            // for extra winzip aes header
 1721592            WinZipAesExtraField? aesExtraField = null;
 1721593            int aesExtraFieldSize = 0;
 1594
 1595            // if we already know that we have an empty file don't worry about anything, just do a straight shot of the 
 1721596            if (isEmptyFile)
 01597            {
 01598                CompressionMethod = ZipCompressionMethod.Stored;
 01599                compressedSizeTruncated = 0;
 01600                uncompressedSizeTruncated = 0;
 01601                Debug.Assert(_uncompressedSize == 0);
 01602                Debug.Assert(_crc32 == 0);
 01603            }
 1604            else
 1721605            {
 1721606                bool isCreateMode = _archive.Mode == ZipArchiveMode.Create;
 1607
 1721608                if (Encryption == ZipEncryptionMethod.ZipCrypto)
 01609                {
 01610                    _generalPurposeBitFlag |= BitFlagValues.IsEncrypted;
 01611                    _generalPurposeBitFlag |= BitFlagValues.DataDescriptor;
 01612                    compressedSizeTruncated = 0;
 01613                    uncompressedSizeTruncated = 0;
 01614                }
 1721615                else if (UseAesEncryption)
 1721616                {
 1721617                    _generalPurposeBitFlag |= BitFlagValues.IsEncrypted;
 1721618                    CompressionMethod = (ZipCompressionMethod)WinZipAesMethod;
 1721619                    compressedSizeTruncated = 0;
 1721620                    uncompressedSizeTruncated = 0;
 1721621                    aesExtraField = CreateAesExtraField();
 1721622                    aesExtraFieldSize = WinZipAesExtraField.TotalSize;
 1721623                }
 01624                else if (isCreateMode && !_archive.ArchiveStream.CanSeek)
 01625                {
 01626                    _generalPurposeBitFlag |= BitFlagValues.DataDescriptor;
 01627                    compressedSizeTruncated = 0;
 01628                    uncompressedSizeTruncated = 0;
 01629                    Debug.Assert(_crc32 == 0);
 01630                }
 1631                else
 01632                {
 1633
 01634                    if (ShouldUseZIP64
 01635#if DEBUG_FORCE_ZIP64
 01636                || (_archive._forceZip64 && _archive.Mode == ZipArchiveMode.Update)
 01637#endif
 01638                        )
 01639                    {
 01640                        compressedSizeTruncated = ZipHelper.Mask32Bit;
 01641                        uncompressedSizeTruncated = ZipHelper.Mask32Bit;
 1642
 01643                        zip64ExtraField = new()
 01644                        {
 01645                            CompressedSize = _compressedSize,
 01646                            UncompressedSize = _uncompressedSize,
 01647                        };
 1648
 01649                        VersionToExtractAtLeast(ZipVersionNeededValues.Zip64);
 01650                    }
 1651                    else
 01652                    {
 01653                        compressedSizeTruncated = (uint)_compressedSize;
 01654                        uncompressedSizeTruncated = (uint)_uncompressedSize;
 01655                    }
 01656                }
 1721657            }
 1658
 1659            // save offset
 1721660            _offsetOfLocalHeader = _archive.ArchiveStream.Position;
 1661
 1662            // Calculate extra field
 1663            // When using AES encryption, exclude the AES tag from currExtraFieldDataLength since we're writing a new on
 1721664            int currExtraFieldDataLength = UseAesEncryption
 1721665                ? ZipGenericExtraField.TotalSizeExcludingTag(_lhUnknownExtraFields, _lhTrailingExtraFieldData?.Length ??
 1721666                : ZipGenericExtraField.TotalSize(_lhUnknownExtraFields, _lhTrailingExtraFieldData?.Length ?? 0);
 1721667            int bigExtraFieldLength = (zip64ExtraField != null ? zip64ExtraField.TotalSize : 0)
 1721668                                      + aesExtraFieldSize
 1721669                                      + currExtraFieldDataLength;
 1670
 1721671            if (bigExtraFieldLength > ushort.MaxValue)
 01672            {
 01673                extraFieldLength = (ushort)((zip64ExtraField != null ? zip64ExtraField.TotalSize : 0) + aesExtraFieldSiz
 01674                _lhUnknownExtraFields = null;
 01675            }
 1676            else
 1721677            {
 1721678                extraFieldLength = (ushort)bigExtraFieldLength;
 1721679            }
 1680
 1721681            crc32ToWrite = _crc32;
 1682
 1683            // For AE-2, CRC is always 0 in the local file header
 1721684            if (UseAesEncryption)
 1721685            {
 1721686                crc32ToWrite = 0;
 1721687            }
 1688
 1689            // If this is an existing, unchanged entry then silently skip forwards.
 1690            // If it's new or changed, write the header.
 1721691            if (_originallyInArchive && Changes == ZipArchive.ChangeState.Unchanged && !forceWrite)
 01692            {
 01693                _archive.ArchiveStream.Seek(ZipLocalFileHeader.SizeOfLocalHeader + _storedEntryNameBytes.Length + extraF
 1694
 1695
 1696
 01697                return false;
 1698            }
 1699
 1700            // We are writing the header. For seekable/empty-file paths the sizes are written
 1701            // directly into the header, so a data descriptor is not needed.
 1721702            if (isEmptyFile || _archive.ArchiveStream.CanSeek)
 1721703            {
 1721704                if (preserveDataDescriptor)
 01705                {
 01706                    compressedSizeTruncated = 0;
 01707                    uncompressedSizeTruncated = 0;
 1708
 1709                    // zero the CRC/sizes since the real values live in the trailing descriptor that remains on disk.
 01710                    crc32ToWrite = 0;
 1711
 01712                    if (zip64ExtraField is not null)
 01713                    {
 01714                        zip64ExtraField = new() { CompressedSize = 0, UncompressedSize = 0 };
 01715                    }
 01716                }
 1721717                else if (Encryption != ZipEncryptionMethod.ZipCrypto)
 1721718                {
 1721719                    _generalPurposeBitFlag &= ~BitFlagValues.DataDescriptor;
 1721720                }
 1721721            }
 1722
 1721723            return true;
 1721724        }
 1725
 1726        private void WriteLocalFileHeaderPrepare(Span<byte> lfStaticHeader, uint crc32, uint compressedSizeTruncated, ui
 1721727        {
 1721728            ZipLocalFileHeader.SignatureConstantBytes.CopyTo(lfStaticHeader[ZipLocalFileHeader.FieldLocations.Signature.
 1721729            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.VersionNeededToExt
 1721730            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.GeneralPurposeBitF
 1731
 1732            // For AES encryption, write compression method 99 (Aes) in the header
 1721733            ushort compressionMethodToWrite = UseAesEncryption ? (ushort)WinZipAesMethod : (ushort)CompressionMethod;
 1721734            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.CompressionMethod.
 1735
 1721736            BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.LastModified..], Z
 1721737            BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.Crc32..], crc32);
 1721738            BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.CompressedSize..],
 1721739            BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.UncompressedSize..
 1721740            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.FilenameLength..],
 1721741            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.ExtraFieldLength..
 1721742        }
 1743
 1744        // return value is true if we allocated an extra field for 64 bit headers, un/compressed size
 1745        private unsafe bool WriteLocalFileHeader(bool isEmptyFile, bool forceWrite, bool preserveDataDescriptor = false)
 861746        {
 861747            if (WriteLocalFileHeaderInitialize(isEmptyFile, forceWrite, preserveDataDescriptor, out Zip64ExtraField? zip
 861748            {
 861749                Span<byte> lfStaticHeader = stackalloc byte[ZipLocalFileHeader.SizeOfLocalHeader];
 861750                WriteLocalFileHeaderPrepare(lfStaticHeader, crc32ToWrite, compressedSizeTruncated, uncompressedSizeTrunc
 1751
 1752                // write header
 861753                _archive.ArchiveStream.Write(lfStaticHeader);
 861754                _archive.ArchiveStream.Write(_storedEntryNameBytes);
 1755
 1756                // Write Zip64 extra field if needed
 861757                zip64ExtraField?.WriteBlock(_archive.ArchiveStream);
 1758
 1759                // Write AES extra field if using AES encryption
 861760                if (UseAesEncryption)
 861761                {
 861762                    CreateAesExtraField().WriteBlock(_archive.ArchiveStream);
 1763
 1764                    // Write other extra fields, excluding any existing AES extra field to avoid duplication
 861765                    ZipGenericExtraField.WriteAllBlocksExcludingTag(_lhUnknownExtraFields, _lhTrailingExtraFieldData ?? 
 861766                }
 1767                else
 01768                {
 1769                    // Write other extra fields
 01770                    ZipGenericExtraField.WriteAllBlocks(_lhUnknownExtraFields, _lhTrailingExtraFieldData ?? Array.Empty<
 01771                }
 861772            }
 1773
 861774            return zip64ExtraField != null;
 861775        }
 1776
 1777        private unsafe void WriteLocalFileHeaderAndDataIfNeeded(bool forceWrite)
 861778        {
 1779            // Check if the entry's stored data was actually modified (StoredData flag is set).
 1780            // If _storedUncompressedData is loaded but StoredData is not set, it means the entry
 1781            // was opened for update but no writes occurred - we should use the original compressed bytes.
 861782            bool storedDataModified = (Changes & ZipArchive.ChangeState.StoredData) != 0;
 1783
 1784            // If _storedUncompressedData is loaded but not modified, clear it so we use _compressedBytes
 861785            if (_storedUncompressedData != null && !storedDataModified)
 01786            {
 01787                _storedUncompressedData.Dispose();
 01788                _storedUncompressedData = null;
 01789            }
 1790
 1791            // _storedUncompressedData gets frozen here, and is what gets written to the file
 861792            if (_storedUncompressedData != null || _compressedBytes != null)
 01793            {
 01794                if (_storedUncompressedData != null)
 01795                {
 01796                    _uncompressedSize = _storedUncompressedData.Length;
 1797
 1798                    // Check if we need to re-encrypt with ZipCrypto (only if we have cached key material)
 01799                    if (Encryption == ZipEncryptionMethod.ZipCrypto && _derivedZipCryptoKeyMaterial is not null)
 01800                    {
 01801                        WriteLocalFileHeader(isEmptyFile: false, forceWrite: true);
 1802
 01803                        long startPosition = _archive.ArchiveStream.Position;
 1804
 01805                        ushort verifierLow2Bytes = (ushort)ZipHelper.DateTimeToDosTime(_lastModified.DateTime);
 1806
 01807                        using (ZipCryptoStream encryptionStream = ZipCryptoStream.Create(
 01808                            baseStream: _archive.ArchiveStream,
 01809                            keys: _derivedZipCryptoKeyMaterial.Value,
 01810                            passwordVerifierLow2Bytes: verifierLow2Bytes,
 01811                            encrypting: true,
 01812                            crc32: null,
 01813                            leaveOpen: true))
 01814                        {
 01815                            using (CheckSumAndSizeWriteStream crcStream = GetDataCompressor(encryptionStream, leaveBacki
 01816                            {
 01817                                _storedUncompressedData.Seek(0, SeekOrigin.Begin);
 01818                                _storedUncompressedData.CopyTo(crcStream);
 01819                            }
 01820                        }
 1821
 01822                        _compressedSize = _archive.ArchiveStream.Position - startPosition;
 1823
 01824                        WriteDataDescriptor();
 1825
 01826                        _storedUncompressedData.Dispose();
 01827                        _storedUncompressedData = null;
 01828                    }
 01829                    else if (UseAesEncryption && _derivedAesKeyMaterial is not null)
 01830                    {
 1831
 01832                        bool usedZip64InLH = WriteLocalFileHeader(isEmptyFile: false, forceWrite: true);
 1833
 01834                        long startPosition = _archive.ArchiveStream.Position;
 1835
 01836                        bool useDeflate = _compressionLevel != CompressionLevel.NoCompression;
 1837
 01838                        using (WinZipAesStream encryptionStream = WinZipAesStream.Create(
 01839                            baseStream: _archive.ArchiveStream,
 01840                            keyMaterial: _derivedAesKeyMaterial.Value,
 01841                            totalStreamSize: -1,
 01842                            encrypting: true,
 01843                            leaveOpen: true))
 01844                        {
 01845                            if (_storedUncompressedData.Length > 0)
 01846                            {
 01847                                ZipCompressionMethod savedMethod = CompressionMethod;
 01848                                CompressionMethod = useDeflate ? ZipCompressionMethod.Deflate : ZipCompressionMethod.Sto
 1849
 1850                                try
 01851                                {
 01852                                    using (CheckSumAndSizeWriteStream crcStream = GetDataCompressor(encryptionStream, le
 01853                                    {
 01854                                        _storedUncompressedData.Seek(0, SeekOrigin.Begin);
 01855                                        _storedUncompressedData.CopyTo(crcStream);
 01856                                    }
 01857                                }
 1858                                finally
 01859                                {
 01860                                    CompressionMethod = (ZipCompressionMethod)WinZipAesMethod;
 01861                                }
 01862                            }
 1863                            else
 01864                            {
 01865                                _crc32 = 0;
 01866                                _uncompressedSize = 0;
 01867                            }
 01868                        }
 1869
 01870                        _compressedSize = _archive.ArchiveStream.Position - startPosition;
 1871
 01872                        WriteCrcAndSizesInLocalHeader(usedZip64InLH);
 1873
 01874                        _storedUncompressedData.Dispose();
 01875                        _storedUncompressedData = null;
 01876                    }
 1877                    else
 01878                    {
 1879                        // Non-encrypted: use standard path
 01880                        using (DirectToArchiveWriterStream entryWriter = new(GetDataCompressor(_archive.ArchiveStream, t
 01881                        {
 01882                            _storedUncompressedData.Seek(0, SeekOrigin.Begin);
 01883                            _storedUncompressedData.CopyTo(entryWriter);
 01884                        }
 01885                        _storedUncompressedData.Dispose();
 01886                        _storedUncompressedData = null;
 01887                    }
 01888                }
 1889                else // _compressedBytes path - copying unchanged entry data
 01890                {
 01891                    if (_uncompressedSize == 0)
 01892                    {
 1893                        // reset size to ensure proper central directory size header
 01894                        _compressedSize = 0;
 01895                    }
 1896
 1897                    // For unchanged entries, we need to write the header correctly but avoid
 1898                    // WriteLocalFileHeader creating NEW encryption structures (which would have
 1899                    // wrong compression method from _compressionLevel).
 1900                    // The original AES extra field is preserved in _lhUnknownExtraFields.
 01901                    BitFlagValues savedFlags = _generalPurposeBitFlag;
 01902                    ZipEncryptionMethod savedEncryption = Encryption;
 01903                    ZipCompressionMethod savedCompressionMethod = CompressionMethod;
 1904
 1905                    try
 01906                    {
 1907                        // For AES entries: set CompressionMethod to Aes so header writes method 99,
 1908                        // but clear _encryptionMethod so WriteLocalFileHeader doesn't create a new
 1909                        // AES extra field (the original one in _lhUnknownExtraFields will be used).
 01910                        if (savedEncryption is ZipEncryptionMethod.Aes128 or ZipEncryptionMethod.Aes192 or ZipEncryption
 01911                        {
 01912                            CompressionMethod = (ZipCompressionMethod)WinZipAesMethod;
 01913                            Encryption = ZipEncryptionMethod.None;
 01914                        }
 1915
 01916                        WriteLocalFileHeader(isEmptyFile: _uncompressedSize == 0, forceWrite: true);
 1917
 1918                        // WriteLocalFileHeaderInitialize may have cleared the DataDescriptor flag
 1919                        // (because Encryption was temporarily set to None and the stream is seekable).
 1920                        // If the original entry had a data descriptor, patch the general-purpose bit
 1921                        // flags in the already-written local header to match, so the header on disk
 1922                        // is consistent with the data descriptor we conditionally write below.
 01923                        if ((savedFlags & BitFlagValues.DataDescriptor) != 0 &&
 01924                            (_generalPurposeBitFlag & BitFlagValues.DataDescriptor) == 0)
 01925                        {
 01926                            long currentPos = _archive.ArchiveStream.Position;
 01927                            _archive.ArchiveStream.Seek(
 01928                                _offsetOfLocalHeader + ZipLocalFileHeader.FieldLocations.GeneralPurposeBitFlags,
 01929                                SeekOrigin.Begin);
 01930                            Span<byte> flagBytes = stackalloc byte[2];
 01931                            BinaryPrimitives.WriteUInt16LittleEndian(flagBytes, (ushort)savedFlags);
 01932                            _archive.ArchiveStream.Write(flagBytes);
 01933                            _archive.ArchiveStream.Seek(currentPos, SeekOrigin.Begin);
 01934                        }
 01935                    }
 1936                    finally
 01937                    {
 1938                        // Restore original state
 01939                        _generalPurposeBitFlag = savedFlags;
 01940                        Encryption = savedEncryption;
 01941                        CompressionMethod = savedCompressionMethod;
 01942                    }
 1943
 1944                    // according to ZIP specs, zero-byte files MUST NOT include file data
 01945                    if (_uncompressedSize != 0)
 01946                    {
 01947                        Debug.Assert(_compressedBytes != null);
 01948                        foreach (byte[] compressedBytes in _compressedBytes)
 01949                        {
 01950                            _archive.ArchiveStream.Write(compressedBytes, 0, compressedBytes.Length);
 01951                        }
 01952                    }
 1953
 1954                    // Write data descriptor if the original entry had one
 01955                    if ((savedFlags & BitFlagValues.DataDescriptor) != 0)
 01956                    {
 01957                        WriteDataDescriptor();
 01958                    }
 01959                }
 01960            }
 1961            else // there is no data in the file (or the data in the file has not been loaded), but if we are in update 
 861962            {
 861963                if (_archive.Mode == ZipArchiveMode.Update || !_everOpenedForWrite)
 01964                {
 01965                    _everOpenedForWrite = true;
 1966                    // Preserve the data descriptor flag for entries that originally had one,
 1967                    // since the descriptor bytes remain on disk after the compressed data.
 01968                    bool preserveDataDescriptor = _originallyInArchive
 01969                        && (_generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0;
 01970                    WriteLocalFileHeader(isEmptyFile: _uncompressedSize == 0, forceWrite: forceWrite, preserveDataDescri
 1971
 1972                    // Advance the stream past the compressed data and any trailing data descriptor
 1973                    // by seeking to the pre-computed end-of-entry boundary.
 01974                    if (_endOfLocalEntryData > _archive.ArchiveStream.Position)
 01975                    {
 01976                        _archive.ArchiveStream.Seek(_endOfLocalEntryData, SeekOrigin.Begin);
 01977                    }
 01978                }
 861979            }
 861980        }
 1981
 1982        private const int MetadataBufferLength = ZipLocalFileHeader.FieldLengths.VersionNeededToExtract + ZipLocalFileHe
 1983        private const int CrcAndSizesBufferLength = ZipLocalFileHeader.FieldLengths.Crc32 + ZipLocalFileHeader.FieldLeng
 1984        private const int Zip64SizesBufferLength = Zip64ExtraField.FieldLengths.UncompressedSize + Zip64ExtraField.Field
 1985        private const int Zip64DataDescriptorCrcAndSizesBufferLength = ZipLocalFileHeader.Zip64DataDescriptor.FieldLengt
 1986            + ZipLocalFileHeader.Zip64DataDescriptor.FieldLengths.CompressedSize + ZipLocalFileHeader.Zip64DataDescripto
 1987
 1988        // Using _offsetOfLocalHeader, seeks back to where CRC and sizes should be in the header,
 1989        // writes them, then seeks back to where you started
 1990        // Assumes that the stream is currently at the end of the data
 1991        private unsafe void WriteCrcAndSizesInLocalHeader(bool zip64HeaderUsed)
 861992        {
 1993            // Buffer has been sized to the largest data payload required: the 64-bit data descriptor.
 861994            Span<byte> writeBuffer = stackalloc byte[Zip64DataDescriptorCrcAndSizesBufferLength];
 1995
 861996            WriteCrcAndSizesInLocalHeaderInitialize(zip64HeaderUsed, out long finalPosition, out bool pretendStreaming, 
 1997
 1998            // first step is, if we need zip64, but didn't allocate it, pretend we did a stream write, because
 1999            // we can't go back and give ourselves the space that the extra field needs.
 2000            // we do this by setting the correct property in the bit flag to indicate we have a data descriptor
 2001            // and setting the version to Zip64 to indicate that descriptor contains 64-bit values
 862002            if (pretendStreaming)
 02003            {
 02004                WriteCrcAndSizesInLocalHeaderPrepareForZip64PretendStreaming(writeBuffer);
 02005                _archive.ArchiveStream.Write(writeBuffer[..MetadataBufferLength]);
 02006            }
 2007
 2008            // next step is fill out the 32-bit size values in the normal header. we can't assume that
 2009            // they are correct. we also write the CRC
 862010            WriteCrcAndSizesInLocalHeaderPrepareFor32bitValuesWriting(pretendStreaming, writeBuffer, compressedSizeTrunc
 862011            _archive.ArchiveStream.Write(writeBuffer[..CrcAndSizesBufferLength]);
 2012
 2013            // next step: if we wrote the 64 bit header initially, a different implementation might
 2014            // try to read it, even if the 32-bit size values aren't masked. thus, we should always put the
 2015            // correct size information in there. note that order of uncomp/comp is switched, and these are
 2016            // 64-bit values
 2017            // also, note that in order for this to be correct, we have to ensure that the zip64 extra field
 2018            // is always the first extra field that is written
 862019            if (zip64HeaderUsed)
 02020            {
 02021                WriteCrcAndSizesInLocalHeaderPrepareForWritingWhenZip64HeaderUsed(writeBuffer);
 02022                _archive.ArchiveStream.Write(writeBuffer[..Zip64SizesBufferLength]);
 02023            }
 2024
 2025            // now go to the where we were. assume that this is the end of the data
 862026            _archive.ArchiveStream.Seek(finalPosition, SeekOrigin.Begin);
 2027
 2028            // if we are pretending we did a stream write, we want to write the data descriptor out
 2029            // the data descriptor can have 32-bit sizes or 64-bit sizes. In this case, we always use
 2030            // 64-bit sizes
 862031            if (pretendStreaming)
 02032            {
 02033                WriteCrcAndSizesInLocalHeaderPrepareForWritingDataDescriptor(writeBuffer);
 02034                _archive.ArchiveStream.Write(writeBuffer[..Zip64DataDescriptorCrcAndSizesBufferLength]);
 02035            }
 862036        }
 2037
 2038        private void WriteCrcAndSizesInLocalHeaderInitialize(bool zip64HeaderUsed, out long finalPosition, out bool pret
 1722039        {
 1722040            finalPosition = _archive.ArchiveStream.Position;
 2041
 1722042            bool zip64Needed = ShouldUseZIP64
 1722043#if DEBUG_FORCE_ZIP64
 1722044                || _archive._forceZip64
 1722045#endif
 1722046            ;
 2047
 1722048            pretendStreaming = zip64Needed && !zip64HeaderUsed;
 1722049            compressedSizeTruncated = zip64Needed ? ZipHelper.Mask32Bit : (uint)_compressedSize;
 1722050            uncompressedSizeTruncated = zip64Needed ? ZipHelper.Mask32Bit : (uint)_uncompressedSize;
 1722051        }
 2052
 2053        private void WriteCrcAndSizesInLocalHeaderPrepareForZip64PretendStreaming(Span<byte> writeBuffer)
 02054        {
 02055            int relativeVersionToExtractLocation = ZipLocalFileHeader.FieldLocations.VersionNeededToExtract - ZipLocalFi
 02056            int relativeGeneralPurposeBitFlagsLocation = ZipLocalFileHeader.FieldLocations.GeneralPurposeBitFlags - ZipL
 2057
 02058            VersionToExtractAtLeast(ZipVersionNeededValues.Zip64);
 02059            _generalPurposeBitFlag |= BitFlagValues.DataDescriptor;
 2060
 02061            _archive.ArchiveStream.Seek(_offsetOfLocalHeader + ZipLocalFileHeader.FieldLocations.VersionNeededToExtract,
 02062                                        SeekOrigin.Begin);
 02063            BinaryPrimitives.WriteUInt16LittleEndian(writeBuffer[relativeVersionToExtractLocation..], (ushort)_versionTo
 02064            BinaryPrimitives.WriteUInt16LittleEndian(writeBuffer[relativeGeneralPurposeBitFlagsLocation..], (ushort)_gen
 02065        }
 2066
 2067        private void WriteCrcAndSizesInLocalHeaderPrepareFor32bitValuesWriting(bool pretendStreaming, Span<byte> writeBu
 1722068        {
 1722069            _archive.ArchiveStream.Seek(_offsetOfLocalHeader + ZipLocalFileHeader.FieldLocations.Crc32,
 1722070                                            SeekOrigin.Begin);
 1722071            if (!pretendStreaming)
 1722072            {
 1722073                int relativeCrc32Location = ZipLocalFileHeader.FieldLocations.Crc32 - ZipLocalFileHeader.FieldLocations.
 1722074                int relativeCompressedSizeLocation = ZipLocalFileHeader.FieldLocations.CompressedSize - ZipLocalFileHead
 1722075                int relativeUncompressedSizeLocation = ZipLocalFileHeader.FieldLocations.UncompressedSize - ZipLocalFile
 2076                // when using aes encryption, ae-2 standard dictates crc to be 0
 1722077                uint crcToWrite = UseAesEncryption ? 0 : _crc32;
 1722078                BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer[relativeCrc32Location..], crcToWrite);
 1722079                BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer[relativeCompressedSizeLocation..], compressedSizeTr
 1722080                BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer[relativeUncompressedSizeLocation..], uncompressedSi
 1722081            }
 2082            else // but if we are pretending to stream, we want to fill in with zeroes
 02083            {
 02084                writeBuffer[..CrcAndSizesBufferLength].Clear();
 02085            }
 1722086        }
 2087
 2088        private void WriteCrcAndSizesInLocalHeaderPrepareForWritingWhenZip64HeaderUsed(Span<byte> writeBuffer)
 02089        {
 02090            int relativeUncompressedSizeLocation = Zip64ExtraField.FieldLocations.UncompressedSize - Zip64ExtraField.Fie
 02091            int relativeCompressedSizeLocation = Zip64ExtraField.FieldLocations.CompressedSize - Zip64ExtraField.FieldLo
 2092
 02093            _archive.ArchiveStream.Seek(_offsetOfLocalHeader + ZipLocalFileHeader.SizeOfLocalHeader
 02094                                        + _storedEntryNameBytes.Length + Zip64ExtraField.OffsetToFirstField,
 02095                                        SeekOrigin.Begin);
 02096            BinaryPrimitives.WriteInt64LittleEndian(writeBuffer[relativeUncompressedSizeLocation..], _uncompressedSize);
 02097            BinaryPrimitives.WriteInt64LittleEndian(writeBuffer[relativeCompressedSizeLocation..], _compressedSize);
 02098        }
 2099
 2100        private void WriteCrcAndSizesInLocalHeaderPrepareForWritingDataDescriptor(Span<byte> writeBuffer)
 02101        {
 02102            int relativeCrc32Location = ZipLocalFileHeader.Zip64DataDescriptor.FieldLocations.Crc32 - ZipLocalFileHeader
 02103            int relativeCompressedSizeLocation = ZipLocalFileHeader.Zip64DataDescriptor.FieldLocations.CompressedSize - 
 02104            int relativeUncompressedSizeLocation = ZipLocalFileHeader.Zip64DataDescriptor.FieldLocations.UncompressedSiz
 2105            // when using aes encryption, ae-2 standard dictates crc to be 0
 02106            uint crcToWrite = UseAesEncryption ? 0 : _crc32;
 02107            BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer.Slice(relativeCrc32Location), crcToWrite);
 02108            BinaryPrimitives.WriteInt64LittleEndian(writeBuffer.Slice(relativeCompressedSizeLocation), _compressedSize);
 02109            BinaryPrimitives.WriteInt64LittleEndian(writeBuffer.Slice(relativeUncompressedSizeLocation), _uncompressedSi
 2110
 02111        }
 2112
 2113        // data descriptor can be 32-bit or 64-bit sizes. 32-bit is more compatible, so use that if possible
 2114        // signature is optional but recommended by the spec
 2115        private const int MaxSizeOfDataDescriptor = 24;
 2116
 2117        private unsafe void WriteDataDescriptor()
 02118        {
 02119            Span<byte> dataDescriptor = stackalloc byte[MaxSizeOfDataDescriptor];
 02120            int bytesToWrite = PrepareToWriteDataDescriptor(dataDescriptor);
 02121            _archive.ArchiveStream.Write(dataDescriptor[..bytesToWrite]);
 02122        }
 2123
 2124        private int PrepareToWriteDataDescriptor(Span<byte> dataDescriptor)
 02125        {
 2126            // We enter here because we cannot seek, so the data descriptor bit should be on
 02127            Debug.Assert((_generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0);
 2128
 2129            int bytesToWrite;
 2130
 02131            ZipLocalFileHeader.DataDescriptorSignatureConstantBytes.CopyTo(dataDescriptor[ZipLocalFileHeader.ZipDataDesc
 2132            // when using aes encryption, ae-2 standard dictates crc to be 0
 02133            uint crcToWrite = UseAesEncryption ? 0 : _crc32;
 02134            BinaryPrimitives.WriteUInt32LittleEndian(dataDescriptor[ZipLocalFileHeader.ZipDataDescriptor.FieldLocations.
 2135
 02136            if (AreSizesTooLarge)
 02137            {
 02138                BinaryPrimitives.WriteInt64LittleEndian(dataDescriptor[ZipLocalFileHeader.Zip64DataDescriptor.FieldLocat
 02139                BinaryPrimitives.WriteInt64LittleEndian(dataDescriptor[ZipLocalFileHeader.Zip64DataDescriptor.FieldLocat
 2140
 02141                bytesToWrite = ZipLocalFileHeader.Zip64DataDescriptor.FieldLocations.UncompressedSize + ZipLocalFileHead
 02142            }
 2143            else
 02144            {
 02145                BinaryPrimitives.WriteUInt32LittleEndian(dataDescriptor[ZipLocalFileHeader.ZipDataDescriptor.FieldLocati
 02146                BinaryPrimitives.WriteUInt32LittleEndian(dataDescriptor[ZipLocalFileHeader.ZipDataDescriptor.FieldLocati
 2147
 02148                bytesToWrite = ZipLocalFileHeader.ZipDataDescriptor.FieldLocations.UncompressedSize + ZipLocalFileHeader
 02149            }
 2150
 02151            return bytesToWrite;
 02152        }
 2153
 2154        internal void UnloadStreams()
 862155        {
 862156            _storedUncompressedData?.Dispose();
 862157            _compressedBytes = null;
 862158            _outstandingWriteStream = null;
 862159        }
 2160
 2161        private void CloseStreams()
 862162        {
 2163            // if the user left the stream open, close the underlying stream for them
 862164            _outstandingWriteStream?.Dispose();
 862165        }
 2166
 2167        private void VersionToExtractAtLeast(ZipVersionNeededValues value)
 3442168        {
 3442169            if (_versionToExtract < value)
 1722170            {
 1722171                _versionToExtract = value;
 1722172                Changes |= ZipArchive.ChangeState.FixedLengthMetadata;
 1722173            }
 3442174            if (_versionMadeBySpecification < value)
 1722175            {
 1722176                _versionMadeBySpecification = value;
 1722177                Changes |= ZipArchive.ChangeState.FixedLengthMetadata;
 1722178            }
 3442179        }
 2180
 2181        private void ThrowIfInvalidArchive()
 5162182        {
 5162183            if (_archive == null)
 02184            {
 02185                throw new InvalidOperationException(SR.DeletedEntry);
 2186            }
 5162187            _archive.ThrowIfDisposed();
 5162188        }
 2189
 2190        /// <summary>
 2191        /// Gets the file name of the path based on Windows path separator characters
 2192        /// </summary>
 2193        private static string GetFileName_Windows(string path)
 3442194        {
 3442195            int i = path.AsSpan().LastIndexOfAny('\\', '/', ':');
 3442196            return i >= 0 ?
 3442197                path.Substring(i + 1) :
 3442198                path;
 3442199        }
 2200
 2201        /// <summary>
 2202        /// Gets the file name of the path based on Unix path separator characters
 2203        /// </summary>
 2204        private static string GetFileName_Unix(string path)
 02205        {
 02206            int i = path.LastIndexOf('/');
 02207            return i >= 0 ?
 02208                path.Substring(i + 1) :
 02209                path;
 02210        }
 2211
 2212        private sealed class DirectToArchiveWriterStream : Stream
 2213        {
 2214            private long _position;
 2215            private readonly CheckSumAndSizeWriteStream _crcSizeStream;
 2216            private bool _everWritten;
 2217            private bool _isDisposed;
 2218            private readonly ZipArchiveEntry _entry;
 2219            private bool _usedZip64inLH;
 2220            private bool _canWrite;
 2221            private readonly ZipEncryptionMethod _encryption;
 2222            private readonly Stream? _encryptionStream;
 2223
 2224            // makes the assumption that somewhere down the line, crcSizeStream is eventually writing directly to the ar
 2225            // this class calls other functions on ZipArchiveEntry that write directly to the archive
 1722226            public DirectToArchiveWriterStream(CheckSumAndSizeWriteStream crcSizeStream, ZipArchiveEntry entry, ZipEncry
 1722227            {
 1722228                _position = 0;
 1722229                _crcSizeStream = crcSizeStream;
 1722230                _everWritten = false;
 1722231                _isDisposed = false;
 1722232                _entry = entry;
 1722233                _usedZip64inLH = false;
 1722234                _canWrite = true;
 1722235                _encryption = encryptionMethod;
 1722236                _encryptionStream = encryptionStream;
 1722237            }
 2238
 2239            public override long Length
 2240            {
 2241                get
 02242                {
 02243                    ThrowIfDisposed();
 02244                    throw new NotSupportedException(SR.SeekingNotSupported);
 2245                }
 2246            }
 2247            public override long Position
 2248            {
 2249                get
 02250                {
 02251                    ThrowIfDisposed();
 02252                    return _position;
 02253                }
 2254                set
 02255                {
 02256                    ThrowIfDisposed();
 02257                    throw new NotSupportedException(SR.SeekingNotSupported);
 2258                }
 2259            }
 2260
 02261            public override bool CanRead => false;
 02262            public override bool CanSeek => false;
 3442263            public override bool CanWrite => _canWrite;
 2264
 2265            private void ThrowIfDisposed()
 1722266            {
 1722267                if (_isDisposed)
 02268                {
 02269                    throw new ObjectDisposedException(GetType().ToString(), SR.HiddenStreamName);
 2270                }
 1722271            }
 2272
 2273            public override int Read(byte[] buffer, int offset, int count)
 02274            {
 02275                ThrowIfDisposed();
 02276                throw new NotSupportedException(SR.ReadingNotSupported);
 2277            }
 2278
 2279            public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToke
 02280            {
 02281                ThrowIfDisposed();
 02282                throw new NotSupportedException(SR.ReadingNotSupported);
 2283            }
 2284
 2285            public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
 02286            {
 02287                ThrowIfDisposed();
 02288                throw new NotSupportedException(SR.ReadingNotSupported);
 2289            }
 2290
 2291            public override long Seek(long offset, SeekOrigin origin)
 02292            {
 02293                ThrowIfDisposed();
 02294                throw new NotSupportedException(SR.SeekingNotSupported);
 2295            }
 2296
 2297            public override void SetLength(long value)
 02298            {
 02299                ThrowIfDisposed();
 02300                throw new NotSupportedException(SR.SetLengthRequiresSeekingAndWriting);
 2301            }
 2302
 2303            // careful: assumes that write is the only way to write to the stream, if writebyte/beginwrite are implement
 2304            // they must set _everWritten, etc.
 2305            public override void Write(byte[] buffer, int offset, int count)
 862306            {
 862307                ValidateBufferArguments(buffer, offset, count);
 2308
 862309                ThrowIfDisposed();
 862310                Debug.Assert(CanWrite);
 2311
 2312                // if we're not actually writing anything, we don't want to trigger the header
 862313                if (count == 0)
 02314                {
 02315                    return;
 2316                }
 2317
 862318                if (!_everWritten)
 862319                {
 862320                    _everWritten = true;
 2321                    // write local header, we are good to go
 862322                    _usedZip64inLH = _entry.WriteLocalFileHeader(isEmptyFile: false, forceWrite: true);
 862323                }
 2324
 862325                _crcSizeStream.Write(buffer, offset, count);
 862326                _position += count;
 862327            }
 2328
 2329            public override void Write(ReadOnlySpan<byte> source)
 02330            {
 02331                ThrowIfDisposed();
 02332                Debug.Assert(CanWrite);
 2333
 2334                // if we're not actually writing anything, we don't want to trigger the header
 02335                if (source.Length == 0)
 02336                {
 02337                    return;
 2338                }
 2339
 02340                if (!_everWritten)
 02341                {
 02342                    _everWritten = true;
 2343                    // write local header, we are good to go
 02344                    _usedZip64inLH = _entry.WriteLocalFileHeader(isEmptyFile: false, forceWrite: true);
 02345                }
 2346
 02347                _crcSizeStream.Write(source);
 02348                _position += source.Length;
 02349            }
 2350
 2351            public override void WriteByte(byte value) =>
 02352                Write(new ReadOnlySpan<byte>(in value));
 2353
 2354            public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
 02355            {
 02356                ValidateBufferArguments(buffer, offset, count);
 02357                return WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken).AsTask();
 02358            }
 2359
 2360            public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = defa
 862361            {
 862362                ThrowIfDisposed();
 862363                Debug.Assert(CanWrite);
 2364
 862365                return !buffer.IsEmpty ?
 862366                    Core(buffer, cancellationToken) :
 862367                    default;
 2368
 2369                async ValueTask Core(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
 862370                {
 862371                    if (!_everWritten)
 862372                    {
 862373                        _everWritten = true;
 2374                        // write local header, we are good to go
 862375                        _usedZip64inLH = await _entry.WriteLocalFileHeaderAsync(isEmptyFile: false, forceWrite: true, pr
 862376                    }
 2377
 862378                    await _crcSizeStream.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
 862379                    _position += buffer.Length;
 862380                }
 862381            }
 2382
 2383            public override void Flush()
 02384            {
 02385                ThrowIfDisposed();
 02386                Debug.Assert(CanWrite);
 2387
 02388                _crcSizeStream.Flush();
 02389            }
 2390
 2391            public override Task FlushAsync(CancellationToken cancellationToken)
 02392            {
 02393                ThrowIfDisposed();
 02394                Debug.Assert(CanWrite);
 2395
 02396                return _crcSizeStream.FlushAsync(cancellationToken);
 02397            }
 2398
 2399            protected override void Dispose(bool disposing)
 1722400            {
 1722401                if (disposing && !_isDisposed)
 862402                {
 862403                    _crcSizeStream.Dispose(); // now we have size/crc info
 2404
 2405                    // If no data was written through CheckSumAndSizeWriteStream, its lazy _baseStream
 2406                    // (DeflateStream wrapping the encryption stream) was never created, so the encryption
 2407                    // stream would be orphaned. Dispose it explicitly to finalize encryption
 2408                    // (e.g., write the ZipCrypto 12-byte header or AES salt/verifier/HMAC).
 862409                    if (!_everWritten)
 02410                    {
 02411                        _encryptionStream?.Dispose();
 2412
 2413                        // write local header, no data, so we use stored
 02414                        _entry.WriteLocalFileHeader(isEmptyFile: true, forceWrite: true);
 02415                    }
 2416                    else
 862417                    {
 2418                        // go back and finish writing
 862419                        if (_entry._archive.ArchiveStream.CanSeek)
 862420                        {
 2421                            // finish writing local header if we have seek capabilities
 862422                            _entry.WriteCrcAndSizesInLocalHeader(_usedZip64inLH);
 2423
 2424                            // ZipCrypto entries retain DataDescriptor for check byte correctness;
 2425                            // write the trailing descriptor so the archive is consistent.
 862426                            if ((_entry._generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0)
 02427                            {
 02428                                _entry.WriteDataDescriptor();
 02429                            }
 862430                        }
 2431                        else
 2432                            // write out data descriptor if we don't have seek capabilities
 02433                            _entry.WriteDataDescriptor();
 862434                    }
 862435                    _canWrite = false;
 862436                    _isDisposed = true;
 862437                }
 2438
 1722439                base.Dispose(disposing);
 1722440            }
 2441
 2442            public override async ValueTask DisposeAsync()
 862443            {
 862444                if (!_isDisposed)
 862445                {
 862446                    await _crcSizeStream.DisposeAsync().ConfigureAwait(false); // now we have size/crc info
 2447
 2448                    // If no data was written through CheckSumAndSizeWriteStream, its lazy _baseStream
 2449                    // (DeflateStream wrapping the encryption stream) was never created, so the encryption
 2450                    // stream would be orphaned. Dispose it explicitly to finalize encryption
 2451                    // (e.g., write the ZipCrypto 12-byte header or AES salt/verifier/HMAC).
 862452                    if (!_everWritten)
 02453                    {
 02454                        if (_encryptionStream is not null)
 02455                        {
 02456                            await _encryptionStream.DisposeAsync().ConfigureAwait(false);
 02457                        }
 2458
 2459                        // write local header, no data, so we use stored
 02460                        await _entry.WriteLocalFileHeaderAsync(isEmptyFile: true, forceWrite: true, preserveDataDescript
 02461                    }
 2462                    else
 862463                    {
 2464                        // go back and finish writing
 862465                        if (_entry._archive.ArchiveStream.CanSeek)
 862466                        {
 2467                            // finish writing local header if we have seek capabilities
 862468                            await _entry.WriteCrcAndSizesInLocalHeaderAsync(_usedZip64inLH, cancellationToken: default).
 2469
 2470                            // ZipCrypto entries retain DataDescriptor for check byte correctness;
 2471                            // write the trailing descriptor so the archive is consistent.
 862472                            if ((_entry._generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0)
 02473                            {
 02474                                await _entry.WriteDataDescriptorAsync(cancellationToken: default).ConfigureAwait(false);
 02475                            }
 862476                        }
 2477                        else
 2478                            // write out data descriptor if we don't have seek capabilities
 02479                            await _entry.WriteDataDescriptorAsync(cancellationToken: default).ConfigureAwait(false);
 862480                    }
 862481                    _canWrite = false;
 862482                    _isDisposed = true;
 862483                }
 2484
 862485                await base.DisposeAsync().ConfigureAwait(false);
 862486            }
 2487        }
 2488        [Flags]
 2489        internal enum BitFlagValues : ushort
 2490        {
 2491            IsEncrypted = 0x1,
 2492            DataDescriptor = 0x8,
 2493            StrongEncryption = 0x40,
 2494            UnicodeFileNameAndComment = 0x800
 2495        }
 2496
 2497        internal sealed class LocalHeaderOffsetComparer : Comparer<ZipArchiveEntry>
 2498        {
 02499            private static readonly LocalHeaderOffsetComparer s_instance = new LocalHeaderOffsetComparer();
 2500
 02501            public static LocalHeaderOffsetComparer Instance => s_instance;
 2502
 2503            // Newly added ZipArchiveEntry records should always go to the end of the file.
 2504            public override int Compare(ZipArchiveEntry? x, ZipArchiveEntry? y)
 02505            {
 02506                long xOffset = x != null && !x.OriginallyInArchive ? long.MaxValue : x?.OffsetOfLocalHeader ?? long.MinV
 02507                long yOffset = y != null && !y.OriginallyInArchive ? long.MaxValue : y?.OffsetOfLocalHeader ?? long.MinV
 2508
 02509                return xOffset.CompareTo(yOffset);
 02510            }
 2511        }
 2512    }
 2513}

D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\ZipArchiveEntry.Windows.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4namespace System.IO.Compression
 5{
 6    public partial class ZipArchiveEntry
 7    {
 8        internal const ZipVersionMadeByPlatform CurrentZipPlatform = ZipVersionMadeByPlatform.Windows;
 9
 10        /// <summary>
 11        /// To get the file name of a ZipArchiveEntry, we should be parsing the FullName based
 12        /// on the path specifications and requirements of the OS that ZipArchive was created on.
 13        /// This method takes in a FullName and the platform of the ZipArchiveEntry and returns
 14        /// the platform-correct file name.
 15        /// </summary>
 16        /// <remarks>This method ensures no validation on the paths. Invalid characters are allowed.</remarks>
 17        internal static string ParseFileName(string path, ZipVersionMadeByPlatform madeByPlatform) =>
 34418            madeByPlatform == ZipVersionMadeByPlatform.Unix ? GetFileName_Unix(path) : GetFileName_Windows(path);
 19    }
 20}

Methods/Properties

OpenAsync(System.Threading.CancellationToken)
OpenAsync(System.IO.FileAccess,System.Threading.CancellationToken)
OpenAsync(System.IO.FileAccess,System.ReadOnlySpan`1<System.Char>,System.Threading.CancellationToken)
OpenAsync(System.ReadOnlySpan`1<System.Char>,System.Threading.CancellationToken)
OpenAsyncCore(System.IO.FileAccess,System.ReadOnlySpan`1<System.Char>,System.Threading.CancellationToken)
GetOffsetOfCompressedDataAsync(System.Threading.CancellationToken)
ReadEncryptionSaltIfNeededAsync(System.Threading.CancellationToken)
OpenInReadModeAsync(System.Boolean,System.ReadOnlySpan`1<System.Char>,System.Threading.CancellationToken)
OpenInReadModeAsyncCore(System.Boolean,System.Nullable`1<System.IO.Compression.WinZipAesKeyMaterial>,System.Nullable`1<System.IO.Compression.ZipCryptoKeys>,System.Byte,System.Threading.CancellationToken)
OpenInUpdateModeAsync(System.Boolean,System.Threading.CancellationToken)
OpenInUpdateModeForReadAsync(System.Threading.CancellationToken)
GetUncompressedDataAsync(System.Threading.CancellationToken)
WriteAndFinishLocalEntryAsync(System.Boolean,System.Threading.CancellationToken)
WriteCentralDirectoryFileHeaderAsync(System.Boolean,System.Threading.CancellationToken)
LoadLocalHeaderExtraFieldIfNeededAsync(System.Threading.CancellationToken)
LoadCompressedBytesIfNeededAsync(System.Threading.CancellationToken)
GetIsOpenableAsync(System.Boolean,System.Boolean,System.Threading.CancellationToken)
ThrowIfNotOpenableAsync(System.Boolean,System.Boolean,System.Threading.CancellationToken)
OpenInUpdateModeWithPasswordAsync(System.Boolean,System.ReadOnlySpan`1<System.Char>,System.Threading.CancellationToken)
CastToStreamAsync(System.Threading.Tasks.Task`1<System.IO.Compression.WrappedStream>)
DecryptAndStoreForUpdateWithZipCryptoAsync(System.IO.Compression.ZipCryptoKeys,System.Byte,System.Threading.CancellationToken)
DecryptAndStoreForUpdateWithAesAsync(System.IO.Compression.WinZipAesKeyMaterial,System.Threading.CancellationToken)
StoreDecryptedDataForUpdateAsync(System.IO.Stream,System.Threading.CancellationToken)
IsOpenableAsync(System.Boolean,System.Boolean,System.Threading.CancellationToken)
WriteLocalFileHeaderAsync(System.Boolean,System.Boolean,System.Boolean,System.Threading.CancellationToken)
WriteLocalFileHeaderAndDataIfNeededAsync(System.Boolean,System.Threading.CancellationToken)
WriteCrcAndSizesInLocalHeaderAsync(System.Boolean,System.Threading.CancellationToken)
WriteDataDescriptorAsync(System.Threading.CancellationToken)
UnloadStreamsAsync()
CloseStreamsAsync()
.ctor(System.IO.Compression.ZipArchive,System.IO.Compression.ZipCentralDirectoryFileHeader)
.ctor(System.IO.Compression.ZipArchive,System.String,System.IO.Compression.CompressionLevel)
.ctor(System.IO.Compression.ZipArchive,System.String)
Archive()
Crc32()
IsEncrypted()
EncryptionMethod()
CompressionMethod()
CompressionMethod(System.IO.Compression.ZipCompressionMethod)
CompressedLength()
ExternalAttributes()
ExternalAttributes(System.Int32)
Comment()
Comment(System.String)
FullName()
FullName(System.String)
ToString()
LastWriteTime()
LastWriteTime(System.DateTimeOffset)
Length()
Name()
VersionMadeBy()
Changes()
OriginallyInArchive()
OffsetOfLocalHeader()
Delete()
Open()
Open(System.ReadOnlySpan`1<System.Char>)
Open(System.IO.FileAccess)
Open(System.IO.FileAccess,System.ReadOnlySpan`1<System.Char>)
InferAccessFromMode()
ValidateAccessForMode(System.IO.FileAccess)
OpenCore(System.IO.FileAccess,System.ReadOnlySpan`1<System.Char>)
DecodeEntryString(System.Byte[])
.cctor()
EverOpenedForWrite()
GetOffsetOfCompressedData()
ReadEncryptionSaltIfNeeded()
GetUncompressedData(System.ReadOnlySpan`1<System.Char>)
WriteAndFinishLocalEntry(System.Boolean)
WriteCentralDirectoryFileHeaderInitialize(System.Boolean,System.IO.Compression.Zip64ExtraField&,System.UInt32&,System.UInt32&,System.UInt16&,System.UInt32&)
WriteCentralDirectoryFileHeaderPrepare(System.Span`1<System.Byte>,System.UInt32,System.UInt32,System.UInt16,System.UInt32)
WriteCentralDirectoryFileHeader(System.Boolean)
LoadLocalHeaderExtraFieldIfNeeded()
LoadCompressedBytesIfNeededInitialize(System.Int32&)
LoadCompressedBytesIfNeeded()
ThrowIfNotOpenable(System.Boolean,System.Boolean)
DetectEntryNameVersion()
GetDataCompressor(System.IO.Stream,System.Boolean,System.EventHandler,System.IO.Stream)
CalculateZipCryptoCheckByte()
IsZipCryptoEncrypted()
UseAesEncryption()
IsAesEncrypted()
GetAesKeySizeBits(System.IO.Compression.ZipEncryptionMethod)
WrapWithDecryptionIfNeeded(System.IO.Stream,System.ReadOnlySpan`1<System.Char>)
GetDataDecompressor(System.IO.Stream)
OpenInReadMode(System.Boolean,System.ReadOnlySpan`1<System.Char>)
OpenInReadModeGetDataCompressor(System.Int64,System.ReadOnlySpan`1<System.Char>)
BuildDecompressionPipeline(System.IO.Stream)
OpenInWriteMode()
OpenInWriteModeCore()
OpenInUpdateMode(System.Boolean,System.ReadOnlySpan`1<System.Char>)
OpenInUpdateModeForRead()
MarkAsModified()
SetupEncryptionKeyMaterial(System.ReadOnlySpan`1<System.Char>)
PrepareEncryption(System.ReadOnlySpan`1<System.Char>,System.IO.Compression.ZipEncryptionMethod)
CreateAesExtraField()
IsOpenable(System.Boolean,System.Boolean,System.String&)
IsOpenableInitialVerifications(System.Boolean,System.String&)
IsOpenableFinalVerifications(System.Boolean,System.Int64,System.String&)
AreSizesTooLarge()
EndOfLocalEntryData()
EndOfLocalEntryData(System.Int64)
MapCompressionLevel(System.IO.Compression.ZipArchiveEntry/BitFlagValues,System.IO.Compression.ZipCompressionMethod)
MapDeflateCompressionOption(System.IO.Compression.ZipArchiveEntry/BitFlagValues,System.IO.Compression.CompressionLevel,System.IO.Compression.ZipCompressionMethod)
IsOffsetTooLarge()
ShouldUseZIP64()
Encryption()
WriteLocalFileHeaderInitialize(System.Boolean,System.Boolean,System.Boolean,System.IO.Compression.Zip64ExtraField&,System.UInt32&,System.UInt32&,System.UInt16&,System.UInt32&)
WriteLocalFileHeaderPrepare(System.Span`1<System.Byte>,System.UInt32,System.UInt32,System.UInt32,System.UInt16)
WriteLocalFileHeader(System.Boolean,System.Boolean,System.Boolean)
WriteLocalFileHeaderAndDataIfNeeded(System.Boolean)
WriteCrcAndSizesInLocalHeader(System.Boolean)
WriteCrcAndSizesInLocalHeaderInitialize(System.Boolean,System.Int64&,System.Boolean&,System.UInt32&,System.UInt32&)
WriteCrcAndSizesInLocalHeaderPrepareForZip64PretendStreaming(System.Span`1<System.Byte>)
WriteCrcAndSizesInLocalHeaderPrepareFor32bitValuesWriting(System.Boolean,System.Span`1<System.Byte>,System.UInt32,System.UInt32)
WriteCrcAndSizesInLocalHeaderPrepareForWritingWhenZip64HeaderUsed(System.Span`1<System.Byte>)
WriteCrcAndSizesInLocalHeaderPrepareForWritingDataDescriptor(System.Span`1<System.Byte>)
WriteDataDescriptor()
PrepareToWriteDataDescriptor(System.Span`1<System.Byte>)
UnloadStreams()
CloseStreams()
VersionToExtractAtLeast(System.IO.Compression.ZipVersionNeededValues)
ThrowIfInvalidArchive()
GetFileName_Windows(System.String)
GetFileName_Unix(System.String)
.ctor(System.IO.Compression.CheckSumAndSizeWriteStream,System.IO.Compression.ZipArchiveEntry,System.IO.Compression.ZipEncryptionMethod,System.IO.Stream)
Length()
Position()
Position(System.Int64)
CanRead()
CanSeek()
CanWrite()
ThrowIfDisposed()
Read(System.Byte[],System.Int32,System.Int32)
ReadAsync(System.Byte[],System.Int32,System.Int32,System.Threading.CancellationToken)
ReadAsync(System.Memory`1<System.Byte>,System.Threading.CancellationToken)
Seek(System.Int64,System.IO.SeekOrigin)
SetLength(System.Int64)
Write(System.Byte[],System.Int32,System.Int32)
Write(System.ReadOnlySpan`1<System.Byte>)
WriteByte(System.Byte)
WriteAsync(System.Byte[],System.Int32,System.Int32,System.Threading.CancellationToken)
WriteAsync(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)
Core(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)
Flush()
FlushAsync(System.Threading.CancellationToken)
Dispose(System.Boolean)
DisposeAsync()
.cctor()
Instance()
Compare(System.IO.Compression.ZipArchiveEntry,System.IO.Compression.ZipArchiveEntry)
ParseFileName(System.String,System.IO.Compression.ZipVersionMadeByPlatform)