< Summary

Line coverage
9%
Covered lines: 195
Uncovered lines: 1868
Coverable lines: 2063
Total lines: 3583
Line coverage: 9.4%
Branch coverage
7%
Covered branches: 59
Total branches: 777
Branch coverage: 7.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%110%
File 1: OpenAsync(...)100%110%
File 1: OpenAsync(...)0%440%
File 1: OpenAsync(...)0%440%
File 1: OpenAsyncCore(...)0%24240%
File 1: GetOffsetOfCompressedDataAsync(...)75%44100%
File 1: ReadEncryptionSaltIfNeededAsync(...)100%8883.33%
File 1: OpenInReadModeAsync(...)0%14140%
File 1: OpenInReadModeAsyncCore(System.Boolean,System.Nullable`1<System.IO.Compression.WinZipAesKeyMaterial>,System.Nullable`1<System.IO.Compression.ZipCryptoKeys>,System.Byte,System.Threading.CancellationToken)0%660%
File 1: OpenInUpdateModeAsync(...)0%10100%
File 1: OpenInUpdateModeForReadAsync(...)0%220%
File 1: GetUncompressedDataAsync(...)0%16160%
File 1: WriteAndFinishLocalEntryAsync(...)100%110%
File 1: WriteCentralDirectoryFileHeaderAsync(...)0%12120%
File 1: LoadLocalHeaderExtraFieldIfNeededAsync(...)0%220%
File 1: LoadCompressedBytesIfNeededAsync(...)0%660%
File 1: GetIsOpenableAsync(...)100%110%
File 1: ThrowIfNotOpenableAsync(...)0%220%
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(...)0%16160%
File 1: WriteLocalFileHeaderAsync(...)0%10100%
File 1: WriteLocalFileHeaderAndDataIfNeededAsync(...)0%68680%
File 1: WriteCrcAndSizesInLocalHeaderAsync(...)0%660%
File 1: WriteDataDescriptorAsync(...)100%110%
File 1: UnloadStreamsAsync()0%220%
File 1: CloseStreamsAsync()0%220%
File 2: .ctor(...)58.33%121279.66%
File 2: .ctor(...)0%220%
File 2: .ctor(...)0%880%
File 2: ToString()100%110%
File 2: Delete()0%660%
File 2: Open()100%110%
File 2: Open(...)0%440%
File 2: Open(...)100%110%
File 2: Open(...)0%440%
File 2: InferAccessFromMode()0%440%
File 2: ValidateAccessForMode(...)0%12120%
File 2: OpenCore(...)0%12120%
File 2: DecodeEntryString(...)66.66%66100%
File 2: .cctor()100%110%
File 2: GetOffsetOfCompressedData()75%44100%
File 2: ReadEncryptionSaltIfNeeded()100%8883.33%
File 2: GetUncompressedData(...)0%880%
File 2: WriteAndFinishLocalEntry(...)100%110%
File 2: WriteCentralDirectoryFileHeaderInitialize(...)0%28280%
File 2: WriteCentralDirectoryFileHeaderPrepare(...)0%440%
File 2: WriteCentralDirectoryFileHeader(...)0%12120%
File 2: LoadLocalHeaderExtraFieldIfNeeded()0%220%
File 2: LoadCompressedBytesIfNeededInitialize(...)0%220%
File 2: LoadCompressedBytesIfNeeded()0%660%
File 2: ThrowIfNotOpenable(...)0%220%
File 2: DetectEntryNameVersion()100%22100%
File 2: GetDataCompressor(...)0%12120%
File 2: CalculateZipCryptoCheckByte()0%220%
File 2: GetAesKeySizeBits(...)25%4455.55%
File 2: WrapWithDecryptionIfNeeded(...)0%10100%
File 2: GetDataDecompressor(...)0%660%
File 2: OpenInReadMode(...)0%220%
File 2: OpenInReadModeGetDataCompressor(...)0%220%
File 2: BuildDecompressionPipeline(...)0%660%
File 2: OpenInWriteMode()0%220%
File 2: OpenInWriteModeCore()0%14140%
File 2: OpenInUpdateMode(...)0%16160%
File 2: OpenInUpdateModeForRead()0%220%
File 2: MarkAsModified()0%220%
File 2: SetupEncryptionKeyMaterial(...)0%440%
File 2: PrepareEncryption(...)0%880%
File 2: CreateAesExtraField()0%660%
File 2: IsOpenable(...)0%16160%
File 2: IsOpenableInitialVerifications(...)0%12120%
File 2: IsOpenableFinalVerifications(...)0%12120%
File 2: MapCompressionLevel(...)66.66%121292.85%
File 2: MapDeflateCompressionOption(...)0%990%
File 2: WriteLocalFileHeaderInitialize(...)0%42420%
File 2: WriteLocalFileHeaderPrepare(...)0%220%
File 2: WriteLocalFileHeader(...)0%10100%
File 2: WriteLocalFileHeaderAndDataIfNeeded(...)0%46460%
File 2: WriteCrcAndSizesInLocalHeader(...)0%660%
File 2: WriteCrcAndSizesInLocalHeaderInitialize(...)0%660%
File 2: WriteCrcAndSizesInLocalHeaderPrepareForZip64PretendStreaming(...)100%110%
File 2: WriteCrcAndSizesInLocalHeaderPrepareFor32bitValuesWriting(...)0%440%
File 2: WriteCrcAndSizesInLocalHeaderPrepareForWritingWhenZip64HeaderUsed(...)100%110%
File 2: WriteCrcAndSizesInLocalHeaderPrepareForWritingDataDescriptor(...)0%220%
File 2: WriteDataDescriptor()100%110%
File 2: PrepareToWriteDataDescriptor(...)0%440%
File 2: UnloadStreams()0%220%
File 2: CloseStreams()0%220%
File 2: VersionToExtractAtLeast(...)100%44100%
File 2: ThrowIfInvalidArchive()0%220%
File 2: GetFileName_Windows(...)100%22100%
File 2: GetFileName_Unix(...)100%22100%
File 2: .ctor(...)100%110%
File 2: ThrowIfDisposed()0%220%
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(...)0%440%
File 2: Write(...)0%440%
File 2: WriteByte(...)100%110%
File 2: WriteAsync(...)100%110%
File 2: WriteAsync(...)0%220%
File 2: Core(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)0%220%
File 2: Flush()100%110%
File 2: FlushAsync(...)100%110%
File 2: Dispose(...)0%12120%
File 2: DisposeAsync()0%10100%
File 2: .cctor()100%110%
File 2: Compare(...)0%12120%
File 3: ParseFileName(...)100%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)
 023    {
 024        cancellationToken.ThrowIfCancellationRequested();
 025        ThrowIfInvalidArchive();
 026        return OpenAsyncCore(InferAccessFromMode(), default, cancellationToken);
 027    }
 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)
 0104    {
 0105        cancellationToken.ThrowIfCancellationRequested();
 0106        ThrowIfInvalidArchive();
 107
 0108        if (IsEncrypted && password.IsEmpty)
 0109        {
 0110            throw new ArgumentException(SR.PasswordRequired, nameof(password));
 111        }
 112
 0113        return OpenAsyncCore(InferAccessFromMode(), password, cancellationToken);
 0114    }
 115
 116    private Task<Stream> OpenAsyncCore(FileAccess access, ReadOnlySpan<char> password, CancellationToken cancellationTok
 0117    {
 0118        bool usePassword = IsEncrypted && !password.IsEmpty;
 119
 0120        switch (_archive.Mode)
 121        {
 122            case ZipArchiveMode.Read:
 0123                return OpenInReadModeAsync(checkOpenable: true, password, cancellationToken);
 124            case ZipArchiveMode.Create:
 0125                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        }
 0151    }
 152
 153    internal async Task<long> GetOffsetOfCompressedDataAsync(CancellationToken cancellationToken)
 403154    {
 403155        cancellationToken.ThrowIfCancellationRequested();
 403156        if (_storedOffsetOfCompressedData == null)
 403157        {
 158            // Seek to local header
 403159            _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
 403163            if (!await ZipLocalFileHeader.TrySkipBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(fa
 383164            {
 383165                throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
 166            }
 167
 20168            _storedOffsetOfCompressedData = _archive.ArchiveStream.Position;
 20169        }
 20170        return _storedOffsetOfCompressedData.Value;
 20171    }
 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)
 903179    {
 903180        if (!IsAesEncrypted || !_originallyInArchive || OperatingSystem.IsBrowser())
 345181        {
 345182            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.
 558189        if (_offsetOfLocalHeader > _archive.ArchiveStream.Length)
 155190        {
 155191            return;
 192        }
 193
 403194        long savedPosition = _archive.ArchiveStream.Position;
 195        try
 403196        {
 403197            long offset = await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false);
 20198            _archive.ArchiveStream.Seek(offset, SeekOrigin.Begin);
 199
 20200            int keySizeBits = GetAesKeySizeBits(Encryption);
 0201            int saltSize = WinZipAesStream.GetSaltSize(keySizeBits);
 0202            _aesSalt = new byte[saltSize];
 0203            await _archive.ArchiveStream.ReadExactlyAsync(_aesSalt, cancellationToken).ConfigureAwait(false);
 0204        }
 403205        catch (Exception ex) when (ex is InvalidDataException or EndOfStreamException)
 403206        {
 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.
 403210            _aesSalt = null;
 403211        }
 212        finally
 403213        {
 403214            _archive.ArchiveStream.Seek(savedPosition, SeekOrigin.Begin);
 403215        }
 903216    }
 217
 218    private Task<Stream> OpenInReadModeAsync(bool checkOpenable, ReadOnlySpan<char> password, CancellationToken cancella
 0219    {
 0220        cancellationToken.ThrowIfCancellationRequested();
 221
 222        // Derive key material from the password span before entering the async core.
 0223        WinZipAesKeyMaterial? aesKeys = null;
 0224        ZipCryptoKeys? zipCryptoKeys = null;
 0225        byte zipCryptoCheckByte = 0;
 226
 0227        if (IsEncrypted)
 0228        {
 0229            if (Encryption == ZipEncryptionMethod.Unknown)
 0230            {
 0231                throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 232            }
 233
 0234            if (password.IsEmpty)
 0235            {
 0236                throw new InvalidDataException(SR.PasswordRequired);
 237            }
 238
 0239            if (IsAesEncrypted)
 0240            {
 0241                if (OperatingSystem.IsBrowser())
 0242                {
 0243                    throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnBrowser);
 244                }
 245
 0246                if (_aesSalt is null)
 0247                {
 0248                    throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
 249                }
 250
 0251                int keySizeBits = GetAesKeySizeBits(Encryption);
 0252                aesKeys = WinZipAesStream.CreateKey(password, _aesSalt, keySizeBits);
 0253            }
 0254            else if (IsZipCryptoEncrypted)
 0255            {
 0256                zipCryptoCheckByte = CalculateZipCryptoCheckByte();
 0257                zipCryptoKeys = ZipCryptoStream.CreateKey(password);
 0258            }
 259            else
 0260            {
 0261                throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 262            }
 0263        }
 264
 0265        return OpenInReadModeAsyncCore(checkOpenable, aesKeys, zipCryptoKeys, zipCryptoCheckByte, cancellationToken);
 266
 267        async Task<Stream> OpenInReadModeAsyncCore(bool checkOpenable, WinZipAesKeyMaterial? aesKeys, ZipCryptoKeys? zip
 0268        {
 0269            if (checkOpenable)
 0270            {
 0271                await ThrowIfNotOpenableAsync(needToUncompress: true, needToLoadIntoMemory: false, cancellationToken).Co
 0272            }
 273
 0274            long offset = await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false);
 0275            Stream compressedStream = new SubReadStream(_archive.ArchiveStream, offset, _compressedSize);
 276
 277            Stream streamToDecompress;
 0278            if (aesKeys is not null)
 0279            {
 0280                streamToDecompress = await WinZipAesStream.CreateAsync(
 0281                    baseStream: compressedStream,
 0282                    keyMaterial: aesKeys.Value,
 0283                    totalStreamSize: _compressedSize,
 0284                    encrypting: false,
 0285                    cancellationToken: cancellationToken).ConfigureAwait(false);
 0286            }
 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
 0298            return BuildDecompressionPipeline(streamToDecompress);
 0299        }
 0300    }
 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)
 0409    {
 0410        cancellationToken.ThrowIfCancellationRequested();
 0411        await CloseStreamsAsync().ConfigureAwait(false);
 0412        await WriteLocalFileHeaderAndDataIfNeededAsync(forceWrite, cancellationToken).ConfigureAwait(false);
 0413        await UnloadStreamsAsync().ConfigureAwait(false);
 0414    }
 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)
 0418    {
 0419        cancellationToken.ThrowIfCancellationRequested();
 420
 0421        if (WriteCentralDirectoryFileHeaderInitialize(forceWrite, out Zip64ExtraField? zip64ExtraField, out uint compres
 0422        {
 0423            byte[] cdStaticHeader = new byte[ZipCentralDirectoryFileHeader.BlockConstantSectionSize];
 0424            WriteCentralDirectoryFileHeaderPrepare(cdStaticHeader, compressedSizeTruncated, uncompressedSizeTruncated, e
 425
 0426            await _archive.ArchiveStream.WriteAsync(cdStaticHeader, cancellationToken).ConfigureAwait(false);
 0427            await _archive.ArchiveStream.WriteAsync(_storedEntryNameBytes, cancellationToken).ConfigureAwait(false);
 428
 429            // Write zip64ExtraField first if we decided we need it
 0430            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
 0437            if (UseAesEncryption)
 0438            {
 0439                await CreateAesExtraField().WriteBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(fa
 440
 441                // write extra fields excluding existing AES extra field (and any malformed trailing data).
 0442                await ZipGenericExtraField.WriteAllBlocksExcludingTagAsync(_cdUnknownExtraFields, _cdTrailingExtraFieldD
 0443            }
 444            else
 0445            {
 446                // write extra fields (and any malformed trailing data).
 0447                await ZipGenericExtraField.WriteAllBlocksAsync(_cdUnknownExtraFields, _cdTrailingExtraFieldData ?? Array
 0448            }
 449
 0450            if (_fileComment.Length > 0)
 0451            {
 0452                await _archive.ArchiveStream.WriteAsync(_fileComment, cancellationToken).ConfigureAwait(false);
 0453            }
 0454        }
 0455    }
 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
 0498    {
 0499        cancellationToken.ThrowIfCancellationRequested();
 0500        (bool openable, string? message) = await IsOpenableAsync(needToUncompress, needToLoadIntoMemory, cancellationTok
 0501        if (!openable)
 0502        {
 0503            throw new InvalidDataException(message);
 504        }
 0505    }
 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
 0646    {
 0647        cancellationToken.ThrowIfCancellationRequested();
 0648        string? message = null;
 649
 0650        if (!_originallyInArchive)
 0651        {
 0652            return (true, message);
 653        }
 654
 0655        if (!IsOpenableInitialVerifications(needToUncompress, out message))
 0656        {
 0657            return (false, message);
 658        }
 659
 0660        if (!IsEncrypted && !await ZipLocalFileHeader.TrySkipBlockAsync(_archive.ArchiveStream, cancellationToken).Confi
 0661        {
 0662            message = SR.LocalFileHeaderCorrupt;
 0663            return (false, message);
 664        }
 0665        else if (IsEncrypted && (ushort)_headerCompressionMethod == WinZipAesMethod)
 0666        {
 0667            _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.
 0671            if (!await ZipLocalFileHeader.TrySkipBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(fa
 0672            {
 0673                message = SR.LocalFileHeaderCorrupt;
 0674                return (false, message);
 675            }
 0676        }
 677
 678        // when this property gets called, some duplicated work
 0679        long offsetOfCompressedData = await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false);
 0680        if (!IsOpenableFinalVerifications(needToLoadIntoMemory, offsetOfCompressedData, out message))
 0681        {
 0682            return (false, message);
 683        }
 684
 0685        return (true, message);
 0686    }
 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
 0690    {
 0691        cancellationToken.ThrowIfCancellationRequested();
 692
 0693        if (WriteLocalFileHeaderInitialize(isEmptyFile, forceWrite, preserveDataDescriptor, out Zip64ExtraField? zip64Ex
 0694        {
 0695            byte[] lfStaticHeader = new byte[ZipLocalFileHeader.SizeOfLocalHeader];
 0696            WriteLocalFileHeaderPrepare(lfStaticHeader, crc32ToWrite, compressedSizeTruncated, uncompressedSizeTruncated
 697
 698            // write header
 0699            await _archive.ArchiveStream.WriteAsync(lfStaticHeader, cancellationToken).ConfigureAwait(false);
 0700            await _archive.ArchiveStream.WriteAsync(_storedEntryNameBytes, cancellationToken).ConfigureAwait(false);
 701
 702            // Only when handling zip64
 0703            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
 0710            if (UseAesEncryption)
 0711            {
 0712                await CreateAesExtraField().WriteBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(fa
 713
 714                // Write other extra fields, excluding any existing AES extra field to avoid duplication
 0715                await ZipGenericExtraField.WriteAllBlocksExcludingTagAsync(_lhUnknownExtraFields, _lhTrailingExtraFieldD
 0716            }
 717            else
 0718            {
 0719                await ZipGenericExtraField.WriteAllBlocksAsync(_lhUnknownExtraFields, _lhTrailingExtraFieldData ?? Array
 0720            }
 0721        }
 722
 0723        return zip64ExtraField != null;
 0724    }
 725
 726    private async Task WriteLocalFileHeaderAndDataIfNeededAsync(bool forceWrite, CancellationToken cancellationToken)
 0727    {
 0728        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.
 0733        bool storedDataModified = (Changes & ZipArchive.ChangeState.StoredData) != 0;
 734
 735        // If _storedUncompressedData is loaded but not modified, clear it so we use _compressedBytes
 0736        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
 0743        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
 0953        {
 0954            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            }
 0970        }
 0971    }
 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)
 0977    {
 0978        cancellationToken.ThrowIfCancellationRequested();
 979        // Buffer has been sized to the largest data payload required: the 64-bit data descriptor.
 0980        byte[] writeBuffer = new byte[Zip64DataDescriptorCrcAndSizesBufferLength];
 981
 0982        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
 0988        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
 0996        WriteCrcAndSizesInLocalHeaderPrepareFor32bitValuesWriting(pretendStreaming, writeBuffer, compressedSizeTruncated
 0997        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
 01005        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
 01012        _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
 01017        if (pretendStreaming)
 01018        {
 01019            WriteCrcAndSizesInLocalHeaderPrepareForWritingDataDescriptor(writeBuffer);
 01020            await _archive.ArchiveStream.WriteAsync(writeBuffer.AsMemory(0, Zip64DataDescriptorCrcAndSizesBufferLength),
 01021        }
 01022    }
 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()
 01033    {
 01034        if (_storedUncompressedData != null)
 01035        {
 01036            await _storedUncompressedData.DisposeAsync().ConfigureAwait(false);
 01037        }
 01038        _compressedBytes = null;
 01039        _outstandingWriteStream = null;
 01040    }
 1041
 1042    private async Task CloseStreamsAsync()
 01043    {
 1044        // if the user left the stream open, close the underlying stream for them
 01045        if (_outstandingWriteStream != null)
 01046        {
 01047            await _outstandingWriteStream.DisposeAsync().ConfigureAwait(false);
 01048        }
 01049    }
 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.
 2975662        internal ZipArchiveEntry(ZipArchive archive, ZipCentralDirectoryFileHeader cd)
 2975663        {
 2975664            _archive = archive;
 65
 2975666            _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.)
 2975671            Changes = ZipArchive.ChangeState.Unchanged;
 72
 2975673            _diskNumberStart = cd.DiskNumberStart;
 2975674            _versionMadeByPlatform = (ZipVersionMadeByPlatform)cd.VersionMadeByCompatibility;
 2975675            _versionMadeBySpecification = (ZipVersionNeededValues)cd.VersionMadeBySpecification;
 2975676            _versionToExtract = (ZipVersionNeededValues)cd.VersionNeededToExtract;
 2975677            _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.
 2975680            _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.
 2975683            if (IsEncrypted && cd.AesExtraField.HasValue)
 084            {
 085                WinZipAesExtraField aesField = cd.AesExtraField.Value;
 86                // Set the real compression method from the AES extra field
 087                CompressionMethod = (ZipCompressionMethod)aesField.CompressionMethod;
 88
 89                // Also parse remaining needed metadata now
 090                _aeVersion = aesField.VendorVersion;
 091                Encryption = aesField.AesStrength switch
 092                {
 093                    1 => ZipEncryptionMethod.Aes128,
 094                    2 => ZipEncryptionMethod.Aes192,
 095                    3 => ZipEncryptionMethod.Aes256,
 096                    _ => throw new InvalidDataException(SR.InvalidAesStrength)
 097                };
 098            }
 2975699            else if (IsEncrypted)
 1908100            {
 1908101                if ((_generalPurposeBitFlag & BitFlagValues.StrongEncryption) != 0)
 610102                {
 610103                    Encryption = ZipEncryptionMethod.Unknown;
 610104                }
 105                else
 1298106                {
 107                    // Encrypted but no AES extra field means ZipCrypto
 1298108                    Encryption = ZipEncryptionMethod.ZipCrypto;
 1298109                }
 1908110                CompressionMethod = (ZipCompressionMethod)cd.CompressionMethod;
 1908111            }
 112            else
 27848113            {
 114                // Non-AES entry: compression method from CD is the real method
 27848115                CompressionMethod = (ZipCompressionMethod)cd.CompressionMethod;
 27848116            }
 117
 29756118            _lastModified = new DateTimeOffset(ZipHelper.DosTimeToDateTime(cd.LastModified));
 29756119            _compressedSize = cd.CompressedSize;
 29756120            _uncompressedSize = cd.UncompressedSize;
 29756121            _externalFileAttr = cd.ExternalFileAttributes;
 29756122            _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
 29756125            _storedOffsetOfCompressedData = null;
 29756126            _crc32 = cd.Crc32;
 127
 29756128            _compressedBytes = null;
 29756129            _storedUncompressedData = null;
 29756130            _currentlyOpenForWrite = false;
 29756131            _everOpenedForWrite = false;
 29756132            _outstandingWriteStream = null;
 133
 29756134            _storedEntryNameBytes = cd.Filename;
 29756135            _storedEntryName = DecodeEntryString(_storedEntryNameBytes);
 29756136            DetectEntryNameVersion();
 137
 29756138            _lhUnknownExtraFields = null;
 139            // the cd should have this as null if we aren't in Update mode
 29756140            _cdUnknownExtraFields = cd.ExtraFields;
 29756141            _cdTrailingExtraFieldData = cd.TrailingExtraFieldData;
 142
 29756143            _fileComment = cd.FileComment;
 144
 29756145            _compressionLevel = MapCompressionLevel(_generalPurposeBitFlag, CompressionMethod);
 29756146        }
 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.
 0160        internal ZipArchiveEntry(ZipArchive archive, string entryName)
 0161        {
 0162            _archive = archive;
 163
 0164            _originallyInArchive = false;
 165
 0166            _diskNumberStart = 0;
 0167            _versionMadeByPlatform = CurrentZipPlatform;
 0168            _versionMadeBySpecification = ZipVersionNeededValues.Default;
 0169            _versionToExtract = ZipVersionNeededValues.Default; // this must happen before following two assignment
 0170            _compressionLevel = CompressionLevel.Optimal;
 0171            CompressionMethod = ZipCompressionMethod.Deflate;
 0172            _generalPurposeBitFlag = MapDeflateCompressionOption(0, _compressionLevel, CompressionMethod);
 0173            _lastModified = DateTimeOffset.Now;
 174
 0175            _compressedSize = 0; // we don't know these yet
 0176            _uncompressedSize = 0;
 0177            _externalFileAttr = entryName.EndsWith(Path.DirectorySeparatorChar) || entryName.EndsWith(Path.AltDirectoryS
 0178                                        ? DefaultDirectoryExternalAttributes
 0179                                        : DefaultFileExternalAttributes;
 180
 0181            _offsetOfLocalHeader = 0;
 0182            _storedOffsetOfCompressedData = null;
 0183            _crc32 = 0;
 184
 0185            _compressedBytes = null;
 0186            _storedUncompressedData = null;
 0187            _currentlyOpenForWrite = false;
 0188            _everOpenedForWrite = false;
 0189            _outstandingWriteStream = null;
 190
 0191            FullName = entryName;
 192
 0193            _cdUnknownExtraFields = null;
 0194            _lhUnknownExtraFields = null;
 195
 0196            _fileComment = Array.Empty<byte>();
 197
 0198            if (_storedEntryNameBytes.Length > ushort.MaxValue)
 0199            {
 0200                throw new ArgumentException(SR.EntryNamesTooLong);
 201            }
 202
 203            // grab the stream if we're in create mode
 0204            if (_archive.Mode == ZipArchiveMode.Create)
 0205            {
 0206                _archive.AcquireArchiveStream(this);
 0207            }
 208
 0209            Changes = ZipArchive.ChangeState.Unchanged;
 0210        }
 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)]
 126218        public uint Crc32 => _crc32;
 219
 220        /// <summary>
 221        /// Gets a value that indicates whether the entry is encrypted.
 222        /// </summary>
 88708223        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>
 0235        public ZipEncryptionMethod EncryptionMethod => _encryptionMethod;
 236
 237        /// <summary>
 238        /// Gets the compression method used to compress the entry.
 239        /// </summary>
 240        public ZipCompressionMethod CompressionMethod
 241        {
 29756242            get => _storedCompressionMethod;
 243            private set
 29756244            {
 29756245                if (value == ZipCompressionMethod.Deflate)
 278246                {
 278247                    VersionToExtractAtLeast(ZipVersionNeededValues.Deflate);
 278248                }
 29478249                else if (value == ZipCompressionMethod.Deflate64)
 7360250                {
 7360251                    VersionToExtractAtLeast(ZipVersionNeededValues.Deflate64);
 7360252                }
 29756253                _storedCompressionMethod = value;
 29756254            }
 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        {
 126297            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
 29882316            {
 29882317                return _storedEntryName;
 29882318            }
 319
 320            [MemberNotNull(nameof(_storedEntryNameBytes))]
 321            [MemberNotNull(nameof(_storedEntryName))]
 322            private set
 0323            {
 0324                ArgumentNullException.ThrowIfNull(value, nameof(FullName));
 325
 0326                _storedEntryNameBytes = ZipHelper.GetEncodedTruncatedBytesFromString(
 0327                    value, _archive.EntryNameAndCommentEncoding, 0 /* No truncation */, out bool isUTF8);
 328
 0329                _storedEntryName = value;
 330
 0331                if (isUTF8)
 0332                {
 0333                    _generalPurposeBitFlag |= BitFlagValues.UnicodeFileNameAndComment;
 0334                }
 335                else
 0336                {
 0337                    _generalPurposeBitFlag &= ~BitFlagValues.UnicodeFileNameAndComment;
 0338                }
 339
 0340                DetectEntryNameVersion();
 0341            }
 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
 126362            {
 126363                return _lastModified;
 126364            }
 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
 126393            {
 126394                if (_everOpenedForWrite)
 0395                {
 0396                    throw new InvalidOperationException(SR.LengthAfterWrite);
 397                }
 126398                return _uncompressedSize;
 126399            }
 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
 99204416        internal ZipArchive.ChangeState Changes { get; private set; }
 417
 0418        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()
 0460        {
 0461            ThrowIfInvalidArchive();
 0462            return OpenCore(InferAccessFromMode());
 0463        }
 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)
 0477        {
 0478            ThrowIfInvalidArchive();
 479
 0480            if (IsEncrypted && password.IsEmpty)
 0481            {
 0482                throw new ArgumentException(SR.PasswordRequired, nameof(password));
 483            }
 484
 0485            return OpenCore(InferAccessFromMode(), password);
 0486        }
 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
 0526        private FileAccess InferAccessFromMode() => _archive.Mode switch
 0527        {
 0528            ZipArchiveMode.Read => FileAccess.Read,
 0529            ZipArchiveMode.Create => FileAccess.Write,
 0530            _ => FileAccess.ReadWrite
 0531        };
 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)
 0558        {
 0559            switch (_archive.Mode)
 560            {
 561                case ZipArchiveMode.Read:
 0562                    return OpenInReadMode(checkOpenable: true, password);
 563                case ZipArchiveMode.Create:
 0564                    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            }
 0580        }
 581
 582        private string DecodeEntryString(byte[] entryStringBytes)
 29882583        {
 29882584            Debug.Assert(entryStringBytes != null);
 585
 29882586            Encoding readEntryStringEncoding =
 29882587                (_generalPurposeBitFlag & BitFlagValues.UnicodeFileNameAndComment) == BitFlagValues.UnicodeFileNameAndCo
 29882588                ? Encoding.UTF8
 29882589                : _archive?.EntryNameAndCommentEncoding ?? Encoding.UTF8;
 590
 29882591            return readEntryStringEncoding.GetString(entryStringBytes);
 29882592        }
 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()
 403602        {
 403603            if (_storedOffsetOfCompressedData == null)
 403604            {
 605                // Seek to local header
 403606                _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
 403610                if (!ZipLocalFileHeader.TrySkipBlock(_archive.ArchiveStream))
 383611                {
 383612                    throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
 613                }
 614
 20615                _storedOffsetOfCompressedData = _archive.ArchiveStream.Position;
 20616            }
 617
 20618            return _storedOffsetOfCompressedData.Value;
 20619        }
 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()
 903627        {
 903628            if (!IsAesEncrypted || !_originallyInArchive || OperatingSystem.IsBrowser())
 345629            {
 345630                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.
 558637            if (_offsetOfLocalHeader > _archive.ArchiveStream.Length)
 155638            {
 155639                return;
 640            }
 641
 403642            long savedPosition = _archive.ArchiveStream.Position;
 643            try
 403644            {
 403645                long offset = GetOffsetOfCompressedData();
 20646                _archive.ArchiveStream.Seek(offset, SeekOrigin.Begin);
 647
 20648                int keySizeBits = GetAesKeySizeBits(Encryption);
 0649                int saltSize = WinZipAesStream.GetSaltSize(keySizeBits);
 0650                _aesSalt = new byte[saltSize];
 0651                _archive.ArchiveStream.ReadExactly(_aesSalt);
 0652            }
 403653            catch (Exception ex) when (ex is InvalidDataException or EndOfStreamException)
 403654            {
 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.
 403658                _aesSalt = null;
 403659            }
 660            finally
 403661            {
 403662                _archive.ArchiveStream.Seek(savedPosition, SeekOrigin.Begin);
 403663            }
 903664        }
 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)
 0732        {
 0733            CloseStreams();
 0734            WriteLocalFileHeaderAndDataIfNeeded(forceWrite);
 0735            UnloadStreams();
 0736        }
 737
 738        private bool WriteCentralDirectoryFileHeaderInitialize(bool forceWrite, out Zip64ExtraField? zip64ExtraField, ou
 0739        {
 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.
 0746            Debug.Assert(_storedEntryNameBytes.Length <= ushort.MaxValue);
 0747            Debug.Assert(_fileComment.Length <= ushort.MaxValue);
 748
 749            // decide if we need the Zip64 extra field:
 0750            zip64ExtraField = null;
 751
 0752            if (AreSizesTooLarge
 0753#if DEBUG_FORCE_ZIP64
 0754                || _archive._forceZip64
 0755#endif
 0756                )
 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
 0769            {
 0770                compressedSizeTruncated = (uint)_compressedSize;
 0771                uncompressedSizeTruncated = (uint)_uncompressedSize;
 0772            }
 773
 774
 0775            if (IsOffsetTooLarge
 0776#if DEBUG_FORCE_ZIP64
 0777                || _archive._forceZip64
 0778#endif
 0779                )
 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
 0789            {
 0790                offsetOfLocalHeaderTruncated = (uint)_offsetOfLocalHeader;
 0791            }
 792
 0793            if (zip64ExtraField != null)
 0794            {
 0795                VersionToExtractAtLeast(ZipVersionNeededValues.Zip64);
 0796            }
 797
 798
 0799            WinZipAesExtraField? aesExtraField = null;
 0800            int aesExtraFieldSize = 0;
 801
 0802            if (UseAesEncryption)
 0803            {
 0804                aesExtraField = CreateAesExtraField();
 0805                aesExtraFieldSize = WinZipAesExtraField.TotalSize;
 0806            }
 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
 0810            int currExtraFieldDataLength = UseAesEncryption
 0811                ? ZipGenericExtraField.TotalSizeExcludingTag(_cdUnknownExtraFields, _cdTrailingExtraFieldData?.Length ??
 0812                : ZipGenericExtraField.TotalSize(_cdUnknownExtraFields, _cdTrailingExtraFieldData?.Length ?? 0);
 0813            int bigExtraFieldLength = (zip64ExtraField != null ? zip64ExtraField.TotalSize : 0)
 0814                                      + aesExtraFieldSize
 0815                                      + currExtraFieldDataLength;
 816
 0817            if (bigExtraFieldLength > ushort.MaxValue)
 0818            {
 0819                extraFieldLength = (ushort)((zip64ExtraField != null ? zip64ExtraField.TotalSize : 0) + aesExtraFieldSiz
 0820                _cdUnknownExtraFields = null;
 0821            }
 822            else
 0823            {
 0824                extraFieldLength = (ushort)bigExtraFieldLength;
 0825            }
 826
 0827            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
 0839            return true;
 0840        }
 841
 842        private void WriteCentralDirectoryFileHeaderPrepare(Span<byte> cdStaticHeader, uint compressedSizeTruncated, uin
 0843        {
 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
 0863            ZipCentralDirectoryFileHeader.SignatureConstantBytes.CopyTo(cdStaticHeader[ZipCentralDirectoryFileHeader.Fie
 0864            cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.VersionMadeBySpecification] = (byte)_versionMade
 0865            cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.VersionMadeByCompatibility] = (byte)CurrentZipPl
 0866            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Version
 0867            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
 0871            ushort compressionMethodToWrite = UseAesEncryption ? (ushort)WinZipAesMethod : (ushort)CompressionMethod;
 0872            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Compres
 873
 0874            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.LastMod
 875            // when using aes encryption, ae-2 standard dictates crc to be 0
 0876            uint crcToWrite = UseAesEncryption ? 0 : _crc32;
 0877            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Crc32..
 0878            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Compres
 0879            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Uncompr
 0880            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Filenam
 0881            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.ExtraFi
 0882            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.FileCom
 0883            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.DiskNum
 0884            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Interna
 0885            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Externa
 0886            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Relativ
 0887        }
 888
 889        // should only throw an exception in extremely exceptional cases because it is called from dispose
 890        internal unsafe void WriteCentralDirectoryFileHeader(bool forceWrite)
 0891        {
 0892            if (WriteCentralDirectoryFileHeaderInitialize(forceWrite, out Zip64ExtraField? zip64ExtraField, out uint com
 0893            {
 0894                Span<byte> cdStaticHeader = stackalloc byte[ZipCentralDirectoryFileHeader.BlockConstantSectionSize];
 0895                WriteCentralDirectoryFileHeaderPrepare(cdStaticHeader, compressedSizeTruncated, uncompressedSizeTruncate
 896
 0897                _archive.ArchiveStream.Write(cdStaticHeader);
 0898                _archive.ArchiveStream.Write(_storedEntryNameBytes);
 899
 900                // only write zip64ExtraField if we decided we need it (it's not null)
 0901                zip64ExtraField?.WriteBlock(_archive.ArchiveStream);
 902
 903                // Write AES extra field if using AES encryption
 0904                if (UseAesEncryption)
 0905                {
 0906                    CreateAesExtraField().WriteBlock(_archive.ArchiveStream);
 907
 908                    // write extra fields excluding existing AES extra field (and any malformed trailing data).
 0909                    ZipGenericExtraField.WriteAllBlocksExcludingTag(_cdUnknownExtraFields, _cdTrailingExtraFieldData ?? 
 0910                }
 911                else
 0912                {
 913                    // write extra fields (and any malformed trailing data).
 0914                    ZipGenericExtraField.WriteAllBlocks(_cdUnknownExtraFields, _cdTrailingExtraFieldData ?? Array.Empty<
 0915                }
 916
 0917                if (_fileComment.Length > 0)
 0918                {
 0919                    _archive.ArchiveStream.Write(_fileComment);
 0920                }
 0921            }
 0922        }
 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)
 0976        {
 0977            if (!IsOpenable(needToUncompress, needToLoadIntoMemory, out string? message))
 0978            {
 0979                throw new InvalidDataException(message);
 980            }
 0981        }
 982
 983        private void DetectEntryNameVersion()
 29756984        {
 29756985            if (ParseFileName(_storedEntryName, _versionMadeByPlatform) == "")
 28982986            {
 28982987                VersionToExtractAtLeast(ZipVersionNeededValues.ExplicitDirectory);
 28982988            }
 29756989        }
 990
 991
 992        private CheckSumAndSizeWriteStream GetDataCompressor(Stream backingStream, bool leaveBackingStreamOpen, EventHan
 0993        {
 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
 01008            Debug.Assert(CompressionMethod == ZipCompressionMethod.Deflate
 01009                || CompressionMethod == ZipCompressionMethod.Stored);
 1010            Func<Stream> compressorStreamFactory;
 1011
 01012            bool isIntermediateStream = true;
 1013
 01014            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:
 01023                    compressorStreamFactory = () => new DeflateStream(backingStream, _compressionLevel, leaveBackingStre
 01024                    break;
 1025            }
 01026            bool leaveCompressorStreamOpenOnClose = leaveBackingStreamOpen && !isIntermediateStream;
 01027            var checkSumStream = new CheckSumAndSizeWriteStream(
 01028                compressorStreamFactory,
 01029                streamForPosition ?? backingStream,
 01030                leaveCompressorStreamOpenOnClose,
 01031                this,
 01032                onClose,
 01033                (long initialPosition, long currentPosition, uint checkSum, Stream backing, ZipArchiveEntry thisRef, Eve
 01034                {
 01035                    thisRef._crc32 = checkSum;
 01036                    thisRef._uncompressedSize = currentPosition;
 01037                    thisRef._compressedSize = backing.Position - initialPosition;
 01038                    closeHandler?.Invoke(thisRef, EventArgs.Empty);
 01039                });
 1040
 01041            return checkSumStream;
 01042        }
 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
 01058        private bool UseAesEncryption => Encryption is ZipEncryptionMethod.Aes128 or ZipEncryptionMethod.Aes192 or ZipEn
 1059
 18061060        private bool IsAesEncrypted => (ushort)_headerCompressionMethod == WinZipAesMethod;
 1061
 1062        private static int GetAesKeySizeBits(ZipEncryptionMethod encryption)
 401063        {
 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
 401066            return encryption switch
 401067            {
 01068                ZipEncryptionMethod.Aes128 => 128,
 01069                ZipEncryptionMethod.Aes192 => 192,
 01070                ZipEncryptionMethod.Aes256 => 256,
 401071                _ => throw new InvalidDataException(SR.InvalidAesStrength)
 401072            };
 01073        }
 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)
 01080        {
 01081            if (Encryption == ZipEncryptionMethod.Unknown)
 01082            {
 01083                throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 1084            }
 1085
 01086            if (password.IsEmpty)
 01087            {
 01088                throw new InvalidDataException(SR.PasswordRequired);
 1089            }
 1090
 01091            if (IsAesEncrypted)
 01092            {
 01093                if (_aesSalt is null)
 01094                {
 01095                    throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
 1096                }
 1097
 01098                int keySizeBits = GetAesKeySizeBits(Encryption);
 01099                WinZipAesKeyMaterial keyMaterial = WinZipAesStream.CreateKey(password, _aesSalt, keySizeBits);
 01100                return WinZipAesStream.Create(
 01101                    baseStream: compressedStream,
 01102                    keyMaterial: keyMaterial,
 01103                    totalStreamSize: _compressedSize,
 01104                    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);
 01115        }
 1116
 1117        private Stream GetDataDecompressor(Stream compressedStreamToRead)
 01118        {
 1119            Stream? uncompressedStream;
 01120            switch (CompressionMethod)
 1121            {
 1122                case ZipCompressionMethod.Deflate:
 01123                    uncompressedStream = new DeflateStream(compressedStreamToRead, CompressionMode.Decompress, _uncompre
 01124                    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
 01142            return uncompressedStream;
 01143        }
 1144
 1145        private Stream OpenInReadMode(bool checkOpenable, ReadOnlySpan<char> password = default)
 01146        {
 01147            if (checkOpenable)
 01148            {
 01149                ThrowIfNotOpenable(needToUncompress: true, needToLoadIntoMemory: false);
 01150            }
 01151            return OpenInReadModeGetDataCompressor(GetOffsetOfCompressedData(), password);
 01152        }
 1153
 1154        private Stream OpenInReadModeGetDataCompressor(long offsetOfCompressedData, ReadOnlySpan<char> password = defaul
 01155        {
 01156            Stream compressedStream = new SubReadStream(_archive.ArchiveStream, offsetOfCompressedData, _compressedSize)
 1157            Stream streamToDecompress;
 1158
 01159            if (IsEncrypted)
 01160            {
 01161                streamToDecompress = WrapWithDecryptionIfNeeded(compressedStream, password);
 01162            }
 1163            else
 01164            {
 01165                streamToDecompress = compressedStream;
 01166            }
 1167
 01168            return BuildDecompressionPipeline(streamToDecompress);
 01169        }
 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)
 01176        {
 01177            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.
 01181            if (IsAesEncrypted && _aeVersion == 2)
 01182            {
 01183                return decompressedStream;
 1184            }
 1185
 01186            return new CrcValidatingReadStream(decompressedStream, _crc32, _uncompressedSize);
 01187        }
 1188
 1189        private WrappedStream OpenInWriteMode()
 01190        {
 01191            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 
 01197            _archive.DebugAssertIsStillArchiveStreamOwner(this);
 1198
 01199            return OpenInWriteModeCore();
 01200        }
 1201
 1202        private WrappedStream OpenInWriteModeCore()
 01203        {
 01204            _everOpenedForWrite = true;
 01205            Changes |= ZipArchive.ChangeState.StoredData;
 1206
 1207            // Use the encryption method pre-configured via PrepareEncryption (CreateEntry with password).
 01208            ZipEncryptionMethod encryptionMethod = Encryption;
 1209
 1210            // Build the stream stack with encryption if needed
 01211            Stream targetStream = _archive.ArchiveStream;
 01212            Stream? encryptionStream = null;
 1213
 01214            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            }
 01230            else if (encryptionMethod is ZipEncryptionMethod.Aes256 or ZipEncryptionMethod.Aes192 or ZipEncryptionMethod
 01231            {
 01232                WinZipAesKeyMaterial keyMaterial = _derivedAesKeyMaterial
 01233                    ?? throw new InvalidOperationException(SR.EmptyPassword);
 1234
 01235                targetStream = WinZipAesStream.Create(
 01236                    baseStream: _archive.ArchiveStream,
 01237                    keyMaterial: keyMaterial,
 01238                    totalStreamSize: -1,
 01239                    encrypting: true,
 01240                    leaveOpen: true);
 01241                encryptionStream = targetStream;
 01242            }
 1243
 01244            bool isAesEncryption = encryptionMethod is ZipEncryptionMethod.Aes256 or ZipEncryptionMethod.Aes192 or ZipEn
 1245
 01246            CheckSumAndSizeWriteStream crcSizeStream = GetDataCompressor(
 01247                targetStream,
 01248                leaveBackingStreamOpen: !isAesEncryption,
 01249                (object? o, EventArgs e) =>
 01250                {
 01251                    // release the archive stream
 01252                    var entry = (ZipArchiveEntry)o!;
 01253                    entry._archive.ReleaseArchiveStream(entry);
 01254                    entry._outstandingWriteStream = null;
 01255                },
 01256                streamForPosition: encryptionMethod != ZipEncryptionMethod.None ? _archive.ArchiveStream : null);
 1257
 01258            _outstandingWriteStream = new DirectToArchiveWriterStream(crcSizeStream, this, encryptionMethod, encryptionS
 1259
 01260            return new WrappedStream(baseStream: _outstandingWriteStream, closeBaseStream: true);
 01261        }
 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)
 01370        {
 01371            if (password.IsEmpty)
 01372            {
 01373                throw new ArgumentException(SR.EmptyPassword, nameof(password));
 1374            }
 1375
 01376            if (encryptionMethod == ZipEncryptionMethod.ZipCrypto)
 01377            {
 01378                Encryption = encryptionMethod;
 01379                _derivedZipCryptoKeyMaterial = ZipCryptoStream.CreateKey(password);
 01380            }
 01381            else if (encryptionMethod is ZipEncryptionMethod.Aes128 or ZipEncryptionMethod.Aes192 or ZipEncryptionMethod
 01382            {
 1383
 01384                Encryption = encryptionMethod;
 01385                int keySizeBits = GetAesKeySizeBits(encryptionMethod);
 01386                _derivedAesKeyMaterial = WinZipAesStream.CreateKey(password, salt: null, keySizeBits);
 01387            }
 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            }
 01393        }
 1394
 1395        /// <summary>
 1396        /// Creates a WinZip AES extra field for writing to local/central directory headers.
 1397        /// </summary>
 1398        private WinZipAesExtraField CreateAesExtraField()
 01399        {
 01400            return new WinZipAesExtraField
 01401            {
 01402                VendorVersion = 2,
 01403                AesStrength = Encryption switch
 01404                {
 01405                    ZipEncryptionMethod.Aes128 => (byte)1,
 01406                    ZipEncryptionMethod.Aes192 => (byte)2,
 01407                    ZipEncryptionMethod.Aes256 => (byte)3,
 01408                    _ => throw new InvalidDataException(SR.InvalidAesStrength)
 01409                },
 01410                CompressionMethod = _compressionLevel == CompressionLevel.NoCompression
 01411                    ? (ushort)ZipCompressionMethod.Stored
 01412                    : (ushort)ZipCompressionMethod.Deflate
 01413            };
 01414        }
 1415
 1416        private bool IsOpenable(bool needToUncompress, bool needToLoadIntoMemory, out string? message)
 01417        {
 01418            message = null;
 1419
 01420            if (_originallyInArchive)
 01421            {
 01422                if (!IsOpenableInitialVerifications(needToUncompress, out message))
 01423                {
 01424                    return false;
 1425                }
 1426
 01427                if (!IsEncrypted && !ZipLocalFileHeader.TrySkipBlock(_archive.ArchiveStream))
 01428                {
 01429                    message = SR.LocalFileHeaderCorrupt;
 01430                    return false;
 1431                }
 01432                else if (IsEncrypted && IsAesEncrypted)
 01433                {
 01434                    _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
 01438                    if (!ZipLocalFileHeader.TrySkipBlock(_archive.ArchiveStream))
 01439                    {
 01440                        message = SR.LocalFileHeaderCorrupt;
 01441                        return false;
 1442                    }
 01443                }
 1444
 1445                // Pass the detected encryption method to GetOffsetOfCompressedData
 01446                long offsetOfCompressedData = GetOffsetOfCompressedData();
 1447
 01448                if (!IsOpenableFinalVerifications(needToLoadIntoMemory, offsetOfCompressedData, out message))
 01449                {
 01450                    return false;
 1451                }
 1452
 01453                return true;
 1454            }
 1455
 01456            return true;
 01457        }
 1458
 1459        private bool IsOpenableInitialVerifications(bool needToUncompress, out string? message)
 01460        {
 01461            message = null;
 01462            if (needToUncompress)
 01463            {
 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.
 01467                if (CompressionMethod != ZipCompressionMethod.Stored &&
 01468                    CompressionMethod != ZipCompressionMethod.Deflate &&
 01469                    CompressionMethod != ZipCompressionMethod.Deflate64)
 01470                {
 01471                    message = SR.UnsupportedCompression;
 01472                    return false;
 1473                }
 01474            }
 01475            if (_diskNumberStart != _archive.NumberOfThisDisk)
 01476            {
 01477                message = SR.SplitSpanned;
 01478                return false;
 1479            }
 01480            if (_offsetOfLocalHeader > _archive.ArchiveStream.Length)
 01481            {
 01482                message = SR.LocalFileHeaderCorrupt;
 01483                return false;
 1484            }
 1485
 01486            _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
 01487            return true;
 01488        }
 1489
 1490        private bool IsOpenableFinalVerifications(bool needToLoadIntoMemory, long offsetOfCompressedData, out string? me
 01491        {
 01492            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'.
 01495            if (offsetOfCompressedData < 0 ||
 01496                _compressedSize < 0 ||
 01497                _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.
 01506            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
 01518            return true;
 01519        }
 1520
 01521        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
 297561535        {
 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
 297561538            if (compressionMethod == ZipCompressionMethod.Deflate || compressionMethod == ZipCompressionMethod.Deflate64
 76381539            {
 76381540                return ((int)generalPurposeBitFlag & 0x6) switch
 76381541                {
 13981542                    0 => CompressionLevel.Optimal,
 59741543                    2 => CompressionLevel.SmallestSize,
 561544                    4 => CompressionLevel.Fastest,
 2101545                    6 => CompressionLevel.Fastest,
 01546                    _ => CompressionLevel.Optimal
 76381547                };
 1548            }
 1549            else
 221181550            {
 221181551                return CompressionLevel.NoCompression;
 1552            }
 297561553        }
 1554
 1555        private static BitFlagValues MapDeflateCompressionOption(BitFlagValues generalPurposeBitFlag, CompressionLevel c
 01556        {
 01557            ushort deflateCompressionOptions = (ushort)(
 01558                // The Deflate compression level is only valid if the compression method is actually Deflate (or Deflate
 01559                // value of the two bits is undefined and they should be zeroed out.
 01560                compressionMethod == ZipCompressionMethod.Deflate || compressionMethod == ZipCompressionMethod.Deflate64
 01561                    ? compressionLevel switch
 01562                    {
 01563                        CompressionLevel.Optimal => 0,
 01564                        CompressionLevel.SmallestSize => 2,
 01565                        CompressionLevel.Fastest => 6,
 01566                        CompressionLevel.NoCompression => 6,
 01567                        _ => 0
 01568                    }
 01569                    : 0);
 1570
 01571            return (BitFlagValues)(((int)generalPurposeBitFlag & ~0x6) | deflateCompressionOptions);
 01572        }
 1573
 01574        private bool IsOffsetTooLarge => _offsetOfLocalHeader > uint.MaxValue;
 1575
 01576        private bool ShouldUseZIP64 => AreSizesTooLarge || IsOffsetTooLarge;
 19481577        internal ZipEncryptionMethod Encryption { get => _encryptionMethod; private set => _encryptionMethod = value; }
 1578
 1579        private unsafe bool WriteLocalFileHeaderInitialize(bool isEmptyFile, bool forceWrite, bool preserveDataDescripto
 01580        {
 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
 01583            Debug.Assert(_storedEntryNameBytes.Length <= ushort.MaxValue);
 1584
 1585            // decide if we need the Zip64 extra field:
 01586            zip64ExtraField = null;
 1587
 1588            // save offset
 01589            _offsetOfLocalHeader = _archive.ArchiveStream.Position;
 1590
 1591            // for extra winzip aes header
 01592            WinZipAesExtraField? aesExtraField = null;
 01593            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 
 01596            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
 01605            {
 01606                bool isCreateMode = _archive.Mode == ZipArchiveMode.Create;
 1607
 01608                if (Encryption == ZipEncryptionMethod.ZipCrypto)
 01609                {
 01610                    _generalPurposeBitFlag |= BitFlagValues.IsEncrypted;
 01611                    _generalPurposeBitFlag |= BitFlagValues.DataDescriptor;
 01612                    compressedSizeTruncated = 0;
 01613                    uncompressedSizeTruncated = 0;
 01614                }
 01615                else if (UseAesEncryption)
 01616                {
 01617                    _generalPurposeBitFlag |= BitFlagValues.IsEncrypted;
 01618                    CompressionMethod = (ZipCompressionMethod)WinZipAesMethod;
 01619                    compressedSizeTruncated = 0;
 01620                    uncompressedSizeTruncated = 0;
 01621                    aesExtraField = CreateAesExtraField();
 01622                    aesExtraFieldSize = WinZipAesExtraField.TotalSize;
 01623                }
 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                }
 01657            }
 1658
 1659            // save offset
 01660            _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
 01664            int currExtraFieldDataLength = UseAesEncryption
 01665                ? ZipGenericExtraField.TotalSizeExcludingTag(_lhUnknownExtraFields, _lhTrailingExtraFieldData?.Length ??
 01666                : ZipGenericExtraField.TotalSize(_lhUnknownExtraFields, _lhTrailingExtraFieldData?.Length ?? 0);
 01667            int bigExtraFieldLength = (zip64ExtraField != null ? zip64ExtraField.TotalSize : 0)
 01668                                      + aesExtraFieldSize
 01669                                      + currExtraFieldDataLength;
 1670
 01671            if (bigExtraFieldLength > ushort.MaxValue)
 01672            {
 01673                extraFieldLength = (ushort)((zip64ExtraField != null ? zip64ExtraField.TotalSize : 0) + aesExtraFieldSiz
 01674                _lhUnknownExtraFields = null;
 01675            }
 1676            else
 01677            {
 01678                extraFieldLength = (ushort)bigExtraFieldLength;
 01679            }
 1680
 01681            crc32ToWrite = _crc32;
 1682
 1683            // For AE-2, CRC is always 0 in the local file header
 01684            if (UseAesEncryption)
 01685            {
 01686                crc32ToWrite = 0;
 01687            }
 1688
 1689            // If this is an existing, unchanged entry then silently skip forwards.
 1690            // If it's new or changed, write the header.
 01691            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.
 01702            if (isEmptyFile || _archive.ArchiveStream.CanSeek)
 01703            {
 01704                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                }
 01717                else if (Encryption != ZipEncryptionMethod.ZipCrypto)
 01718                {
 01719                    _generalPurposeBitFlag &= ~BitFlagValues.DataDescriptor;
 01720                }
 01721            }
 1722
 01723            return true;
 01724        }
 1725
 1726        private void WriteLocalFileHeaderPrepare(Span<byte> lfStaticHeader, uint crc32, uint compressedSizeTruncated, ui
 01727        {
 01728            ZipLocalFileHeader.SignatureConstantBytes.CopyTo(lfStaticHeader[ZipLocalFileHeader.FieldLocations.Signature.
 01729            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.VersionNeededToExt
 01730            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.GeneralPurposeBitF
 1731
 1732            // For AES encryption, write compression method 99 (Aes) in the header
 01733            ushort compressionMethodToWrite = UseAesEncryption ? (ushort)WinZipAesMethod : (ushort)CompressionMethod;
 01734            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.CompressionMethod.
 1735
 01736            BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.LastModified..], Z
 01737            BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.Crc32..], crc32);
 01738            BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.CompressedSize..],
 01739            BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.UncompressedSize..
 01740            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.FilenameLength..],
 01741            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.ExtraFieldLength..
 01742        }
 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)
 01746        {
 01747            if (WriteLocalFileHeaderInitialize(isEmptyFile, forceWrite, preserveDataDescriptor, out Zip64ExtraField? zip
 01748            {
 01749                Span<byte> lfStaticHeader = stackalloc byte[ZipLocalFileHeader.SizeOfLocalHeader];
 01750                WriteLocalFileHeaderPrepare(lfStaticHeader, crc32ToWrite, compressedSizeTruncated, uncompressedSizeTrunc
 1751
 1752                // write header
 01753                _archive.ArchiveStream.Write(lfStaticHeader);
 01754                _archive.ArchiveStream.Write(_storedEntryNameBytes);
 1755
 1756                // Write Zip64 extra field if needed
 01757                zip64ExtraField?.WriteBlock(_archive.ArchiveStream);
 1758
 1759                // Write AES extra field if using AES encryption
 01760                if (UseAesEncryption)
 01761                {
 01762                    CreateAesExtraField().WriteBlock(_archive.ArchiveStream);
 1763
 1764                    // Write other extra fields, excluding any existing AES extra field to avoid duplication
 01765                    ZipGenericExtraField.WriteAllBlocksExcludingTag(_lhUnknownExtraFields, _lhTrailingExtraFieldData ?? 
 01766                }
 1767                else
 01768                {
 1769                    // Write other extra fields
 01770                    ZipGenericExtraField.WriteAllBlocks(_lhUnknownExtraFields, _lhTrailingExtraFieldData ?? Array.Empty<
 01771                }
 01772            }
 1773
 01774            return zip64ExtraField != null;
 01775        }
 1776
 1777        private unsafe void WriteLocalFileHeaderAndDataIfNeeded(bool forceWrite)
 01778        {
 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.
 01782            bool storedDataModified = (Changes & ZipArchive.ChangeState.StoredData) != 0;
 1783
 1784            // If _storedUncompressedData is loaded but not modified, clear it so we use _compressedBytes
 01785            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
 01792            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 
 01962            {
 01963                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                }
 01979            }
 01980        }
 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)
 01992        {
 1993            // Buffer has been sized to the largest data payload required: the 64-bit data descriptor.
 01994            Span<byte> writeBuffer = stackalloc byte[Zip64DataDescriptorCrcAndSizesBufferLength];
 1995
 01996            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
 02002            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
 02010            WriteCrcAndSizesInLocalHeaderPrepareFor32bitValuesWriting(pretendStreaming, writeBuffer, compressedSizeTrunc
 02011            _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
 02019            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
 02026            _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
 02031            if (pretendStreaming)
 02032            {
 02033                WriteCrcAndSizesInLocalHeaderPrepareForWritingDataDescriptor(writeBuffer);
 02034                _archive.ArchiveStream.Write(writeBuffer[..Zip64DataDescriptorCrcAndSizesBufferLength]);
 02035            }
 02036        }
 2037
 2038        private void WriteCrcAndSizesInLocalHeaderInitialize(bool zip64HeaderUsed, out long finalPosition, out bool pret
 02039        {
 02040            finalPosition = _archive.ArchiveStream.Position;
 2041
 02042            bool zip64Needed = ShouldUseZIP64
 02043#if DEBUG_FORCE_ZIP64
 02044                || _archive._forceZip64
 02045#endif
 02046            ;
 2047
 02048            pretendStreaming = zip64Needed && !zip64HeaderUsed;
 02049            compressedSizeTruncated = zip64Needed ? ZipHelper.Mask32Bit : (uint)_compressedSize;
 02050            uncompressedSizeTruncated = zip64Needed ? ZipHelper.Mask32Bit : (uint)_uncompressedSize;
 02051        }
 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
 02068        {
 02069            _archive.ArchiveStream.Seek(_offsetOfLocalHeader + ZipLocalFileHeader.FieldLocations.Crc32,
 02070                                            SeekOrigin.Begin);
 02071            if (!pretendStreaming)
 02072            {
 02073                int relativeCrc32Location = ZipLocalFileHeader.FieldLocations.Crc32 - ZipLocalFileHeader.FieldLocations.
 02074                int relativeCompressedSizeLocation = ZipLocalFileHeader.FieldLocations.CompressedSize - ZipLocalFileHead
 02075                int relativeUncompressedSizeLocation = ZipLocalFileHeader.FieldLocations.UncompressedSize - ZipLocalFile
 2076                // when using aes encryption, ae-2 standard dictates crc to be 0
 02077                uint crcToWrite = UseAesEncryption ? 0 : _crc32;
 02078                BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer[relativeCrc32Location..], crcToWrite);
 02079                BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer[relativeCompressedSizeLocation..], compressedSizeTr
 02080                BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer[relativeUncompressedSizeLocation..], uncompressedSi
 02081            }
 2082            else // but if we are pretending to stream, we want to fill in with zeroes
 02083            {
 02084                writeBuffer[..CrcAndSizesBufferLength].Clear();
 02085            }
 02086        }
 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()
 02155        {
 02156            _storedUncompressedData?.Dispose();
 02157            _compressedBytes = null;
 02158            _outstandingWriteStream = null;
 02159        }
 2160
 2161        private void CloseStreams()
 02162        {
 2163            // if the user left the stream open, close the underlying stream for them
 02164            _outstandingWriteStream?.Dispose();
 02165        }
 2166
 2167        private void VersionToExtractAtLeast(ZipVersionNeededValues value)
 366202168        {
 366202169            if (_versionToExtract < value)
 74082170            {
 74082171                _versionToExtract = value;
 74082172                Changes |= ZipArchive.ChangeState.FixedLengthMetadata;
 74082173            }
 366202174            if (_versionMadeBySpecification < value)
 273162175            {
 273162176                _versionMadeBySpecification = value;
 273162177                Changes |= ZipArchive.ChangeState.FixedLengthMetadata;
 273162178            }
 366202179        }
 2180
 2181        private void ThrowIfInvalidArchive()
 02182        {
 02183            if (_archive == null)
 02184            {
 02185                throw new InvalidOperationException(SR.DeletedEntry);
 2186            }
 02187            _archive.ThrowIfDisposed();
 02188        }
 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)
 296022194        {
 296022195            int i = path.AsSpan().LastIndexOfAny('\\', '/', ':');
 296022196            return i >= 0 ?
 296022197                path.Substring(i + 1) :
 296022198                path;
 296022199        }
 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)
 1542205        {
 1542206            int i = path.LastIndexOf('/');
 1542207            return i >= 0 ?
 1542208                path.Substring(i + 1) :
 1542209                path;
 1542210        }
 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
 02226            public DirectToArchiveWriterStream(CheckSumAndSizeWriteStream crcSizeStream, ZipArchiveEntry entry, ZipEncry
 02227            {
 02228                _position = 0;
 02229                _crcSizeStream = crcSizeStream;
 02230                _everWritten = false;
 02231                _isDisposed = false;
 02232                _entry = entry;
 02233                _usedZip64inLH = false;
 02234                _canWrite = true;
 02235                _encryption = encryptionMethod;
 02236                _encryptionStream = encryptionStream;
 02237            }
 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;
 02263            public override bool CanWrite => _canWrite;
 2264
 2265            private void ThrowIfDisposed()
 02266            {
 02267                if (_isDisposed)
 02268                {
 02269                    throw new ObjectDisposedException(GetType().ToString(), SR.HiddenStreamName);
 2270                }
 02271            }
 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)
 02306            {
 02307                ValidateBufferArguments(buffer, offset, count);
 2308
 02309                ThrowIfDisposed();
 02310                Debug.Assert(CanWrite);
 2311
 2312                // if we're not actually writing anything, we don't want to trigger the header
 02313                if (count == 0)
 02314                {
 02315                    return;
 2316                }
 2317
 02318                if (!_everWritten)
 02319                {
 02320                    _everWritten = true;
 2321                    // write local header, we are good to go
 02322                    _usedZip64inLH = _entry.WriteLocalFileHeader(isEmptyFile: false, forceWrite: true);
 02323                }
 2324
 02325                _crcSizeStream.Write(buffer, offset, count);
 02326                _position += count;
 02327            }
 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
 02361            {
 02362                ThrowIfDisposed();
 02363                Debug.Assert(CanWrite);
 2364
 02365                return !buffer.IsEmpty ?
 02366                    Core(buffer, cancellationToken) :
 02367                    default;
 2368
 2369                async ValueTask Core(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
 02370                {
 02371                    if (!_everWritten)
 02372                    {
 02373                        _everWritten = true;
 2374                        // write local header, we are good to go
 02375                        _usedZip64inLH = await _entry.WriteLocalFileHeaderAsync(isEmptyFile: false, forceWrite: true, pr
 02376                    }
 2377
 02378                    await _crcSizeStream.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
 02379                    _position += buffer.Length;
 02380                }
 02381            }
 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)
 02400            {
 02401                if (disposing && !_isDisposed)
 02402                {
 02403                    _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).
 02409                    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
 02417                    {
 2418                        // go back and finish writing
 02419                        if (_entry._archive.ArchiveStream.CanSeek)
 02420                        {
 2421                            // finish writing local header if we have seek capabilities
 02422                            _entry.WriteCrcAndSizesInLocalHeader(_usedZip64inLH);
 2423
 2424                            // ZipCrypto entries retain DataDescriptor for check byte correctness;
 2425                            // write the trailing descriptor so the archive is consistent.
 02426                            if ((_entry._generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0)
 02427                            {
 02428                                _entry.WriteDataDescriptor();
 02429                            }
 02430                        }
 2431                        else
 2432                            // write out data descriptor if we don't have seek capabilities
 02433                            _entry.WriteDataDescriptor();
 02434                    }
 02435                    _canWrite = false;
 02436                    _isDisposed = true;
 02437                }
 2438
 02439                base.Dispose(disposing);
 02440            }
 2441
 2442            public override async ValueTask DisposeAsync()
 02443            {
 02444                if (!_isDisposed)
 02445                {
 02446                    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).
 02452                    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
 02463                    {
 2464                        // go back and finish writing
 02465                        if (_entry._archive.ArchiveStream.CanSeek)
 02466                        {
 2467                            // finish writing local header if we have seek capabilities
 02468                            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.
 02472                            if ((_entry._generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0)
 02473                            {
 02474                                await _entry.WriteDataDescriptorAsync(cancellationToken: default).ConfigureAwait(false);
 02475                            }
 02476                        }
 2477                        else
 2478                            // write out data descriptor if we don't have seek capabilities
 02479                            await _entry.WriteDataDescriptorAsync(cancellationToken: default).ConfigureAwait(false);
 02480                    }
 02481                    _canWrite = false;
 02482                    _isDisposed = true;
 02483                }
 2484
 02485                await base.DisposeAsync().ConfigureAwait(false);
 02486            }
 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) =>
 2975618            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)