private long TestLocalHeader(ZipEntry entry, HeaderTest tests) {
lock (baseStream_) {
bool testHeader=(tests&HeaderTest.Header)!=0;
bool testData=(tests&HeaderTest.Extract)!=0;
baseStream_.Seek(offsetOfFirstEntry+entry.Offset, SeekOrigin.Begin);
if ((int)ReadLEUint()!=ZipConstants.LocalHeaderSignature) {
throw new ZipException(string.Format("Wrong local header signature @{0:X}",
offsetOfFirstEntry+entry.Offset));
}
var extractVersion=(short)(ReadLEUshort()&0x00ff);
var localFlags=(short)ReadLEUshort();
var compressionMethod=(short)ReadLEUshort();
var fileTime=(short)ReadLEUshort();
var fileDate=(short)ReadLEUshort();
uint crcValue=ReadLEUint();
long compressedSize=ReadLEUint();
long size=ReadLEUint();
int storedNameLength=ReadLEUshort();
int extraDataLength=ReadLEUshort();
var nameData=new byte[storedNameLength];
StreamUtils.ReadFully(baseStream_, nameData);
var extraData=new byte[extraDataLength];
StreamUtils.ReadFully(baseStream_, extraData);
var localExtraData=new ZipExtraData(extraData);
// Extra data / zip64 checks
if (localExtraData.Find(1)) {
// 2010-03-04 Forum 10512: removed checks for version >= ZipConstants.VersionZip64
// and size or compressedSize = MaxValue, due to rogue creators.
size=localExtraData.ReadLong();
compressedSize=localExtraData.ReadLong();
if ((localFlags&(int)GeneralBitFlags.Descriptor)!=0) {
// These may be valid if patched later
if ((size!=-1)&&(size!=entry.Size)) {
throw new ZipException("Size invalid for descriptor");
}
if ((compressedSize!=-1)&&(compressedSize!=entry.CompressedSize)) {
throw new ZipException("Compressed size invalid for descriptor");
}
}
} else {
// No zip64 extra data but entry requires it.
if ((extractVersion>=ZipConstants.VersionZip64)&&
(((uint)size==uint.MaxValue)||((uint)compressedSize==uint.MaxValue))) {
throw new ZipException("Required Zip64 extended information missing");
}
}
if (testData) {
if (entry.IsFile) {
if (!entry.IsCompressionMethodSupported()) {
throw new ZipException("Compression method not supported");
}
if ((extractVersion>ZipConstants.VersionMadeBy)
||((extractVersion>20)&&(extractVersion<ZipConstants.VersionZip64))) {
throw new ZipException(
string.Format("Version required to extract this entry not supported ({0})",
extractVersion));
}
if ((localFlags&
(int)
(GeneralBitFlags.Patched|GeneralBitFlags.StrongEncryption|
GeneralBitFlags.EnhancedCompress|GeneralBitFlags.HeaderMasked))!=0) {
throw new ZipException(
"The library does not support the zip version required to extract this entry");
}
}
}
if (testHeader) {
if ((extractVersion<=63)&& // Ignore later versions as we dont know about them..
(extractVersion!=10)&&
(extractVersion!=11)&&
(extractVersion!=20)&&
(extractVersion!=21)&&
(extractVersion!=25)&&
(extractVersion!=27)&&
(extractVersion!=45)&&
(extractVersion!=46)&&
(extractVersion!=50)&&
(extractVersion!=51)&&
(extractVersion!=52)&&
(extractVersion!=61)&&
(extractVersion!=62)&&
(extractVersion!=63)
) {
throw new ZipException(string.Format("Version required to extract this entry is invalid ({0})",
extractVersion));
}
// Local entry flags dont have reserved bit set on.
if ((localFlags&
(int)
(GeneralBitFlags.ReservedPKware4|GeneralBitFlags.ReservedPkware14|
GeneralBitFlags.ReservedPkware15))!=0) {
throw new ZipException("Reserved bit flags cannot be set.");
}
// Encryption requires extract version >= 20
if (((localFlags&(int)GeneralBitFlags.Encrypted)!=0)&&(extractVersion<20)) {
throw new ZipException(
string.Format("Version required to extract this entry is too low for encryption ({0})",
extractVersion));
}
// Strong encryption requires encryption flag to be set and extract version >= 50.
if ((localFlags&(int)GeneralBitFlags.StrongEncryption)!=0) {
if ((localFlags&(int)GeneralBitFlags.Encrypted)==0) {
throw new ZipException("Strong encryption flag set but encryption flag is not set");
}
if (extractVersion<50) {
throw new ZipException(
string.Format("Version required to extract this entry is too low for encryption ({0})",
extractVersion));
}
}
// Patched entries require extract version >= 27
if (((localFlags&(int)GeneralBitFlags.Patched)!=0)&&(extractVersion<27)) {
throw new ZipException(string.Format("Patched data requires higher version than ({0})",
extractVersion));
}
// Central header flags match local entry flags.
if (localFlags!=entry.Flags) {
throw new ZipException("Central header/local header flags mismatch");
}
// Central header compression method matches local entry
if (entry.CompressionMethod!=(CompressionMethod)compressionMethod) {
throw new ZipException("Central header/local header compression method mismatch");
}
if (entry.Version!=extractVersion) {
throw new ZipException("Extract version mismatch");
}
// Strong encryption and extract version match
if ((localFlags&(int)GeneralBitFlags.StrongEncryption)!=0) {
if (extractVersion<62) {
throw new ZipException("Strong encryption flag set but version not high enough");
}
}
if ((localFlags&(int)GeneralBitFlags.HeaderMasked)!=0) {
if ((fileTime!=0)||(fileDate!=0)) {
throw new ZipException("Header masked set but date/time values non-zero");
}
}
if ((localFlags&(int)GeneralBitFlags.Descriptor)==0) {
if (crcValue!=(uint)entry.Crc) {
throw new ZipException("Central header/local header crc mismatch");
}
}
// Crc valid for empty entry.
// This will also apply to streamed entries where size isnt known and the header cant be patched
if ((size==0)&&(compressedSize==0)) {
if (crcValue!=0) {
throw new ZipException("Invalid CRC for empty entry");
}
}
// TODO: make test more correct... can't compare lengths as was done originally as this can fail for MBCS strings
// Assuming a code page at this point is not valid? Best is to store the name length in the ZipEntry probably
if (entry.Name.Length>storedNameLength) {
throw new ZipException("File name length mismatch");
}
// Name data has already been read convert it and compare.
string localName=ZipConstants.ConvertToStringExt(localFlags, nameData);
// Central directory and local entry name match
if (localName!=entry.Name) {
throw new ZipException("Central header and local header file name mismatch");
}
// Directories have zero actual size but can have compressed size
if (entry.IsDirectory) {
if (size>0) {
throw new ZipException("Directory cannot have size");
}
// There may be other cases where the compressed size can be greater than this?
// If so until details are known we will be strict.
if (entry.IsCrypted) {
if (compressedSize>ZipConstants.CryptoHeaderSize+2) {
throw new ZipException("Directory compressed size invalid");
}
} else if (compressedSize>2) {
// When not compressed the directory size can validly be 2 bytes
// if the true size wasnt known when data was originally being written.
// NOTE: Versions of the library 0.85.4 and earlier always added 2 bytes
throw new ZipException("Directory compressed size invalid");
}
}
if (!ZipNameTransform.IsValidName(localName, true)) {
throw new ZipException("Name is invalid");
}
}
// Tests that apply to both data and header.
// Size can be verified only if it is known in the local header.
// it will always be known in the central header.
if (((localFlags&(int)GeneralBitFlags.Descriptor)==0)||
((size>0)||(compressedSize>0))) {
if (size!=entry.Size) {
throw new ZipException(
string.Format("Size mismatch between central header({0}) and local header({1})",
entry.Size, size));
}
if (compressedSize!=entry.CompressedSize&&
compressedSize!=0xFFFFFFFF&&compressedSize!=-1) {
throw new ZipException(
string.Format("Compressed size mismatch between central header({0}) and local header({1})",
entry.CompressedSize, compressedSize));
}
}
int extraLength=storedNameLength+extraDataLength;
return offsetOfFirstEntry+entry.Offset+ZipConstants.LocalHeaderBaseSize+extraLength;
}
}