|
1 | 1 | // Licensed to the .NET Foundation under one or more agreements.
|
2 | 2 | // The .NET Foundation licenses this file to you under the MIT license.
|
3 | 3 |
|
4 |
| -using System.Collections.Generic; |
5 |
| -using System.Linq; |
| 4 | +using System.Reflection; |
6 | 5 | using Xunit;
|
7 | 6 |
|
8 |
| -namespace System.IO.Compression.Tests |
| 7 | +namespace System.IO.Compression.Tests; |
| 8 | + |
| 9 | +[Collection(nameof(DisableParallelization))] |
| 10 | +public class zip_LargeFiles : ZipFileTestBase |
9 | 11 | {
|
10 |
| - [Collection(nameof(DisableParallelization))] |
11 |
| - public class zip_LargeFiles : ZipFileTestBase |
| 12 | + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsSpeedOptimized), nameof(PlatformDetection.Is64BitProcess))] // don't run it on slower runtimes |
| 13 | + [OuterLoop("It requires almost 12 GB of free disk space")] |
| 14 | + public static void UnzipOver4GBZipFile() |
12 | 15 | {
|
13 |
| - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsSpeedOptimized), nameof(PlatformDetection.Is64BitProcess))] // don't run it on slower runtimes |
14 |
| - [OuterLoop("It requires almost 12 GB of free disk space")] |
15 |
| - public static void UnzipOver4GBZipFile() |
| 16 | + byte[] buffer = GC.AllocateUninitializedArray<byte>(1_000_000_000); // 1 GB |
| 17 | + |
| 18 | + string zipArchivePath = Path.Combine(Path.GetTempPath(), "over4GB.zip"); |
| 19 | + DirectoryInfo tempDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "over4GB")); |
| 20 | + |
| 21 | + try |
| 22 | + { |
| 23 | + for (byte i = 0; i < 6; i++) |
| 24 | + { |
| 25 | + File.WriteAllBytes(Path.Combine(tempDir.FullName, $"{i}.test"), buffer); |
| 26 | + } |
| 27 | + |
| 28 | + ZipFile.CreateFromDirectory(tempDir.FullName, zipArchivePath, CompressionLevel.NoCompression, includeBaseDirectory: false); |
| 29 | + |
| 30 | + using ZipArchive zipArchive = ZipFile.OpenRead(zipArchivePath); |
| 31 | + foreach (ZipArchiveEntry entry in zipArchive.Entries) |
| 32 | + { |
| 33 | + using Stream entryStream = entry.Open(); |
| 34 | + |
| 35 | + Assert.True(entryStream.CanRead); |
| 36 | + Assert.Equal(buffer.Length, entryStream.Length); |
| 37 | + } |
| 38 | + } |
| 39 | + finally |
16 | 40 | {
|
17 |
| - byte[] buffer = GC.AllocateUninitializedArray<byte>(1_000_000_000); // 1 GB |
| 41 | + File.Delete(zipArchivePath); |
| 42 | + |
| 43 | + tempDir.Delete(recursive: true); |
| 44 | + } |
| 45 | + } |
| 46 | + |
| 47 | + private static void FillWithHardToCompressData(byte[] buffer) |
| 48 | + { |
| 49 | + Random.Shared.NextBytes(buffer); |
| 50 | + } |
18 | 51 |
|
19 |
| - string zipArchivePath = Path.Combine(Path.GetTempPath(), "over4GB.zip"); |
20 |
| - DirectoryInfo tempDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "over4GB")); |
| 52 | + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsSpeedOptimized), nameof(PlatformDetection.Is64BitProcess))] // don't run it on slower runtimes |
| 53 | + [OuterLoop("It requires 5~6 GB of free disk space and a lot of CPU time for compressed tests")] |
| 54 | + [InlineData(false)] |
| 55 | + [InlineData(true)] |
| 56 | + public static void CheckZIP64VersionIsSet_ForSmallFilesAfterBigFiles(bool isCompressed) |
| 57 | + { |
| 58 | + // issue #94899 |
| 59 | + |
| 60 | + CompressionLevel compressLevel = isCompressed ? CompressionLevel.Optimal : CompressionLevel.NoCompression; |
| 61 | + byte[] smallBuffer = GC.AllocateUninitializedArray<byte>(1000); |
| 62 | + byte[] largeBuffer = GC.AllocateUninitializedArray<byte>(1_000_000_000); // ~1 GB |
| 63 | + string zipArchivePath = Path.Combine(Path.GetTempPath(), "over4GB.zip"); |
| 64 | + string LargeFileName = "largefile"; |
| 65 | + string SmallFileName = "smallfile"; |
| 66 | + uint ZipLocalFileHeader_OffsetToVersionFromHeaderStart = 4; |
| 67 | + ushort Zip64Version = 45; |
21 | 68 |
|
22 |
| - try |
| 69 | + try |
| 70 | + { |
| 71 | + using FileStream fs = File.Open(zipArchivePath, FileMode.Create, FileAccess.ReadWrite); |
| 72 | + |
| 73 | + // Create |
| 74 | + using (ZipArchive archive = new(fs, ZipArchiveMode.Create, true)) |
23 | 75 | {
|
24 |
| - for (byte i = 0; i < 6; i++) |
| 76 | + ZipArchiveEntry file = archive.CreateEntry(LargeFileName, compressLevel); |
| 77 | + |
| 78 | + using (Stream stream = file.Open()) |
25 | 79 | {
|
26 |
| - File.WriteAllBytes(Path.Combine(tempDir.FullName, $"{i}.test"), buffer); |
| 80 | + // Write 5GB of data |
| 81 | + for (var i = 0; i < 5; i++) |
| 82 | + { |
| 83 | + if (isCompressed) |
| 84 | + { |
| 85 | + FillWithHardToCompressData(largeBuffer); |
| 86 | + } |
| 87 | + |
| 88 | + stream.Write(largeBuffer); |
| 89 | + } |
27 | 90 | }
|
28 | 91 |
|
29 |
| - ZipFile.CreateFromDirectory(tempDir.FullName, zipArchivePath, CompressionLevel.NoCompression, includeBaseDirectory: false); |
| 92 | + file = archive.CreateEntry(SmallFileName, compressLevel); |
30 | 93 |
|
31 |
| - using ZipArchive zipArchive = ZipFile.OpenRead(zipArchivePath); |
32 |
| - foreach (ZipArchiveEntry entry in zipArchive.Entries) |
| 94 | + using (Stream stream = file.Open()) |
33 | 95 | {
|
34 |
| - using Stream entryStream = entry.Open(); |
35 |
| - |
36 |
| - Assert.True(entryStream.CanRead); |
37 |
| - Assert.Equal(buffer.Length, entryStream.Length); |
| 96 | + stream.Write(smallBuffer); |
38 | 97 | }
|
39 | 98 | }
|
40 |
| - finally |
| 99 | + |
| 100 | + fs.Position = 0; |
| 101 | + |
| 102 | + // Validate |
| 103 | + using (ZipArchive archive = new(fs, ZipArchiveMode.Read)) |
41 | 104 | {
|
42 |
| - File.Delete(zipArchivePath); |
| 105 | + using var reader = new BinaryReader(fs); |
43 | 106 |
|
44 |
| - tempDir.Delete(recursive: true); |
| 107 | + FieldInfo offsetOfLHField = typeof(ZipArchiveEntry).GetField("_offsetOfLocalHeader", BindingFlags.NonPublic | BindingFlags.Instance); |
| 108 | + |
| 109 | + if (offsetOfLHField is null || offsetOfLHField.FieldType != typeof(long)) |
| 110 | + { |
| 111 | + Assert.Fail("Cannot find the private field of _offsetOfLocalHeader in ZipArchiveEntry or the type is not long. Code may be changed after the test is written."); |
| 112 | + } |
| 113 | + |
| 114 | + foreach (ZipArchiveEntry entry in archive.Entries) |
| 115 | + { |
| 116 | + fs.Position = (long)offsetOfLHField.GetValue(entry) + ZipLocalFileHeader_OffsetToVersionFromHeaderStart; |
| 117 | + ushort versionNeeded = reader.ReadUInt16(); |
| 118 | + |
| 119 | + // Version is not ZIP64 for files with Local Header at >4GB offset. |
| 120 | + Assert.Equal(Zip64Version, versionNeeded); |
| 121 | + } |
45 | 122 | }
|
46 | 123 | }
|
| 124 | + finally |
| 125 | + { |
| 126 | + File.Delete(zipArchivePath); |
| 127 | + } |
47 | 128 | }
|
48 | 129 | }
|
0 commit comments