| // ZipFile.Save.cs | 
 | // ------------------------------------------------------------------ | 
 | // | 
 | // Copyright (c) 2009 Dino Chiesa. | 
 | // All rights reserved. | 
 | // | 
 | // This code module is part of DotNetZip, a zipfile class library. | 
 | // | 
 | // ------------------------------------------------------------------ | 
 | // | 
 | // This code is licensed under the Microsoft Public License. | 
 | // See the file License.txt for the license details. | 
 | // More info on: http://dotnetzip.codeplex.com | 
 | // | 
 | // ------------------------------------------------------------------ | 
 | // | 
 | // last saved (in emacs): | 
 | // Time-stamp: <2011-August-05 13:31:23> | 
 | // | 
 | // ------------------------------------------------------------------ | 
 | // | 
 | // This module defines the methods for Save operations on zip files. | 
 | // | 
 | // ------------------------------------------------------------------ | 
 | // | 
 |  | 
 |  | 
 | using System; | 
 | using System.IO; | 
 | using System.Collections.Generic; | 
 |  | 
 | namespace OfficeOpenXml.Packaging.Ionic.Zip | 
 | { | 
 |  | 
 |     internal partial class ZipFile | 
 |     { | 
 |  | 
 |         /// <summary> | 
 |         ///   Delete file with retry on UnauthorizedAccessException. | 
 |         /// </summary> | 
 |         /// | 
 |         /// <remarks> | 
 |         ///   <para> | 
 |         ///     When calling File.Delete() on a file that has been "recently" | 
 |         ///     created, the call sometimes fails with | 
 |         ///     UnauthorizedAccessException. This method simply retries the Delete 3 | 
 |         ///     times with a sleep between tries. | 
 |         ///   </para> | 
 |         /// </remarks> | 
 |         /// | 
 |         /// <param name='filename'>the name of the file to be deleted</param> | 
 |         private void DeleteFileWithRetry(string filename) | 
 |         { | 
 |             bool done = false; | 
 |             int nRetries = 3; | 
 |             for (int i=0; i < nRetries && !done; i++) | 
 |             { | 
 |                 try | 
 |                 { | 
 |                     File.Delete(filename); | 
 |                     done = true; | 
 |                 } | 
 |                 catch (System.UnauthorizedAccessException) | 
 |                 { | 
 |                     Console.WriteLine("************************************************** Retry delete."); | 
 |                     System.Threading.Thread.Sleep(200+i*200); | 
 |                 } | 
 |             } | 
 |         } | 
 |  | 
 |  | 
 |         /// <summary> | 
 |         ///   Saves the Zip archive to a file, specified by the Name property of the | 
 |         ///   <c>ZipFile</c>. | 
 |         /// </summary> | 
 |         /// | 
 |         /// <remarks> | 
 |         /// <para> | 
 |         ///   The <c>ZipFile</c> instance is written to storage, typically a zip file | 
 |         ///   in a filesystem, only when the caller calls <c>Save</c>.  In the typical | 
 |         ///   case, the Save operation writes the zip content to a temporary file, and | 
 |         ///   then renames the temporary file to the desired name. If necessary, this | 
 |         ///   method will delete a pre-existing file before the rename. | 
 |         /// </para> | 
 |         /// | 
 |         /// <para> | 
 |         ///   The <see cref="ZipFile.Name"/> property is specified either explicitly, | 
 |         ///   or implicitly using one of the parameterized ZipFile constructors.  For | 
 |         ///   COM Automation clients, the <c>Name</c> property must be set explicitly, | 
 |         ///   because COM Automation clients cannot call parameterized constructors. | 
 |         /// </para> | 
 |         /// | 
 |         /// <para> | 
 |         ///   When using a filesystem file for the Zip output, it is possible to call | 
 |         ///   <c>Save</c> multiple times on the <c>ZipFile</c> instance. With each | 
 |         ///   call the zip content is re-written to the same output file. | 
 |         /// </para> | 
 |         /// | 
 |         /// <para> | 
 |         ///   Data for entries that have been added to the <c>ZipFile</c> instance is | 
 |         ///   written to the output when the <c>Save</c> method is called. This means | 
 |         ///   that the input streams for those entries must be available at the time | 
 |         ///   the application calls <c>Save</c>.  If, for example, the application | 
 |         ///   adds entries with <c>AddEntry</c> using a dynamically-allocated | 
 |         ///   <c>MemoryStream</c>, the memory stream must not have been disposed | 
 |         ///   before the call to <c>Save</c>. See the <see | 
 |         ///   cref="ZipEntry.InputStream"/> property for more discussion of the | 
 |         ///   availability requirements of the input stream for an entry, and an | 
 |         ///   approach for providing just-in-time stream lifecycle management. | 
 |         /// </para> | 
 |         /// | 
 |         /// </remarks> | 
 |         /// | 
 |         /// <seealso cref="Ionic.Zip.ZipFile.AddEntry(String, System.IO.Stream)"/> | 
 |         /// | 
 |         /// <exception cref="Ionic.Zip.BadStateException"> | 
 |         ///   Thrown if you haven't specified a location or stream for saving the zip, | 
 |         ///   either in the constructor or by setting the Name property, or if you try | 
 |         ///   to save a regular zip archive to a filename with a .exe extension. | 
 |         /// </exception> | 
 |         /// | 
 |         /// <exception cref="System.OverflowException"> | 
 |         ///   Thrown if <see cref="MaxOutputSegmentSize"/> is non-zero, and the number | 
 |         ///   of segments that would be generated for the spanned zip file during the | 
 |         ///   save operation exceeds 99.  If this happens, you need to increase the | 
 |         ///   segment size. | 
 |         /// </exception> | 
 |         /// | 
 |         public void Save() | 
 |         { | 
 |             try | 
 |             { | 
 |                 bool thisSaveUsedZip64 = false; | 
 |                 _saveOperationCanceled = false; | 
 |                 _numberOfSegmentsForMostRecentSave = 0; | 
 |                 OnSaveStarted(); | 
 |  | 
 |                 if (WriteStream == null) | 
 |                     throw new BadStateException("You haven't specified where to save the zip."); | 
 |  | 
 |                 if (_name != null && _name.EndsWith(".exe") && !_SavingSfx) | 
 |                     throw new BadStateException("You specified an EXE for a plain zip file."); | 
 |  | 
 |                 // check if modified, before saving. | 
 |                 if (!_contentsChanged) | 
 |                 { | 
 |                     OnSaveCompleted(); | 
 |                     if (Verbose) StatusMessageTextWriter.WriteLine("No save is necessary...."); | 
 |                     return; | 
 |                 } | 
 |  | 
 |                 Reset(true); | 
 |  | 
 |                 if (Verbose) StatusMessageTextWriter.WriteLine("saving...."); | 
 |  | 
 |                 // validate the number of entries | 
 |                 if (_entries.Count >= 0xFFFF && _zip64 == Zip64Option.Never) | 
 |                     throw new ZipException("The number of entries is 65535 or greater. Consider setting the UseZip64WhenSaving property on the ZipFile instance."); | 
 |  | 
 |  | 
 |                 // write an entry in the zip for each file | 
 |                 int n = 0; | 
 |                 // workitem 9831 | 
 |                 ICollection<ZipEntry> c = (SortEntriesBeforeSaving) ? EntriesSorted : Entries; | 
 |                 foreach (ZipEntry e in c) // _entries.Values | 
 |                 { | 
 |                     OnSaveEntry(n, e, true); | 
 |                     e.Write(WriteStream); | 
 |                     if (_saveOperationCanceled) | 
 |                         break; | 
 |  | 
 |                     n++; | 
 |                     OnSaveEntry(n, e, false); | 
 |                     if (_saveOperationCanceled) | 
 |                         break; | 
 |  | 
 |                     // Some entries can be skipped during the save. | 
 |                     if (e.IncludedInMostRecentSave) | 
 |                         thisSaveUsedZip64 |= e.OutputUsedZip64.Value; | 
 |                 } | 
 |  | 
 |  | 
 |  | 
 |                 if (_saveOperationCanceled) | 
 |                     return; | 
 |  | 
 |                 var zss = WriteStream as ZipSegmentedStream; | 
 |  | 
 |                 _numberOfSegmentsForMostRecentSave = (zss!=null) | 
 |                     ? zss.CurrentSegment | 
 |                     : 1; | 
 |  | 
 |                 bool directoryNeededZip64 = | 
 |                     ZipOutput.WriteCentralDirectoryStructure | 
 |                     (WriteStream, | 
 |                      c, | 
 |                      _numberOfSegmentsForMostRecentSave, | 
 |                      _zip64, | 
 |                      Comment, | 
 |                      new ZipContainer(this)); | 
 |  | 
 |                 OnSaveEvent(ZipProgressEventType.Saving_AfterSaveTempArchive); | 
 |  | 
 |                 _hasBeenSaved = true; | 
 |                 _contentsChanged = false; | 
 |  | 
 |                 thisSaveUsedZip64 |= directoryNeededZip64; | 
 |                 _OutputUsesZip64 = new Nullable<bool>(thisSaveUsedZip64); | 
 |  | 
 |  | 
 |                 // do the rename as necessary | 
 |                 if (_name != null && | 
 |                     (_temporaryFileName!=null || zss != null)) | 
 |                 { | 
 |                     // _temporaryFileName may remain null if we are writing to a stream. | 
 |                     // only close the stream if there is a file behind it. | 
 | #if NETCF | 
 |                     WriteStream.Close(); | 
 | #else | 
 |                     WriteStream.Dispose(); | 
 | #endif | 
 |                     if (_saveOperationCanceled) | 
 |                         return; | 
 |  | 
 |                     if (_fileAlreadyExists && this._readstream != null) | 
 |                     { | 
 |                         // This means we opened and read a zip file. | 
 |                         // If we are now saving to the same file, we need to close the | 
 |                         // orig file, first. | 
 |                         this._readstream.Close(); | 
 |                         this._readstream = null; | 
 |                         // the archiveStream for each entry needs to be null | 
 |                         foreach (var e in c) | 
 |                         { | 
 |                             var zss1 = e._archiveStream as ZipSegmentedStream; | 
 |                             if (zss1 != null) | 
 | #if NETCF | 
 |                                 zss1.Close(); | 
 | #else | 
 |                                 zss1.Dispose(); | 
 | #endif | 
 |                             e._archiveStream = null; | 
 |                         } | 
 |                     } | 
 |  | 
 |                     string tmpName = null; | 
 |                     if (File.Exists(_name)) | 
 |                     { | 
 |                         // the steps: | 
 |                         // | 
 |                         // 1. Delete tmpName | 
 |                         // 2. move existing zip to tmpName | 
 |                         // 3. rename (File.Move) working file to name of existing zip | 
 |                         // 4. delete tmpName | 
 |                         // | 
 |                         // This series of steps avoids the exception, | 
 |                         // System.IO.IOException: | 
 |                         //   "Cannot create a file when that file already exists." | 
 |                         // | 
 |                         // Cannot just call File.Replace() here because | 
 |                         // there is a possibility that the TEMP volume is different | 
 |                         // that the volume for the final file (c:\ vs d:\). | 
 |                         // So we need to do a Delete+Move pair. | 
 |                         // | 
 |                         // But, when doing the delete, Windows allows a process to | 
 |                         // delete the file, even though it is held open by, say, a | 
 |                         // virus scanner. It gets internally marked as "delete | 
 |                         // pending". The file does not actually get removed from the | 
 |                         // file system, it is still there after the File.Delete | 
 |                         // call. | 
 |                         // | 
 |                         // Therefore, we need to move the existing zip, which may be | 
 |                         // held open, to some other name. Then rename our working | 
 |                         // file to the desired name, then delete (possibly delete | 
 |                         // pending) the "other name". | 
 |                         // | 
 |                         // Ideally this would be transactional. It's possible that the | 
 |                         // delete succeeds and the move fails. Lacking transactions, if | 
 |                         // this kind of failure happens, we're hosed, and this logic will | 
 |                         // throw on the next File.Move(). | 
 |                         // | 
 |                         //File.Delete(_name); | 
 |                         // workitem 10447 | 
 | #if NETCF || SILVERLIGHT | 
 |                         tmpName = _name + "." + SharedUtilities.GenerateRandomStringImpl(8,0) + ".tmp"; | 
 | #else | 
 |                         tmpName = _name + "." + Path.GetRandomFileName(); | 
 | #endif | 
 |                         if (File.Exists(tmpName)) | 
 |                             DeleteFileWithRetry(tmpName); | 
 |                         File.Move(_name, tmpName); | 
 |                     } | 
 |  | 
 |                     OnSaveEvent(ZipProgressEventType.Saving_BeforeRenameTempArchive); | 
 |                     File.Move((zss != null) ? zss.CurrentTempName : _temporaryFileName, | 
 |                               _name); | 
 |  | 
 |                     OnSaveEvent(ZipProgressEventType.Saving_AfterRenameTempArchive); | 
 |  | 
 |                     if (tmpName != null) | 
 |                     { | 
 |                         try | 
 |                         { | 
 |                             // not critical | 
 |                             if (File.Exists(tmpName)) | 
 |                                 File.Delete(tmpName); | 
 |                         } | 
 |                         catch | 
 |                         { | 
 |                             // don't care about exceptions here. | 
 |                         } | 
 |  | 
 |                     } | 
 |                     _fileAlreadyExists = true; | 
 |                 } | 
 |  | 
 |                 NotifyEntriesSaveComplete(c); | 
 |                 OnSaveCompleted(); | 
 |                 _JustSaved = true; | 
 |             } | 
 |  | 
 |             // workitem 5043 | 
 |             finally | 
 |             { | 
 |                 CleanupAfterSaveOperation(); | 
 |             } | 
 |  | 
 |             return; | 
 |         } | 
 |  | 
 |  | 
 |  | 
 |         private static void NotifyEntriesSaveComplete(ICollection<ZipEntry> c) | 
 |         { | 
 |             foreach (ZipEntry e in  c) | 
 |             { | 
 |                 e.NotifySaveComplete(); | 
 |             } | 
 |         } | 
 |  | 
 |  | 
 |         private void RemoveTempFile() | 
 |         { | 
 |             try | 
 |             { | 
 |                 if (File.Exists(_temporaryFileName)) | 
 |                 { | 
 |                     File.Delete(_temporaryFileName); | 
 |                 } | 
 |             } | 
 |             catch (IOException ex1) | 
 |             { | 
 |                 if (Verbose) | 
 |                     StatusMessageTextWriter.WriteLine("ZipFile::Save: could not delete temp file: {0}.", ex1.Message); | 
 |             } | 
 |         } | 
 |  | 
 |  | 
 |         private void CleanupAfterSaveOperation() | 
 |         { | 
 |             if (_name != null) | 
 |             { | 
 |                 // close the stream if there is a file behind it. | 
 |                 if (_writestream != null) | 
 |                 { | 
 |                     try | 
 |                     { | 
 |                         // workitem 7704 | 
 | #if NETCF | 
 |                         _writestream.Close(); | 
 | #else | 
 |                         _writestream.Dispose(); | 
 | #endif | 
 |                     } | 
 |                     catch (System.IO.IOException) { } | 
 |                 } | 
 |                 _writestream = null; | 
 |  | 
 |                 if (_temporaryFileName != null) | 
 |                 { | 
 |                     RemoveTempFile(); | 
 |                     _temporaryFileName = null; | 
 |                 } | 
 |             } | 
 |         } | 
 |  | 
 |  | 
 |         /// <summary> | 
 |         /// Save the file to a new zipfile, with the given name. | 
 |         /// </summary> | 
 |         /// | 
 |         /// <remarks> | 
 |         /// <para> | 
 |         /// This method allows the application to explicitly specify the name of the zip | 
 |         /// file when saving. Use this when creating a new zip file, or when | 
 |         /// updating a zip archive. | 
 |         /// </para> | 
 |         /// | 
 |         /// <para> | 
 |         /// An application can also save a zip archive in several places by calling this | 
 |         /// method multiple times in succession, with different filenames. | 
 |         /// </para> | 
 |         /// | 
 |         /// <para> | 
 |         /// The <c>ZipFile</c> instance is written to storage, typically a zip file in a | 
 |         /// filesystem, only when the caller calls <c>Save</c>.  The Save operation writes | 
 |         /// the zip content to a temporary file, and then renames the temporary file | 
 |         /// to the desired name. If necessary, this method will delete a pre-existing file | 
 |         /// before the rename. | 
 |         /// </para> | 
 |         /// | 
 |         /// </remarks> | 
 |         /// | 
 |         /// <exception cref="System.ArgumentException"> | 
 |         /// Thrown if you specify a directory for the filename. | 
 |         /// </exception> | 
 |         /// | 
 |         /// <param name="fileName"> | 
 |         /// The name of the zip archive to save to. Existing files will | 
 |         /// be overwritten with great prejudice. | 
 |         /// </param> | 
 |         /// | 
 |         /// <example> | 
 |         /// This example shows how to create and Save a zip file. | 
 |         /// <code> | 
 |         /// using (ZipFile zip = new ZipFile()) | 
 |         /// { | 
 |         ///   zip.AddDirectory(@"c:\reports\January"); | 
 |         ///   zip.Save("January.zip"); | 
 |         /// } | 
 |         /// </code> | 
 |         /// | 
 |         /// <code lang="VB"> | 
 |         /// Using zip As New ZipFile() | 
 |         ///   zip.AddDirectory("c:\reports\January") | 
 |         ///   zip.Save("January.zip") | 
 |         /// End Using | 
 |         /// </code> | 
 |         /// | 
 |         /// </example> | 
 |         /// | 
 |         /// <example> | 
 |         /// This example shows how to update a zip file. | 
 |         /// <code> | 
 |         /// using (ZipFile zip = ZipFile.Read("ExistingArchive.zip")) | 
 |         /// { | 
 |         ///   zip.AddFile("NewData.csv"); | 
 |         ///   zip.Save("UpdatedArchive.zip"); | 
 |         /// } | 
 |         /// </code> | 
 |         /// | 
 |         /// <code lang="VB"> | 
 |         /// Using zip As ZipFile = ZipFile.Read("ExistingArchive.zip") | 
 |         ///   zip.AddFile("NewData.csv") | 
 |         ///   zip.Save("UpdatedArchive.zip") | 
 |         /// End Using | 
 |         /// </code> | 
 |         /// | 
 |         /// </example> | 
 |         public void Save(String fileName) | 
 |         { | 
 |             // Check for the case where we are re-saving a zip archive | 
 |             // that was originally instantiated with a stream.  In that case, | 
 |             // the _name will be null. If so, we set _writestream to null, | 
 |             // which insures that we'll cons up a new WriteStream (with a filesystem | 
 |             // file backing it) in the Save() method. | 
 |             if (_name == null) | 
 |                 _writestream = null; | 
 |  | 
 |             else _readName = _name; // workitem 13915 | 
 |  | 
 |             _name = fileName; | 
 |             if (Directory.Exists(_name)) | 
 |                 throw new ZipException("Bad Directory", new System.ArgumentException("That name specifies an existing directory. Please specify a filename.", "fileName")); | 
 |             _contentsChanged = true; | 
 |             _fileAlreadyExists = File.Exists(_name); | 
 |             Save(); | 
 |         } | 
 |  | 
 |  | 
 |         /// <summary> | 
 |         ///   Save the zip archive to the specified stream. | 
 |         /// </summary> | 
 |         /// | 
 |         /// <remarks> | 
 |         /// <para> | 
 |         ///   The <c>ZipFile</c> instance is written to storage - typically a zip file | 
 |         ///   in a filesystem, but using this overload, the storage can be anything | 
 |         ///   accessible via a writable stream - only when the caller calls <c>Save</c>. | 
 |         /// </para> | 
 |         /// | 
 |         /// <para> | 
 |         ///   Use this method to save the zip content to a stream directly.  A common | 
 |         ///   scenario is an ASP.NET application that dynamically generates a zip file | 
 |         ///   and allows the browser to download it. The application can call | 
 |         ///   <c>Save(Response.OutputStream)</c> to write a zipfile directly to the | 
 |         ///   output stream, without creating a zip file on the disk on the ASP.NET | 
 |         ///   server. | 
 |         /// </para> | 
 |         /// | 
 |         /// <para> | 
 |         ///   Be careful when saving a file to a non-seekable stream, including | 
 |         ///   <c>Response.OutputStream</c>. When DotNetZip writes to a non-seekable | 
 |         ///   stream, the zip archive is formatted in such a way that may not be | 
 |         ///   compatible with all zip tools on all platforms.  It's a perfectly legal | 
 |         ///   and compliant zip file, but some people have reported problems opening | 
 |         ///   files produced this way using the Mac OS archive utility. | 
 |         /// </para> | 
 |         /// | 
 |         /// </remarks> | 
 |         /// | 
 |         /// <example> | 
 |         /// | 
 |         ///   This example saves the zipfile content into a MemoryStream, and | 
 |         ///   then gets the array of bytes from that MemoryStream. | 
 |         /// | 
 |         /// <code lang="C#"> | 
 |         /// using (var zip = new Ionic.Zip.ZipFile()) | 
 |         /// { | 
 |         ///     zip.CompressionLevel= Ionic.Zlib.CompressionLevel.BestCompression; | 
 |         ///     zip.Password = "VerySecret."; | 
 |         ///     zip.Encryption = EncryptionAlgorithm.WinZipAes128; | 
 |         ///     zip.AddFile(sourceFileName); | 
 |         ///     MemoryStream output = new MemoryStream(); | 
 |         ///     zip.Save(output); | 
 |         /// | 
 |         ///     byte[] zipbytes = output.ToArray(); | 
 |         /// } | 
 |         /// </code> | 
 |         /// </example> | 
 |         /// | 
 |         /// <example> | 
 |         /// <para> | 
 |         ///   This example shows a pitfall you should avoid. DO NOT read | 
 |         ///   from a stream, then try to save to the same stream.  DO | 
 |         ///   NOT DO THIS: | 
 |         /// </para> | 
 |         /// | 
 |         /// <code lang="C#"> | 
 |         /// using (var fs = new FileSteeam(filename, FileMode.Open)) | 
 |         /// { | 
 |         ///   using (var zip = Ionic.Zip.ZipFile.Read(inputStream)) | 
 |         ///   { | 
 |         ///     zip.AddEntry("Name1.txt", "this is the content"); | 
 |         ///     zip.Save(inputStream);  // NO NO NO!! | 
 |         ///   } | 
 |         /// } | 
 |         /// </code> | 
 |         /// | 
 |         /// <para> | 
 |         ///   Better like this: | 
 |         /// </para> | 
 |         /// | 
 |         /// <code lang="C#"> | 
 |         /// using (var zip = Ionic.Zip.ZipFile.Read(filename)) | 
 |         /// { | 
 |         ///     zip.AddEntry("Name1.txt", "this is the content"); | 
 |         ///     zip.Save();  // YES! | 
 |         /// } | 
 |         /// </code> | 
 |         /// | 
 |         /// </example> | 
 |         /// | 
 |         /// <param name="outputStream"> | 
 |         ///   The <c>System.IO.Stream</c> to write to. It must be | 
 |         ///   writable. If you created the ZipFile instanct by calling | 
 |         ///   ZipFile.Read(), this stream must not be the same stream | 
 |         ///   you passed to ZipFile.Read(). | 
 |         /// </param> | 
 |         public void Save(Stream outputStream) | 
 |         { | 
 |             if (outputStream == null) | 
 |                 throw new ArgumentNullException("outputStream"); | 
 |             if (!outputStream.CanWrite) | 
 |                 throw new ArgumentException("Must be a writable stream.", "outputStream"); | 
 |  | 
 |             // if we had a filename to save to, we are now obliterating it. | 
 |             _name = null; | 
 |  | 
 |             _writestream = new CountingStream(outputStream); | 
 |  | 
 |             _contentsChanged = true; | 
 |             _fileAlreadyExists = false; | 
 |             Save(); | 
 |         } | 
 |  | 
 |  | 
 |     } | 
 |  | 
 |  | 
 |  | 
 |     internal static class ZipOutput | 
 |     { | 
 |         public static bool WriteCentralDirectoryStructure(Stream s, | 
 |                                                           ICollection<ZipEntry> entries, | 
 |                                                           uint numSegments, | 
 |                                                           Zip64Option zip64, | 
 |                                                           String comment, | 
 |                                                           ZipContainer container) | 
 |         { | 
 |             var zss = s as ZipSegmentedStream; | 
 |             if (zss != null) | 
 |                 zss.ContiguousWrite = true; | 
 |  | 
 |             // write to a memory stream in order to keep the | 
 |             // CDR contiguous | 
 |             Int64 aLength = 0; | 
 |             using (var ms = new MemoryStream()) | 
 |             { | 
 |                 foreach (ZipEntry e in entries) | 
 |                 { | 
 |                     if (e.IncludedInMostRecentSave) | 
 |                     { | 
 |                         // this writes a ZipDirEntry corresponding to the ZipEntry | 
 |                         e.WriteCentralDirectoryEntry(ms); | 
 |                     } | 
 |                 } | 
 |                 var a = ms.ToArray(); | 
 |                 s.Write(a, 0, a.Length); | 
 |                 aLength = a.Length; | 
 |             } | 
 |  | 
 |  | 
 |             // We need to keep track of the start and | 
 |             // Finish of the Central Directory Structure. | 
 |  | 
 |             // Cannot always use WriteStream.Length or Position; some streams do | 
 |             // not support these. (eg, ASP.NET Response.OutputStream) In those | 
 |             // cases we have a CountingStream. | 
 |  | 
 |             // Also, we cannot just set Start as s.Position bfore the write, and Finish | 
 |             // as s.Position after the write.  In a split zip, the write may actually | 
 |             // flip to the next segment.  In that case, Start will be zero.  But we | 
 |             // don't know that til after we know the size of the thing to write.  So the | 
 |             // answer is to compute the directory, then ask the ZipSegmentedStream which | 
 |             // segment that directory would fall in, it it were written.  Then, include | 
 |             // that data into the directory, and finally, write the directory to the | 
 |             // output stream. | 
 |  | 
 |             var output = s as CountingStream; | 
 |             long Finish = (output != null) ? output.ComputedPosition : s.Position;  // BytesWritten | 
 |             long Start = Finish - aLength; | 
 |  | 
 |             // need to know which segment the EOCD record starts in | 
 |             UInt32 startSegment = (zss != null) | 
 |                 ? zss.CurrentSegment | 
 |                 : 0; | 
 |  | 
 |             Int64 SizeOfCentralDirectory = Finish - Start; | 
 |  | 
 |             int countOfEntries = CountEntries(entries); | 
 |  | 
 |             bool needZip64CentralDirectory = | 
 |                 zip64 == Zip64Option.Always || | 
 |                 countOfEntries >= 0xFFFF || | 
 |                 SizeOfCentralDirectory > 0xFFFFFFFF || | 
 |                 Start > 0xFFFFFFFF; | 
 |  | 
 |             byte[] a2 = null; | 
 |  | 
 |             // emit ZIP64 extensions as required | 
 |             if (needZip64CentralDirectory) | 
 |             { | 
 |                 if (zip64 == Zip64Option.Never) | 
 |                 { | 
 | #if NETCF | 
 |                     throw new ZipException("The archive requires a ZIP64 Central Directory. Consider enabling ZIP64 extensions."); | 
 | #else | 
 |                     System.Diagnostics.StackFrame sf = new System.Diagnostics.StackFrame(1); | 
 |                     if (sf.GetMethod().DeclaringType == typeof(ZipFile)) | 
 |                         throw new ZipException("The archive requires a ZIP64 Central Directory. Consider setting the ZipFile.UseZip64WhenSaving property."); | 
 |                     else | 
 |                         throw new ZipException("The archive requires a ZIP64 Central Directory. Consider setting the ZipOutputStream.EnableZip64 property."); | 
 | #endif | 
 |  | 
 |                 } | 
 |  | 
 |                 var a = GenZip64EndOfCentralDirectory(Start, Finish, countOfEntries, numSegments); | 
 |                 a2 = GenCentralDirectoryFooter(Start, Finish, zip64, countOfEntries, comment, container); | 
 |                 if (startSegment != 0) | 
 |                 { | 
 |                     UInt32 thisSegment = zss.ComputeSegment(a.Length + a2.Length); | 
 |                     int i = 16; | 
 |                     // number of this disk | 
 |                     Array.Copy(BitConverter.GetBytes(thisSegment), 0, a, i, 4); | 
 |                     i += 4; | 
 |                     // number of the disk with the start of the central directory | 
 |                     //Array.Copy(BitConverter.GetBytes(startSegment), 0, a, i, 4); | 
 |                     Array.Copy(BitConverter.GetBytes(thisSegment), 0, a, i, 4); | 
 |  | 
 |                     i = 60; | 
 |                     // offset 60 | 
 |                     // number of the disk with the start of the zip64 eocd | 
 |                     Array.Copy(BitConverter.GetBytes(thisSegment), 0, a, i, 4); | 
 |                     i += 4; | 
 |                     i += 8; | 
 |  | 
 |                     // offset 72 | 
 |                     // total number of disks | 
 |                     Array.Copy(BitConverter.GetBytes(thisSegment), 0, a, i, 4); | 
 |                 } | 
 |                 s.Write(a, 0, a.Length); | 
 |             } | 
 |             else | 
 |                 a2 = GenCentralDirectoryFooter(Start, Finish, zip64, countOfEntries, comment, container); | 
 |  | 
 |  | 
 |             // now, the regular footer | 
 |             if (startSegment != 0) | 
 |             { | 
 |                 // The assumption is the central directory is never split across | 
 |                 // segment boundaries. | 
 |  | 
 |                 UInt16 thisSegment = (UInt16) zss.ComputeSegment(a2.Length); | 
 |                 int i = 4; | 
 |                 // number of this disk | 
 |                 Array.Copy(BitConverter.GetBytes(thisSegment), 0, a2, i, 2); | 
 |                 i += 2; | 
 |                 // number of the disk with the start of the central directory | 
 |                 //Array.Copy(BitConverter.GetBytes((UInt16)startSegment), 0, a2, i, 2); | 
 |                 Array.Copy(BitConverter.GetBytes(thisSegment), 0, a2, i, 2); | 
 |                 i += 2; | 
 |             } | 
 |  | 
 |             s.Write(a2, 0, a2.Length); | 
 |  | 
 |             // reset the contiguous write property if necessary | 
 |             if (zss != null) | 
 |                 zss.ContiguousWrite = false; | 
 |  | 
 |             return needZip64CentralDirectory; | 
 |         } | 
 |  | 
 |  | 
 |         private static System.Text.Encoding GetEncoding(ZipContainer container, string t) | 
 |         { | 
 |             switch (container.AlternateEncodingUsage) | 
 |             { | 
 |                 case ZipOption.Always: | 
 |                     return container.AlternateEncoding; | 
 |                 case ZipOption.Never: | 
 |                     return container.DefaultEncoding; | 
 |             } | 
 |  | 
 |             // AsNecessary is in force | 
 |             var e = container.DefaultEncoding; | 
 |             if (t == null) return e; | 
 |  | 
 |             var bytes = e.GetBytes(t); | 
 |             var t2 = e.GetString(bytes,0,bytes.Length); | 
 |             if (t2.Equals(t)) return e; | 
 |             return container.AlternateEncoding; | 
 |         } | 
 |  | 
 |  | 
 |  | 
 |         private static byte[] GenCentralDirectoryFooter(long StartOfCentralDirectory, | 
 |                                                         long EndOfCentralDirectory, | 
 |                                                         Zip64Option zip64, | 
 |                                                         int entryCount, | 
 |                                                         string comment, | 
 |                                                         ZipContainer container) | 
 |         { | 
 |             System.Text.Encoding encoding = GetEncoding(container, comment); | 
 |             int j = 0; | 
 |             int bufferLength = 22; | 
 |             byte[] block = null; | 
 |             Int16 commentLength = 0; | 
 |             if ((comment != null) && (comment.Length != 0)) | 
 |             { | 
 |                 block = encoding.GetBytes(comment); | 
 |                 commentLength = (Int16)block.Length; | 
 |             } | 
 |             bufferLength += commentLength; | 
 |             byte[] bytes = new byte[bufferLength]; | 
 |  | 
 |             int i = 0; | 
 |             // signature | 
 |             byte[] sig = BitConverter.GetBytes(ZipConstants.EndOfCentralDirectorySignature); | 
 |             Array.Copy(sig, 0, bytes, i, 4); | 
 |             i+=4; | 
 |  | 
 |             // number of this disk | 
 |             // (this number may change later) | 
 |             bytes[i++] = 0; | 
 |             bytes[i++] = 0; | 
 |  | 
 |             // number of the disk with the start of the central directory | 
 |             // (this number may change later) | 
 |             bytes[i++] = 0; | 
 |             bytes[i++] = 0; | 
 |  | 
 |             // handle ZIP64 extensions for the end-of-central-directory | 
 |             if (entryCount >= 0xFFFF || zip64 == Zip64Option.Always) | 
 |             { | 
 |                 // the ZIP64 version. | 
 |                 for (j = 0; j < 4; j++) | 
 |                     bytes[i++] = 0xFF; | 
 |             } | 
 |             else | 
 |             { | 
 |                 // the standard version. | 
 |                 // total number of entries in the central dir on this disk | 
 |                 bytes[i++] = (byte)(entryCount & 0x00FF); | 
 |                 bytes[i++] = (byte)((entryCount & 0xFF00) >> 8); | 
 |  | 
 |                 // total number of entries in the central directory | 
 |                 bytes[i++] = (byte)(entryCount & 0x00FF); | 
 |                 bytes[i++] = (byte)((entryCount & 0xFF00) >> 8); | 
 |             } | 
 |  | 
 |             // size of the central directory | 
 |             Int64 SizeOfCentralDirectory = EndOfCentralDirectory - StartOfCentralDirectory; | 
 |  | 
 |             if (SizeOfCentralDirectory >= 0xFFFFFFFF || StartOfCentralDirectory >= 0xFFFFFFFF) | 
 |             { | 
 |                 // The actual data is in the ZIP64 central directory structure | 
 |                 for (j = 0; j < 8; j++) | 
 |                     bytes[i++] = 0xFF; | 
 |             } | 
 |             else | 
 |             { | 
 |                 // size of the central directory (we just get the low 4 bytes) | 
 |                 bytes[i++] = (byte)(SizeOfCentralDirectory & 0x000000FF); | 
 |                 bytes[i++] = (byte)((SizeOfCentralDirectory & 0x0000FF00) >> 8); | 
 |                 bytes[i++] = (byte)((SizeOfCentralDirectory & 0x00FF0000) >> 16); | 
 |                 bytes[i++] = (byte)((SizeOfCentralDirectory & 0xFF000000) >> 24); | 
 |  | 
 |                 // offset of the start of the central directory (we just get the low 4 bytes) | 
 |                 bytes[i++] = (byte)(StartOfCentralDirectory & 0x000000FF); | 
 |                 bytes[i++] = (byte)((StartOfCentralDirectory & 0x0000FF00) >> 8); | 
 |                 bytes[i++] = (byte)((StartOfCentralDirectory & 0x00FF0000) >> 16); | 
 |                 bytes[i++] = (byte)((StartOfCentralDirectory & 0xFF000000) >> 24); | 
 |             } | 
 |  | 
 |  | 
 |             // zip archive comment | 
 |             if ((comment == null) || (comment.Length == 0)) | 
 |             { | 
 |                 // no comment! | 
 |                 bytes[i++] = (byte)0; | 
 |                 bytes[i++] = (byte)0; | 
 |             } | 
 |             else | 
 |             { | 
 |                 // the size of our buffer defines the max length of the comment we can write | 
 |                 if (commentLength + i + 2 > bytes.Length) commentLength = (Int16)(bytes.Length - i - 2); | 
 |                 bytes[i++] = (byte)(commentLength & 0x00FF); | 
 |                 bytes[i++] = (byte)((commentLength & 0xFF00) >> 8); | 
 |  | 
 |                 if (commentLength != 0) | 
 |                 { | 
 |                     // now actually write the comment itself into the byte buffer | 
 |                     for (j = 0; (j < commentLength) && (i + j < bytes.Length); j++) | 
 |                     { | 
 |                         bytes[i + j] = block[j]; | 
 |                     } | 
 |                     i += j; | 
 |                 } | 
 |             } | 
 |  | 
 |             //   s.Write(bytes, 0, i); | 
 |             return bytes; | 
 |         } | 
 |  | 
 |  | 
 |  | 
 |         private static byte[] GenZip64EndOfCentralDirectory(long StartOfCentralDirectory, | 
 |                                                             long EndOfCentralDirectory, | 
 |                                                             int entryCount, | 
 |                                                             uint numSegments) | 
 |         { | 
 |             const int bufferLength = 12 + 44 + 20; | 
 |  | 
 |             byte[] bytes = new byte[bufferLength]; | 
 |  | 
 |             int i = 0; | 
 |             // signature | 
 |             byte[] sig = BitConverter.GetBytes(ZipConstants.Zip64EndOfCentralDirectoryRecordSignature); | 
 |             Array.Copy(sig, 0, bytes, i, 4); | 
 |             i+=4; | 
 |  | 
 |             // There is a possibility to include "Extensible" data in the zip64 | 
 |             // end-of-central-dir record.  I cannot figure out what it might be used to | 
 |             // store, so the size of this record is always fixed.  Maybe it is used for | 
 |             // strong encryption data?  That is for another day. | 
 |             long DataSize = 44; | 
 |             Array.Copy(BitConverter.GetBytes(DataSize), 0, bytes, i, 8); | 
 |             i += 8; | 
 |  | 
 |             // offset 12 | 
 |             // VersionMadeBy = 45; | 
 |             bytes[i++] = 45; | 
 |             bytes[i++] = 0x00; | 
 |  | 
 |             // VersionNeededToExtract = 45; | 
 |             bytes[i++] = 45; | 
 |             bytes[i++] = 0x00; | 
 |  | 
 |             // offset 16 | 
 |             // number of the disk, and the disk with the start of the central dir. | 
 |             // (this may change later) | 
 |             for (int j = 0; j < 8; j++) | 
 |                 bytes[i++] = 0x00; | 
 |  | 
 |             // offset 24 | 
 |             long numberOfEntries = entryCount; | 
 |             Array.Copy(BitConverter.GetBytes(numberOfEntries), 0, bytes, i, 8); | 
 |             i += 8; | 
 |             Array.Copy(BitConverter.GetBytes(numberOfEntries), 0, bytes, i, 8); | 
 |             i += 8; | 
 |  | 
 |             // offset 40 | 
 |             Int64 SizeofCentraldirectory = EndOfCentralDirectory - StartOfCentralDirectory; | 
 |             Array.Copy(BitConverter.GetBytes(SizeofCentraldirectory), 0, bytes, i, 8); | 
 |             i += 8; | 
 |             Array.Copy(BitConverter.GetBytes(StartOfCentralDirectory), 0, bytes, i, 8); | 
 |             i += 8; | 
 |  | 
 |             // offset 56 | 
 |             // now, the locator | 
 |             // signature | 
 |             sig = BitConverter.GetBytes(ZipConstants.Zip64EndOfCentralDirectoryLocatorSignature); | 
 |             Array.Copy(sig, 0, bytes, i, 4); | 
 |             i+=4; | 
 |  | 
 |             // offset 60 | 
 |             // number of the disk with the start of the zip64 eocd | 
 |             // (this will change later)  (it will?) | 
 |             uint x2 = (numSegments==0)?0:(uint)(numSegments-1); | 
 |             Array.Copy(BitConverter.GetBytes(x2), 0, bytes, i, 4); | 
 |             i+=4; | 
 |  | 
 |             // offset 64 | 
 |             // relative offset of the zip64 eocd | 
 |             Array.Copy(BitConverter.GetBytes(EndOfCentralDirectory), 0, bytes, i, 8); | 
 |             i += 8; | 
 |  | 
 |             // offset 72 | 
 |             // total number of disks | 
 |             // (this will change later) | 
 |             Array.Copy(BitConverter.GetBytes(numSegments), 0, bytes, i, 4); | 
 |             i+=4; | 
 |  | 
 |             return bytes; | 
 |         } | 
 |  | 
 |  | 
 |  | 
 |         private static int CountEntries(ICollection<ZipEntry> _entries) | 
 |         { | 
 |             // Cannot just emit _entries.Count, because some of the entries | 
 |             // may have been skipped. | 
 |             int count = 0; | 
 |             foreach (var entry in _entries) | 
 |                 if (entry.IncludedInMostRecentSave) count++; | 
 |             return count; | 
 |         } | 
 |  | 
 |  | 
 |  | 
 |  | 
 |     } | 
 | } |