< Summary

Line coverage
45%
Covered lines: 945
Uncovered lines: 1151
Coverable lines: 2096
Total lines: 3643
Line coverage: 45%
Branch coverage
34%
Covered branches: 282
Total branches: 821
Branch coverage: 34.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
File 1: OpenAsync(...)100%11100%
File 1: OpenAsync(...)100%110%
File 1: OpenAsync(...)0%440%
File 1: OpenAsync(...)50%4475%
File 1: OpenAsyncCore(...)16.66%242424%
File 1: GetOffsetOfCompressedDataAsync(...)75%4483.33%
File 1: ReadEncryptionSaltIfNeededAsync(...)90%101075%
File 1: OpenInReadModeAsync(...)56.25%161670.58%
File 1: OpenInReadModeAsyncCore(System.Boolean,System.Nullable`1<System.IO.Compression.WinZipAesKeyMaterial>,System.Nullable`1<System.IO.Compression.ZipCryptoKeys>,System.Byte,System.Threading.CancellationToken)66.66%6688.88%
File 1: OpenInUpdateModeAsync(...)0%10100%
File 1: OpenInUpdateModeForReadAsync(...)0%220%
File 1: GetUncompressedDataAsync(...)0%16160%
File 1: WriteAndFinishLocalEntryAsync(...)100%11100%
File 1: WriteCentralDirectoryFileHeaderAsync(...)58.33%121276.92%
File 1: LoadLocalHeaderExtraFieldIfNeededAsync(...)0%220%
File 1: LoadCompressedBytesIfNeededAsync(...)0%660%
File 1: GetIsOpenableAsync(...)100%110%
File 1: ThrowIfNotOpenableAsync(...)50%2271.42%
File 1: OpenInUpdateModeWithPasswordAsync(...)0%24240%
File 1: CastToStreamAsync(...)100%110%
File 1: DecryptAndStoreForUpdateWithZipCryptoAsync(...)100%110%
File 1: DecryptAndStoreForUpdateWithAesAsync(...)0%440%
File 1: StoreDecryptedDataForUpdateAsync(...)0%12120%
File 1: IsOpenableAsync(...)57.14%141455.55%
File 1: WriteLocalFileHeaderAsync(...)60%101086.95%
File 1: WriteLocalFileHeaderAndDataIfNeededAsync(...)7.69%78785.8%
File 1: WriteCrcAndSizesInLocalHeaderAsync(...)50%6647.82%
File 1: WriteDataDescriptorAsync(...)100%11100%
File 1: UnloadStreamsAsync()50%2262.5%
File 1: CloseStreamsAsync()50%2250%
File 2: .ctor(...)62.5%161685.24%
File 2: .ctor(...)0%220%
File 2: .ctor(...)50%8894.73%
File 2: ToString()100%110%
File 2: Delete()0%660%
File 2: Open()100%11100%
File 2: Open(...)50%4471.42%
File 2: Open(...)100%110%
File 2: Open(...)0%440%
File 2: InferAccessFromMode()75%4483.33%
File 2: ValidateAccessForMode(...)0%14140%
File 2: OpenCore(...)16.66%121229.41%
File 2: DecodeEntryString(...)50%66100%
File 2: .cctor()100%110%
File 2: GetOffsetOfCompressedData()75%4481.81%
File 2: ReadEncryptionSaltIfNeeded()90%101075%
File 2: GetUncompressedData(...)0%880%
File 2: WriteAndFinishLocalEntry(...)100%11100%
File 2: WriteCentralDirectoryFileHeaderInitialize(...)50%282860%
File 2: WriteCentralDirectoryFileHeaderPrepare(...)100%44100%
File 2: WriteCentralDirectoryFileHeader(...)58.33%121286.36%
File 2: LoadLocalHeaderExtraFieldIfNeeded()0%220%
File 2: LoadCompressedBytesIfNeededInitialize(...)0%220%
File 2: LoadCompressedBytesIfNeeded()0%660%
File 2: ThrowIfNotOpenable(...)50%2260%
File 2: DetectEntryNameVersion()50%2250%
File 2: GetDataCompressor(...)75%121288.46%
File 2: CalculateZipCryptoCheckByte()50%2266.66%
File 2: GetZipCryptoTimeCheckByte(...)100%11100%
File 2: ThrowIfZipCryptoLastWriteTimeChangedWithoutReencrypting()0%10100%
File 2: GetAesKeySizeBits(...)75%4488.88%
File 2: WrapWithDecryptionIfNeeded(...)60%101073.07%
File 2: GetDataDecompressor(...)33.33%6642.85%
File 2: OpenInReadMode(...)50%22100%
File 2: OpenInReadModeGetDataCompressor(...)50%2272.72%
File 2: BuildDecompressionPipeline(...)83.33%66100%
File 2: OpenInWriteMode()50%2271.42%
File 2: OpenInWriteModeCore()60%1010100%
File 2: OpenInUpdateMode(...)0%16160%
File 2: OpenInUpdateModeForRead()0%220%
File 2: MarkAsModified()0%220%
File 2: SetupEncryptionKeyMaterial(...)0%440%
File 2: PrepareEncryption(...)80%101077.77%
File 2: CreateAesExtraField()66.66%6693.75%
File 2: IsOpenable(...)57.14%141457.69%
File 2: IsOpenableInitialVerifications(...)58.33%121259.09%
File 2: IsOpenableFinalVerifications(...)33.33%121240%
File 2: MapCompressionLevel(...)25%121257.14%
File 2: MapDeflateCompressionOption(...)22.22%9975%
File 2: WriteLocalFileHeaderInitialize(...)47.72%444450%
File 2: WriteLocalFileHeaderPrepare(...)100%22100%
File 2: WriteLocalFileHeader(...)60%1010100%
File 2: WriteLocalFileHeaderAndDataIfNeeded(...)11.11%54545.51%
File 2: WriteCrcAndSizesInLocalHeader(...)50%6645.45%
File 2: WriteCrcAndSizesInLocalHeaderInitialize(...)50%66100%
File 2: WriteCrcAndSizesInLocalHeaderPrepareForZip64PretendStreaming(...)100%110%
File 2: WriteCrcAndSizesInLocalHeaderPrepareFor32bitValuesWriting(...)75%4482.35%
File 2: WriteCrcAndSizesInLocalHeaderPrepareForWritingWhenZip64HeaderUsed(...)100%110%
File 2: WriteCrcAndSizesInLocalHeaderPrepareForWritingDataDescriptor(...)0%220%
File 2: WriteDataDescriptor()100%11100%
File 2: PrepareToWriteDataDescriptor(...)50%4472.22%
File 2: UnloadStreams()50%22100%
File 2: CloseStreams()50%22100%
File 2: VersionToExtractAtLeast(...)100%44100%
File 2: ThrowIfInvalidArchive()50%2266.66%
File 2: GetFileName_Windows(...)50%22100%
File 2: GetFileName_Unix(...)0%220%
File 2: .ctor(...)100%11100%
File 2: ThrowIfDisposed()50%2260%
File 2: Read(...)100%110%
File 2: ReadAsync(...)100%110%
File 2: ReadAsync(...)100%110%
File 2: Seek(...)100%110%
File 2: SetLength(...)100%110%
File 2: Write(...)0%440%
File 2: Write(...)50%4485.71%
File 2: WriteByte(...)100%110%
File 2: WriteAsync(...)100%110%
File 2: WriteAsync(...)50%22100%
File 2: Core(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)50%22100%
File 2: Flush()100%110%
File 2: FlushAsync(...)100%110%
File 2: Dispose(...)57.14%141461.11%
File 2: DisposeAsync()50%121261.11%
File 2: .cctor()100%110%
File 2: Compare(...)0%12120%
File 3: ParseFileName(...)50%22100%

File(s)

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

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers.Binary;
 5using System.Diagnostics;
 6using System.Threading;
 7using System.Threading.Tasks;
 8
 9namespace System.IO.Compression;
 10
 11// The disposable fields that this class owns get disposed when the ZipArchive it belongs to gets disposed
 12public partial class ZipArchiveEntry
 13{
 14    /// <summary>
 15    /// Asynchronously opens the entry. If the archive that the entry belongs to was opened in Read mode, the returned s
 16    /// </summary>
 17    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 18    /// <returns>A Stream that represents the contents of the entry.</returns>
 19    /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been deleted f
 20    /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be read. -
 21    /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exceptio
 22    public Task<Stream> OpenAsync(CancellationToken cancellationToken = default)
 9123    {
 9124        cancellationToken.ThrowIfCancellationRequested();
 9125        ThrowIfInvalidArchive();
 9126        return OpenAsyncCore(InferAccessFromMode(), default, cancellationToken);
 9127    }
 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)
 182104    {
 182105        cancellationToken.ThrowIfCancellationRequested();
 182106        ThrowIfInvalidArchive();
 107
 182108        if (IsEncrypted && password.IsEmpty)
 0109        {
 0110            throw new ArgumentException(SR.PasswordRequired, nameof(password));
 111        }
 112
 182113        return OpenAsyncCore(InferAccessFromMode(), password, cancellationToken);
 182114    }
 115
 116    private Task<Stream> OpenAsyncCore(FileAccess access, ReadOnlySpan<char> password, CancellationToken cancellationTok
 273117    {
 273118        bool usePassword = IsEncrypted && !password.IsEmpty;
 119
 273120        switch (_archive.Mode)
 121        {
 122            case ZipArchiveMode.Read:
 182123                return OpenInReadModeAsync(checkOpenable: true, password, cancellationToken);
 124            case ZipArchiveMode.Create:
 91125                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        }
 273151    }
 152
 153    internal async Task<long> GetOffsetOfCompressedDataAsync(CancellationToken cancellationToken)
 388154    {
 388155        cancellationToken.ThrowIfCancellationRequested();
 388156        if (_storedOffsetOfCompressedData == null)
 91157        {
 158            // Seek to local header
 91159            _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
 91163            if (!await ZipLocalFileHeader.TrySkipBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(fa
 0164            {
 0165                throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
 166            }
 167
 91168            _storedOffsetOfCompressedData = _archive.ArchiveStream.Position;
 91169        }
 388170        return _storedOffsetOfCompressedData.Value;
 388171    }
 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)
 91179    {
 91180        if (!IsAesEncrypted || !_originallyInArchive || OperatingSystem.IsBrowser() || OperatingSystem.IsWasi())
 67181        {
 67182            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.
 24189        if (_offsetOfLocalHeader > _archive.ArchiveStream.Length)
 0190        {
 0191            return;
 192        }
 193
 24194        long savedPosition = _archive.ArchiveStream.Position;
 195        try
 24196        {
 24197            long offset = await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false);
 24198            _archive.ArchiveStream.Seek(offset, SeekOrigin.Begin);
 199
 24200            int keySizeBits = GetAesKeySizeBits(Encryption);
 24201            int saltSize = WinZipAesStream.GetSaltSize(keySizeBits);
 24202            _aesSalt = new byte[saltSize];
 24203            await _archive.ArchiveStream.ReadExactlyAsync(_aesSalt, cancellationToken).ConfigureAwait(false);
 24204        }
 0205        catch (Exception ex) when (ex is InvalidDataException or EndOfStreamException)
 0206        {
 207            // These are the only exceptions GetOffsetOfCompressedDataAsync() and ReadExactlyAsync()
 208            // can throw for corrupt or truncated data. Swallow them here and defer the error
 209            // to when the entry is actually opened.
 0210            _aesSalt = null;
 0211        }
 212        finally
 24213        {
 24214            _archive.ArchiveStream.Seek(savedPosition, SeekOrigin.Begin);
 24215        }
 91216    }
 217
 218    private Task<Stream> OpenInReadModeAsync(bool checkOpenable, ReadOnlySpan<char> password, CancellationToken cancella
 182219    {
 182220        cancellationToken.ThrowIfCancellationRequested();
 221
 222        // Derive key material from the password span before entering the async core.
 182223        WinZipAesKeyMaterial? aesKeys = null;
 182224        ZipCryptoKeys? zipCryptoKeys = null;
 182225        byte zipCryptoCheckByte = 0;
 226
 182227        if (IsEncrypted)
 182228        {
 182229            if (Encryption == ZipEncryptionMethod.Unknown)
 0230            {
 0231                throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 232            }
 233
 182234            if (password.IsEmpty)
 0235            {
 0236                throw new InvalidDataException(SR.PasswordRequired);
 237            }
 238
 182239            if (IsAesEncrypted)
 48240            {
 48241                if (OperatingSystem.IsBrowser() || OperatingSystem.IsWasi())
 0242                {
 0243                    throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnPlatform);
 244                }
 245
 48246                if (_aesSalt is null)
 0247                {
 0248                    throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
 249                }
 250
 48251                int keySizeBits = GetAesKeySizeBits(Encryption);
 48252                aesKeys = WinZipAesStream.CreateKey(password, _aesSalt, keySizeBits);
 48253            }
 134254            else if (EncryptionMethod == ZipEncryptionMethod.ZipCrypto)
 134255            {
 134256                zipCryptoCheckByte = CalculateZipCryptoCheckByte();
 134257                zipCryptoKeys = ZipCryptoStream.CreateKey(password);
 134258            }
 259            else
 0260            {
 0261                throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 262            }
 182263        }
 264
 182265        return OpenInReadModeAsyncCore(checkOpenable, aesKeys, zipCryptoKeys, zipCryptoCheckByte, cancellationToken);
 266
 267        async Task<Stream> OpenInReadModeAsyncCore(bool checkOpenable, WinZipAesKeyMaterial? aesKeys, ZipCryptoKeys? zip
 182268        {
 182269            if (checkOpenable)
 182270            {
 182271                await ThrowIfNotOpenableAsync(needToUncompress: true, needToLoadIntoMemory: false, cancellationToken).Co
 182272            }
 273
 182274            long offset = await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false);
 182275            Stream compressedStream = new SubReadStream(_archive.ArchiveStream, offset, _compressedSize);
 276
 277            Stream streamToDecompress;
 182278            if (aesKeys is not null)
 48279            {
 48280                streamToDecompress = await WinZipAesStream.CreateAsync(
 48281                    baseStream: compressedStream,
 48282                    keyMaterial: aesKeys.Value,
 48283                    totalStreamSize: _compressedSize,
 48284                    encrypting: false,
 48285                    cancellationToken: cancellationToken).ConfigureAwait(false);
 24286            }
 134287            else if (zipCryptoKeys is not null)
 134288            {
 134289                streamToDecompress = await ZipCryptoStream.CreateAsync(
 134290                    compressedStream, zipCryptoKeys.Value, zipCryptoCheckByte,
 134291                    encrypting: false, cancellationToken).ConfigureAwait(false);
 67292            }
 293            else
 0294            {
 0295                streamToDecompress = compressedStream;
 0296            }
 297
 91298            return BuildDecompressionPipeline(streamToDecompress);
 91299        }
 182300    }
 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)
 91409    {
 91410        cancellationToken.ThrowIfCancellationRequested();
 91411        await CloseStreamsAsync().ConfigureAwait(false);
 91412        await WriteLocalFileHeaderAndDataIfNeededAsync(forceWrite, cancellationToken).ConfigureAwait(false);
 91413        await UnloadStreamsAsync().ConfigureAwait(false);
 91414    }
 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)
 91418    {
 91419        cancellationToken.ThrowIfCancellationRequested();
 420
 91421        if (WriteCentralDirectoryFileHeaderInitialize(forceWrite, out Zip64ExtraField? zip64ExtraField, out uint compres
 91422        {
 91423            byte[] cdStaticHeader = new byte[ZipCentralDirectoryFileHeader.BlockConstantSectionSize];
 91424            WriteCentralDirectoryFileHeaderPrepare(cdStaticHeader, compressedSizeTruncated, uncompressedSizeTruncated, e
 425
 91426            await _archive.ArchiveStream.WriteAsync(cdStaticHeader, cancellationToken).ConfigureAwait(false);
 91427            await _archive.ArchiveStream.WriteAsync(_storedEntryNameBytes, cancellationToken).ConfigureAwait(false);
 428
 429            // Write zip64ExtraField first if we decided we need it
 91430            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
 91437            if (IsAesEncrypted)
 24438            {
 24439                await CreateAesExtraField().WriteBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(fa
 440
 441                // write extra fields excluding existing AES extra field (and any malformed trailing data).
 24442                await ZipGenericExtraField.WriteAllBlocksExcludingTagAsync(_cdUnknownExtraFields, _cdTrailingExtraFieldD
 24443            }
 444            else
 67445            {
 446                // write extra fields (and any malformed trailing data).
 67447                await ZipGenericExtraField.WriteAllBlocksAsync(_cdUnknownExtraFields, _cdTrailingExtraFieldData ?? Array
 67448            }
 449
 91450            if (_fileComment.Length > 0)
 0451            {
 0452                await _archive.ArchiveStream.WriteAsync(_fileComment, cancellationToken).ConfigureAwait(false);
 0453            }
 91454        }
 91455    }
 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
 182498    {
 182499        cancellationToken.ThrowIfCancellationRequested();
 182500        (bool openable, string? message) = await IsOpenableAsync(needToUncompress, needToLoadIntoMemory, cancellationTok
 182501        if (!openable)
 0502        {
 0503            throw new InvalidDataException(message);
 504        }
 182505    }
 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() || OperatingSystem.IsWasi())
 0541                {
 0542                    throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnPlatform);
 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 (EncryptionMethod == ZipEncryptionMethod.ZipCrypto)
 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() || OperatingSystem.IsWasi())
 0585        {
 0586            throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnPlatform);
 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
 182646    {
 182647        cancellationToken.ThrowIfCancellationRequested();
 182648        string? message = null;
 649
 182650        if (!_originallyInArchive)
 0651        {
 0652            return (true, message);
 653        }
 654
 182655        if (!IsOpenableInitialVerifications(needToUncompress, out message))
 0656        {
 0657            return (false, message);
 658        }
 659
 182660        if (!IsEncrypted && !await ZipLocalFileHeader.TrySkipBlockAsync(_archive.ArchiveStream, cancellationToken).Confi
 0661        {
 0662            message = SR.LocalFileHeaderCorrupt;
 0663            return (false, message);
 664        }
 182665        else if (IsAesEncrypted)
 48666        {
 48667            _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.
 48671            if (!await ZipLocalFileHeader.TrySkipBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(fa
 0672            {
 0673                message = SR.LocalFileHeaderCorrupt;
 0674                return (false, message);
 675            }
 48676        }
 677
 678        // when this property gets called, some duplicated work
 182679        long offsetOfCompressedData = await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false);
 182680        if (!IsOpenableFinalVerifications(needToLoadIntoMemory, offsetOfCompressedData, out message))
 0681        {
 0682            return (false, message);
 683        }
 684
 182685        return (true, message);
 182686    }
 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
 91690    {
 91691        cancellationToken.ThrowIfCancellationRequested();
 692
 91693        if (WriteLocalFileHeaderInitialize(isEmptyFile, forceWrite, preserveDataDescriptor, out Zip64ExtraField? zip64Ex
 91694        {
 91695            byte[] lfStaticHeader = new byte[ZipLocalFileHeader.SizeOfLocalHeader];
 91696            WriteLocalFileHeaderPrepare(lfStaticHeader, crc32ToWrite, compressedSizeTruncated, uncompressedSizeTruncated
 697
 698            // write header
 91699            await _archive.ArchiveStream.WriteAsync(lfStaticHeader, cancellationToken).ConfigureAwait(false);
 91700            await _archive.ArchiveStream.WriteAsync(_storedEntryNameBytes, cancellationToken).ConfigureAwait(false);
 701
 702            // Only when handling zip64
 91703            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
 91710            if (IsAesEncrypted)
 24711            {
 24712                await CreateAesExtraField().WriteBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(fa
 713
 714                // Write other extra fields, excluding any existing AES extra field to avoid duplication
 24715                await ZipGenericExtraField.WriteAllBlocksExcludingTagAsync(_lhUnknownExtraFields, _lhTrailingExtraFieldD
 24716            }
 717            else
 67718            {
 67719                await ZipGenericExtraField.WriteAllBlocksAsync(_lhUnknownExtraFields, _lhTrailingExtraFieldData ?? Array
 67720            }
 91721        }
 722
 91723        return zip64ExtraField != null;
 91724    }
 725
 726    private async Task WriteLocalFileHeaderAndDataIfNeededAsync(bool forceWrite, CancellationToken cancellationToken)
 91727    {
 91728        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.
 91733        bool storedDataModified = (Changes & ZipArchive.ChangeState.StoredData) != 0;
 734
 735        // If _storedUncompressedData is loaded but not modified, clear it so we use _compressedBytes
 91736        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
 91743        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                    Stream 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 (IsAesEncrypted && _derivedAesKeyMaterial != null)
 0792                {
 793
 0794                    if (OperatingSystem.IsBrowser() || OperatingSystem.IsWasi())
 0795                    {
 0796                        throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnPlatform);
 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                    Stream 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                bool emptyEncryptedEntry = _uncompressedSize == 0 && Encryption != ZipEncryptionMethod.None;
 0883                if (_uncompressedSize == 0 && !emptyEncryptedEntry)
 0884                {
 885                    // reset size to ensure proper central directory size header
 0886                    _compressedSize = 0;
 0887                }
 888
 889                // For unchanged entries, we need to write the header correctly but avoid
 890                // WriteLocalFileHeaderAsync creating NEW encryption structures (which would have
 891                // wrong compression method from _compressionLevel).
 892                // The original AES extra field is preserved in _lhUnknownExtraFields.
 0893                BitFlagValues savedFlags = _generalPurposeBitFlag;
 0894                ZipEncryptionMethod savedEncryption = Encryption;
 0895                ZipCompressionMethod savedCompressionMethod = CompressionMethod;
 896
 897                try
 0898                {
 899                    // For AES entries: set CompressionMethod to Aes so header writes method 99,
 900                    // but clear _encryptionMethod so WriteLocalFileHeaderAsync doesn't create a new
 901                    // AES extra field (the original one in _lhUnknownExtraFields will be used).
 0902                    if (savedEncryption is ZipEncryptionMethod.Aes128 or ZipEncryptionMethod.Aes192 or ZipEncryptionMeth
 0903                    {
 0904                        CompressionMethod = (ZipCompressionMethod)WinZipAesMethod;
 0905                        Encryption = ZipEncryptionMethod.None;
 0906                    }
 907
 0908                    await WriteLocalFileHeaderAsync(isEmptyFile: _uncompressedSize == 0 && !emptyEncryptedEntry, forceWr
 909
 910                    // WriteLocalFileHeaderInitialize may have cleared the DataDescriptor flag
 911                    // (because Encryption was temporarily set to None and the stream is seekable).
 912                    // If the original entry had a data descriptor, patch the general-purpose bit
 913                    // flags in the already-written local header to match, so the header on disk
 914                    // is consistent with the data descriptor we conditionally write below.
 0915                    if ((savedFlags & BitFlagValues.DataDescriptor) != 0 &&
 0916                        (_generalPurposeBitFlag & BitFlagValues.DataDescriptor) == 0)
 0917                    {
 0918                        long currentPos = _archive.ArchiveStream.Position;
 0919                        _archive.ArchiveStream.Seek(
 0920                            _offsetOfLocalHeader + ZipLocalFileHeader.FieldLocations.GeneralPurposeBitFlags,
 0921                            SeekOrigin.Begin);
 0922                        byte[] flagBytes = new byte[2];
 0923                        BinaryPrimitives.WriteUInt16LittleEndian(flagBytes, (ushort)savedFlags);
 0924                        await _archive.ArchiveStream.WriteAsync(flagBytes, cancellationToken).ConfigureAwait(false);
 0925                        _archive.ArchiveStream.Seek(currentPos, SeekOrigin.Begin);
 0926                    }
 0927                }
 928                finally
 0929                {
 930                    // Restore original state
 0931                    _generalPurposeBitFlag = savedFlags;
 0932                    Encryption = savedEncryption;
 0933                    CompressionMethod = savedCompressionMethod;
 0934                }
 935
 936                // according to ZIP specs, zero-byte unencrypted files MUST NOT include file data
 0937                if (_uncompressedSize != 0 || emptyEncryptedEntry)
 0938                {
 0939                    Debug.Assert(_compressedBytes != null);
 0940                    foreach (byte[] compressedBytes in _compressedBytes)
 0941                    {
 0942                        await _archive.ArchiveStream.WriteAsync(compressedBytes, cancellationToken).ConfigureAwait(false
 0943                    }
 0944                }
 945
 946                // Write data descriptor if the original entry had one
 0947                if ((savedFlags & BitFlagValues.DataDescriptor) != 0)
 0948                {
 0949                    await WriteDataDescriptorAsync(cancellationToken).ConfigureAwait(false);
 0950                }
 0951            }
 0952        }
 953        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
 91954        {
 91955            if (_archive.Mode == ZipArchiveMode.Update || !_everOpenedForWrite)
 0956            {
 0957                _everOpenedForWrite = true;
 958                // Preserve the data descriptor flag for entries that originally had one,
 959                // since the descriptor bytes remain on disk after the compressed data.
 0960                bool preserveDataDescriptor = _originallyInArchive
 0961                    && (_generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0;
 0962                await WriteLocalFileHeaderAsync(isEmptyFile: _uncompressedSize == 0, forceWrite: forceWrite, preserveDat
 963
 964                // Advance the stream past the compressed data and any trailing data descriptor
 965                // by seeking to the pre-computed end-of-entry boundary.
 0966                if (_endOfLocalEntryData > _archive.ArchiveStream.Position)
 0967                {
 0968                    _archive.ArchiveStream.Seek(_endOfLocalEntryData, SeekOrigin.Begin);
 0969                }
 0970            }
 91971        }
 91972    }
 973
 974    // Using _offsetOfLocalHeader, seeks back to where CRC and sizes should be in the header,
 975    // writes them, then seeks back to where you started
 976    // Assumes that the stream is currently at the end of the data
 977    private async Task WriteCrcAndSizesInLocalHeaderAsync(bool zip64HeaderUsed, CancellationToken cancellationToken)
 91978    {
 91979        cancellationToken.ThrowIfCancellationRequested();
 980        // Buffer has been sized to the largest data payload required: the 64-bit data descriptor.
 91981        byte[] writeBuffer = new byte[Zip64DataDescriptorCrcAndSizesBufferLength];
 982
 91983        WriteCrcAndSizesInLocalHeaderInitialize(zip64HeaderUsed, out long finalPosition, out bool pretendStreaming, out 
 984
 985        // first step is, if we need zip64, but didn't allocate it, pretend we did a stream write, because
 986        // we can't go back and give ourselves the space that the extra field needs.
 987        // we do this by setting the correct property in the bit flag to indicate we have a data descriptor
 988        // and setting the version to Zip64 to indicate that descriptor contains 64-bit values
 91989        if (pretendStreaming)
 0990        {
 0991            WriteCrcAndSizesInLocalHeaderPrepareForZip64PretendStreaming(writeBuffer);
 0992            await _archive.ArchiveStream.WriteAsync(writeBuffer.AsMemory(0, MetadataBufferLength), cancellationToken).Co
 0993        }
 994
 995        // next step is fill out the 32-bit size values in the normal header. we can't assume that
 996        // they are correct. we also write the CRC
 91997        WriteCrcAndSizesInLocalHeaderPrepareFor32bitValuesWriting(pretendStreaming, writeBuffer, compressedSizeTruncated
 91998        await _archive.ArchiveStream.WriteAsync(writeBuffer.AsMemory(0, CrcAndSizesBufferLength), cancellationToken).Con
 999
 1000        // next step: if we wrote the 64 bit header initially, a different implementation might
 1001        // try to read it, even if the 32-bit size values aren't masked. thus, we should always put the
 1002        // correct size information in there. note that order of uncomp/comp is switched, and these are
 1003        // 64-bit values
 1004        // also, note that in order for this to be correct, we have to ensure that the zip64 extra field
 1005        // is always the first extra field that is written
 911006        if (zip64HeaderUsed)
 01007        {
 01008            WriteCrcAndSizesInLocalHeaderPrepareForWritingWhenZip64HeaderUsed(writeBuffer);
 01009            await _archive.ArchiveStream.WriteAsync(writeBuffer.AsMemory(0, Zip64SizesBufferLength), cancellationToken).
 01010        }
 1011
 1012        // now go to the where we were. assume that this is the end of the data
 911013        _archive.ArchiveStream.Seek(finalPosition, SeekOrigin.Begin);
 1014
 1015        // if we are pretending we did a stream write, we want to write the data descriptor out
 1016        // the data descriptor can have 32-bit sizes or 64-bit sizes. In this case, we always use
 1017        // 64-bit sizes
 911018        if (pretendStreaming)
 01019        {
 01020            WriteCrcAndSizesInLocalHeaderPrepareForWritingDataDescriptor(writeBuffer);
 01021            await _archive.ArchiveStream.WriteAsync(writeBuffer.AsMemory(0, Zip64DataDescriptorCrcAndSizesBufferLength),
 01022        }
 911023    }
 1024
 1025    private ValueTask WriteDataDescriptorAsync(CancellationToken cancellationToken)
 671026    {
 671027        cancellationToken.ThrowIfCancellationRequested();
 671028        byte[] dataDescriptor = new byte[MaxSizeOfDataDescriptor];
 671029        int bytesToWrite = PrepareToWriteDataDescriptor(dataDescriptor);
 671030        return _archive.ArchiveStream.WriteAsync(dataDescriptor.AsMemory(0, bytesToWrite), cancellationToken);
 671031    }
 1032
 1033    internal async Task UnloadStreamsAsync()
 911034    {
 911035        if (_storedUncompressedData != null)
 01036        {
 01037            await _storedUncompressedData.DisposeAsync().ConfigureAwait(false);
 01038        }
 911039        _compressedBytes = null;
 911040        _outstandingWriteStream = null;
 911041    }
 1042
 1043    private async Task CloseStreamsAsync()
 911044    {
 1045        // if the user left the stream open, close the underlying stream for them
 911046        if (_outstandingWriteStream != null)
 01047        {
 01048            await _outstandingWriteStream.DisposeAsync().ConfigureAwait(false);
 01049        }
 911050    }
 1051}

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        // The LastWriteTime baked into this entry's on-disk ZipCrypto ciphertext, captured from the
 29        // central directory. Used to validate/decrypt the existing ciphertext and to detect LastWriteTime
 30        // changes made without re-encrypting.
 31        private readonly DateTimeOffset _originalLastModified;
 32        private long _compressedSize;
 33        private long _uncompressedSize;
 34        private long _offsetOfLocalHeader;
 35        private long? _storedOffsetOfCompressedData;
 36        private long _endOfLocalEntryData;
 37        private uint _crc32;
 38        // An array of buffers, each a maximum of MaxSingleBufferSize in size
 39        private byte[][]? _compressedBytes;
 40        private MemoryStream? _storedUncompressedData;
 41        private bool _currentlyOpenForWrite;
 42        private bool _everOpenedForWrite;
 43        private Stream? _outstandingWriteStream;
 44        private uint _externalFileAttr;
 45        private string _storedEntryName;
 46        private byte[] _storedEntryNameBytes;
 47        // only apply to update mode
 48        private List<ZipGenericExtraField>? _cdUnknownExtraFields;
 49        private byte[]? _cdTrailingExtraFieldData;
 50        private List<ZipGenericExtraField>? _lhUnknownExtraFields;
 51        private byte[]? _lhTrailingExtraFieldData;
 52        private byte[] _fileComment;
 53        private ZipEncryptionMethod _encryptionMethod;
 54        private readonly CompressionLevel _compressionLevel;
 55        private ushort? _aeVersion;
 56        // Cached derived key material for encrypted entries to allow updating in place.
 57        // Only one of these is set at a time, depending on the encryption method.
 58        private ZipCryptoKeys? _derivedZipCryptoKeyMaterial;
 59        private WinZipAesKeyMaterial? _derivedAesKeyMaterial;
 60        // Pre-read AES salt from the local file data area during central directory parsing.
 61        // This allows async open methods to derive keys without synchronous I/O.
 62        private byte[]? _aesSalt;
 63        internal const ushort WinZipAesMethod = 99;
 64        // Initializes a ZipArchiveEntry instance for an existing archive entry.
 18265        internal ZipArchiveEntry(ZipArchive archive, ZipCentralDirectoryFileHeader cd)
 18266        {
 18267            _archive = archive;
 68
 18269            _originallyInArchive = true;
 70            // It's possible for the CompressionMethod setter and DetectEntryNameVersion to update this, even without an
 71            // changes. This can occur if a ZipArchive instance runs in Update mode and opens a stream with invalid data
 72            // a situation, both the local file header and the central directory header will be rewritten (to prevent th
 73            // from falling out of sync when the central directory header is rewritten.)
 18274            Changes = ZipArchive.ChangeState.Unchanged;
 75
 18276            _diskNumberStart = cd.DiskNumberStart;
 18277            _versionMadeByPlatform = (ZipVersionMadeByPlatform)cd.VersionMadeByCompatibility;
 18278            _versionMadeBySpecification = (ZipVersionNeededValues)cd.VersionMadeBySpecification;
 18279            _versionToExtract = (ZipVersionNeededValues)cd.VersionNeededToExtract;
 18280            _generalPurposeBitFlag = (BitFlagValues)cd.GeneralPurposeBitFlag;
 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.
 18283            if (IsEncrypted && cd.CompressionMethod == WinZipAesMethod)
 4884            {
 4885                if (cd.AesExtraField is not WinZipAesExtraField aesField)
 086                {
 087                    throw new InvalidDataException(SR.InvalidAesExtraField);
 88                }
 89
 90                // Set the real compression method from the AES extra field
 4891                CompressionMethod = (ZipCompressionMethod)aesField.CompressionMethod;
 92
 93                // Also parse remaining needed metadata now
 4894                _aeVersion = aesField.VendorVersion;
 4895                Encryption = aesField.AesStrength switch
 4896                {
 1697                    1 => ZipEncryptionMethod.Aes128,
 1498                    2 => ZipEncryptionMethod.Aes192,
 1899                    3 => ZipEncryptionMethod.Aes256,
 0100                    _ => throw new InvalidDataException(SR.InvalidAesStrength)
 48101                };
 48102            }
 134103            else if (IsEncrypted)
 134104            {
 134105                if ((_generalPurposeBitFlag & BitFlagValues.StrongEncryption) != 0)
 0106                {
 0107                    Encryption = ZipEncryptionMethod.Unknown;
 0108                }
 109                else
 134110                {
 111                    // Encrypted entries without the AES method indicator use ZipCrypto.
 134112                    Encryption = ZipEncryptionMethod.ZipCrypto;
 134113                }
 134114                CompressionMethod = (ZipCompressionMethod)cd.CompressionMethod;
 134115            }
 116            else
 0117            {
 118                // Non-AES entry: compression method from CD is the real method
 0119                CompressionMethod = (ZipCompressionMethod)cd.CompressionMethod;
 0120            }
 121
 182122            _lastModified = new DateTimeOffset(ZipHelper.DosTimeToDateTime(cd.LastModified));
 123            // Remember the time this entry was originally encrypted with, so we can detect an attempt
 124            // to change LastWriteTime on a ZipCrypto entry without re-encrypting its data (which would
 125            // make the password verification bytes inconsistent with the rewritten header).
 182126            _originalLastModified = _lastModified;
 182127            _compressedSize = cd.CompressedSize;
 182128            _uncompressedSize = cd.UncompressedSize;
 182129            _externalFileAttr = cd.ExternalFileAttributes;
 182130            _offsetOfLocalHeader = cd.RelativeOffsetOfLocalHeader;
 131            // we don't know this yet: should be _offsetOfLocalHeader + 30 + _storedEntryNameBytes.Length + extrafieldle
 132            // but entryname/extra length could be different in LH
 182133            _storedOffsetOfCompressedData = null;
 182134            _crc32 = cd.Crc32;
 135
 182136            _compressedBytes = null;
 182137            _storedUncompressedData = null;
 182138            _currentlyOpenForWrite = false;
 182139            _everOpenedForWrite = false;
 182140            _outstandingWriteStream = null;
 141
 182142            _storedEntryNameBytes = cd.Filename;
 182143            _storedEntryName = DecodeEntryString(_storedEntryNameBytes);
 182144            DetectEntryNameVersion();
 145
 182146            _lhUnknownExtraFields = null;
 147            // the cd should have this as null if we aren't in Update mode
 182148            _cdUnknownExtraFields = cd.ExtraFields;
 182149            _cdTrailingExtraFieldData = cd.TrailingExtraFieldData;
 150
 182151            _fileComment = cd.FileComment;
 152
 182153            _compressionLevel = MapCompressionLevel(_generalPurposeBitFlag, CompressionMethod);
 182154        }
 155        // Initializes a ZipArchiveEntry instance for a new archive entry with a specified compression level.
 156        internal ZipArchiveEntry(ZipArchive archive, string entryName, CompressionLevel compressionLevel)
 0157            : this(archive, entryName)
 0158        {
 0159            _compressionLevel = compressionLevel;
 0160            if (_compressionLevel == CompressionLevel.NoCompression)
 0161            {
 0162                CompressionMethod = ZipCompressionMethod.Stored;
 0163            }
 0164            _generalPurposeBitFlag = MapDeflateCompressionOption(_generalPurposeBitFlag, _compressionLevel, CompressionM
 0165        }
 166
 167        // Initializes a ZipArchiveEntry instance for a new archive entry.
 182168        internal ZipArchiveEntry(ZipArchive archive, string entryName)
 182169        {
 182170            _archive = archive;
 171
 182172            _originallyInArchive = false;
 173
 182174            _diskNumberStart = 0;
 182175            _versionMadeByPlatform = CurrentZipPlatform;
 182176            _versionMadeBySpecification = ZipVersionNeededValues.Default;
 182177            _versionToExtract = ZipVersionNeededValues.Default; // this must happen before following two assignment
 182178            _compressionLevel = CompressionLevel.Optimal;
 182179            CompressionMethod = ZipCompressionMethod.Deflate;
 182180            _generalPurposeBitFlag = MapDeflateCompressionOption(0, _compressionLevel, CompressionMethod);
 182181            _lastModified = DateTimeOffset.Now;
 182
 182183            _compressedSize = 0; // we don't know these yet
 182184            _uncompressedSize = 0;
 182185            _externalFileAttr = entryName.EndsWith(Path.DirectorySeparatorChar) || entryName.EndsWith(Path.AltDirectoryS
 182186                                        ? DefaultDirectoryExternalAttributes
 182187                                        : DefaultFileExternalAttributes;
 188
 182189            _offsetOfLocalHeader = 0;
 182190            _storedOffsetOfCompressedData = null;
 182191            _crc32 = 0;
 192
 182193            _compressedBytes = null;
 182194            _storedUncompressedData = null;
 182195            _currentlyOpenForWrite = false;
 182196            _everOpenedForWrite = false;
 182197            _outstandingWriteStream = null;
 198
 182199            FullName = entryName;
 200
 182201            _cdUnknownExtraFields = null;
 182202            _lhUnknownExtraFields = null;
 203
 182204            _fileComment = Array.Empty<byte>();
 205
 182206            if (_storedEntryNameBytes.Length > ushort.MaxValue)
 0207            {
 0208                throw new ArgumentException(SR.EntryNamesTooLong);
 209            }
 210
 211            // grab the stream if we're in create mode
 182212            if (_archive.Mode == ZipArchiveMode.Create)
 182213            {
 182214                _archive.AcquireArchiveStream(this);
 182215            }
 216
 182217            Changes = ZipArchive.ChangeState.Unchanged;
 182218        }
 219
 220        /// <summary>
 221        /// The ZipArchive that this entry belongs to. If this entry has been deleted, this will return null.
 222        /// </summary>
 0223        public ZipArchive Archive => _archive;
 224
 225        [CLSCompliant(false)]
 0226        public uint Crc32 => _crc32;
 227
 228        /// <summary>
 229        /// Gets a value that indicates whether the entry is encrypted.
 230        /// </summary>
 2045231        public bool IsEncrypted => (_generalPurposeBitFlag & BitFlagValues.IsEncrypted) != 0;
 232
 233        /// <summary>
 234        /// Gets the encryption method used to encrypt the entry.
 235        /// </summary>
 236        /// <value>
 237        /// <see cref="ZipEncryptionMethod.None"/> if the entry is not encrypted;
 238        /// <see cref="ZipEncryptionMethod.Unknown"/> if the entry uses an unsupported encryption method;
 239        /// otherwise, the specific encryption method (e.g., <see cref="ZipEncryptionMethod.ZipCrypto"/>,
 240        /// <see cref="ZipEncryptionMethod.Aes128"/>, <see cref="ZipEncryptionMethod.Aes192"/>,
 241        /// or <see cref="ZipEncryptionMethod.Aes256"/>).
 242        /// </value>
 3774243        public ZipEncryptionMethod EncryptionMethod => _encryptionMethod;
 244
 245        /// <summary>
 246        /// Gets the compression method used to compress the entry.
 247        /// </summary>
 248        public ZipCompressionMethod CompressionMethod
 249        {
 1906250            get => _storedCompressionMethod;
 251            private set
 412252            {
 412253                if (value == ZipCompressionMethod.Deflate)
 364254                {
 364255                    VersionToExtractAtLeast(ZipVersionNeededValues.Deflate);
 364256                }
 48257                else if (value == ZipCompressionMethod.Deflate64)
 0258                {
 0259                    VersionToExtractAtLeast(ZipVersionNeededValues.Deflate64);
 0260                }
 412261                _storedCompressionMethod = value;
 412262            }
 263        }
 264
 265        /// <summary>
 266        /// The compressed size of the entry. If the archive that the entry belongs to is in Create mode, attempts to ge
 267        /// </summary>
 268        /// <exception cref="InvalidOperationException">This property is not available because the entry has been writte
 269        public long CompressedLength
 270        {
 271            get
 0272            {
 0273                if (_everOpenedForWrite)
 0274                {
 0275                    throw new InvalidOperationException(SR.LengthAfterWrite);
 276                }
 0277                return _compressedSize;
 0278            }
 279        }
 280
 281        public int ExternalAttributes
 282        {
 283            get
 0284            {
 0285                return (int)_externalFileAttr;
 0286            }
 287            set
 0288            {
 0289                ThrowIfInvalidArchive();
 0290                _externalFileAttr = (uint)value;
 0291                Changes |= ZipArchive.ChangeState.FixedLengthMetadata;
 0292            }
 293        }
 294
 295        /// <summary>
 296        /// Gets or sets the optional entry comment.
 297        /// </summary>
 298        /// <remarks>
 299        ///The comment encoding is determined by the <c>entryNameEncoding</c> parameter of the <see cref="ZipArchive(Str
 300        /// If the comment byte length is larger than <see cref="ushort.MaxValue"/>, it will be truncated when disposing
 301        /// </remarks>
 302        [AllowNull]
 303        public string Comment
 304        {
 0305            get => DecodeEntryString(_fileComment);
 306            set
 0307            {
 0308                _fileComment = ZipHelper.GetEncodedTruncatedBytesFromString(value, _archive.EntryNameAndCommentEncoding,
 309
 0310                if (isUTF8)
 0311                {
 0312                    _generalPurposeBitFlag |= BitFlagValues.UnicodeFileNameAndComment;
 0313                }
 0314                Changes |= ZipArchive.ChangeState.DynamicLengthMetadata;
 0315            }
 316        }
 317
 318        /// <summary>
 319        /// The relative path of the entry as stored in the Zip archive. Note that Zip archives allow any string to be t
 320        /// </summary>
 321        public string FullName
 322        {
 323            get
 364324            {
 364325                return _storedEntryName;
 364326            }
 327
 328            [MemberNotNull(nameof(_storedEntryNameBytes))]
 329            [MemberNotNull(nameof(_storedEntryName))]
 330            private set
 182331            {
 182332                ArgumentNullException.ThrowIfNull(value, nameof(FullName));
 333
 182334                _storedEntryNameBytes = ZipHelper.GetEncodedTruncatedBytesFromString(
 182335                    value, _archive.EntryNameAndCommentEncoding, 0 /* No truncation */, out bool isUTF8);
 336
 182337                _storedEntryName = value;
 338
 182339                if (isUTF8)
 0340                {
 0341                    _generalPurposeBitFlag |= BitFlagValues.UnicodeFileNameAndComment;
 0342                }
 343                else
 182344                {
 182345                    _generalPurposeBitFlag &= ~BitFlagValues.UnicodeFileNameAndComment;
 182346                }
 347
 182348                DetectEntryNameVersion();
 182349            }
 350        }
 351
 352        /// <summary>
 353        /// Returns the relative path of the entry in the Zip archive, equivalent to <see cref="FullName"/>.
 354        /// </summary>
 0355        public override string ToString() => FullName;
 356
 357        /// <summary>
 358        /// The last write time of the entry as stored in the Zip archive. When setting this property, the DateTime will
 359        /// Zip timestamp format, which supports a resolution of two seconds. If the data in the last write time field i
 360        /// an indicator value of 1980 January 1 at midnight will be returned.
 361        /// </summary>
 362        /// <remarks>
 363        /// For a ZipCrypto-encrypted entry that uses a data descriptor and is read from an existing archive in <see cre
 364        /// changing this property in a way that changes the password verification byte without also reopening the entry
 365        /// rewrite its data will cause a <see cref="NotSupportedException"/> when the archive is disposed, because the 
 366        /// can't be kept consistent with the new timestamp without re-encrypting.
 367        /// </remarks>
 368        /// <exception cref="NotSupportedException">An attempt to set this property was made, but the ZipArchive that th
 369        /// opened in read-only mode.</exception>
 370        /// <exception cref="ArgumentOutOfRangeException">An attempt was made to set this property to a value that canno
 371        /// Zip timestamp format. The earliest date/time that can be represented is 1980 January 1 0:00:00 (midnight), a
 372        /// that can be represented is 2107 December 31 23:59:58 (one second before midnight).</exception>
 373        public DateTimeOffset LastWriteTime
 374        {
 375            get
 0376            {
 0377                return _lastModified;
 0378            }
 379            set
 0380            {
 0381                ThrowIfInvalidArchive();
 0382                if (_archive.Mode == ZipArchiveMode.Read)
 0383                {
 0384                    throw new NotSupportedException(SR.ReadOnlyArchive);
 385                }
 0386                if (_archive.Mode == ZipArchiveMode.Create && _everOpenedForWrite)
 0387                {
 0388                    throw new IOException(SR.FrozenAfterWrite);
 389                }
 0390                if (value.DateTime.Year < ZipHelper.ValidZipDate_YearMin || value.DateTime.Year > ZipHelper.ValidZipDate
 0391                {
 0392                    throw new ArgumentOutOfRangeException(nameof(value), SR.DateTimeOutOfRange);
 393                }
 394
 0395                _lastModified = value;
 0396                Changes |= ZipArchive.ChangeState.FixedLengthMetadata;
 0397            }
 398        }
 399
 400        /// <summary>
 401        /// The uncompressed size of the entry. This property is not valid in Create mode, and it is only valid in Updat
 402        /// </summary>
 403        /// <exception cref="InvalidOperationException">This property is not available because the entry has been writte
 404        public long Length
 405        {
 406            get
 0407            {
 0408                if (_everOpenedForWrite)
 0409                {
 0410                    throw new InvalidOperationException(SR.LengthAfterWrite);
 411                }
 0412                return _uncompressedSize;
 0413            }
 414        }
 415
 416        /// <summary>
 417        /// The filename of the entry. This is equivalent to the substring of Fullname that follows the final directory 
 418        /// </summary>
 0419        public string Name => ParseFileName(FullName, _versionMadeByPlatform);
 420
 421        /// <summary>
 422        /// Gets the "version made by" field of the entry as stored in the archive's central directory record.
 423        /// </summary>
 424        /// <remarks>
 425        /// As defined by the ZIP file format specification, the low byte contains the version of the specification used
 426        /// </remarks>
 427        [CLSCompliant(false)]
 0428        public ushort VersionMadeBy => (ushort)(((byte)_versionMadeByPlatform << 8) | (byte)_versionMadeBySpecification)
 429
 1638430        internal ZipArchive.ChangeState Changes { get; private set; }
 431
 182432        internal bool OriginallyInArchive => _originallyInArchive;
 433
 0434        internal long OffsetOfLocalHeader => _offsetOfLocalHeader;
 435
 436        /// <summary>
 437        /// Deletes the entry from the archive.
 438        /// </summary>
 439        /// <exception cref="IOException">The entry is already open for reading or writing.</exception>
 440        /// <exception cref="NotSupportedException">The ZipArchive that this entry belongs to was opened in a mode other
 441        /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exce
 442        public void Delete()
 0443        {
 0444            if (_archive == null)
 0445            {
 0446                return;
 447            }
 448
 0449            if (_currentlyOpenForWrite)
 0450            {
 0451                throw new IOException(SR.DeleteOpenEntry);
 452            }
 453
 0454            if (_archive.Mode != ZipArchiveMode.Update)
 0455            {
 0456                throw new NotSupportedException(SR.DeleteOnlyInUpdate);
 457            }
 458
 0459            _archive.ThrowIfDisposed();
 460
 0461            _archive.RemoveEntry(this);
 0462            _archive = null!;
 0463            UnloadStreams();
 0464        }
 465
 466        /// <summary>
 467        /// Opens the entry. If the archive that the entry belongs to was opened in Read mode, the returned stream will 
 468        /// </summary>
 469        /// <returns>A Stream that represents the contents of the entry.</returns>
 470        /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been delet
 471        /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be rea
 472        /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exce
 473        public Stream Open()
 91474        {
 91475            ThrowIfInvalidArchive();
 91476            return OpenCore(InferAccessFromMode());
 91477        }
 478
 479
 480        /// <summary>
 481        /// Opens the entry for reading or updating with the specified password.
 482        /// If the entry is not encrypted, the password is ignored and the entry is opened normally.
 483        /// </summary>
 484        /// <returns>A Stream that represents the contents of the entry.</returns>
 485        /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been delet
 486        /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be rea
 487        /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exce
 488        /// <exception cref="InvalidOperationException">The requested access is not compatible with the archive's open m
 489        /// <param name="password">The password used to decrypt the entry. If the entry is not encrypted, this parameter
 490        public Stream Open(ReadOnlySpan<char> password)
 182491        {
 182492            ThrowIfInvalidArchive();
 493
 182494            if (IsEncrypted && password.IsEmpty)
 0495            {
 0496                throw new ArgumentException(SR.PasswordRequired, nameof(password));
 497            }
 498
 182499            return OpenCore(InferAccessFromMode(), password);
 91500        }
 501
 502        /// <summary>
 503        /// Opens the entry with the specified access mode. This allows for more granular control over the returned stre
 504        /// </summary>
 505        /// <param name="access">The file access mode for the returned stream.</param>
 506        /// <returns>A <see cref="Stream"/> that represents the contents of the entry with the specified access capabili
 507        /// <remarks>
 508        /// <para>The allowed <paramref name="access"/> values depend on the <see cref="ZipArchiveMode"/>:</para>
 509        /// <list type="bullet">
 510        /// <item><description><see cref="ZipArchiveMode.Read"/>: Only <see cref="FileAccess.Read"/> is allowed.</descri
 511        /// <item><description><see cref="ZipArchiveMode.Create"/>: <see cref="FileAccess.Write"/> and <see cref="FileAc
 512        /// <item><description><see cref="ZipArchiveMode.Update"/>: All values are allowed. <see cref="FileAccess.Read"/
 513        /// </list>
 514        /// </remarks>
 515        /// <exception cref="ArgumentOutOfRangeException"><paramref name="access"/> is not a valid <see cref="FileAccess
 516        /// <exception cref="InvalidOperationException">The requested access is not compatible with the archive's open m
 517        /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been delet
 518        /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be rea
 519        /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exce
 520        public Stream Open(FileAccess access)
 0521        {
 0522            ThrowIfInvalidArchive();
 0523            ValidateAccessForMode(access);
 0524            return OpenCore(access);
 0525        }
 526
 527        public Stream Open(FileAccess access, ReadOnlySpan<char> password)
 0528        {
 0529            ThrowIfInvalidArchive();
 0530            ValidateAccessForMode(access);
 531
 0532            if (IsEncrypted && password.IsEmpty)
 0533            {
 0534                throw new ArgumentException(SR.PasswordRequired, nameof(password));
 535            }
 536
 0537            return OpenCore(access, password);
 0538        }
 539
 546540        private FileAccess InferAccessFromMode() => _archive.Mode switch
 546541        {
 364542            ZipArchiveMode.Read => FileAccess.Read,
 182543            ZipArchiveMode.Create => FileAccess.Write,
 0544            _ => FileAccess.ReadWrite
 546545        };
 546
 547        private void ValidateAccessForMode(FileAccess access)
 0548        {
 0549            if (access is not (FileAccess.Read or FileAccess.Write or FileAccess.ReadWrite))
 0550            {
 0551                throw new ArgumentOutOfRangeException(nameof(access), SR.InvalidFileAccess);
 552            }
 553
 0554            switch (_archive.Mode)
 555            {
 556                case ZipArchiveMode.Read:
 0557                    if (access != FileAccess.Read)
 0558                    {
 0559                        throw new InvalidOperationException(SR.CannotBeWrittenInReadMode);
 560                    }
 0561                    break;
 562                case ZipArchiveMode.Create:
 0563                    if (access == FileAccess.Read)
 0564                    {
 0565                        throw new InvalidOperationException(SR.CannotBeReadInCreateMode);
 566                    }
 0567                    break;
 568            }
 0569        }
 570
 571        private Stream OpenCore(FileAccess access, ReadOnlySpan<char> password = default)
 273572        {
 273573            switch (_archive.Mode)
 574            {
 575                case ZipArchiveMode.Read:
 182576                    return OpenInReadMode(checkOpenable: true, password);
 577                case ZipArchiveMode.Create:
 91578                    return OpenInWriteMode();
 579                case ZipArchiveMode.Update:
 580                default:
 0581                    Debug.Assert(_archive.Mode == ZipArchiveMode.Update);
 0582                    return access switch
 0583                    {
 0584                        // Reads in Update mode must observe content written earlier in this session and
 0585                        // treat a newly created entry as empty. Only an unmodified entry that already
 0586                        // exists in the archive can be read directly without loading it into memory.
 0587                        FileAccess.Read => _storedUncompressedData is not null || !_originallyInArchive
 0588                            ? OpenInUpdateModeForRead()
 0589                            : OpenInReadMode(checkOpenable: true, password),
 0590                        FileAccess.Write => OpenInUpdateMode(loadExistingContent: false, password),
 0591                        _ => OpenInUpdateMode(loadExistingContent: true, password),
 0592                    };
 593            }
 182594        }
 595
 596        private string DecodeEntryString(byte[] entryStringBytes)
 182597        {
 182598            Debug.Assert(entryStringBytes != null);
 599
 182600            Encoding readEntryStringEncoding =
 182601                (_generalPurposeBitFlag & BitFlagValues.UnicodeFileNameAndComment) == BitFlagValues.UnicodeFileNameAndCo
 182602                ? Encoding.UTF8
 182603                : _archive?.EntryNameAndCommentEncoding ?? Encoding.UTF8;
 604
 182605            return readEntryStringEncoding.GetString(entryStringBytes);
 182606        }
 607
 608        // Only allow opening ZipArchives with large ZipArchiveEntries in update mode when running in a 64-bit process.
 609        // This is for compatibility with old behavior that threw an exception for all process bitnesses, because this
 610        // will not work in a 32-bit process.
 0611        private static readonly bool s_allowLargeZipArchiveEntriesInUpdateMode = IntPtr.Size > 4;
 612
 0613        internal bool EverOpenedForWrite => _everOpenedForWrite;
 614
 615        internal long GetOffsetOfCompressedData()
 388616        {
 388617            if (_storedOffsetOfCompressedData == null)
 91618            {
 619                // Seek to local header
 91620                _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
 621
 622                // Skip the local file header to get to the compressed data
 623                // TrySkipBlock handles both AES and non-AES cases correctly
 91624                if (!ZipLocalFileHeader.TrySkipBlock(_archive.ArchiveStream))
 0625                {
 0626                    throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
 627                }
 628
 91629                _storedOffsetOfCompressedData = _archive.ArchiveStream.Position;
 91630            }
 631
 388632            return _storedOffsetOfCompressedData.Value;
 388633        }
 634
 635        /// <summary>
 636        /// Reads and caches the AES encryption salt from the local file data area.
 637        /// Called during central directory parsing for AES-encrypted entries so that
 638        /// the salt is available for key derivation without additional I/O at open time.
 639        /// </summary>
 640        internal void ReadEncryptionSaltIfNeeded()
 91641        {
 91642            if (!IsAesEncrypted || !_originallyInArchive || OperatingSystem.IsBrowser() || OperatingSystem.IsWasi())
 67643            {
 67644                return;
 645            }
 646
 647            // A corrupt central directory can point the local header offset past the end of the
 648            // archive. Seeking there throws ArgumentOutOfRangeException on some streams (e.g.
 649            // MemoryStream). Mirror the check in IsOpenableInitialVerifications and defer the error
 650            // to when the entry is actually opened, same as for non AES encrypted entries.
 24651            if (_offsetOfLocalHeader > _archive.ArchiveStream.Length)
 0652            {
 0653                return;
 654            }
 655
 24656            long savedPosition = _archive.ArchiveStream.Position;
 657            try
 24658            {
 24659                long offset = GetOffsetOfCompressedData();
 24660                _archive.ArchiveStream.Seek(offset, SeekOrigin.Begin);
 661
 24662                int keySizeBits = GetAesKeySizeBits(Encryption);
 24663                int saltSize = WinZipAesStream.GetSaltSize(keySizeBits);
 24664                _aesSalt = new byte[saltSize];
 24665                _archive.ArchiveStream.ReadExactly(_aesSalt);
 24666            }
 0667            catch (Exception ex) when (ex is InvalidDataException or EndOfStreamException)
 0668            {
 669                // These are the only exceptions GetOffsetOfCompressedData() and ReadExactly()
 670                // can throw for corrupt or truncated data. Swallow them here and defer the error
 671                // to when the entry is actually opened.
 0672                _aesSalt = null;
 0673            }
 674            finally
 24675            {
 24676                _archive.ArchiveStream.Seek(savedPosition, SeekOrigin.Begin);
 24677            }
 91678        }
 679
 680        private MemoryStream GetUncompressedData(ReadOnlySpan<char> password = default)
 0681        {
 0682            if (_storedUncompressedData == null)
 0683            {
 684                // this means we have never opened it before
 685
 686
 687                // MemoryStream is backed by a single byte[] and cannot grow beyond Array.MaxLength.
 688                // Validate up front before attempting the (int) cast.
 0689                if ((ulong)_uncompressedSize > (ulong)Array.MaxLength)
 0690                {
 0691                    _currentlyOpenForWrite = false;
 0692                    throw new InvalidDataException(SR.EntryUncompressedSizeTooLargeForUpdateMode);
 693                }
 694
 0695                _storedUncompressedData = new MemoryStream((int)_uncompressedSize);
 696
 0697                if (_originallyInArchive)
 0698                {
 0699                    Stream decompressor = !password.IsEmpty
 0700                        ? OpenInReadMode(checkOpenable: false, password)
 0701                        : OpenInReadMode(checkOpenable: false);
 702
 0703                    using (decompressor)
 0704                    {
 705                        try
 0706                        {
 0707                            decompressor.CopyTo(_storedUncompressedData);
 0708                        }
 0709                        catch (InvalidDataException)
 0710                        {
 711                            // this is the case where the archive say the entry is deflate, but deflateStream
 712                            // throws an InvalidDataException. This property should only be getting accessed in
 713                            // Update mode, so we want to make sure _storedUncompressedData stays null so
 714                            // that later when we dispose the archive, this entry loads the compressedBytes, and
 715                            // copies them straight over
 0716                            _storedUncompressedData.Dispose();
 0717                            _storedUncompressedData = null;
 0718                            _currentlyOpenForWrite = false;
 0719                            _everOpenedForWrite = false;
 0720                            _derivedZipCryptoKeyMaterial = null;
 0721                            _derivedAesKeyMaterial = null;
 0722                            throw;
 723                        }
 0724                    }
 0725                }
 726
 727                // NOTE: CompressionMethod normalization is deferred to MarkAsModified() to avoid
 728                // corrupting entries that are opened in Update mode but not actually written to.
 729                // If we normalized here and the entry wasn't modified, we'd write a header with
 730                // CompressionMethod=Deflate but the original _compressedBytes would still be in
 731                // their original format (e.g., Deflate64), producing an invalid entry.
 0732            }
 733
 0734            return _storedUncompressedData;
 0735        }
 736        // does almost everything you need to do to forget about this entry
 737        // writes the local header/data, gets rid of all the data,
 738        // closes all of the streams except for the very outermost one that
 739        // the user holds on to and is responsible for closing
 740        //
 741        // after calling this, and only after calling this can we be guaranteed
 742        // that we are reading to write the central directory
 743        //
 744        // should only throw an exception in extremely exceptional cases because it is called from dispose
 745        internal void WriteAndFinishLocalEntry(bool forceWrite)
 91746        {
 91747            CloseStreams();
 91748            WriteLocalFileHeaderAndDataIfNeeded(forceWrite);
 91749            UnloadStreams();
 91750        }
 751
 752        private bool WriteCentralDirectoryFileHeaderInitialize(bool forceWrite, out Zip64ExtraField? zip64ExtraField, ou
 182753        {
 754            // This part is simple, because we should definitely know the sizes by this time
 755
 756            // _storedEntryNameBytes only gets set when we read in or call moveTo. MoveTo does a check, and
 757            // reading in should not be able to produce an entryname longer than ushort.MaxValue
 758            // _fileComment only gets set when we read in or set the FileComment property. This performs its own
 759            // length check.
 182760            Debug.Assert(_storedEntryNameBytes.Length <= ushort.MaxValue);
 182761            Debug.Assert(_fileComment.Length <= ushort.MaxValue);
 762
 763            // decide if we need the Zip64 extra field:
 182764            zip64ExtraField = null;
 765
 182766            if (AreSizesTooLarge
 182767#if DEBUG_FORCE_ZIP64
 182768                || _archive._forceZip64
 182769#endif
 182770                )
 0771            {
 0772                compressedSizeTruncated = ZipHelper.Mask32Bit;
 0773                uncompressedSizeTruncated = ZipHelper.Mask32Bit;
 774
 775                // If we have one of the sizes, the other must go in there as speced for LH, but not necessarily for CH,
 0776                zip64ExtraField = new()
 0777                {
 0778                    CompressedSize = _compressedSize,
 0779                    UncompressedSize = _uncompressedSize
 0780                };
 0781            }
 782            else
 182783            {
 182784                compressedSizeTruncated = (uint)_compressedSize;
 182785                uncompressedSizeTruncated = (uint)_uncompressedSize;
 182786            }
 787
 788
 182789            if (IsOffsetTooLarge
 182790#if DEBUG_FORCE_ZIP64
 182791                || _archive._forceZip64
 182792#endif
 182793                )
 0794            {
 0795                offsetOfLocalHeaderTruncated = ZipHelper.Mask32Bit;
 796
 797                // If we already created a zip64 extra field for sizes, add the offset to it
 798                // Otherwise, create a new one for just the offset
 0799                zip64ExtraField ??= new();
 0800                zip64ExtraField.LocalHeaderOffset = _offsetOfLocalHeader;
 0801            }
 802            else
 182803            {
 182804                offsetOfLocalHeaderTruncated = (uint)_offsetOfLocalHeader;
 182805            }
 806
 182807            if (zip64ExtraField != null)
 0808            {
 0809                VersionToExtractAtLeast(ZipVersionNeededValues.Zip64);
 0810            }
 811
 812
 182813            WinZipAesExtraField? aesExtraField = null;
 182814            int aesExtraFieldSize = 0;
 815
 182816            if (IsAesEncrypted)
 48817            {
 48818                aesExtraField = CreateAesExtraField();
 48819                aesExtraFieldSize = WinZipAesExtraField.TotalSize;
 48820            }
 821
 822            // determine if we can fit zip64 extra field and original extra fields all in
 823            // When using AES encryption, exclude the AES tag from currExtraFieldDataLength since we're writing a new on
 182824            int currExtraFieldDataLength = IsAesEncrypted
 182825                ? ZipGenericExtraField.TotalSizeExcludingTag(_cdUnknownExtraFields, _cdTrailingExtraFieldData?.Length ??
 182826                : ZipGenericExtraField.TotalSize(_cdUnknownExtraFields, _cdTrailingExtraFieldData?.Length ?? 0);
 182827            int bigExtraFieldLength = (zip64ExtraField != null ? zip64ExtraField.TotalSize : 0)
 182828                                      + aesExtraFieldSize
 182829                                      + currExtraFieldDataLength;
 830
 182831            if (bigExtraFieldLength > ushort.MaxValue)
 0832            {
 0833                extraFieldLength = (ushort)((zip64ExtraField != null ? zip64ExtraField.TotalSize : 0) + aesExtraFieldSiz
 0834                _cdUnknownExtraFields = null;
 0835            }
 836            else
 182837            {
 182838                extraFieldLength = (ushort)bigExtraFieldLength;
 182839            }
 840
 182841            if (_originallyInArchive && Changes == ZipArchive.ChangeState.Unchanged && !forceWrite)
 0842            {
 0843                long centralDirectoryHeaderLength = ZipCentralDirectoryFileHeader.FieldLocations.DynamicData
 0844                    + _storedEntryNameBytes.Length
 0845                    + extraFieldLength
 0846                    + _fileComment.Length;
 847
 0848                _archive.ArchiveStream.Seek(centralDirectoryHeaderLength, SeekOrigin.Current);
 849
 0850                return false;
 851            }
 852
 182853            return true;
 182854        }
 855
 856        private void WriteCentralDirectoryFileHeaderPrepare(Span<byte> cdStaticHeader, uint compressedSizeTruncated, uin
 182857        {
 858            // The central directory file header begins with the below constant-length structure:
 859            // Central directory file header signature  (4 bytes)
 860            // Version made by Specification (version)  (1 byte)
 861            // Version made by Compatibility (type)     (1 byte)
 862            // Minimum version needed to extract        (2 bytes)
 863            // General Purpose bit flag                 (2 bytes)
 864            // The Compression method                   (2 bytes)
 865            // File last modification time and date     (4 bytes)
 866            // CRC-32                                   (4 bytes)
 867            // Compressed Size                          (4 bytes)
 868            // Uncompressed Size                        (4 bytes)
 869            // File Name Length                         (2 bytes)
 870            // Extra Field Length                       (2 bytes)
 871            // File Comment Length                      (2 bytes)
 872            // Start Disk Number                        (2 bytes)
 873            // Internal File Attributes                 (2 bytes)
 874            // External File Attributes                 (4 bytes)
 875            // Offset Of Local Header                   (4 bytes)
 876
 182877            ZipCentralDirectoryFileHeader.SignatureConstantBytes.CopyTo(cdStaticHeader[ZipCentralDirectoryFileHeader.Fie
 182878            cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.VersionMadeBySpecification] = (byte)_versionMade
 182879            cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.VersionMadeByCompatibility] = (byte)CurrentZipPl
 182880            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Version
 182881            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.General
 882
 883            // For AES encryption, write compression method 99 (Aes) in the header
 182884            ushort compressionMethodToWrite = IsAesEncrypted ? (ushort)WinZipAesMethod : (ushort)CompressionMethod;
 182885            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Compres
 886
 182887            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.LastMod
 888            // when using aes encryption, ae-2 standard dictates crc to be 0
 182889            uint crcToWrite = IsAesEncrypted ? 0 : _crc32;
 182890            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Crc32..
 182891            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Compres
 182892            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Uncompr
 182893            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Filenam
 182894            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.ExtraFi
 182895            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.FileCom
 182896            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.DiskNum
 182897            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Interna
 182898            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Externa
 182899            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Relativ
 182900        }
 901
 902        // should only throw an exception in extremely exceptional cases because it is called from dispose
 903        internal unsafe void WriteCentralDirectoryFileHeader(bool forceWrite)
 91904        {
 91905            if (WriteCentralDirectoryFileHeaderInitialize(forceWrite, out Zip64ExtraField? zip64ExtraField, out uint com
 91906            {
 91907                Span<byte> cdStaticHeader = stackalloc byte[ZipCentralDirectoryFileHeader.BlockConstantSectionSize];
 91908                WriteCentralDirectoryFileHeaderPrepare(cdStaticHeader, compressedSizeTruncated, uncompressedSizeTruncate
 909
 91910                _archive.ArchiveStream.Write(cdStaticHeader);
 91911                _archive.ArchiveStream.Write(_storedEntryNameBytes);
 912
 913                // only write zip64ExtraField if we decided we need it (it's not null)
 91914                zip64ExtraField?.WriteBlock(_archive.ArchiveStream);
 915
 916                // Write AES extra field if using AES encryption
 91917                if (IsAesEncrypted)
 24918                {
 24919                    CreateAesExtraField().WriteBlock(_archive.ArchiveStream);
 920
 921                    // write extra fields excluding existing AES extra field (and any malformed trailing data).
 24922                    ZipGenericExtraField.WriteAllBlocksExcludingTag(_cdUnknownExtraFields, _cdTrailingExtraFieldData ?? 
 24923                }
 924                else
 67925                {
 926                    // write extra fields (and any malformed trailing data).
 67927                    ZipGenericExtraField.WriteAllBlocks(_cdUnknownExtraFields, _cdTrailingExtraFieldData ?? Array.Empty<
 67928                }
 929
 91930                if (_fileComment.Length > 0)
 0931                {
 0932                    _archive.ArchiveStream.Write(_fileComment);
 0933                }
 91934            }
 91935        }
 936
 937        // throws exception if fails, will get called on every relevant entry before closing in update mode
 938        // can throw InvalidDataException
 939        internal void LoadLocalHeaderExtraFieldIfNeeded()
 0940        {
 941            // we should have made this exact call in _archive.Init through ThrowIfOpenable
 0942            Debug.Assert(IsOpenable(false, true, out _));
 943
 944            // load local header's extra fields. it will be null if we couldn't read for some reason
 0945            if (_originallyInArchive)
 0946            {
 0947                _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
 0948                _lhUnknownExtraFields = ZipLocalFileHeader.GetExtraFields(_archive.ArchiveStream, out _lhTrailingExtraFi
 0949            }
 0950        }
 951
 952        private byte[][] LoadCompressedBytesIfNeededInitialize(out int maxSingleBufferSize)
 0953        {
 954            // we know that it is openable at this point
 0955            maxSingleBufferSize = Array.MaxLength;
 956
 0957            byte[][] compressedBytes = new byte[(_compressedSize / maxSingleBufferSize) + 1][];
 0958            for (int i = 0; i < compressedBytes.Length - 1; i++)
 0959            {
 0960                compressedBytes[i] = new byte[maxSingleBufferSize];
 0961            }
 0962            compressedBytes[compressedBytes.Length - 1] = new byte[_compressedSize % maxSingleBufferSize];
 963
 0964            return compressedBytes;
 0965        }
 966
 967        // throws exception if fails, will get called on every relevant entry before closing in update mode
 968        // can throw InvalidDataException
 969        internal void LoadCompressedBytesIfNeeded()
 0970        {
 971            // we should have made this exact call in _archive.Init through ThrowIfOpenable
 0972            Debug.Assert(IsOpenable(false, true, out _));
 973
 0974            if (!_everOpenedForWrite && _originallyInArchive)
 0975            {
 0976                _compressedBytes = LoadCompressedBytesIfNeededInitialize(out int maxSingleBufferSize);
 977
 0978                _archive.ArchiveStream.Seek(GetOffsetOfCompressedData(), SeekOrigin.Begin);
 979
 0980                for (int i = 0; i < _compressedBytes.Length - 1; i++)
 0981                {
 0982                    _archive.ArchiveStream.ReadAtLeast(_compressedBytes[i], maxSingleBufferSize, throwOnEndOfStream: tru
 0983                }
 0984                _archive.ArchiveStream.ReadAtLeast(_compressedBytes[_compressedBytes.Length - 1], (int)(_compressedSize 
 0985            }
 0986        }
 987
 988        internal void ThrowIfNotOpenable(bool needToUncompress, bool needToLoadIntoMemory)
 182989        {
 182990            if (!IsOpenable(needToUncompress, needToLoadIntoMemory, out string? message))
 0991            {
 0992                throw new InvalidDataException(message);
 993            }
 182994        }
 995
 996        private void DetectEntryNameVersion()
 364997        {
 364998            if (ParseFileName(_storedEntryName, _versionMadeByPlatform) == "")
 0999            {
 01000                VersionToExtractAtLeast(ZipVersionNeededValues.ExplicitDirectory);
 01001            }
 3641002        }
 1003
 1004
 1005        private CheckSumAndSizeWriteStream GetDataCompressor(Stream backingStream, bool leaveBackingStreamOpen, EventHan
 1821006        {
 1007            // stream stack: backingStream -> DeflateStream -> CheckSumWriteStream
 1008
 1009            // By default we compress with deflate, except if compression level
 1010            // is set to NoCompression then stored is used.
 1011            //
 1012            // Stored is also used for empty files, but we can't know at this
 1013            // point if user will write anything to the stream or not. For that
 1014            // reason, we defer the instantiation of the compression stream
 1015            // until the first write to the CheckSumAndSizeWriteStream happens.
 1016            // If the user never writes anything, this will be detected during
 1017            // saving and the compression method in the file header will be
 1018            // changed to Stored.
 1019            //
 1020            // Note: Deflate64 is not supported on all platforms
 1821021            Debug.Assert(CompressionMethod == ZipCompressionMethod.Deflate
 1821022                || CompressionMethod == ZipCompressionMethod.Stored);
 1023            Func<Stream> compressorStreamFactory;
 1024
 1821025            bool isIntermediateStream = true;
 1026
 1821027            switch (CompressionMethod)
 1028            {
 1029                case ZipCompressionMethod.Stored:
 01030                    compressorStreamFactory = () => backingStream;
 01031                    isIntermediateStream = false;
 01032                    break;
 1033                case ZipCompressionMethod.Deflate:
 1034                case ZipCompressionMethod.Deflate64:
 1035                default:
 3641036                    compressorStreamFactory = () => new DeflateStream(backingStream, _compressionLevel, leaveBackingStre
 1821037                    break;
 1038            }
 1821039            bool leaveCompressorStreamOpenOnClose = leaveBackingStreamOpen && !isIntermediateStream;
 1821040            var checkSumStream = new CheckSumAndSizeWriteStream(
 1821041                compressorStreamFactory,
 1821042                streamForPosition ?? backingStream,
 1821043                leaveCompressorStreamOpenOnClose,
 1821044                this,
 1821045                onClose,
 1821046                (long initialPosition, long currentPosition, uint checkSum, Stream backing, ZipArchiveEntry thisRef, Eve
 1821047                {
 1821048                    thisRef._crc32 = checkSum;
 1821049                    thisRef._uncompressedSize = currentPosition;
 1821050                    thisRef._compressedSize = backing.Position - initialPosition;
 1821051                    closeHandler?.Invoke(thisRef, EventArgs.Empty);
 3641052                });
 1053
 1821054            return checkSumStream;
 1821055        }
 1056
 1057        private byte CalculateZipCryptoCheckByte()
 2681058        {
 1059            // If data descriptor NOT used, the check byte is the MSB of CRC32
 2681060            if ((_generalPurposeBitFlag & BitFlagValues.DataDescriptor) == 0)
 01061            {
 01062                return (byte)((_crc32 >> 24) & 0xFF);
 1063            }
 1064
 1065            // If data descriptor IS used, the check byte is the MSB of the DOS time from the *local* header.
 1066            // Use _originalLastModified (the time baked into the entry's on-disk ciphertext), not
 1067            // _lastModified, since LastWriteTime may have been changed since without re-encrypting.
 2681068            return GetZipCryptoTimeCheckByte(_originalLastModified);
 2681069        }
 1070
 1071        private static byte GetZipCryptoTimeCheckByte(DateTimeOffset timestamp) =>
 2681072            (byte)(ZipHelper.DateTimeToDosTime(timestamp.DateTime) >> 8);
 1073
 1074        internal void ThrowIfZipCryptoLastWriteTimeChangedWithoutReencrypting()
 01075        {
 01076            if (_originallyInArchive
 01077                && Encryption == ZipEncryptionMethod.ZipCrypto
 01078                && (_generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0
 01079                && (Changes & ZipArchive.ChangeState.StoredData) == 0
 01080                && GetZipCryptoTimeCheckByte(_lastModified) != GetZipCryptoTimeCheckByte(_originalLastModified))
 01081            {
 01082                throw new NotSupportedException(SR.ZipCryptoLastWriteTimeChangeNotSupported);
 1083            }
 01084        }
 1085
 33241086        private bool IsAesEncrypted => EncryptionMethod is ZipEncryptionMethod.Aes128 or ZipEncryptionMethod.Aes192 or Z
 1087
 1088        private static int GetAesKeySizeBits(ZipEncryptionMethod encryption)
 1921089        {
 1090            // Get number of bits for AES key size based on the encryption method
 1091            // Only possible values are: AES-128 = 128 bits, AES-192 = 192 bits, AES-256 as per the specs
 1921092            return encryption switch
 1921093            {
 641094                ZipEncryptionMethod.Aes128 => 128,
 561095                ZipEncryptionMethod.Aes192 => 192,
 721096                ZipEncryptionMethod.Aes256 => 256,
 01097                _ => throw new InvalidDataException(SR.InvalidAesStrength)
 1921098            };
 1921099        }
 1100
 1101        /// <summary>
 1102        /// Creates the appropriate decryption stream for an encrypted entry.
 1103        /// For AES entries, uses the salt that was pre-read during central directory parsing.
 1104        /// </summary>
 1105        private Stream WrapWithDecryptionIfNeeded(Stream compressedStream, ReadOnlySpan<char> password)
 1821106        {
 1821107            if (Encryption == ZipEncryptionMethod.Unknown)
 01108            {
 01109                throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 1110            }
 1111
 1821112            if (password.IsEmpty)
 01113            {
 01114                throw new InvalidDataException(SR.PasswordRequired);
 1115            }
 1116
 1821117            if (IsAesEncrypted)
 481118            {
 481119                if (_aesSalt is null)
 01120                {
 01121                    throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
 1122                }
 1123
 481124                int keySizeBits = GetAesKeySizeBits(Encryption);
 481125                WinZipAesKeyMaterial keyMaterial = WinZipAesStream.CreateKey(password, _aesSalt, keySizeBits);
 481126                return WinZipAesStream.Create(
 481127                    baseStream: compressedStream,
 481128                    keyMaterial: keyMaterial,
 481129                    totalStreamSize: _compressedSize,
 481130                    encrypting: false);
 1131            }
 1132
 1341133            if (EncryptionMethod == ZipEncryptionMethod.ZipCrypto)
 1341134            {
 1341135                byte expectedCheckByte = CalculateZipCryptoCheckByte();
 1341136                ZipCryptoKeys keyMaterial = ZipCryptoStream.CreateKey(password);
 1341137                return ZipCryptoStream.Create(compressedStream, keyMaterial, expectedCheckByte, encrypting: false);
 1138            }
 1139
 01140            throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 911141        }
 1142
 1143        private Stream GetDataDecompressor(Stream compressedStreamToRead)
 1821144        {
 1145            Stream? uncompressedStream;
 1821146            switch (CompressionMethod)
 1147            {
 1148                case ZipCompressionMethod.Deflate:
 1821149                    uncompressedStream = new DeflateStream(compressedStreamToRead, CompressionMode.Decompress, _uncompre
 1821150                    break;
 1151                case ZipCompressionMethod.Deflate64:
 01152                    uncompressedStream = new DeflateManagedStream(compressedStreamToRead, ZipCompressionMethod.Deflate64
 01153                    break;
 1154                case ZipCompressionMethod.Stored:
 01155                    uncompressedStream = compressedStreamToRead;
 01156                    break;
 1157                default:
 1158                    // We should not get here with Aes as CompressionMethod anymore
 1159                    // as it should have been replaced with the actual compression method
 01160                    Debug.Assert((ushort)CompressionMethod != WinZipAesMethod,
 01161                        "AES compression method should have been replaced with actual compression method");
 1162
 1163                    // Fallback to stored if we somehow get here
 01164                    uncompressedStream = compressedStreamToRead;
 01165                    break;
 1166            }
 1167
 1821168            return uncompressedStream;
 1821169        }
 1170
 1171        private Stream OpenInReadMode(bool checkOpenable, ReadOnlySpan<char> password = default)
 1821172        {
 1821173            if (checkOpenable)
 1821174            {
 1821175                ThrowIfNotOpenable(needToUncompress: true, needToLoadIntoMemory: false);
 1821176            }
 1821177            return OpenInReadModeGetDataCompressor(GetOffsetOfCompressedData(), password);
 911178        }
 1179
 1180        private Stream OpenInReadModeGetDataCompressor(long offsetOfCompressedData, ReadOnlySpan<char> password = defaul
 1821181        {
 1821182            Stream compressedStream = new SubReadStream(_archive.ArchiveStream, offsetOfCompressedData, _compressedSize)
 1183            Stream streamToDecompress;
 1184
 1821185            if (IsEncrypted)
 1821186            {
 1821187                streamToDecompress = WrapWithDecryptionIfNeeded(compressedStream, password);
 911188            }
 1189            else
 01190            {
 01191                streamToDecompress = compressedStream;
 01192            }
 1193
 911194            return BuildDecompressionPipeline(streamToDecompress);
 911195        }
 1196
 1197        /// <summary>
 1198        /// Wraps a (possibly decrypted) stream with decompression and CRC validation.
 1199        /// Shared by both sync and async read paths.
 1200        /// </summary>
 1201        private Stream BuildDecompressionPipeline(Stream streamToDecompress)
 1821202        {
 1821203            Stream decompressedStream = GetDataDecompressor(streamToDecompress);
 1204
 1205            // AE-2 encrypted entries store CRC as 0 so skip CRC validation for those.
 1206            // AE-1 version entries store a valid CRC.
 1821207            if (IsAesEncrypted && _aeVersion == 2)
 481208            {
 481209                return decompressedStream;
 1210            }
 1211
 1341212            return new CrcValidatingReadStream(decompressedStream, _crc32, _uncompressedSize);
 1821213        }
 1214
 1215        private WrappedStream OpenInWriteMode()
 1821216        {
 1821217            if (_everOpenedForWrite)
 01218            {
 01219                throw new IOException(SR.CreateModeWriteOnceAndOneEntryAtATime);
 1220            }
 1221
 1222            // we assume that if another entry grabbed the archive stream, that it set this entry's _everOpenedForWrite 
 1821223            _archive.DebugAssertIsStillArchiveStreamOwner(this);
 1224
 1821225            return OpenInWriteModeCore();
 1821226        }
 1227
 1228        private WrappedStream OpenInWriteModeCore()
 1821229        {
 1821230            _everOpenedForWrite = true;
 1821231            Changes |= ZipArchive.ChangeState.StoredData;
 1232
 1233            // Use the encryption method pre-configured via PrepareEncryption (CreateEntry with password).
 1821234            ZipEncryptionMethod encryptionMethod = Encryption;
 1235
 1236            // Build the stream stack with encryption if needed
 1821237            Stream targetStream = _archive.ArchiveStream;
 1821238            Stream? encryptionStream = null;
 1239
 1821240            if (encryptionMethod == ZipEncryptionMethod.ZipCrypto)
 1341241            {
 1341242                ZipCryptoKeys keyMaterial = _derivedZipCryptoKeyMaterial
 1341243                    ?? throw new InvalidOperationException(SR.EmptyPassword);
 1244
 1341245                ushort verifierLow2Bytes = (ushort)ZipHelper.DateTimeToDosTime(_lastModified.DateTime);
 1246
 1341247                targetStream = ZipCryptoStream.Create(
 1341248                    baseStream: _archive.ArchiveStream,
 1341249                    keys: keyMaterial,
 1341250                    passwordVerifierLow2Bytes: verifierLow2Bytes,
 1341251                    encrypting: true,
 1341252                    crc32: null,
 1341253                    leaveOpen: true);
 1341254                encryptionStream = targetStream;
 1341255            }
 481256            else if (IsAesEncrypted)
 481257            {
 481258                WinZipAesKeyMaterial keyMaterial = _derivedAesKeyMaterial
 481259                    ?? throw new InvalidOperationException(SR.EmptyPassword);
 1260
 481261                targetStream = WinZipAesStream.Create(
 481262                    baseStream: _archive.ArchiveStream,
 481263                    keyMaterial: keyMaterial,
 481264                    totalStreamSize: -1,
 481265                    encrypting: true,
 481266                    leaveOpen: true);
 481267                encryptionStream = targetStream;
 481268            }
 1269
 1821270            CheckSumAndSizeWriteStream crcSizeStream = GetDataCompressor(
 1821271                targetStream,
 1821272                leaveBackingStreamOpen: !IsAesEncrypted,
 1821273                (object? o, EventArgs e) =>
 1821274                {
 1821275                    // release the archive stream
 1821276                    var entry = (ZipArchiveEntry)o!;
 1821277                    entry._archive.ReleaseArchiveStream(entry);
 1821278                    entry._outstandingWriteStream = null;
 1821279                },
 1821280                streamForPosition: encryptionMethod != ZipEncryptionMethod.None ? _archive.ArchiveStream : null);
 1281
 1821282            _outstandingWriteStream = new DirectToArchiveWriterStream(crcSizeStream, this, encryptionMethod, encryptionS
 1283
 1821284            return new WrappedStream(baseStream: _outstandingWriteStream, closeBaseStream: true);
 1821285        }
 1286
 1287        private WrappedStream OpenInUpdateMode(bool loadExistingContent = true, ReadOnlySpan<char> password = default)
 01288        {
 01289            if (_currentlyOpenForWrite)
 01290            {
 01291                throw new IOException(SR.UpdateModeOneStream);
 1292            }
 1293
 01294            if (Encryption == ZipEncryptionMethod.Unknown)
 01295            {
 01296                throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 1297            }
 1298
 1299            // Encrypted entries always require a password for re-encryption,
 1300            // even when discarding existing content (write-only access).
 01301            if (IsEncrypted && password.IsEmpty)
 01302            {
 01303                throw new ArgumentException(SR.PasswordRequired, nameof(password));
 1304            }
 1305
 01306            if (loadExistingContent)
 01307            {
 01308                ThrowIfNotOpenable(needToUncompress: true, needToLoadIntoMemory: true);
 01309            }
 1310
 01311            _currentlyOpenForWrite = true;
 1312
 1313            // Set up re-encryption key material so that the rewritten entry has valid encryption headers.
 01314            if (IsEncrypted)
 01315            {
 01316                SetupEncryptionKeyMaterial(password);
 01317            }
 1318
 01319            if (loadExistingContent)
 01320            {
 01321                _storedUncompressedData = GetUncompressedData(password);
 01322            }
 1323            else
 01324            {
 01325                _storedUncompressedData?.Dispose();
 01326                _storedUncompressedData = new MemoryStream();
 1327                // Opening with loadExistingContent: false discards existing content, which is a modification
 01328                MarkAsModified();
 01329            }
 1330
 01331            _storedUncompressedData.Seek(0, SeekOrigin.Begin);
 1332
 01333            return new WrappedStream(_storedUncompressedData, this,
 01334                onClosed: thisRef => thisRef!._currentlyOpenForWrite = false,
 01335                notifyEntryOnWrite: true);
 01336        }
 1337
 1338        // Returns a read-only, seekable view over the entry's current uncompressed buffer in Update mode.
 1339        // It reflects modifications made earlier in the current session (and is empty for a freshly created
 1340        // entry), unlike OpenInReadMode which reads the original bytes directly from the archive. The stream
 1341        // shares the underlying buffer with the entry rather than copying it, so it is not isolated from later
 1342        // in-place writes; callers are expected to finish reading before reopening the entry for writing.
 1343        private MemoryStream OpenInUpdateModeForRead()
 01344        {
 01345            if (_currentlyOpenForWrite)
 01346                throw new IOException(SR.UpdateModeOneStream);
 1347
 01348            MemoryStream uncompressedData = GetUncompressedData();
 01349            return new MemoryStream(uncompressedData.GetBuffer(), 0, (int)uncompressedData.Length, writable: false);
 01350        }
 1351
 1352        /// <summary>
 1353        /// Marks this entry as modified, indicating that its data has changed and needs to be rewritten.
 1354        /// </summary>
 1355        internal void MarkAsModified()
 01356        {
 01357            _everOpenedForWrite = true;
 01358            Changes |= ZipArchive.ChangeState.StoredData;
 1359
 1360            // Normalize compression method when actually modifying - Deflate64 data will be
 1361            // re-compressed as Deflate since we don't support writing Deflate64.
 01362            if (CompressionMethod != ZipCompressionMethod.Stored)
 01363            {
 01364                CompressionMethod = ZipCompressionMethod.Deflate;
 01365            }
 01366        }
 1367
 1368        /// <summary>
 1369        /// Sets up encryption key material for re-encryption when writing back to the archive.
 1370        /// </summary>
 1371        private void SetupEncryptionKeyMaterial(ReadOnlySpan<char> password)
 01372        {
 1373            // Derive and save key material for re-encryption
 01374            if (EncryptionMethod == ZipEncryptionMethod.ZipCrypto)
 01375            {
 01376                _derivedZipCryptoKeyMaterial = ZipCryptoStream.CreateKey(password);
 01377            }
 01378            else if (IsAesEncrypted)
 01379            {
 1380                // Generate new salt and derive key material for AES
 1381                // This ensures each write uses a fresh random salt for security
 01382                int keySizeBits = GetAesKeySizeBits(Encryption);
 01383                _derivedAesKeyMaterial = WinZipAesStream.CreateKey(password, salt: null, keySizeBits);
 01384            }
 01385        }
 1386
 1387        /// <summary>
 1388        /// Pre-derives encryption key material from a password for use when the entry is later opened for writing.
 1389        /// Called by <see cref="ZipArchive.CreateEntry(string, ReadOnlySpan{char}, ZipEncryptionMethod)"/>.
 1390        /// </summary>
 1391        internal void PrepareEncryption(ReadOnlySpan<char> password, ZipEncryptionMethod encryptionMethod)
 1821392        {
 1821393            if (password.IsEmpty)
 01394            {
 01395                throw new ArgumentException(SR.EmptyPassword, nameof(password));
 1396            }
 1397
 1821398            if (encryptionMethod == ZipEncryptionMethod.ZipCrypto)
 1341399            {
 1341400                Encryption = encryptionMethod;
 1341401                _derivedZipCryptoKeyMaterial = ZipCryptoStream.CreateKey(password);
 1341402            }
 481403            else if (encryptionMethod is ZipEncryptionMethod.Aes128 or ZipEncryptionMethod.Aes192 or ZipEncryptionMethod
 481404            {
 1405
 481406                Encryption = encryptionMethod;
 481407                int keySizeBits = GetAesKeySizeBits(encryptionMethod);
 481408                _derivedAesKeyMaterial = WinZipAesStream.CreateKey(password, salt: null, keySizeBits);
 481409            }
 1410            else
 01411            {
 1412                // Covers ZipEncryptionMethod.None, ZipEncryptionMethod.Unknown, and any undefined/out-of-range value.
 01413                throw new ArgumentOutOfRangeException(nameof(encryptionMethod), encryptionMethod, SR.EncryptionNotSpecif
 1414            }
 1821415        }
 1416
 1417        /// <summary>
 1418        /// Creates a WinZip AES extra field for writing to local/central directory headers.
 1419        /// </summary>
 1420        private WinZipAesExtraField CreateAesExtraField()
 1921421        {
 1921422            return new WinZipAesExtraField
 1921423            {
 1921424                VendorVersion = 2,
 1921425                AesStrength = Encryption switch
 1921426                {
 641427                    ZipEncryptionMethod.Aes128 => (byte)1,
 561428                    ZipEncryptionMethod.Aes192 => (byte)2,
 721429                    ZipEncryptionMethod.Aes256 => (byte)3,
 01430                    _ => throw new InvalidDataException(SR.InvalidAesStrength)
 1921431                },
 1921432                CompressionMethod = _compressionLevel == CompressionLevel.NoCompression
 1921433                    ? (ushort)ZipCompressionMethod.Stored
 1921434                    : (ushort)ZipCompressionMethod.Deflate
 1921435            };
 1921436        }
 1437
 1438        private bool IsOpenable(bool needToUncompress, bool needToLoadIntoMemory, out string? message)
 1821439        {
 1821440            message = null;
 1441
 1821442            if (_originallyInArchive)
 1821443            {
 1821444                if (!IsOpenableInitialVerifications(needToUncompress, out message))
 01445                {
 01446                    return false;
 1447                }
 1448
 1821449                if (!IsEncrypted && !ZipLocalFileHeader.TrySkipBlock(_archive.ArchiveStream))
 01450                {
 01451                    message = SR.LocalFileHeaderCorrupt;
 01452                    return false;
 1453                }
 1821454                else if (IsAesEncrypted)
 481455                {
 481456                    _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
 1457                    // AES case - skip the local file header and validate it exists.
 1458                    // The AES metadata (encryption strength, actual compression method) was already
 1459                    // parsed from the central directory in the constructor
 481460                    if (!ZipLocalFileHeader.TrySkipBlock(_archive.ArchiveStream))
 01461                    {
 01462                        message = SR.LocalFileHeaderCorrupt;
 01463                        return false;
 1464                    }
 481465                }
 1466
 1467                // Pass the detected encryption method to GetOffsetOfCompressedData
 1821468                long offsetOfCompressedData = GetOffsetOfCompressedData();
 1469
 1821470                if (!IsOpenableFinalVerifications(needToLoadIntoMemory, offsetOfCompressedData, out message))
 01471                {
 01472                    return false;
 1473                }
 1474
 1821475                return true;
 1476            }
 1477
 01478            return true;
 1821479        }
 1480
 1481        private bool IsOpenableInitialVerifications(bool needToUncompress, out string? message)
 3641482        {
 3641483            message = null;
 3641484            if (needToUncompress)
 3641485            {
 1486                // For AES-encrypted entries, CompressionMethod now contains the actual compression
 1487                // method (from the AES extra field), not the wrapper value 99. So we can use
 1488                // the same validation logic for both encrypted and non-encrypted entries.
 3641489                if (CompressionMethod != ZipCompressionMethod.Stored &&
 3641490                    CompressionMethod != ZipCompressionMethod.Deflate &&
 3641491                    CompressionMethod != ZipCompressionMethod.Deflate64)
 01492                {
 01493                    message = SR.UnsupportedCompression;
 01494                    return false;
 1495                }
 3641496            }
 3641497            if (_diskNumberStart != _archive.NumberOfThisDisk)
 01498            {
 01499                message = SR.SplitSpanned;
 01500                return false;
 1501            }
 3641502            if (_offsetOfLocalHeader > _archive.ArchiveStream.Length)
 01503            {
 01504                message = SR.LocalFileHeaderCorrupt;
 01505                return false;
 1506            }
 1507
 3641508            _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
 3641509            return true;
 3641510        }
 1511
 1512        private bool IsOpenableFinalVerifications(bool needToLoadIntoMemory, long offsetOfCompressedData, out string? me
 3641513        {
 3641514            message = null;
 1515            // Use an overflow-safe form: corrupt zip64 archives can report compressed sizes / offsets
 1516            // near long.MaxValue, which would wrap around in 'offsetOfCompressedData + _compressedSize'.
 3641517            if (offsetOfCompressedData < 0 ||
 3641518                _compressedSize < 0 ||
 3641519                _compressedSize > _archive.ArchiveStream.Length - offsetOfCompressedData)
 01520            {
 01521                message = SR.LocalFileHeaderCorrupt;
 01522                return false;
 1523            }
 1524            // This limitation originally existed because a) it is unreasonable to load > 4GB into memory
 1525            // but also because the stream reading functions make it hard.  This has been updated to handle
 1526            // this scenario in a 64-bit process using multiple buffers, delivered first as an OOB for
 1527            // compatibility.
 3641528            if (needToLoadIntoMemory)
 01529            {
 01530                if (_compressedSize > int.MaxValue)
 01531                {
 01532                    if (!s_allowLargeZipArchiveEntriesInUpdateMode)
 01533                    {
 01534                        message = SR.EntryTooLarge;
 01535                        return false;
 1536                    }
 01537                }
 01538            }
 1539
 3641540            return true;
 3641541        }
 1542
 4981543        private bool AreSizesTooLarge => _compressedSize > uint.MaxValue || _uncompressedSize > uint.MaxValue;
 1544
 1545        /// <summary>
 1546        /// The position immediately after all entry data (including any trailing data descriptor).
 1547        /// Computed while reading the central directory from the next entry's local header offset
 1548        /// or, for the last entry, the central directory start offset.
 1549        /// </summary>
 1550        internal long EndOfLocalEntryData
 1551        {
 01552            get => _endOfLocalEntryData;
 01553            set => _endOfLocalEntryData = value;
 1554        }
 1555
 1556        private static CompressionLevel MapCompressionLevel(BitFlagValues generalPurposeBitFlag, ZipCompressionMethod co
 1821557        {
 1558            // Information about the Deflate compression option is stored in bits 1 and 2 of the general purpose bit fla
 1559            // If the compression method is not Deflate, the Deflate compression option is invalid - default to NoCompre
 1821560            if (compressionMethod == ZipCompressionMethod.Deflate || compressionMethod == ZipCompressionMethod.Deflate64
 1821561            {
 1821562                return ((int)generalPurposeBitFlag & 0x6) switch
 1821563                {
 1821564                    0 => CompressionLevel.Optimal,
 01565                    2 => CompressionLevel.SmallestSize,
 01566                    4 => CompressionLevel.Fastest,
 01567                    6 => CompressionLevel.Fastest,
 01568                    _ => CompressionLevel.Optimal
 1821569                };
 1570            }
 1571            else
 01572            {
 01573                return CompressionLevel.NoCompression;
 1574            }
 1821575        }
 1576
 1577        private static BitFlagValues MapDeflateCompressionOption(BitFlagValues generalPurposeBitFlag, CompressionLevel c
 1821578        {
 1821579            ushort deflateCompressionOptions = (ushort)(
 1821580                // The Deflate compression level is only valid if the compression method is actually Deflate (or Deflate
 1821581                // value of the two bits is undefined and they should be zeroed out.
 1821582                compressionMethod == ZipCompressionMethod.Deflate || compressionMethod == ZipCompressionMethod.Deflate64
 1821583                    ? compressionLevel switch
 1821584                    {
 1821585                        CompressionLevel.Optimal => 0,
 01586                        CompressionLevel.SmallestSize => 2,
 01587                        CompressionLevel.Fastest => 6,
 01588                        CompressionLevel.NoCompression => 6,
 01589                        _ => 0
 1821590                    }
 1821591                    : 0);
 1592
 1821593            return (BitFlagValues)(((int)generalPurposeBitFlag & ~0x6) | deflateCompressionOptions);
 1821594        }
 1595
 3641596        private bool IsOffsetTooLarge => _offsetOfLocalHeader > uint.MaxValue;
 1597
 1821598        private bool ShouldUseZIP64 => AreSizesTooLarge || IsOffsetTooLarge;
 16101599        internal ZipEncryptionMethod Encryption { get => _encryptionMethod; private set => _encryptionMethod = value; }
 1600
 1601        private unsafe bool WriteLocalFileHeaderInitialize(bool isEmptyFile, bool forceWrite, bool preserveDataDescripto
 1821602        {
 1603            // _entryname only gets set when we read in or call moveTo. MoveTo does a check, and
 1604            // reading in should not be able to produce an entryname longer than ushort.MaxValue
 1821605            Debug.Assert(_storedEntryNameBytes.Length <= ushort.MaxValue);
 1606
 1607            // decide if we need the Zip64 extra field:
 1821608            zip64ExtraField = null;
 1609
 1610            // save offset
 1821611            _offsetOfLocalHeader = _archive.ArchiveStream.Position;
 1612
 1613            // for extra winzip aes header
 1821614            WinZipAesExtraField? aesExtraField = null;
 1821615            int aesExtraFieldSize = 0;
 1616
 1617            // if we already know that we have an empty file don't worry about anything, just do a straight shot of the 
 1821618            if (isEmptyFile)
 01619            {
 01620                CompressionMethod = ZipCompressionMethod.Stored;
 01621                compressedSizeTruncated = 0;
 01622                uncompressedSizeTruncated = 0;
 01623                Debug.Assert(_uncompressedSize == 0);
 01624                Debug.Assert(_crc32 == 0);
 01625            }
 1626            else
 1821627            {
 1821628                bool isCreateMode = _archive.Mode == ZipArchiveMode.Create;
 1629
 1821630                if (Encryption == ZipEncryptionMethod.ZipCrypto)
 1341631                {
 1341632                    _generalPurposeBitFlag |= BitFlagValues.IsEncrypted;
 1633                    // If DataDescriptor is used, then ZipCrypto password validation is done against the modification ti
 1634                    // not the CRC (which is not known in DataDescriptor mode). The current implementation always uses D
 1635                    // for ZipCrypto to avoid needing to seek back and update the header with the CRC that is known only
 1341636                    _generalPurposeBitFlag |= BitFlagValues.DataDescriptor;
 1341637                    compressedSizeTruncated = 0;
 1341638                    uncompressedSizeTruncated = 0;
 1341639                }
 481640                else if (IsAesEncrypted)
 481641                {
 481642                    _generalPurposeBitFlag |= BitFlagValues.IsEncrypted;
 481643                    if (!_archive.ArchiveStream.CanSeek)
 01644                    {
 01645                        _generalPurposeBitFlag |= BitFlagValues.DataDescriptor;
 01646                    }
 481647                    CompressionMethod = (ZipCompressionMethod)WinZipAesMethod;
 481648                    compressedSizeTruncated = 0;
 481649                    uncompressedSizeTruncated = 0;
 481650                    aesExtraField = CreateAesExtraField();
 481651                    aesExtraFieldSize = WinZipAesExtraField.TotalSize;
 481652                }
 01653                else if (isCreateMode && !_archive.ArchiveStream.CanSeek)
 01654                {
 01655                    _generalPurposeBitFlag |= BitFlagValues.DataDescriptor;
 01656                    compressedSizeTruncated = 0;
 01657                    uncompressedSizeTruncated = 0;
 01658                    Debug.Assert(_crc32 == 0);
 01659                }
 1660                else
 01661                {
 1662
 01663                    if (ShouldUseZIP64
 01664#if DEBUG_FORCE_ZIP64
 01665                || (_archive._forceZip64 && _archive.Mode == ZipArchiveMode.Update)
 01666#endif
 01667                        )
 01668                    {
 01669                        compressedSizeTruncated = ZipHelper.Mask32Bit;
 01670                        uncompressedSizeTruncated = ZipHelper.Mask32Bit;
 1671
 01672                        zip64ExtraField = new()
 01673                        {
 01674                            CompressedSize = _compressedSize,
 01675                            UncompressedSize = _uncompressedSize,
 01676                        };
 1677
 01678                        VersionToExtractAtLeast(ZipVersionNeededValues.Zip64);
 01679                    }
 1680                    else
 01681                    {
 01682                        compressedSizeTruncated = (uint)_compressedSize;
 01683                        uncompressedSizeTruncated = (uint)_uncompressedSize;
 01684                    }
 01685                }
 1821686            }
 1687
 1688            // save offset
 1821689            _offsetOfLocalHeader = _archive.ArchiveStream.Position;
 1690
 1691            // Calculate extra field
 1692            // When using AES encryption, exclude the AES tag from currExtraFieldDataLength since we're writing a new on
 1821693            int currExtraFieldDataLength = IsAesEncrypted
 1821694                ? ZipGenericExtraField.TotalSizeExcludingTag(_lhUnknownExtraFields, _lhTrailingExtraFieldData?.Length ??
 1821695                : ZipGenericExtraField.TotalSize(_lhUnknownExtraFields, _lhTrailingExtraFieldData?.Length ?? 0);
 1821696            int bigExtraFieldLength = (zip64ExtraField != null ? zip64ExtraField.TotalSize : 0)
 1821697                                      + aesExtraFieldSize
 1821698                                      + currExtraFieldDataLength;
 1699
 1821700            if (bigExtraFieldLength > ushort.MaxValue)
 01701            {
 01702                extraFieldLength = (ushort)((zip64ExtraField != null ? zip64ExtraField.TotalSize : 0) + aesExtraFieldSiz
 01703                _lhUnknownExtraFields = null;
 01704            }
 1705            else
 1821706            {
 1821707                extraFieldLength = (ushort)bigExtraFieldLength;
 1821708            }
 1709
 1821710            crc32ToWrite = _crc32;
 1711
 1712            // For AE-2, CRC is always 0 in the local file header
 1821713            if (IsAesEncrypted)
 481714            {
 481715                crc32ToWrite = 0;
 481716            }
 1717
 1718            // If this is an existing, unchanged entry then silently skip forwards.
 1719            // If it's new or changed, write the header.
 1821720            if (_originallyInArchive && Changes == ZipArchive.ChangeState.Unchanged && !forceWrite)
 01721            {
 01722                _archive.ArchiveStream.Seek(ZipLocalFileHeader.SizeOfLocalHeader + _storedEntryNameBytes.Length + extraF
 1723
 1724
 1725
 01726                return false;
 1727            }
 1728
 1729            // We are writing the header. For seekable/empty-file paths the sizes are written
 1730            // directly into the header, so a data descriptor is not needed.
 1821731            if (isEmptyFile || _archive.ArchiveStream.CanSeek)
 1821732            {
 1821733                if (preserveDataDescriptor)
 01734                {
 01735                    compressedSizeTruncated = 0;
 01736                    uncompressedSizeTruncated = 0;
 1737
 1738                    // zero the CRC/sizes since the real values live in the trailing descriptor that remains on disk.
 01739                    crc32ToWrite = 0;
 1740
 01741                    if (zip64ExtraField is not null)
 01742                    {
 01743                        zip64ExtraField = new() { CompressedSize = 0, UncompressedSize = 0 };
 01744                    }
 01745                }
 1821746                else if (Encryption != ZipEncryptionMethod.ZipCrypto)
 481747                {
 481748                    _generalPurposeBitFlag &= ~BitFlagValues.DataDescriptor;
 481749                }
 1821750            }
 1751
 1821752            return true;
 1821753        }
 1754
 1755        private void WriteLocalFileHeaderPrepare(Span<byte> lfStaticHeader, uint crc32, uint compressedSizeTruncated, ui
 1821756        {
 1821757            ZipLocalFileHeader.SignatureConstantBytes.CopyTo(lfStaticHeader[ZipLocalFileHeader.FieldLocations.Signature.
 1821758            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.VersionNeededToExt
 1821759            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.GeneralPurposeBitF
 1760
 1761            // For AES encryption, write compression method 99 (Aes) in the header
 1821762            ushort compressionMethodToWrite = IsAesEncrypted ? (ushort)WinZipAesMethod : (ushort)CompressionMethod;
 1821763            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.CompressionMethod.
 1764
 1821765            BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.LastModified..], Z
 1821766            BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.Crc32..], crc32);
 1821767            BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.CompressedSize..],
 1821768            BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.UncompressedSize..
 1821769            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.FilenameLength..],
 1821770            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.ExtraFieldLength..
 1821771        }
 1772
 1773        // return value is true if we allocated an extra field for 64 bit headers, un/compressed size
 1774        private unsafe bool WriteLocalFileHeader(bool isEmptyFile, bool forceWrite, bool preserveDataDescriptor = false)
 911775        {
 911776            if (WriteLocalFileHeaderInitialize(isEmptyFile, forceWrite, preserveDataDescriptor, out Zip64ExtraField? zip
 911777            {
 911778                Span<byte> lfStaticHeader = stackalloc byte[ZipLocalFileHeader.SizeOfLocalHeader];
 911779                WriteLocalFileHeaderPrepare(lfStaticHeader, crc32ToWrite, compressedSizeTruncated, uncompressedSizeTrunc
 1780
 1781                // write header
 911782                _archive.ArchiveStream.Write(lfStaticHeader);
 911783                _archive.ArchiveStream.Write(_storedEntryNameBytes);
 1784
 1785                // Write Zip64 extra field if needed
 911786                zip64ExtraField?.WriteBlock(_archive.ArchiveStream);
 1787
 1788                // Write AES extra field if using AES encryption
 911789                if (IsAesEncrypted)
 241790                {
 241791                    CreateAesExtraField().WriteBlock(_archive.ArchiveStream);
 1792
 1793                    // Write other extra fields, excluding any existing AES extra field to avoid duplication
 241794                    ZipGenericExtraField.WriteAllBlocksExcludingTag(_lhUnknownExtraFields, _lhTrailingExtraFieldData ?? 
 241795                }
 1796                else
 671797                {
 1798                    // Write other extra fields
 671799                    ZipGenericExtraField.WriteAllBlocks(_lhUnknownExtraFields, _lhTrailingExtraFieldData ?? Array.Empty<
 671800                }
 911801            }
 1802
 911803            return zip64ExtraField != null;
 911804        }
 1805
 1806        private unsafe void WriteLocalFileHeaderAndDataIfNeeded(bool forceWrite)
 911807        {
 1808            // Check if the entry's stored data was actually modified (StoredData flag is set).
 1809            // If _storedUncompressedData is loaded but StoredData is not set, it means the entry
 1810            // was opened for update but no writes occurred - we should use the original compressed bytes.
 911811            bool storedDataModified = (Changes & ZipArchive.ChangeState.StoredData) != 0;
 1812
 1813            // If _storedUncompressedData is loaded but not modified, clear it so we use _compressedBytes
 911814            if (_storedUncompressedData != null && !storedDataModified)
 01815            {
 01816                _storedUncompressedData.Dispose();
 01817                _storedUncompressedData = null;
 01818            }
 1819
 1820            // _storedUncompressedData gets frozen here, and is what gets written to the file
 911821            if (_storedUncompressedData != null || _compressedBytes != null)
 01822            {
 01823                if (_storedUncompressedData != null)
 01824                {
 01825                    _uncompressedSize = _storedUncompressedData.Length;
 1826
 1827                    // Check if we need to re-encrypt with ZipCrypto (only if we have cached key material)
 01828                    if (Encryption == ZipEncryptionMethod.ZipCrypto && _derivedZipCryptoKeyMaterial is not null)
 01829                    {
 01830                        WriteLocalFileHeader(isEmptyFile: false, forceWrite: true);
 1831
 01832                        long startPosition = _archive.ArchiveStream.Position;
 1833
 01834                        ushort verifierLow2Bytes = (ushort)ZipHelper.DateTimeToDosTime(_lastModified.DateTime);
 1835
 01836                        using (Stream encryptionStream = ZipCryptoStream.Create(
 01837                            baseStream: _archive.ArchiveStream,
 01838                            keys: _derivedZipCryptoKeyMaterial.Value,
 01839                            passwordVerifierLow2Bytes: verifierLow2Bytes,
 01840                            encrypting: true,
 01841                            crc32: null,
 01842                            leaveOpen: true))
 01843                        {
 01844                            using (CheckSumAndSizeWriteStream crcStream = GetDataCompressor(encryptionStream, leaveBacki
 01845                            {
 01846                                _storedUncompressedData.Seek(0, SeekOrigin.Begin);
 01847                                _storedUncompressedData.CopyTo(crcStream);
 01848                            }
 01849                        }
 1850
 01851                        _compressedSize = _archive.ArchiveStream.Position - startPosition;
 1852
 01853                        WriteDataDescriptor();
 1854
 01855                        _storedUncompressedData.Dispose();
 01856                        _storedUncompressedData = null;
 01857                    }
 01858                    else if (IsAesEncrypted && _derivedAesKeyMaterial is not null)
 01859                    {
 1860
 01861                        bool usedZip64InLH = WriteLocalFileHeader(isEmptyFile: false, forceWrite: true);
 1862
 01863                        long startPosition = _archive.ArchiveStream.Position;
 1864
 01865                        bool useDeflate = _compressionLevel != CompressionLevel.NoCompression;
 1866
 01867                        using (Stream encryptionStream = WinZipAesStream.Create(
 01868                            baseStream: _archive.ArchiveStream,
 01869                            keyMaterial: _derivedAesKeyMaterial.Value,
 01870                            totalStreamSize: -1,
 01871                            encrypting: true,
 01872                            leaveOpen: true))
 01873                        {
 01874                            if (_storedUncompressedData.Length > 0)
 01875                            {
 01876                                ZipCompressionMethod savedMethod = CompressionMethod;
 01877                                CompressionMethod = useDeflate ? ZipCompressionMethod.Deflate : ZipCompressionMethod.Sto
 1878
 1879                                try
 01880                                {
 01881                                    using (CheckSumAndSizeWriteStream crcStream = GetDataCompressor(encryptionStream, le
 01882                                    {
 01883                                        _storedUncompressedData.Seek(0, SeekOrigin.Begin);
 01884                                        _storedUncompressedData.CopyTo(crcStream);
 01885                                    }
 01886                                }
 1887                                finally
 01888                                {
 01889                                    CompressionMethod = (ZipCompressionMethod)WinZipAesMethod;
 01890                                }
 01891                            }
 1892                            else
 01893                            {
 01894                                _crc32 = 0;
 01895                                _uncompressedSize = 0;
 01896                            }
 01897                        }
 1898
 01899                        _compressedSize = _archive.ArchiveStream.Position - startPosition;
 1900
 01901                        WriteCrcAndSizesInLocalHeader(usedZip64InLH);
 1902
 01903                        _storedUncompressedData.Dispose();
 01904                        _storedUncompressedData = null;
 01905                    }
 1906                    else
 01907                    {
 1908                        // Non-encrypted: use standard path
 01909                        using (DirectToArchiveWriterStream entryWriter = new(GetDataCompressor(_archive.ArchiveStream, t
 01910                        {
 01911                            _storedUncompressedData.Seek(0, SeekOrigin.Begin);
 01912                            _storedUncompressedData.CopyTo(entryWriter);
 01913                        }
 01914                        _storedUncompressedData.Dispose();
 01915                        _storedUncompressedData = null;
 01916                    }
 01917                }
 1918                else // _compressedBytes path - copying unchanged entry data
 01919                {
 01920                    bool emptyEncryptedEntry = _uncompressedSize == 0 && Encryption != ZipEncryptionMethod.None;
 01921                    if (_uncompressedSize == 0 && !emptyEncryptedEntry)
 01922                    {
 1923                        // reset size to ensure proper central directory size header
 01924                        _compressedSize = 0;
 01925                    }
 1926
 1927                    // For unchanged entries, we need to write the header correctly but avoid
 1928                    // WriteLocalFileHeader creating NEW encryption structures (which would have
 1929                    // wrong compression method from _compressionLevel).
 1930                    // The original AES extra field is preserved in _lhUnknownExtraFields.
 01931                    BitFlagValues savedFlags = _generalPurposeBitFlag;
 01932                    ZipEncryptionMethod savedEncryption = Encryption;
 01933                    ZipCompressionMethod savedCompressionMethod = CompressionMethod;
 1934
 1935                    try
 01936                    {
 1937                        // For AES entries: set CompressionMethod to Aes so header writes method 99,
 1938                        // but clear _encryptionMethod so WriteLocalFileHeader doesn't create a new
 1939                        // AES extra field (the original one in _lhUnknownExtraFields will be used).
 01940                        if (savedEncryption is ZipEncryptionMethod.Aes128 or ZipEncryptionMethod.Aes192 or ZipEncryption
 01941                        {
 01942                            CompressionMethod = (ZipCompressionMethod)WinZipAesMethod;
 01943                            Encryption = ZipEncryptionMethod.None;
 01944                        }
 1945
 01946                        WriteLocalFileHeader(isEmptyFile: _uncompressedSize == 0 && !emptyEncryptedEntry, forceWrite: tr
 1947
 1948                        // WriteLocalFileHeaderInitialize may have cleared the DataDescriptor flag
 1949                        // (because Encryption was temporarily set to None and the stream is seekable).
 1950                        // If the original entry had a data descriptor, patch the general-purpose bit
 1951                        // flags in the already-written local header to match, so the header on disk
 1952                        // is consistent with the data descriptor we conditionally write below.
 01953                        if ((savedFlags & BitFlagValues.DataDescriptor) != 0 &&
 01954                            (_generalPurposeBitFlag & BitFlagValues.DataDescriptor) == 0)
 01955                        {
 01956                            long currentPos = _archive.ArchiveStream.Position;
 01957                            _archive.ArchiveStream.Seek(
 01958                                _offsetOfLocalHeader + ZipLocalFileHeader.FieldLocations.GeneralPurposeBitFlags,
 01959                                SeekOrigin.Begin);
 01960                            Span<byte> flagBytes = stackalloc byte[2];
 01961                            BinaryPrimitives.WriteUInt16LittleEndian(flagBytes, (ushort)savedFlags);
 01962                            _archive.ArchiveStream.Write(flagBytes);
 01963                            _archive.ArchiveStream.Seek(currentPos, SeekOrigin.Begin);
 01964                        }
 01965                    }
 1966                    finally
 01967                    {
 1968                        // Restore original state
 01969                        _generalPurposeBitFlag = savedFlags;
 01970                        Encryption = savedEncryption;
 01971                        CompressionMethod = savedCompressionMethod;
 01972                    }
 1973
 1974                    // according to ZIP specs, zero-byte unencrypted files MUST NOT include file data
 01975                    if (_uncompressedSize != 0 || emptyEncryptedEntry)
 01976                    {
 01977                        Debug.Assert(_compressedBytes != null);
 01978                        foreach (byte[] compressedBytes in _compressedBytes)
 01979                        {
 01980                            _archive.ArchiveStream.Write(compressedBytes, 0, compressedBytes.Length);
 01981                        }
 01982                    }
 1983
 1984                    // Write data descriptor if the original entry had one
 01985                    if ((savedFlags & BitFlagValues.DataDescriptor) != 0)
 01986                    {
 01987                        WriteDataDescriptor();
 01988                    }
 01989                }
 01990            }
 1991            else // there is no data in the file (or the data in the file has not been loaded), but if we are in update 
 911992            {
 911993                if (_archive.Mode == ZipArchiveMode.Update || !_everOpenedForWrite)
 01994                {
 01995                    _everOpenedForWrite = true;
 1996                    // Preserve the data descriptor flag for entries that originally had one,
 1997                    // since the descriptor bytes remain on disk after the compressed data.
 01998                    bool preserveDataDescriptor = _originallyInArchive
 01999                        && (_generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0;
 02000                    WriteLocalFileHeader(isEmptyFile: _uncompressedSize == 0, forceWrite: forceWrite, preserveDataDescri
 2001
 2002                    // Advance the stream past the compressed data and any trailing data descriptor
 2003                    // by seeking to the pre-computed end-of-entry boundary.
 02004                    if (_endOfLocalEntryData > _archive.ArchiveStream.Position)
 02005                    {
 02006                        _archive.ArchiveStream.Seek(_endOfLocalEntryData, SeekOrigin.Begin);
 02007                    }
 02008                }
 912009            }
 912010        }
 2011
 2012        private const int MetadataBufferLength = ZipLocalFileHeader.FieldLengths.VersionNeededToExtract + ZipLocalFileHe
 2013        private const int CrcAndSizesBufferLength = ZipLocalFileHeader.FieldLengths.Crc32 + ZipLocalFileHeader.FieldLeng
 2014        private const int Zip64SizesBufferLength = Zip64ExtraField.FieldLengths.UncompressedSize + Zip64ExtraField.Field
 2015        private const int Zip64DataDescriptorCrcAndSizesBufferLength = ZipLocalFileHeader.Zip64DataDescriptor.FieldLengt
 2016            + ZipLocalFileHeader.Zip64DataDescriptor.FieldLengths.CompressedSize + ZipLocalFileHeader.Zip64DataDescripto
 2017
 2018        // Using _offsetOfLocalHeader, seeks back to where CRC and sizes should be in the header,
 2019        // writes them, then seeks back to where you started
 2020        // Assumes that the stream is currently at the end of the data
 2021        private unsafe void WriteCrcAndSizesInLocalHeader(bool zip64HeaderUsed)
 912022        {
 2023            // Buffer has been sized to the largest data payload required: the 64-bit data descriptor.
 912024            Span<byte> writeBuffer = stackalloc byte[Zip64DataDescriptorCrcAndSizesBufferLength];
 2025
 912026            WriteCrcAndSizesInLocalHeaderInitialize(zip64HeaderUsed, out long finalPosition, out bool pretendStreaming, 
 2027
 2028            // first step is, if we need zip64, but didn't allocate it, pretend we did a stream write, because
 2029            // we can't go back and give ourselves the space that the extra field needs.
 2030            // we do this by setting the correct property in the bit flag to indicate we have a data descriptor
 2031            // and setting the version to Zip64 to indicate that descriptor contains 64-bit values
 912032            if (pretendStreaming)
 02033            {
 02034                WriteCrcAndSizesInLocalHeaderPrepareForZip64PretendStreaming(writeBuffer);
 02035                _archive.ArchiveStream.Write(writeBuffer[..MetadataBufferLength]);
 02036            }
 2037
 2038            // next step is fill out the 32-bit size values in the normal header. we can't assume that
 2039            // they are correct. we also write the CRC
 912040            WriteCrcAndSizesInLocalHeaderPrepareFor32bitValuesWriting(pretendStreaming, writeBuffer, compressedSizeTrunc
 912041            _archive.ArchiveStream.Write(writeBuffer[..CrcAndSizesBufferLength]);
 2042
 2043            // next step: if we wrote the 64 bit header initially, a different implementation might
 2044            // try to read it, even if the 32-bit size values aren't masked. thus, we should always put the
 2045            // correct size information in there. note that order of uncomp/comp is switched, and these are
 2046            // 64-bit values
 2047            // also, note that in order for this to be correct, we have to ensure that the zip64 extra field
 2048            // is always the first extra field that is written
 912049            if (zip64HeaderUsed)
 02050            {
 02051                WriteCrcAndSizesInLocalHeaderPrepareForWritingWhenZip64HeaderUsed(writeBuffer);
 02052                _archive.ArchiveStream.Write(writeBuffer[..Zip64SizesBufferLength]);
 02053            }
 2054
 2055            // now go to the where we were. assume that this is the end of the data
 912056            _archive.ArchiveStream.Seek(finalPosition, SeekOrigin.Begin);
 2057
 2058            // if we are pretending we did a stream write, we want to write the data descriptor out
 2059            // the data descriptor can have 32-bit sizes or 64-bit sizes. In this case, we always use
 2060            // 64-bit sizes
 912061            if (pretendStreaming)
 02062            {
 02063                WriteCrcAndSizesInLocalHeaderPrepareForWritingDataDescriptor(writeBuffer);
 02064                _archive.ArchiveStream.Write(writeBuffer[..Zip64DataDescriptorCrcAndSizesBufferLength]);
 02065            }
 912066        }
 2067
 2068        private void WriteCrcAndSizesInLocalHeaderInitialize(bool zip64HeaderUsed, out long finalPosition, out bool pret
 1822069        {
 1822070            finalPosition = _archive.ArchiveStream.Position;
 2071
 1822072            bool zip64Needed = ShouldUseZIP64
 1822073#if DEBUG_FORCE_ZIP64
 1822074                || _archive._forceZip64
 1822075#endif
 1822076            ;
 2077
 1822078            pretendStreaming = zip64Needed && !zip64HeaderUsed;
 1822079            compressedSizeTruncated = zip64Needed ? ZipHelper.Mask32Bit : (uint)_compressedSize;
 1822080            uncompressedSizeTruncated = zip64Needed ? ZipHelper.Mask32Bit : (uint)_uncompressedSize;
 1822081        }
 2082
 2083        private void WriteCrcAndSizesInLocalHeaderPrepareForZip64PretendStreaming(Span<byte> writeBuffer)
 02084        {
 02085            int relativeVersionToExtractLocation = ZipLocalFileHeader.FieldLocations.VersionNeededToExtract - ZipLocalFi
 02086            int relativeGeneralPurposeBitFlagsLocation = ZipLocalFileHeader.FieldLocations.GeneralPurposeBitFlags - ZipL
 2087
 02088            VersionToExtractAtLeast(ZipVersionNeededValues.Zip64);
 02089            _generalPurposeBitFlag |= BitFlagValues.DataDescriptor;
 2090
 02091            _archive.ArchiveStream.Seek(_offsetOfLocalHeader + ZipLocalFileHeader.FieldLocations.VersionNeededToExtract,
 02092                                        SeekOrigin.Begin);
 02093            BinaryPrimitives.WriteUInt16LittleEndian(writeBuffer[relativeVersionToExtractLocation..], (ushort)_versionTo
 02094            BinaryPrimitives.WriteUInt16LittleEndian(writeBuffer[relativeGeneralPurposeBitFlagsLocation..], (ushort)_gen
 02095        }
 2096
 2097        private void WriteCrcAndSizesInLocalHeaderPrepareFor32bitValuesWriting(bool pretendStreaming, Span<byte> writeBu
 1822098        {
 1822099            _archive.ArchiveStream.Seek(_offsetOfLocalHeader + ZipLocalFileHeader.FieldLocations.Crc32,
 1822100                                            SeekOrigin.Begin);
 1822101            if (!pretendStreaming)
 1822102            {
 1822103                int relativeCrc32Location = ZipLocalFileHeader.FieldLocations.Crc32 - ZipLocalFileHeader.FieldLocations.
 1822104                int relativeCompressedSizeLocation = ZipLocalFileHeader.FieldLocations.CompressedSize - ZipLocalFileHead
 1822105                int relativeUncompressedSizeLocation = ZipLocalFileHeader.FieldLocations.UncompressedSize - ZipLocalFile
 2106                // when using aes encryption, ae-2 standard dictates crc to be 0
 1822107                uint crcToWrite = IsAesEncrypted ? 0 : _crc32;
 1822108                BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer[relativeCrc32Location..], crcToWrite);
 1822109                BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer[relativeCompressedSizeLocation..], compressedSizeTr
 1822110                BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer[relativeUncompressedSizeLocation..], uncompressedSi
 1822111            }
 2112            else // but if we are pretending to stream, we want to fill in with zeroes
 02113            {
 02114                writeBuffer[..CrcAndSizesBufferLength].Clear();
 02115            }
 1822116        }
 2117
 2118        private void WriteCrcAndSizesInLocalHeaderPrepareForWritingWhenZip64HeaderUsed(Span<byte> writeBuffer)
 02119        {
 02120            int relativeUncompressedSizeLocation = Zip64ExtraField.FieldLocations.UncompressedSize - Zip64ExtraField.Fie
 02121            int relativeCompressedSizeLocation = Zip64ExtraField.FieldLocations.CompressedSize - Zip64ExtraField.FieldLo
 2122
 02123            _archive.ArchiveStream.Seek(_offsetOfLocalHeader + ZipLocalFileHeader.SizeOfLocalHeader
 02124                                        + _storedEntryNameBytes.Length + Zip64ExtraField.OffsetToFirstField,
 02125                                        SeekOrigin.Begin);
 02126            BinaryPrimitives.WriteInt64LittleEndian(writeBuffer[relativeUncompressedSizeLocation..], _uncompressedSize);
 02127            BinaryPrimitives.WriteInt64LittleEndian(writeBuffer[relativeCompressedSizeLocation..], _compressedSize);
 02128        }
 2129
 2130        private void WriteCrcAndSizesInLocalHeaderPrepareForWritingDataDescriptor(Span<byte> writeBuffer)
 02131        {
 02132            int relativeCrc32Location = ZipLocalFileHeader.Zip64DataDescriptor.FieldLocations.Crc32 - ZipLocalFileHeader
 02133            int relativeCompressedSizeLocation = ZipLocalFileHeader.Zip64DataDescriptor.FieldLocations.CompressedSize - 
 02134            int relativeUncompressedSizeLocation = ZipLocalFileHeader.Zip64DataDescriptor.FieldLocations.UncompressedSiz
 2135            // when using aes encryption, ae-2 standard dictates crc to be 0
 02136            uint crcToWrite = IsAesEncrypted ? 0 : _crc32;
 02137            BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer.Slice(relativeCrc32Location), crcToWrite);
 02138            BinaryPrimitives.WriteInt64LittleEndian(writeBuffer.Slice(relativeCompressedSizeLocation), _compressedSize);
 02139            BinaryPrimitives.WriteInt64LittleEndian(writeBuffer.Slice(relativeUncompressedSizeLocation), _uncompressedSi
 2140
 02141        }
 2142
 2143        // data descriptor can be 32-bit or 64-bit sizes. 32-bit is more compatible, so use that if possible
 2144        // signature is optional but recommended by the spec
 2145        private const int MaxSizeOfDataDescriptor = 24;
 2146
 2147        private unsafe void WriteDataDescriptor()
 672148        {
 672149            Span<byte> dataDescriptor = stackalloc byte[MaxSizeOfDataDescriptor];
 672150            int bytesToWrite = PrepareToWriteDataDescriptor(dataDescriptor);
 672151            _archive.ArchiveStream.Write(dataDescriptor[..bytesToWrite]);
 672152        }
 2153
 2154        private int PrepareToWriteDataDescriptor(Span<byte> dataDescriptor)
 1342155        {
 2156            // We enter here because we cannot seek, so the data descriptor bit should be on
 1342157            Debug.Assert((_generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0);
 2158
 2159            int bytesToWrite;
 2160
 1342161            ZipLocalFileHeader.DataDescriptorSignatureConstantBytes.CopyTo(dataDescriptor[ZipLocalFileHeader.ZipDataDesc
 2162            // when using aes encryption, ae-2 standard dictates crc to be 0
 1342163            uint crcToWrite = IsAesEncrypted ? 0 : _crc32;
 1342164            BinaryPrimitives.WriteUInt32LittleEndian(dataDescriptor[ZipLocalFileHeader.ZipDataDescriptor.FieldLocations.
 2165
 1342166            if (AreSizesTooLarge)
 02167            {
 02168                BinaryPrimitives.WriteInt64LittleEndian(dataDescriptor[ZipLocalFileHeader.Zip64DataDescriptor.FieldLocat
 02169                BinaryPrimitives.WriteInt64LittleEndian(dataDescriptor[ZipLocalFileHeader.Zip64DataDescriptor.FieldLocat
 2170
 02171                bytesToWrite = ZipLocalFileHeader.Zip64DataDescriptor.FieldLocations.UncompressedSize + ZipLocalFileHead
 02172            }
 2173            else
 1342174            {
 1342175                BinaryPrimitives.WriteUInt32LittleEndian(dataDescriptor[ZipLocalFileHeader.ZipDataDescriptor.FieldLocati
 1342176                BinaryPrimitives.WriteUInt32LittleEndian(dataDescriptor[ZipLocalFileHeader.ZipDataDescriptor.FieldLocati
 2177
 1342178                bytesToWrite = ZipLocalFileHeader.ZipDataDescriptor.FieldLocations.UncompressedSize + ZipLocalFileHeader
 1342179            }
 2180
 1342181            return bytesToWrite;
 1342182        }
 2183
 2184        internal void UnloadStreams()
 912185        {
 912186            _storedUncompressedData?.Dispose();
 912187            _compressedBytes = null;
 912188            _outstandingWriteStream = null;
 912189        }
 2190
 2191        private void CloseStreams()
 912192        {
 2193            // if the user left the stream open, close the underlying stream for them
 912194            _outstandingWriteStream?.Dispose();
 912195        }
 2196
 2197        private void VersionToExtractAtLeast(ZipVersionNeededValues value)
 3642198        {
 3642199            if (_versionToExtract < value)
 1822200            {
 1822201                _versionToExtract = value;
 1822202                Changes |= ZipArchive.ChangeState.FixedLengthMetadata;
 1822203            }
 3642204            if (_versionMadeBySpecification < value)
 1822205            {
 1822206                _versionMadeBySpecification = value;
 1822207                Changes |= ZipArchive.ChangeState.FixedLengthMetadata;
 1822208            }
 3642209        }
 2210
 2211        private void ThrowIfInvalidArchive()
 5462212        {
 5462213            if (_archive == null)
 02214            {
 02215                throw new InvalidOperationException(SR.DeletedEntry);
 2216            }
 5462217            _archive.ThrowIfDisposed();
 5462218        }
 2219
 2220        /// <summary>
 2221        /// Gets the file name of the path based on Windows path separator characters
 2222        /// </summary>
 2223        private static string GetFileName_Windows(string path)
 3642224        {
 3642225            int i = path.AsSpan().LastIndexOfAny('\\', '/', ':');
 3642226            return i >= 0 ?
 3642227                path.Substring(i + 1) :
 3642228                path;
 3642229        }
 2230
 2231        /// <summary>
 2232        /// Gets the file name of the path based on Unix path separator characters
 2233        /// </summary>
 2234        private static string GetFileName_Unix(string path)
 02235        {
 02236            int i = path.LastIndexOf('/');
 02237            return i >= 0 ?
 02238                path.Substring(i + 1) :
 02239                path;
 02240        }
 2241
 2242        private sealed class DirectToArchiveWriterStream : Stream
 2243        {
 2244            private long _position;
 2245            private readonly CheckSumAndSizeWriteStream _crcSizeStream;
 2246            private bool _everWritten;
 2247            private bool _isDisposed;
 2248            private readonly ZipArchiveEntry _entry;
 2249            private bool _usedZip64inLH;
 2250            private bool _canWrite;
 2251            private readonly ZipEncryptionMethod _encryption;
 2252            private readonly Stream? _encryptionStream;
 2253
 2254            // makes the assumption that somewhere down the line, crcSizeStream is eventually writing directly to the ar
 2255            // this class calls other functions on ZipArchiveEntry that write directly to the archive
 1822256            public DirectToArchiveWriterStream(CheckSumAndSizeWriteStream crcSizeStream, ZipArchiveEntry entry, ZipEncry
 1822257            {
 1822258                _position = 0;
 1822259                _crcSizeStream = crcSizeStream;
 1822260                _everWritten = false;
 1822261                _isDisposed = false;
 1822262                _entry = entry;
 1822263                _usedZip64inLH = false;
 1822264                _canWrite = true;
 1822265                _encryption = encryptionMethod;
 1822266                _encryptionStream = encryptionStream;
 1822267            }
 2268
 2269            public override long Length
 2270            {
 2271                get
 02272                {
 02273                    ThrowIfDisposed();
 02274                    throw new NotSupportedException(SR.SeekingNotSupported);
 2275                }
 2276            }
 2277            public override long Position
 2278            {
 2279                get
 02280                {
 02281                    ThrowIfDisposed();
 02282                    return _position;
 02283                }
 2284                set
 02285                {
 02286                    ThrowIfDisposed();
 02287                    throw new NotSupportedException(SR.SeekingNotSupported);
 2288                }
 2289            }
 2290
 02291            public override bool CanRead => false;
 02292            public override bool CanSeek => false;
 3642293            public override bool CanWrite => _canWrite;
 2294
 2295            private void ThrowIfDisposed()
 1822296            {
 1822297                if (_isDisposed)
 02298                {
 02299                    throw new ObjectDisposedException(GetType().ToString(), SR.HiddenStreamName);
 2300                }
 1822301            }
 2302
 2303            public override int Read(byte[] buffer, int offset, int count)
 02304            {
 02305                ThrowIfDisposed();
 02306                throw new NotSupportedException(SR.ReadingNotSupported);
 2307            }
 2308
 2309            public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToke
 02310            {
 02311                ThrowIfDisposed();
 02312                throw new NotSupportedException(SR.ReadingNotSupported);
 2313            }
 2314
 2315            public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
 02316            {
 02317                ThrowIfDisposed();
 02318                throw new NotSupportedException(SR.ReadingNotSupported);
 2319            }
 2320
 2321            public override long Seek(long offset, SeekOrigin origin)
 02322            {
 02323                ThrowIfDisposed();
 02324                throw new NotSupportedException(SR.SeekingNotSupported);
 2325            }
 2326
 2327            public override void SetLength(long value)
 02328            {
 02329                ThrowIfDisposed();
 02330                throw new NotSupportedException(SR.SetLengthRequiresSeekingAndWriting);
 2331            }
 2332
 2333            // careful: assumes that write is the only way to write to the stream, if writebyte/beginwrite are implement
 2334            // they must set _everWritten, etc.
 2335            public override void Write(byte[] buffer, int offset, int count)
 02336            {
 02337                ValidateBufferArguments(buffer, offset, count);
 2338
 02339                ThrowIfDisposed();
 02340                Debug.Assert(CanWrite);
 2341
 2342                // if we're not actually writing anything, we don't want to trigger the header
 02343                if (count == 0)
 02344                {
 02345                    return;
 2346                }
 2347
 02348                if (!_everWritten)
 02349                {
 02350                    _everWritten = true;
 2351                    // write local header, we are good to go
 02352                    _usedZip64inLH = _entry.WriteLocalFileHeader(isEmptyFile: false, forceWrite: true);
 02353                }
 2354
 02355                _crcSizeStream.Write(buffer, offset, count);
 02356                _position += count;
 02357            }
 2358
 2359            public override void Write(ReadOnlySpan<byte> source)
 912360            {
 912361                ThrowIfDisposed();
 912362                Debug.Assert(CanWrite);
 2363
 2364                // if we're not actually writing anything, we don't want to trigger the header
 912365                if (source.Length == 0)
 02366                {
 02367                    return;
 2368                }
 2369
 912370                if (!_everWritten)
 912371                {
 912372                    _everWritten = true;
 2373                    // write local header, we are good to go
 912374                    _usedZip64inLH = _entry.WriteLocalFileHeader(isEmptyFile: false, forceWrite: true);
 912375                }
 2376
 912377                _crcSizeStream.Write(source);
 912378                _position += source.Length;
 912379            }
 2380
 2381            public override void WriteByte(byte value) =>
 02382                Write(new ReadOnlySpan<byte>(in value));
 2383
 2384            public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
 02385            {
 02386                ValidateBufferArguments(buffer, offset, count);
 02387                return WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken).AsTask();
 02388            }
 2389
 2390            public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = defa
 912391            {
 912392                ThrowIfDisposed();
 912393                Debug.Assert(CanWrite);
 2394
 912395                return !buffer.IsEmpty ?
 912396                    Core(buffer, cancellationToken) :
 912397                    default;
 2398
 2399                async ValueTask Core(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
 912400                {
 912401                    if (!_everWritten)
 912402                    {
 912403                        _everWritten = true;
 2404                        // write local header, we are good to go
 912405                        _usedZip64inLH = await _entry.WriteLocalFileHeaderAsync(isEmptyFile: false, forceWrite: true, pr
 912406                    }
 2407
 912408                    await _crcSizeStream.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
 912409                    _position += buffer.Length;
 912410                }
 912411            }
 2412
 2413            public override void Flush()
 02414            {
 02415                ThrowIfDisposed();
 02416                Debug.Assert(CanWrite);
 2417
 02418                _crcSizeStream.Flush();
 02419            }
 2420
 2421            public override Task FlushAsync(CancellationToken cancellationToken)
 02422            {
 02423                ThrowIfDisposed();
 02424                Debug.Assert(CanWrite);
 2425
 02426                return _crcSizeStream.FlushAsync(cancellationToken);
 02427            }
 2428
 2429            protected override void Dispose(bool disposing)
 1822430            {
 1822431                if (disposing && !_isDisposed)
 912432                {
 912433                    _crcSizeStream.Dispose(); // now we have size/crc info
 2434
 912435                    bool finishHeader = _everWritten;
 2436
 912437                    if (!_everWritten)
 02438                    {
 02439                        if (_encryptionStream is null)
 02440                        {
 2441                            // write local header, no data, so we use stored
 02442                            _entry.WriteLocalFileHeader(isEmptyFile: true, forceWrite: true);
 02443                        }
 2444                        else
 02445                        {
 2446                            // No data was written through CheckSumAndSizeWriteStream, but an encrypted entry
 2447                            // still stores its encryption header (and, for AES, the authentication code), so
 2448                            // the local file header must describe an encrypted entry. Write it first, then
 2449                            // dispose the encryption stream (its lazy _baseStream was never created, so it
 2450                            // would otherwise be orphaned) to emit those bytes after the header.
 02451                            _usedZip64inLH = _entry.WriteLocalFileHeader(isEmptyFile: false, forceWrite: true);
 2452
 02453                            long startPosition = _entry._archive.ArchiveStream.Position;
 2454
 02455                            _encryptionStream.Dispose();
 2456
 02457                            _entry._compressedSize = _entry._archive.ArchiveStream.Position - startPosition;
 02458                            finishHeader = true;
 02459                        }
 02460                    }
 2461
 912462                    if (finishHeader)
 912463                    {
 2464                        // go back and finish writing
 912465                        if (_entry._archive.ArchiveStream.CanSeek)
 912466                        {
 2467                            // finish writing local header if we have seek capabilities
 912468                            _entry.WriteCrcAndSizesInLocalHeader(_usedZip64inLH);
 2469
 2470                            // ZipCrypto entries retain DataDescriptor for check byte correctness;
 2471                            // write the trailing descriptor so the archive is consistent.
 912472                            if ((_entry._generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0)
 672473                            {
 672474                                _entry.WriteDataDescriptor();
 672475                            }
 912476                        }
 2477                        else
 2478                            // write out data descriptor if we don't have seek capabilities
 02479                            _entry.WriteDataDescriptor();
 912480                    }
 912481                    _canWrite = false;
 912482                    _isDisposed = true;
 912483                }
 2484
 1822485                base.Dispose(disposing);
 1822486            }
 2487
 2488            public override async ValueTask DisposeAsync()
 912489            {
 912490                if (!_isDisposed)
 912491                {
 912492                    await _crcSizeStream.DisposeAsync().ConfigureAwait(false); // now we have size/crc info
 2493
 912494                    bool finishHeader = _everWritten;
 2495
 912496                    if (!_everWritten)
 02497                    {
 02498                        if (_encryptionStream is null)
 02499                        {
 2500                            // write local header, no data, so we use stored
 02501                            await _entry.WriteLocalFileHeaderAsync(isEmptyFile: true, forceWrite: true, preserveDataDesc
 02502                        }
 2503                        else
 02504                        {
 2505                            // No data was written through CheckSumAndSizeWriteStream, but an encrypted entry
 2506                            // still stores its encryption header (and, for AES, the authentication code), so
 2507                            // the local file header must describe an encrypted entry. Write it first, then
 2508                            // dispose the encryption stream (its lazy _baseStream was never created, so it
 2509                            // would otherwise be orphaned) to emit those bytes after the header.
 02510                            _usedZip64inLH = await _entry.WriteLocalFileHeaderAsync(isEmptyFile: false, forceWrite: true
 2511
 02512                            long startPosition = _entry._archive.ArchiveStream.Position;
 2513
 02514                            await _encryptionStream.DisposeAsync().ConfigureAwait(false);
 2515
 02516                            _entry._compressedSize = _entry._archive.ArchiveStream.Position - startPosition;
 02517                            finishHeader = true;
 02518                        }
 02519                    }
 2520
 912521                    if (finishHeader)
 912522                    {
 2523                        // go back and finish writing
 912524                        if (_entry._archive.ArchiveStream.CanSeek)
 912525                        {
 2526                            // finish writing local header if we have seek capabilities
 912527                            await _entry.WriteCrcAndSizesInLocalHeaderAsync(_usedZip64inLH, cancellationToken: default).
 2528
 2529                            // ZipCrypto entries retain DataDescriptor for check byte correctness;
 2530                            // write the trailing descriptor so the archive is consistent.
 912531                            if ((_entry._generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0)
 672532                            {
 672533                                await _entry.WriteDataDescriptorAsync(cancellationToken: default).ConfigureAwait(false);
 672534                            }
 912535                        }
 2536                        else
 2537                            // write out data descriptor if we don't have seek capabilities
 02538                            await _entry.WriteDataDescriptorAsync(cancellationToken: default).ConfigureAwait(false);
 912539                    }
 912540                    _canWrite = false;
 912541                    _isDisposed = true;
 912542                }
 2543
 912544                await base.DisposeAsync().ConfigureAwait(false);
 912545            }
 2546        }
 2547        [Flags]
 2548        internal enum BitFlagValues : ushort
 2549        {
 2550            IsEncrypted = 0x1,
 2551            DataDescriptor = 0x8,
 2552            StrongEncryption = 0x40,
 2553            UnicodeFileNameAndComment = 0x800
 2554        }
 2555
 2556        internal sealed class LocalHeaderOffsetComparer : Comparer<ZipArchiveEntry>
 2557        {
 02558            private static readonly LocalHeaderOffsetComparer s_instance = new LocalHeaderOffsetComparer();
 2559
 02560            public static LocalHeaderOffsetComparer Instance => s_instance;
 2561
 2562            // Newly added ZipArchiveEntry records should always go to the end of the file.
 2563            public override int Compare(ZipArchiveEntry? x, ZipArchiveEntry? y)
 02564            {
 02565                long xOffset = x != null && !x.OriginallyInArchive ? long.MaxValue : x?.OffsetOfLocalHeader ?? long.MinV
 02566                long yOffset = y != null && !y.OriginallyInArchive ? long.MaxValue : y?.OffsetOfLocalHeader ?? long.MinV
 2567
 02568                return xOffset.CompareTo(yOffset);
 02569            }
 2570        }
 2571    }
 2572}

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) =>
 36418            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()
GetZipCryptoTimeCheckByte(System.DateTimeOffset)
ThrowIfZipCryptoLastWriteTimeChangedWithoutReencrypting()
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)