Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ internal ZipArchiveEntry() { }
public string Name { get { throw null; } }
public void Delete() { }
public System.IO.Stream Open() { throw null; }
public System.IO.Stream Open(FileAccess access) { throw null; }
public System.Threading.Tasks.Task<System.IO.Stream> OpenAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public override string ToString() { throw null; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,15 @@
<data name="InvalidOffsetToZip64EOCD" xml:space="preserve">
<value>Invalid offset to the Zip64 End of Central Directory record.</value>
</data>
<data name="CannotBeWrittenInReadMode" xml:space="preserve">
<value>Cannot open entry for writing when archive is opened in read-only mode.</value>
</data>
<data name="CannotBeReadInCreateMode" xml:space="preserve">
<value>Cannot open entry for reading when archive is opened in create mode.</value>
</data>
<data name="InvalidFileAccess" xml:space="preserve">
<value>The specified FileAccess value is not valid.</value>
</data>
<data name="IO_SeekBeforeBegin" xml:space="preserve">
<value>An attempt was made to move the position before the beginning of the stream.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,48 @@ public Stream Open()
}
}

/// <summary>
/// Opens the entry with the specified access mode. This allows for more granular control over the returned stream's capabilities.
/// </summary>
Comment on lines +381 to +383
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The XML documentation summary doesn't adequately describe what each FileAccess mode does in different ZipArchiveModes, particularly the important behavioral differences in Update mode. Consider adding a remarks section that explains:

  • In Read mode: only FileAccess.Read is allowed
  • In Create mode: FileAccess.Write and FileAccess.ReadWrite are allowed (both behave as write-only)
  • In Update mode:
    • FileAccess.Read opens a read-only stream without loading into memory
    • FileAccess.Write provides an empty writable stream, discarding existing data
    • FileAccess.ReadWrite loads existing data into memory for modification (same as parameterless Open())

This is especially important because the behavior in Update mode with FileAccess.Write (discarding existing data) is semantically different from intuitive expectations.

Copilot uses AI. Check for mistakes.
/// <param name="access">The file access mode for the returned stream.</param>
/// <returns>A <see cref="Stream"/> that represents the contents of the entry with the specified access capabilities.</returns>
/// <exception cref="ArgumentException">The requested access is not compatible with the archive's open mode.</exception>
/// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been deleted from the archive. -or- The archive that this entry belongs to was opened in ZipArchiveMode.Create, and this entry has already been written to once.</exception>
/// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be read. -or- The entry has been compressed using a compression method that is not supported.</exception>
/// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exception>
public Stream Open(FileAccess access)
{
ThrowIfInvalidArchive();

if (access is not FileAccess.Read and not FileAccess.Write and not FileAccess.ReadWrite)
throw new ArgumentException(SR.InvalidFileAccess, nameof(access));

// Validate that the requested access is compatible with the archive's mode
switch (_archive.Mode)
{
case ZipArchiveMode.Read:
if (access != FileAccess.Read)
throw new ArgumentException(SR.CannotBeWrittenInReadMode, nameof(access));
return OpenInReadMode(checkOpenable: true);

case ZipArchiveMode.Create:
if (access == FileAccess.Read)
throw new ArgumentException(SR.CannotBeReadInCreateMode, nameof(access));
return OpenInWriteMode();

case ZipArchiveMode.Update:
default:
Debug.Assert(_archive.Mode == ZipArchiveMode.Update);
return access switch
{
FileAccess.Read => OpenInReadMode(checkOpenable: true),
FileAccess.Write => OpenInWriteModeForUpdate(),
FileAccess.ReadWrite => OpenInUpdateMode(),
_ => throw new UnreachableException()
Comment on lines +417 to +418
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UnreachableException in the switch expression default case is unnecessary. Since the access parameter is validated at lines 394-395 to be one of Read, Write, or ReadWrite, and all three cases are explicitly handled in the switch expression (lines 415-417), the default case at line 418 can never be reached. This makes the code slightly harder to reason about. Consider removing the default case entirely, as the switch expression will be exhaustive without it.

Suggested change
FileAccess.ReadWrite => OpenInUpdateMode(),
_ => throw new UnreachableException()
FileAccess.ReadWrite => OpenInUpdateMode()

Copilot uses AI. Check for mistakes.
};
}
}

/// <summary>
/// Returns the FullName of the entry.
/// </summary>
Expand Down Expand Up @@ -800,6 +842,36 @@ private WrappedStream OpenInWriteMode()
// we assume that if another entry grabbed the archive stream, that it set this entry's _everOpenedForWrite property to true by calling WriteLocalFileHeaderAndDataIfNeeded
_archive.DebugAssertIsStillArchiveStreamOwner(this);

return OpenInWriteModeCore();
}

private WrappedStream OpenInWriteModeForUpdate()
{
if (_currentlyOpenForWrite)
throw new IOException(SR.UpdateModeOneStream);

// Write access in Update mode means "replace the entry content entirely".
// We provide an empty MemoryStream (discarding any existing data) and write
// the new content to the archive at dispose time. This is necessary because
// writing directly to the archive could overwrite the next entry if the new
// data is larger than the original.
_everOpenedForWrite = true;
Changes |= ZipArchive.ChangeState.StoredData;
_currentlyOpenForWrite = true;

// Create a fresh empty MemoryStream, discarding any previously loaded data
_storedUncompressedData = new MemoryStream();

// Return a stream wrapper. The stream is writable and seekable (like MemoryStream),
// but starts empty unlike ReadWrite mode which loads existing data.
return new WrappedStream(_storedUncompressedData, this, thisRef =>
{
thisRef!._currentlyOpenForWrite = false;
});
}

private WrappedStream OpenInWriteModeCore()
{
_everOpenedForWrite = true;
Changes |= ZipArchive.ChangeState.StoredData;
CheckSumAndSizeWriteStream crcSizeStream = GetDataCompressor(_archive.ArchiveStream, true, (object? o, EventArgs e) =>
Expand Down
Loading
Loading