Skip to content

Commit fbad143

Browse files
authored
Merge pull request haf#184 from darkms/fix-zip64-headers
Fix central directory headers when Zip64 extension is applied
2 parents 2cd8a3c + a9cc4bd commit fbad143

File tree

3 files changed

+271
-20
lines changed

3 files changed

+271
-20
lines changed

src/Zip Tests/IonicTestClass.cs

+14-6
Original file line numberDiff line numberDiff line change
@@ -304,15 +304,23 @@ protected bool WinZipIsPresent
304304
{
305305
if (_WinZipIsPresent == null)
306306
{
307-
string progfiles = null;
308-
if (_wzunzip == null || _wzzip == null)
307+
var programFilesRoots = new[]
309308
{
310-
progfiles = System.Environment.GetEnvironmentVariable("ProgramFiles(x86)");
311-
_wzunzip = Path.Combine(progfiles, "winzip\\wzunzip.exe");
312-
_wzzip = Path.Combine(progfiles, "winzip\\wzzip.exe");
309+
System.Environment.GetEnvironmentVariable("ProgramFiles(x86)"),
310+
System.Environment.GetEnvironmentVariable("ProgramW6432")
311+
};
312+
foreach (var programFiles in programFilesRoots)
313+
{
314+
_wzunzip = Path.Combine(programFiles, "winzip\\wzunzip.exe");
315+
_wzzip = Path.Combine(programFiles, "winzip\\wzzip.exe");
316+
_WinZipIsPresent = File.Exists(_wzunzip) && File.Exists(_wzzip);
317+
if (_WinZipIsPresent.Value)
318+
{
319+
break;
320+
}
313321
}
314-
_WinZipIsPresent = new Nullable<bool>(File.Exists(_wzunzip) && File.Exists(_wzzip));
315322
}
323+
316324
return _WinZipIsPresent.Value;
317325
}
318326
}

src/Zip Tests/SplitArchives.cs

+189-2
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,150 @@ public void Spanned_WinZip_Unzip_wi13691()
722722
}
723723
}
724724

725+
public void WinZip_Unzip(string zipFilePath, string extractDir)
726+
{
727+
var args = string.Format("-d -yx {0} \"{1}\"",
728+
zipFilePath, extractDir);
729+
Exec(wzunzip, args);
730+
}
731+
732+
public void SevenZip_Unzip(string zipFilePath, string extractDir)
733+
{
734+
var args = string.Format("x {0} -o\"{1}\"",
735+
zipFilePath, extractDir);
736+
var output = Exec(sevenZip, args);
737+
Assert.IsTrue(
738+
output.IndexOf("error", StringComparison.OrdinalIgnoreCase) == -1,
739+
"Output contains errors: " + output);
740+
}
741+
742+
[TestMethod]
743+
[Timeout(15 * 60 * 1000)]
744+
public void Spanned_Zip64Always_HugeFiles_WinZip_Unzip()
745+
{
746+
if (!WinZipIsPresent)
747+
throw new Exception("winzip is not present");
748+
749+
Test_SpannedZip64_Unzip_Compatibility(
750+
// 6GB files (over default non-Zip64 '4GB' limit)
751+
6 * 1024 * 1024 * 1024L,
752+
// 5GB span (over default non-Zip64 '4GB' limit)
753+
5 * 1024 * 1024 * 1024L,
754+
Zip64Option.Always,
755+
WinZip_Unzip);
756+
}
757+
758+
[TestMethod]
759+
[Timeout(15 * 60 * 1000)]
760+
public void Spanned_Zip64Always_SmallFiles_WinZip_Unzip()
761+
{
762+
if (!WinZipIsPresent)
763+
throw new Exception("winzip is not present");
764+
765+
Test_SpannedZip64_Unzip_Compatibility(
766+
// 2MB files
767+
2 * 1024 * 1024L,
768+
// 1MB span
769+
1 * 1024 * 1024L,
770+
Zip64Option.Always,
771+
WinZip_Unzip);
772+
}
773+
774+
[TestMethod]
775+
[Timeout(15 * 60 * 1000)]
776+
public void Spanned_Zip64AsNecessary_SmallFiles_WinZip_Unzip()
777+
{
778+
if (!WinZipIsPresent)
779+
throw new Exception("winzip is not present");
780+
781+
Test_SpannedZip64_Unzip_Compatibility(
782+
// 2MB files
783+
2 * 1024 * 1024L,
784+
// 1MB span
785+
1 * 1024 * 1024L,
786+
Zip64Option.AsNecessary,
787+
WinZip_Unzip);
788+
}
789+
790+
[TestMethod]
791+
[Timeout(15 * 60 * 1000)]
792+
public void Spanned_Zip64Never_SmallFiles_WinZip_Unzip()
793+
{
794+
if (!WinZipIsPresent)
795+
throw new Exception("winzip is not present");
796+
797+
Test_SpannedZip64_Unzip_Compatibility(
798+
// 2MB files
799+
2 * 1024 * 1024L,
800+
// 1MB span
801+
1 * 1024 * 1024L,
802+
Zip64Option.Never,
803+
WinZip_Unzip);
804+
}
805+
806+
[TestMethod]
807+
[Timeout(15 * 60 * 1000)]
808+
public void Spanned_Zip64Always_HugeFiles_7Zip_Unzip()
809+
{
810+
if (!SevenZipIsPresent)
811+
throw new Exception("7-zip is not present");
812+
813+
Test_SpannedZip64_Unzip_Compatibility(
814+
// 6GB files (over default non-Zip64 '4GB' limit)
815+
6 * 1024 * 1024 * 1024L,
816+
// 5GB span (over default non-Zip64 '4GB' limit)
817+
5 * 1024 * 1024 * 1024L,
818+
Zip64Option.Always,
819+
SevenZip_Unzip);
820+
}
821+
822+
[TestMethod]
823+
[Timeout(15 * 60 * 1000)]
824+
public void Spanned_Zip64Always_SmallFiles_7Zip_Unzip()
825+
{
826+
if (!SevenZipIsPresent)
827+
throw new Exception("7-zip is not present");
828+
829+
Test_SpannedZip64_Unzip_Compatibility(
830+
// 2MB files
831+
2 * 1024 * 1024L,
832+
// 1MB span
833+
1 * 1024 * 1024L,
834+
Zip64Option.Always,
835+
SevenZip_Unzip);
836+
}
837+
838+
[TestMethod]
839+
[Timeout(15 * 60 * 1000)]
840+
public void Spanned_Zip64AsNecessary_SmallFiles_7Zip_Unzip()
841+
{
842+
if (!SevenZipIsPresent)
843+
throw new Exception("7-zip is not present");
844+
845+
Test_SpannedZip64_Unzip_Compatibility(
846+
// 2MB files
847+
2 * 1024 * 1024L,
848+
// 1MB span
849+
1 * 1024 * 1024L,
850+
Zip64Option.AsNecessary,
851+
SevenZip_Unzip);
852+
}
853+
854+
[TestMethod]
855+
[Timeout(15 * 60 * 1000)]
856+
public void Spanned_Zip64Never_SmallFiles_7Zip_Unzip()
857+
{
858+
if (!SevenZipIsPresent)
859+
throw new Exception("7-zip is not present");
860+
861+
Test_SpannedZip64_Unzip_Compatibility(
862+
// 2MB files
863+
2 * 1024 * 1024L,
864+
// 1MB span
865+
1 * 1024 * 1024L,
866+
Zip64Option.Never,
867+
SevenZip_Unzip);
868+
}
725869

726870
#if INFOZIP_UNZIP_SUPPORTS_SPLIT_ARCHIVES
727871

@@ -881,10 +1025,53 @@ public void Spanned_InfoZip_Zip_wi13691()
8811025
Assert.AreEqual<int>(filesToAdd.Count, filesUnzipped.Length,
8821026
"Incorrect number of files extracted, trail {0}", k);
8831027
}
884-
8851028
}
8861029

1030+
private void Test_SpannedZip64_Unzip_Compatibility(
1031+
long fileSize,
1032+
long spanSize,
1033+
Zip64Option zip64Option,
1034+
Action<string, string> unzipAction)
1035+
{
1036+
TestContext.WriteLine("Creating fodder files... {0}",
1037+
DateTime.Now.ToString("G"));
1038+
CreateSomeFiles();
1039+
1040+
var file1Path = Path.Combine(_fodderDir, "1.dat");
1041+
var file2Path = Path.Combine(_fodderDir, "2.dat");
8871042

888-
}
1043+
foreach (var filePath in new[] { file1Path, file2Path })
1044+
{
1045+
using (var file = File.Create(filePath))
1046+
{
1047+
file.Seek(fileSize, SeekOrigin.Begin);
1048+
file.Write(new byte[] { 1 }, 0, 1);
1049+
}
1050+
}
8891051

1052+
Directory.CreateDirectory("zip-output");
1053+
var zipFilePath = Path.Combine("zip-output", "archive.zip");
1054+
1055+
using (var zipFile = new ZipFile())
1056+
{
1057+
zipFile.UseZip64WhenSaving = zip64Option;
1058+
// disable compression to make sure out 0-filled files would keep their size
1059+
zipFile.CompressionLevel = Zlib.CompressionLevel.None;
1060+
zipFile.MaxOutputSegmentSize64 = spanSize;
1061+
1062+
zipFile.AddFile(file1Path, "");
1063+
zipFile.AddFile(file2Path, "");
1064+
1065+
zipFile.Save(zipFilePath);
1066+
}
1067+
1068+
var extractDir = "extract";
1069+
Directory.CreateDirectory(extractDir);
1070+
1071+
unzipAction(zipFilePath, extractDir);
1072+
1073+
string[] filesUnzipped = Directory.GetFiles(extractDir);
1074+
Assert.AreEqual(2, filesUnzipped.Length, "Incorrect number of files extracted");
1075+
}
1076+
}
8901077
}

src/Zip.Shared/ZipEntry.Write.cs

+68-12
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,59 @@ internal void WriteCentralDirectoryEntry(Stream s)
192192
(this._container.ZipFile.MaxOutputSegmentSize64 != 0);
193193
if (segmented) // workitem 13915
194194
{
195-
// Emit nonzero disknumber only if saving segmented archive.
196-
bytes[i++] = (byte)(_diskNumber & 0x00FF);
197-
bytes[i++] = (byte)((_diskNumber & 0xFF00) >> 8);
195+
if (_presumeZip64 || _diskNumber > 0xFFFF)
196+
{
197+
/*
198+
* From spec:
199+
4.5.3 -Zip64 Extended Information Extra Field (0x0001):
200+
201+
The following is the layout of the zip64 extended
202+
information "extra" block. If one of the size or
203+
offset fields in the Local or Central directory
204+
record is too small to hold the required data,
205+
a Zip64 extended information record is created.
206+
The order of the fields in the zip64 extended
207+
information record is fixed, but the fields MUST
208+
only appear if the corresponding Local or Central
209+
directory record field is set to 0xFFFF or 0xFFFFFFFF.
210+
211+
Note: all fields stored in Intel low-byte/high-byte order.
212+
213+
Value Size Description
214+
----- ---- -----------
215+
(ZIP64) 0x0001 2 bytes Tag for this "extra" block type
216+
Size 2 bytes Size of this "extra" block
217+
Original
218+
Size 8 bytes Original uncompressed file size
219+
Compressed
220+
Size 8 bytes Size of compressed data
221+
Relative Header
222+
Offset 8 bytes Offset of local header record
223+
Disk Start
224+
Number 4 bytes Number of the disk on which
225+
this file starts
226+
227+
*
228+
* As of 2019, major tools actually enforce this constraint and
229+
* would display warnings (7-Zip) or even not unzip archives (WinZip)
230+
* that have a header set in Zip64, but doesn't have -1 value in
231+
* appropriate fields of the record itself.
232+
*
233+
* Currently we always set 'Relative Header' and 'Disk Start' inside
234+
* the Extra block for Zip64, meaning that we also need to make sure
235+
* that their non-Zip64 counterparts would be -1 (0xFFFFFFFF
236+
* and 0xFFFF respectively), regardless of the fact if the value fits
237+
* into uint32/uint16 range or not.
238+
*/
239+
bytes[i++] = 0xFF;
240+
bytes[i++] = 0xFF;
241+
}
242+
else
243+
{
244+
// Emit nonzero disknumber only if saving segmented archive.
245+
bytes[i++] = (byte)(_diskNumber & 0x00FF);
246+
bytes[i++] = (byte)((_diskNumber & 0xFF00) >> 8);
247+
}
198248
}
199249
else
200250
{
@@ -220,14 +270,19 @@ internal void WriteCentralDirectoryEntry(Stream s)
220270
// workitem 11131
221271
// relative offset of local header.
222272
//
223-
// If necessary to go to 64-bit value, then emit 0xFFFFFFFF,
224-
// else write out the value.
225-
//
226-
// Even if zip64 is required for other reasons - number of the entry
227-
// > 65534, or uncompressed size of the entry > MAX_INT32, the ROLH
228-
// need not be stored in a 64-bit field .
229-
if (_RelativeOffsetOfLocalHeader > 0xFFFFFFFFL) // _OutputUsesZip64.Value
230-
{
273+
// If necessary to go to 64-bit value or when Zip64 is required, then
274+
// emit 0xFFFFFFFF, else write out the value.
275+
if (_presumeZip64 || _RelativeOffsetOfLocalHeader > 0xFFFFFFFFL)
276+
{
277+
/*
278+
* See spec & more details above in _diskNumber section
279+
*
280+
* Currently we always set 'Relative Header' and 'Disk Start' inside
281+
* the Extra block for Zip64, meaning that we also need to make sure
282+
* that their non-Zip64 counterparts would be -1 (0xFFFFFFFF
283+
* and 0xFFFF respectively), regardless of the fact if the value fits
284+
* into uint32/uint16 range or not.
285+
*/
231286
bytes[i++] = 0xFF;
232287
bytes[i++] = 0xFF;
233288
bytes[i++] = 0xFF;
@@ -369,8 +424,9 @@ private byte[] ConstructExtraField(bool forCentralDirectory)
369424
i += 8;
370425

371426
// starting disk number
372-
Array.Copy(BitConverter.GetBytes(0), 0, block, i, 4);
427+
Array.Copy(BitConverter.GetBytes(_diskNumber), 0, block, i, 4);
373428
}
429+
374430
listOfBlocks.Add(block);
375431
}
376432

0 commit comments

Comments
 (0)