//#define Trace

// ZipEntry.Write.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: <2011-July-30 14:55:47>
//
// ------------------------------------------------------------------
//
// This module defines logic for writing (saving) the ZipEntry into a
// zip file.
//
// ------------------------------------------------------------------


using OfficeOpenXml.Packaging.Ionic.Zlib;
using System;
using System.IO;
using RE = System.Text.RegularExpressions;

namespace OfficeOpenXml.Packaging.Ionic.Zip
{
    internal partial class ZipEntry
    {
        internal void WriteCentralDirectoryEntry(Stream s)
        {
            byte[] bytes = new byte[4096];
            int i = 0;
            // signature
            bytes[i++] = (byte)(ZipConstants.ZipDirEntrySignature & 0x000000FF);
            bytes[i++] = (byte)((ZipConstants.ZipDirEntrySignature & 0x0000FF00) >> 8);
            bytes[i++] = (byte)((ZipConstants.ZipDirEntrySignature & 0x00FF0000) >> 16);
            bytes[i++] = (byte)((ZipConstants.ZipDirEntrySignature & 0xFF000000) >> 24);

            // Version Made By
            // workitem 7071
            // We must not overwrite the VersionMadeBy field when writing out a zip
            // archive.  The VersionMadeBy tells the zip reader the meaning of the
            // File attributes.  Overwriting the VersionMadeBy will result in
            // inconsistent metadata.  Consider the scenario where the application
            // opens and reads a zip file that had been created on Linux. Then the
            // app adds one file to the Zip archive, and saves it.  The file
            // attributes for all the entries added on Linux will be significant for
            // Linux.  Therefore the VersionMadeBy for those entries must not be
            // changed.  Only the entries that are actually created on Windows NTFS
            // should get the VersionMadeBy indicating Windows/NTFS.
            bytes[i++] = (byte)(_VersionMadeBy & 0x00FF);
            bytes[i++] = (byte)((_VersionMadeBy & 0xFF00) >> 8);

            // Apparently we want to duplicate the extra field here; we cannot
            // simply zero it out and assume tools and apps will use the right one.

            ////Int16 extraFieldLengthSave = (short)(_EntryHeader[28] + _EntryHeader[29] * 256);
            ////_EntryHeader[28] = 0;
            ////_EntryHeader[29] = 0;

            // Version Needed, Bitfield, compression method, lastmod,
            // crc, compressed and uncompressed sizes, filename length and extra field length.
            // These are all present in the local file header, but they may be zero values there.
            // So we cannot just copy them.

            // workitem 11969: Version Needed To Extract in central directory must be
            // the same as the local entry or MS .NET System.IO.Zip fails read.
            Int16 vNeeded = (Int16)(VersionNeeded != 0 ? VersionNeeded : 20);
            // workitem 12964
            if (_OutputUsesZip64==null)
            {
                // a zipentry in a zipoutputstream, with zero bytes written
                _OutputUsesZip64 = new Nullable<bool>(_container.Zip64 == Zip64Option.Always);
            }

            Int16 versionNeededToExtract = (Int16)(_OutputUsesZip64.Value ? 45 : vNeeded);
#if BZIP
            if (this.CompressionMethod == Ionic.Zip.CompressionMethod.BZip2)
                versionNeededToExtract = 46;
#endif

            bytes[i++] = (byte)(versionNeededToExtract & 0x00FF);
            bytes[i++] = (byte)((versionNeededToExtract & 0xFF00) >> 8);

            bytes[i++] = (byte)(_BitField & 0x00FF);
            bytes[i++] = (byte)((_BitField & 0xFF00) >> 8);

            bytes[i++] = (byte)(_CompressionMethod & 0x00FF);
            bytes[i++] = (byte)((_CompressionMethod & 0xFF00) >> 8);

#if AESCRYPTO
            if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
            Encryption == EncryptionAlgorithm.WinZipAes256)
            {
                i -= 2;
                bytes[i++] = 0x63;
                bytes[i++] = 0;
            }
#endif

            bytes[i++] = (byte)(_TimeBlob & 0x000000FF);
            bytes[i++] = (byte)((_TimeBlob & 0x0000FF00) >> 8);
            bytes[i++] = (byte)((_TimeBlob & 0x00FF0000) >> 16);
            bytes[i++] = (byte)((_TimeBlob & 0xFF000000) >> 24);
            bytes[i++] = (byte)(_Crc32 & 0x000000FF);
            bytes[i++] = (byte)((_Crc32 & 0x0000FF00) >> 8);
            bytes[i++] = (byte)((_Crc32 & 0x00FF0000) >> 16);
            bytes[i++] = (byte)((_Crc32 & 0xFF000000) >> 24);

            int j = 0;
            if (_OutputUsesZip64.Value)
            {
                // CompressedSize (Int32) and UncompressedSize - all 0xFF
                for (j = 0; j < 8; j++)
                    bytes[i++] = 0xFF;
            }
            else
            {
                bytes[i++] = (byte)(_CompressedSize & 0x000000FF);
                bytes[i++] = (byte)((_CompressedSize & 0x0000FF00) >> 8);
                bytes[i++] = (byte)((_CompressedSize & 0x00FF0000) >> 16);
                bytes[i++] = (byte)((_CompressedSize & 0xFF000000) >> 24);

                bytes[i++] = (byte)(_UncompressedSize & 0x000000FF);
                bytes[i++] = (byte)((_UncompressedSize & 0x0000FF00) >> 8);
                bytes[i++] = (byte)((_UncompressedSize & 0x00FF0000) >> 16);
                bytes[i++] = (byte)((_UncompressedSize & 0xFF000000) >> 24);
            }

            byte[] fileNameBytes = GetEncodedFileNameBytes();
            Int16 filenameLength = (Int16)fileNameBytes.Length;
            bytes[i++] = (byte)(filenameLength & 0x00FF);
            bytes[i++] = (byte)((filenameLength & 0xFF00) >> 8);

            // do this again because now we have real data
            _presumeZip64 = _OutputUsesZip64.Value;

            // workitem 11131
            //
            // cannot generate the extra field again, here's why: In the case of a
            // zero-byte entry, which uses encryption, DotNetZip will "remove" the
            // encryption from the entry.  It does this in PostProcessOutput; it
            // modifies the entry header, and rewrites it, resetting the Bitfield
            // (one bit indicates encryption), and potentially resetting the
            // compression method - for AES the Compression method is 0x63, and it
            // would get reset to zero (no compression).  It then calls SetLength()
            // to truncate the stream to remove the encryption header (12 bytes for
            // AES256).  But, it leaves the previously-generated "Extra Field"
            // metadata (11 bytes) for AES in the entry header. This extra field
            // data is now "orphaned" - it refers to AES encryption when in fact no
            // AES encryption is used. But no problem, the PKWARE spec says that
            // unrecognized extra fields can just be ignored. ok.  After "removal"
            // of AES encryption, the length of the Extra Field can remains the
            // same; it's just that there will be 11 bytes in there that previously
            // pertained to AES which are now unused. Even the field code is still
            // there, but it will be unused by readers, as the encryption bit is not
            // set.
            //
            // Re-calculating the Extra field now would produce a block that is 11
            // bytes shorter, and that mismatch - between the extra field in the
            // local header and the extra field in the Central Directory - would
            // cause problems. (where? why? what problems?)  So we can't do
            // that. It's all good though, because though the content may have
            // changed, the length definitely has not. Also, the _EntryHeader
            // contains the "updated" extra field (after PostProcessOutput) at
            // offset (30 + filenameLength).

            _Extra = ConstructExtraField(true);

            Int16 extraFieldLength = (Int16)((_Extra == null) ? 0 : _Extra.Length);
            bytes[i++] = (byte)(extraFieldLength & 0x00FF);
            bytes[i++] = (byte)((extraFieldLength & 0xFF00) >> 8);

            // File (entry) Comment Length
            // the _CommentBytes private field was set during WriteHeader()
            int commentLength = (_CommentBytes == null) ? 0 : _CommentBytes.Length;

            // the size of our buffer defines the max length of the comment we can write
            if (commentLength + i > bytes.Length) commentLength = bytes.Length - i;
            bytes[i++] = (byte)(commentLength & 0x00FF);
            bytes[i++] = (byte)((commentLength & 0xFF00) >> 8);

            // Disk number start
            bool segmented = (this._container.ZipFile != null) &&
                (this._container.ZipFile.MaxOutputSegmentSize != 0);
            if (segmented) // workitem 13915
            {
                // Emit nonzero disknumber only if saving segmented archive.
                bytes[i++] = (byte)(_diskNumber & 0x00FF);
                bytes[i++] = (byte)((_diskNumber & 0xFF00) >> 8);
            }
            else
            {
                // If reading a segmneted archive and saving to a regular archive,
                // ZipEntry._diskNumber will be non-zero but it should be saved as
                // zero.
                bytes[i++] = 0;
                bytes[i++] = 0;
            }

            // internal file attrs
            // workitem 7801
            bytes[i++] = (byte)((_IsText) ? 1 : 0); // lo bit: filetype hint.  0=bin, 1=txt.
            bytes[i++] = 0;

            // external file attrs
            // workitem 7071
            bytes[i++] = (byte)(_ExternalFileAttrs & 0x000000FF);
            bytes[i++] = (byte)((_ExternalFileAttrs & 0x0000FF00) >> 8);
            bytes[i++] = (byte)((_ExternalFileAttrs & 0x00FF0000) >> 16);
            bytes[i++] = (byte)((_ExternalFileAttrs & 0xFF000000) >> 24);

            // workitem 11131
            // relative offset of local header.
            //
            // If necessary to go to 64-bit value, then emit 0xFFFFFFFF,
            // else write out the value.
            //
            // Even if zip64 is required for other reasons - number of the entry
            // > 65534, or uncompressed size of the entry > MAX_INT32, the ROLH
            // need not be stored in a 64-bit field .
            if (_RelativeOffsetOfLocalHeader > 0xFFFFFFFFL) // _OutputUsesZip64.Value
            {
                bytes[i++] = 0xFF;
                bytes[i++] = 0xFF;
                bytes[i++] = 0xFF;
                bytes[i++] = 0xFF;
            }
            else
            {
                bytes[i++] = (byte)(_RelativeOffsetOfLocalHeader & 0x000000FF);
                bytes[i++] = (byte)((_RelativeOffsetOfLocalHeader & 0x0000FF00) >> 8);
                bytes[i++] = (byte)((_RelativeOffsetOfLocalHeader & 0x00FF0000) >> 16);
                bytes[i++] = (byte)((_RelativeOffsetOfLocalHeader & 0xFF000000) >> 24);
            }

            // actual filename
            Buffer.BlockCopy(fileNameBytes, 0, bytes, i, filenameLength);
            i += filenameLength;

            // "Extra field"
            if (_Extra != null)
            {
                // workitem 11131
                //
                // copy from EntryHeader if available - it may have been updated.
                // if not, copy from Extra. This would be unnecessary if I just
                // updated the Extra field when updating EntryHeader, in
                // PostProcessOutput.

                //?? I don't understand why I wouldn't want to just use
                // the recalculated Extra field. ??

                // byte[] h = _EntryHeader ?? _Extra;
                // int offx = (h == _EntryHeader) ? 30 + filenameLength : 0;
                // Buffer.BlockCopy(h, offx, bytes, i, extraFieldLength);
                // i += extraFieldLength;

                byte[] h = _Extra;
                int offx = 0;
                Buffer.BlockCopy(h, offx, bytes, i, extraFieldLength);
                i += extraFieldLength;
            }

            // file (entry) comment
            if (commentLength != 0)
            {
                // now actually write the comment itself into the byte buffer
                Buffer.BlockCopy(_CommentBytes, 0, bytes, i, commentLength);
                // for (j = 0; (j < commentLength) && (i + j < bytes.Length); j++)
                //     bytes[i + j] = _CommentBytes[j];
                i += commentLength;
            }

            s.Write(bytes, 0, i);
        }


#if INFOZIP_UTF8
        static private bool FileNameIsUtf8(char[] FileNameChars)
        {
            bool isUTF8 = false;
            bool isUnicode = false;
            for (int j = 0; j < FileNameChars.Length; j++)
            {
                byte[] b = System.BitConverter.GetBytes(FileNameChars[j]);
                isUnicode |= (b.Length != 2);
                isUnicode |= (b[1] != 0);
                isUTF8 |= ((b[0] & 0x80) != 0);
            }

            return isUTF8;
        }
#endif


        private byte[] ConstructExtraField(bool forCentralDirectory)
        {
            var listOfBlocks = new System.Collections.Generic.List<byte[]>();
            byte[] block;

            // Conditionally emit an extra field with Zip64 information.  If the
            // Zip64 option is Always, we emit the field, before knowing that it's
            // necessary.  Later, if it turns out this entry does not need zip64,
            // we'll set the header ID to rubbish and the data will be ignored.
            // This results in additional overhead metadata in the zip file, but
            // it will be small in comparison to the entry data.
            //
            // On the other hand if the Zip64 option is AsNecessary and it's NOT
            // for the central directory, then we do the same thing.  Or, if the
            // Zip64 option is AsNecessary and it IS for the central directory,
            // and the entry requires zip64, then emit the header.
            if (_container.Zip64 == Zip64Option.Always ||
                (_container.Zip64 == Zip64Option.AsNecessary &&
                 (!forCentralDirectory || _entryRequiresZip64.Value)))
            {
                // add extra field for zip64 here
                // workitem 7924
                int sz = 4 + (forCentralDirectory ? 28 : 16);
                block = new byte[sz];
                int i = 0;

                if (_presumeZip64 || forCentralDirectory)
                {
                    // HeaderId = always use zip64 extensions.
                    block[i++] = 0x01;
                    block[i++] = 0x00;
                }
                else
                {
                    // HeaderId = dummy data now, maybe set to 0x0001 (ZIP64) later.
                    block[i++] = 0x99;
                    block[i++] = 0x99;
                }

                // DataSize
                block[i++] = (byte)(sz - 4);  // decimal 28 or 16  (workitem 7924)
                block[i++] = 0x00;

                // The actual metadata - we may or may not have real values yet...

                // uncompressed size
                Array.Copy(BitConverter.GetBytes(_UncompressedSize), 0, block, i, 8);
                i += 8;
                // compressed size
                Array.Copy(BitConverter.GetBytes(_CompressedSize), 0, block, i, 8);
                i += 8;

                // workitem 7924 - only include this if the "extra" field is for
                // use in the central directory.  It is unnecessary and not useful
                // for local header; makes WinZip choke.
                if (forCentralDirectory)
                {
                    // relative offset
                    Array.Copy(BitConverter.GetBytes(_RelativeOffsetOfLocalHeader), 0, block, i, 8);
                    i += 8;

                    // starting disk number
                    Array.Copy(BitConverter.GetBytes(0), 0, block, i, 4);
                }
                listOfBlocks.Add(block);
            }


#if AESCRYPTO
            if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
                Encryption == EncryptionAlgorithm.WinZipAes256)
            {
                block = new byte[4 + 7];
                int i = 0;
                // extra field for WinZip AES
                // header id
                block[i++] = 0x01;
                block[i++] = 0x99;

                // data size
                block[i++] = 0x07;
                block[i++] = 0x00;

                // vendor number
                block[i++] = 0x01;  // AE-1 - means "Verify CRC"
                block[i++] = 0x00;

                // vendor id "AE"
                block[i++] = 0x41;
                block[i++] = 0x45;

                // key strength
                int keystrength = GetKeyStrengthInBits(Encryption);
                if (keystrength == 128)
                    block[i] = 1;
                else if (keystrength == 256)
                    block[i] = 3;
                else
                    block[i] = 0xFF;
                i++;

                // actual compression method
                block[i++] = (byte)(_CompressionMethod & 0x00FF);
                block[i++] = (byte)(_CompressionMethod & 0xFF00);

                listOfBlocks.Add(block);
            }
#endif

            if (_ntfsTimesAreSet && _emitNtfsTimes)
            {
                block = new byte[32 + 4];
                // HeaderId   2 bytes    0x000a == NTFS times
                // Datasize   2 bytes    32
                // reserved   4 bytes    ?? don't care
                // timetag    2 bytes    0x0001 == NTFS 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
                int i = 0;
                // extra field for NTFS times
                // header id
                block[i++] = 0x0a;
                block[i++] = 0x00;

                // data size
                block[i++] = 32;
                block[i++] = 0;

                i += 4; // reserved

                // time tag
                block[i++] = 0x01;
                block[i++] = 0x00;

                // data size (again)
                block[i++] = 24;
                block[i++] = 0;

                Int64 z = _Mtime.ToFileTime();
                Array.Copy(BitConverter.GetBytes(z), 0, block, i, 8);
                i += 8;
                z = _Atime.ToFileTime();
                Array.Copy(BitConverter.GetBytes(z), 0, block, i, 8);
                i += 8;
                z = _Ctime.ToFileTime();
                Array.Copy(BitConverter.GetBytes(z), 0, block, i, 8);
                i += 8;

                listOfBlocks.Add(block);
            }

            if (_ntfsTimesAreSet && _emitUnixTimes)
            {
                int len = 5 + 4;
                if (!forCentralDirectory) len += 8;

                block = new byte[len];
                // local form:
                // --------------
                // HeaderId   2 bytes    0x5455 == unix timestamp
                // Datasize   2 bytes    13
                // flags      1 byte     7 (low three bits all set)
                // mtime      4 bytes    seconds since unix epoch
                // atime      4 bytes    seconds since unix epoch
                // ctime      4 bytes    seconds since unix epoch
                //
                // central directory form:
                //---------------------------------
                // HeaderId   2 bytes    0x5455 == unix timestamp
                // Datasize   2 bytes    5
                // flags      1 byte     7 (low three bits all set)
                // mtime      4 bytes    seconds since unix epoch
                //
                int i = 0;
                // extra field for "unix" times
                // header id
                block[i++] = 0x55;
                block[i++] = 0x54;

                // data size
                block[i++] = unchecked((byte)(len - 4));
                block[i++] = 0;

                // flags
                block[i++] = 0x07;

                Int32 z = unchecked((int)((_Mtime - _unixEpoch).TotalSeconds));
                Array.Copy(BitConverter.GetBytes(z), 0, block, i, 4);
                i += 4;
                if (!forCentralDirectory)
                {
                    z = unchecked((int)((_Atime - _unixEpoch).TotalSeconds));
                    Array.Copy(BitConverter.GetBytes(z), 0, block, i, 4);
                    i += 4;
                    z = unchecked((int)((_Ctime - _unixEpoch).TotalSeconds));
                    Array.Copy(BitConverter.GetBytes(z), 0, block, i, 4);
                    i += 4;
                }
                listOfBlocks.Add(block);
            }


            // inject other blocks here...


            // concatenate any blocks we've got:
            byte[] aggregateBlock = null;
            if (listOfBlocks.Count > 0)
            {
                int totalLength = 0;
                int i, current = 0;
                for (i = 0; i < listOfBlocks.Count; i++)
                    totalLength += listOfBlocks[i].Length;
                aggregateBlock = new byte[totalLength];
                for (i = 0; i < listOfBlocks.Count; i++)
                {
                    System.Array.Copy(listOfBlocks[i], 0, aggregateBlock, current, listOfBlocks[i].Length);
                    current += listOfBlocks[i].Length;
                }
            }

            return aggregateBlock;
        }


        private string NormalizeFileName()
        {
            // here, we need to flip the backslashes to forward-slashes,
            // also, we need to trim the \\server\share syntax from any UNC path.
            // and finally, we need to remove any leading .\

            string SlashFixed = FileName.Replace("\\", "/");
            string s1 = null;
            if ((_TrimVolumeFromFullyQualifiedPaths) && (FileName.Length >= 3)
                && (FileName[1] == ':') && (SlashFixed[2] == '/'))
            {
                // trim off volume letter, colon, and slash
                s1 = SlashFixed.Substring(3);
            }
            else if ((FileName.Length >= 4)
                     && ((SlashFixed[0] == '/') && (SlashFixed[1] == '/')))
            {
                int n = SlashFixed.IndexOf('/', 2);
                if (n == -1)
                    throw new ArgumentException("The path for that entry appears to be badly formatted");
                s1 = SlashFixed.Substring(n + 1);
            }
            else if ((FileName.Length >= 3)
                     && ((SlashFixed[0] == '.') && (SlashFixed[1] == '/')))
            {
                // trim off dot and slash
                s1 = SlashFixed.Substring(2);
            }
            else
            {
                s1 = SlashFixed;
            }
            return s1;
        }


        /// <summary>
        ///   generate and return a byte array that encodes the filename
        ///   for the entry.
        /// </summary>
        /// <remarks>
        ///   <para>
        ///     side effects: generate and store into _CommentBytes the
        ///     byte array for any comment attached to the entry. Also
        ///     sets _actualEncoding to indicate the actual encoding
        ///     used. The same encoding is used for both filename and
        ///     comment.
        ///   </para>
        /// </remarks>
        private byte[] GetEncodedFileNameBytes()
        {
            // workitem 6513
            var s1 = NormalizeFileName();

            switch(AlternateEncodingUsage)
            {
                case ZipOption.Always:
                    if (!(_Comment == null || _Comment.Length == 0))
                        _CommentBytes = AlternateEncoding.GetBytes(_Comment);
                    _actualEncoding = AlternateEncoding;
                    return AlternateEncoding.GetBytes(s1);

                case ZipOption.Never:
                    if (!(_Comment == null || _Comment.Length == 0))
                        _CommentBytes = ibm437.GetBytes(_Comment);
                    _actualEncoding = ibm437;
                    return ibm437.GetBytes(s1);
            }

            // arriving here means AlternateEncodingUsage is "AsNecessary"

            // case ZipOption.AsNecessary:
            // workitem 6513: when writing, use the alternative encoding
            // only when _actualEncoding is not yet set (it can be set
            // during Read), and when ibm437 will not do.

            byte[] result = ibm437.GetBytes(s1);
            // need to use this form of GetString() for .NET CF
            string s2 = ibm437.GetString(result, 0, result.Length);
            _CommentBytes = null;
            if (s2 != s1)
            {
                // Encoding the filename with ibm437 does not allow round-trips.
                // Therefore, use the alternate encoding.  Assume it will work,
                // no checking of round trips here.
                result = AlternateEncoding.GetBytes(s1);
                if (_Comment != null && _Comment.Length != 0)
                    _CommentBytes = AlternateEncoding.GetBytes(_Comment);
                _actualEncoding = AlternateEncoding;
                return result;
            }

            _actualEncoding = ibm437;

            // Using ibm437, FileName can be encoded without information
            // loss; now try the Comment.

            // if there is no comment, use ibm437.
            if (_Comment == null || _Comment.Length == 0)
                return result;

            // there is a comment. Get the encoded form.
            byte[] cbytes = ibm437.GetBytes(_Comment);
            string c2 = ibm437.GetString(cbytes,0,cbytes.Length);

            // Check for round-trip.
            if (c2 != Comment)
            {
                // Comment cannot correctly be encoded with ibm437.  Use
                // the alternate encoding.

                result = AlternateEncoding.GetBytes(s1);
                _CommentBytes = AlternateEncoding.GetBytes(_Comment);
                _actualEncoding = AlternateEncoding;
                return result;
            }

            // use IBM437
            _CommentBytes = cbytes;
            return result;
        }



        private bool WantReadAgain()
        {
            if (_UncompressedSize < 0x10) return false;
            if (_CompressionMethod == 0x00) return false;
            if (CompressionLevel == OfficeOpenXml.Packaging.Ionic.Zlib.CompressionLevel.None) return false;
            if (_CompressedSize < _UncompressedSize) return false;

            if (this._Source == ZipEntrySource.Stream && !this._sourceStream.CanSeek) return false;

#if AESCRYPTO
            if (_aesCrypto_forWrite != null && (CompressedSize - _aesCrypto_forWrite.SizeOfEncryptionMetadata) <= UncompressedSize + 0x10) return false;
#endif

            if (_zipCrypto_forWrite != null && (CompressedSize - 12) <= UncompressedSize) return false;

            return true;
        }



        private void MaybeUnsetCompressionMethodForWriting(int cycle)
        {
            // if we've already tried with compression... turn it off this time
            if (cycle > 1)
            {
                _CompressionMethod = 0x0;
                return;
            }
            // compression for directories = 0x00 (No Compression)
            if (IsDirectory)
            {
                _CompressionMethod = 0x0;
                return;
            }

            if (this._Source == ZipEntrySource.ZipFile)
            {
                return; // do nothing
            }

            // If __FileDataPosition is zero, then that means we will get the data
            // from a file or stream.

            // It is never possible to compress a zero-length file, so we check for
            // this condition.

            if (this._Source == ZipEntrySource.Stream)
            {
                // workitem 7742
                if (_sourceStream != null && _sourceStream.CanSeek)
                {
                    // Length prop will throw if CanSeek is false
                    long fileLength = _sourceStream.Length;
                    if (fileLength == 0)
                    {
                        _CompressionMethod = 0x00;
                        return;
                    }
                }
            }
            else if ((this._Source == ZipEntrySource.FileSystem) && (SharedUtilities.GetFileLength(LocalFileName) == 0L))
            {
                _CompressionMethod = 0x00;
                return;
            }

            // Ok, we're getting the data to be compressed from a
            // non-zero-length file or stream, or a file or stream of
            // unknown length, and we presume that it is non-zero.  In
            // that case we check the callback to see if the app wants
            // to tell us whether to compress or not.
            if (SetCompression != null)
                CompressionLevel = SetCompression(LocalFileName, _FileNameInArchive);

            // finally, set CompressionMethod to None if CompressionLevel is None
            if (CompressionLevel == (short)Ionic.Zlib.CompressionLevel.None &&
                CompressionMethod == Ionic.Zip.CompressionMethod.Deflate)
                _CompressionMethod = 0x00;

            return;
        }



        // write the header info for an entry
        internal void WriteHeader(Stream s, int cycle)
        {
            // Must remember the offset, within the output stream, of this particular
            // entry header.
            //
            // This is for 2 reasons:
            //
            //  1. so we can determine the RelativeOffsetOfLocalHeader (ROLH) for
            //     use in the central directory.
            //  2. so we can seek backward in case there is an error opening or reading
            //     the file, and the application decides to skip the file. In this case,
            //     we need to seek backward in the output stream to allow the next entry
            //     to be added to the zipfile output stream.
            //
            // Normally you would just store the offset before writing to the output
            // stream and be done with it.  But the possibility to use split archives
            // makes this approach ineffective.  In split archives, each file or segment
            // is bound to a max size limit, and each local file header must not span a
            // segment boundary; it must be written contiguously.  If it will fit in the
            // current segment, then the ROLH is just the current Position in the output
            // stream.  If it won't fit, then we need a new file (segment) and the ROLH
            // is zero.
            //
            // But we only can know if it is possible to write a header contiguously
            // after we know the size of the local header, a size that varies with
            // things like filename length, comments, and extra fields.  We have to
            // compute the header fully before knowing whether it will fit.
            //
            // That takes care of item #1 above.  Now, regarding #2.  If an error occurs
            // while computing the local header, we want to just seek backward. The
            // exception handling logic (in the caller of WriteHeader) uses ROLH to
            // scroll back.
            //
            // All this means we have to preserve the starting offset before computing
            // the header, and also we have to compute the offset later, to handle the
            // case of split archives.

            var counter = s as CountingStream;

            // workitem 8098: ok (output)
            // This may change later, for split archives

            // Don't set _RelativeOffsetOfLocalHeader. Instead, set a temp variable.
            // This allows for re-streaming, where a zip entry might be read from a
            // zip archive (and maybe decrypted, and maybe decompressed) and then
            // written to another zip archive, with different settings for
            // compression method, compression level, or encryption algorithm.
            _future_ROLH = (counter != null)
                ? counter.ComputedPosition
                : s.Position;

            int j = 0, i = 0;

            byte[] block = new byte[30];

            // signature
            block[i++] = (byte)(ZipConstants.ZipEntrySignature & 0x000000FF);
            block[i++] = (byte)((ZipConstants.ZipEntrySignature & 0x0000FF00) >> 8);
            block[i++] = (byte)((ZipConstants.ZipEntrySignature & 0x00FF0000) >> 16);
            block[i++] = (byte)((ZipConstants.ZipEntrySignature & 0xFF000000) >> 24);

            // Design notes for ZIP64:
            //
            // The specification says that the header must include the Compressed
            // and Uncompressed sizes, as well as the CRC32 value.  When creating
            // a zip via streamed processing, these quantities are not known until
            // after the compression is done.  Thus, a typical way to do it is to
            // insert zeroes for these quantities, then do the compression, then
            // seek back to insert the appropriate values, then seek forward to
            // the end of the file data.
            //
            // There is also the option of using bit 3 in the GP bitfield - to
            // specify that there is a data descriptor after the file data
            // containing these three quantities.
            //
            // This works when the size of the quantities is known, either 32-bits
            // or 64 bits as with the ZIP64 extensions.
            //
            // With Zip64, the 4-byte fields are set to 0xffffffff, and there is a
            // corresponding data block in the "extra field" that contains the
            // actual Compressed, uncompressed sizes.  (As well as an additional
            // field, the "Relative Offset of Local Header")
            //
            // The problem is when the app desires to use ZIP64 extensions
            // optionally, only when necessary.  Suppose the library assumes no
            // zip64 extensions when writing the header, then after compression
            // finds that the size of the data requires zip64.  At this point, the
            // header, already written to the file, won't have the necessary data
            // block in the "extra field".  The size of the entry header is fixed,
            // so it is not possible to just "add on" the zip64 data block after
            // compressing the file.  On the other hand, always using zip64 will
            // break interoperability with many other systems and apps.
            //
            // The approach we take is to insert a 32-byte dummy data block in the
            // extra field, whenever zip64 is to be used "as necessary". This data
            // block will get the actual zip64 HeaderId and zip64 metadata if
            // necessary.  If not necessary, the data block will get a meaningless
            // HeaderId (0x1111), and will be filled with zeroes.
            //
            // When zip64 is actually in use, we also need to set the
            // VersionNeededToExtract field to 45.
            //
            // There is one additional wrinkle: using zip64 as necessary conflicts
            // with output to non-seekable devices.  The header is emitted and
            // must indicate whether zip64 is in use, before we know if zip64 is
            // necessary.  Because there is no seeking, the header can never be
            // changed.  Therefore, on non-seekable devices,
            // Zip64Option.AsNecessary is the same as Zip64Option.Always.
            //


            // version needed- see AppNote.txt.
            //
            // need v5.1 for PKZIP strong encryption, or v2.0 for no encryption or
            // for PK encryption, 4.5 for zip64.  We may reset this later, as
            // necessary or zip64.

            _presumeZip64 = (_container.Zip64 == Zip64Option.Always ||
                             (_container.Zip64 == Zip64Option.AsNecessary && !s.CanSeek));
            Int16 VersionNeededToExtract = (Int16)(_presumeZip64 ? 45 : 20);
#if BZIP
            if (this.CompressionMethod == Ionic.Zip.CompressionMethod.BZip2)
                VersionNeededToExtract = 46;
#endif

            // (i==4)
            block[i++] = (byte)(VersionNeededToExtract & 0x00FF);
            block[i++] = (byte)((VersionNeededToExtract & 0xFF00) >> 8);

            // Get byte array. Side effect: sets ActualEncoding.
            // Must determine encoding before setting the bitfield.
            // workitem 6513
            byte[] fileNameBytes = GetEncodedFileNameBytes();
            Int16 filenameLength = (Int16)fileNameBytes.Length;

            // general purpose bitfield
            // In the current implementation, this library uses only these bits
            // in the GP bitfield:
            //  bit 0 = if set, indicates the entry is encrypted
            //  bit 3 = if set, indicates the CRC, C and UC sizes follow the file data.
            //  bit 6 = strong encryption - for pkware's meaning of strong encryption
            //  bit 11 = UTF-8 encoding is used in the comment and filename


            // Here we set or unset the encryption bit.
            // _BitField may already be set, as with a ZipEntry added into ZipOutputStream, which
            // has bit 3 always set. We only want to set one bit
            if (_Encryption == EncryptionAlgorithm.None)
                _BitField &= ~1;  // encryption bit OFF
            else
                _BitField |= 1;   // encryption bit ON


            // workitem 7941: WinZip does not the "strong encryption" bit  when using AES.
            // This "Strong Encryption" is a PKWare Strong encryption thing.
            //                 _BitField |= 0x0020;

            // set the UTF8 bit if necessary
#if SILVERLIGHT
            if (_actualEncoding.WebName == "utf-8")
#else
            if (_actualEncoding.CodePage == System.Text.Encoding.UTF8.CodePage)
#endif
                _BitField |= 0x0800;

            // The PKZIP spec says that if bit 3 is set (0x0008) in the General
            // Purpose BitField, then the CRC, Compressed size, and uncompressed
            // size are written directly after the file data.
            //
            // These 3 quantities are normally present in the regular zip entry
            // header. But, they are not knowable until after the compression is
            // done. So, in the normal case, we
            //
            //  - write the header, using zeros for these quantities
            //  - compress the data, and incidentally compute these quantities.
            //  - seek back and write the correct values them into the header.
            //
            // This is nice because, while it is more complicated to write the zip
            // file, it is simpler and less error prone to read the zip file, and
            // as a result more applications can read zip files produced this way,
            // with those 3 quantities in the header.
            //
            // But if seeking in the output stream is not possible, then we need
            // to set the appropriate bitfield and emit these quantities after the
            // compressed file data in the output.
            //
            // workitem 7216 - having trouble formatting a zip64 file that is
            // readable by WinZip.  not sure why!  What I found is that setting
            // bit 3 and following all the implications, the zip64 file is
            // readable by WinZip 12. and Perl's IO::Compress::Zip .  Perl takes
            // an interesting approach - it always sets bit 3 if ZIP64 in use.
            // DotNetZip now does the same; this gives better compatibility with
            // WinZip 12.

            if (IsDirectory || cycle == 99)
            {
                // (cycle == 99) indicates a zero-length entry written by ZipOutputStream

                _BitField &= ~0x0008;  // unset bit 3 - no "data descriptor" - ever
                _BitField &= ~0x0001;  // unset bit 1 - no encryption - ever
                Encryption = EncryptionAlgorithm.None;
                Password = null;
            }
            else if (!s.CanSeek)
                _BitField |= 0x0008;

#if DONT_GO_THERE
            else if (this.Encryption == EncryptionAlgorithm.PkzipWeak  &&
                     this._Source != ZipEntrySource.ZipFile)
            {
                // Set bit 3 to avoid the double-read perf issue.
                //
                // When PKZIP encryption is used, byte 11 of the encryption header is
                // used as a consistency check. It is normally set to the MSByte of the
                // CRC.  But this means the cRC must be known ebfore compression and
                // encryption, which means the entire stream has to be read twice.  To
                // avoid that, the high-byte of the time blob (when in DOS format) can
                // be used for the consistency check (byte 11 in the encryption header).
                // But this means the entry must have bit 3 set.
                //
                // Previously I used a more complex arrangement - using the methods like
                // FigureCrc32(), PrepOutputStream() and others, in order to manage the
                // seek-back in the source stream.  Why?  Because bit 3 is not always
                // friendly with third-party zip tools, like those on the Mac.
                //
                // This is why this code is still ifdef'd  out.
                //
                // Might consider making this yet another programmable option -
                // AlwaysUseBit3ForPkzip.  But that's for another day.
                //
                _BitField |= 0x0008;
            }
#endif

            // (i==6)
            block[i++] = (byte)(_BitField & 0x00FF);
            block[i++] = (byte)((_BitField & 0xFF00) >> 8);

            // Here, we want to set values for Compressed Size, Uncompressed Size,
            // and CRC.  If we have __FileDataPosition as not -1 (zero is a valid
            // FDP), then that means we are reading this zip entry from a zip
            // file, and we have good values for those quantities.
            //
            // If _FileDataPosition is -1, then we are constructing this Entry
            // from nothing.  We zero those quantities now, and we will compute
            // actual values for the three quantities later, when we do the
            // compression, and then seek back to write them into the appropriate
            // place in the header.
            if (this.__FileDataPosition == -1)
            {
                //_UncompressedSize = 0; // do not unset - may need this value for restream
                // _Crc32 = 0;           // ditto
                _CompressedSize = 0;
                _crcCalculated = false;
            }

            // set compression method here
            MaybeUnsetCompressionMethodForWriting(cycle);

            // (i==8) compression method
            block[i++] = (byte)(_CompressionMethod & 0x00FF);
            block[i++] = (byte)((_CompressionMethod & 0xFF00) >> 8);

            if (cycle == 99)
            {
                // (cycle == 99) indicates a zero-length entry written by ZipOutputStream
                SetZip64Flags();
            }

#if AESCRYPTO
            else if (Encryption == EncryptionAlgorithm.WinZipAes128 || Encryption == EncryptionAlgorithm.WinZipAes256)
            {
                i -= 2;
                block[i++] = 0x63;
                block[i++] = 0;
            }
#endif

            // LastMod
            _TimeBlob = Ionic.Zip.SharedUtilities.DateTimeToPacked(LastModified);

            // (i==10) time blob
            block[i++] = (byte)(_TimeBlob & 0x000000FF);
            block[i++] = (byte)((_TimeBlob & 0x0000FF00) >> 8);
            block[i++] = (byte)((_TimeBlob & 0x00FF0000) >> 16);
            block[i++] = (byte)((_TimeBlob & 0xFF000000) >> 24);

            // (i==14) CRC - if source==filesystem, this is zero now, actual value
            // will be calculated later.  if source==archive, this is a bonafide
            // value.
            block[i++] = (byte)(_Crc32 & 0x000000FF);
            block[i++] = (byte)((_Crc32 & 0x0000FF00) >> 8);
            block[i++] = (byte)((_Crc32 & 0x00FF0000) >> 16);
            block[i++] = (byte)((_Crc32 & 0xFF000000) >> 24);

            if (_presumeZip64)
            {
                // (i==18) CompressedSize (Int32) and UncompressedSize - all 0xFF for now
                for (j = 0; j < 8; j++)
                    block[i++] = 0xFF;
            }
            else
            {
                // (i==18) CompressedSize (Int32) - this value may or may not be
                // bonafide.  if source == filesystem, then it is zero, and we'll
                // learn it after we compress.  if source == archive, then it is
                // bonafide data.
                block[i++] = (byte)(_CompressedSize & 0x000000FF);
                block[i++] = (byte)((_CompressedSize & 0x0000FF00) >> 8);
                block[i++] = (byte)((_CompressedSize & 0x00FF0000) >> 16);
                block[i++] = (byte)((_CompressedSize & 0xFF000000) >> 24);

                // (i==22) UncompressedSize (Int32) - this value may or may not be
                // bonafide.
                block[i++] = (byte)(_UncompressedSize & 0x000000FF);
                block[i++] = (byte)((_UncompressedSize & 0x0000FF00) >> 8);
                block[i++] = (byte)((_UncompressedSize & 0x00FF0000) >> 16);
                block[i++] = (byte)((_UncompressedSize & 0xFF000000) >> 24);
            }

            // (i==26) filename length (Int16)
            block[i++] = (byte)(filenameLength & 0x00FF);
            block[i++] = (byte)((filenameLength & 0xFF00) >> 8);

            _Extra = ConstructExtraField(false);

            // (i==28) extra field length (short)
            Int16 extraFieldLength = (Int16)((_Extra == null) ? 0 : _Extra.Length);
            block[i++] = (byte)(extraFieldLength & 0x00FF);
            block[i++] = (byte)((extraFieldLength & 0xFF00) >> 8);

            // workitem 13542
            byte[] bytes = new byte[i + filenameLength + extraFieldLength];

            // get the fixed portion
            Buffer.BlockCopy(block, 0, bytes, 0, i);
            //for (j = 0; j < i; j++) bytes[j] = block[j];

            // The filename written to the archive.
            Buffer.BlockCopy(fileNameBytes, 0, bytes, i, fileNameBytes.Length);
            // for (j = 0; j < fileNameBytes.Length; j++)
            //     bytes[i + j] = fileNameBytes[j];

            i += fileNameBytes.Length;

            // "Extra field"
            if (_Extra != null)
            {
                Buffer.BlockCopy(_Extra, 0, bytes, i, _Extra.Length);
                // for (j = 0; j < _Extra.Length; j++)
                //     bytes[i + j] = _Extra[j];
                i += _Extra.Length;
            }

            _LengthOfHeader = i;

            // handle split archives
            var zss = s as ZipSegmentedStream;
            if (zss != null)
            {
                zss.ContiguousWrite = true;
                UInt32 requiredSegment = zss.ComputeSegment(i);
                if (requiredSegment != zss.CurrentSegment)
                    _future_ROLH = 0; // rollover!
                else
                    _future_ROLH = zss.Position;

                _diskNumber = requiredSegment;
            }

            // validate the ZIP64 usage
            if (_container.Zip64 == Zip64Option.Never && (uint)_RelativeOffsetOfLocalHeader >= 0xFFFFFFFF)
                throw new ZipException("Offset within the zip archive exceeds 0xFFFFFFFF. Consider setting the UseZip64WhenSaving property on the ZipFile instance.");


            // finally, write the header to the stream
            s.Write(bytes, 0, i);

            // now that the header is written, we can turn off the contiguous write restriction.
            if (zss != null)
                zss.ContiguousWrite = false;

            // Preserve this header data, we'll use it again later.
            // ..when seeking backward, to write again, after we have the Crc, compressed
            //   and uncompressed sizes.
            // ..and when writing the central directory structure.
            _EntryHeader = bytes;
        }




        private Int32 FigureCrc32()
        {
            if (_crcCalculated == false)
            {
                Stream input = null;
                // get the original stream:
                if (this._Source == ZipEntrySource.WriteDelegate)
                {
                    var output = new Ionic.Crc.CrcCalculatorStream(Stream.Null);
                    // allow the application to write the data
                    this._WriteDelegate(this.FileName, output);
                    _Crc32 = output.Crc;
                }
                else if (this._Source == ZipEntrySource.ZipFile)
                {
                    // nothing to do - the CRC is already set
                }
                else
                {
                    if (this._Source == ZipEntrySource.Stream)
                    {
                        PrepSourceStream();
                        input = this._sourceStream;
                    }
                    else if (this._Source == ZipEntrySource.JitStream)
                    {
                        // allow the application to open the stream
                        if (this._sourceStream == null)
                            _sourceStream = this._OpenDelegate(this.FileName);
                        PrepSourceStream();
                        input = this._sourceStream;
                    }
                    else if (this._Source == ZipEntrySource.ZipOutputStream)
                    {
                    }
                    else
                    {
                        //input = File.OpenRead(LocalFileName);
                        input = File.Open(LocalFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                    }

                    var crc32 = new Ionic.Crc.CRC32();
                    _Crc32 = crc32.GetCrc32(input);

                    if (_sourceStream == null)
                    {
#if NETCF
                        input.Close();
#else
                        input.Dispose();
#endif
                    }
                }
                _crcCalculated = true;
            }
            return _Crc32;
        }


        /// <summary>
        ///   Stores the position of the entry source stream, or, if the position is
        ///   already stored, seeks to that position.
        /// </summary>
        ///
        /// <remarks>
        /// <para>
        ///   This method is called in prep for reading the source stream.  If PKZIP
        ///   encryption is used, then we need to calc the CRC32 before doing the
        ///   encryption, because the CRC is used in the 12th byte of the PKZIP
        ///   encryption header.  So, we need to be able to seek backward in the source
        ///   when saving the ZipEntry. This method is called from the place that
        ///   calculates the CRC, and also from the method that does the encryption of
        ///   the file data.
        /// </para>
        ///
        /// <para>
        ///   The first time through, this method sets the _sourceStreamOriginalPosition
        ///   field. Subsequent calls to this method seek to that position.
        /// </para>
        /// </remarks>
        private void PrepSourceStream()
        {
            if (_sourceStream == null)
                throw new ZipException(String.Format("The input stream is null for entry '{0}'.", FileName));

            if (this._sourceStreamOriginalPosition != null)
            {
                // this will happen the 2nd cycle through, if the stream is seekable
                this._sourceStream.Position = this._sourceStreamOriginalPosition.Value;
            }
            else if (this._sourceStream.CanSeek)
            {
                // this will happen the first cycle through, if seekable
                this._sourceStreamOriginalPosition = new Nullable<Int64>(this._sourceStream.Position);
            }
            else if (this.Encryption == EncryptionAlgorithm.PkzipWeak)
            {
                // In general, using PKZIP encryption on a a zip entry whose input
                // comes from a non-seekable stream, is tricky.  Here's why:
                //
                // Byte 11 of the PKZIP encryption header is used for password
                // validation and consistency checknig.
                //
                // Normally, the highest byte of the CRC is used as the 11th (last) byte
                // in the PKZIP encryption header. This means the CRC must be known
                // before encryption is performed. Normally that means we read the full
                // data stream, compute the CRC, then seek back and read it again for
                // the compression+encryption phase. Obviously this is bad for
                // performance with a large input file.
                //
                // There's a twist in the ZIP spec (actually documented only in infozip
                // code, not in the spec itself) that allows the high-order byte of the
                // last modified time for the entry, when the lastmod time is in packed
                // (DOS) format, to be used for Byte 11 in the encryption header. In
                // this case, the bit 3 "data descriptor" must be used.
                //
                // An intelligent implementation would therefore force the use of the
                // bit 3 data descriptor when PKZIP encryption is in use, regardless.
                // This avoids the double-read of the stream to be encrypted.  So far,
                // DotNetZip doesn't do that; it just punts when the input stream is
                // non-seekable, and the output does not use Bit 3.
                //
                // The other option is to use the CRC when it is already available, eg,
                // when the source for the data is a ZipEntry (when the zip file is
                // being updated). In this case we already know the CRC and can just use
                // what we know.

                if (this._Source != ZipEntrySource.ZipFile && ((this._BitField & 0x0008) != 0x0008))
                    throw new ZipException("It is not possible to use PKZIP encryption on a non-seekable input stream");
            }
        }


        /// <summary>
        /// Copy metadata that may have been changed by the app.  We do this when
        /// resetting the zipFile instance.  If the app calls Save() on a ZipFile, then
        /// tries to party on that file some more, we may need to Reset() it , which
        /// means re-reading the entries and then copying the metadata.  I think.
        /// </summary>
        internal void CopyMetaData(ZipEntry source)
        {
            this.__FileDataPosition = source.__FileDataPosition;
            this.CompressionMethod = source.CompressionMethod;
            this._CompressionMethod_FromZipFile = source._CompressionMethod_FromZipFile;
            this._CompressedFileDataSize = source._CompressedFileDataSize;
            this._UncompressedSize = source._UncompressedSize;
            this._BitField = source._BitField;
            this._Source = source._Source;
            this._LastModified = source._LastModified;
            this._Mtime = source._Mtime;
            this._Atime = source._Atime;
            this._Ctime = source._Ctime;
            this._ntfsTimesAreSet = source._ntfsTimesAreSet;
            this._emitUnixTimes = source._emitUnixTimes;
            this._emitNtfsTimes = source._emitNtfsTimes;
        }


        private void OnWriteBlock(Int64 bytesXferred, Int64 totalBytesToXfer)
        {
            if (_container.ZipFile != null)
                _ioOperationCanceled = _container.ZipFile.OnSaveBlock(this, bytesXferred, totalBytesToXfer);
        }



        private void _WriteEntryData(Stream s)
        {
            // Read in the data from the input stream (often a file in the filesystem),
            // and write it to the output stream, calculating a CRC on it as we go.
            // We will also compress and encrypt as necessary.

            Stream input = null;
            long fdp = -1L;
            try
            {
                // Want to record the position in the zip file of the zip entry
                // data (as opposed to the metadata).  s.Position may fail on some
                // write-only streams, eg stdout or System.Web.HttpResponseStream.
                // We swallow that exception, because we don't care, in that case.
                // But, don't set __FileDataPosition directly.  It may be needed
                // to READ the zip entry from the zip file, if this is a
                // "re-stream" situation. In other words if the zip entry has
                // changed compression level, or compression method, or (maybe?)
                // encryption algorithm.  In that case if the original entry is
                // encrypted, we need __FileDataPosition to be the value for the
                // input zip file.  This s.Position is for the output zipfile.  So
                // we copy fdp to __FileDataPosition after this entry has been
                // (maybe) restreamed.
                fdp = s.Position;
            }
            catch (Exception) { }

            try
            {
                // Use fileLength for progress updates, and to decide whether we can
                // skip encryption and compression altogether (in case of length==zero)
                long fileLength = SetInputAndFigureFileLength(ref input);

                // Wrap a counting stream around the raw output stream:
                // This is the last thing that happens before the bits go to the
                // application-provided stream.
                //
                // Sometimes s is a CountingStream. Doesn't matter. Wrap it with a
                // counter anyway. We need to count at both levels.

                CountingStream entryCounter = new CountingStream(s);

                Stream encryptor;
                Stream compressor;

                if (fileLength != 0L)
                {
                    // Maybe wrap an encrypting stream around the counter: This will
                    // happen BEFORE output counting, and AFTER compression, if encryption
                    // is used.
                    encryptor = MaybeApplyEncryption(entryCounter);

                    // Maybe wrap a compressing Stream around that.
                    // This will happen BEFORE encryption (if any) as we write data out.
                    compressor = MaybeApplyCompression(encryptor, fileLength);
                }
                else
                {
                    encryptor = compressor = entryCounter;
                }

                // Wrap a CrcCalculatorStream around that.
                // This will happen BEFORE compression (if any) as we write data out.
                var output = new Ionic.Crc.CrcCalculatorStream(compressor, true);

                // output.Write() causes this flow:
                // calc-crc -> compress -> encrypt -> count -> actually write

                if (this._Source == ZipEntrySource.WriteDelegate)
                {
                    // allow the application to write the data
                    this._WriteDelegate(this.FileName, output);
                }
                else
                {
                    // synchronously copy the input stream to the output stream-chain
                    byte[] buffer = new byte[BufferSize];
                    int n;
                    while ((n = SharedUtilities.ReadWithRetry(input, buffer, 0, buffer.Length, FileName)) != 0)
                    {
                        output.Write(buffer, 0, n);
                        OnWriteBlock(output.TotalBytesSlurped, fileLength);
                        if (_ioOperationCanceled)
                            break;
                    }
                }

                FinishOutputStream(s, entryCounter, encryptor, compressor, output);
            }
            finally
            {
                if (this._Source == ZipEntrySource.JitStream)
                {
                    // allow the application to close the stream
                    if (this._CloseDelegate != null)
                        this._CloseDelegate(this.FileName, input);
                }
                else if ((input as FileStream) != null)
                {
#if NETCF
                    input.Close();
#else
                    input.Dispose();
#endif
                }
            }

            if (_ioOperationCanceled)
                return;

            // set FDP now, to allow for re-streaming
            this.__FileDataPosition = fdp;
            PostProcessOutput(s);
        }


        /// <summary>
        ///   Set the input stream and get its length, if possible.  The length is
        ///   used for progress updates, AND, to allow an optimization in case of
        ///   a stream/file of zero length. In that case we skip the Encrypt and
        ///   compression Stream. (like DeflateStream or BZip2OutputStream)
        /// </summary>
        private long SetInputAndFigureFileLength(ref Stream input)
        {
            long fileLength = -1L;
            // get the original stream:
            if (this._Source == ZipEntrySource.Stream)
            {
                PrepSourceStream();
                input = this._sourceStream;

                // Try to get the length, no big deal if not available.
                try { fileLength = this._sourceStream.Length; }
                catch (NotSupportedException) { }
            }
            else if (this._Source == ZipEntrySource.ZipFile)
            {
                // we are "re-streaming" the zip entry.
                string pwd = (_Encryption_FromZipFile == EncryptionAlgorithm.None) ? null : (this._Password ?? this._container.Password);
                this._sourceStream = InternalOpenReader(pwd);
                PrepSourceStream();
                input = this._sourceStream;
                fileLength = this._sourceStream.Length;
            }
            else if (this._Source == ZipEntrySource.JitStream)
            {
                // allow the application to open the stream
                if (this._sourceStream == null) _sourceStream = this._OpenDelegate(this.FileName);
                PrepSourceStream();
                input = this._sourceStream;
                try { fileLength = this._sourceStream.Length; }
                catch (NotSupportedException) { }
            }
            else if (this._Source == ZipEntrySource.FileSystem)
            {
                // workitem 7145
                FileShare fs = FileShare.ReadWrite;
#if !NETCF
                // FileShare.Delete is not defined for the Compact Framework
                fs |= FileShare.Delete;
#endif
                // workitem 8423
                input = File.Open(LocalFileName, FileMode.Open, FileAccess.Read, fs);
                fileLength = input.Length;
            }

            return fileLength;
        }



        internal void FinishOutputStream(Stream s,
                                         CountingStream entryCounter,
                                         Stream encryptor,
                                         Stream compressor,
                                         Ionic.Crc.CrcCalculatorStream output)
        {
            if (output == null) return;

            output.Close();
            output.Dispose();
            // by calling Close() on the deflate stream, we write the footer bytes, as necessary.
            if ((compressor as Ionic.Zlib.DeflateStream) != null)
            {
                compressor.Close();
                compressor.Dispose();
            }
#if BZIP
            else if ((compressor as Ionic.BZip2.BZip2OutputStream) != null)
                compressor.Close();
#if !NETCF
            else if ((compressor as Ionic.BZip2.ParallelBZip2OutputStream) != null)
                compressor.Close();
#endif
#endif

#if !NETCF
            else if ((compressor as Ionic.Zlib.ParallelDeflateOutputStream) != null)
            {
                compressor.Close();
                compressor.Dispose();
            }
#endif

            encryptor.Flush();
            encryptor.Close();
            encryptor.Dispose();
            _LengthOfTrailer = 0;

            _UncompressedSize = output.TotalBytesSlurped;

#if AESCRYPTO
            WinZipAesCipherStream wzacs = encryptor as WinZipAesCipherStream;
            if (wzacs != null && _UncompressedSize > 0)
            {
                s.Write(wzacs.FinalAuthentication, 0, 10);
                _LengthOfTrailer += 10;
            }
#endif
            _CompressedFileDataSize = entryCounter.BytesWritten;
            _CompressedSize = _CompressedFileDataSize;   // may be adjusted
            _Crc32 = output.Crc;

            // Set _RelativeOffsetOfLocalHeader now, to allow for re-streaming
            StoreRelativeOffset();
        }




        internal void PostProcessOutput(Stream s)
        {
            var s1 = s as CountingStream;

            // workitem 8931 - for WriteDelegate.
            // The WriteDelegate changes things because there can be a zero-byte stream
            // written. In all other cases DotNetZip knows the length of the stream
            // before compressing and encrypting. In this case we have to circle back,
            // and omit all the crypto stuff - the GP bitfield, and the crypto header.
            if (_UncompressedSize == 0 && _CompressedSize == 0)
            {
                if (this._Source == ZipEntrySource.ZipOutputStream) return;  // nothing to do...

                if (_Password != null)
                {
                    int headerBytesToRetract = 0;
                    if (Encryption == EncryptionAlgorithm.PkzipWeak)
                        headerBytesToRetract = 12;
#if AESCRYPTO
                    else if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
                             Encryption == EncryptionAlgorithm.WinZipAes256)
                    {
                        headerBytesToRetract = _aesCrypto_forWrite._Salt.Length + _aesCrypto_forWrite.GeneratedPV.Length;
                    }
#endif
                    if (this._Source == ZipEntrySource.ZipOutputStream && !s.CanSeek)
                        throw new ZipException("Zero bytes written, encryption in use, and non-seekable output.");

                    if (Encryption != EncryptionAlgorithm.None)
                    {
                        // seek back in the stream to un-output the security metadata
                        s.Seek(-1 * headerBytesToRetract, SeekOrigin.Current);
                        s.SetLength(s.Position);
                        // workitem 10178
                        Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(s);

                        // workitem 11131
                        // adjust the count on the CountingStream as necessary
                        if (s1 != null) s1.Adjust(headerBytesToRetract);

                        // subtract the size of the security header from the _LengthOfHeader
                        _LengthOfHeader -= headerBytesToRetract;
                        __FileDataPosition -= headerBytesToRetract;
                    }
                    _Password = null;

                    // turn off the encryption bit
                    _BitField &= ~(0x0001);

                    // copy the updated bitfield value into the header
                    int j = 6;
                    _EntryHeader[j++] = (byte)(_BitField & 0x00FF);
                    _EntryHeader[j++] = (byte)((_BitField & 0xFF00) >> 8);

#if AESCRYPTO
                    if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
                        Encryption == EncryptionAlgorithm.WinZipAes256)
                    {
                        // Fix the extra field - overwrite the 0x9901 headerId
                        // with dummy data. (arbitrarily, 0x9999)
                        Int16 fnLength = (short)(_EntryHeader[26] + _EntryHeader[27] * 256);
                        int offx = 30 + fnLength;
                        int aesIndex = FindExtraFieldSegment(_EntryHeader, offx, 0x9901);
                        if (aesIndex >= 0)
                        {
                            _EntryHeader[aesIndex++] = 0x99;
                            _EntryHeader[aesIndex++] = 0x99;
                        }
                    }
#endif
                }

                CompressionMethod = 0;
                Encryption = EncryptionAlgorithm.None;
            }
            else if (_zipCrypto_forWrite != null
#if AESCRYPTO
                     || _aesCrypto_forWrite != null
#endif
                     )

            {
                if (Encryption == EncryptionAlgorithm.PkzipWeak)
                {
                    _CompressedSize += 12; // 12 extra bytes for the encryption header
                }
#if AESCRYPTO
                else if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
                         Encryption == EncryptionAlgorithm.WinZipAes256)
                {
                    // adjust the compressed size to include the variable (salt+pv)
                    // security header and 10-byte trailer. According to the winzip AES
                    // spec, that metadata is included in the "Compressed Size" figure
                    // when encoding the zip archive.
                    _CompressedSize += _aesCrypto_forWrite.SizeOfEncryptionMetadata;
                }
#endif
            }

            int i = 8;
            _EntryHeader[i++] = (byte)(_CompressionMethod & 0x00FF);
            _EntryHeader[i++] = (byte)((_CompressionMethod & 0xFF00) >> 8);

            i = 14;
            // CRC - the correct value now
            _EntryHeader[i++] = (byte)(_Crc32 & 0x000000FF);
            _EntryHeader[i++] = (byte)((_Crc32 & 0x0000FF00) >> 8);
            _EntryHeader[i++] = (byte)((_Crc32 & 0x00FF0000) >> 16);
            _EntryHeader[i++] = (byte)((_Crc32 & 0xFF000000) >> 24);

            SetZip64Flags();

            // (i==26) filename length (Int16)
            Int16 filenameLength = (short)(_EntryHeader[26] + _EntryHeader[27] * 256);
            Int16 extraFieldLength = (short)(_EntryHeader[28] + _EntryHeader[29] * 256);

            if (_OutputUsesZip64.Value)
            {
                // VersionNeededToExtract - set to 45 to indicate zip64
                _EntryHeader[4] = (byte)(45 & 0x00FF);
                _EntryHeader[5] = 0x00;

                // workitem 7924 - don't need bit 3
                // // workitem 7917
                // // set bit 3 for ZIP64 compatibility with WinZip12
                // _BitField |= 0x0008;
                // _EntryHeader[6] = (byte)(_BitField & 0x00FF);

                // CompressedSize and UncompressedSize - 0xFF
                for (int j = 0; j < 8; j++)
                    _EntryHeader[i++] = 0xff;

                // At this point we need to find the "Extra field" that follows the
                // filename.  We had already emitted it, but the data (uncomp, comp,
                // ROLH) was not available at the time we did so.  Here, we emit it
                // again, with final values.

                i = 30 + filenameLength;
                _EntryHeader[i++] = 0x01;  // zip64
                _EntryHeader[i++] = 0x00;

                i += 2; // skip over data size, which is 16+4

                Array.Copy(BitConverter.GetBytes(_UncompressedSize), 0, _EntryHeader, i, 8);
                i += 8;
                Array.Copy(BitConverter.GetBytes(_CompressedSize), 0, _EntryHeader, i, 8);
            }
            else
            {
                // VersionNeededToExtract - reset to 20 since no zip64
                _EntryHeader[4] = (byte)(20 & 0x00FF);
                _EntryHeader[5] = 0x00;

                // CompressedSize - the correct value now
                i = 18;
                _EntryHeader[i++] = (byte)(_CompressedSize & 0x000000FF);
                _EntryHeader[i++] = (byte)((_CompressedSize & 0x0000FF00) >> 8);
                _EntryHeader[i++] = (byte)((_CompressedSize & 0x00FF0000) >> 16);
                _EntryHeader[i++] = (byte)((_CompressedSize & 0xFF000000) >> 24);

                // UncompressedSize - the correct value now
                _EntryHeader[i++] = (byte)(_UncompressedSize & 0x000000FF);
                _EntryHeader[i++] = (byte)((_UncompressedSize & 0x0000FF00) >> 8);
                _EntryHeader[i++] = (byte)((_UncompressedSize & 0x00FF0000) >> 16);
                _EntryHeader[i++] = (byte)((_UncompressedSize & 0xFF000000) >> 24);

                // The HeaderId in the extra field header, is already dummied out.
                if (extraFieldLength != 0)
                {
                    i = 30 + filenameLength;
                    // For zip archives written by this library, if the zip64
                    // header exists, it is the first header. Because of the logic
                    // used when first writing the _EntryHeader bytes, the
                    // HeaderId is not guaranteed to be any particular value.  So
                    // we determine if the first header is a putative zip64 header
                    // by examining the datasize.  UInt16 HeaderId =
                    // (UInt16)(_EntryHeader[i] + _EntryHeader[i + 1] * 256);
                    Int16 DataSize = (short)(_EntryHeader[i + 2] + _EntryHeader[i + 3] * 256);
                    if (DataSize == 16)
                    {
                        // reset to Header Id to dummy value, effectively dummy-ing out the zip64 metadata
                        _EntryHeader[i++] = 0x99;
                        _EntryHeader[i++] = 0x99;
                    }
                }
            }


#if AESCRYPTO

            if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
                Encryption == EncryptionAlgorithm.WinZipAes256)
            {
                // Must set compressionmethod to 0x0063 (decimal 99)
                //
                // and then set the compression method bytes inside the extra
                // field to the actual compression method value.

                i = 8;
                _EntryHeader[i++] = 0x63;
                _EntryHeader[i++] = 0;

                i = 30 + filenameLength;
                do
                {
                    UInt16 HeaderId = (UInt16)(_EntryHeader[i] + _EntryHeader[i + 1] * 256);
                    Int16 DataSize = (short)(_EntryHeader[i + 2] + _EntryHeader[i + 3] * 256);
                    if (HeaderId != 0x9901)
                    {
                        // skip this header
                        i += DataSize + 4;
                    }
                    else
                    {
                        i += 9;
                        // actual compression method
                        _EntryHeader[i++] = (byte)(_CompressionMethod & 0x00FF);
                        _EntryHeader[i++] = (byte)(_CompressionMethod & 0xFF00);
                    }
                } while (i < (extraFieldLength - 30 - filenameLength));
            }
#endif

            // finally, write the data.

            // workitem 7216 - sometimes we don't seek even if we CAN.  ASP.NET
            // Response.OutputStream, or stdout are non-seekable.  But we may also want
            // to NOT seek in other cases, eg zip64.  For all cases, we just check bit 3
            // to see if we want to seek.  There's one exception - if using a
            // ZipOutputStream, and PKZip encryption is in use, then we set bit 3 even
            // if the out is seekable. This is so the check on the last byte of the
            // PKZip Encryption Header can be done on the current time, as opposed to
            // the CRC, to prevent streaming the file twice.  So, test for
            // ZipOutputStream and seekable, and if so, seek back, even if bit 3 is set.

            if ((_BitField & 0x0008) != 0x0008 ||
                 (this._Source == ZipEntrySource.ZipOutputStream && s.CanSeek))
            {
                // seek back and rewrite the entry header
                var zss = s as ZipSegmentedStream;
                if (zss != null && _diskNumber != zss.CurrentSegment)
                {
                    // In this case the entry header is in a different file,
                    // which has already been closed. Need to re-open it.
                    using (Stream hseg = ZipSegmentedStream.ForUpdate(this._container.ZipFile.Name, _diskNumber))
                    {
                        hseg.Seek(this._RelativeOffsetOfLocalHeader, SeekOrigin.Begin);
                        hseg.Write(_EntryHeader, 0, _EntryHeader.Length);
                    }
                }
                else
                {
                    // seek in the raw output stream, to the beginning of the header for
                    // this entry.
                    // workitem 8098: ok (output)
                    s.Seek(this._RelativeOffsetOfLocalHeader, SeekOrigin.Begin);

                    // write the updated header to the output stream
                    s.Write(_EntryHeader, 0, _EntryHeader.Length);

                    // adjust the count on the CountingStream as necessary
                    if (s1 != null) s1.Adjust(_EntryHeader.Length);

                    // seek in the raw output stream, to the end of the file data
                    // for this entry
                    s.Seek(_CompressedSize, SeekOrigin.Current);
                }
            }

            // emit the descriptor - only if not a directory.
            if (((_BitField & 0x0008) == 0x0008) && !IsDirectory)
            {
                byte[] Descriptor = new byte[16 + (_OutputUsesZip64.Value ? 8 : 0)];
                i = 0;

                // signature
                Array.Copy(BitConverter.GetBytes(ZipConstants.ZipEntryDataDescriptorSignature), 0, Descriptor, i, 4);
                i += 4;

                // CRC - the correct value now
                Array.Copy(BitConverter.GetBytes(_Crc32), 0, Descriptor, i, 4);
                i += 4;

                // workitem 7917
                if (_OutputUsesZip64.Value)
                {
                    // CompressedSize - the correct value now
                    Array.Copy(BitConverter.GetBytes(_CompressedSize), 0, Descriptor, i, 8);
                    i += 8;

                    // UncompressedSize - the correct value now
                    Array.Copy(BitConverter.GetBytes(_UncompressedSize), 0, Descriptor, i, 8);
                    i += 8;
                }
                else
                {
                    // CompressedSize - (lower 32 bits) the correct value now
                    Descriptor[i++] = (byte)(_CompressedSize & 0x000000FF);
                    Descriptor[i++] = (byte)((_CompressedSize & 0x0000FF00) >> 8);
                    Descriptor[i++] = (byte)((_CompressedSize & 0x00FF0000) >> 16);
                    Descriptor[i++] = (byte)((_CompressedSize & 0xFF000000) >> 24);

                    // UncompressedSize - (lower 32 bits) the correct value now
                    Descriptor[i++] = (byte)(_UncompressedSize & 0x000000FF);
                    Descriptor[i++] = (byte)((_UncompressedSize & 0x0000FF00) >> 8);
                    Descriptor[i++] = (byte)((_UncompressedSize & 0x00FF0000) >> 16);
                    Descriptor[i++] = (byte)((_UncompressedSize & 0xFF000000) >> 24);
                }

                // finally, write the trailing descriptor to the output stream
                s.Write(Descriptor, 0, Descriptor.Length);

                _LengthOfTrailer += Descriptor.Length;
            }
        }



        private void SetZip64Flags()
        {
            // zip64 housekeeping
            _entryRequiresZip64 = new Nullable<bool>
                (_CompressedSize >= 0xFFFFFFFF || _UncompressedSize >= 0xFFFFFFFF || _RelativeOffsetOfLocalHeader >= 0xFFFFFFFF);

            // validate the ZIP64 usage
            if (_container.Zip64 == Zip64Option.Never && _entryRequiresZip64.Value)
                throw new ZipException("Compressed or Uncompressed size, or offset exceeds the maximum value. Consider setting the UseZip64WhenSaving property on the ZipFile instance.");

            _OutputUsesZip64 = new Nullable<bool>(_container.Zip64 == Zip64Option.Always || _entryRequiresZip64.Value);
        }



        /// <summary>
        ///   Prepare the given stream for output - wrap it in a CountingStream, and
        ///   then in a CRC stream, and an encryptor and deflator as appropriate.
        /// </summary>
        /// <remarks>
        ///   <para>
        ///     Previously this was used in ZipEntry.Write(), but in an effort to
        ///     introduce some efficiencies in that method I've refactored to put the
        ///     code inline.  This method still gets called by ZipOutputStream.
        ///   </para>
        /// </remarks>
        internal void PrepOutputStream(Stream s,
                                       long streamLength,
                                       out CountingStream outputCounter,
                                       out Stream encryptor,
                                       out Stream compressor,
                                       out Ionic.Crc.CrcCalculatorStream output)
        {
            TraceWriteLine("PrepOutputStream: e({0}) comp({1}) crypto({2}) zf({3})",
                           FileName,
                           CompressionLevel,
                           Encryption,
                           _container.Name);

            // Wrap a counting stream around the raw output stream:
            // This is the last thing that happens before the bits go to the
            // application-provided stream.
            outputCounter = new CountingStream(s);

            // Sometimes the incoming "raw" output stream is already a CountingStream.
            // Doesn't matter. Wrap it with a counter anyway. We need to count at both
            // levels.

            if (streamLength != 0L)
            {
                // Maybe wrap an encrypting stream around that:
                // This will happen BEFORE output counting, and AFTER deflation, if encryption
                // is used.
                encryptor = MaybeApplyEncryption(outputCounter);

                // Maybe wrap a compressing Stream around that.
                // This will happen BEFORE encryption (if any) as we write data out.
                compressor = MaybeApplyCompression(encryptor, streamLength);
            }
            else
            {
                encryptor = compressor = outputCounter;
            }
            // Wrap a CrcCalculatorStream around that.
            // This will happen BEFORE compression (if any) as we write data out.
            output = new Ionic.Crc.CrcCalculatorStream(compressor, true);
        }



        private Stream MaybeApplyCompression(Stream s, long streamLength)
        {
            if (_CompressionMethod == 0x08 && CompressionLevel != Ionic.Zlib.CompressionLevel.None)
            {
#if !NETCF
                // ParallelDeflateThreshold == 0    means ALWAYS use parallel deflate
                // ParallelDeflateThreshold == -1L  means NEVER use parallel deflate
                // Other values specify the actual threshold.
                if (_container.ParallelDeflateThreshold == 0L ||
                    (streamLength > _container.ParallelDeflateThreshold &&
                     _container.ParallelDeflateThreshold > 0L))
                {
                    // This is sort of hacky.
                    //
                    // It's expensive to create a ParallelDeflateOutputStream, because
                    // of the large memory buffers.  But the class is unlike most Stream
                    // classes in that it can be re-used, so the caller can compress
                    // multiple files with it, one file at a time.  The key is to call
                    // Reset() on it, in between uses.
                    //
                    // The ParallelDeflateOutputStream is attached to the container
                    // itself - there is just one for the entire ZipFile or
                    // ZipOutputStream. So it gets created once, per save, and then
                    // re-used many times.
                    //
                    // This approach will break when we go to a "parallel save"
                    // approach, where multiple entries within the zip file are being
                    // compressed and saved at the same time.  But for now it's ok.
                    //

                    // instantiate the ParallelDeflateOutputStream
                    if (_container.ParallelDeflater == null)
                    {
                        _container.ParallelDeflater =
                            new ParallelDeflateOutputStream(s,
                                                                        CompressionLevel,
                                                                       _container.Strategy,
                                                                       true);
                        // can set the codec buffer size only before the first call to Write().
                        if (_container.CodecBufferSize > 0)
                            _container.ParallelDeflater.BufferSize = _container.CodecBufferSize;
                        if (_container.ParallelDeflateMaxBufferPairs > 0)
                            _container.ParallelDeflater.MaxBufferPairs =
                                _container.ParallelDeflateMaxBufferPairs;
                    }
                    // reset it with the new stream
                    Ionic.Zlib.ParallelDeflateOutputStream o1 = _container.ParallelDeflater;
                    o1.Reset(s);
                    return o1;
                }
#endif
                var o = new DeflateStream(s, OfficeOpenXml.Packaging.Ionic.Zlib.CompressionMode.Compress,
                                                     CompressionLevel,
                                                     true);
                if (_container.CodecBufferSize > 0)
                    o.BufferSize = _container.CodecBufferSize;
                o.Strategy = _container.Strategy;
                return o;
            }


#if BZIP
            if (_CompressionMethod == 0x0c)
            {
#if !NETCF
                if (_container.ParallelDeflateThreshold == 0L ||
                    (streamLength > _container.ParallelDeflateThreshold &&
                     _container.ParallelDeflateThreshold > 0L))
                {

                    var o1 = new Ionic.BZip2.ParallelBZip2OutputStream(s, true);
                    return o1;
                }
#endif
                var o = new Ionic.BZip2.BZip2OutputStream(s, true);
                return o;
            }
#endif

            return s;
        }



        private Stream MaybeApplyEncryption(Stream s)
        {
            if (Encryption == EncryptionAlgorithm.PkzipWeak)
            {
                TraceWriteLine("MaybeApplyEncryption: e({0}) PKZIP", FileName);

                return new ZipCipherStream(s, _zipCrypto_forWrite, CryptoMode.Encrypt);
            }
#if AESCRYPTO
            if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
                     Encryption == EncryptionAlgorithm.WinZipAes256)
            {
                TraceWriteLine("MaybeApplyEncryption: e({0}) AES", FileName);

                return new WinZipAesCipherStream(s, _aesCrypto_forWrite, CryptoMode.Encrypt);
            }
#endif
            TraceWriteLine("MaybeApplyEncryption: e({0}) None", FileName);

            return s;
        }



        private void OnZipErrorWhileSaving(Exception e)
        {
            if (_container.ZipFile != null)
                _ioOperationCanceled = _container.ZipFile.OnZipErrorSaving(this, e);
        }



        internal void Write(Stream s)
        {
            var cs1 = s as CountingStream;
            var zss1 = s as ZipSegmentedStream;

            bool done = false;
            do
            {
                try
                {
                    // When the app is updating a zip file, it may be possible to
                    // just copy data for a ZipEntry from the source zipfile to the
                    // destination, as a block, without decompressing and
                    // recompressing, etc.  But, in some cases the app modifies the
                    // properties on a ZipEntry prior to calling Save(). A change to
                    // any of the metadata - the FileName, CompressioLeve and so on,
                    // means DotNetZip cannot simply copy through the existing
                    // ZipEntry data unchanged.
                    //
                    // There are two cases:
                    //
                    //  1. Changes to only metadata, which means the header and
                    //     central directory must be changed.
                    //
                    //  2. Changes to the properties that affect the compressed
                    //     stream, such as CompressionMethod, CompressionLevel, or
                    //     EncryptionAlgorithm. In this case, DotNetZip must
                    //     "re-stream" the data: the old entry data must be maybe
                    //     decrypted, maybe decompressed, then maybe re-compressed
                    //     and maybe re-encrypted.
                    //
                    // This test checks if the source for the entry data is a zip file, and
                    // if a restream is necessary.  If NOT, then it just copies through
                    // one entry, potentially changing the metadata.

                    if (_Source == ZipEntrySource.ZipFile && !_restreamRequiredOnSave)
                    {
                        CopyThroughOneEntry(s);
                        return;
                    }

                    // Is the entry a directory?  If so, the write is relatively simple.
                    if (IsDirectory)
                    {
                        WriteHeader(s, 1);
                        StoreRelativeOffset();
                        _entryRequiresZip64 = new Nullable<bool>(_RelativeOffsetOfLocalHeader >= 0xFFFFFFFF);
                        _OutputUsesZip64 = new Nullable<bool>(_container.Zip64 == Zip64Option.Always || _entryRequiresZip64.Value);
                        // handle case for split archives
                        if (zss1 != null)
                            _diskNumber = zss1.CurrentSegment;

                        return;
                    }

                    // At this point, the source for this entry is not a directory, and
                    // not a previously created zip file, or the source for the entry IS
                    // a previously created zip but the settings whave changed in
                    // important ways and therefore we will need to process the
                    // bytestream (compute crc, maybe compress, maybe encrypt) in order
                    // to write the content into the new zip.
                    //
                    // We do this in potentially 2 passes: The first time we do it as
                    // requested, maybe with compression and maybe encryption.  If that
                    // causes the bytestream to inflate in size, and if compression was
                    // on, then we turn off compression and do it again.


                    bool readAgain = true;
                    int nCycles = 0;
                    do
                    {
                        nCycles++;

                        WriteHeader(s, nCycles);

                        // write the encrypted header
                        WriteSecurityMetadata(s);

                        // write the (potentially compressed, potentially encrypted) file data
                        _WriteEntryData(s);

                        // track total entry size (including the trailing descriptor and MAC)
                        _TotalEntrySize = _LengthOfHeader + _CompressedFileDataSize + _LengthOfTrailer;

                        // The file data has now been written to the stream, and
                        // the file pointer is positioned directly after file data.

                        if (nCycles > 1) readAgain = false;
                        else if (!s.CanSeek) readAgain = false;
                        else readAgain = WantReadAgain();

                        if (readAgain)
                        {
                            // Seek back in the raw output stream, to the beginning of the file
                            // data for this entry.

                            // handle case for split archives
                            if (zss1 != null)
                            {
                                // Console.WriteLine("***_diskNumber/first: {0}", _diskNumber);
                                // Console.WriteLine("***_diskNumber/current: {0}", zss.CurrentSegment);
                                zss1.TruncateBackward(_diskNumber, _RelativeOffsetOfLocalHeader);
                            }
                            else
                                // workitem 8098: ok (output).
                                s.Seek(_RelativeOffsetOfLocalHeader, SeekOrigin.Begin);

                            // If the last entry expands, we read again; but here, we must
                            // truncate the stream to prevent garbage data after the
                            // end-of-central-directory.

                            // workitem 8098: ok (output).
                            s.SetLength(s.Position);

                            // Adjust the count on the CountingStream as necessary.
                            if (cs1 != null) cs1.Adjust(_TotalEntrySize);
                        }
                    }
                    while (readAgain);
                    _skippedDuringSave = false;
                    done = true;
                }
                catch (System.Exception exc1)
                {
                    ZipErrorAction orig = this.ZipErrorAction;
                    int loop = 0;
                    do
                    {
                        if (ZipErrorAction == ZipErrorAction.Throw)
                            throw;

                        if (ZipErrorAction == ZipErrorAction.Skip ||
                            ZipErrorAction == ZipErrorAction.Retry)
                        {
                            // must reset file pointer here.
                            // workitem 13903 - seek back only when necessary
                            long p1 = (cs1 != null)
                                ? cs1.ComputedPosition
                                : s.Position;
                            long delta = p1 - _future_ROLH;
                            if (delta > 0)
                            {
                                s.Seek(delta, SeekOrigin.Current); // may throw
                                long p2 = s.Position;
                                s.SetLength(s.Position);  // to prevent garbage if this is the last entry
                                if (cs1 != null) cs1.Adjust(p1 - p2);
                            }
                            if (ZipErrorAction == ZipErrorAction.Skip)
                            {
                                WriteStatus("Skipping file {0} (exception: {1})", LocalFileName, exc1.ToString());

                                _skippedDuringSave = true;
                                done = true;
                            }
                            else
                                this.ZipErrorAction = orig;
                            break;
                        }

                        if (loop > 0) throw;

                        if (ZipErrorAction == ZipErrorAction.InvokeErrorEvent)
                        {
                            OnZipErrorWhileSaving(exc1);
                            if (_ioOperationCanceled)
                            {
                                done = true;
                                break;
                            }
                        }
                        loop++;
                    }
                    while (true);
                }
            }
            while (!done);
        }


        internal void StoreRelativeOffset()
        {
            _RelativeOffsetOfLocalHeader = _future_ROLH;
        }



        internal void NotifySaveComplete()
        {
            // When updating a zip file, there are two contexts for properties
            // like Encryption or CompressionMethod - the values read from the
            // original zip file, and the values used in the updated zip file.
            // The _FromZipFile versions are the originals.  At the end of a save,
            // these values are the same.  So we need to update them.  This takes
            // care of the boundary case where a single zipfile instance can be
            // saved multiple times, with distinct changes to the properties on
            // the entries, in between each Save().
            _Encryption_FromZipFile = _Encryption;
            _CompressionMethod_FromZipFile = _CompressionMethod;
            _restreamRequiredOnSave = false;
            _metadataChanged = false;
            //_Source = ZipEntrySource.None;
            _Source = ZipEntrySource.ZipFile; // workitem 10694
        }


        internal void WriteSecurityMetadata(Stream outstream)
        {
            if (Encryption == EncryptionAlgorithm.None)
                return;

            string pwd = this._Password;

            // special handling for source == ZipFile.
            // Want to support the case where we re-stream an encrypted entry. This will involve,
            // at runtime, reading, decrypting, and decompressing from the original zip file, then
            // compressing, encrypting, and writing to the output zip file.

            // If that's what we're doing, and the password hasn't been set on the entry,
            // we use the container (ZipFile/ZipOutputStream) password to decrypt.
            // This test here says to use the container password to re-encrypt, as well,
            // with that password, if the entry password is null.

            if (this._Source == ZipEntrySource.ZipFile && pwd == null)
                pwd = this._container.Password;

            if (pwd == null)
            {
                _zipCrypto_forWrite = null;
#if AESCRYPTO
                _aesCrypto_forWrite = null;
#endif
                return;
            }

            TraceWriteLine("WriteSecurityMetadata: e({0}) crypto({1}) pw({2})",
                           FileName, Encryption.ToString(), pwd);

            if (Encryption == EncryptionAlgorithm.PkzipWeak)
            {
                // If PKZip (weak) encryption is in use, then the encrypted entry data
                // is preceded by 12-byte "encryption header" for the entry.

                _zipCrypto_forWrite = ZipCrypto.ForWrite(pwd);

                // generate the random 12-byte header:
                var rnd = new System.Random();
                byte[] encryptionHeader = new byte[12];
                rnd.NextBytes(encryptionHeader);

                // workitem 8271
                if ((this._BitField & 0x0008) == 0x0008)
                {
                    // In the case that bit 3 of the general purpose bit flag is set to
                    // indicate the presence of a 'data descriptor' (signature
                    // 0x08074b50), the last byte of the decrypted header is sometimes
                    // compared with the high-order byte of the lastmodified time,
                    // rather than the high-order byte of the CRC, to verify the
                    // password.
                    //
                    // This is not documented in the PKWare Appnote.txt.
                    // This was discovered this by analysis of the Crypt.c source file in the
                    // InfoZip library
                    // http://www.info-zip.org/pub/infozip/

                    // Also, winzip insists on this!
                    _TimeBlob = Ionic.Zip.SharedUtilities.DateTimeToPacked(LastModified);
                    encryptionHeader[11] = (byte)((this._TimeBlob >> 8) & 0xff);
                }
                else
                {
                    // When bit 3 is not set, the CRC value is required before
                    // encryption of the file data begins. In this case there is no way
                    // around it: must read the stream in its entirety to compute the
                    // actual CRC before proceeding.
                    FigureCrc32();
                    encryptionHeader[11] = (byte)((this._Crc32 >> 24) & 0xff);
                }

                // Encrypt the random header, INCLUDING the final byte which is either
                // the high-order byte of the CRC32, or the high-order byte of the
                // _TimeBlob.  Must do this BEFORE encrypting the file data.  This
                // step changes the state of the cipher, or in the words of the PKZIP
                // spec, it "further initializes" the cipher keys.

                byte[] cipherText = _zipCrypto_forWrite.EncryptMessage(encryptionHeader, encryptionHeader.Length);

                // Write the ciphered bonafide encryption header.
                outstream.Write(cipherText, 0, cipherText.Length);
                _LengthOfHeader += cipherText.Length;  // 12 bytes
            }

#if AESCRYPTO
            else if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
                Encryption == EncryptionAlgorithm.WinZipAes256)
            {
                // If WinZip AES encryption is in use, then the encrypted entry data is
                // preceded by a variable-sized Salt and a 2-byte "password
                // verification" value for the entry.

                int keystrength = GetKeyStrengthInBits(Encryption);
                _aesCrypto_forWrite = WinZipAesCrypto.Generate(pwd, keystrength);
                outstream.Write(_aesCrypto_forWrite.Salt, 0, _aesCrypto_forWrite._Salt.Length);
                outstream.Write(_aesCrypto_forWrite.GeneratedPV, 0, _aesCrypto_forWrite.GeneratedPV.Length);
                _LengthOfHeader += _aesCrypto_forWrite._Salt.Length + _aesCrypto_forWrite.GeneratedPV.Length;

                TraceWriteLine("WriteSecurityMetadata: AES e({0}) keybits({1}) _LOH({2})",
                               FileName, keystrength, _LengthOfHeader);

            }
#endif

        }



        private void CopyThroughOneEntry(Stream outStream)
        {
            // Just read the entry from the existing input zipfile and write to the output.
            // But, if metadata has changed (like file times or attributes), or if the ZIP64
            // option has changed, we can re-stream the entry data but must recompute the
            // metadata.
            if (this.LengthOfHeader == 0)
                throw new BadStateException("Bad header length.");

            // is it necessary to re-constitute new metadata for this entry?
            bool needRecompute = _metadataChanged ||
                (this.ArchiveStream is ZipSegmentedStream) ||
                (outStream is ZipSegmentedStream) ||
                (_InputUsesZip64 && _container.UseZip64WhenSaving == Zip64Option.Never) ||
                (!_InputUsesZip64 && _container.UseZip64WhenSaving == Zip64Option.Always);

            if (needRecompute)
                CopyThroughWithRecompute(outStream);
            else
                CopyThroughWithNoChange(outStream);

            // zip64 housekeeping
            _entryRequiresZip64 = new Nullable<bool>
                (_CompressedSize >= 0xFFFFFFFF || _UncompressedSize >= 0xFFFFFFFF ||
                _RelativeOffsetOfLocalHeader >= 0xFFFFFFFF
                );

            _OutputUsesZip64 = new Nullable<bool>(_container.Zip64 == Zip64Option.Always || _entryRequiresZip64.Value);
        }



        private void CopyThroughWithRecompute(Stream outstream)
        {
            int n;
            byte[] bytes = new byte[BufferSize];
            var input = new CountingStream(this.ArchiveStream);

            long origRelativeOffsetOfHeader = _RelativeOffsetOfLocalHeader;

            // The header length may change due to rename of file, add a comment, etc.
            // We need to retain the original.
            int origLengthOfHeader = LengthOfHeader; // including crypto bytes!

            // WriteHeader() has the side effect of changing _RelativeOffsetOfLocalHeader
            // and setting _LengthOfHeader.  While ReadHeader() reads the crypto header if
            // present, WriteHeader() does not write the crypto header.
            WriteHeader(outstream, 0);
            StoreRelativeOffset();

            if (!this.FileName.EndsWith("/"))
            {
                // Not a directory; there is file data.
                // Seek to the beginning of the entry data in the input stream.

                long pos = origRelativeOffsetOfHeader + origLengthOfHeader;
                int len = GetLengthOfCryptoHeaderBytes(_Encryption_FromZipFile);
                pos -= len; // want to keep the crypto header
                _LengthOfHeader += len;

                input.Seek(pos, SeekOrigin.Begin);

                // copy through everything after the header to the output stream
                long remaining = this._CompressedSize;

                while (remaining > 0)
                {
                    len = (remaining > bytes.Length) ? bytes.Length : (int)remaining;

                    // read
                    n = input.Read(bytes, 0, len);
                    //_CheckRead(n);

                    // write
                    outstream.Write(bytes, 0, n);
                    remaining -= n;
                    OnWriteBlock(input.BytesRead, this._CompressedSize);
                    if (_ioOperationCanceled)
                        break;
                }

                // bit 3 descriptor
                if ((this._BitField & 0x0008) == 0x0008)
                {
                    int size = 16;
                    if (_InputUsesZip64) size += 8;
                    byte[] Descriptor = new byte[size];
                    input.Read(Descriptor, 0, size);

                    if (_InputUsesZip64 && _container.UseZip64WhenSaving == Zip64Option.Never)
                    {
                        // original descriptor was 24 bytes, now we need 16.
                        // Must check for underflow here.
                        // signature + CRC.
                        outstream.Write(Descriptor, 0, 8);

                        // Compressed
                        if (_CompressedSize > 0xFFFFFFFF)
                            throw new InvalidOperationException("ZIP64 is required");
                        outstream.Write(Descriptor, 8, 4);

                        // UnCompressed
                        if (_UncompressedSize > 0xFFFFFFFF)
                            throw new InvalidOperationException("ZIP64 is required");
                        outstream.Write(Descriptor, 16, 4);
                        _LengthOfTrailer -= 8;
                    }
                    else if (!_InputUsesZip64 && _container.UseZip64WhenSaving == Zip64Option.Always)
                    {
                        // original descriptor was 16 bytes, now we need 24
                        // signature + CRC
                        byte[] pad = new byte[4];
                        outstream.Write(Descriptor, 0, 8);
                        // Compressed
                        outstream.Write(Descriptor, 8, 4);
                        outstream.Write(pad, 0, 4);
                        // UnCompressed
                        outstream.Write(Descriptor, 12, 4);
                        outstream.Write(pad, 0, 4);
                        _LengthOfTrailer += 8;
                    }
                    else
                    {
                        // same descriptor on input and output. Copy it through.
                        outstream.Write(Descriptor, 0, size);
                        //_LengthOfTrailer += size;
                    }
                }
            }

            _TotalEntrySize = _LengthOfHeader + _CompressedFileDataSize + _LengthOfTrailer;
        }


        private void CopyThroughWithNoChange(Stream outstream)
        {
            int n;
            byte[] bytes = new byte[BufferSize];
            var input = new CountingStream(this.ArchiveStream);

            // seek to the beginning of the entry data in the input stream
            input.Seek(this._RelativeOffsetOfLocalHeader, SeekOrigin.Begin);

            if (this._TotalEntrySize == 0)
            {
                // We've never set the length of the entry.
                // Set it here.
                this._TotalEntrySize = this._LengthOfHeader + this._CompressedFileDataSize + _LengthOfTrailer;

                // The CompressedSize includes all the leading metadata associated
                // to encryption, if any, as well as the compressed data, or
                // compressed-then-encrypted data, and the trailer in case of AES.

                // The CompressedFileData size is the same, less the encryption
                // framing data (12 bytes header for PKZip; 10/18 bytes header and
                // 10 byte trailer for AES).

                // The _LengthOfHeader includes all the zip entry header plus the
                // crypto header, if any.  The _LengthOfTrailer includes the
                // 10-byte MAC for AES, where appropriate, and the bit-3
                // Descriptor, where applicable.
            }


            // workitem 5616
            // remember the offset, within the output stream, of this particular entry header.
            // This may have changed if any of the other entries changed (eg, if a different
            // entry was removed or added.)
            var counter = outstream as CountingStream;
            _RelativeOffsetOfLocalHeader = (counter != null)
                ? counter.ComputedPosition
                : outstream.Position;  // BytesWritten

            // copy through the header, filedata, trailer, everything...
            long remaining = this._TotalEntrySize;
            while (remaining > 0)
            {
                int len = (remaining > bytes.Length) ? bytes.Length : (int)remaining;

                // read
                n = input.Read(bytes, 0, len);
                //_CheckRead(n);

                // write
                outstream.Write(bytes, 0, n);
                remaining -= n;
                OnWriteBlock(input.BytesRead, this._TotalEntrySize);
                if (_ioOperationCanceled)
                    break;
            }
        }




        [System.Diagnostics.ConditionalAttribute("Trace")]
        private void TraceWriteLine(string format, params object[] varParams)
        {
            lock (_outputLock)
            {
                int tid = System.Threading.Thread.CurrentThread.GetHashCode();
#if !(NETCF || SILVERLIGHT)
                Console.ForegroundColor = (ConsoleColor)(tid % 8 + 8);
#endif
                Console.Write("{0:000} ZipEntry.Write ", tid);
                Console.WriteLine(format, varParams);
#if !(NETCF || SILVERLIGHT)
                Console.ResetColor();
#endif
            }
        }

        private object _outputLock = new Object();
    }
}
