| // 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; |
| } |
| |
| |
| } |
| } |