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 @@ -212,6 +212,9 @@
<data name="EntryTooLarge" xml:space="preserve">
<value>Entries larger than 4GB are not supported in Update mode.</value>
</data>
<data name="EntryUncompressedSizeTooLargeForUpdateMode" xml:space="preserve">

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@rzikm since a new error message was added, does it count as breaking change?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think we consider change of exception message a breaking change. We only consider it breaking if we change the exception type to something the method never has thrown before (and wasn't documented as a possibility)

<value>Entries with uncompressed data larger than 2GB are not supported in Update mode.</value>
</data>
<data name="EOCDNotFound" xml:space="preserve">
<value>End of Central Directory record could not be found.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,14 @@ private async Task<MemoryStream> GetUncompressedDataAsync(CancellationToken canc
{
// this means we have never opened it before

// if _uncompressedSize > int.MaxValue, it's still okay, because MemoryStream will just
// grow as data is copied into it
// MemoryStream is backed by a single byte[] and cannot grow beyond Array.MaxLength.
// Validate up front before attempting the (int) cast.
if ((ulong)_uncompressedSize > (ulong)Array.MaxLength)
{
_currentlyOpenForWrite = false;
throw new InvalidDataException(SR.EntryUncompressedSizeTooLargeForUpdateMode);
Comment thread
alinpahontu2912 marked this conversation as resolved.
}

_storedUncompressedData = new MemoryStream((int)_uncompressedSize);

if (_originallyInArchive)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -481,8 +481,14 @@ private MemoryStream GetUncompressedData()
{
// this means we have never opened it before

// if _uncompressedSize > int.MaxValue, it's still okay, because MemoryStream will just
// grow as data is copied into it
// MemoryStream is backed by a single byte[] and cannot grow beyond Array.MaxLength.
// Validate up front before attempting the (int) cast.
if ((ulong)_uncompressedSize > (ulong)Array.MaxLength)
{
_currentlyOpenForWrite = false;
throw new InvalidDataException(SR.EntryUncompressedSizeTooLargeForUpdateMode);
Comment thread
alinpahontu2912 marked this conversation as resolved.
}

_storedUncompressedData = new MemoryStream((int)_uncompressedSize);

if (_originallyInArchive)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,47 @@ await Assert.ThrowsAsync<InvalidDataException>(async () =>
await DisposeZipArchive(async, archive);
}

[Theory]
[MemberData(nameof(Get_Booleans_Data))]
public static async Task ZipArchiveEntry_OpenInUpdateMode_UncompressedSizeGreaterThanArrayMaxLength_ThrowsInvalidData(bool async)
{
// When _uncompressedSize > Array.MaxLength, the entry's uncompressed payload
// cannot be loaded into a MemoryStream (which is backed by a single byte[] and
// therefore bounded by Array.MaxLength). The entry must be rejected up front
// with a descriptive InvalidDataException when opened in Update mode, rather
// than failing later from the MemoryStream constructor with a misleading
// argument-out-of-range exception caused by the (int) cast wrapping negative
// when _uncompressedSize exceeds int.MaxValue.
byte[] payload = [0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF];
MemoryStream stream = new MemoryStream();

ZipArchive archive = await CreateZipArchive(async, stream, ZipArchiveMode.Create, leaveOpen: true);
ZipArchiveEntry entry = archive.CreateEntry("entry.bin", CompressionLevel.NoCompression);
Stream entryStream = await OpenEntryStream(async, entry);
await entryStream.WriteAsync(payload);
await DisposeStream(async, entryStream);
await DisposeZipArchive(async, archive);

stream.Position = 0;
archive = await CreateZipArchive(async, stream, ZipArchiveMode.Update, leaveOpen: true);
entry = archive.GetEntry("entry.bin");

FieldInfo uncompressedSizeField = typeof(ZipArchiveEntry).GetField("_uncompressedSize", BindingFlags.NonPublic | BindingFlags.Instance);
Assert.NotNull(uncompressedSizeField);
uncompressedSizeField.SetValue(entry, (long)int.MaxValue + 1L);
Comment thread
alinpahontu2912 marked this conversation as resolved.

if (async)
{
await Assert.ThrowsAsync<InvalidDataException>(() => entry.OpenAsync());
}
else
{
Assert.Throws<InvalidDataException>(() => entry.Open());
}

await DisposeZipArchive(async, archive);
}

[Theory]
[MemberData(nameof(Get_Booleans_Data))]
public static async Task UnseekableVeryLargeArchive_DataDescriptor_Read_Zip64(bool async)
Expand Down
Loading