Skip to content
Merged
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 numberDiff line numberDiff line change
Expand Up@@ -199,30 +199,55 @@ private static bool TryGetZip64BlockFromGenericExtraField(ZipGenericExtraField e
if (extraField.Size < sizeof(long))
return true;

long value64 = reader.ReadInt64();
// Advancing the stream (by reading from it) is possible only when:
// 1. There is an explicit ask to do that (valid files, corresponding boolean flag(s) set to true).
// 2. When the size indicates that all the information is available ("slightly invalid files").
bool readAllFields = extraField.Size >= sizeof(long) + sizeof(long) + sizeof(long) + sizeof(int);

if (readUncompressedSize)
zip64Block._uncompressedSize = value64;
{
zip64Block._uncompressedSize = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For my own education: is there a perf improvement when using _ = ReadInt64(); to discard the value, compared to just not using _? Or is the idea here to explicitly signal that we are ignoring the value on purpose?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It shouldn't have a runtime effect, it's self documenting, both for humans and also for linters/analyzers that might otherwise flag an unused method result.

}

if (ms.Position > extraField.Size - sizeof(long))
return true;

value64 = reader.ReadInt64();
if (readCompressedSize)
zip64Block._compressedSize = value64;
{
zip64Block._compressedSize = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();
}

if (ms.Position > extraField.Size - sizeof(long))
return true;
Comment thread
jozkee marked this conversation as resolved.

value64 = reader.ReadInt64();
if (readLocalHeaderOffset)
zip64Block._localHeaderOffset = value64;
{
zip64Block._localHeaderOffset = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();
}

if (ms.Position > extraField.Size - sizeof(int))
return true;

int value32 = reader.ReadInt32();
if (readStartDiskNumber)
zip64Block._startDiskNumber = value32;
{
zip64Block._startDiskNumber = reader.ReadInt32();
}
else if (readAllFields)
{
_ = reader.ReadInt32();
}

// original values are unsigned, so implies value is too big to fit in signed integer
if (zip64Block._uncompressedSize < 0) throw new InvalidDataException(SR.FieldTooBigUncompressedSize);

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.

These validations only occur if extraField.Size >= 28 since you are now returning if there's less than that.

Shouldn't we validate when reader.ReadInt* is called and/or if read of the field was requested?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This is how it always was, I would prefer not to do it now as I intend to backport it to 7.0 and trying to make it as defensive/safe as possible.

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.

The reason I was asking is because it wasn't always like that. It was changed to how it is now on #68106, which is the PR that introduced the regression.

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,7 @@
<Compile Include="ZipArchive\zip_InvalidParametersAndStrangeFiles.cs" />
<Compile Include="ZipArchive\zip_ManualAndCompatibilityTests.cs" />
<Compile Include="ZipArchive\zip_netcoreappTests.cs" />
<Compile Include="ZipArchive\zip_LargeFiles.cs" />
<Compile Include="ZipArchive\zip_ReadTests.cs" />
<Compile Include="ZipArchive\zip_UpdateTests.cs" />
<Compile Include="ZipArchive\zip_UpdateTests.Comments.cs" />
Expand All@@ -36,6 +37,7 @@
<Compile Include="$(CommonTestPath)System\IO\Compression\StreamHelpers.cs" Link="Common\System\IO\Compression\StreamHelpers.cs" />
<Compile Include="$(CommonTestPath)System\IO\TempFile.cs" Link="Common\System\IO\TempFile.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\ZipTestHelper.cs" Link="Common\System\IO\Compression\ZipTestHelper.cs" />
<Compile Include="$(CommonTestPath)TestUtilities\System\DisableParallelization.cs" Link="Common\TestUtilities\System\DisableParallelization.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs" Link="Common\System\Threading\Tasks\TaskToApm.cs" />
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Linq;
using Xunit;

namespace System.IO.Compression.Tests
{
[Collection(nameof(DisableParallelization))]
public class zip_LargeFiles : ZipFileTestBase
{
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsSpeedOptimized), nameof(PlatformDetection.Is64BitProcess))] // don't run it on slower runtimes
[OuterLoop("It requires almost 12 GB of free disk space")]
public static void UnzipOver4GBZipFile()
Comment thread
adamsitnik marked this conversation as resolved.
{
byte[] buffer = GC.AllocateUninitializedArray<byte>(1_000_000_000); // 1 GB

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.

Technically, this is not 1Gb, but since you are creating 6 files, the test is still valid.

I would rather create two tests, one that succeeds at the limit (say 3.999 Gb) without the fix and one that fails and then verify that both pass with your fix.

Since this is urgent for a backport, we can defer that.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Technically, this is not 1Gb, but since you are creating 6 files, the test is still valid.

You mean that $1000^3$ is not 1 GB but $1024^3$ is?

I would rather create two tests, one that succeeds at the limit (say 3.999 Gb) without the fix and one that fails and then verify that both pass with your fix.

My typical workflow is to add test that fails first, then make it pass. We have plenty of tests that verify < 4 GB archives so I don't see the value in adding it (also it would require us to use even more disk space and take longer to execute).

In the long term we could add more tests, especially such that verify that decompress(compress(x)) == x (which this test avoids, as it was taking 10 minutes to do so on my beefy PC).

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.

You mean that $1000^3$ is not 1 GB but $1024^3$ is?

Yes.

We have plenty of tests that verify < 4 GB archives so I don't see the value in adding it (also it would require us to use even more disk space and take longer to execute).

I meant to verify on the edge cases of the bug. In this case that would be 4Gb - 1, 4Gb, and 4Gb + 1; rather than a 6Gb case. But yes, that would multiply test execution time which is already quite large with this new test.

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.

Is there a reasonable chance this will OOM? If so, consider catching that and skipping the test, rather than failing it.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Is there a reasonable chance this will OOM?

It should be executed only in Outerloop runs for the 64 bit "fast runtimes" and not in parallel with other tests so I don't think it's needed (at least for now).

[ConditionalFact(typeof(PlatformDetection),nameof(PlatformDetection.IsSpeedOptimized),nameof(PlatformDetection.Is64BitProcess))]// don't run it on slower runtimes

publicstaticboolIsSpeedOptimized=>!IsSizeOptimized;
publicstaticboolIsSizeOptimized=>IsBrowser||IsAndroid||IsAppleMobile;


string zipArchivePath = Path.Combine(Path.GetTempPath(), "over4GB.zip");
DirectoryInfo tempDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "over4GB"));

try
{
for (byte i = 0; i < 6; i++)
{
File.WriteAllBytes(Path.Combine(tempDir.FullName, $"{i}.test"), buffer);
}

ZipFile.CreateFromDirectory(tempDir.FullName, zipArchivePath, CompressionLevel.NoCompression, includeBaseDirectory: false);

using ZipArchive zipArchive = ZipFile.OpenRead(zipArchivePath);
foreach (ZipArchiveEntry entry in zipArchive.Entries)
{
using Stream entryStream = entry.Open();

Assert.True(entryStream.CanRead);
Assert.Equal(buffer.Length, entryStream.Length);
}
}
finally
{
File.Delete(zipArchivePath);

tempDir.Delete(recursive: true);
}
}
}
}
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Fix unzipping 4GB+ zip files by adamsitnik · Pull Request #77181 · dotnet/runtime · GitHub
Skip to content
Merged
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 numberDiff line numberDiff line change
Expand Up@@ -199,30 +199,55 @@ private static bool TryGetZip64BlockFromGenericExtraField(ZipGenericExtraField e
if (extraField.Size < sizeof(long))
return true;

long value64 = reader.ReadInt64();
// Advancing the stream (by reading from it) is possible only when:
// 1. There is an explicit ask to do that (valid files, corresponding boolean flag(s) set to true).
// 2. When the size indicates that all the information is available ("slightly invalid files").
bool readAllFields = extraField.Size >= sizeof(long) + sizeof(long) + sizeof(long) + sizeof(int);

if (readUncompressedSize)
zip64Block._uncompressedSize = value64;
{
zip64Block._uncompressedSize = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For my own education: is there a perf improvement when using _ = ReadInt64(); to discard the value, compared to just not using _? Or is the idea here to explicitly signal that we are ignoring the value on purpose?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It shouldn't have a runtime effect, it's self documenting, both for humans and also for linters/analyzers that might otherwise flag an unused method result.

}

if (ms.Position > extraField.Size - sizeof(long))
return true;

value64 = reader.ReadInt64();
if (readCompressedSize)
zip64Block._compressedSize = value64;
{
zip64Block._compressedSize = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();
}

if (ms.Position > extraField.Size - sizeof(long))
return true;
Comment thread
jozkee marked this conversation as resolved.

value64 = reader.ReadInt64();
if (readLocalHeaderOffset)
zip64Block._localHeaderOffset = value64;
{
zip64Block._localHeaderOffset = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();
}

if (ms.Position > extraField.Size - sizeof(int))
return true;

int value32 = reader.ReadInt32();
if (readStartDiskNumber)
zip64Block._startDiskNumber = value32;
{
zip64Block._startDiskNumber = reader.ReadInt32();
}
else if (readAllFields)
{
_ = reader.ReadInt32();
}

// original values are unsigned, so implies value is too big to fit in signed integer
if (zip64Block._uncompressedSize < 0) throw new InvalidDataException(SR.FieldTooBigUncompressedSize);

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.

These validations only occur if extraField.Size >= 28 since you are now returning if there's less than that.

Shouldn't we validate when reader.ReadInt* is called and/or if read of the field was requested?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This is how it always was, I would prefer not to do it now as I intend to backport it to 7.0 and trying to make it as defensive/safe as possible.

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.

The reason I was asking is because it wasn't always like that. It was changed to how it is now on #68106, which is the PR that introduced the regression.

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,7 @@
<Compile Include="ZipArchive\zip_InvalidParametersAndStrangeFiles.cs" />
<Compile Include="ZipArchive\zip_ManualAndCompatibilityTests.cs" />
<Compile Include="ZipArchive\zip_netcoreappTests.cs" />
<Compile Include="ZipArchive\zip_LargeFiles.cs" />
<Compile Include="ZipArchive\zip_ReadTests.cs" />
<Compile Include="ZipArchive\zip_UpdateTests.cs" />
<Compile Include="ZipArchive\zip_UpdateTests.Comments.cs" />
Expand All@@ -36,6 +37,7 @@
<Compile Include="$(CommonTestPath)System\IO\Compression\StreamHelpers.cs" Link="Common\System\IO\Compression\StreamHelpers.cs" />
<Compile Include="$(CommonTestPath)System\IO\TempFile.cs" Link="Common\System\IO\TempFile.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\ZipTestHelper.cs" Link="Common\System\IO\Compression\ZipTestHelper.cs" />
<Compile Include="$(CommonTestPath)TestUtilities\System\DisableParallelization.cs" Link="Common\TestUtilities\System\DisableParallelization.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs" Link="Common\System\Threading\Tasks\TaskToApm.cs" />
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Linq;
using Xunit;

namespace System.IO.Compression.Tests
{
[Collection(nameof(DisableParallelization))]
public class zip_LargeFiles : ZipFileTestBase
{
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsSpeedOptimized), nameof(PlatformDetection.Is64BitProcess))] // don't run it on slower runtimes
[OuterLoop("It requires almost 12 GB of free disk space")]
public static void UnzipOver4GBZipFile()
Comment thread
adamsitnik marked this conversation as resolved.
{
byte[] buffer = GC.AllocateUninitializedArray<byte>(1_000_000_000); // 1 GB

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.

Technically, this is not 1Gb, but since you are creating 6 files, the test is still valid.

I would rather create two tests, one that succeeds at the limit (say 3.999 Gb) without the fix and one that fails and then verify that both pass with your fix.

Since this is urgent for a backport, we can defer that.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Technically, this is not 1Gb, but since you are creating 6 files, the test is still valid.

You mean that $1000^3$ is not 1 GB but $1024^3$ is?

I would rather create two tests, one that succeeds at the limit (say 3.999 Gb) without the fix and one that fails and then verify that both pass with your fix.

My typical workflow is to add test that fails first, then make it pass. We have plenty of tests that verify < 4 GB archives so I don't see the value in adding it (also it would require us to use even more disk space and take longer to execute).

In the long term we could add more tests, especially such that verify that decompress(compress(x)) == x (which this test avoids, as it was taking 10 minutes to do so on my beefy PC).

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.

You mean that $1000^3$ is not 1 GB but $1024^3$ is?

Yes.

We have plenty of tests that verify < 4 GB archives so I don't see the value in adding it (also it would require us to use even more disk space and take longer to execute).

I meant to verify on the edge cases of the bug. In this case that would be 4Gb - 1, 4Gb, and 4Gb + 1; rather than a 6Gb case. But yes, that would multiply test execution time which is already quite large with this new test.

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.

Is there a reasonable chance this will OOM? If so, consider catching that and skipping the test, rather than failing it.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Is there a reasonable chance this will OOM?

It should be executed only in Outerloop runs for the 64 bit "fast runtimes" and not in parallel with other tests so I don't think it's needed (at least for now).

[ConditionalFact(typeof(PlatformDetection),nameof(PlatformDetection.IsSpeedOptimized),nameof(PlatformDetection.Is64BitProcess))]// don't run it on slower runtimes

publicstaticboolIsSpeedOptimized=>!IsSizeOptimized;
publicstaticboolIsSizeOptimized=>IsBrowser||IsAndroid||IsAppleMobile;


string zipArchivePath = Path.Combine(Path.GetTempPath(), "over4GB.zip");
DirectoryInfo tempDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "over4GB"));

try
{
for (byte i = 0; i < 6; i++)
{
File.WriteAllBytes(Path.Combine(tempDir.FullName, $"{i}.test"), buffer);
}

ZipFile.CreateFromDirectory(tempDir.FullName, zipArchivePath, CompressionLevel.NoCompression, includeBaseDirectory: false);

using ZipArchive zipArchive = ZipFile.OpenRead(zipArchivePath);
foreach (ZipArchiveEntry entry in zipArchive.Entries)
{
using Stream entryStream = entry.Open();

Assert.True(entryStream.CanRead);
Assert.Equal(buffer.Length, entryStream.Length);
}
}
finally
{
File.Delete(zipArchivePath);

tempDir.Delete(recursive: true);
}
}
}
}
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix unzipping 4GB+ zip files by adamsitnik · Pull Request #77181 · dotnet/runtime · GitHub
Skip to content
Merged
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 numberDiff line numberDiff line change
Expand Up@@ -199,30 +199,55 @@ private static bool TryGetZip64BlockFromGenericExtraField(ZipGenericExtraField e
if (extraField.Size < sizeof(long))
return true;

long value64 = reader.ReadInt64();
// Advancing the stream (by reading from it) is possible only when:
// 1. There is an explicit ask to do that (valid files, corresponding boolean flag(s) set to true).
// 2. When the size indicates that all the information is available ("slightly invalid files").
bool readAllFields = extraField.Size >= sizeof(long) + sizeof(long) + sizeof(long) + sizeof(int);

if (readUncompressedSize)
zip64Block._uncompressedSize = value64;
{
zip64Block._uncompressedSize = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For my own education: is there a perf improvement when using _ = ReadInt64(); to discard the value, compared to just not using _? Or is the idea here to explicitly signal that we are ignoring the value on purpose?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It shouldn't have a runtime effect, it's self documenting, both for humans and also for linters/analyzers that might otherwise flag an unused method result.

}

if (ms.Position > extraField.Size - sizeof(long))
return true;

value64 = reader.ReadInt64();
if (readCompressedSize)
zip64Block._compressedSize = value64;
{
zip64Block._compressedSize = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();
}

if (ms.Position > extraField.Size - sizeof(long))
return true;
Comment thread
jozkee marked this conversation as resolved.

value64 = reader.ReadInt64();
if (readLocalHeaderOffset)
zip64Block._localHeaderOffset = value64;
{
zip64Block._localHeaderOffset = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();
}

if (ms.Position > extraField.Size - sizeof(int))
return true;

int value32 = reader.ReadInt32();
if (readStartDiskNumber)
zip64Block._startDiskNumber = value32;
{
zip64Block._startDiskNumber = reader.ReadInt32();
}
else if (readAllFields)
{
_ = reader.ReadInt32();
}

// original values are unsigned, so implies value is too big to fit in signed integer
if (zip64Block._uncompressedSize < 0) throw new InvalidDataException(SR.FieldTooBigUncompressedSize);

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.

These validations only occur if extraField.Size >= 28 since you are now returning if there's less than that.

Shouldn't we validate when reader.ReadInt* is called and/or if read of the field was requested?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This is how it always was, I would prefer not to do it now as I intend to backport it to 7.0 and trying to make it as defensive/safe as possible.

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.

The reason I was asking is because it wasn't always like that. It was changed to how it is now on #68106, which is the PR that introduced the regression.

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,7 @@
<Compile Include="ZipArchive\zip_InvalidParametersAndStrangeFiles.cs" />
<Compile Include="ZipArchive\zip_ManualAndCompatibilityTests.cs" />
<Compile Include="ZipArchive\zip_netcoreappTests.cs" />
<Compile Include="ZipArchive\zip_LargeFiles.cs" />
<Compile Include="ZipArchive\zip_ReadTests.cs" />
<Compile Include="ZipArchive\zip_UpdateTests.cs" />
<Compile Include="ZipArchive\zip_UpdateTests.Comments.cs" />
Expand All@@ -36,6 +37,7 @@
<Compile Include="$(CommonTestPath)System\IO\Compression\StreamHelpers.cs" Link="Common\System\IO\Compression\StreamHelpers.cs" />
<Compile Include="$(CommonTestPath)System\IO\TempFile.cs" Link="Common\System\IO\TempFile.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\ZipTestHelper.cs" Link="Common\System\IO\Compression\ZipTestHelper.cs" />
<Compile Include="$(CommonTestPath)TestUtilities\System\DisableParallelization.cs" Link="Common\TestUtilities\System\DisableParallelization.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs" Link="Common\System\Threading\Tasks\TaskToApm.cs" />
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Linq;
using Xunit;

namespace System.IO.Compression.Tests
{
[Collection(nameof(DisableParallelization))]
public class zip_LargeFiles : ZipFileTestBase
{
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsSpeedOptimized), nameof(PlatformDetection.Is64BitProcess))] // don't run it on slower runtimes
[OuterLoop("It requires almost 12 GB of free disk space")]
public static void UnzipOver4GBZipFile()
Comment thread
adamsitnik marked this conversation as resolved.
{
byte[] buffer = GC.AllocateUninitializedArray<byte>(1_000_000_000); // 1 GB

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.

Technically, this is not 1Gb, but since you are creating 6 files, the test is still valid.

I would rather create two tests, one that succeeds at the limit (say 3.999 Gb) without the fix and one that fails and then verify that both pass with your fix.

Since this is urgent for a backport, we can defer that.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Technically, this is not 1Gb, but since you are creating 6 files, the test is still valid.

You mean that $1000^3$ is not 1 GB but $1024^3$ is?

I would rather create two tests, one that succeeds at the limit (say 3.999 Gb) without the fix and one that fails and then verify that both pass with your fix.

My typical workflow is to add test that fails first, then make it pass. We have plenty of tests that verify < 4 GB archives so I don't see the value in adding it (also it would require us to use even more disk space and take longer to execute).

In the long term we could add more tests, especially such that verify that decompress(compress(x)) == x (which this test avoids, as it was taking 10 minutes to do so on my beefy PC).

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.

You mean that $1000^3$ is not 1 GB but $1024^3$ is?

Yes.

We have plenty of tests that verify < 4 GB archives so I don't see the value in adding it (also it would require us to use even more disk space and take longer to execute).

I meant to verify on the edge cases of the bug. In this case that would be 4Gb - 1, 4Gb, and 4Gb + 1; rather than a 6Gb case. But yes, that would multiply test execution time which is already quite large with this new test.

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.

Is there a reasonable chance this will OOM? If so, consider catching that and skipping the test, rather than failing it.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Is there a reasonable chance this will OOM?

It should be executed only in Outerloop runs for the 64 bit "fast runtimes" and not in parallel with other tests so I don't think it's needed (at least for now).

[ConditionalFact(typeof(PlatformDetection),nameof(PlatformDetection.IsSpeedOptimized),nameof(PlatformDetection.Is64BitProcess))]// don't run it on slower runtimes

publicstaticboolIsSpeedOptimized=>!IsSizeOptimized;
publicstaticboolIsSizeOptimized=>IsBrowser||IsAndroid||IsAppleMobile;


string zipArchivePath = Path.Combine(Path.GetTempPath(), "over4GB.zip");
DirectoryInfo tempDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "over4GB"));

try
{
for (byte i = 0; i < 6; i++)
{
File.WriteAllBytes(Path.Combine(tempDir.FullName, $"{i}.test"), buffer);
}

ZipFile.CreateFromDirectory(tempDir.FullName, zipArchivePath, CompressionLevel.NoCompression, includeBaseDirectory: false);

using ZipArchive zipArchive = ZipFile.OpenRead(zipArchivePath);
foreach (ZipArchiveEntry entry in zipArchive.Entries)
{
using Stream entryStream = entry.Open();

Assert.True(entryStream.CanRead);
Assert.Equal(buffer.Length, entryStream.Length);
}
}
finally
{
File.Delete(zipArchivePath);

tempDir.Delete(recursive: true);
}
}
}
}
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix unzipping 4GB+ zip files by adamsitnik · Pull Request #77181 · dotnet/runtime · GitHub
Skip to content
Merged
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 numberDiff line numberDiff line change
Expand Up@@ -199,30 +199,55 @@ private static bool TryGetZip64BlockFromGenericExtraField(ZipGenericExtraField e
if (extraField.Size < sizeof(long))
return true;

long value64 = reader.ReadInt64();
// Advancing the stream (by reading from it) is possible only when:
// 1. There is an explicit ask to do that (valid files, corresponding boolean flag(s) set to true).
// 2. When the size indicates that all the information is available ("slightly invalid files").
bool readAllFields = extraField.Size >= sizeof(long) + sizeof(long) + sizeof(long) + sizeof(int);

if (readUncompressedSize)
zip64Block._uncompressedSize = value64;
{
zip64Block._uncompressedSize = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For my own education: is there a perf improvement when using _ = ReadInt64(); to discard the value, compared to just not using _? Or is the idea here to explicitly signal that we are ignoring the value on purpose?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It shouldn't have a runtime effect, it's self documenting, both for humans and also for linters/analyzers that might otherwise flag an unused method result.

}

if (ms.Position > extraField.Size - sizeof(long))
return true;

value64 = reader.ReadInt64();
if (readCompressedSize)
zip64Block._compressedSize = value64;
{
zip64Block._compressedSize = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();
}

if (ms.Position > extraField.Size - sizeof(long))
return true;
Comment thread
jozkee marked this conversation as resolved.

value64 = reader.ReadInt64();
if (readLocalHeaderOffset)
zip64Block._localHeaderOffset = value64;
{
zip64Block._localHeaderOffset = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();
}

if (ms.Position > extraField.Size - sizeof(int))
return true;

int value32 = reader.ReadInt32();
if (readStartDiskNumber)
zip64Block._startDiskNumber = value32;
{
zip64Block._startDiskNumber = reader.ReadInt32();
}
else if (readAllFields)
{
_ = reader.ReadInt32();
}

// original values are unsigned, so implies value is too big to fit in signed integer
if (zip64Block._uncompressedSize < 0) throw new InvalidDataException(SR.FieldTooBigUncompressedSize);

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.

These validations only occur if extraField.Size >= 28 since you are now returning if there's less than that.

Shouldn't we validate when reader.ReadInt* is called and/or if read of the field was requested?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This is how it always was, I would prefer not to do it now as I intend to backport it to 7.0 and trying to make it as defensive/safe as possible.

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.

The reason I was asking is because it wasn't always like that. It was changed to how it is now on #68106, which is the PR that introduced the regression.

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,7 @@
<Compile Include="ZipArchive\zip_InvalidParametersAndStrangeFiles.cs" />
<Compile Include="ZipArchive\zip_ManualAndCompatibilityTests.cs" />
<Compile Include="ZipArchive\zip_netcoreappTests.cs" />
<Compile Include="ZipArchive\zip_LargeFiles.cs" />
<Compile Include="ZipArchive\zip_ReadTests.cs" />
<Compile Include="ZipArchive\zip_UpdateTests.cs" />
<Compile Include="ZipArchive\zip_UpdateTests.Comments.cs" />
Expand All@@ -36,6 +37,7 @@
<Compile Include="$(CommonTestPath)System\IO\Compression\StreamHelpers.cs" Link="Common\System\IO\Compression\StreamHelpers.cs" />
<Compile Include="$(CommonTestPath)System\IO\TempFile.cs" Link="Common\System\IO\TempFile.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\ZipTestHelper.cs" Link="Common\System\IO\Compression\ZipTestHelper.cs" />
<Compile Include="$(CommonTestPath)TestUtilities\System\DisableParallelization.cs" Link="Common\TestUtilities\System\DisableParallelization.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs" Link="Common\System\Threading\Tasks\TaskToApm.cs" />
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Linq;
using Xunit;

namespace System.IO.Compression.Tests
{
[Collection(nameof(DisableParallelization))]
public class zip_LargeFiles : ZipFileTestBase
{
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsSpeedOptimized), nameof(PlatformDetection.Is64BitProcess))] // don't run it on slower runtimes
[OuterLoop("It requires almost 12 GB of free disk space")]
public static void UnzipOver4GBZipFile()
Comment thread
adamsitnik marked this conversation as resolved.
{
byte[] buffer = GC.AllocateUninitializedArray<byte>(1_000_000_000); // 1 GB

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.

Technically, this is not 1Gb, but since you are creating 6 files, the test is still valid.

I would rather create two tests, one that succeeds at the limit (say 3.999 Gb) without the fix and one that fails and then verify that both pass with your fix.

Since this is urgent for a backport, we can defer that.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Technically, this is not 1Gb, but since you are creating 6 files, the test is still valid.

You mean that $1000^3$ is not 1 GB but $1024^3$ is?

I would rather create two tests, one that succeeds at the limit (say 3.999 Gb) without the fix and one that fails and then verify that both pass with your fix.

My typical workflow is to add test that fails first, then make it pass. We have plenty of tests that verify < 4 GB archives so I don't see the value in adding it (also it would require us to use even more disk space and take longer to execute).

In the long term we could add more tests, especially such that verify that decompress(compress(x)) == x (which this test avoids, as it was taking 10 minutes to do so on my beefy PC).

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.

You mean that $1000^3$ is not 1 GB but $1024^3$ is?

Yes.

We have plenty of tests that verify < 4 GB archives so I don't see the value in adding it (also it would require us to use even more disk space and take longer to execute).

I meant to verify on the edge cases of the bug. In this case that would be 4Gb - 1, 4Gb, and 4Gb + 1; rather than a 6Gb case. But yes, that would multiply test execution time which is already quite large with this new test.

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.

Is there a reasonable chance this will OOM? If so, consider catching that and skipping the test, rather than failing it.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Is there a reasonable chance this will OOM?

It should be executed only in Outerloop runs for the 64 bit "fast runtimes" and not in parallel with other tests so I don't think it's needed (at least for now).

[ConditionalFact(typeof(PlatformDetection),nameof(PlatformDetection.IsSpeedOptimized),nameof(PlatformDetection.Is64BitProcess))]// don't run it on slower runtimes

publicstaticboolIsSpeedOptimized=>!IsSizeOptimized;
publicstaticboolIsSizeOptimized=>IsBrowser||IsAndroid||IsAppleMobile;


string zipArchivePath = Path.Combine(Path.GetTempPath(), "over4GB.zip");
DirectoryInfo tempDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "over4GB"));

try
{
for (byte i = 0; i < 6; i++)
{
File.WriteAllBytes(Path.Combine(tempDir.FullName, $"{i}.test"), buffer);
}

ZipFile.CreateFromDirectory(tempDir.FullName, zipArchivePath, CompressionLevel.NoCompression, includeBaseDirectory: false);

using ZipArchive zipArchive = ZipFile.OpenRead(zipArchivePath);
foreach (ZipArchiveEntry entry in zipArchive.Entries)
{
using Stream entryStream = entry.Open();

Assert.True(entryStream.CanRead);
Assert.Equal(buffer.Length, entryStream.Length);
}
}
finally
{
File.Delete(zipArchivePath);

tempDir.Delete(recursive: true);
}
}
}
}
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Fix unzipping 4GB+ zip files by adamsitnik · Pull Request #77181 · dotnet/runtime · GitHub
Skip to content
Merged
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 numberDiff line numberDiff line change
Expand Up@@ -199,30 +199,55 @@ private static bool TryGetZip64BlockFromGenericExtraField(ZipGenericExtraField e
if (extraField.Size < sizeof(long))
return true;

long value64 = reader.ReadInt64();
// Advancing the stream (by reading from it) is possible only when:
// 1. There is an explicit ask to do that (valid files, corresponding boolean flag(s) set to true).
// 2. When the size indicates that all the information is available ("slightly invalid files").
bool readAllFields = extraField.Size >= sizeof(long) + sizeof(long) + sizeof(long) + sizeof(int);

if (readUncompressedSize)
zip64Block._uncompressedSize = value64;
{
zip64Block._uncompressedSize = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For my own education: is there a perf improvement when using _ = ReadInt64(); to discard the value, compared to just not using _? Or is the idea here to explicitly signal that we are ignoring the value on purpose?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It shouldn't have a runtime effect, it's self documenting, both for humans and also for linters/analyzers that might otherwise flag an unused method result.

}

if (ms.Position > extraField.Size - sizeof(long))
return true;

value64 = reader.ReadInt64();
if (readCompressedSize)
zip64Block._compressedSize = value64;
{
zip64Block._compressedSize = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();
}

if (ms.Position > extraField.Size - sizeof(long))
return true;
Comment thread
jozkee marked this conversation as resolved.

value64 = reader.ReadInt64();
if (readLocalHeaderOffset)
zip64Block._localHeaderOffset = value64;
{
zip64Block._localHeaderOffset = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();
}

if (ms.Position > extraField.Size - sizeof(int))
return true;

int value32 = reader.ReadInt32();
if (readStartDiskNumber)
zip64Block._startDiskNumber = value32;
{
zip64Block._startDiskNumber = reader.ReadInt32();
}
else if (readAllFields)
{
_ = reader.ReadInt32();
}

// original values are unsigned, so implies value is too big to fit in signed integer
if (zip64Block._uncompressedSize < 0) throw new InvalidDataException(SR.FieldTooBigUncompressedSize);

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.

These validations only occur if extraField.Size >= 28 since you are now returning if there's less than that.

Shouldn't we validate when reader.ReadInt* is called and/or if read of the field was requested?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This is how it always was, I would prefer not to do it now as I intend to backport it to 7.0 and trying to make it as defensive/safe as possible.

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.

The reason I was asking is because it wasn't always like that. It was changed to how it is now on #68106, which is the PR that introduced the regression.

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,7 @@
<Compile Include="ZipArchive\zip_InvalidParametersAndStrangeFiles.cs" />
<Compile Include="ZipArchive\zip_ManualAndCompatibilityTests.cs" />
<Compile Include="ZipArchive\zip_netcoreappTests.cs" />
<Compile Include="ZipArchive\zip_LargeFiles.cs" />
<Compile Include="ZipArchive\zip_ReadTests.cs" />
<Compile Include="ZipArchive\zip_UpdateTests.cs" />
<Compile Include="ZipArchive\zip_UpdateTests.Comments.cs" />
Expand All@@ -36,6 +37,7 @@
<Compile Include="$(CommonTestPath)System\IO\Compression\StreamHelpers.cs" Link="Common\System\IO\Compression\StreamHelpers.cs" />
<Compile Include="$(CommonTestPath)System\IO\TempFile.cs" Link="Common\System\IO\TempFile.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\ZipTestHelper.cs" Link="Common\System\IO\Compression\ZipTestHelper.cs" />
<Compile Include="$(CommonTestPath)TestUtilities\System\DisableParallelization.cs" Link="Common\TestUtilities\System\DisableParallelization.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs" Link="Common\System\Threading\Tasks\TaskToApm.cs" />
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Linq;
using Xunit;

namespace System.IO.Compression.Tests
{
[Collection(nameof(DisableParallelization))]
public class zip_LargeFiles : ZipFileTestBase
{
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsSpeedOptimized), nameof(PlatformDetection.Is64BitProcess))] // don't run it on slower runtimes
[OuterLoop("It requires almost 12 GB of free disk space")]
public static void UnzipOver4GBZipFile()
Comment thread
adamsitnik marked this conversation as resolved.
{
byte[] buffer = GC.AllocateUninitializedArray<byte>(1_000_000_000); // 1 GB

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.

Technically, this is not 1Gb, but since you are creating 6 files, the test is still valid.

I would rather create two tests, one that succeeds at the limit (say 3.999 Gb) without the fix and one that fails and then verify that both pass with your fix.

Since this is urgent for a backport, we can defer that.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Technically, this is not 1Gb, but since you are creating 6 files, the test is still valid.

You mean that $1000^3$ is not 1 GB but $1024^3$ is?

I would rather create two tests, one that succeeds at the limit (say 3.999 Gb) without the fix and one that fails and then verify that both pass with your fix.

My typical workflow is to add test that fails first, then make it pass. We have plenty of tests that verify < 4 GB archives so I don't see the value in adding it (also it would require us to use even more disk space and take longer to execute).

In the long term we could add more tests, especially such that verify that decompress(compress(x)) == x (which this test avoids, as it was taking 10 minutes to do so on my beefy PC).

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.

You mean that $1000^3$ is not 1 GB but $1024^3$ is?

Yes.

We have plenty of tests that verify < 4 GB archives so I don't see the value in adding it (also it would require us to use even more disk space and take longer to execute).

I meant to verify on the edge cases of the bug. In this case that would be 4Gb - 1, 4Gb, and 4Gb + 1; rather than a 6Gb case. But yes, that would multiply test execution time which is already quite large with this new test.

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.

Is there a reasonable chance this will OOM? If so, consider catching that and skipping the test, rather than failing it.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Is there a reasonable chance this will OOM?

It should be executed only in Outerloop runs for the 64 bit "fast runtimes" and not in parallel with other tests so I don't think it's needed (at least for now).

[ConditionalFact(typeof(PlatformDetection),nameof(PlatformDetection.IsSpeedOptimized),nameof(PlatformDetection.Is64BitProcess))]// don't run it on slower runtimes

publicstaticboolIsSpeedOptimized=>!IsSizeOptimized;
publicstaticboolIsSizeOptimized=>IsBrowser||IsAndroid||IsAppleMobile;


string zipArchivePath = Path.Combine(Path.GetTempPath(), "over4GB.zip");
DirectoryInfo tempDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "over4GB"));

try
{
for (byte i = 0; i < 6; i++)
{
File.WriteAllBytes(Path.Combine(tempDir.FullName, $"{i}.test"), buffer);
}

ZipFile.CreateFromDirectory(tempDir.FullName, zipArchivePath, CompressionLevel.NoCompression, includeBaseDirectory: false);

using ZipArchive zipArchive = ZipFile.OpenRead(zipArchivePath);
foreach (ZipArchiveEntry entry in zipArchive.Entries)
{
using Stream entryStream = entry.Open();

Assert.True(entryStream.CanRead);
Assert.Equal(buffer.Length, entryStream.Length);
}
}
finally
{
File.Delete(zipArchivePath);

tempDir.Delete(recursive: true);
}
}
}
}
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix unzipping 4GB+ zip files by adamsitnik · Pull Request #77181 · dotnet/runtime · GitHub
Skip to content
Merged
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 numberDiff line numberDiff line change
Expand Up@@ -199,30 +199,55 @@ private static bool TryGetZip64BlockFromGenericExtraField(ZipGenericExtraField e
if (extraField.Size < sizeof(long))
return true;

long value64 = reader.ReadInt64();
// Advancing the stream (by reading from it) is possible only when:
// 1. There is an explicit ask to do that (valid files, corresponding boolean flag(s) set to true).
// 2. When the size indicates that all the information is available ("slightly invalid files").
bool readAllFields = extraField.Size >= sizeof(long) + sizeof(long) + sizeof(long) + sizeof(int);

if (readUncompressedSize)
zip64Block._uncompressedSize = value64;
{
zip64Block._uncompressedSize = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For my own education: is there a perf improvement when using _ = ReadInt64(); to discard the value, compared to just not using _? Or is the idea here to explicitly signal that we are ignoring the value on purpose?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It shouldn't have a runtime effect, it's self documenting, both for humans and also for linters/analyzers that might otherwise flag an unused method result.

}

if (ms.Position > extraField.Size - sizeof(long))
return true;

value64 = reader.ReadInt64();
if (readCompressedSize)
zip64Block._compressedSize = value64;
{
zip64Block._compressedSize = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();
}

if (ms.Position > extraField.Size - sizeof(long))
return true;
Comment thread
jozkee marked this conversation as resolved.

value64 = reader.ReadInt64();
if (readLocalHeaderOffset)
zip64Block._localHeaderOffset = value64;
{
zip64Block._localHeaderOffset = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();
}

if (ms.Position > extraField.Size - sizeof(int))
return true;

int value32 = reader.ReadInt32();
if (readStartDiskNumber)
zip64Block._startDiskNumber = value32;
{
zip64Block._startDiskNumber = reader.ReadInt32();
}
else if (readAllFields)
{
_ = reader.ReadInt32();
}

// original values are unsigned, so implies value is too big to fit in signed integer
if (zip64Block._uncompressedSize < 0) throw new InvalidDataException(SR.FieldTooBigUncompressedSize);

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.

These validations only occur if extraField.Size >= 28 since you are now returning if there's less than that.

Shouldn't we validate when reader.ReadInt* is called and/or if read of the field was requested?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This is how it always was, I would prefer not to do it now as I intend to backport it to 7.0 and trying to make it as defensive/safe as possible.

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.

The reason I was asking is because it wasn't always like that. It was changed to how it is now on #68106, which is the PR that introduced the regression.

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,7 @@
<Compile Include="ZipArchive\zip_InvalidParametersAndStrangeFiles.cs" />
<Compile Include="ZipArchive\zip_ManualAndCompatibilityTests.cs" />
<Compile Include="ZipArchive\zip_netcoreappTests.cs" />
<Compile Include="ZipArchive\zip_LargeFiles.cs" />
<Compile Include="ZipArchive\zip_ReadTests.cs" />
<Compile Include="ZipArchive\zip_UpdateTests.cs" />
<Compile Include="ZipArchive\zip_UpdateTests.Comments.cs" />
Expand All@@ -36,6 +37,7 @@
<Compile Include="$(CommonTestPath)System\IO\Compression\StreamHelpers.cs" Link="Common\System\IO\Compression\StreamHelpers.cs" />
<Compile Include="$(CommonTestPath)System\IO\TempFile.cs" Link="Common\System\IO\TempFile.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\ZipTestHelper.cs" Link="Common\System\IO\Compression\ZipTestHelper.cs" />
<Compile Include="$(CommonTestPath)TestUtilities\System\DisableParallelization.cs" Link="Common\TestUtilities\System\DisableParallelization.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs" Link="Common\System\Threading\Tasks\TaskToApm.cs" />
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Linq;
using Xunit;

namespace System.IO.Compression.Tests
{
[Collection(nameof(DisableParallelization))]
public class zip_LargeFiles : ZipFileTestBase
{
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsSpeedOptimized), nameof(PlatformDetection.Is64BitProcess))] // don't run it on slower runtimes
[OuterLoop("It requires almost 12 GB of free disk space")]
public static void UnzipOver4GBZipFile()
Comment thread
adamsitnik marked this conversation as resolved.
{
byte[] buffer = GC.AllocateUninitializedArray<byte>(1_000_000_000); // 1 GB

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.

Technically, this is not 1Gb, but since you are creating 6 files, the test is still valid.

I would rather create two tests, one that succeeds at the limit (say 3.999 Gb) without the fix and one that fails and then verify that both pass with your fix.

Since this is urgent for a backport, we can defer that.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Technically, this is not 1Gb, but since you are creating 6 files, the test is still valid.

You mean that $1000^3$ is not 1 GB but $1024^3$ is?

I would rather create two tests, one that succeeds at the limit (say 3.999 Gb) without the fix and one that fails and then verify that both pass with your fix.

My typical workflow is to add test that fails first, then make it pass. We have plenty of tests that verify < 4 GB archives so I don't see the value in adding it (also it would require us to use even more disk space and take longer to execute).

In the long term we could add more tests, especially such that verify that decompress(compress(x)) == x (which this test avoids, as it was taking 10 minutes to do so on my beefy PC).

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.

You mean that $1000^3$ is not 1 GB but $1024^3$ is?

Yes.

We have plenty of tests that verify < 4 GB archives so I don't see the value in adding it (also it would require us to use even more disk space and take longer to execute).

I meant to verify on the edge cases of the bug. In this case that would be 4Gb - 1, 4Gb, and 4Gb + 1; rather than a 6Gb case. But yes, that would multiply test execution time which is already quite large with this new test.

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.

Is there a reasonable chance this will OOM? If so, consider catching that and skipping the test, rather than failing it.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Is there a reasonable chance this will OOM?

It should be executed only in Outerloop runs for the 64 bit "fast runtimes" and not in parallel with other tests so I don't think it's needed (at least for now).

[ConditionalFact(typeof(PlatformDetection),nameof(PlatformDetection.IsSpeedOptimized),nameof(PlatformDetection.Is64BitProcess))]// don't run it on slower runtimes

publicstaticboolIsSpeedOptimized=>!IsSizeOptimized;
publicstaticboolIsSizeOptimized=>IsBrowser||IsAndroid||IsAppleMobile;


string zipArchivePath = Path.Combine(Path.GetTempPath(), "over4GB.zip");
DirectoryInfo tempDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "over4GB"));

try
{
for (byte i = 0; i < 6; i++)
{
File.WriteAllBytes(Path.Combine(tempDir.FullName, $"{i}.test"), buffer);
}

ZipFile.CreateFromDirectory(tempDir.FullName, zipArchivePath, CompressionLevel.NoCompression, includeBaseDirectory: false);

using ZipArchive zipArchive = ZipFile.OpenRead(zipArchivePath);
foreach (ZipArchiveEntry entry in zipArchive.Entries)
{
using Stream entryStream = entry.Open();

Assert.True(entryStream.CanRead);
Assert.Equal(buffer.Length, entryStream.Length);
}
}
finally
{
File.Delete(zipArchivePath);

tempDir.Delete(recursive: true);
}
}
}
}
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Fix unzipping 4GB+ zip files by adamsitnik · Pull Request #77181 · dotnet/runtime · GitHub
Skip to content
Merged
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 numberDiff line numberDiff line change
Expand Up@@ -199,30 +199,55 @@ private static bool TryGetZip64BlockFromGenericExtraField(ZipGenericExtraField e
if (extraField.Size < sizeof(long))
return true;

long value64 = reader.ReadInt64();
// Advancing the stream (by reading from it) is possible only when:
// 1. There is an explicit ask to do that (valid files, corresponding boolean flag(s) set to true).
// 2. When the size indicates that all the information is available ("slightly invalid files").
bool readAllFields = extraField.Size >= sizeof(long) + sizeof(long) + sizeof(long) + sizeof(int);

if (readUncompressedSize)
zip64Block._uncompressedSize = value64;
{
zip64Block._uncompressedSize = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For my own education: is there a perf improvement when using _ = ReadInt64(); to discard the value, compared to just not using _? Or is the idea here to explicitly signal that we are ignoring the value on purpose?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It shouldn't have a runtime effect, it's self documenting, both for humans and also for linters/analyzers that might otherwise flag an unused method result.

}

if (ms.Position > extraField.Size - sizeof(long))
return true;

value64 = reader.ReadInt64();
if (readCompressedSize)
zip64Block._compressedSize = value64;
{
zip64Block._compressedSize = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();
}

if (ms.Position > extraField.Size - sizeof(long))
return true;
Comment thread
jozkee marked this conversation as resolved.

value64 = reader.ReadInt64();
if (readLocalHeaderOffset)
zip64Block._localHeaderOffset = value64;
{
zip64Block._localHeaderOffset = reader.ReadInt64();
}
else if (readAllFields)
{
_ = reader.ReadInt64();
}

if (ms.Position > extraField.Size - sizeof(int))
return true;

int value32 = reader.ReadInt32();
if (readStartDiskNumber)
zip64Block._startDiskNumber = value32;
{
zip64Block._startDiskNumber = reader.ReadInt32();
}
else if (readAllFields)
{
_ = reader.ReadInt32();
}

// original values are unsigned, so implies value is too big to fit in signed integer
if (zip64Block._uncompressedSize < 0) throw new InvalidDataException(SR.FieldTooBigUncompressedSize);

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.

These validations only occur if extraField.Size >= 28 since you are now returning if there's less than that.

Shouldn't we validate when reader.ReadInt* is called and/or if read of the field was requested?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This is how it always was, I would prefer not to do it now as I intend to backport it to 7.0 and trying to make it as defensive/safe as possible.

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.

The reason I was asking is because it wasn't always like that. It was changed to how it is now on #68106, which is the PR that introduced the regression.

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,7 @@
<Compile Include="ZipArchive\zip_InvalidParametersAndStrangeFiles.cs" />
<Compile Include="ZipArchive\zip_ManualAndCompatibilityTests.cs" />
<Compile Include="ZipArchive\zip_netcoreappTests.cs" />
<Compile Include="ZipArchive\zip_LargeFiles.cs" />
<Compile Include="ZipArchive\zip_ReadTests.cs" />
<Compile Include="ZipArchive\zip_UpdateTests.cs" />
<Compile Include="ZipArchive\zip_UpdateTests.Comments.cs" />
Expand All@@ -36,6 +37,7 @@
<Compile Include="$(CommonTestPath)System\IO\Compression\StreamHelpers.cs" Link="Common\System\IO\Compression\StreamHelpers.cs" />
<Compile Include="$(CommonTestPath)System\IO\TempFile.cs" Link="Common\System\IO\TempFile.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\ZipTestHelper.cs" Link="Common\System\IO\Compression\ZipTestHelper.cs" />
<Compile Include="$(CommonTestPath)TestUtilities\System\DisableParallelization.cs" Link="Common\TestUtilities\System\DisableParallelization.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs" Link="Common\System\Threading\Tasks\TaskToApm.cs" />
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Linq;
using Xunit;

namespace System.IO.Compression.Tests
{
[Collection(nameof(DisableParallelization))]
public class zip_LargeFiles : ZipFileTestBase
{
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsSpeedOptimized), nameof(PlatformDetection.Is64BitProcess))] // don't run it on slower runtimes
[OuterLoop("It requires almost 12 GB of free disk space")]
public static void UnzipOver4GBZipFile()
Comment thread
adamsitnik marked this conversation as resolved.
{
byte[] buffer = GC.AllocateUninitializedArray<byte>(1_000_000_000); // 1 GB

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.

Technically, this is not 1Gb, but since you are creating 6 files, the test is still valid.

I would rather create two tests, one that succeeds at the limit (say 3.999 Gb) without the fix and one that fails and then verify that both pass with your fix.

Since this is urgent for a backport, we can defer that.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Technically, this is not 1Gb, but since you are creating 6 files, the test is still valid.

You mean that $1000^3$ is not 1 GB but $1024^3$ is?

I would rather create two tests, one that succeeds at the limit (say 3.999 Gb) without the fix and one that fails and then verify that both pass with your fix.

My typical workflow is to add test that fails first, then make it pass. We have plenty of tests that verify < 4 GB archives so I don't see the value in adding it (also it would require us to use even more disk space and take longer to execute).

In the long term we could add more tests, especially such that verify that decompress(compress(x)) == x (which this test avoids, as it was taking 10 minutes to do so on my beefy PC).

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.

You mean that $1000^3$ is not 1 GB but $1024^3$ is?

Yes.

We have plenty of tests that verify < 4 GB archives so I don't see the value in adding it (also it would require us to use even more disk space and take longer to execute).

I meant to verify on the edge cases of the bug. In this case that would be 4Gb - 1, 4Gb, and 4Gb + 1; rather than a 6Gb case. But yes, that would multiply test execution time which is already quite large with this new test.

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.

Is there a reasonable chance this will OOM? If so, consider catching that and skipping the test, rather than failing it.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Is there a reasonable chance this will OOM?

It should be executed only in Outerloop runs for the 64 bit "fast runtimes" and not in parallel with other tests so I don't think it's needed (at least for now).

[ConditionalFact(typeof(PlatformDetection),nameof(PlatformDetection.IsSpeedOptimized),nameof(PlatformDetection.Is64BitProcess))]// don't run it on slower runtimes

publicstaticboolIsSpeedOptimized=>!IsSizeOptimized;
publicstaticboolIsSizeOptimized=>IsBrowser||IsAndroid||IsAppleMobile;


string zipArchivePath = Path.Combine(Path.GetTempPath(), "over4GB.zip");
DirectoryInfo tempDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "over4GB"));

try
{
for (byte i = 0; i < 6; i++)
{
File.WriteAllBytes(Path.Combine(tempDir.FullName, $"{i}.test"), buffer);
}

ZipFile.CreateFromDirectory(tempDir.FullName, zipArchivePath, CompressionLevel.NoCompression, includeBaseDirectory: false);

using ZipArchive zipArchive = ZipFile.OpenRead(zipArchivePath);
foreach (ZipArchiveEntry entry in zipArchive.Entries)
{
using Stream entryStream = entry.Open();

Assert.True(entryStream.CanRead);
Assert.Equal(buffer.Length, entryStream.Length);
}
}
finally
{
File.Delete(zipArchivePath);

tempDir.Delete(recursive: true);
}
}
}
}