| // ZipEntry.Read.cs | |
| // ------------------------------------------------------------------ | |
| // | |
| // Copyright (c) 2009-2011 Dino Chiesa | |
| // All rights reserved. | |
| // | |
| // This code module is part of DotNetZip, a zipfile class library. | |
| // | |
| // ------------------------------------------------------------------ | |
| // | |
| // This code is licensed under the Microsoft Public License. | |
| // See the file License.txt for the license details. | |
| // More info on: http://dotnetzip.codeplex.com | |
| // | |
| // ------------------------------------------------------------------ | |
| // | |
| // last saved (in emacs): | |
| // Time-stamp: <2011-July-09 21:31:28> | |
| // | |
| // ------------------------------------------------------------------ | |
| // | |
| // This module defines logic for Reading the ZipEntry from a | |
| // zip file. | |
| // | |
| // ------------------------------------------------------------------ | |
| using System; | |
| using System.IO; | |
| namespace OfficeOpenXml.Packaging.Ionic.Zip | |
| { | |
| internal partial class ZipEntry | |
| { | |
| private int _readExtraDepth; | |
| private void ReadExtraField() | |
| { | |
| _readExtraDepth++; | |
| // workitem 8098: ok (restore) | |
| long posn = this.ArchiveStream.Position; | |
| this.ArchiveStream.Seek(this._RelativeOffsetOfLocalHeader, SeekOrigin.Begin); | |
| // workitem 10178 | |
| Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(this.ArchiveStream); | |
| byte[] block = new byte[30]; | |
| this.ArchiveStream.Read(block, 0, block.Length); | |
| int i = 26; | |
| Int16 filenameLength = (short)(block[i++] + block[i++] * 256); | |
| Int16 extraFieldLength = (short)(block[i++] + block[i++] * 256); | |
| // workitem 8098: ok (relative) | |
| this.ArchiveStream.Seek(filenameLength, SeekOrigin.Current); | |
| // workitem 10178 | |
| Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(this.ArchiveStream); | |
| ProcessExtraField(this.ArchiveStream, extraFieldLength); | |
| // workitem 8098: ok (restore) | |
| this.ArchiveStream.Seek(posn, SeekOrigin.Begin); | |
| // workitem 10178 | |
| Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(this.ArchiveStream); | |
| _readExtraDepth--; | |
| } | |
| private static bool ReadHeader(ZipEntry ze, System.Text.Encoding defaultEncoding) | |
| { | |
| int bytesRead = 0; | |
| // change for workitem 8098 | |
| ze._RelativeOffsetOfLocalHeader = ze.ArchiveStream.Position; | |
| int signature = Ionic.Zip.SharedUtilities.ReadEntrySignature(ze.ArchiveStream); | |
| bytesRead += 4; | |
| // Return false if this is not a local file header signature. | |
| if (ZipEntry.IsNotValidSig(signature)) | |
| { | |
| // Getting "not a ZipEntry signature" is not always wrong or an error. | |
| // This will happen after the last entry in a zipfile. In that case, we | |
| // expect to read : | |
| // a ZipDirEntry signature (if a non-empty zip file) or | |
| // a ZipConstants.EndOfCentralDirectorySignature. | |
| // | |
| // Anything else is a surprise. | |
| ze.ArchiveStream.Seek(-4, SeekOrigin.Current); // unread the signature | |
| // workitem 10178 | |
| Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(ze.ArchiveStream); | |
| if (ZipEntry.IsNotValidZipDirEntrySig(signature) && (signature != ZipConstants.EndOfCentralDirectorySignature)) | |
| { | |
| throw new BadReadException(String.Format(" Bad signature (0x{0:X8}) at position 0x{1:X8}", signature, ze.ArchiveStream.Position)); | |
| } | |
| return false; | |
| } | |
| byte[] block = new byte[26]; | |
| int n = ze.ArchiveStream.Read(block, 0, block.Length); | |
| if (n != block.Length) return false; | |
| bytesRead += n; | |
| int i = 0; | |
| ze._VersionNeeded = (Int16)(block[i++] + block[i++] * 256); | |
| ze._BitField = (Int16)(block[i++] + block[i++] * 256); | |
| ze._CompressionMethod_FromZipFile = ze._CompressionMethod = (Int16)(block[i++] + block[i++] * 256); | |
| ze._TimeBlob = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256; | |
| // transform the time data into something usable (a DateTime) | |
| ze._LastModified = Ionic.Zip.SharedUtilities.PackedToDateTime(ze._TimeBlob); | |
| ze._timestamp |= ZipEntryTimestamp.DOS; | |
| if ((ze._BitField & 0x01) == 0x01) | |
| { | |
| ze._Encryption_FromZipFile = ze._Encryption = EncryptionAlgorithm.PkzipWeak; // this *may* change after processing the Extra field | |
| ze._sourceIsEncrypted = true; | |
| } | |
| // NB: if ((ze._BitField & 0x0008) != 0x0008), then the Compressed, uncompressed and | |
| // CRC values are not true values; the true values will follow the entry data. | |
| // But, regardless of the status of bit 3 in the bitfield, the slots for | |
| // the three amigos may contain marker values for ZIP64. So we must read them. | |
| { | |
| ze._Crc32 = (Int32)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256); | |
| ze._CompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256); | |
| ze._UncompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256); | |
| if ((uint)ze._CompressedSize == 0xFFFFFFFF || | |
| (uint)ze._UncompressedSize == 0xFFFFFFFF) | |
| ze._InputUsesZip64 = true; | |
| } | |
| Int16 filenameLength = (short)(block[i++] + block[i++] * 256); | |
| Int16 extraFieldLength = (short)(block[i++] + block[i++] * 256); | |
| block = new byte[filenameLength]; | |
| n = ze.ArchiveStream.Read(block, 0, block.Length); | |
| bytesRead += n; | |
| // if the UTF8 bit is set for this entry, override the | |
| // encoding the application requested. | |
| if ((ze._BitField & 0x0800) == 0x0800) | |
| { | |
| // workitem 12744 | |
| ze.AlternateEncoding = System.Text.Encoding.UTF8; | |
| ze.AlternateEncodingUsage = ZipOption.Always; | |
| } | |
| // need to use this form of GetString() for .NET CF | |
| ze._FileNameInArchive = ze.AlternateEncoding.GetString(block, 0, block.Length); | |
| // workitem 6898 | |
| if (ze._FileNameInArchive.EndsWith("/")) ze.MarkAsDirectory(); | |
| bytesRead += ze.ProcessExtraField(ze.ArchiveStream, extraFieldLength); | |
| ze._LengthOfTrailer = 0; | |
| // workitem 6607 - don't read for directories | |
| // actually get the compressed size and CRC if necessary | |
| if (!ze._FileNameInArchive.EndsWith("/") && (ze._BitField & 0x0008) == 0x0008) | |
| { | |
| // This descriptor exists only if bit 3 of the general | |
| // purpose bit flag is set (see below). It is byte aligned | |
| // and immediately follows the last byte of compressed data, | |
| // as well as any encryption trailer, as with AES. | |
| // This descriptor is used only when it was not possible to | |
| // seek in the output .ZIP file, e.g., when the output .ZIP file | |
| // was standard output or a non-seekable device. For ZIP64(tm) format | |
| // archives, the compressed and uncompressed sizes are 8 bytes each. | |
| // workitem 8098: ok (restore) | |
| long posn = ze.ArchiveStream.Position; | |
| // Here, we're going to loop until we find a ZipEntryDataDescriptorSignature and | |
| // a consistent data record after that. To be consistent, the data record must | |
| // indicate the length of the entry data. | |
| bool wantMore = true; | |
| long SizeOfDataRead = 0; | |
| int tries = 0; | |
| while (wantMore) | |
| { | |
| tries++; | |
| // We call the FindSignature shared routine to find the specified signature | |
| // in the already-opened zip archive, starting from the current cursor | |
| // position in that filestream. If we cannot find the signature, then the | |
| // routine returns -1, and the ReadHeader() method returns false, | |
| // indicating we cannot read a legal entry header. If we have found it, | |
| // then the FindSignature() method returns the number of bytes in the | |
| // stream we had to seek forward, to find the sig. We need this to | |
| // determine if the zip entry is valid, later. | |
| if (ze._container.ZipFile != null) | |
| ze._container.ZipFile.OnReadBytes(ze); | |
| long d = Ionic.Zip.SharedUtilities.FindSignature(ze.ArchiveStream, ZipConstants.ZipEntryDataDescriptorSignature); | |
| if (d == -1) return false; | |
| // total size of data read (through all loops of this). | |
| SizeOfDataRead += d; | |
| if (ze._InputUsesZip64) | |
| { | |
| // read 1x 4-byte (CRC) and 2x 8-bytes (Compressed Size, Uncompressed Size) | |
| block = new byte[20]; | |
| n = ze.ArchiveStream.Read(block, 0, block.Length); | |
| if (n != 20) return false; | |
| // do not increment bytesRead - it is for entry header only. | |
| // the data we have just read is a footer (falls after the file data) | |
| //bytesRead += n; | |
| i = 0; | |
| ze._Crc32 = (Int32)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256); | |
| ze._CompressedSize = BitConverter.ToInt64(block, i); | |
| i += 8; | |
| ze._UncompressedSize = BitConverter.ToInt64(block, i); | |
| i += 8; | |
| ze._LengthOfTrailer += 24; // bytes including sig, CRC, Comp and Uncomp sizes | |
| } | |
| else | |
| { | |
| // read 3x 4-byte fields (CRC, Compressed Size, Uncompressed Size) | |
| block = new byte[12]; | |
| n = ze.ArchiveStream.Read(block, 0, block.Length); | |
| if (n != 12) return false; | |
| // do not increment bytesRead - it is for entry header only. | |
| // the data we have just read is a footer (falls after the file data) | |
| //bytesRead += n; | |
| i = 0; | |
| ze._Crc32 = (Int32)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256); | |
| ze._CompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256); | |
| ze._UncompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256); | |
| ze._LengthOfTrailer += 16; // bytes including sig, CRC, Comp and Uncomp sizes | |
| } | |
| wantMore = (SizeOfDataRead != ze._CompressedSize); | |
| if (wantMore) | |
| { | |
| // Seek back to un-read the last 12 bytes - maybe THEY contain | |
| // the ZipEntryDataDescriptorSignature. | |
| // (12 bytes for the CRC, Comp and Uncomp size.) | |
| ze.ArchiveStream.Seek(-12, SeekOrigin.Current); | |
| // workitem 10178 | |
| Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(ze.ArchiveStream); | |
| // Adjust the size to account for the false signature read in | |
| // FindSignature(). | |
| SizeOfDataRead += 4; | |
| } | |
| } | |
| // seek back to previous position, to prepare to read file data | |
| // workitem 8098: ok (restore) | |
| ze.ArchiveStream.Seek(posn, SeekOrigin.Begin); | |
| // workitem 10178 | |
| Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(ze.ArchiveStream); | |
| } | |
| ze._CompressedFileDataSize = ze._CompressedSize; | |
| // bit 0 set indicates that some kind of encryption is in use | |
| if ((ze._BitField & 0x01) == 0x01) | |
| { | |
| #if AESCRYPTO | |
| if (ze.Encryption == EncryptionAlgorithm.WinZipAes128 || | |
| ze.Encryption == EncryptionAlgorithm.WinZipAes256) | |
| { | |
| int bits = ZipEntry.GetKeyStrengthInBits(ze._Encryption_FromZipFile); | |
| // read in the WinZip AES metadata: salt + PV. 18 bytes for AES256. 10 bytes for AES128. | |
| ze._aesCrypto_forExtract = WinZipAesCrypto.ReadFromStream(null, bits, ze.ArchiveStream); | |
| bytesRead += ze._aesCrypto_forExtract.SizeOfEncryptionMetadata - 10; // MAC (follows crypto bytes) | |
| // according to WinZip, the CompressedSize includes the AES Crypto framing data. | |
| ze._CompressedFileDataSize -= ze._aesCrypto_forExtract.SizeOfEncryptionMetadata; | |
| ze._LengthOfTrailer += 10; // MAC | |
| } | |
| else | |
| #endif | |
| { | |
| // read in the header data for "weak" encryption | |
| ze._WeakEncryptionHeader = new byte[12]; | |
| bytesRead += ZipEntry.ReadWeakEncryptionHeader(ze._archiveStream, ze._WeakEncryptionHeader); | |
| // decrease the filedata size by 12 bytes | |
| ze._CompressedFileDataSize -= 12; | |
| } | |
| } | |
| // Remember the size of the blob for this entry. | |
| // We also have the starting position in the stream for this entry. | |
| ze._LengthOfHeader = bytesRead; | |
| ze._TotalEntrySize = ze._LengthOfHeader + ze._CompressedFileDataSize + ze._LengthOfTrailer; | |
| // We've read in the regular entry header, the extra field, and any | |
| // encryption header. The pointer in the file is now at the start of the | |
| // filedata, which is potentially compressed and encrypted. Just ahead in | |
| // the file, there are _CompressedFileDataSize bytes of data, followed by | |
| // potentially a non-zero length trailer, consisting of optionally, some | |
| // encryption stuff (10 byte MAC for AES), and the bit-3 trailer (16 or 24 | |
| // bytes). | |
| return true; | |
| } | |
| internal static int ReadWeakEncryptionHeader(Stream s, byte[] buffer) | |
| { | |
| // PKZIP encrypts the compressed data stream. Encrypted files must | |
| // be decrypted before they can be extracted. | |
| // Each PKZIP-encrypted file has an extra 12 bytes stored at the start of the data | |
| // area defining the encryption header for that file. The encryption header is | |
| // originally set to random values, and then itself encrypted, using three, 32-bit | |
| // keys. The key values are initialized using the supplied encryption password. | |
| // After each byte is encrypted, the keys are then updated using pseudo-random | |
| // number generation techniques in combination with the same CRC-32 algorithm used | |
| // in PKZIP and implemented in the CRC32.cs module in this project. | |
| // read the 12-byte encryption header | |
| int additionalBytesRead = s.Read(buffer, 0, 12); | |
| if (additionalBytesRead != 12) | |
| throw new ZipException(String.Format("Unexpected end of data at position 0x{0:X8}", s.Position)); | |
| return additionalBytesRead; | |
| } | |
| private static bool IsNotValidSig(int signature) | |
| { | |
| return (signature != ZipConstants.ZipEntrySignature); | |
| } | |
| /// <summary> | |
| /// Reads one <c>ZipEntry</c> from the given stream. The content for | |
| /// the entry does not get decompressed or decrypted. This method | |
| /// basically reads metadata, and seeks. | |
| /// </summary> | |
| /// <param name="zc">the ZipContainer this entry belongs to.</param> | |
| /// <param name="first"> | |
| /// true of this is the first entry being read from the stream. | |
| /// </param> | |
| /// <returns>the <c>ZipEntry</c> read from the stream.</returns> | |
| internal static ZipEntry ReadEntry(ZipContainer zc, bool first) | |
| { | |
| ZipFile zf = zc.ZipFile; | |
| Stream s = zc.ReadStream; | |
| System.Text.Encoding defaultEncoding = zc.AlternateEncoding; | |
| ZipEntry entry = new ZipEntry(); | |
| entry._Source = ZipEntrySource.ZipFile; | |
| entry._container = zc; | |
| entry._archiveStream = s; | |
| if (zf != null) | |
| zf.OnReadEntry(true, null); | |
| if (first) HandlePK00Prefix(s); | |
| // Read entry header, including any encryption header | |
| if (!ReadHeader(entry, defaultEncoding)) return null; | |
| // Store the position in the stream for this entry | |
| // change for workitem 8098 | |
| entry.__FileDataPosition = entry.ArchiveStream.Position; | |
| // seek past the data without reading it. We will read on Extract() | |
| s.Seek(entry._CompressedFileDataSize + entry._LengthOfTrailer, SeekOrigin.Current); | |
| // workitem 10178 | |
| Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(s); | |
| // ReadHeader moves the file pointer to the end of the entry header, | |
| // as well as any encryption header. | |
| // CompressedFileDataSize includes: | |
| // the maybe compressed, maybe encrypted file data | |
| // the encryption trailer, if any | |
| // the bit 3 descriptor, if any | |
| // workitem 5306 | |
| // http://www.codeplex.com/DotNetZip/WorkItem/View.aspx?WorkItemId=5306 | |
| HandleUnexpectedDataDescriptor(entry); | |
| if (zf != null) | |
| { | |
| zf.OnReadBytes(entry); | |
| zf.OnReadEntry(false, entry); | |
| } | |
| return entry; | |
| } | |
| internal static void HandlePK00Prefix(Stream s) | |
| { | |
| // in some cases, the zip file begins with "PK00". This is a throwback and is rare, | |
| // but we handle it anyway. We do not change behavior based on it. | |
| uint datum = (uint)Ionic.Zip.SharedUtilities.ReadInt(s); | |
| if (datum != ZipConstants.PackedToRemovableMedia) | |
| { | |
| s.Seek(-4, SeekOrigin.Current); // unread the block | |
| // workitem 10178 | |
| Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(s); | |
| } | |
| } | |
| private static void HandleUnexpectedDataDescriptor(ZipEntry entry) | |
| { | |
| Stream s = entry.ArchiveStream; | |
| // In some cases, the "data descriptor" is present, without a signature, even when | |
| // bit 3 of the BitField is NOT SET. This is the CRC, followed | |
| // by the compressed length and the uncompressed length (4 bytes for each | |
| // of those three elements). Need to check that here. | |
| // | |
| uint datum = (uint)Ionic.Zip.SharedUtilities.ReadInt(s); | |
| if (datum == entry._Crc32) | |
| { | |
| int sz = Ionic.Zip.SharedUtilities.ReadInt(s); | |
| if (sz == entry._CompressedSize) | |
| { | |
| sz = Ionic.Zip.SharedUtilities.ReadInt(s); | |
| if (sz == entry._UncompressedSize) | |
| { | |
| // ignore everything and discard it. | |
| } | |
| else | |
| { | |
| s.Seek(-12, SeekOrigin.Current); // unread the three blocks | |
| // workitem 10178 | |
| Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(s); | |
| } | |
| } | |
| else | |
| { | |
| s.Seek(-8, SeekOrigin.Current); // unread the two blocks | |
| // workitem 10178 | |
| Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(s); | |
| } | |
| } | |
| else | |
| { | |
| s.Seek(-4, SeekOrigin.Current); // unread the block | |
| // workitem 10178 | |
| Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(s); | |
| } | |
| } | |
| /// <summary> | |
| /// Finds a particular segment in the given extra field. | |
| /// This is used when modifying a previously-generated | |
| /// extra field, in particular when removing the AES crypto | |
| /// segment in the extra field. | |
| /// </summary> | |
| static internal int FindExtraFieldSegment(byte[] extra, int offx, UInt16 targetHeaderId) | |
| { | |
| int j = offx; | |
| while (j + 3 < extra.Length) | |
| { | |
| UInt16 headerId = (UInt16)(extra[j++] + extra[j++] * 256); | |
| if (headerId == targetHeaderId) return j-2; | |
| // else advance to next segment | |
| Int16 dataSize = (short)(extra[j++] + extra[j++] * 256); | |
| j+= dataSize; | |
| } | |
| return -1; | |
| } | |
| /// <summary> | |
| /// At current cursor position in the stream, read the extra | |
| /// field, and set the properties on the ZipEntry instance | |
| /// appropriately. This can be called when processing the | |
| /// Extra field in the Central Directory, or in the local | |
| /// header. | |
| /// </summary> | |
| internal int ProcessExtraField(Stream s, Int16 extraFieldLength) | |
| { | |
| int additionalBytesRead = 0; | |
| if (extraFieldLength > 0) | |
| { | |
| byte[] buffer = this._Extra = new byte[extraFieldLength]; | |
| additionalBytesRead = s.Read(buffer, 0, buffer.Length); | |
| long posn = s.Position - additionalBytesRead; | |
| int j = 0; | |
| while (j + 3 < buffer.Length) | |
| { | |
| int start = j; | |
| UInt16 headerId = (UInt16)(buffer[j++] + buffer[j++] * 256); | |
| Int16 dataSize = (short)(buffer[j++] + buffer[j++] * 256); | |
| switch (headerId) | |
| { | |
| case 0x000a: // NTFS ctime, atime, mtime | |
| j = ProcessExtraFieldWindowsTimes(buffer, j, dataSize, posn); | |
| break; | |
| case 0x5455: // Unix ctime, atime, mtime | |
| j = ProcessExtraFieldUnixTimes(buffer, j, dataSize, posn); | |
| break; | |
| case 0x5855: // Info-zip Extra field (outdated) | |
| // This is outdated, so the field is supported on | |
| // read only. | |
| j = ProcessExtraFieldInfoZipTimes(buffer, j, dataSize, posn); | |
| break; | |
| case 0x7855: // Unix uid/gid | |
| // ignored. DotNetZip does not handle this field. | |
| break; | |
| case 0x7875: // ?? | |
| // ignored. I could not find documentation on this field, | |
| // though it appears in some zip files. | |
| break; | |
| case 0x0001: // ZIP64 | |
| j = ProcessExtraFieldZip64(buffer, j, dataSize, posn); | |
| break; | |
| #if AESCRYPTO | |
| case 0x9901: // WinZip AES encryption is in use. (workitem 6834) | |
| // we will handle this extra field only if compressionmethod is 0x63 | |
| j = ProcessExtraFieldWinZipAes(buffer, j, dataSize, posn); | |
| break; | |
| #endif | |
| case 0x0017: // workitem 7968: handle PKWare Strong encryption header | |
| j = ProcessExtraFieldPkwareStrongEncryption(buffer, j); | |
| break; | |
| } | |
| // move to the next Header in the extra field | |
| j = start + dataSize + 4; | |
| } | |
| } | |
| return additionalBytesRead; | |
| } | |
| private int ProcessExtraFieldPkwareStrongEncryption(byte[] Buffer, int j) | |
| { | |
| // Value Size Description | |
| // ----- ---- ----------- | |
| // 0x0017 2 bytes Tag for this "extra" block type | |
| // TSize 2 bytes Size of data that follows | |
| // Format 2 bytes Format definition for this record | |
| // AlgID 2 bytes Encryption algorithm identifier | |
| // Bitlen 2 bytes Bit length of encryption key | |
| // Flags 2 bytes Processing flags | |
| // CertData TSize-8 Certificate decryption extra field data | |
| // (refer to the explanation for CertData | |
| // in the section describing the | |
| // Certificate Processing Method under | |
| // the Strong Encryption Specification) | |
| j += 2; | |
| _UnsupportedAlgorithmId = (UInt16)(Buffer[j++] + Buffer[j++] * 256); | |
| _Encryption_FromZipFile = _Encryption = EncryptionAlgorithm.Unsupported; | |
| // DotNetZip doesn't support this algorithm, but we don't need to throw | |
| // here. we might just be reading the archive, which is fine. We'll | |
| // need to throw if Extract() is called. | |
| return j; | |
| } | |
| #if AESCRYPTO | |
| private int ProcessExtraFieldWinZipAes(byte[] buffer, int j, Int16 dataSize, long posn) | |
| { | |
| if (this._CompressionMethod == 0x0063) | |
| { | |
| if ((this._BitField & 0x01) != 0x01) | |
| throw new BadReadException(String.Format(" Inconsistent metadata at position 0x{0:X16}", posn)); | |
| this._sourceIsEncrypted = true; | |
| //this._aesCrypto = new WinZipAesCrypto(this); | |
| // see spec at http://www.winzip.com/aes_info.htm | |
| if (dataSize != 7) | |
| throw new BadReadException(String.Format(" Inconsistent size (0x{0:X4}) in WinZip AES field at position 0x{1:X16}", dataSize, posn)); | |
| this._WinZipAesMethod = BitConverter.ToInt16(buffer, j); | |
| j += 2; | |
| if (this._WinZipAesMethod != 0x01 && this._WinZipAesMethod != 0x02) | |
| throw new BadReadException(String.Format(" Unexpected vendor version number (0x{0:X4}) for WinZip AES metadata at position 0x{1:X16}", | |
| this._WinZipAesMethod, posn)); | |
| Int16 vendorId = BitConverter.ToInt16(buffer, j); | |
| j += 2; | |
| if (vendorId != 0x4541) | |
| throw new BadReadException(String.Format(" Unexpected vendor ID (0x{0:X4}) for WinZip AES metadata at position 0x{1:X16}", vendorId, posn)); | |
| int keystrength = (buffer[j] == 1) ? 128 : (buffer[j] == 3) ? 256 : -1; | |
| if (keystrength < 0) | |
| throw new BadReadException(String.Format("Invalid key strength ({0})", keystrength)); | |
| _Encryption_FromZipFile = this._Encryption = (keystrength == 128) | |
| ? EncryptionAlgorithm.WinZipAes128 | |
| : EncryptionAlgorithm.WinZipAes256; | |
| j++; | |
| // set the actual compression method | |
| this._CompressionMethod_FromZipFile = | |
| this._CompressionMethod = BitConverter.ToInt16(buffer, j); | |
| j += 2; // for the next segment of the extra field | |
| } | |
| return j; | |
| } | |
| #endif | |
| private delegate T Func<T>(); | |
| private int ProcessExtraFieldZip64(byte[] buffer, int j, Int16 dataSize, long posn) | |
| { | |
| // The PKWare spec says that any of {UncompressedSize, CompressedSize, | |
| // RelativeOffset} exceeding 0xFFFFFFFF can lead to the ZIP64 header, | |
| // and the ZIP64 header may contain one or more of those. If the | |
| // values are present, they will be found in the prescribed order. | |
| // There may also be a 4-byte "disk start number." | |
| // This means that the DataSize must be 28 bytes or less. | |
| this._InputUsesZip64 = true; | |
| // workitem 7941: check datasize before reading. | |
| if (dataSize > 28) | |
| throw new BadReadException(String.Format(" Inconsistent size (0x{0:X4}) for ZIP64 extra field at position 0x{1:X16}", | |
| dataSize, posn)); | |
| int remainingData = dataSize; | |
| var slurp = new Func<Int64>( () => { | |
| if (remainingData < 8) | |
| throw new BadReadException(String.Format(" Missing data for ZIP64 extra field, position 0x{0:X16}", posn)); | |
| var x = BitConverter.ToInt64(buffer, j); | |
| j+= 8; | |
| remainingData -= 8; | |
| return x; | |
| }); | |
| if (this._UncompressedSize == 0xFFFFFFFF) | |
| this._UncompressedSize = slurp(); | |
| if (this._CompressedSize == 0xFFFFFFFF) | |
| this._CompressedSize = slurp(); | |
| if (this._RelativeOffsetOfLocalHeader == 0xFFFFFFFF) | |
| this._RelativeOffsetOfLocalHeader = slurp(); | |
| // Ignore anything else. Potentially there are 4 more bytes for the | |
| // disk start number. DotNetZip currently doesn't handle multi-disk | |
| // archives. | |
| return j; | |
| } | |
| private int ProcessExtraFieldInfoZipTimes(byte[] buffer, int j, Int16 dataSize, long posn) | |
| { | |
| if (dataSize != 12 && dataSize != 8) | |
| throw new BadReadException(String.Format(" Unexpected size (0x{0:X4}) for InfoZip v1 extra field at position 0x{1:X16}", dataSize, posn)); | |
| Int32 timet = BitConverter.ToInt32(buffer, j); | |
| this._Mtime = _unixEpoch.AddSeconds(timet); | |
| j += 4; | |
| timet = BitConverter.ToInt32(buffer, j); | |
| this._Atime = _unixEpoch.AddSeconds(timet); | |
| j += 4; | |
| this._Ctime = DateTime.UtcNow; | |
| _ntfsTimesAreSet = true; | |
| _timestamp |= ZipEntryTimestamp.InfoZip1; return j; | |
| } | |
| private int ProcessExtraFieldUnixTimes(byte[] buffer, int j, Int16 dataSize, long posn) | |
| { | |
| // The Unix filetimes are 32-bit unsigned integers, | |
| // storing seconds since Unix epoch. | |
| if (dataSize != 13 && dataSize != 9 && dataSize != 5) | |
| throw new BadReadException(String.Format(" Unexpected size (0x{0:X4}) for Extended Timestamp extra field at position 0x{1:X16}", dataSize, posn)); | |
| int remainingData = dataSize; | |
| var slurp = new Func<DateTime>( () => { | |
| Int32 timet = BitConverter.ToInt32(buffer, j); | |
| j += 4; | |
| remainingData -= 4; | |
| return _unixEpoch.AddSeconds(timet); | |
| }); | |
| if (dataSize == 13 || _readExtraDepth > 0) | |
| { | |
| byte flag = buffer[j++]; | |
| remainingData--; | |
| if ((flag & 0x0001) != 0 && remainingData >= 4) | |
| this._Mtime = slurp(); | |
| this._Atime = ((flag & 0x0002) != 0 && remainingData >= 4) | |
| ? slurp() | |
| : DateTime.UtcNow; | |
| this._Ctime = ((flag & 0x0004) != 0 && remainingData >= 4) | |
| ? slurp() | |
| :DateTime.UtcNow; | |
| _timestamp |= ZipEntryTimestamp.Unix; | |
| _ntfsTimesAreSet = true; | |
| _emitUnixTimes = true; | |
| } | |
| else | |
| ReadExtraField(); // will recurse | |
| return j; | |
| } | |
| private int ProcessExtraFieldWindowsTimes(byte[] buffer, int j, Int16 dataSize, long posn) | |
| { | |
| // The NTFS filetimes are 64-bit unsigned integers, stored in Intel | |
| // (least significant byte first) byte order. They are expressed as the | |
| // number of 1.0E-07 seconds (1/10th microseconds!) past WinNT "epoch", | |
| // which is "01-Jan-1601 00:00:00 UTC". | |
| // | |
| // HeaderId 2 bytes 0x000a == NTFS stuff | |
| // Datasize 2 bytes ?? (usually 32) | |
| // reserved 4 bytes ?? | |
| // timetag 2 bytes 0x0001 == time | |
| // size 2 bytes 24 == 8 bytes each for ctime, mtime, atime | |
| // mtime 8 bytes win32 ticks since win32epoch | |
| // atime 8 bytes win32 ticks since win32epoch | |
| // ctime 8 bytes win32 ticks since win32epoch | |
| if (dataSize != 32) | |
| throw new BadReadException(String.Format(" Unexpected size (0x{0:X4}) for NTFS times extra field at position 0x{1:X16}", dataSize, posn)); | |
| j += 4; // reserved | |
| Int16 timetag = (Int16)(buffer[j] + buffer[j + 1] * 256); | |
| Int16 addlsize = (Int16)(buffer[j + 2] + buffer[j + 3] * 256); | |
| j += 4; // tag and size | |
| if (timetag == 0x0001 && addlsize == 24) | |
| { | |
| Int64 z = BitConverter.ToInt64(buffer, j); | |
| this._Mtime = DateTime.FromFileTimeUtc(z); | |
| j += 8; | |
| // At this point the library *could* set the LastModified value | |
| // to coincide with the Mtime value. In theory, they refer to | |
| // the same property of the file, and should be the same anyway, | |
| // allowing for differences in precision. But they are | |
| // independent quantities in the zip archive, and this library | |
| // will keep them separate in the object model. There is no ill | |
| // effect from this, because as files are extracted, the | |
| // higher-precision value (Mtime) is used if it is present. | |
| // Apps may wish to compare the Mtime versus LastModified | |
| // values, but any difference when both are present is not | |
| // germaine to the correctness of the library. but note: when | |
| // explicitly setting either value, both are set. See the setter | |
| // for LastModified or the SetNtfsTimes() method. | |
| z = BitConverter.ToInt64(buffer, j); | |
| this._Atime = DateTime.FromFileTimeUtc(z); | |
| j += 8; | |
| z = BitConverter.ToInt64(buffer, j); | |
| this._Ctime = DateTime.FromFileTimeUtc(z); | |
| j += 8; | |
| _ntfsTimesAreSet = true; | |
| _timestamp |= ZipEntryTimestamp.Windows; | |
| _emitNtfsTimes = true; | |
| } | |
| return j; | |
| } | |
| } | |
| } |