< Summary

Line coverage
6%
Covered lines: 141
Uncovered lines: 1949
Coverable lines: 2090
Total lines: 3628
Line coverage: 6.7%
Branch coverage
5%
Covered branches: 47
Total branches: 825
Branch coverage: 5.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
File 1: OpenAsync(...)100%110%
File 1: OpenAsync(...)100%110%
File 1: OpenAsync(...)0%440%
File 1: OpenAsync(...)0%440%
File 1: OpenAsyncCore(...)0%24240%
File 1: GetOffsetOfCompressedDataAsync(...)0%440%
File 1: ReadEncryptionSaltIfNeededAsync(...)40%101020.83%
File 1: OpenInReadModeAsync(...)0%16160%
File 1: OpenInReadModeAsyncCore(System.Boolean,System.Nullable`1<System.IO.Compression.WinZipAesKeyMaterial>,System.Nullable`1<System.IO.Compression.ZipCryptoKeys>,System.Byte,System.Threading.CancellationToken)0%660%
File 1: OpenInUpdateModeAsync(...)0%10100%
File 1: OpenInUpdateModeForReadAsync(...)0%220%
File 1: GetUncompressedDataAsync(...)0%16160%
File 1: WriteAndFinishLocalEntryAsync(...)100%110%
File 1: WriteCentralDirectoryFileHeaderAsync(...)0%12120%
File 1: LoadLocalHeaderExtraFieldIfNeededAsync(...)0%220%
File 1: LoadCompressedBytesIfNeededAsync(...)0%660%
File 1: GetIsOpenableAsync(...)100%110%
File 1: ThrowIfNotOpenableAsync(...)0%220%
File 1: OpenInUpdateModeWithPasswordAsync(...)0%24240%
File 1: CastToStreamAsync(...)100%110%
File 1: DecryptAndStoreForUpdateWithZipCryptoAsync(...)100%110%
File 1: DecryptAndStoreForUpdateWithAesAsync(...)0%440%
File 1: StoreDecryptedDataForUpdateAsync(...)0%12120%
File 1: IsOpenableAsync(...)0%16160%
File 1: WriteLocalFileHeaderAsync(...)0%10100%
File 1: WriteLocalFileHeaderAndDataIfNeededAsync(...)0%78780%
File 1: WriteCrcAndSizesInLocalHeaderAsync(...)0%660%
File 1: WriteDataDescriptorAsync(...)100%110%
File 1: UnloadStreamsAsync()0%220%
File 1: CloseStreamsAsync()0%220%
File 2: .ctor(...)57.14%141483.6%
File 2: .ctor(...)0%220%
File 2: .ctor(...)0%880%
File 2: ToString()100%110%
File 2: Delete()0%660%
File 2: Open()100%110%
File 2: Open(...)0%440%
File 2: Open(...)100%110%
File 2: Open(...)0%440%
File 2: InferAccessFromMode()0%440%
File 2: ValidateAccessForMode(...)0%14140%
File 2: OpenCore(...)0%12120%
File 2: DecodeEntryString(...)66.66%66100%
File 2: .cctor()100%110%
File 2: GetOffsetOfCompressedData()0%440%
File 2: ReadEncryptionSaltIfNeeded()40%101020.83%
File 2: GetUncompressedData(...)0%880%
File 2: WriteAndFinishLocalEntry(...)100%110%
File 2: WriteCentralDirectoryFileHeaderInitialize(...)0%28280%
File 2: WriteCentralDirectoryFileHeaderPrepare(...)0%440%
File 2: WriteCentralDirectoryFileHeader(...)0%12120%
File 2: LoadLocalHeaderExtraFieldIfNeeded()0%220%
File 2: LoadCompressedBytesIfNeededInitialize(...)0%220%
File 2: LoadCompressedBytesIfNeeded()0%660%
File 2: ThrowIfNotOpenable(...)0%220%
File 2: DetectEntryNameVersion()100%22100%
File 2: GetDataCompressor(...)0%12120%
File 2: CalculateZipCryptoCheckByte()0%220%
File 2: GetAesKeySizeBits(...)0%440%
File 2: WrapWithDecryptionIfNeeded(...)0%10100%
File 2: GetDataDecompressor(...)0%660%
File 2: OpenInReadMode(...)0%220%
File 2: OpenInReadModeGetDataCompressor(...)0%220%
File 2: BuildDecompressionPipeline(...)0%660%
File 2: OpenInWriteMode()0%220%
File 2: OpenInWriteModeCore()0%18180%
File 2: OpenInUpdateMode(...)0%16160%
File 2: OpenInUpdateModeForRead()0%220%
File 2: MarkAsModified()0%220%
File 2: SetupEncryptionKeyMaterial(...)0%440%
File 2: PrepareEncryption(...)0%10100%
File 2: CreateAesExtraField()0%660%
File 2: IsOpenable(...)0%16160%
File 2: IsOpenableInitialVerifications(...)0%12120%
File 2: IsOpenableFinalVerifications(...)0%12120%
File 2: MapCompressionLevel(...)66.66%121292.85%
File 2: MapDeflateCompressionOption(...)0%990%
File 2: WriteLocalFileHeaderInitialize(...)0%44440%
File 2: WriteLocalFileHeaderPrepare(...)0%220%
File 2: WriteLocalFileHeader(...)0%10100%
File 2: WriteLocalFileHeaderAndDataIfNeeded(...)0%54540%
File 2: WriteCrcAndSizesInLocalHeader(...)0%660%
File 2: WriteCrcAndSizesInLocalHeaderInitialize(...)0%660%
File 2: WriteCrcAndSizesInLocalHeaderPrepareForZip64PretendStreaming(...)100%110%
File 2: WriteCrcAndSizesInLocalHeaderPrepareFor32bitValuesWriting(...)0%440%
File 2: WriteCrcAndSizesInLocalHeaderPrepareForWritingWhenZip64HeaderUsed(...)100%110%
File 2: WriteCrcAndSizesInLocalHeaderPrepareForWritingDataDescriptor(...)0%220%
File 2: WriteDataDescriptor()100%110%
File 2: PrepareToWriteDataDescriptor(...)0%440%
File 2: UnloadStreams()0%220%
File 2: CloseStreams()0%220%
File 2: VersionToExtractAtLeast(...)100%44100%
File 2: ThrowIfInvalidArchive()0%220%
File 2: GetFileName_Windows(...)100%22100%
File 2: GetFileName_Unix(...)100%22100%
File 2: .ctor(...)100%110%
File 2: ThrowIfDisposed()0%220%
File 2: Read(...)100%110%
File 2: ReadAsync(...)100%110%
File 2: ReadAsync(...)100%110%
File 2: Seek(...)100%110%
File 2: SetLength(...)100%110%
File 2: Write(...)0%440%
File 2: Write(...)0%440%
File 2: WriteByte(...)100%110%
File 2: WriteAsync(...)100%110%
File 2: WriteAsync(...)0%220%
File 2: Core(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)0%220%
File 2: Flush()100%110%
File 2: FlushAsync(...)100%110%
File 2: Dispose(...)0%14140%
File 2: DisposeAsync()0%12120%
File 2: .cctor()100%110%
File 2: Compare(...)0%12120%
File 3: ParseFileName(...)100%22100%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.Async.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers.Binary;
 5using System.Diagnostics;
 6using System.Threading;
 7using System.Threading.Tasks;
 8
 9namespace System.IO.Compression;
 10
 11// The disposable fields that this class owns get disposed when the ZipArchive it belongs to gets disposed
 12public partial class ZipArchiveEntry
 13{
 14    /// <summary>
 15    /// Asynchronously opens the entry. If the archive that the entry belongs to was opened in Read mode, the returned s
 16    /// </summary>
 17    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 18    /// <returns>A Stream that represents the contents of the entry.</returns>
 19    /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been deleted f
 20    /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be read. -
 21    /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exceptio
 22    public Task<Stream> OpenAsync(CancellationToken cancellationToken = default)
 023    {
 024        cancellationToken.ThrowIfCancellationRequested();
 025        ThrowIfInvalidArchive();
 026        return OpenAsyncCore(InferAccessFromMode(), default, cancellationToken);
 027    }
 28
 29    /// <summary>
 30    /// Asynchronously opens the entry with the specified access mode. This allows for more granular control over the re
 31    /// </summary>
 32    /// <param name="access">The file access mode for the returned stream.</param>
 33    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 34    /// <returns>A <see cref="Task{Stream}"/> that represents the asynchronous open operation.</returns>
 35    /// <remarks>
 36    /// <para>The allowed <paramref name="access"/> values depend on the <see cref="ZipArchiveMode"/>:</para>
 37    /// <list type="bullet">
 38    /// <item><description><see cref="ZipArchiveMode.Read"/>: Only <see cref="FileAccess.Read"/> is allowed.</descriptio
 39    /// <item><description><see cref="ZipArchiveMode.Create"/>: <see cref="FileAccess.Write"/> and <see cref="FileAccess
 40    /// <item><description><see cref="ZipArchiveMode.Update"/>: All values are allowed. <see cref="FileAccess.Read"/> pr
 41    /// </list>
 42    /// </remarks>
 43    /// <exception cref="ArgumentOutOfRangeException"><paramref name="access"/> is not a valid <see cref="FileAccess"/> 
 44    /// <exception cref="InvalidOperationException">The requested access is not compatible with the archive's open mode.
 45    /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been deleted f
 46    /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be read. -
 47    /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exceptio
 48    public Task<Stream> OpenAsync(FileAccess access, CancellationToken cancellationToken = default)
 049    {
 050        cancellationToken.ThrowIfCancellationRequested();
 051        ThrowIfInvalidArchive();
 052        ValidateAccessForMode(access);
 053        return OpenAsyncCore(access, default, cancellationToken);
 054    }
 55
 56    /// <summary>
 57    /// Asynchronously opens the entry with the specified access mode and password for decrypting encrypted entries.
 58    /// </summary>
 59    /// <param name="access">The file access mode for the returned stream.</param>
 60    /// <param name="password">The password used to decrypt the encrypted entry.</param>
 61    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 62    /// <returns>A <see cref="Task{Stream}"/> that represents the asynchronous open operation.</returns>
 63    /// <remarks>
 64    /// <para>The allowed <paramref name="access"/> values depend on the <see cref="ZipArchiveMode"/>:</para>
 65    /// <list type="bullet">
 66    /// <item><description><see cref="ZipArchiveMode.Read"/>: Only <see cref="FileAccess.Read"/> is allowed.</descriptio
 67    /// <item><description><see cref="ZipArchiveMode.Create"/>: <see cref="FileAccess.Write"/> and <see cref="FileAccess
 68    /// <item><description><see cref="ZipArchiveMode.Update"/>: All values are allowed for encrypted entries.</descripti
 69    /// </list>
 70    /// </remarks>
 71    /// <exception cref="ArgumentOutOfRangeException"><paramref name="access"/> is not a valid <see cref="FileAccess"/> 
 72    /// <exception cref="InvalidOperationException">The requested access is not compatible with the archive's open mode.
 73    /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been deleted f
 74    /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exceptio
 75    public Task<Stream> OpenAsync(FileAccess access, ReadOnlySpan<char> password, CancellationToken cancellationToken = 
 076    {
 077        cancellationToken.ThrowIfCancellationRequested();
 078        ThrowIfInvalidArchive();
 079        ValidateAccessForMode(access);
 80
 081        if (IsEncrypted && password.IsEmpty)
 082        {
 083            throw new ArgumentException(SR.PasswordRequired, nameof(password));
 84        }
 85
 086        return OpenAsyncCore(access, password, cancellationToken);
 087    }
 88
 89    /// <summary>
 90    /// Asynchronously opens the entry and uses the specified password to decrypt it if it is encrypted.
 91    /// If the archive that the entry belongs to was opened in Read mode, the returned stream will be readable, and it m
 92    /// </summary>
 93    /// <param name="password">The password used to decrypt the encrypted entry.</param>
 94    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 95    /// <returns>A task whose result is a stream that represents the contents of the entry.</returns>
 96    /// <remarks>
 97    /// <para>If the entry is not encrypted, <paramref name="password" /> is ignored.</para>
 98    /// </remarks>
 99    /// <exception cref="ArgumentException">The entry is encrypted and <paramref name="password" /> is empty.</exception
 100    /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been deleted f
 101    /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be read. -
 102    /// <exception cref="ObjectDisposedException">The <see cref="ZipArchive" /> that this entry belongs to has been disp
 103    public Task<Stream> OpenAsync(ReadOnlySpan<char> password, CancellationToken cancellationToken = default)
 0104    {
 0105        cancellationToken.ThrowIfCancellationRequested();
 0106        ThrowIfInvalidArchive();
 107
 0108        if (IsEncrypted && password.IsEmpty)
 0109        {
 0110            throw new ArgumentException(SR.PasswordRequired, nameof(password));
 111        }
 112
 0113        return OpenAsyncCore(InferAccessFromMode(), password, cancellationToken);
 0114    }
 115
 116    private Task<Stream> OpenAsyncCore(FileAccess access, ReadOnlySpan<char> password, CancellationToken cancellationTok
 0117    {
 0118        bool usePassword = IsEncrypted && !password.IsEmpty;
 119
 0120        switch (_archive.Mode)
 121        {
 122            case ZipArchiveMode.Read:
 0123                return OpenInReadModeAsync(checkOpenable: true, password, cancellationToken);
 124            case ZipArchiveMode.Create:
 0125                return Task.FromResult<Stream>(OpenInWriteMode());
 126            case ZipArchiveMode.Update:
 127            default:
 0128                Debug.Assert(_archive.Mode == ZipArchiveMode.Update);
 129                // Encrypted entries always require a password for re-encryption,
 130                // even when discarding existing content (write-only access).
 0131                if (IsEncrypted && password.IsEmpty && access != FileAccess.Read)
 0132                {
 0133                    throw new ArgumentException(SR.PasswordRequired, nameof(password));
 134                }
 0135                return access switch
 0136                {
 0137                    // Reads in Update mode must observe content written earlier in this session and
 0138                    // treat a newly created entry as empty. Only an unmodified entry that already
 0139                    // exists in the archive can be read directly without loading it into memory.
 0140                    FileAccess.Read => _storedUncompressedData is not null || !_originallyInArchive
 0141                        ? OpenInUpdateModeForReadAsync(cancellationToken)
 0142                        : OpenInReadModeAsync(checkOpenable: true, password, cancellationToken),
 0143                    FileAccess.Write => usePassword
 0144                        ? OpenInUpdateModeWithPasswordAsync(loadExistingContent: false, password, cancellationToken)
 0145                        : CastToStreamAsync(OpenInUpdateModeAsync(loadExistingContent: false, cancellationToken)),
 0146                    _ => usePassword
 0147                        ? OpenInUpdateModeWithPasswordAsync(loadExistingContent: true, password, cancellationToken)
 0148                        : CastToStreamAsync(OpenInUpdateModeAsync(loadExistingContent: true, cancellationToken)),
 0149                };
 150        }
 0151    }
 152
 153    internal async Task<long> GetOffsetOfCompressedDataAsync(CancellationToken cancellationToken)
 0154    {
 0155        cancellationToken.ThrowIfCancellationRequested();
 0156        if (_storedOffsetOfCompressedData == null)
 0157        {
 158            // Seek to local header
 0159            _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
 0163            if (!await ZipLocalFileHeader.TrySkipBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(fa
 0164            {
 0165                throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
 166            }
 167
 0168            _storedOffsetOfCompressedData = _archive.ArchiveStream.Position;
 0169        }
 0170        return _storedOffsetOfCompressedData.Value;
 0171    }
 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)
 523179    {
 523180        if (!IsAesEncrypted || !_originallyInArchive || OperatingSystem.IsBrowser() || OperatingSystem.IsWasi())
 523181        {
 523182            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.
 0189        if (_offsetOfLocalHeader > _archive.ArchiveStream.Length)
 0190        {
 0191            return;
 192        }
 193
 0194        long savedPosition = _archive.ArchiveStream.Position;
 195        try
 0196        {
 0197            long offset = await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false);
 0198            _archive.ArchiveStream.Seek(offset, SeekOrigin.Begin);
 199
 0200            int keySizeBits = GetAesKeySizeBits(Encryption);
 0201            int saltSize = WinZipAesStream.GetSaltSize(keySizeBits);
 0202            _aesSalt = new byte[saltSize];
 0203            await _archive.ArchiveStream.ReadExactlyAsync(_aesSalt, cancellationToken).ConfigureAwait(false);
 0204        }
 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
 0213        {
 0214            _archive.ArchiveStream.Seek(savedPosition, SeekOrigin.Begin);
 0215        }
 523216    }
 217
 218    private Task<Stream> OpenInReadModeAsync(bool checkOpenable, ReadOnlySpan<char> password, CancellationToken cancella
 0219    {
 0220        cancellationToken.ThrowIfCancellationRequested();
 221
 222        // Derive key material from the password span before entering the async core.
 0223        WinZipAesKeyMaterial? aesKeys = null;
 0224        ZipCryptoKeys? zipCryptoKeys = null;
 0225        byte zipCryptoCheckByte = 0;
 226
 0227        if (IsEncrypted)
 0228        {
 0229            if (Encryption == ZipEncryptionMethod.Unknown)
 0230            {
 0231                throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 232            }
 233
 0234            if (password.IsEmpty)
 0235            {
 0236                throw new InvalidDataException(SR.PasswordRequired);
 237            }
 238
 0239            if (IsAesEncrypted)
 0240            {
 0241                if (OperatingSystem.IsBrowser() || OperatingSystem.IsWasi())
 0242                {
 0243                    throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnPlatform);
 244                }
 245
 0246                if (_aesSalt is null)
 0247                {
 0248                    throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
 249                }
 250
 0251                int keySizeBits = GetAesKeySizeBits(Encryption);
 0252                aesKeys = WinZipAesStream.CreateKey(password, _aesSalt, keySizeBits);
 0253            }
 0254            else if (IsZipCryptoEncrypted)
 0255            {
 0256                zipCryptoCheckByte = CalculateZipCryptoCheckByte();
 0257                zipCryptoKeys = ZipCryptoStream.CreateKey(password);
 0258            }
 259            else
 0260            {
 0261                throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 262            }
 0263        }
 264
 0265        return OpenInReadModeAsyncCore(checkOpenable, aesKeys, zipCryptoKeys, zipCryptoCheckByte, cancellationToken);
 266
 267        async Task<Stream> OpenInReadModeAsyncCore(bool checkOpenable, WinZipAesKeyMaterial? aesKeys, ZipCryptoKeys? zip
 0268        {
 0269            if (checkOpenable)
 0270            {
 0271                await ThrowIfNotOpenableAsync(needToUncompress: true, needToLoadIntoMemory: false, cancellationToken).Co
 0272            }
 273
 0274            long offset = await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false);
 0275            Stream compressedStream = new SubReadStream(_archive.ArchiveStream, offset, _compressedSize);
 276
 277            Stream streamToDecompress;
 0278            if (aesKeys is not null)
 0279            {
 0280                streamToDecompress = await WinZipAesStream.CreateAsync(
 0281                    baseStream: compressedStream,
 0282                    keyMaterial: aesKeys.Value,
 0283                    totalStreamSize: _compressedSize,
 0284                    encrypting: false,
 0285                    cancellationToken: cancellationToken).ConfigureAwait(false);
 0286            }
 0287            else if (zipCryptoKeys is not null)
 0288            {
 0289                streamToDecompress = await ZipCryptoStream.CreateAsync(
 0290                    compressedStream, zipCryptoKeys.Value, zipCryptoCheckByte,
 0291                    encrypting: false, cancellationToken).ConfigureAwait(false);
 0292            }
 293            else
 0294            {
 0295                streamToDecompress = compressedStream;
 0296            }
 297
 0298            return BuildDecompressionPipeline(streamToDecompress);
 0299        }
 0300    }
 301
 302    private async Task<WrappedStream> OpenInUpdateModeAsync(bool loadExistingContent, CancellationToken cancellationToke
 0303    {
 0304        cancellationToken.ThrowIfCancellationRequested();
 0305        if (_currentlyOpenForWrite)
 0306        {
 0307            throw new IOException(SR.UpdateModeOneStream);
 308        }
 309
 0310        if (Encryption == ZipEncryptionMethod.Unknown)
 0311        {
 0312            throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 313        }
 314
 0315        if (loadExistingContent)
 0316        {
 0317            await ThrowIfNotOpenableAsync(needToUncompress: true, needToLoadIntoMemory: true, cancellationToken).Configu
 0318        }
 319
 0320        _currentlyOpenForWrite = true;
 321
 0322        if (loadExistingContent)
 0323        {
 0324            _storedUncompressedData = await GetUncompressedDataAsync(cancellationToken).ConfigureAwait(false);
 0325        }
 326        else
 0327        {
 0328            _storedUncompressedData?.Dispose();
 0329            _storedUncompressedData = new MemoryStream();
 0330            MarkAsModified();
 0331        }
 332
 0333        _storedUncompressedData.Seek(0, SeekOrigin.Begin);
 334
 0335        return new WrappedStream(_storedUncompressedData, this,
 0336            onClosed: thisRef => thisRef!._currentlyOpenForWrite = false,
 0337            notifyEntryOnWrite: true);
 0338    }
 339
 340    // Returns a read-only view over the entry's current uncompressed buffer in Update mode.
 341    // It reflects modifications made earlier in the current session (and is empty for a freshly created
 342    // entry), unlike OpenInReadModeAsync which reads the original bytes directly from the archive. The stream
 343    // shares the underlying buffer with the entry rather than copying it, so it is not isolated from later
 344    // in-place writes; callers are expected to finish reading before reopening the entry for writing.
 345    private async Task<Stream> OpenInUpdateModeForReadAsync(CancellationToken cancellationToken)
 0346    {
 0347        if (_currentlyOpenForWrite)
 0348        {
 0349            throw new IOException(SR.UpdateModeOneStream);
 350        }
 351
 0352        MemoryStream uncompressedData = await GetUncompressedDataAsync(cancellationToken).ConfigureAwait(false);
 0353        return new MemoryStream(uncompressedData.GetBuffer(), 0, (int)uncompressedData.Length, writable: false);
 0354    }
 355
 356    private async Task<MemoryStream> GetUncompressedDataAsync(CancellationToken cancellationToken)
 0357    {
 0358        cancellationToken.ThrowIfCancellationRequested();
 0359        if (_storedUncompressedData is null)
 0360        {
 361            // MemoryStream is backed by a single byte[] and cannot grow beyond Array.MaxLength.
 362            // Validate up front before attempting the (int) cast.
 0363            if ((ulong)_uncompressedSize > (ulong)Array.MaxLength)
 0364            {
 0365                _currentlyOpenForWrite = false;
 0366                throw new InvalidDataException(SR.EntryUncompressedSizeTooLargeForUpdateMode);
 367            }
 368
 369
 0370            _storedUncompressedData = new MemoryStream((int)_uncompressedSize);
 371
 0372            if (_originallyInArchive)
 0373            {
 0374                Stream decompressor = await OpenInReadModeAsync(checkOpenable: false, default, cancellationToken).Config
 375
 0376                await using (decompressor)
 0377                {
 378                    try
 0379                    {
 0380                        await decompressor.CopyToAsync(_storedUncompressedData, cancellationToken).ConfigureAwait(false)
 0381                    }
 0382                    catch (InvalidDataException)
 0383                    {
 0384                        await _storedUncompressedData.DisposeAsync().ConfigureAwait(false);
 0385                        _storedUncompressedData = null;
 0386                        _currentlyOpenForWrite = false;
 0387                        _everOpenedForWrite = false;
 0388                        _derivedZipCryptoKeyMaterial = null;
 0389                        _derivedAesKeyMaterial = null;
 0390                        throw;
 391                    }
 0392                }
 0393            }
 0394        }
 395
 0396        return _storedUncompressedData;
 0397    }
 398
 399    // does almost everything you need to do to forget about this entry
 400    // writes the local header/data, gets rid of all the data,
 401    // closes all of the streams except for the very outermost one that
 402    // the user holds on to and is responsible for closing
 403    //
 404    // after calling this, and only after calling this can we be guaranteed
 405    // that we are reading to write the central directory
 406    //
 407    // should only throw an exception in extremely exceptional cases because it is called from dispose
 408    internal async Task WriteAndFinishLocalEntryAsync(bool forceWrite, CancellationToken cancellationToken)
 0409    {
 0410        cancellationToken.ThrowIfCancellationRequested();
 0411        await CloseStreamsAsync().ConfigureAwait(false);
 0412        await WriteLocalFileHeaderAndDataIfNeededAsync(forceWrite, cancellationToken).ConfigureAwait(false);
 0413        await UnloadStreamsAsync().ConfigureAwait(false);
 0414    }
 415
 416    // should only throw an exception in extremely exceptional cases because it is called from dispose
 417    internal async Task WriteCentralDirectoryFileHeaderAsync(bool forceWrite, CancellationToken cancellationToken)
 0418    {
 0419        cancellationToken.ThrowIfCancellationRequested();
 420
 0421        if (WriteCentralDirectoryFileHeaderInitialize(forceWrite, out Zip64ExtraField? zip64ExtraField, out uint compres
 0422        {
 0423            byte[] cdStaticHeader = new byte[ZipCentralDirectoryFileHeader.BlockConstantSectionSize];
 0424            WriteCentralDirectoryFileHeaderPrepare(cdStaticHeader, compressedSizeTruncated, uncompressedSizeTruncated, e
 425
 0426            await _archive.ArchiveStream.WriteAsync(cdStaticHeader, cancellationToken).ConfigureAwait(false);
 0427            await _archive.ArchiveStream.WriteAsync(_storedEntryNameBytes, cancellationToken).ConfigureAwait(false);
 428
 429            // Write zip64ExtraField first if we decided we need it
 0430            if (zip64ExtraField != null)
 0431            {
 0432                await zip64ExtraField.WriteBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(false);
 0433            }
 434
 435            // Write WinZip AES extra field AFTER Zip64 (matching sync version order)
 436            // Must match the exact check used in the sync version WriteCentralDirectoryFileHeader
 0437            if (UseAesEncryption)
 0438            {
 0439                await CreateAesExtraField().WriteBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(fa
 440
 441                // write extra fields excluding existing AES extra field (and any malformed trailing data).
 0442                await ZipGenericExtraField.WriteAllBlocksExcludingTagAsync(_cdUnknownExtraFields, _cdTrailingExtraFieldD
 0443            }
 444            else
 0445            {
 446                // write extra fields (and any malformed trailing data).
 0447                await ZipGenericExtraField.WriteAllBlocksAsync(_cdUnknownExtraFields, _cdTrailingExtraFieldData ?? Array
 0448            }
 449
 0450            if (_fileComment.Length > 0)
 0451            {
 0452                await _archive.ArchiveStream.WriteAsync(_fileComment, cancellationToken).ConfigureAwait(false);
 0453            }
 0454        }
 0455    }
 456    internal async Task LoadLocalHeaderExtraFieldIfNeededAsync(CancellationToken cancellationToken)
 0457    {
 0458        cancellationToken.ThrowIfCancellationRequested();
 459        // we should have made this exact call in _archive.Init through ThrowIfOpenable
 0460        Debug.Assert(await GetIsOpenableAsync(false, true, cancellationToken).ConfigureAwait(false));
 461
 462        // load local header's extra fields. it will be null if we couldn't read for some reason
 0463        if (_originallyInArchive)
 0464        {
 0465            _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
 0466            (_lhUnknownExtraFields, _lhTrailingExtraFieldData) = await ZipLocalFileHeader.GetExtraFieldsAsync(_archive.A
 0467        }
 0468    }
 469
 470    internal async Task LoadCompressedBytesIfNeededAsync(CancellationToken cancellationToken)
 0471    {
 0472        cancellationToken.ThrowIfCancellationRequested();
 473        // we should have made this exact call in _archive.Init through ThrowIfOpenable
 0474        Debug.Assert(await GetIsOpenableAsync(false, true, cancellationToken).ConfigureAwait(false));
 475
 0476        if (!_everOpenedForWrite && _originallyInArchive)
 0477        {
 0478            _compressedBytes = LoadCompressedBytesIfNeededInitialize(out int maxSingleBufferSize);
 479
 0480            _archive.ArchiveStream.Seek(await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false), S
 481
 0482            for (int i = 0; i < _compressedBytes.Length - 1; i++)
 0483            {
 0484                await _archive.ArchiveStream.ReadAtLeastAsync(_compressedBytes[i], maxSingleBufferSize, throwOnEndOfStre
 0485            }
 0486            await _archive.ArchiveStream.ReadAtLeastAsync(_compressedBytes[_compressedBytes.Length - 1], (int)(_compress
 0487        }
 0488    }
 489
 490    private async Task<bool> GetIsOpenableAsync(bool needToUncompress, bool needToLoadIntoMemory, CancellationToken canc
 0491    {
 0492        cancellationToken.ThrowIfCancellationRequested();
 0493        (bool result, _) = await IsOpenableAsync(needToUncompress, needToLoadIntoMemory, cancellationToken).ConfigureAwa
 0494        return result;
 0495    }
 496
 497    internal async Task ThrowIfNotOpenableAsync(bool needToUncompress, bool needToLoadIntoMemory, CancellationToken canc
 0498    {
 0499        cancellationToken.ThrowIfCancellationRequested();
 0500        (bool openable, string? message) = await IsOpenableAsync(needToUncompress, needToLoadIntoMemory, cancellationTok
 0501        if (!openable)
 0502        {
 0503            throw new InvalidDataException(message);
 504        }
 0505    }
 506
 507    /// <summary>
 508    /// Accepts a <see cref="ReadOnlySpan{T}"/> password, derives decryption and re-encryption keys (CPU-only),
 509    /// and delegates all I/O to fully async helper methods.
 510    /// </summary>
 511    private Task<Stream> OpenInUpdateModeWithPasswordAsync(bool loadExistingContent, ReadOnlySpan<char> password, Cancel
 0512    {
 0513        if (_currentlyOpenForWrite)
 0514        {
 0515            throw new IOException(SR.UpdateModeOneStream);
 516        }
 517
 0518        if (Encryption == ZipEncryptionMethod.Unknown)
 0519        {
 0520            throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 521        }
 522
 523        // Encrypted entries always require a password for re-encryption,
 524        // even when discarding existing content (write-only access).
 0525        if (IsEncrypted && password.IsEmpty)
 0526        {
 0527            throw new ArgumentException(SR.PasswordRequired, nameof(password));
 528        }
 529
 530        // Derive re-encryption key material while the password span is still valid.
 0531        if (IsEncrypted)
 0532        {
 0533            SetupEncryptionKeyMaterial(password);
 0534        }
 535
 0536        if (loadExistingContent && IsEncrypted)
 0537        {
 0538            if (IsAesEncrypted)
 0539            {
 0540                if (OperatingSystem.IsBrowser() || 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 (IsZipCryptoEncrypted)
 0556            {
 0557                byte checkByte = CalculateZipCryptoCheckByte();
 0558                ZipCryptoKeys keys = ZipCryptoStream.CreateKey(password);
 0559                return DecryptAndStoreForUpdateWithZipCryptoAsync(keys, checkByte, cancellationToken);
 560            }
 561
 0562            throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 563        }
 564
 0565        return CastToStreamAsync(OpenInUpdateModeAsync(loadExistingContent, cancellationToken));
 0566    }
 567
 0568    private static async Task<Stream> CastToStreamAsync(Task<WrappedStream> task) => await task.ConfigureAwait(false);
 569
 570    private async Task<Stream> DecryptAndStoreForUpdateWithZipCryptoAsync(ZipCryptoKeys keys, byte checkByte, Cancellati
 0571    {
 0572        await ThrowIfNotOpenableAsync(needToUncompress: true, needToLoadIntoMemory: true, cancellationToken).ConfigureAw
 573
 0574        long offset = await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false);
 0575        Stream compressedStream = new SubReadStream(_archive.ArchiveStream, offset, _compressedSize);
 576
 0577        Stream decrypted = await ZipCryptoStream.CreateAsync(compressedStream, keys, checkByte, encrypting: false, cance
 578
 0579        return await StoreDecryptedDataForUpdateAsync(decrypted, cancellationToken).ConfigureAwait(false);
 0580    }
 581
 582    private async Task<Stream> DecryptAndStoreForUpdateWithAesAsync(WinZipAesKeyMaterial aesKeys, CancellationToken canc
 0583    {
 0584        if (OperatingSystem.IsBrowser() || 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
 0646    {
 0647        cancellationToken.ThrowIfCancellationRequested();
 0648        string? message = null;
 649
 0650        if (!_originallyInArchive)
 0651        {
 0652            return (true, message);
 653        }
 654
 0655        if (!IsOpenableInitialVerifications(needToUncompress, out message))
 0656        {
 0657            return (false, message);
 658        }
 659
 0660        if (!IsEncrypted && !await ZipLocalFileHeader.TrySkipBlockAsync(_archive.ArchiveStream, cancellationToken).Confi
 0661        {
 0662            message = SR.LocalFileHeaderCorrupt;
 0663            return (false, message);
 664        }
 0665        else if (IsEncrypted && (ushort)_headerCompressionMethod == WinZipAesMethod)
 0666        {
 0667            _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
 668            // AES case - skip the local file header and validate it exists.
 669            // The AES metadata (encryption strength, actual compression method) was already
 670            // parsed from the central directory in the constructor.
 0671            if (!await ZipLocalFileHeader.TrySkipBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(fa
 0672            {
 0673                message = SR.LocalFileHeaderCorrupt;
 0674                return (false, message);
 675            }
 0676        }
 677
 678        // when this property gets called, some duplicated work
 0679        long offsetOfCompressedData = await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false);
 0680        if (!IsOpenableFinalVerifications(needToLoadIntoMemory, offsetOfCompressedData, out message))
 0681        {
 0682            return (false, message);
 683        }
 684
 0685        return (true, message);
 0686    }
 687
 688    // return value is true if we allocated an extra field for 64 bit headers, un/compressed size
 689    private async Task<bool> WriteLocalFileHeaderAsync(bool isEmptyFile, bool forceWrite, bool preserveDataDescriptor, C
 0690    {
 0691        cancellationToken.ThrowIfCancellationRequested();
 692
 0693        if (WriteLocalFileHeaderInitialize(isEmptyFile, forceWrite, preserveDataDescriptor, out Zip64ExtraField? zip64Ex
 0694        {
 0695            byte[] lfStaticHeader = new byte[ZipLocalFileHeader.SizeOfLocalHeader];
 0696            WriteLocalFileHeaderPrepare(lfStaticHeader, crc32ToWrite, compressedSizeTruncated, uncompressedSizeTruncated
 697
 698            // write header
 0699            await _archive.ArchiveStream.WriteAsync(lfStaticHeader, cancellationToken).ConfigureAwait(false);
 0700            await _archive.ArchiveStream.WriteAsync(_storedEntryNameBytes, cancellationToken).ConfigureAwait(false);
 701
 702            // Only when handling zip64
 0703            if (zip64ExtraField != null)
 0704            {
 0705                await zip64ExtraField.WriteBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(false);
 0706            }
 707
 708            // Write WinZip AES extra field if using AES encryption
 709            // Must match the exact check used in the sync version WriteLocalFileHeader
 0710            if (UseAesEncryption)
 0711            {
 0712                await CreateAesExtraField().WriteBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(fa
 713
 714                // Write other extra fields, excluding any existing AES extra field to avoid duplication
 0715                await ZipGenericExtraField.WriteAllBlocksExcludingTagAsync(_lhUnknownExtraFields, _lhTrailingExtraFieldD
 0716            }
 717            else
 0718            {
 0719                await ZipGenericExtraField.WriteAllBlocksAsync(_lhUnknownExtraFields, _lhTrailingExtraFieldData ?? Array
 0720            }
 0721        }
 722
 0723        return zip64ExtraField != null;
 0724    }
 725
 726    private async Task WriteLocalFileHeaderAndDataIfNeededAsync(bool forceWrite, CancellationToken cancellationToken)
 0727    {
 0728        cancellationToken.ThrowIfCancellationRequested();
 729
 730        // Check if the entry's stored data was actually modified (StoredData flag is set).
 731        // If _storedUncompressedData is loaded but StoredData is not set, it means the entry
 732        // was opened for update but no writes occurred - we should use the original compressed bytes.
 0733        bool storedDataModified = (Changes & ZipArchive.ChangeState.StoredData) != 0;
 734
 735        // If _storedUncompressedData is loaded but not modified, clear it so we use _compressedBytes
 0736        if (_storedUncompressedData != null && !storedDataModified)
 0737        {
 0738            await _storedUncompressedData.DisposeAsync().ConfigureAwait(false);
 0739            _storedUncompressedData = null;
 0740        }
 741
 742        // _storedUncompressedData gets frozen here, and is what gets written to the file
 0743        if (_storedUncompressedData != null || _compressedBytes != null)
 0744        {
 0745            if (_storedUncompressedData != null)
 0746            {
 0747                _uncompressedSize = _storedUncompressedData.Length;
 748
 749                // Check if we need to re-encrypt with ZipCrypto (only if we have cached key material)
 0750                if (Encryption == ZipEncryptionMethod.ZipCrypto && _derivedZipCryptoKeyMaterial != null)
 0751                {
 752                    // Write local file header first (with encryption flag set)
 753                    // Pass isEmptyFile: false because even empty encrypted files have the 12-byte header
 0754                    await WriteLocalFileHeaderAsync(isEmptyFile: false, forceWrite: true, preserveDataDescriptor: false,
 755
 756                    // Record position before encryption data
 0757                    long startPosition = _archive.ArchiveStream.Position;
 758
 0759                    ushort verifierLow2Bytes = (ushort)ZipHelper.DateTimeToDosTime(_lastModified.DateTime);
 760
 0761                    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 (UseAesEncryption && _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
 0954        {
 0955            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            }
 0971        }
 0972    }
 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)
 0978    {
 0979        cancellationToken.ThrowIfCancellationRequested();
 980        // Buffer has been sized to the largest data payload required: the 64-bit data descriptor.
 0981        byte[] writeBuffer = new byte[Zip64DataDescriptorCrcAndSizesBufferLength];
 982
 0983        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
 0989        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
 0997        WriteCrcAndSizesInLocalHeaderPrepareFor32bitValuesWriting(pretendStreaming, writeBuffer, compressedSizeTruncated
 0998        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
 01006        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
 01013        _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
 01018        if (pretendStreaming)
 01019        {
 01020            WriteCrcAndSizesInLocalHeaderPrepareForWritingDataDescriptor(writeBuffer);
 01021            await _archive.ArchiveStream.WriteAsync(writeBuffer.AsMemory(0, Zip64DataDescriptorCrcAndSizesBufferLength),
 01022        }
 01023    }
 1024
 1025    private ValueTask WriteDataDescriptorAsync(CancellationToken cancellationToken)
 01026    {
 01027        cancellationToken.ThrowIfCancellationRequested();
 01028        byte[] dataDescriptor = new byte[MaxSizeOfDataDescriptor];
 01029        int bytesToWrite = PrepareToWriteDataDescriptor(dataDescriptor);
 01030        return _archive.ArchiveStream.WriteAsync(dataDescriptor.AsMemory(0, bytesToWrite), cancellationToken);
 01031    }
 1032
 1033    internal async Task UnloadStreamsAsync()
 01034    {
 01035        if (_storedUncompressedData != null)
 01036        {
 01037            await _storedUncompressedData.DisposeAsync().ConfigureAwait(false);
 01038        }
 01039        _compressedBytes = null;
 01040        _outstandingWriteStream = null;
 01041    }
 1042
 1043    private async Task CloseStreamsAsync()
 01044    {
 1045        // if the user left the stream open, close the underlying stream for them
 01046        if (_outstandingWriteStream != null)
 01047        {
 01048            await _outstandingWriteStream.DisposeAsync().ConfigureAwait(false);
 01049        }
 01050    }
 1051}
 1052

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers.Binary;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.Diagnostics.CodeAnalysis;
 8using System.Text;
 9using System.Threading;
 10using System.Threading.Tasks;
 11
 12using static System.IO.Compression.ZipArchiveEntryConstants;
 13
 14namespace System.IO.Compression
 15{
 16    // The disposable fields that this class owns get disposed when the ZipArchive it belongs to gets disposed
 17    public partial class ZipArchiveEntry
 18    {
 19        private ZipArchive _archive;
 20        private readonly bool _originallyInArchive;
 21        private readonly uint _diskNumberStart;
 22        private readonly ZipVersionMadeByPlatform _versionMadeByPlatform;
 23        private ZipVersionNeededValues _versionMadeBySpecification;
 24        private ZipVersionNeededValues _versionToExtract;
 25        private BitFlagValues _generalPurposeBitFlag;
 26        private ZipCompressionMethod _storedCompressionMethod;
 27        private DateTimeOffset _lastModified;
 28        private long _compressedSize;
 29        private long _uncompressedSize;
 30        private long _offsetOfLocalHeader;
 31        private long? _storedOffsetOfCompressedData;
 32        private long _endOfLocalEntryData;
 33        private uint _crc32;
 34        // An array of buffers, each a maximum of MaxSingleBufferSize in size
 35        private byte[][]? _compressedBytes;
 36        private MemoryStream? _storedUncompressedData;
 37        private bool _currentlyOpenForWrite;
 38        private bool _everOpenedForWrite;
 39        private Stream? _outstandingWriteStream;
 40        private uint _externalFileAttr;
 41        private string _storedEntryName;
 42        private byte[] _storedEntryNameBytes;
 43        // only apply to update mode
 44        private List<ZipGenericExtraField>? _cdUnknownExtraFields;
 45        private byte[]? _cdTrailingExtraFieldData;
 46        private List<ZipGenericExtraField>? _lhUnknownExtraFields;
 47        private byte[]? _lhTrailingExtraFieldData;
 48        private byte[] _fileComment;
 49        private ZipEncryptionMethod _encryptionMethod;
 50        private readonly CompressionLevel _compressionLevel;
 51        private ZipCompressionMethod _headerCompressionMethod;
 52        private ushort? _aeVersion;
 53        // Cached derived key material for encrypted entries to allow updating in place.
 54        // Only one of these is set at a time, depending on the encryption method.
 55        private ZipCryptoKeys? _derivedZipCryptoKeyMaterial;
 56        private WinZipAesKeyMaterial? _derivedAesKeyMaterial;
 57        // Pre-read AES salt from the local file data area during central directory parsing.
 58        // This allows async open methods to derive keys without synchronous I/O.
 59        private byte[]? _aesSalt;
 60        internal const ushort WinZipAesMethod = 99;
 61        // Initializes a ZipArchiveEntry instance for an existing archive entry.
 195462        internal ZipArchiveEntry(ZipArchive archive, ZipCentralDirectoryFileHeader cd)
 195463        {
 195464            _archive = archive;
 65
 195466            _originallyInArchive = true;
 67            // It's possible for the CompressionMethod setter and DetectEntryNameVersion to update this, even without an
 68            // changes. This can occur if a ZipArchive instance runs in Update mode and opens a stream with invalid data
 69            // a situation, both the local file header and the central directory header will be rewritten (to prevent th
 70            // from falling out of sync when the central directory header is rewritten.)
 195471            Changes = ZipArchive.ChangeState.Unchanged;
 72
 195473            _diskNumberStart = cd.DiskNumberStart;
 195474            _versionMadeByPlatform = (ZipVersionMadeByPlatform)cd.VersionMadeByCompatibility;
 195475            _versionMadeBySpecification = (ZipVersionNeededValues)cd.VersionMadeBySpecification;
 195476            _versionToExtract = (ZipVersionNeededValues)cd.VersionNeededToExtract;
 195477            _generalPurposeBitFlag = (BitFlagValues)cd.GeneralPurposeBitFlag;
 78            // Initialize _headerCompressionMethod from the central directory.
 79            // For AES entries, this will be 99 (WinZip AES wrapper indicator) and never changes.
 195480            _headerCompressionMethod = (ZipCompressionMethod)cd.CompressionMethod;
 81            // For AES-encrypted entries, the real compression method is stored in the AES extra field (0x9901)
 82            // Parse it now so that people can see the actual value before opening the entry.
 195483            if (IsAesEncrypted)
 4484            {
 4485                if (cd.AesExtraField is not WinZipAesExtraField aesField)
 4486                {
 4487                    throw new InvalidDataException(SR.InvalidAesExtraField);
 88                }
 89
 90                // Set the real compression method from the AES extra field
 091                CompressionMethod = (ZipCompressionMethod)aesField.CompressionMethod;
 92
 93                // Also parse remaining needed metadata now
 094                _aeVersion = aesField.VendorVersion;
 095                Encryption = aesField.AesStrength switch
 096                {
 097                    1 => ZipEncryptionMethod.Aes128,
 098                    2 => ZipEncryptionMethod.Aes192,
 099                    3 => ZipEncryptionMethod.Aes256,
 0100                    _ => throw new InvalidDataException(SR.InvalidAesStrength)
 0101                };
 0102            }
 1910103            else if (IsEncrypted)
 1114104            {
 1114105                if ((_generalPurposeBitFlag & BitFlagValues.StrongEncryption) != 0)
 410106                {
 410107                    Encryption = ZipEncryptionMethod.Unknown;
 410108                }
 109                else
 704110                {
 111                    // Encrypted entries without the AES method indicator use ZipCrypto.
 704112                    Encryption = ZipEncryptionMethod.ZipCrypto;
 704113                }
 1114114                CompressionMethod = (ZipCompressionMethod)cd.CompressionMethod;
 1114115            }
 116            else
 796117            {
 118                // Non-AES entry: compression method from CD is the real method
 796119                CompressionMethod = (ZipCompressionMethod)cd.CompressionMethod;
 796120            }
 121
 1910122            _lastModified = new DateTimeOffset(ZipHelper.DosTimeToDateTime(cd.LastModified));
 1910123            _compressedSize = cd.CompressedSize;
 1910124            _uncompressedSize = cd.UncompressedSize;
 1910125            _externalFileAttr = cd.ExternalFileAttributes;
 1910126            _offsetOfLocalHeader = cd.RelativeOffsetOfLocalHeader;
 127            // we don't know this yet: should be _offsetOfLocalHeader + 30 + _storedEntryNameBytes.Length + extrafieldle
 128            // but entryname/extra length could be different in LH
 1910129            _storedOffsetOfCompressedData = null;
 1910130            _crc32 = cd.Crc32;
 131
 1910132            _compressedBytes = null;
 1910133            _storedUncompressedData = null;
 1910134            _currentlyOpenForWrite = false;
 1910135            _everOpenedForWrite = false;
 1910136            _outstandingWriteStream = null;
 137
 1910138            _storedEntryNameBytes = cd.Filename;
 1910139            _storedEntryName = DecodeEntryString(_storedEntryNameBytes);
 1910140            DetectEntryNameVersion();
 141
 1910142            _lhUnknownExtraFields = null;
 143            // the cd should have this as null if we aren't in Update mode
 1910144            _cdUnknownExtraFields = cd.ExtraFields;
 1910145            _cdTrailingExtraFieldData = cd.TrailingExtraFieldData;
 146
 1910147            _fileComment = cd.FileComment;
 148
 1910149            _compressionLevel = MapCompressionLevel(_generalPurposeBitFlag, CompressionMethod);
 1910150        }
 151        // Initializes a ZipArchiveEntry instance for a new archive entry with a specified compression level.
 152        internal ZipArchiveEntry(ZipArchive archive, string entryName, CompressionLevel compressionLevel)
 0153            : this(archive, entryName)
 0154        {
 0155            _compressionLevel = compressionLevel;
 0156            if (_compressionLevel == CompressionLevel.NoCompression)
 0157            {
 0158                CompressionMethod = ZipCompressionMethod.Stored;
 0159            }
 0160            _generalPurposeBitFlag = MapDeflateCompressionOption(_generalPurposeBitFlag, _compressionLevel, CompressionM
 0161        }
 162
 163        // Initializes a ZipArchiveEntry instance for a new archive entry.
 0164        internal ZipArchiveEntry(ZipArchive archive, string entryName)
 0165        {
 0166            _archive = archive;
 167
 0168            _originallyInArchive = false;
 169
 0170            _diskNumberStart = 0;
 0171            _versionMadeByPlatform = CurrentZipPlatform;
 0172            _versionMadeBySpecification = ZipVersionNeededValues.Default;
 0173            _versionToExtract = ZipVersionNeededValues.Default; // this must happen before following two assignment
 0174            _compressionLevel = CompressionLevel.Optimal;
 0175            CompressionMethod = ZipCompressionMethod.Deflate;
 0176            _generalPurposeBitFlag = MapDeflateCompressionOption(0, _compressionLevel, CompressionMethod);
 0177            _lastModified = DateTimeOffset.Now;
 178
 0179            _compressedSize = 0; // we don't know these yet
 0180            _uncompressedSize = 0;
 0181            _externalFileAttr = entryName.EndsWith(Path.DirectorySeparatorChar) || entryName.EndsWith(Path.AltDirectoryS
 0182                                        ? DefaultDirectoryExternalAttributes
 0183                                        : DefaultFileExternalAttributes;
 184
 0185            _offsetOfLocalHeader = 0;
 0186            _storedOffsetOfCompressedData = null;
 0187            _crc32 = 0;
 188
 0189            _compressedBytes = null;
 0190            _storedUncompressedData = null;
 0191            _currentlyOpenForWrite = false;
 0192            _everOpenedForWrite = false;
 0193            _outstandingWriteStream = null;
 194
 0195            FullName = entryName;
 196
 0197            _cdUnknownExtraFields = null;
 0198            _lhUnknownExtraFields = null;
 199
 0200            _fileComment = Array.Empty<byte>();
 201
 0202            if (_storedEntryNameBytes.Length > ushort.MaxValue)
 0203            {
 0204                throw new ArgumentException(SR.EntryNamesTooLong);
 205            }
 206
 207            // grab the stream if we're in create mode
 0208            if (_archive.Mode == ZipArchiveMode.Create)
 0209            {
 0210                _archive.AcquireArchiveStream(this);
 0211            }
 212
 0213            Changes = ZipArchive.ChangeState.Unchanged;
 0214        }
 215
 216        /// <summary>
 217        /// The ZipArchive that this entry belongs to. If this entry has been deleted, this will return null.
 218        /// </summary>
 0219        public ZipArchive Archive => _archive;
 220
 221        [CLSCompliant(false)]
 602222        public uint Crc32 => _crc32;
 223
 224        /// <summary>
 225        /// Gets a value that indicates whether the entry is encrypted.
 226        /// </summary>
 6746227        public bool IsEncrypted => (_generalPurposeBitFlag & BitFlagValues.IsEncrypted) != 0;
 228
 229        /// <summary>
 230        /// Gets the encryption method used to encrypt the entry.
 231        /// </summary>
 232        /// <value>
 233        /// <see cref="ZipEncryptionMethod.None"/> if the entry is not encrypted;
 234        /// <see cref="ZipEncryptionMethod.Unknown"/> if the entry uses an unsupported encryption method;
 235        /// otherwise, the specific encryption method (e.g., <see cref="ZipEncryptionMethod.ZipCrypto"/>,
 236        /// <see cref="ZipEncryptionMethod.Aes128"/>, <see cref="ZipEncryptionMethod.Aes192"/>,
 237        /// or <see cref="ZipEncryptionMethod.Aes256"/>).
 238        /// </value>
 0239        public ZipEncryptionMethod EncryptionMethod => _encryptionMethod;
 240
 241        /// <summary>
 242        /// Gets the compression method used to compress the entry.
 243        /// </summary>
 244        public ZipCompressionMethod CompressionMethod
 245        {
 1910246            get => _storedCompressionMethod;
 247            private set
 1910248            {
 1910249                if (value == ZipCompressionMethod.Deflate)
 22250                {
 22251                    VersionToExtractAtLeast(ZipVersionNeededValues.Deflate);
 22252                }
 1888253                else if (value == ZipCompressionMethod.Deflate64)
 40254                {
 40255                    VersionToExtractAtLeast(ZipVersionNeededValues.Deflate64);
 40256                }
 1910257                _storedCompressionMethod = value;
 1910258            }
 259        }
 260
 261        /// <summary>
 262        /// The compressed size of the entry. If the archive that the entry belongs to is in Create mode, attempts to ge
 263        /// </summary>
 264        /// <exception cref="InvalidOperationException">This property is not available because the entry has been writte
 265        public long CompressedLength
 266        {
 267            get
 0268            {
 0269                if (_everOpenedForWrite)
 0270                {
 0271                    throw new InvalidOperationException(SR.LengthAfterWrite);
 272                }
 0273                return _compressedSize;
 0274            }
 275        }
 276
 277        public int ExternalAttributes
 278        {
 279            get
 0280            {
 0281                return (int)_externalFileAttr;
 0282            }
 283            set
 0284            {
 0285                ThrowIfInvalidArchive();
 0286                _externalFileAttr = (uint)value;
 0287                Changes |= ZipArchive.ChangeState.FixedLengthMetadata;
 0288            }
 289        }
 290
 291        /// <summary>
 292        /// Gets or sets the optional entry comment.
 293        /// </summary>
 294        /// <remarks>
 295        ///The comment encoding is determined by the <c>entryNameEncoding</c> parameter of the <see cref="ZipArchive(Str
 296        /// If the comment byte length is larger than <see cref="ushort.MaxValue"/>, it will be truncated when disposing
 297        /// </remarks>
 298        [AllowNull]
 299        public string Comment
 300        {
 602301            get => DecodeEntryString(_fileComment);
 302            set
 0303            {
 0304                _fileComment = ZipHelper.GetEncodedTruncatedBytesFromString(value, _archive.EntryNameAndCommentEncoding,
 305
 0306                if (isUTF8)
 0307                {
 0308                    _generalPurposeBitFlag |= BitFlagValues.UnicodeFileNameAndComment;
 0309                }
 0310                Changes |= ZipArchive.ChangeState.DynamicLengthMetadata;
 0311            }
 312        }
 313
 314        /// <summary>
 315        /// The relative path of the entry as stored in the Zip archive. Note that Zip archives allow any string to be t
 316        /// </summary>
 317        public string FullName
 318        {
 319            get
 2512320            {
 2512321                return _storedEntryName;
 2512322            }
 323
 324            [MemberNotNull(nameof(_storedEntryNameBytes))]
 325            [MemberNotNull(nameof(_storedEntryName))]
 326            private set
 0327            {
 0328                ArgumentNullException.ThrowIfNull(value, nameof(FullName));
 329
 0330                _storedEntryNameBytes = ZipHelper.GetEncodedTruncatedBytesFromString(
 0331                    value, _archive.EntryNameAndCommentEncoding, 0 /* No truncation */, out bool isUTF8);
 332
 0333                _storedEntryName = value;
 334
 0335                if (isUTF8)
 0336                {
 0337                    _generalPurposeBitFlag |= BitFlagValues.UnicodeFileNameAndComment;
 0338                }
 339                else
 0340                {
 0341                    _generalPurposeBitFlag &= ~BitFlagValues.UnicodeFileNameAndComment;
 0342                }
 343
 0344                DetectEntryNameVersion();
 0345            }
 346        }
 347
 348        /// <summary>
 349        /// Returns the relative path of the entry in the Zip archive, equivalent to <see cref="FullName"/>.
 350        /// </summary>
 0351        public override string ToString() => FullName;
 352
 353        /// <summary>
 354        /// The last write time of the entry as stored in the Zip archive. When setting this property, the DateTime will
 355        /// Zip timestamp format, which supports a resolution of two seconds. If the data in the last write time field i
 356        /// an indicator value of 1980 January 1 at midnight will be returned.
 357        /// </summary>
 358        /// <exception cref="NotSupportedException">An attempt to set this property was made, but the ZipArchive that th
 359        /// opened in read-only mode.</exception>
 360        /// <exception cref="ArgumentOutOfRangeException">An attempt was made to set this property to a value that canno
 361        /// Zip timestamp format. The earliest date/time that can be represented is 1980 January 1 0:00:00 (midnight), a
 362        /// that can be represented is 2107 December 31 23:59:58 (one second before midnight).</exception>
 363        public DateTimeOffset LastWriteTime
 364        {
 365            get
 602366            {
 602367                return _lastModified;
 602368            }
 369            set
 0370            {
 0371                ThrowIfInvalidArchive();
 0372                if (_archive.Mode == ZipArchiveMode.Read)
 0373                {
 0374                    throw new NotSupportedException(SR.ReadOnlyArchive);
 375                }
 0376                if (_archive.Mode == ZipArchiveMode.Create && _everOpenedForWrite)
 0377                {
 0378                    throw new IOException(SR.FrozenAfterWrite);
 379                }
 0380                if (value.DateTime.Year < ZipHelper.ValidZipDate_YearMin || value.DateTime.Year > ZipHelper.ValidZipDate
 0381                {
 0382                    throw new ArgumentOutOfRangeException(nameof(value), SR.DateTimeOutOfRange);
 383                }
 384
 0385                _lastModified = value;
 0386                Changes |= ZipArchive.ChangeState.FixedLengthMetadata;
 0387            }
 388        }
 389
 390        /// <summary>
 391        /// The uncompressed size of the entry. This property is not valid in Create mode, and it is only valid in Updat
 392        /// </summary>
 393        /// <exception cref="InvalidOperationException">This property is not available because the entry has been writte
 394        public long Length
 395        {
 396            get
 602397            {
 602398                if (_everOpenedForWrite)
 0399                {
 0400                    throw new InvalidOperationException(SR.LengthAfterWrite);
 401                }
 602402                return _uncompressedSize;
 602403            }
 404        }
 405
 406        /// <summary>
 407        /// The filename of the entry. This is equivalent to the substring of Fullname that follows the final directory 
 408        /// </summary>
 0409        public string Name => ParseFileName(FullName, _versionMadeByPlatform);
 410
 411        /// <summary>
 412        /// Gets the "version made by" field of the entry as stored in the archive's central directory record.
 413        /// </summary>
 414        /// <remarks>
 415        /// As defined by the ZIP file format specification, the low byte contains the version of the specification used
 416        /// </remarks>
 417        [CLSCompliant(false)]
 0418        public ushort VersionMadeBy => (ushort)(((byte)_versionMadeByPlatform << 8) | (byte)_versionMadeBySpecification)
 419
 2526420        internal ZipArchive.ChangeState Changes { get; private set; }
 421
 0422        internal bool OriginallyInArchive => _originallyInArchive;
 423
 0424        internal long OffsetOfLocalHeader => _offsetOfLocalHeader;
 425
 426        /// <summary>
 427        /// Deletes the entry from the archive.
 428        /// </summary>
 429        /// <exception cref="IOException">The entry is already open for reading or writing.</exception>
 430        /// <exception cref="NotSupportedException">The ZipArchive that this entry belongs to was opened in a mode other
 431        /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exce
 432        public void Delete()
 0433        {
 0434            if (_archive == null)
 0435            {
 0436                return;
 437            }
 438
 0439            if (_currentlyOpenForWrite)
 0440            {
 0441                throw new IOException(SR.DeleteOpenEntry);
 442            }
 443
 0444            if (_archive.Mode != ZipArchiveMode.Update)
 0445            {
 0446                throw new NotSupportedException(SR.DeleteOnlyInUpdate);
 447            }
 448
 0449            _archive.ThrowIfDisposed();
 450
 0451            _archive.RemoveEntry(this);
 0452            _archive = null!;
 0453            UnloadStreams();
 0454        }
 455
 456        /// <summary>
 457        /// Opens the entry. If the archive that the entry belongs to was opened in Read mode, the returned stream will 
 458        /// </summary>
 459        /// <returns>A Stream that represents the contents of the entry.</returns>
 460        /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been delet
 461        /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be rea
 462        /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exce
 463        public Stream Open()
 0464        {
 0465            ThrowIfInvalidArchive();
 0466            return OpenCore(InferAccessFromMode());
 0467        }
 468
 469
 470        /// <summary>
 471        /// Opens the entry for reading or updating with the specified password.
 472        /// If the entry is not encrypted, the password is ignored and the entry is opened normally.
 473        /// </summary>
 474        /// <returns>A Stream that represents the contents of the entry.</returns>
 475        /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been delet
 476        /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be rea
 477        /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exce
 478        /// <exception cref="InvalidOperationException">The requested access is not compatible with the archive's open m
 479        /// <param name="password">The password used to decrypt the entry. If the entry is not encrypted, this parameter
 480        public Stream Open(ReadOnlySpan<char> password)
 0481        {
 0482            ThrowIfInvalidArchive();
 483
 0484            if (IsEncrypted && password.IsEmpty)
 0485            {
 0486                throw new ArgumentException(SR.PasswordRequired, nameof(password));
 487            }
 488
 0489            return OpenCore(InferAccessFromMode(), password);
 0490        }
 491
 492        /// <summary>
 493        /// Opens the entry with the specified access mode. This allows for more granular control over the returned stre
 494        /// </summary>
 495        /// <param name="access">The file access mode for the returned stream.</param>
 496        /// <returns>A <see cref="Stream"/> that represents the contents of the entry with the specified access capabili
 497        /// <remarks>
 498        /// <para>The allowed <paramref name="access"/> values depend on the <see cref="ZipArchiveMode"/>:</para>
 499        /// <list type="bullet">
 500        /// <item><description><see cref="ZipArchiveMode.Read"/>: Only <see cref="FileAccess.Read"/> is allowed.</descri
 501        /// <item><description><see cref="ZipArchiveMode.Create"/>: <see cref="FileAccess.Write"/> and <see cref="FileAc
 502        /// <item><description><see cref="ZipArchiveMode.Update"/>: All values are allowed. <see cref="FileAccess.Read"/
 503        /// </list>
 504        /// </remarks>
 505        /// <exception cref="ArgumentOutOfRangeException"><paramref name="access"/> is not a valid <see cref="FileAccess
 506        /// <exception cref="InvalidOperationException">The requested access is not compatible with the archive's open m
 507        /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been delet
 508        /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be rea
 509        /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exce
 510        public Stream Open(FileAccess access)
 0511        {
 0512            ThrowIfInvalidArchive();
 0513            ValidateAccessForMode(access);
 0514            return OpenCore(access);
 0515        }
 516
 517        public Stream Open(FileAccess access, ReadOnlySpan<char> password)
 0518        {
 0519            ThrowIfInvalidArchive();
 0520            ValidateAccessForMode(access);
 521
 0522            if (IsEncrypted && password.IsEmpty)
 0523            {
 0524                throw new ArgumentException(SR.PasswordRequired, nameof(password));
 525            }
 526
 0527            return OpenCore(access, password);
 0528        }
 529
 0530        private FileAccess InferAccessFromMode() => _archive.Mode switch
 0531        {
 0532            ZipArchiveMode.Read => FileAccess.Read,
 0533            ZipArchiveMode.Create => FileAccess.Write,
 0534            _ => FileAccess.ReadWrite
 0535        };
 536
 537        private void ValidateAccessForMode(FileAccess access)
 0538        {
 0539            if (access is not (FileAccess.Read or FileAccess.Write or FileAccess.ReadWrite))
 0540            {
 0541                throw new ArgumentOutOfRangeException(nameof(access), SR.InvalidFileAccess);
 542            }
 543
 0544            switch (_archive.Mode)
 545            {
 546                case ZipArchiveMode.Read:
 0547                    if (access != FileAccess.Read)
 0548                    {
 0549                        throw new InvalidOperationException(SR.CannotBeWrittenInReadMode);
 550                    }
 0551                    break;
 552                case ZipArchiveMode.Create:
 0553                    if (access == FileAccess.Read)
 0554                    {
 0555                        throw new InvalidOperationException(SR.CannotBeReadInCreateMode);
 556                    }
 0557                    break;
 558            }
 0559        }
 560
 561        private Stream OpenCore(FileAccess access, ReadOnlySpan<char> password = default)
 0562        {
 0563            switch (_archive.Mode)
 564            {
 565                case ZipArchiveMode.Read:
 0566                    return OpenInReadMode(checkOpenable: true, password);
 567                case ZipArchiveMode.Create:
 0568                    return OpenInWriteMode();
 569                case ZipArchiveMode.Update:
 570                default:
 0571                    Debug.Assert(_archive.Mode == ZipArchiveMode.Update);
 0572                    return access switch
 0573                    {
 0574                        // Reads in Update mode must observe content written earlier in this session and
 0575                        // treat a newly created entry as empty. Only an unmodified entry that already
 0576                        // exists in the archive can be read directly without loading it into memory.
 0577                        FileAccess.Read => _storedUncompressedData is not null || !_originallyInArchive
 0578                            ? OpenInUpdateModeForRead()
 0579                            : OpenInReadMode(checkOpenable: true, password),
 0580                        FileAccess.Write => OpenInUpdateMode(loadExistingContent: false, password),
 0581                        _ => OpenInUpdateMode(loadExistingContent: true, password),
 0582                    };
 583            }
 0584        }
 585
 586        private string DecodeEntryString(byte[] entryStringBytes)
 2512587        {
 2512588            Debug.Assert(entryStringBytes != null);
 589
 2512590            Encoding readEntryStringEncoding =
 2512591                (_generalPurposeBitFlag & BitFlagValues.UnicodeFileNameAndComment) == BitFlagValues.UnicodeFileNameAndCo
 2512592                ? Encoding.UTF8
 2512593                : _archive?.EntryNameAndCommentEncoding ?? Encoding.UTF8;
 594
 2512595            return readEntryStringEncoding.GetString(entryStringBytes);
 2512596        }
 597
 598        // Only allow opening ZipArchives with large ZipArchiveEntries in update mode when running in a 64-bit process.
 599        // This is for compatibility with old behavior that threw an exception for all process bitnesses, because this
 600        // will not work in a 32-bit process.
 0601        private static readonly bool s_allowLargeZipArchiveEntriesInUpdateMode = IntPtr.Size > 4;
 602
 0603        internal bool EverOpenedForWrite => _everOpenedForWrite;
 604
 605        internal long GetOffsetOfCompressedData()
 0606        {
 0607            if (_storedOffsetOfCompressedData == null)
 0608            {
 609                // Seek to local header
 0610                _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
 611
 612                // Skip the local file header to get to the compressed data
 613                // TrySkipBlock handles both AES and non-AES cases correctly
 0614                if (!ZipLocalFileHeader.TrySkipBlock(_archive.ArchiveStream))
 0615                {
 0616                    throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
 617                }
 618
 0619                _storedOffsetOfCompressedData = _archive.ArchiveStream.Position;
 0620            }
 621
 0622            return _storedOffsetOfCompressedData.Value;
 0623        }
 624
 625        /// <summary>
 626        /// Reads and caches the AES encryption salt from the local file data area.
 627        /// Called during central directory parsing for AES-encrypted entries so that
 628        /// the salt is available for key derivation without additional I/O at open time.
 629        /// </summary>
 630        internal void ReadEncryptionSaltIfNeeded()
 523631        {
 523632            if (!IsAesEncrypted || !_originallyInArchive || OperatingSystem.IsBrowser() || OperatingSystem.IsWasi())
 523633            {
 523634                return;
 635            }
 636
 637            // A corrupt central directory can point the local header offset past the end of the
 638            // archive. Seeking there throws ArgumentOutOfRangeException on some streams (e.g.
 639            // MemoryStream). Mirror the check in IsOpenableInitialVerifications and defer the error
 640            // to when the entry is actually opened, same as for non AES encrypted entries.
 0641            if (_offsetOfLocalHeader > _archive.ArchiveStream.Length)
 0642            {
 0643                return;
 644            }
 645
 0646            long savedPosition = _archive.ArchiveStream.Position;
 647            try
 0648            {
 0649                long offset = GetOffsetOfCompressedData();
 0650                _archive.ArchiveStream.Seek(offset, SeekOrigin.Begin);
 651
 0652                int keySizeBits = GetAesKeySizeBits(Encryption);
 0653                int saltSize = WinZipAesStream.GetSaltSize(keySizeBits);
 0654                _aesSalt = new byte[saltSize];
 0655                _archive.ArchiveStream.ReadExactly(_aesSalt);
 0656            }
 0657            catch (Exception ex) when (ex is InvalidDataException or EndOfStreamException)
 0658            {
 659                // These are the only exceptions GetOffsetOfCompressedData() and ReadExactly()
 660                // can throw for corrupt or truncated data. Swallow them here and defer the error
 661                // to when the entry is actually opened.
 0662                _aesSalt = null;
 0663            }
 664            finally
 0665            {
 0666                _archive.ArchiveStream.Seek(savedPosition, SeekOrigin.Begin);
 0667            }
 523668        }
 669
 670        private MemoryStream GetUncompressedData(ReadOnlySpan<char> password = default)
 0671        {
 0672            if (_storedUncompressedData == null)
 0673            {
 674                // this means we have never opened it before
 675
 676
 677                // MemoryStream is backed by a single byte[] and cannot grow beyond Array.MaxLength.
 678                // Validate up front before attempting the (int) cast.
 0679                if ((ulong)_uncompressedSize > (ulong)Array.MaxLength)
 0680                {
 0681                    _currentlyOpenForWrite = false;
 0682                    throw new InvalidDataException(SR.EntryUncompressedSizeTooLargeForUpdateMode);
 683                }
 684
 0685                _storedUncompressedData = new MemoryStream((int)_uncompressedSize);
 686
 0687                if (_originallyInArchive)
 0688                {
 0689                    Stream decompressor = !password.IsEmpty
 0690                        ? OpenInReadMode(checkOpenable: false, password)
 0691                        : OpenInReadMode(checkOpenable: false);
 692
 0693                    using (decompressor)
 0694                    {
 695                        try
 0696                        {
 0697                            decompressor.CopyTo(_storedUncompressedData);
 0698                        }
 0699                        catch (InvalidDataException)
 0700                        {
 701                            // this is the case where the archive say the entry is deflate, but deflateStream
 702                            // throws an InvalidDataException. This property should only be getting accessed in
 703                            // Update mode, so we want to make sure _storedUncompressedData stays null so
 704                            // that later when we dispose the archive, this entry loads the compressedBytes, and
 705                            // copies them straight over
 0706                            _storedUncompressedData.Dispose();
 0707                            _storedUncompressedData = null;
 0708                            _currentlyOpenForWrite = false;
 0709                            _everOpenedForWrite = false;
 0710                            _derivedZipCryptoKeyMaterial = null;
 0711                            _derivedAesKeyMaterial = null;
 0712                            throw;
 713                        }
 0714                    }
 0715                }
 716
 717                // NOTE: CompressionMethod normalization is deferred to MarkAsModified() to avoid
 718                // corrupting entries that are opened in Update mode but not actually written to.
 719                // If we normalized here and the entry wasn't modified, we'd write a header with
 720                // CompressionMethod=Deflate but the original _compressedBytes would still be in
 721                // their original format (e.g., Deflate64), producing an invalid entry.
 0722            }
 723
 0724            return _storedUncompressedData;
 0725        }
 726        // does almost everything you need to do to forget about this entry
 727        // writes the local header/data, gets rid of all the data,
 728        // closes all of the streams except for the very outermost one that
 729        // the user holds on to and is responsible for closing
 730        //
 731        // after calling this, and only after calling this can we be guaranteed
 732        // that we are reading to write the central directory
 733        //
 734        // should only throw an exception in extremely exceptional cases because it is called from dispose
 735        internal void WriteAndFinishLocalEntry(bool forceWrite)
 0736        {
 0737            CloseStreams();
 0738            WriteLocalFileHeaderAndDataIfNeeded(forceWrite);
 0739            UnloadStreams();
 0740        }
 741
 742        private bool WriteCentralDirectoryFileHeaderInitialize(bool forceWrite, out Zip64ExtraField? zip64ExtraField, ou
 0743        {
 744            // This part is simple, because we should definitely know the sizes by this time
 745
 746            // _storedEntryNameBytes only gets set when we read in or call moveTo. MoveTo does a check, and
 747            // reading in should not be able to produce an entryname longer than ushort.MaxValue
 748            // _fileComment only gets set when we read in or set the FileComment property. This performs its own
 749            // length check.
 0750            Debug.Assert(_storedEntryNameBytes.Length <= ushort.MaxValue);
 0751            Debug.Assert(_fileComment.Length <= ushort.MaxValue);
 752
 753            // decide if we need the Zip64 extra field:
 0754            zip64ExtraField = null;
 755
 0756            if (AreSizesTooLarge
 0757#if DEBUG_FORCE_ZIP64
 0758                || _archive._forceZip64
 0759#endif
 0760                )
 0761            {
 0762                compressedSizeTruncated = ZipHelper.Mask32Bit;
 0763                uncompressedSizeTruncated = ZipHelper.Mask32Bit;
 764
 765                // If we have one of the sizes, the other must go in there as speced for LH, but not necessarily for CH,
 0766                zip64ExtraField = new()
 0767                {
 0768                    CompressedSize = _compressedSize,
 0769                    UncompressedSize = _uncompressedSize
 0770                };
 0771            }
 772            else
 0773            {
 0774                compressedSizeTruncated = (uint)_compressedSize;
 0775                uncompressedSizeTruncated = (uint)_uncompressedSize;
 0776            }
 777
 778
 0779            if (IsOffsetTooLarge
 0780#if DEBUG_FORCE_ZIP64
 0781                || _archive._forceZip64
 0782#endif
 0783                )
 0784            {
 0785                offsetOfLocalHeaderTruncated = ZipHelper.Mask32Bit;
 786
 787                // If we already created a zip64 extra field for sizes, add the offset to it
 788                // Otherwise, create a new one for just the offset
 0789                zip64ExtraField ??= new();
 0790                zip64ExtraField.LocalHeaderOffset = _offsetOfLocalHeader;
 0791            }
 792            else
 0793            {
 0794                offsetOfLocalHeaderTruncated = (uint)_offsetOfLocalHeader;
 0795            }
 796
 0797            if (zip64ExtraField != null)
 0798            {
 0799                VersionToExtractAtLeast(ZipVersionNeededValues.Zip64);
 0800            }
 801
 802
 0803            WinZipAesExtraField? aesExtraField = null;
 0804            int aesExtraFieldSize = 0;
 805
 0806            if (UseAesEncryption)
 0807            {
 0808                aesExtraField = CreateAesExtraField();
 0809                aesExtraFieldSize = WinZipAesExtraField.TotalSize;
 0810            }
 811
 812            // determine if we can fit zip64 extra field and original extra fields all in
 813            // When using AES encryption, exclude the AES tag from currExtraFieldDataLength since we're writing a new on
 0814            int currExtraFieldDataLength = UseAesEncryption
 0815                ? ZipGenericExtraField.TotalSizeExcludingTag(_cdUnknownExtraFields, _cdTrailingExtraFieldData?.Length ??
 0816                : ZipGenericExtraField.TotalSize(_cdUnknownExtraFields, _cdTrailingExtraFieldData?.Length ?? 0);
 0817            int bigExtraFieldLength = (zip64ExtraField != null ? zip64ExtraField.TotalSize : 0)
 0818                                      + aesExtraFieldSize
 0819                                      + currExtraFieldDataLength;
 820
 0821            if (bigExtraFieldLength > ushort.MaxValue)
 0822            {
 0823                extraFieldLength = (ushort)((zip64ExtraField != null ? zip64ExtraField.TotalSize : 0) + aesExtraFieldSiz
 0824                _cdUnknownExtraFields = null;
 0825            }
 826            else
 0827            {
 0828                extraFieldLength = (ushort)bigExtraFieldLength;
 0829            }
 830
 0831            if (_originallyInArchive && Changes == ZipArchive.ChangeState.Unchanged && !forceWrite)
 0832            {
 0833                long centralDirectoryHeaderLength = ZipCentralDirectoryFileHeader.FieldLocations.DynamicData
 0834                    + _storedEntryNameBytes.Length
 0835                    + extraFieldLength
 0836                    + _fileComment.Length;
 837
 0838                _archive.ArchiveStream.Seek(centralDirectoryHeaderLength, SeekOrigin.Current);
 839
 0840                return false;
 841            }
 842
 0843            return true;
 0844        }
 845
 846        private void WriteCentralDirectoryFileHeaderPrepare(Span<byte> cdStaticHeader, uint compressedSizeTruncated, uin
 0847        {
 848            // The central directory file header begins with the below constant-length structure:
 849            // Central directory file header signature  (4 bytes)
 850            // Version made by Specification (version)  (1 byte)
 851            // Version made by Compatibility (type)     (1 byte)
 852            // Minimum version needed to extract        (2 bytes)
 853            // General Purpose bit flag                 (2 bytes)
 854            // The Compression method                   (2 bytes)
 855            // File last modification time and date     (4 bytes)
 856            // CRC-32                                   (4 bytes)
 857            // Compressed Size                          (4 bytes)
 858            // Uncompressed Size                        (4 bytes)
 859            // File Name Length                         (2 bytes)
 860            // Extra Field Length                       (2 bytes)
 861            // File Comment Length                      (2 bytes)
 862            // Start Disk Number                        (2 bytes)
 863            // Internal File Attributes                 (2 bytes)
 864            // External File Attributes                 (4 bytes)
 865            // Offset Of Local Header                   (4 bytes)
 866
 0867            ZipCentralDirectoryFileHeader.SignatureConstantBytes.CopyTo(cdStaticHeader[ZipCentralDirectoryFileHeader.Fie
 0868            cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.VersionMadeBySpecification] = (byte)_versionMade
 0869            cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.VersionMadeByCompatibility] = (byte)CurrentZipPl
 0870            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Version
 0871            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.General
 872
 873            // For AES encryption, write compression method 99 (Aes) in the header
 874            // _headerCompressionMethod preserves the original value from the central directory
 0875            ushort compressionMethodToWrite = UseAesEncryption ? (ushort)WinZipAesMethod : (ushort)CompressionMethod;
 0876            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Compres
 877
 0878            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.LastMod
 879            // when using aes encryption, ae-2 standard dictates crc to be 0
 0880            uint crcToWrite = UseAesEncryption ? 0 : _crc32;
 0881            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Crc32..
 0882            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Compres
 0883            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Uncompr
 0884            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Filenam
 0885            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.ExtraFi
 0886            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.FileCom
 0887            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.DiskNum
 0888            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Interna
 0889            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Externa
 0890            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Relativ
 0891        }
 892
 893        // should only throw an exception in extremely exceptional cases because it is called from dispose
 894        internal unsafe void WriteCentralDirectoryFileHeader(bool forceWrite)
 0895        {
 0896            if (WriteCentralDirectoryFileHeaderInitialize(forceWrite, out Zip64ExtraField? zip64ExtraField, out uint com
 0897            {
 0898                Span<byte> cdStaticHeader = stackalloc byte[ZipCentralDirectoryFileHeader.BlockConstantSectionSize];
 0899                WriteCentralDirectoryFileHeaderPrepare(cdStaticHeader, compressedSizeTruncated, uncompressedSizeTruncate
 900
 0901                _archive.ArchiveStream.Write(cdStaticHeader);
 0902                _archive.ArchiveStream.Write(_storedEntryNameBytes);
 903
 904                // only write zip64ExtraField if we decided we need it (it's not null)
 0905                zip64ExtraField?.WriteBlock(_archive.ArchiveStream);
 906
 907                // Write AES extra field if using AES encryption
 0908                if (UseAesEncryption)
 0909                {
 0910                    CreateAesExtraField().WriteBlock(_archive.ArchiveStream);
 911
 912                    // write extra fields excluding existing AES extra field (and any malformed trailing data).
 0913                    ZipGenericExtraField.WriteAllBlocksExcludingTag(_cdUnknownExtraFields, _cdTrailingExtraFieldData ?? 
 0914                }
 915                else
 0916                {
 917                    // write extra fields (and any malformed trailing data).
 0918                    ZipGenericExtraField.WriteAllBlocks(_cdUnknownExtraFields, _cdTrailingExtraFieldData ?? Array.Empty<
 0919                }
 920
 0921                if (_fileComment.Length > 0)
 0922                {
 0923                    _archive.ArchiveStream.Write(_fileComment);
 0924                }
 0925            }
 0926        }
 927
 928        // throws exception if fails, will get called on every relevant entry before closing in update mode
 929        // can throw InvalidDataException
 930        internal void LoadLocalHeaderExtraFieldIfNeeded()
 0931        {
 932            // we should have made this exact call in _archive.Init through ThrowIfOpenable
 0933            Debug.Assert(IsOpenable(false, true, out _));
 934
 935            // load local header's extra fields. it will be null if we couldn't read for some reason
 0936            if (_originallyInArchive)
 0937            {
 0938                _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
 0939                _lhUnknownExtraFields = ZipLocalFileHeader.GetExtraFields(_archive.ArchiveStream, out _lhTrailingExtraFi
 0940            }
 0941        }
 942
 943        private byte[][] LoadCompressedBytesIfNeededInitialize(out int maxSingleBufferSize)
 0944        {
 945            // we know that it is openable at this point
 0946            maxSingleBufferSize = Array.MaxLength;
 947
 0948            byte[][] compressedBytes = new byte[(_compressedSize / maxSingleBufferSize) + 1][];
 0949            for (int i = 0; i < compressedBytes.Length - 1; i++)
 0950            {
 0951                compressedBytes[i] = new byte[maxSingleBufferSize];
 0952            }
 0953            compressedBytes[compressedBytes.Length - 1] = new byte[_compressedSize % maxSingleBufferSize];
 954
 0955            return compressedBytes;
 0956        }
 957
 958        // throws exception if fails, will get called on every relevant entry before closing in update mode
 959        // can throw InvalidDataException
 960        internal void LoadCompressedBytesIfNeeded()
 0961        {
 962            // we should have made this exact call in _archive.Init through ThrowIfOpenable
 0963            Debug.Assert(IsOpenable(false, true, out _));
 964
 0965            if (!_everOpenedForWrite && _originallyInArchive)
 0966            {
 0967                _compressedBytes = LoadCompressedBytesIfNeededInitialize(out int maxSingleBufferSize);
 968
 0969                _archive.ArchiveStream.Seek(GetOffsetOfCompressedData(), SeekOrigin.Begin);
 970
 0971                for (int i = 0; i < _compressedBytes.Length - 1; i++)
 0972                {
 0973                    _archive.ArchiveStream.ReadAtLeast(_compressedBytes[i], maxSingleBufferSize, throwOnEndOfStream: tru
 0974                }
 0975                _archive.ArchiveStream.ReadAtLeast(_compressedBytes[_compressedBytes.Length - 1], (int)(_compressedSize 
 0976            }
 0977        }
 978
 979        internal void ThrowIfNotOpenable(bool needToUncompress, bool needToLoadIntoMemory)
 0980        {
 0981            if (!IsOpenable(needToUncompress, needToLoadIntoMemory, out string? message))
 0982            {
 0983                throw new InvalidDataException(message);
 984            }
 0985        }
 986
 987        private void DetectEntryNameVersion()
 1910988        {
 1910989            if (ParseFileName(_storedEntryName, _versionMadeByPlatform) == "")
 408990            {
 408991                VersionToExtractAtLeast(ZipVersionNeededValues.ExplicitDirectory);
 408992            }
 1910993        }
 994
 995
 996        private CheckSumAndSizeWriteStream GetDataCompressor(Stream backingStream, bool leaveBackingStreamOpen, EventHan
 0997        {
 998            // stream stack: backingStream -> DeflateStream -> CheckSumWriteStream
 999
 1000            // By default we compress with deflate, except if compression level
 1001            // is set to NoCompression then stored is used.
 1002            //
 1003            // Stored is also used for empty files, but we can't know at this
 1004            // point if user will write anything to the stream or not. For that
 1005            // reason, we defer the instantiation of the compression stream
 1006            // until the first write to the CheckSumAndSizeWriteStream happens.
 1007            // If the user never writes anything, this will be detected during
 1008            // saving and the compression method in the file header will be
 1009            // changed to Stored.
 1010            //
 1011            // Note: Deflate64 is not supported on all platforms
 01012            Debug.Assert(CompressionMethod == ZipCompressionMethod.Deflate
 01013                || CompressionMethod == ZipCompressionMethod.Stored);
 1014            Func<Stream> compressorStreamFactory;
 1015
 01016            bool isIntermediateStream = true;
 1017
 01018            switch (CompressionMethod)
 1019            {
 1020                case ZipCompressionMethod.Stored:
 01021                    compressorStreamFactory = () => backingStream;
 01022                    isIntermediateStream = false;
 01023                    break;
 1024                case ZipCompressionMethod.Deflate:
 1025                case ZipCompressionMethod.Deflate64:
 1026                default:
 01027                    compressorStreamFactory = () => new DeflateStream(backingStream, _compressionLevel, leaveBackingStre
 01028                    break;
 1029            }
 01030            bool leaveCompressorStreamOpenOnClose = leaveBackingStreamOpen && !isIntermediateStream;
 01031            var checkSumStream = new CheckSumAndSizeWriteStream(
 01032                compressorStreamFactory,
 01033                streamForPosition ?? backingStream,
 01034                leaveCompressorStreamOpenOnClose,
 01035                this,
 01036                onClose,
 01037                (long initialPosition, long currentPosition, uint checkSum, Stream backing, ZipArchiveEntry thisRef, Eve
 01038                {
 01039                    thisRef._crc32 = checkSum;
 01040                    thisRef._uncompressedSize = currentPosition;
 01041                    thisRef._compressedSize = backing.Position - initialPosition;
 01042                    closeHandler?.Invoke(thisRef, EventArgs.Empty);
 01043                });
 1044
 01045            return checkSumStream;
 01046        }
 1047
 1048        private byte CalculateZipCryptoCheckByte()
 01049        {
 1050            // If data descriptor NOT used, the check byte is the MSB of CRC32
 01051            if ((_generalPurposeBitFlag & BitFlagValues.DataDescriptor) == 0)
 01052            {
 01053                return (byte)((_crc32 >> 24) & 0xFF);
 1054            }
 1055
 1056            // If data descriptor IS used, the check byte is the MSB of the DOS time from the *local* header
 01057            return (byte)((ZipHelper.DateTimeToDosTime(_lastModified.DateTime) >> 8) & 0xFF);
 01058        }
 1059
 01060        private bool IsZipCryptoEncrypted => (_generalPurposeBitFlag & BitFlagValues.IsEncrypted) != 0 && (ushort)_heade
 1061
 01062        private bool UseAesEncryption => Encryption is ZipEncryptionMethod.Aes128 or ZipEncryptionMethod.Aes192 or ZipEn
 1063
 30001064        private bool IsAesEncrypted => IsEncrypted && (ushort)_headerCompressionMethod == WinZipAesMethod;
 1065
 1066        private static int GetAesKeySizeBits(ZipEncryptionMethod encryption)
 01067        {
 1068            // Get number of bits for AES key size based on the encryption method
 1069            // Only possible values are: AES-128 = 128 bits, AES-192 = 192 bits, AES-256 as per the specs
 01070            return encryption switch
 01071            {
 01072                ZipEncryptionMethod.Aes128 => 128,
 01073                ZipEncryptionMethod.Aes192 => 192,
 01074                ZipEncryptionMethod.Aes256 => 256,
 01075                _ => throw new InvalidDataException(SR.InvalidAesStrength)
 01076            };
 01077        }
 1078
 1079        /// <summary>
 1080        /// Creates the appropriate decryption stream for an encrypted entry.
 1081        /// For AES entries, uses the salt that was pre-read during central directory parsing.
 1082        /// </summary>
 1083        private Stream WrapWithDecryptionIfNeeded(Stream compressedStream, ReadOnlySpan<char> password)
 01084        {
 01085            if (Encryption == ZipEncryptionMethod.Unknown)
 01086            {
 01087                throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 1088            }
 1089
 01090            if (password.IsEmpty)
 01091            {
 01092                throw new InvalidDataException(SR.PasswordRequired);
 1093            }
 1094
 01095            if (IsAesEncrypted)
 01096            {
 01097                if (_aesSalt is null)
 01098                {
 01099                    throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
 1100                }
 1101
 01102                int keySizeBits = GetAesKeySizeBits(Encryption);
 01103                WinZipAesKeyMaterial keyMaterial = WinZipAesStream.CreateKey(password, _aesSalt, keySizeBits);
 01104                return WinZipAesStream.Create(
 01105                    baseStream: compressedStream,
 01106                    keyMaterial: keyMaterial,
 01107                    totalStreamSize: _compressedSize,
 01108                    encrypting: false);
 1109            }
 1110
 01111            if (IsZipCryptoEncrypted)
 01112            {
 01113                byte expectedCheckByte = CalculateZipCryptoCheckByte();
 01114                ZipCryptoKeys keyMaterial = ZipCryptoStream.CreateKey(password);
 01115                return ZipCryptoStream.Create(compressedStream, keyMaterial, expectedCheckByte, encrypting: false);
 1116            }
 1117
 01118            throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 01119        }
 1120
 1121        private Stream GetDataDecompressor(Stream compressedStreamToRead)
 01122        {
 1123            Stream? uncompressedStream;
 01124            switch (CompressionMethod)
 1125            {
 1126                case ZipCompressionMethod.Deflate:
 01127                    uncompressedStream = new DeflateStream(compressedStreamToRead, CompressionMode.Decompress, _uncompre
 01128                    break;
 1129                case ZipCompressionMethod.Deflate64:
 01130                    uncompressedStream = new DeflateManagedStream(compressedStreamToRead, ZipCompressionMethod.Deflate64
 01131                    break;
 1132                case ZipCompressionMethod.Stored:
 01133                    uncompressedStream = compressedStreamToRead;
 01134                    break;
 1135                default:
 1136                    // We should not get here with Aes as CompressionMethod anymore
 1137                    // as it should have been replaced with the actual compression method
 01138                    Debug.Assert((ushort)CompressionMethod != WinZipAesMethod,
 01139                        "AES compression method should have been replaced with actual compression method");
 1140
 1141                    // Fallback to stored if we somehow get here
 01142                    uncompressedStream = compressedStreamToRead;
 01143                    break;
 1144            }
 1145
 01146            return uncompressedStream;
 01147        }
 1148
 1149        private Stream OpenInReadMode(bool checkOpenable, ReadOnlySpan<char> password = default)
 01150        {
 01151            if (checkOpenable)
 01152            {
 01153                ThrowIfNotOpenable(needToUncompress: true, needToLoadIntoMemory: false);
 01154            }
 01155            return OpenInReadModeGetDataCompressor(GetOffsetOfCompressedData(), password);
 01156        }
 1157
 1158        private Stream OpenInReadModeGetDataCompressor(long offsetOfCompressedData, ReadOnlySpan<char> password = defaul
 01159        {
 01160            Stream compressedStream = new SubReadStream(_archive.ArchiveStream, offsetOfCompressedData, _compressedSize)
 1161            Stream streamToDecompress;
 1162
 01163            if (IsEncrypted)
 01164            {
 01165                streamToDecompress = WrapWithDecryptionIfNeeded(compressedStream, password);
 01166            }
 1167            else
 01168            {
 01169                streamToDecompress = compressedStream;
 01170            }
 1171
 01172            return BuildDecompressionPipeline(streamToDecompress);
 01173        }
 1174
 1175        /// <summary>
 1176        /// Wraps a (possibly decrypted) stream with decompression and CRC validation.
 1177        /// Shared by both sync and async read paths.
 1178        /// </summary>
 1179        private Stream BuildDecompressionPipeline(Stream streamToDecompress)
 01180        {
 01181            Stream decompressedStream = GetDataDecompressor(streamToDecompress);
 1182
 1183            // AE-2 encrypted entries store CRC as 0 so skip CRC validation for those.
 1184            // AE-1 version entries store a valid CRC.
 01185            if (IsAesEncrypted && _aeVersion == 2)
 01186            {
 01187                return decompressedStream;
 1188            }
 1189
 01190            return new CrcValidatingReadStream(decompressedStream, _crc32, _uncompressedSize);
 01191        }
 1192
 1193        private WrappedStream OpenInWriteMode()
 01194        {
 01195            if (_everOpenedForWrite)
 01196            {
 01197                throw new IOException(SR.CreateModeWriteOnceAndOneEntryAtATime);
 1198            }
 1199
 1200            // we assume that if another entry grabbed the archive stream, that it set this entry's _everOpenedForWrite 
 01201            _archive.DebugAssertIsStillArchiveStreamOwner(this);
 1202
 01203            return OpenInWriteModeCore();
 01204        }
 1205
 1206        private WrappedStream OpenInWriteModeCore()
 01207        {
 01208            _everOpenedForWrite = true;
 01209            Changes |= ZipArchive.ChangeState.StoredData;
 1210
 1211            // Use the encryption method pre-configured via PrepareEncryption (CreateEntry with password).
 01212            ZipEncryptionMethod encryptionMethod = Encryption;
 1213
 1214            // Build the stream stack with encryption if needed
 01215            Stream targetStream = _archive.ArchiveStream;
 01216            Stream? encryptionStream = null;
 1217
 01218            if (encryptionMethod == ZipEncryptionMethod.ZipCrypto)
 01219            {
 01220                ZipCryptoKeys keyMaterial = _derivedZipCryptoKeyMaterial
 01221                    ?? throw new InvalidOperationException(SR.EmptyPassword);
 1222
 01223                ushort verifierLow2Bytes = (ushort)ZipHelper.DateTimeToDosTime(_lastModified.DateTime);
 1224
 01225                targetStream = ZipCryptoStream.Create(
 01226                    baseStream: _archive.ArchiveStream,
 01227                    keys: keyMaterial,
 01228                    passwordVerifierLow2Bytes: verifierLow2Bytes,
 01229                    encrypting: true,
 01230                    crc32: null,
 01231                    leaveOpen: true);
 01232                encryptionStream = targetStream;
 01233            }
 01234            else if (encryptionMethod is ZipEncryptionMethod.Aes256 or ZipEncryptionMethod.Aes192 or ZipEncryptionMethod
 01235            {
 01236                WinZipAesKeyMaterial keyMaterial = _derivedAesKeyMaterial
 01237                    ?? throw new InvalidOperationException(SR.EmptyPassword);
 1238
 01239                targetStream = WinZipAesStream.Create(
 01240                    baseStream: _archive.ArchiveStream,
 01241                    keyMaterial: keyMaterial,
 01242                    totalStreamSize: -1,
 01243                    encrypting: true,
 01244                    leaveOpen: true);
 01245                encryptionStream = targetStream;
 01246            }
 1247
 01248            bool isAesEncryption = encryptionMethod is ZipEncryptionMethod.Aes256 or ZipEncryptionMethod.Aes192 or ZipEn
 1249
 01250            CheckSumAndSizeWriteStream crcSizeStream = GetDataCompressor(
 01251                targetStream,
 01252                leaveBackingStreamOpen: !isAesEncryption,
 01253                (object? o, EventArgs e) =>
 01254                {
 01255                    // release the archive stream
 01256                    var entry = (ZipArchiveEntry)o!;
 01257                    entry._archive.ReleaseArchiveStream(entry);
 01258                    entry._outstandingWriteStream = null;
 01259                },
 01260                streamForPosition: encryptionMethod != ZipEncryptionMethod.None ? _archive.ArchiveStream : null);
 1261
 01262            _outstandingWriteStream = new DirectToArchiveWriterStream(crcSizeStream, this, encryptionMethod, encryptionS
 1263
 01264            return new WrappedStream(baseStream: _outstandingWriteStream, closeBaseStream: true);
 01265        }
 1266
 1267        private WrappedStream OpenInUpdateMode(bool loadExistingContent = true, ReadOnlySpan<char> password = default)
 01268        {
 01269            if (_currentlyOpenForWrite)
 01270            {
 01271                throw new IOException(SR.UpdateModeOneStream);
 1272            }
 1273
 01274            if (Encryption == ZipEncryptionMethod.Unknown)
 01275            {
 01276                throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
 1277            }
 1278
 1279            // Encrypted entries always require a password for re-encryption,
 1280            // even when discarding existing content (write-only access).
 01281            if (IsEncrypted && password.IsEmpty)
 01282            {
 01283                throw new ArgumentException(SR.PasswordRequired, nameof(password));
 1284            }
 1285
 01286            if (loadExistingContent)
 01287            {
 01288                ThrowIfNotOpenable(needToUncompress: true, needToLoadIntoMemory: true);
 01289            }
 1290
 01291            _currentlyOpenForWrite = true;
 1292
 1293            // Set up re-encryption key material so that the rewritten entry has valid encryption headers.
 01294            if (IsEncrypted)
 01295            {
 01296                SetupEncryptionKeyMaterial(password);
 01297            }
 1298
 01299            if (loadExistingContent)
 01300            {
 01301                _storedUncompressedData = GetUncompressedData(password);
 01302            }
 1303            else
 01304            {
 01305                _storedUncompressedData?.Dispose();
 01306                _storedUncompressedData = new MemoryStream();
 1307                // Opening with loadExistingContent: false discards existing content, which is a modification
 01308                MarkAsModified();
 01309            }
 1310
 01311            _storedUncompressedData.Seek(0, SeekOrigin.Begin);
 1312
 01313            return new WrappedStream(_storedUncompressedData, this,
 01314                onClosed: thisRef => thisRef!._currentlyOpenForWrite = false,
 01315                notifyEntryOnWrite: true);
 01316        }
 1317
 1318        // Returns a read-only, seekable view over the entry's current uncompressed buffer in Update mode.
 1319        // It reflects modifications made earlier in the current session (and is empty for a freshly created
 1320        // entry), unlike OpenInReadMode which reads the original bytes directly from the archive. The stream
 1321        // shares the underlying buffer with the entry rather than copying it, so it is not isolated from later
 1322        // in-place writes; callers are expected to finish reading before reopening the entry for writing.
 1323        private MemoryStream OpenInUpdateModeForRead()
 01324        {
 01325            if (_currentlyOpenForWrite)
 01326                throw new IOException(SR.UpdateModeOneStream);
 1327
 01328            MemoryStream uncompressedData = GetUncompressedData();
 01329            return new MemoryStream(uncompressedData.GetBuffer(), 0, (int)uncompressedData.Length, writable: false);
 01330        }
 1331
 1332        /// <summary>
 1333        /// Marks this entry as modified, indicating that its data has changed and needs to be rewritten.
 1334        /// </summary>
 1335        internal void MarkAsModified()
 01336        {
 01337            _everOpenedForWrite = true;
 01338            Changes |= ZipArchive.ChangeState.StoredData;
 1339
 1340            // Normalize compression method when actually modifying - Deflate64 data will be
 1341            // re-compressed as Deflate since we don't support writing Deflate64.
 01342            if (CompressionMethod != ZipCompressionMethod.Stored)
 01343            {
 01344                CompressionMethod = ZipCompressionMethod.Deflate;
 01345            }
 01346        }
 1347
 1348        /// <summary>
 1349        /// Sets up encryption key material for re-encryption when writing back to the archive.
 1350        /// </summary>
 1351        private void SetupEncryptionKeyMaterial(ReadOnlySpan<char> password)
 01352        {
 1353            // Derive and save key material for re-encryption
 01354            if (IsZipCryptoEncrypted)
 01355            {
 01356                _derivedZipCryptoKeyMaterial = ZipCryptoStream.CreateKey(password);
 01357                Encryption = ZipEncryptionMethod.ZipCrypto;
 01358            }
 01359            else if (UseAesEncryption)
 01360            {
 1361                // Generate new salt and derive key material for AES
 1362                // This ensures each write uses a fresh random salt for security
 01363                int keySizeBits = GetAesKeySizeBits(Encryption);
 01364                _derivedAesKeyMaterial = WinZipAesStream.CreateKey(password, salt: null, keySizeBits);
 1365                // Encryption is already set from constructor (parsed from central directory AES extra field)
 01366            }
 01367        }
 1368
 1369        /// <summary>
 1370        /// Pre-derives encryption key material from a password for use when the entry is later opened for writing.
 1371        /// Called by <see cref="ZipArchive.CreateEntry(string, ReadOnlySpan{char}, ZipEncryptionMethod)"/>.
 1372        /// </summary>
 1373        internal void PrepareEncryption(ReadOnlySpan<char> password, ZipEncryptionMethod encryptionMethod)
 01374        {
 01375            if (password.IsEmpty)
 01376            {
 01377                throw new ArgumentException(SR.EmptyPassword, nameof(password));
 1378            }
 1379
 01380            if (encryptionMethod == ZipEncryptionMethod.ZipCrypto)
 01381            {
 01382                Encryption = encryptionMethod;
 01383                _derivedZipCryptoKeyMaterial = ZipCryptoStream.CreateKey(password);
 01384            }
 01385            else if (encryptionMethod is ZipEncryptionMethod.Aes128 or ZipEncryptionMethod.Aes192 or ZipEncryptionMethod
 01386            {
 1387
 01388                Encryption = encryptionMethod;
 01389                int keySizeBits = GetAesKeySizeBits(encryptionMethod);
 01390                _derivedAesKeyMaterial = WinZipAesStream.CreateKey(password, salt: null, keySizeBits);
 01391            }
 1392            else
 01393            {
 1394                // Covers ZipEncryptionMethod.None, ZipEncryptionMethod.Unknown, and any undefined/out-of-range value.
 01395                throw new ArgumentOutOfRangeException(nameof(encryptionMethod), encryptionMethod, SR.EncryptionNotSpecif
 1396            }
 01397        }
 1398
 1399        /// <summary>
 1400        /// Creates a WinZip AES extra field for writing to local/central directory headers.
 1401        /// </summary>
 1402        private WinZipAesExtraField CreateAesExtraField()
 01403        {
 01404            return new WinZipAesExtraField
 01405            {
 01406                VendorVersion = 2,
 01407                AesStrength = Encryption switch
 01408                {
 01409                    ZipEncryptionMethod.Aes128 => (byte)1,
 01410                    ZipEncryptionMethod.Aes192 => (byte)2,
 01411                    ZipEncryptionMethod.Aes256 => (byte)3,
 01412                    _ => throw new InvalidDataException(SR.InvalidAesStrength)
 01413                },
 01414                CompressionMethod = _compressionLevel == CompressionLevel.NoCompression
 01415                    ? (ushort)ZipCompressionMethod.Stored
 01416                    : (ushort)ZipCompressionMethod.Deflate
 01417            };
 01418        }
 1419
 1420        private bool IsOpenable(bool needToUncompress, bool needToLoadIntoMemory, out string? message)
 01421        {
 01422            message = null;
 1423
 01424            if (_originallyInArchive)
 01425            {
 01426                if (!IsOpenableInitialVerifications(needToUncompress, out message))
 01427                {
 01428                    return false;
 1429                }
 1430
 01431                if (!IsEncrypted && !ZipLocalFileHeader.TrySkipBlock(_archive.ArchiveStream))
 01432                {
 01433                    message = SR.LocalFileHeaderCorrupt;
 01434                    return false;
 1435                }
 01436                else if (IsEncrypted && IsAesEncrypted)
 01437                {
 01438                    _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
 1439                    // AES case - skip the local file header and validate it exists.
 1440                    // The AES metadata (encryption strength, actual compression method) was already
 1441                    // parsed from the central directory in the constructor
 01442                    if (!ZipLocalFileHeader.TrySkipBlock(_archive.ArchiveStream))
 01443                    {
 01444                        message = SR.LocalFileHeaderCorrupt;
 01445                        return false;
 1446                    }
 01447                }
 1448
 1449                // Pass the detected encryption method to GetOffsetOfCompressedData
 01450                long offsetOfCompressedData = GetOffsetOfCompressedData();
 1451
 01452                if (!IsOpenableFinalVerifications(needToLoadIntoMemory, offsetOfCompressedData, out message))
 01453                {
 01454                    return false;
 1455                }
 1456
 01457                return true;
 1458            }
 1459
 01460            return true;
 01461        }
 1462
 1463        private bool IsOpenableInitialVerifications(bool needToUncompress, out string? message)
 01464        {
 01465            message = null;
 01466            if (needToUncompress)
 01467            {
 1468                // For AES-encrypted entries, CompressionMethod now contains the actual compression
 1469                // method (from the AES extra field), not the wrapper value 99. So we can use
 1470                // the same validation logic for both encrypted and non-encrypted entries.
 01471                if (CompressionMethod != ZipCompressionMethod.Stored &&
 01472                    CompressionMethod != ZipCompressionMethod.Deflate &&
 01473                    CompressionMethod != ZipCompressionMethod.Deflate64)
 01474                {
 01475                    message = SR.UnsupportedCompression;
 01476                    return false;
 1477                }
 01478            }
 01479            if (_diskNumberStart != _archive.NumberOfThisDisk)
 01480            {
 01481                message = SR.SplitSpanned;
 01482                return false;
 1483            }
 01484            if (_offsetOfLocalHeader > _archive.ArchiveStream.Length)
 01485            {
 01486                message = SR.LocalFileHeaderCorrupt;
 01487                return false;
 1488            }
 1489
 01490            _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
 01491            return true;
 01492        }
 1493
 1494        private bool IsOpenableFinalVerifications(bool needToLoadIntoMemory, long offsetOfCompressedData, out string? me
 01495        {
 01496            message = null;
 1497            // Use an overflow-safe form: corrupt zip64 archives can report compressed sizes / offsets
 1498            // near long.MaxValue, which would wrap around in 'offsetOfCompressedData + _compressedSize'.
 01499            if (offsetOfCompressedData < 0 ||
 01500                _compressedSize < 0 ||
 01501                _compressedSize > _archive.ArchiveStream.Length - offsetOfCompressedData)
 01502            {
 01503                message = SR.LocalFileHeaderCorrupt;
 01504                return false;
 1505            }
 1506            // This limitation originally existed because a) it is unreasonable to load > 4GB into memory
 1507            // but also because the stream reading functions make it hard.  This has been updated to handle
 1508            // this scenario in a 64-bit process using multiple buffers, delivered first as an OOB for
 1509            // compatibility.
 01510            if (needToLoadIntoMemory)
 01511            {
 01512                if (_compressedSize > int.MaxValue)
 01513                {
 01514                    if (!s_allowLargeZipArchiveEntriesInUpdateMode)
 01515                    {
 01516                        message = SR.EntryTooLarge;
 01517                        return false;
 1518                    }
 01519                }
 01520            }
 1521
 01522            return true;
 01523        }
 1524
 01525        private bool AreSizesTooLarge => _compressedSize > uint.MaxValue || _uncompressedSize > uint.MaxValue;
 1526
 1527        /// <summary>
 1528        /// The position immediately after all entry data (including any trailing data descriptor).
 1529        /// Computed while reading the central directory from the next entry's local header offset
 1530        /// or, for the last entry, the central directory start offset.
 1531        /// </summary>
 1532        internal long EndOfLocalEntryData
 1533        {
 01534            get => _endOfLocalEntryData;
 01535            set => _endOfLocalEntryData = value;
 1536        }
 1537
 1538        private static CompressionLevel MapCompressionLevel(BitFlagValues generalPurposeBitFlag, ZipCompressionMethod co
 19101539        {
 1540            // Information about the Deflate compression option is stored in bits 1 and 2 of the general purpose bit fla
 1541            // If the compression method is not Deflate, the Deflate compression option is invalid - default to NoCompre
 19101542            if (compressionMethod == ZipCompressionMethod.Deflate || compressionMethod == ZipCompressionMethod.Deflate64
 621543            {
 621544                return ((int)generalPurposeBitFlag & 0x6) switch
 621545                {
 481546                    0 => CompressionLevel.Optimal,
 41547                    2 => CompressionLevel.SmallestSize,
 81548                    4 => CompressionLevel.Fastest,
 21549                    6 => CompressionLevel.Fastest,
 01550                    _ => CompressionLevel.Optimal
 621551                };
 1552            }
 1553            else
 18481554            {
 18481555                return CompressionLevel.NoCompression;
 1556            }
 19101557        }
 1558
 1559        private static BitFlagValues MapDeflateCompressionOption(BitFlagValues generalPurposeBitFlag, CompressionLevel c
 01560        {
 01561            ushort deflateCompressionOptions = (ushort)(
 01562                // The Deflate compression level is only valid if the compression method is actually Deflate (or Deflate
 01563                // value of the two bits is undefined and they should be zeroed out.
 01564                compressionMethod == ZipCompressionMethod.Deflate || compressionMethod == ZipCompressionMethod.Deflate64
 01565                    ? compressionLevel switch
 01566                    {
 01567                        CompressionLevel.Optimal => 0,
 01568                        CompressionLevel.SmallestSize => 2,
 01569                        CompressionLevel.Fastest => 6,
 01570                        CompressionLevel.NoCompression => 6,
 01571                        _ => 0
 01572                    }
 01573                    : 0);
 1574
 01575            return (BitFlagValues)(((int)generalPurposeBitFlag & ~0x6) | deflateCompressionOptions);
 01576        }
 1577
 01578        private bool IsOffsetTooLarge => _offsetOfLocalHeader > uint.MaxValue;
 1579
 01580        private bool ShouldUseZIP64 => AreSizesTooLarge || IsOffsetTooLarge;
 11141581        internal ZipEncryptionMethod Encryption { get => _encryptionMethod; private set => _encryptionMethod = value; }
 1582
 1583        private unsafe bool WriteLocalFileHeaderInitialize(bool isEmptyFile, bool forceWrite, bool preserveDataDescripto
 01584        {
 1585            // _entryname only gets set when we read in or call moveTo. MoveTo does a check, and
 1586            // reading in should not be able to produce an entryname longer than ushort.MaxValue
 01587            Debug.Assert(_storedEntryNameBytes.Length <= ushort.MaxValue);
 1588
 1589            // decide if we need the Zip64 extra field:
 01590            zip64ExtraField = null;
 1591
 1592            // save offset
 01593            _offsetOfLocalHeader = _archive.ArchiveStream.Position;
 1594
 1595            // for extra winzip aes header
 01596            WinZipAesExtraField? aesExtraField = null;
 01597            int aesExtraFieldSize = 0;
 1598
 1599            // if we already know that we have an empty file don't worry about anything, just do a straight shot of the 
 01600            if (isEmptyFile)
 01601            {
 01602                CompressionMethod = ZipCompressionMethod.Stored;
 01603                compressedSizeTruncated = 0;
 01604                uncompressedSizeTruncated = 0;
 01605                Debug.Assert(_uncompressedSize == 0);
 01606                Debug.Assert(_crc32 == 0);
 01607            }
 1608            else
 01609            {
 01610                bool isCreateMode = _archive.Mode == ZipArchiveMode.Create;
 1611
 01612                if (Encryption == ZipEncryptionMethod.ZipCrypto)
 01613                {
 01614                    _generalPurposeBitFlag |= BitFlagValues.IsEncrypted;
 1615                    // If DataDescriptor is used, then ZipCrypto password validation is done against the modification ti
 1616                    // not the CRC (which is not known in DataDescriptor mode). The current implementation always uses D
 1617                    // for ZipCrypto to avoid needing to seek back and update the header with the CRC that is known only
 01618                    _generalPurposeBitFlag |= BitFlagValues.DataDescriptor;
 01619                    compressedSizeTruncated = 0;
 01620                    uncompressedSizeTruncated = 0;
 01621                }
 01622                else if (UseAesEncryption)
 01623                {
 01624                    _generalPurposeBitFlag |= BitFlagValues.IsEncrypted;
 01625                    if (!_archive.ArchiveStream.CanSeek)
 01626                    {
 01627                        _generalPurposeBitFlag |= BitFlagValues.DataDescriptor;
 01628                    }
 01629                    CompressionMethod = (ZipCompressionMethod)WinZipAesMethod;
 01630                    compressedSizeTruncated = 0;
 01631                    uncompressedSizeTruncated = 0;
 01632                    aesExtraField = CreateAesExtraField();
 01633                    aesExtraFieldSize = WinZipAesExtraField.TotalSize;
 01634                }
 01635                else if (isCreateMode && !_archive.ArchiveStream.CanSeek)
 01636                {
 01637                    _generalPurposeBitFlag |= BitFlagValues.DataDescriptor;
 01638                    compressedSizeTruncated = 0;
 01639                    uncompressedSizeTruncated = 0;
 01640                    Debug.Assert(_crc32 == 0);
 01641                }
 1642                else
 01643                {
 1644
 01645                    if (ShouldUseZIP64
 01646#if DEBUG_FORCE_ZIP64
 01647                || (_archive._forceZip64 && _archive.Mode == ZipArchiveMode.Update)
 01648#endif
 01649                        )
 01650                    {
 01651                        compressedSizeTruncated = ZipHelper.Mask32Bit;
 01652                        uncompressedSizeTruncated = ZipHelper.Mask32Bit;
 1653
 01654                        zip64ExtraField = new()
 01655                        {
 01656                            CompressedSize = _compressedSize,
 01657                            UncompressedSize = _uncompressedSize,
 01658                        };
 1659
 01660                        VersionToExtractAtLeast(ZipVersionNeededValues.Zip64);
 01661                    }
 1662                    else
 01663                    {
 01664                        compressedSizeTruncated = (uint)_compressedSize;
 01665                        uncompressedSizeTruncated = (uint)_uncompressedSize;
 01666                    }
 01667                }
 01668            }
 1669
 1670            // save offset
 01671            _offsetOfLocalHeader = _archive.ArchiveStream.Position;
 1672
 1673            // Calculate extra field
 1674            // When using AES encryption, exclude the AES tag from currExtraFieldDataLength since we're writing a new on
 01675            int currExtraFieldDataLength = UseAesEncryption
 01676                ? ZipGenericExtraField.TotalSizeExcludingTag(_lhUnknownExtraFields, _lhTrailingExtraFieldData?.Length ??
 01677                : ZipGenericExtraField.TotalSize(_lhUnknownExtraFields, _lhTrailingExtraFieldData?.Length ?? 0);
 01678            int bigExtraFieldLength = (zip64ExtraField != null ? zip64ExtraField.TotalSize : 0)
 01679                                      + aesExtraFieldSize
 01680                                      + currExtraFieldDataLength;
 1681
 01682            if (bigExtraFieldLength > ushort.MaxValue)
 01683            {
 01684                extraFieldLength = (ushort)((zip64ExtraField != null ? zip64ExtraField.TotalSize : 0) + aesExtraFieldSiz
 01685                _lhUnknownExtraFields = null;
 01686            }
 1687            else
 01688            {
 01689                extraFieldLength = (ushort)bigExtraFieldLength;
 01690            }
 1691
 01692            crc32ToWrite = _crc32;
 1693
 1694            // For AE-2, CRC is always 0 in the local file header
 01695            if (UseAesEncryption)
 01696            {
 01697                crc32ToWrite = 0;
 01698            }
 1699
 1700            // If this is an existing, unchanged entry then silently skip forwards.
 1701            // If it's new or changed, write the header.
 01702            if (_originallyInArchive && Changes == ZipArchive.ChangeState.Unchanged && !forceWrite)
 01703            {
 01704                _archive.ArchiveStream.Seek(ZipLocalFileHeader.SizeOfLocalHeader + _storedEntryNameBytes.Length + extraF
 1705
 1706
 1707
 01708                return false;
 1709            }
 1710
 1711            // We are writing the header. For seekable/empty-file paths the sizes are written
 1712            // directly into the header, so a data descriptor is not needed.
 01713            if (isEmptyFile || _archive.ArchiveStream.CanSeek)
 01714            {
 01715                if (preserveDataDescriptor)
 01716                {
 01717                    compressedSizeTruncated = 0;
 01718                    uncompressedSizeTruncated = 0;
 1719
 1720                    // zero the CRC/sizes since the real values live in the trailing descriptor that remains on disk.
 01721                    crc32ToWrite = 0;
 1722
 01723                    if (zip64ExtraField is not null)
 01724                    {
 01725                        zip64ExtraField = new() { CompressedSize = 0, UncompressedSize = 0 };
 01726                    }
 01727                }
 01728                else if (Encryption != ZipEncryptionMethod.ZipCrypto)
 01729                {
 01730                    _generalPurposeBitFlag &= ~BitFlagValues.DataDescriptor;
 01731                }
 01732            }
 1733
 01734            return true;
 01735        }
 1736
 1737        private void WriteLocalFileHeaderPrepare(Span<byte> lfStaticHeader, uint crc32, uint compressedSizeTruncated, ui
 01738        {
 01739            ZipLocalFileHeader.SignatureConstantBytes.CopyTo(lfStaticHeader[ZipLocalFileHeader.FieldLocations.Signature.
 01740            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.VersionNeededToExt
 01741            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.GeneralPurposeBitF
 1742
 1743            // For AES encryption, write compression method 99 (Aes) in the header
 01744            ushort compressionMethodToWrite = UseAesEncryption ? (ushort)WinZipAesMethod : (ushort)CompressionMethod;
 01745            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.CompressionMethod.
 1746
 01747            BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.LastModified..], Z
 01748            BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.Crc32..], crc32);
 01749            BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.CompressedSize..],
 01750            BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.UncompressedSize..
 01751            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.FilenameLength..],
 01752            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.ExtraFieldLength..
 01753        }
 1754
 1755        // return value is true if we allocated an extra field for 64 bit headers, un/compressed size
 1756        private unsafe bool WriteLocalFileHeader(bool isEmptyFile, bool forceWrite, bool preserveDataDescriptor = false)
 01757        {
 01758            if (WriteLocalFileHeaderInitialize(isEmptyFile, forceWrite, preserveDataDescriptor, out Zip64ExtraField? zip
 01759            {
 01760                Span<byte> lfStaticHeader = stackalloc byte[ZipLocalFileHeader.SizeOfLocalHeader];
 01761                WriteLocalFileHeaderPrepare(lfStaticHeader, crc32ToWrite, compressedSizeTruncated, uncompressedSizeTrunc
 1762
 1763                // write header
 01764                _archive.ArchiveStream.Write(lfStaticHeader);
 01765                _archive.ArchiveStream.Write(_storedEntryNameBytes);
 1766
 1767                // Write Zip64 extra field if needed
 01768                zip64ExtraField?.WriteBlock(_archive.ArchiveStream);
 1769
 1770                // Write AES extra field if using AES encryption
 01771                if (UseAesEncryption)
 01772                {
 01773                    CreateAesExtraField().WriteBlock(_archive.ArchiveStream);
 1774
 1775                    // Write other extra fields, excluding any existing AES extra field to avoid duplication
 01776                    ZipGenericExtraField.WriteAllBlocksExcludingTag(_lhUnknownExtraFields, _lhTrailingExtraFieldData ?? 
 01777                }
 1778                else
 01779                {
 1780                    // Write other extra fields
 01781                    ZipGenericExtraField.WriteAllBlocks(_lhUnknownExtraFields, _lhTrailingExtraFieldData ?? Array.Empty<
 01782                }
 01783            }
 1784
 01785            return zip64ExtraField != null;
 01786        }
 1787
 1788        private unsafe void WriteLocalFileHeaderAndDataIfNeeded(bool forceWrite)
 01789        {
 1790            // Check if the entry's stored data was actually modified (StoredData flag is set).
 1791            // If _storedUncompressedData is loaded but StoredData is not set, it means the entry
 1792            // was opened for update but no writes occurred - we should use the original compressed bytes.
 01793            bool storedDataModified = (Changes & ZipArchive.ChangeState.StoredData) != 0;
 1794
 1795            // If _storedUncompressedData is loaded but not modified, clear it so we use _compressedBytes
 01796            if (_storedUncompressedData != null && !storedDataModified)
 01797            {
 01798                _storedUncompressedData.Dispose();
 01799                _storedUncompressedData = null;
 01800            }
 1801
 1802            // _storedUncompressedData gets frozen here, and is what gets written to the file
 01803            if (_storedUncompressedData != null || _compressedBytes != null)
 01804            {
 01805                if (_storedUncompressedData != null)
 01806                {
 01807                    _uncompressedSize = _storedUncompressedData.Length;
 1808
 1809                    // Check if we need to re-encrypt with ZipCrypto (only if we have cached key material)
 01810                    if (Encryption == ZipEncryptionMethod.ZipCrypto && _derivedZipCryptoKeyMaterial is not null)
 01811                    {
 01812                        WriteLocalFileHeader(isEmptyFile: false, forceWrite: true);
 1813
 01814                        long startPosition = _archive.ArchiveStream.Position;
 1815
 01816                        ushort verifierLow2Bytes = (ushort)ZipHelper.DateTimeToDosTime(_lastModified.DateTime);
 1817
 01818                        using (Stream encryptionStream = ZipCryptoStream.Create(
 01819                            baseStream: _archive.ArchiveStream,
 01820                            keys: _derivedZipCryptoKeyMaterial.Value,
 01821                            passwordVerifierLow2Bytes: verifierLow2Bytes,
 01822                            encrypting: true,
 01823                            crc32: null,
 01824                            leaveOpen: true))
 01825                        {
 01826                            using (CheckSumAndSizeWriteStream crcStream = GetDataCompressor(encryptionStream, leaveBacki
 01827                            {
 01828                                _storedUncompressedData.Seek(0, SeekOrigin.Begin);
 01829                                _storedUncompressedData.CopyTo(crcStream);
 01830                            }
 01831                        }
 1832
 01833                        _compressedSize = _archive.ArchiveStream.Position - startPosition;
 1834
 01835                        WriteDataDescriptor();
 1836
 01837                        _storedUncompressedData.Dispose();
 01838                        _storedUncompressedData = null;
 01839                    }
 01840                    else if (UseAesEncryption && _derivedAesKeyMaterial is not null)
 01841                    {
 1842
 01843                        bool usedZip64InLH = WriteLocalFileHeader(isEmptyFile: false, forceWrite: true);
 1844
 01845                        long startPosition = _archive.ArchiveStream.Position;
 1846
 01847                        bool useDeflate = _compressionLevel != CompressionLevel.NoCompression;
 1848
 01849                        using (Stream encryptionStream = WinZipAesStream.Create(
 01850                            baseStream: _archive.ArchiveStream,
 01851                            keyMaterial: _derivedAesKeyMaterial.Value,
 01852                            totalStreamSize: -1,
 01853                            encrypting: true,
 01854                            leaveOpen: true))
 01855                        {
 01856                            if (_storedUncompressedData.Length > 0)
 01857                            {
 01858                                ZipCompressionMethod savedMethod = CompressionMethod;
 01859                                CompressionMethod = useDeflate ? ZipCompressionMethod.Deflate : ZipCompressionMethod.Sto
 1860
 1861                                try
 01862                                {
 01863                                    using (CheckSumAndSizeWriteStream crcStream = GetDataCompressor(encryptionStream, le
 01864                                    {
 01865                                        _storedUncompressedData.Seek(0, SeekOrigin.Begin);
 01866                                        _storedUncompressedData.CopyTo(crcStream);
 01867                                    }
 01868                                }
 1869                                finally
 01870                                {
 01871                                    CompressionMethod = (ZipCompressionMethod)WinZipAesMethod;
 01872                                }
 01873                            }
 1874                            else
 01875                            {
 01876                                _crc32 = 0;
 01877                                _uncompressedSize = 0;
 01878                            }
 01879                        }
 1880
 01881                        _compressedSize = _archive.ArchiveStream.Position - startPosition;
 1882
 01883                        WriteCrcAndSizesInLocalHeader(usedZip64InLH);
 1884
 01885                        _storedUncompressedData.Dispose();
 01886                        _storedUncompressedData = null;
 01887                    }
 1888                    else
 01889                    {
 1890                        // Non-encrypted: use standard path
 01891                        using (DirectToArchiveWriterStream entryWriter = new(GetDataCompressor(_archive.ArchiveStream, t
 01892                        {
 01893                            _storedUncompressedData.Seek(0, SeekOrigin.Begin);
 01894                            _storedUncompressedData.CopyTo(entryWriter);
 01895                        }
 01896                        _storedUncompressedData.Dispose();
 01897                        _storedUncompressedData = null;
 01898                    }
 01899                }
 1900                else // _compressedBytes path - copying unchanged entry data
 01901                {
 01902                    bool emptyEncryptedEntry = _uncompressedSize == 0 && Encryption != ZipEncryptionMethod.None;
 01903                    if (_uncompressedSize == 0 && !emptyEncryptedEntry)
 01904                    {
 1905                        // reset size to ensure proper central directory size header
 01906                        _compressedSize = 0;
 01907                    }
 1908
 1909                    // For unchanged entries, we need to write the header correctly but avoid
 1910                    // WriteLocalFileHeader creating NEW encryption structures (which would have
 1911                    // wrong compression method from _compressionLevel).
 1912                    // The original AES extra field is preserved in _lhUnknownExtraFields.
 01913                    BitFlagValues savedFlags = _generalPurposeBitFlag;
 01914                    ZipEncryptionMethod savedEncryption = Encryption;
 01915                    ZipCompressionMethod savedCompressionMethod = CompressionMethod;
 1916
 1917                    try
 01918                    {
 1919                        // For AES entries: set CompressionMethod to Aes so header writes method 99,
 1920                        // but clear _encryptionMethod so WriteLocalFileHeader doesn't create a new
 1921                        // AES extra field (the original one in _lhUnknownExtraFields will be used).
 01922                        if (savedEncryption is ZipEncryptionMethod.Aes128 or ZipEncryptionMethod.Aes192 or ZipEncryption
 01923                        {
 01924                            CompressionMethod = (ZipCompressionMethod)WinZipAesMethod;
 01925                            Encryption = ZipEncryptionMethod.None;
 01926                        }
 1927
 01928                        WriteLocalFileHeader(isEmptyFile: _uncompressedSize == 0 && !emptyEncryptedEntry, forceWrite: tr
 1929
 1930                        // WriteLocalFileHeaderInitialize may have cleared the DataDescriptor flag
 1931                        // (because Encryption was temporarily set to None and the stream is seekable).
 1932                        // If the original entry had a data descriptor, patch the general-purpose bit
 1933                        // flags in the already-written local header to match, so the header on disk
 1934                        // is consistent with the data descriptor we conditionally write below.
 01935                        if ((savedFlags & BitFlagValues.DataDescriptor) != 0 &&
 01936                            (_generalPurposeBitFlag & BitFlagValues.DataDescriptor) == 0)
 01937                        {
 01938                            long currentPos = _archive.ArchiveStream.Position;
 01939                            _archive.ArchiveStream.Seek(
 01940                                _offsetOfLocalHeader + ZipLocalFileHeader.FieldLocations.GeneralPurposeBitFlags,
 01941                                SeekOrigin.Begin);
 01942                            Span<byte> flagBytes = stackalloc byte[2];
 01943                            BinaryPrimitives.WriteUInt16LittleEndian(flagBytes, (ushort)savedFlags);
 01944                            _archive.ArchiveStream.Write(flagBytes);
 01945                            _archive.ArchiveStream.Seek(currentPos, SeekOrigin.Begin);
 01946                        }
 01947                    }
 1948                    finally
 01949                    {
 1950                        // Restore original state
 01951                        _generalPurposeBitFlag = savedFlags;
 01952                        Encryption = savedEncryption;
 01953                        CompressionMethod = savedCompressionMethod;
 01954                    }
 1955
 1956                    // according to ZIP specs, zero-byte unencrypted files MUST NOT include file data
 01957                    if (_uncompressedSize != 0 || emptyEncryptedEntry)
 01958                    {
 01959                        Debug.Assert(_compressedBytes != null);
 01960                        foreach (byte[] compressedBytes in _compressedBytes)
 01961                        {
 01962                            _archive.ArchiveStream.Write(compressedBytes, 0, compressedBytes.Length);
 01963                        }
 01964                    }
 1965
 1966                    // Write data descriptor if the original entry had one
 01967                    if ((savedFlags & BitFlagValues.DataDescriptor) != 0)
 01968                    {
 01969                        WriteDataDescriptor();
 01970                    }
 01971                }
 01972            }
 1973            else // there is no data in the file (or the data in the file has not been loaded), but if we are in update 
 01974            {
 01975                if (_archive.Mode == ZipArchiveMode.Update || !_everOpenedForWrite)
 01976                {
 01977                    _everOpenedForWrite = true;
 1978                    // Preserve the data descriptor flag for entries that originally had one,
 1979                    // since the descriptor bytes remain on disk after the compressed data.
 01980                    bool preserveDataDescriptor = _originallyInArchive
 01981                        && (_generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0;
 01982                    WriteLocalFileHeader(isEmptyFile: _uncompressedSize == 0, forceWrite: forceWrite, preserveDataDescri
 1983
 1984                    // Advance the stream past the compressed data and any trailing data descriptor
 1985                    // by seeking to the pre-computed end-of-entry boundary.
 01986                    if (_endOfLocalEntryData > _archive.ArchiveStream.Position)
 01987                    {
 01988                        _archive.ArchiveStream.Seek(_endOfLocalEntryData, SeekOrigin.Begin);
 01989                    }
 01990                }
 01991            }
 01992        }
 1993
 1994        private const int MetadataBufferLength = ZipLocalFileHeader.FieldLengths.VersionNeededToExtract + ZipLocalFileHe
 1995        private const int CrcAndSizesBufferLength = ZipLocalFileHeader.FieldLengths.Crc32 + ZipLocalFileHeader.FieldLeng
 1996        private const int Zip64SizesBufferLength = Zip64ExtraField.FieldLengths.UncompressedSize + Zip64ExtraField.Field
 1997        private const int Zip64DataDescriptorCrcAndSizesBufferLength = ZipLocalFileHeader.Zip64DataDescriptor.FieldLengt
 1998            + ZipLocalFileHeader.Zip64DataDescriptor.FieldLengths.CompressedSize + ZipLocalFileHeader.Zip64DataDescripto
 1999
 2000        // Using _offsetOfLocalHeader, seeks back to where CRC and sizes should be in the header,
 2001        // writes them, then seeks back to where you started
 2002        // Assumes that the stream is currently at the end of the data
 2003        private unsafe void WriteCrcAndSizesInLocalHeader(bool zip64HeaderUsed)
 02004        {
 2005            // Buffer has been sized to the largest data payload required: the 64-bit data descriptor.
 02006            Span<byte> writeBuffer = stackalloc byte[Zip64DataDescriptorCrcAndSizesBufferLength];
 2007
 02008            WriteCrcAndSizesInLocalHeaderInitialize(zip64HeaderUsed, out long finalPosition, out bool pretendStreaming, 
 2009
 2010            // first step is, if we need zip64, but didn't allocate it, pretend we did a stream write, because
 2011            // we can't go back and give ourselves the space that the extra field needs.
 2012            // we do this by setting the correct property in the bit flag to indicate we have a data descriptor
 2013            // and setting the version to Zip64 to indicate that descriptor contains 64-bit values
 02014            if (pretendStreaming)
 02015            {
 02016                WriteCrcAndSizesInLocalHeaderPrepareForZip64PretendStreaming(writeBuffer);
 02017                _archive.ArchiveStream.Write(writeBuffer[..MetadataBufferLength]);
 02018            }
 2019
 2020            // next step is fill out the 32-bit size values in the normal header. we can't assume that
 2021            // they are correct. we also write the CRC
 02022            WriteCrcAndSizesInLocalHeaderPrepareFor32bitValuesWriting(pretendStreaming, writeBuffer, compressedSizeTrunc
 02023            _archive.ArchiveStream.Write(writeBuffer[..CrcAndSizesBufferLength]);
 2024
 2025            // next step: if we wrote the 64 bit header initially, a different implementation might
 2026            // try to read it, even if the 32-bit size values aren't masked. thus, we should always put the
 2027            // correct size information in there. note that order of uncomp/comp is switched, and these are
 2028            // 64-bit values
 2029            // also, note that in order for this to be correct, we have to ensure that the zip64 extra field
 2030            // is always the first extra field that is written
 02031            if (zip64HeaderUsed)
 02032            {
 02033                WriteCrcAndSizesInLocalHeaderPrepareForWritingWhenZip64HeaderUsed(writeBuffer);
 02034                _archive.ArchiveStream.Write(writeBuffer[..Zip64SizesBufferLength]);
 02035            }
 2036
 2037            // now go to the where we were. assume that this is the end of the data
 02038            _archive.ArchiveStream.Seek(finalPosition, SeekOrigin.Begin);
 2039
 2040            // if we are pretending we did a stream write, we want to write the data descriptor out
 2041            // the data descriptor can have 32-bit sizes or 64-bit sizes. In this case, we always use
 2042            // 64-bit sizes
 02043            if (pretendStreaming)
 02044            {
 02045                WriteCrcAndSizesInLocalHeaderPrepareForWritingDataDescriptor(writeBuffer);
 02046                _archive.ArchiveStream.Write(writeBuffer[..Zip64DataDescriptorCrcAndSizesBufferLength]);
 02047            }
 02048        }
 2049
 2050        private void WriteCrcAndSizesInLocalHeaderInitialize(bool zip64HeaderUsed, out long finalPosition, out bool pret
 02051        {
 02052            finalPosition = _archive.ArchiveStream.Position;
 2053
 02054            bool zip64Needed = ShouldUseZIP64
 02055#if DEBUG_FORCE_ZIP64
 02056                || _archive._forceZip64
 02057#endif
 02058            ;
 2059
 02060            pretendStreaming = zip64Needed && !zip64HeaderUsed;
 02061            compressedSizeTruncated = zip64Needed ? ZipHelper.Mask32Bit : (uint)_compressedSize;
 02062            uncompressedSizeTruncated = zip64Needed ? ZipHelper.Mask32Bit : (uint)_uncompressedSize;
 02063        }
 2064
 2065        private void WriteCrcAndSizesInLocalHeaderPrepareForZip64PretendStreaming(Span<byte> writeBuffer)
 02066        {
 02067            int relativeVersionToExtractLocation = ZipLocalFileHeader.FieldLocations.VersionNeededToExtract - ZipLocalFi
 02068            int relativeGeneralPurposeBitFlagsLocation = ZipLocalFileHeader.FieldLocations.GeneralPurposeBitFlags - ZipL
 2069
 02070            VersionToExtractAtLeast(ZipVersionNeededValues.Zip64);
 02071            _generalPurposeBitFlag |= BitFlagValues.DataDescriptor;
 2072
 02073            _archive.ArchiveStream.Seek(_offsetOfLocalHeader + ZipLocalFileHeader.FieldLocations.VersionNeededToExtract,
 02074                                        SeekOrigin.Begin);
 02075            BinaryPrimitives.WriteUInt16LittleEndian(writeBuffer[relativeVersionToExtractLocation..], (ushort)_versionTo
 02076            BinaryPrimitives.WriteUInt16LittleEndian(writeBuffer[relativeGeneralPurposeBitFlagsLocation..], (ushort)_gen
 02077        }
 2078
 2079        private void WriteCrcAndSizesInLocalHeaderPrepareFor32bitValuesWriting(bool pretendStreaming, Span<byte> writeBu
 02080        {
 02081            _archive.ArchiveStream.Seek(_offsetOfLocalHeader + ZipLocalFileHeader.FieldLocations.Crc32,
 02082                                            SeekOrigin.Begin);
 02083            if (!pretendStreaming)
 02084            {
 02085                int relativeCrc32Location = ZipLocalFileHeader.FieldLocations.Crc32 - ZipLocalFileHeader.FieldLocations.
 02086                int relativeCompressedSizeLocation = ZipLocalFileHeader.FieldLocations.CompressedSize - ZipLocalFileHead
 02087                int relativeUncompressedSizeLocation = ZipLocalFileHeader.FieldLocations.UncompressedSize - ZipLocalFile
 2088                // when using aes encryption, ae-2 standard dictates crc to be 0
 02089                uint crcToWrite = UseAesEncryption ? 0 : _crc32;
 02090                BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer[relativeCrc32Location..], crcToWrite);
 02091                BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer[relativeCompressedSizeLocation..], compressedSizeTr
 02092                BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer[relativeUncompressedSizeLocation..], uncompressedSi
 02093            }
 2094            else // but if we are pretending to stream, we want to fill in with zeroes
 02095            {
 02096                writeBuffer[..CrcAndSizesBufferLength].Clear();
 02097            }
 02098        }
 2099
 2100        private void WriteCrcAndSizesInLocalHeaderPrepareForWritingWhenZip64HeaderUsed(Span<byte> writeBuffer)
 02101        {
 02102            int relativeUncompressedSizeLocation = Zip64ExtraField.FieldLocations.UncompressedSize - Zip64ExtraField.Fie
 02103            int relativeCompressedSizeLocation = Zip64ExtraField.FieldLocations.CompressedSize - Zip64ExtraField.FieldLo
 2104
 02105            _archive.ArchiveStream.Seek(_offsetOfLocalHeader + ZipLocalFileHeader.SizeOfLocalHeader
 02106                                        + _storedEntryNameBytes.Length + Zip64ExtraField.OffsetToFirstField,
 02107                                        SeekOrigin.Begin);
 02108            BinaryPrimitives.WriteInt64LittleEndian(writeBuffer[relativeUncompressedSizeLocation..], _uncompressedSize);
 02109            BinaryPrimitives.WriteInt64LittleEndian(writeBuffer[relativeCompressedSizeLocation..], _compressedSize);
 02110        }
 2111
 2112        private void WriteCrcAndSizesInLocalHeaderPrepareForWritingDataDescriptor(Span<byte> writeBuffer)
 02113        {
 02114            int relativeCrc32Location = ZipLocalFileHeader.Zip64DataDescriptor.FieldLocations.Crc32 - ZipLocalFileHeader
 02115            int relativeCompressedSizeLocation = ZipLocalFileHeader.Zip64DataDescriptor.FieldLocations.CompressedSize - 
 02116            int relativeUncompressedSizeLocation = ZipLocalFileHeader.Zip64DataDescriptor.FieldLocations.UncompressedSiz
 2117            // when using aes encryption, ae-2 standard dictates crc to be 0
 02118            uint crcToWrite = UseAesEncryption ? 0 : _crc32;
 02119            BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer.Slice(relativeCrc32Location), crcToWrite);
 02120            BinaryPrimitives.WriteInt64LittleEndian(writeBuffer.Slice(relativeCompressedSizeLocation), _compressedSize);
 02121            BinaryPrimitives.WriteInt64LittleEndian(writeBuffer.Slice(relativeUncompressedSizeLocation), _uncompressedSi
 2122
 02123        }
 2124
 2125        // data descriptor can be 32-bit or 64-bit sizes. 32-bit is more compatible, so use that if possible
 2126        // signature is optional but recommended by the spec
 2127        private const int MaxSizeOfDataDescriptor = 24;
 2128
 2129        private unsafe void WriteDataDescriptor()
 02130        {
 02131            Span<byte> dataDescriptor = stackalloc byte[MaxSizeOfDataDescriptor];
 02132            int bytesToWrite = PrepareToWriteDataDescriptor(dataDescriptor);
 02133            _archive.ArchiveStream.Write(dataDescriptor[..bytesToWrite]);
 02134        }
 2135
 2136        private int PrepareToWriteDataDescriptor(Span<byte> dataDescriptor)
 02137        {
 2138            // We enter here because we cannot seek, so the data descriptor bit should be on
 02139            Debug.Assert((_generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0);
 2140
 2141            int bytesToWrite;
 2142
 02143            ZipLocalFileHeader.DataDescriptorSignatureConstantBytes.CopyTo(dataDescriptor[ZipLocalFileHeader.ZipDataDesc
 2144            // when using aes encryption, ae-2 standard dictates crc to be 0
 02145            uint crcToWrite = UseAesEncryption ? 0 : _crc32;
 02146            BinaryPrimitives.WriteUInt32LittleEndian(dataDescriptor[ZipLocalFileHeader.ZipDataDescriptor.FieldLocations.
 2147
 02148            if (AreSizesTooLarge)
 02149            {
 02150                BinaryPrimitives.WriteInt64LittleEndian(dataDescriptor[ZipLocalFileHeader.Zip64DataDescriptor.FieldLocat
 02151                BinaryPrimitives.WriteInt64LittleEndian(dataDescriptor[ZipLocalFileHeader.Zip64DataDescriptor.FieldLocat
 2152
 02153                bytesToWrite = ZipLocalFileHeader.Zip64DataDescriptor.FieldLocations.UncompressedSize + ZipLocalFileHead
 02154            }
 2155            else
 02156            {
 02157                BinaryPrimitives.WriteUInt32LittleEndian(dataDescriptor[ZipLocalFileHeader.ZipDataDescriptor.FieldLocati
 02158                BinaryPrimitives.WriteUInt32LittleEndian(dataDescriptor[ZipLocalFileHeader.ZipDataDescriptor.FieldLocati
 2159
 02160                bytesToWrite = ZipLocalFileHeader.ZipDataDescriptor.FieldLocations.UncompressedSize + ZipLocalFileHeader
 02161            }
 2162
 02163            return bytesToWrite;
 02164        }
 2165
 2166        internal void UnloadStreams()
 02167        {
 02168            _storedUncompressedData?.Dispose();
 02169            _compressedBytes = null;
 02170            _outstandingWriteStream = null;
 02171        }
 2172
 2173        private void CloseStreams()
 02174        {
 2175            // if the user left the stream open, close the underlying stream for them
 02176            _outstandingWriteStream?.Dispose();
 02177        }
 2178
 2179        private void VersionToExtractAtLeast(ZipVersionNeededValues value)
 4702180        {
 4702181            if (_versionToExtract < value)
 2562182            {
 2562183                _versionToExtract = value;
 2562184                Changes |= ZipArchive.ChangeState.FixedLengthMetadata;
 2562185            }
 4702186            if (_versionMadeBySpecification < value)
 302187            {
 302188                _versionMadeBySpecification = value;
 302189                Changes |= ZipArchive.ChangeState.FixedLengthMetadata;
 302190            }
 4702191        }
 2192
 2193        private void ThrowIfInvalidArchive()
 02194        {
 02195            if (_archive == null)
 02196            {
 02197                throw new InvalidOperationException(SR.DeletedEntry);
 2198            }
 02199            _archive.ThrowIfDisposed();
 02200        }
 2201
 2202        /// <summary>
 2203        /// Gets the file name of the path based on Windows path separator characters
 2204        /// </summary>
 2205        private static string GetFileName_Windows(string path)
 17642206        {
 17642207            int i = path.AsSpan().LastIndexOfAny('\\', '/', ':');
 17642208            return i >= 0 ?
 17642209                path.Substring(i + 1) :
 17642210                path;
 17642211        }
 2212
 2213        /// <summary>
 2214        /// Gets the file name of the path based on Unix path separator characters
 2215        /// </summary>
 2216        private static string GetFileName_Unix(string path)
 1462217        {
 1462218            int i = path.LastIndexOf('/');
 1462219            return i >= 0 ?
 1462220                path.Substring(i + 1) :
 1462221                path;
 1462222        }
 2223
 2224        private sealed class DirectToArchiveWriterStream : Stream
 2225        {
 2226            private long _position;
 2227            private readonly CheckSumAndSizeWriteStream _crcSizeStream;
 2228            private bool _everWritten;
 2229            private bool _isDisposed;
 2230            private readonly ZipArchiveEntry _entry;
 2231            private bool _usedZip64inLH;
 2232            private bool _canWrite;
 2233            private readonly ZipEncryptionMethod _encryption;
 2234            private readonly Stream? _encryptionStream;
 2235
 2236            // makes the assumption that somewhere down the line, crcSizeStream is eventually writing directly to the ar
 2237            // this class calls other functions on ZipArchiveEntry that write directly to the archive
 02238            public DirectToArchiveWriterStream(CheckSumAndSizeWriteStream crcSizeStream, ZipArchiveEntry entry, ZipEncry
 02239            {
 02240                _position = 0;
 02241                _crcSizeStream = crcSizeStream;
 02242                _everWritten = false;
 02243                _isDisposed = false;
 02244                _entry = entry;
 02245                _usedZip64inLH = false;
 02246                _canWrite = true;
 02247                _encryption = encryptionMethod;
 02248                _encryptionStream = encryptionStream;
 02249            }
 2250
 2251            public override long Length
 2252            {
 2253                get
 02254                {
 02255                    ThrowIfDisposed();
 02256                    throw new NotSupportedException(SR.SeekingNotSupported);
 2257                }
 2258            }
 2259            public override long Position
 2260            {
 2261                get
 02262                {
 02263                    ThrowIfDisposed();
 02264                    return _position;
 02265                }
 2266                set
 02267                {
 02268                    ThrowIfDisposed();
 02269                    throw new NotSupportedException(SR.SeekingNotSupported);
 2270                }
 2271            }
 2272
 02273            public override bool CanRead => false;
 02274            public override bool CanSeek => false;
 02275            public override bool CanWrite => _canWrite;
 2276
 2277            private void ThrowIfDisposed()
 02278            {
 02279                if (_isDisposed)
 02280                {
 02281                    throw new ObjectDisposedException(GetType().ToString(), SR.HiddenStreamName);
 2282                }
 02283            }
 2284
 2285            public override int Read(byte[] buffer, int offset, int count)
 02286            {
 02287                ThrowIfDisposed();
 02288                throw new NotSupportedException(SR.ReadingNotSupported);
 2289            }
 2290
 2291            public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToke
 02292            {
 02293                ThrowIfDisposed();
 02294                throw new NotSupportedException(SR.ReadingNotSupported);
 2295            }
 2296
 2297            public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
 02298            {
 02299                ThrowIfDisposed();
 02300                throw new NotSupportedException(SR.ReadingNotSupported);
 2301            }
 2302
 2303            public override long Seek(long offset, SeekOrigin origin)
 02304            {
 02305                ThrowIfDisposed();
 02306                throw new NotSupportedException(SR.SeekingNotSupported);
 2307            }
 2308
 2309            public override void SetLength(long value)
 02310            {
 02311                ThrowIfDisposed();
 02312                throw new NotSupportedException(SR.SetLengthRequiresSeekingAndWriting);
 2313            }
 2314
 2315            // careful: assumes that write is the only way to write to the stream, if writebyte/beginwrite are implement
 2316            // they must set _everWritten, etc.
 2317            public override void Write(byte[] buffer, int offset, int count)
 02318            {
 02319                ValidateBufferArguments(buffer, offset, count);
 2320
 02321                ThrowIfDisposed();
 02322                Debug.Assert(CanWrite);
 2323
 2324                // if we're not actually writing anything, we don't want to trigger the header
 02325                if (count == 0)
 02326                {
 02327                    return;
 2328                }
 2329
 02330                if (!_everWritten)
 02331                {
 02332                    _everWritten = true;
 2333                    // write local header, we are good to go
 02334                    _usedZip64inLH = _entry.WriteLocalFileHeader(isEmptyFile: false, forceWrite: true);
 02335                }
 2336
 02337                _crcSizeStream.Write(buffer, offset, count);
 02338                _position += count;
 02339            }
 2340
 2341            public override void Write(ReadOnlySpan<byte> source)
 02342            {
 02343                ThrowIfDisposed();
 02344                Debug.Assert(CanWrite);
 2345
 2346                // if we're not actually writing anything, we don't want to trigger the header
 02347                if (source.Length == 0)
 02348                {
 02349                    return;
 2350                }
 2351
 02352                if (!_everWritten)
 02353                {
 02354                    _everWritten = true;
 2355                    // write local header, we are good to go
 02356                    _usedZip64inLH = _entry.WriteLocalFileHeader(isEmptyFile: false, forceWrite: true);
 02357                }
 2358
 02359                _crcSizeStream.Write(source);
 02360                _position += source.Length;
 02361            }
 2362
 2363            public override void WriteByte(byte value) =>
 02364                Write(new ReadOnlySpan<byte>(in value));
 2365
 2366            public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
 02367            {
 02368                ValidateBufferArguments(buffer, offset, count);
 02369                return WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken).AsTask();
 02370            }
 2371
 2372            public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = defa
 02373            {
 02374                ThrowIfDisposed();
 02375                Debug.Assert(CanWrite);
 2376
 02377                return !buffer.IsEmpty ?
 02378                    Core(buffer, cancellationToken) :
 02379                    default;
 2380
 2381                async ValueTask Core(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
 02382                {
 02383                    if (!_everWritten)
 02384                    {
 02385                        _everWritten = true;
 2386                        // write local header, we are good to go
 02387                        _usedZip64inLH = await _entry.WriteLocalFileHeaderAsync(isEmptyFile: false, forceWrite: true, pr
 02388                    }
 2389
 02390                    await _crcSizeStream.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
 02391                    _position += buffer.Length;
 02392                }
 02393            }
 2394
 2395            public override void Flush()
 02396            {
 02397                ThrowIfDisposed();
 02398                Debug.Assert(CanWrite);
 2399
 02400                _crcSizeStream.Flush();
 02401            }
 2402
 2403            public override Task FlushAsync(CancellationToken cancellationToken)
 02404            {
 02405                ThrowIfDisposed();
 02406                Debug.Assert(CanWrite);
 2407
 02408                return _crcSizeStream.FlushAsync(cancellationToken);
 02409            }
 2410
 2411            protected override void Dispose(bool disposing)
 02412            {
 02413                if (disposing && !_isDisposed)
 02414                {
 02415                    _crcSizeStream.Dispose(); // now we have size/crc info
 2416
 02417                    bool finishHeader = _everWritten;
 2418
 02419                    if (!_everWritten)
 02420                    {
 02421                        if (_encryptionStream is null)
 02422                        {
 2423                            // write local header, no data, so we use stored
 02424                            _entry.WriteLocalFileHeader(isEmptyFile: true, forceWrite: true);
 02425                        }
 2426                        else
 02427                        {
 2428                            // No data was written through CheckSumAndSizeWriteStream, but an encrypted entry
 2429                            // still stores its encryption header (and, for AES, the authentication code), so
 2430                            // the local file header must describe an encrypted entry. Write it first, then
 2431                            // dispose the encryption stream (its lazy _baseStream was never created, so it
 2432                            // would otherwise be orphaned) to emit those bytes after the header.
 02433                            _usedZip64inLH = _entry.WriteLocalFileHeader(isEmptyFile: false, forceWrite: true);
 2434
 02435                            long startPosition = _entry._archive.ArchiveStream.Position;
 2436
 02437                            _encryptionStream.Dispose();
 2438
 02439                            _entry._compressedSize = _entry._archive.ArchiveStream.Position - startPosition;
 02440                            finishHeader = true;
 02441                        }
 02442                    }
 2443
 02444                    if (finishHeader)
 02445                    {
 2446                        // go back and finish writing
 02447                        if (_entry._archive.ArchiveStream.CanSeek)
 02448                        {
 2449                            // finish writing local header if we have seek capabilities
 02450                            _entry.WriteCrcAndSizesInLocalHeader(_usedZip64inLH);
 2451
 2452                            // ZipCrypto entries retain DataDescriptor for check byte correctness;
 2453                            // write the trailing descriptor so the archive is consistent.
 02454                            if ((_entry._generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0)
 02455                            {
 02456                                _entry.WriteDataDescriptor();
 02457                            }
 02458                        }
 2459                        else
 2460                            // write out data descriptor if we don't have seek capabilities
 02461                            _entry.WriteDataDescriptor();
 02462                    }
 02463                    _canWrite = false;
 02464                    _isDisposed = true;
 02465                }
 2466
 02467                base.Dispose(disposing);
 02468            }
 2469
 2470            public override async ValueTask DisposeAsync()
 02471            {
 02472                if (!_isDisposed)
 02473                {
 02474                    await _crcSizeStream.DisposeAsync().ConfigureAwait(false); // now we have size/crc info
 2475
 02476                    bool finishHeader = _everWritten;
 2477
 02478                    if (!_everWritten)
 02479                    {
 02480                        if (_encryptionStream is null)
 02481                        {
 2482                            // write local header, no data, so we use stored
 02483                            await _entry.WriteLocalFileHeaderAsync(isEmptyFile: true, forceWrite: true, preserveDataDesc
 02484                        }
 2485                        else
 02486                        {
 2487                            // No data was written through CheckSumAndSizeWriteStream, but an encrypted entry
 2488                            // still stores its encryption header (and, for AES, the authentication code), so
 2489                            // the local file header must describe an encrypted entry. Write it first, then
 2490                            // dispose the encryption stream (its lazy _baseStream was never created, so it
 2491                            // would otherwise be orphaned) to emit those bytes after the header.
 02492                            _usedZip64inLH = await _entry.WriteLocalFileHeaderAsync(isEmptyFile: false, forceWrite: true
 2493
 02494                            long startPosition = _entry._archive.ArchiveStream.Position;
 2495
 02496                            await _encryptionStream.DisposeAsync().ConfigureAwait(false);
 2497
 02498                            _entry._compressedSize = _entry._archive.ArchiveStream.Position - startPosition;
 02499                            finishHeader = true;
 02500                        }
 02501                    }
 2502
 02503                    if (finishHeader)
 02504                    {
 2505                        // go back and finish writing
 02506                        if (_entry._archive.ArchiveStream.CanSeek)
 02507                        {
 2508                            // finish writing local header if we have seek capabilities
 02509                            await _entry.WriteCrcAndSizesInLocalHeaderAsync(_usedZip64inLH, cancellationToken: default).
 2510
 2511                            // ZipCrypto entries retain DataDescriptor for check byte correctness;
 2512                            // write the trailing descriptor so the archive is consistent.
 02513                            if ((_entry._generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0)
 02514                            {
 02515                                await _entry.WriteDataDescriptorAsync(cancellationToken: default).ConfigureAwait(false);
 02516                            }
 02517                        }
 2518                        else
 2519                            // write out data descriptor if we don't have seek capabilities
 02520                            await _entry.WriteDataDescriptorAsync(cancellationToken: default).ConfigureAwait(false);
 02521                    }
 02522                    _canWrite = false;
 02523                    _isDisposed = true;
 02524                }
 2525
 02526                await base.DisposeAsync().ConfigureAwait(false);
 02527            }
 2528        }
 2529        [Flags]
 2530        internal enum BitFlagValues : ushort
 2531        {
 2532            IsEncrypted = 0x1,
 2533            DataDescriptor = 0x8,
 2534            StrongEncryption = 0x40,
 2535            UnicodeFileNameAndComment = 0x800
 2536        }
 2537
 2538        internal sealed class LocalHeaderOffsetComparer : Comparer<ZipArchiveEntry>
 2539        {
 02540            private static readonly LocalHeaderOffsetComparer s_instance = new LocalHeaderOffsetComparer();
 2541
 02542            public static LocalHeaderOffsetComparer Instance => s_instance;
 2543
 2544            // Newly added ZipArchiveEntry records should always go to the end of the file.
 2545            public override int Compare(ZipArchiveEntry? x, ZipArchiveEntry? y)
 02546            {
 02547                long xOffset = x != null && !x.OriginallyInArchive ? long.MaxValue : x?.OffsetOfLocalHeader ?? long.MinV
 02548                long yOffset = y != null && !y.OriginallyInArchive ? long.MaxValue : y?.OffsetOfLocalHeader ?? long.MinV
 2549
 02550                return xOffset.CompareTo(yOffset);
 02551            }
 2552        }
 2553    }
 2554}
 2555

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/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) =>
 191018            madeByPlatform == ZipVersionMadeByPlatform.Unix ? GetFileName_Unix(path) : GetFileName_Windows(path);
 19    }
 20}
 21

Methods/Properties

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