/// <summary> /// Process extra data fields updating the entry based on the contents. /// </summary> /// <param name="localHeader">True if the extra data fields should be handled /// for a local header, rather than for a central header. /// </param> internal void ProcessExtraData(bool localHeader) { var extraData = new ZipExtraData(extra); if ( extraData.Find(0x0001) ) { // Version required to extract is ignored here as some archivers dont set it correctly // in theory it should be version 45 or higher // The recorded size will change but remember that this is zip64. forceZip64_ = true; if ( extraData.ValueLength < 4 ) { throw new ZipException("Extra data extended Zip64 information length is invalid"); } if ( localHeader || (size == uint.MaxValue) ) { size = (ulong)extraData.ReadLong(); } if ( localHeader || (compressedSize == uint.MaxValue) ) { compressedSize = (ulong)extraData.ReadLong(); } if ( !localHeader && (offset == uint.MaxValue) ) { offset = extraData.ReadLong(); } // Disk number on which file starts is ignored } else { if ( ((versionToExtract & 0xff) >= ZipConstants.VersionZip64) && ((size == uint.MaxValue) || (compressedSize == uint.MaxValue)) ) { throw new ZipException("Zip64 Extended information required but is missing."); } } if ( extraData.Find(10) ) { // No room for any tags. if ( extraData.ValueLength < 4 ) { throw new ZipException("NTFS Extra data invalid"); } extraData.ReadInt(); // Reserved while ( extraData.UnreadCount >= 4 ) { var ntfsTag = extraData.ReadShort(); var ntfsLength = extraData.ReadShort(); if ( ntfsTag == 1 ) { if ( ntfsLength >= 24 ) { var lastModification = extraData.ReadLong(); var lastAccess = extraData.ReadLong(); var createTime = extraData.ReadLong(); DateTime = DateTime.FromFileTime(lastModification); } break; } else { // An unknown NTFS tag so simply skip it. extraData.Skip(ntfsLength); } } } else if ( extraData.Find(0x5455) ) { var length = extraData.ValueLength; var flags = extraData.ReadByte(); // Can include other times but these are ignored. Length of data should // actually be 1 + 4 * no of bits in flags. if ( ((flags & 1) != 0) && (length >= 5) ) { var iTime = extraData.ReadInt(); DateTime = (new DateTime ( 1970, 1, 1, 0, 0, 0 ).ToUniversalTime() + new TimeSpan ( 0, 0, 0, iTime, 0 )).ToLocalTime(); } } if (method == CompressionMethod.WinZipAES) { ProcessAESExtraData(extraData); } }
/// <summary> /// Test a local header against that provided from the central directory /// </summary> /// <param name="entry"> /// The entry to test against /// </param> /// <param name="tests">The type of <see cref="HeaderTest">tests</see> to carry out.</param> /// <returns>The offset of the entries data in the file</returns> long TestLocalHeader(ZipEntry entry, HeaderTest tests) { lock(baseStream_) { var testHeader = (tests & HeaderTest.Header) != 0; var 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(); var localFlags = ( short )ReadLEUshort(); var compressionMethod = ( short )ReadLEUshort(); var fileTime = ( short )ReadLEUshort(); var fileDate = ( short )ReadLEUshort(); var 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. var 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)); } } var extraLength = storedNameLength + extraDataLength; return offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeaderBaseSize + extraLength; } }
/// <summary> /// Process extra data fields updating the entry based on the contents. /// </summary> /// <param name="localHeader">True if the extra data fields should be handled /// for a local header, rather than for a central header. /// </param> internal void ProcessExtraData(bool localHeader) { var extraData = new ZipExtraData(extra); if (extraData.Find(0x0001)) { // Version required to extract is ignored here as some archivers dont set it correctly // in theory it should be version 45 or higher // The recorded size will change but remember that this is zip64. forceZip64_ = true; if (extraData.ValueLength < 4) { throw new ZipException("Extra data extended Zip64 information length is invalid"); } if (localHeader || (size == uint.MaxValue)) { size = (ulong)extraData.ReadLong(); } if (localHeader || (compressedSize == uint.MaxValue)) { compressedSize = (ulong)extraData.ReadLong(); } if (!localHeader && (offset == uint.MaxValue)) { offset = extraData.ReadLong(); } // Disk number on which file starts is ignored } else { if ( ((versionToExtract & 0xff) >= ZipConstants.VersionZip64) && ((size == uint.MaxValue) || (compressedSize == uint.MaxValue)) ) { throw new ZipException("Zip64 Extended information required but is missing."); } } if (extraData.Find(10)) { // No room for any tags. if (extraData.ValueLength < 4) { throw new ZipException("NTFS Extra data invalid"); } extraData.ReadInt(); // Reserved while (extraData.UnreadCount >= 4) { var ntfsTag = extraData.ReadShort(); var ntfsLength = extraData.ReadShort(); if (ntfsTag == 1) { if (ntfsLength >= 24) { var lastModification = extraData.ReadLong(); var lastAccess = extraData.ReadLong(); var createTime = extraData.ReadLong(); DateTime = DateTime.FromFileTime(lastModification); } break; } else { // An unknown NTFS tag so simply skip it. extraData.Skip(ntfsLength); } } } else if (extraData.Find(0x5455)) { var length = extraData.ValueLength; var flags = extraData.ReadByte(); // Can include other times but these are ignored. Length of data should // actually be 1 + 4 * no of bits in flags. if (((flags & 1) != 0) && (length >= 5)) { var iTime = extraData.ReadInt(); DateTime = (new DateTime(1970, 1, 1, 0, 0, 0).ToUniversalTime() + new TimeSpan(0, 0, 0, iTime, 0)).ToLocalTime(); } } if (method == CompressionMethod.WinZipAES) { ProcessAESExtraData(extraData); } }