Repository: dotnet/runtime
File: src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Async.cs (C#)

Code before:
    /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exception>
    /// <exception cref="OperationCanceledException">An asynchronous operation is cancelled.</exception>
    /// <param name="source">The zip archive entry to extract a file from.</param>
    /// <param name="destinationFileName">The name of the file that will hold the contents of the entry.
    /// The path is permitted to specify relative or absolute path information.
    /// Relative path information is interpreted as relative to the current working directory.</param>
    /// <param name="overwrite">True to indicate overwrite.</param>
    /// <param name="cancellationToken">The cancellation token to monitor for cancellation requests.</param>
    /// <returns>A task that represents the asynchronous extract operation. The task completes when the entry contents have been written to the destination file.</returns>
    public static async Task ExtractToFileAsync(this ZipArchiveEntry source, string destinationFileName, bool overwrite, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();

        ExtractToFileInitialize(source, destinationFileName, overwrite, out FileStreamOptions fileStreamOptions);

        // When overwriting, extract to a temporary file first to avoid corrupting the destination file
        // if an exception occurs during extraction (e.g., password-protected archive, corrupted data).
        string extractPath = destinationFileName;
        string? tempPath = null;

        if (overwrite && File.Exists(destinationFileName))
        {
            string? directory = Path.GetDirectoryName(destinationFileName);
            if (string.IsNullOrEmpty(directory))
                directory = ".";
            tempPath = Path.Combine(directory, Path.GetRandomFileName());
            extractPath = tempPath;
        }

        try
        {
            FileStream fs = new FileStream(extractPath, fileStreamOptions);
            await using (fs)
            {
                Stream es = await source.OpenAsync(cancellationToken).ConfigureAwait(false);
                await using (es)
                {
                    await es.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
                }
            }

            // Move the temporary file to the destination only after successful extraction
            if (tempPath is not null)
            {
                File.Move(tempPath, destinationFileName, overwrite: true);
            }

            ExtractToFileFinalize(source, destinationFileName);
        }
        catch
        {

Review comment (bug):
Extracting to the same location has the benefit that the subsequent move will be cheap, but it has the downsides that a) this could conflict with an existing file though unlikely, b) the file will be visible temporarily, and c) if the process were to crash before the file is deleted it'll be left around forever. At a minimum should additional flags be set on it to mark it hidden? An alternative is to use Path.GetTempFileName; that's guaranteed to be unique and will be in a temporary location that will get cleaned up by the OS, but it may involve a copy if the temp location is on a different drive than the destination.

Code after:
    /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exception>
    /// <exception cref="OperationCanceledException">An asynchronous operation is cancelled.</exception>
    /// <param name="source">The zip archive entry to extract a file from.</param>
    /// <param name="destinationFileName">The name of the file that will hold the contents of the entry.
    /// The path is permitted to specify relative or absolute path information.
    /// Relative path information is interpreted as relative to the current working directory.</param>
    /// <param name="overwrite">True to indicate overwrite.</param>
    /// <param name="cancellationToken">The cancellation token to monitor for cancellation requests.</param>
    /// <returns>A task that represents the asynchronous extract operation. The task completes when the entry contents have been written to the destination file.</returns>
    public static async Task ExtractToFileAsync(this ZipArchiveEntry source, string destinationFileName, bool overwrite, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();

        ExtractToFileInitialize(source, destinationFileName, overwrite, useAsync: true, out FileStreamOptions fileStreamOptions);

        // When overwriting, extract to a temporary file first to avoid corrupting the destination file
        // if an exception occurs during extraction (e.g., password-protected archive, corrupted data).
        string extractPath = destinationFileName;
        string? tempPath = null;

        if (overwrite && File.Exists(destinationFileName))
        {
            // Use GetTempFileName for a unique temp file in the system temp directory.
            // This avoids conflicts and ensures cleanup by the OS if the process crashes.
            tempPath = Path.GetTempFileName();
            extractPath = tempPath;
        }

        try
        {
            FileStream fs = new FileStream(extractPath, fileStreamOptions);
            await using (fs.ConfigureAwait(false))
            {
                Stream es = await source.OpenAsync(cancellationToken).ConfigureAwait(false);
                await using (es.ConfigureAwait(false))
                {
                    await es.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
                }
            }

            // Move the temporary file to the destination only after successful extraction
            if (tempPath is not null)
            {
                File.Move(tempPath, destinationFileName, overwrite: true);
            }

            ExtractToFileFinalize(source, destinationFileName);
        }
        catch
        {
            // Clean up the temporary file if extraction failed