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