// 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); } /// /// Reads one ZipEntry from the given stream. The content for /// the entry does not get decompressed or decrypted. This method /// basically reads metadata, and seeks. /// /// the ZipContainer this entry belongs to. /// /// true of this is the first entry being read from the stream. /// /// the ZipEntry read from the stream. 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); } } /// /// 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. /// 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; } /// /// 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. /// 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(); 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( () => { 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( () => { 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; } } }