Skip to content

Commit e3bb2f3

Browse files
authored
feat(zip): make it possible to skip header tests (#689)
1 parent aee3b44 commit e3bb2f3

File tree

10 files changed

+277
-202
lines changed

10 files changed

+277
-202
lines changed

src/ICSharpCode.SharpZipLib/Zip/ZipConstants.cs

+15
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,21 @@ public enum GeneralBitFlags
231231
/// </summary>
232232
ReservedPkware15 = 0x8000
233233
}
234+
235+
/// <summary>
236+
/// Helpers for <see cref="GeneralBitFlags"/>
237+
/// </summary>
238+
public static class GeneralBitFlagsExtensions
239+
{
240+
/// <summary>
241+
/// This is equivalent of <see cref="Enum.HasFlag"/> in .NET Core, but since the .NET FW
242+
/// version is really slow (due to un-/boxing and reflection) we use this wrapper.
243+
/// </summary>
244+
/// <param name="flagData"></param>
245+
/// <param name="flag"></param>
246+
/// <returns></returns>
247+
public static bool Includes(this GeneralBitFlags flagData, GeneralBitFlags flag) => (flag & flagData) != 0;
248+
}
234249

235250
#endregion Enumerations
236251

src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs

+77-76
Original file line numberDiff line numberDiff line change
@@ -1084,6 +1084,7 @@ public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandl
10841084
[Flags]
10851085
private enum HeaderTest
10861086
{
1087+
None = 0x0,
10871088
Extract = 0x01, // Check that this header represents an entry whose data can be extracted
10881089
Header = 0x02, // Check that this header contents are valid
10891090
}
@@ -1110,13 +1111,12 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
11101111

11111112
if (signature != ZipConstants.LocalHeaderSignature)
11121113
{
1113-
throw new ZipException(string.Format("Wrong local header signature at 0x{0:x}, expected 0x{1:x8}, actual 0x{2:x8}",
1114-
entryAbsOffset, ZipConstants.LocalHeaderSignature, signature));
1114+
throw new ZipException($"Wrong local header signature at 0x{entryAbsOffset:x}, expected 0x{ZipConstants.LocalHeaderSignature:x8}, actual 0x{signature:x8}");
11151115
}
11161116

11171117
var extractVersion = (short)(ReadLEUshort() & 0x00ff);
11181118
var localFlags = (GeneralBitFlags)ReadLEUshort();
1119-
var compressionMethod = (short)ReadLEUshort();
1119+
var compressionMethod = (CompressionMethod)ReadLEUshort();
11201120
var fileTime = (short)ReadLEUshort();
11211121
var fileDate = (short)ReadLEUshort();
11221122
uint crcValue = ReadLEUint();
@@ -1134,7 +1134,7 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
11341134
var localExtraData = new ZipExtraData(extraData);
11351135

11361136
// Extra data / zip64 checks
1137-
if (localExtraData.Find(1))
1137+
if (localExtraData.Find(headerID: 1))
11381138
{
11391139
// 2010-03-04 Forum 10512: removed checks for version >= ZipConstants.VersionZip64
11401140
// and size or compressedSize = MaxValue, due to rogue creators.
@@ -1175,15 +1175,19 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
11751175
throw new ZipException("Compression method not supported");
11761176
}
11771177

1178-
if ((extractVersion > ZipConstants.VersionMadeBy)
1179-
|| ((extractVersion > 20) && (extractVersion < ZipConstants.VersionZip64)))
1178+
if (extractVersion > ZipConstants.VersionMadeBy
1179+
|| (extractVersion > 20 && extractVersion < ZipConstants.VersionZip64))
11801180
{
1181-
throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", extractVersion));
1181+
throw new ZipException($"Version required to extract this entry not supported ({extractVersion})");
11821182
}
11831183

1184-
if (localFlags.HasAny(GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.EnhancedCompress | GeneralBitFlags.HeaderMasked))
1184+
const GeneralBitFlags notSupportedFlags = GeneralBitFlags.Patched
1185+
| GeneralBitFlags.StrongEncryption
1186+
| GeneralBitFlags.EnhancedCompress
1187+
| GeneralBitFlags.HeaderMasked;
1188+
if (localFlags.HasAny(notSupportedFlags))
11851189
{
1186-
throw new ZipException("The library does not support the zip version required to extract this entry");
1190+
throw new ZipException($"The library does not support the zip features required to extract this entry ({localFlags & notSupportedFlags:F})");
11871191
}
11881192
}
11891193
}
@@ -1207,7 +1211,7 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
12071211
(extractVersion != 63)
12081212
)
12091213
{
1210-
throw new ZipException(string.Format("Version required to extract this entry is invalid ({0})", extractVersion));
1214+
throw new ZipException($"Version required to extract this entry is invalid ({extractVersion})");
12111215
}
12121216

12131217
var localEncoding = _stringCodec.ZipInputEncoding(localFlags);
@@ -1221,7 +1225,7 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
12211225
// Encryption requires extract version >= 20
12221226
if (localFlags.HasAny(GeneralBitFlags.Encrypted) && extractVersion < 20)
12231227
{
1224-
throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion));
1228+
throw new ZipException($"Version required to extract this entry is too low for encryption ({extractVersion})");
12251229
}
12261230

12271231
// Strong encryption requires encryption flag to be set and extract version >= 50.
@@ -1234,26 +1238,26 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
12341238

12351239
if (extractVersion < 50)
12361240
{
1237-
throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion));
1241+
throw new ZipException($"Version required to extract this entry is too low for encryption ({extractVersion})");
12381242
}
12391243
}
12401244

12411245
// Patched entries require extract version >= 27
12421246
if (localFlags.HasAny(GeneralBitFlags.Patched) && extractVersion < 27)
12431247
{
1244-
throw new ZipException(string.Format("Patched data requires higher version than ({0})", extractVersion));
1248+
throw new ZipException($"Patched data requires higher version than ({extractVersion})");
12451249
}
12461250

12471251
// Central header flags match local entry flags.
12481252
if ((int)localFlags != entry.Flags)
12491253
{
1250-
throw new ZipException("Central header/local header flags mismatch");
1254+
throw new ZipException($"Central header/local header flags mismatch ({(GeneralBitFlags)entry.Flags:F} vs {localFlags:F})");
12511255
}
12521256

12531257
// Central header compression method matches local entry
1254-
if (entry.CompressionMethodForHeader != (CompressionMethod)compressionMethod)
1258+
if (entry.CompressionMethodForHeader != compressionMethod)
12551259
{
1256-
throw new ZipException("Central header/local header compression method mismatch");
1260+
throw new ZipException($"Central header/local header compression method mismatch ({entry.CompressionMethodForHeader:G} vs {compressionMethod:G})");
12571261
}
12581262

12591263
if (entry.Version != extractVersion)
@@ -1272,7 +1276,7 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
12721276

12731277
if (localFlags.HasAny(GeneralBitFlags.HeaderMasked))
12741278
{
1275-
if ((fileTime != 0) || (fileDate != 0))
1279+
if (fileTime != 0 || fileDate != 0)
12761280
{
12771281
throw new ZipException("Header masked set but date/time values non-zero");
12781282
}
@@ -1287,8 +1291,8 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
12871291
}
12881292

12891293
// Crc valid for empty entry.
1290-
// This will also apply to streamed entries where size isnt known and the header cant be patched
1291-
if ((size == 0) && (compressedSize == 0))
1294+
// This will also apply to streamed entries where size isn't known and the header cant be patched
1295+
if (size == 0 && compressedSize == 0)
12921296
{
12931297
if (crcValue != 0)
12941298
{
@@ -1351,20 +1355,15 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
13511355
if (!localFlags.HasAny(GeneralBitFlags.Descriptor) ||
13521356
((size > 0 || compressedSize > 0) && entry.Size > 0))
13531357
{
1354-
if ((size != 0)
1355-
&& (size != entry.Size))
1358+
if (size != 0 && size != entry.Size)
13561359
{
1357-
throw new ZipException(
1358-
string.Format("Size mismatch between central header({0}) and local header({1})",
1359-
entry.Size, size));
1360+
throw new ZipException($"Size mismatch between central header ({entry.Size}) and local header ({size})");
13601361
}
13611362

1362-
if ((compressedSize != 0)
1363+
if (compressedSize != 0
13631364
&& (compressedSize != entry.CompressedSize && compressedSize != 0xFFFFFFFF && compressedSize != -1))
13641365
{
1365-
throw new ZipException(
1366-
string.Format("Compressed size mismatch between central header({0}) and local header({1})",
1367-
entry.CompressedSize, compressedSize));
1366+
throw new ZipException($"Compressed size mismatch between central header({entry.CompressedSize}) and local header({compressedSize})");
13681367
}
13691368
}
13701369

@@ -3502,20 +3501,16 @@ private void ReadEntries()
35023501
}
35033502

35043503
bool isZip64 = false;
3505-
bool requireZip64 = false;
3506-
3504+
35073505
// Check if zip64 header information is required.
3508-
if ((thisDiskNumber == 0xffff) ||
3509-
(startCentralDirDisk == 0xffff) ||
3510-
(entriesForThisDisk == 0xffff) ||
3511-
(entriesForWholeCentralDir == 0xffff) ||
3512-
(centralDirSize == 0xffffffff) ||
3513-
(offsetOfCentralDir == 0xffffffff))
3514-
{
3515-
requireZip64 = true;
3516-
}
3517-
3518-
// #357 - always check for the existance of the Zip64 central directory.
3506+
bool requireZip64 = thisDiskNumber == 0xffff ||
3507+
startCentralDirDisk == 0xffff ||
3508+
entriesForThisDisk == 0xffff ||
3509+
entriesForWholeCentralDir == 0xffff ||
3510+
centralDirSize == 0xffffffff ||
3511+
offsetOfCentralDir == 0xffffffff;
3512+
3513+
// #357 - always check for the existence of the Zip64 central directory.
35193514
// #403 - Take account of the fixed size of the locator when searching.
35203515
// Subtract from locatedEndOfCentralDir so that the endLocation is the location of EndOfCentralDirectorySignature,
35213516
// rather than the data following the signature.
@@ -3549,7 +3544,7 @@ private void ReadEntries()
35493544

35503545
if (sig64 != ZipConstants.Zip64CentralFileHeaderSignature)
35513546
{
3552-
throw new ZipException(string.Format("Invalid Zip64 Central directory signature at {0:X}", offset64));
3547+
throw new ZipException($"Invalid Zip64 Central directory signature at {offset64:X}");
35533548
}
35543549

35553550
// NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12.
@@ -3604,8 +3599,11 @@ private void ReadEntries()
36043599
int extraLen = ReadLEUshort();
36053600
int commentLen = ReadLEUshort();
36063601

3607-
int diskStartNo = ReadLEUshort(); // Not currently used
3608-
int internalAttributes = ReadLEUshort(); // Not currently used
3602+
3603+
// ReSharper disable once UnusedVariable, Currently unused but needs to be read to offset the stream
3604+
int diskStartNo = ReadLEUshort();
3605+
// ReSharper disable once UnusedVariable, Currently unused but needs to be read to offset the stream
3606+
int internalAttributes = ReadLEUshort();
36093607

36103608
uint externalAttributes = ReadLEUint();
36113609
long offset = ReadLEUint();
@@ -3629,7 +3627,7 @@ private void ReadEntries()
36293627
ExternalFileAttributes = (int)externalAttributes
36303628
};
36313629

3632-
if ((bitFlags & 8) == 0)
3630+
if (!entry.HasFlag(GeneralBitFlags.Descriptor))
36333631
{
36343632
entry.CryptoCheckValue = (byte)(crc >> 24);
36353633
}
@@ -3672,9 +3670,15 @@ private void ReadEntries()
36723670
/// </exception>
36733671
private long LocateEntry(ZipEntry entry)
36743672
{
3675-
return TestLocalHeader(entry, HeaderTest.Extract);
3673+
return TestLocalHeader(entry, SkipLocalEntryTestsOnLocate ? HeaderTest.None : HeaderTest.Extract);
36763674
}
36773675

3676+
/// <summary>
3677+
/// Skip the verification of the local header when reading an archive entry. Set this to attempt to read the
3678+
/// entries even if the headers should indicate that doing so would fail or produce an unexpected output.
3679+
/// </summary>
3680+
public bool SkipLocalEntryTestsOnLocate { get; set; } = false;
3681+
36783682
private Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry)
36793683
{
36803684
CryptoStream result = null;
@@ -3691,15 +3695,15 @@ private Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry)
36913695
}
36923696
int saltLen = entry.AESSaltLen;
36933697
byte[] saltBytes = new byte[saltLen];
3694-
int saltIn = StreamUtils.ReadRequestedBytes(baseStream, saltBytes, 0, saltLen);
3695-
if (saltIn != saltLen)
3696-
throw new ZipException("AES Salt expected " + saltLen + " got " + saltIn);
3697-
//
3698+
int saltIn = StreamUtils.ReadRequestedBytes(baseStream, saltBytes, offset: 0, saltLen);
3699+
3700+
if (saltIn != saltLen) throw new ZipException($"AES Salt expected {saltLen} git {saltIn}");
3701+
36983702
byte[] pwdVerifyRead = new byte[2];
36993703
StreamUtils.ReadFully(baseStream, pwdVerifyRead);
37003704
int blockSize = entry.AESKeySize / 8; // bits to bytes
37013705

3702-
var decryptor = new ZipAESTransform(rawPassword_, saltBytes, blockSize, false);
3706+
var decryptor = new ZipAESTransform(rawPassword_, saltBytes, blockSize, writeMode: false);
37033707
byte[] pwdVerifyCalc = decryptor.PwdVerifier;
37043708
if (pwdVerifyCalc[0] != pwdVerifyRead[0] || pwdVerifyCalc[1] != pwdVerifyRead[1])
37053709
throw new ZipException("Invalid password for AES");
@@ -3712,8 +3716,7 @@ private Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry)
37123716
}
37133717
else
37143718
{
3715-
if ((entry.Version < ZipConstants.VersionStrongEncryption)
3716-
|| (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0)
3719+
if (entry.Version < ZipConstants.VersionStrongEncryption || !entry.HasFlag(GeneralBitFlags.StrongEncryption))
37173720
{
37183721
var classicManaged = new PkzipClassicManaged();
37193722

@@ -3738,31 +3741,29 @@ private Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry)
37383741

37393742
private Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry)
37403743
{
3741-
CryptoStream result = null;
3742-
if ((entry.Version < ZipConstants.VersionStrongEncryption)
3743-
|| (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0)
3744-
{
3745-
var classicManaged = new PkzipClassicManaged();
3744+
if (entry.Version >= ZipConstants.VersionStrongEncryption &&
3745+
entry.HasFlag(GeneralBitFlags.StrongEncryption)) return null;
37463746

3747-
OnKeysRequired(entry.Name);
3748-
if (HaveKeys == false)
3749-
{
3750-
throw new ZipException("No password available for encrypted stream");
3751-
}
3747+
var classicManaged = new PkzipClassicManaged();
37523748

3753-
// Closing a CryptoStream will close the base stream as well so wrap it in an UncompressedStream
3754-
// which doesnt do this.
3755-
result = new CryptoStream(new UncompressedStream(baseStream),
3756-
classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write);
3749+
OnKeysRequired(entry.Name);
3750+
if (HaveKeys == false)
3751+
{
3752+
throw new ZipException("No password available for encrypted stream");
3753+
}
37573754

3758-
if ((entry.Crc < 0) || (entry.Flags & 8) != 0)
3759-
{
3760-
WriteEncryptionHeader(result, entry.DosTime << 16);
3761-
}
3762-
else
3763-
{
3764-
WriteEncryptionHeader(result, entry.Crc);
3765-
}
3755+
// Closing a CryptoStream will close the base stream as well so wrap it in an UncompressedStream
3756+
// which doesnt do this.
3757+
var result = new CryptoStream(new UncompressedStream(baseStream),
3758+
classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write);
3759+
3760+
if (entry.Crc < 0 || entry.HasFlag(GeneralBitFlags.Descriptor))
3761+
{
3762+
WriteEncryptionHeader(result, entry.DosTime << 16);
3763+
}
3764+
else
3765+
{
3766+
WriteEncryptionHeader(result, entry.Crc);
37663767
}
37673768
return result;
37683769
}
@@ -3785,7 +3786,7 @@ private static void WriteEncryptionHeader(Stream stream, long crcValue)
37853786
rng.GetBytes(cryptBuffer);
37863787
}
37873788
cryptBuffer[11] = (byte)(crcValue >> 24);
3788-
stream.Write(cryptBuffer, 0, cryptBuffer.Length);
3789+
stream.Write(cryptBuffer, offset: 0, cryptBuffer.Length);
37893790
}
37903791

37913792
#endregion Internal routines

test/ICSharpCode.SharpZipLib.Tests/TestSupport/Utils.cs

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using System;
33
using System.IO;
44
using System.Linq;
5-
using System.Text;
65
using System.Threading.Tasks;
76

87
namespace ICSharpCode.SharpZipLib.Tests.TestSupport

0 commit comments

Comments
 (0)