diff options
author | github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> | 2022-11-03 03:25:15 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-03 03:25:15 +0300 |
commit | 00d3109a0ead8b11243ee8544ab820e2d60df286 (patch) | |
tree | 6564132185715b8a7366b999896f8a57b2552cef | |
parent | 328f53c8ccb56906c12ffe5e1dec3cf54b4c5044 (diff) |
[release/7.0] Fix unzipping 4GB+ zip files (#77605)
* add a failing test
* fix
* extend the test to verify more, move it to outerloop as it takes a LOT of time to execute it
* test fewer things, but way faster so the test can be run more frequently
* add a comment
* address code review feedback
* use temp directory for temporary test files
* address code review feedback
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
3 files changed, 83 insertions, 8 deletions
diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipBlocks.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipBlocks.cs index 44511243b5e..fb186dfcfc9 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipBlocks.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipBlocks.cs @@ -199,30 +199,55 @@ namespace System.IO.Compression 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(); + } 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; - 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); diff --git a/src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj b/src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj index 229119e3aa3..ef1862eb0bf 100644 --- a/src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj +++ b/src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj @@ -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" /> @@ -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" /> diff --git a/src/libraries/System.IO.Compression/tests/ZipArchive/zip_LargeFiles.cs b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_LargeFiles.cs new file mode 100644 index 00000000000..d240a176b2b --- /dev/null +++ b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_LargeFiles.cs @@ -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() + { + byte[] buffer = GC.AllocateUninitializedArray<byte>(1_000_000_000); // 1 GB + + 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); + } + } + } +} |