// 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.VersionNeeded==45)

                    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 = 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 < 4)
                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;
            }
   	    else
	    {
                // An unknown NTFS tag so simply skip it.
                j += dataSize;				
  	    }
            return j;
        }


    }
}
