Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2913_MatlabScriptProblemInstanceProvider/HeuristicLab.ExtLibs/HeuristicLab.EPPlus/4.0.3/EPPlus-4.0.3/Packaging/DotNetZip/ZipFile.Save.cs @ 17800

Last change on this file since 17800 was 12074, checked in by sraggl, 10 years ago

#2341: Added EPPlus-4.0.3 to ExtLibs

File size: 37.4 KB
Line 
1// ZipFile.Save.cs
2// ------------------------------------------------------------------
3//
4// Copyright (c) 2009 Dino Chiesa.
5// All rights reserved.
6//
7// This code module is part of DotNetZip, a zipfile class library.
8//
9// ------------------------------------------------------------------
10//
11// This code is licensed under the Microsoft Public License.
12// See the file License.txt for the license details.
13// More info on: http://dotnetzip.codeplex.com
14//
15// ------------------------------------------------------------------
16//
17// last saved (in emacs):
18// Time-stamp: <2011-August-05 13:31:23>
19//
20// ------------------------------------------------------------------
21//
22// This module defines the methods for Save operations on zip files.
23//
24// ------------------------------------------------------------------
25//
26
27
28using System;
29using System.IO;
30using System.Collections.Generic;
31
32namespace OfficeOpenXml.Packaging.Ionic.Zip
33{
34
35    internal partial class ZipFile
36    {
37
38        /// <summary>
39        ///   Delete file with retry on UnauthorizedAccessException.
40        /// </summary>
41        ///
42        /// <remarks>
43        ///   <para>
44        ///     When calling File.Delete() on a file that has been "recently"
45        ///     created, the call sometimes fails with
46        ///     UnauthorizedAccessException. This method simply retries the Delete 3
47        ///     times with a sleep between tries.
48        ///   </para>
49        /// </remarks>
50        ///
51        /// <param name='filename'>the name of the file to be deleted</param>
52        private void DeleteFileWithRetry(string filename)
53        {
54            bool done = false;
55            int nRetries = 3;
56            for (int i=0; i < nRetries && !done; i++)
57            {
58                try
59                {
60                    File.Delete(filename);
61                    done = true;
62                }
63                catch (System.UnauthorizedAccessException)
64                {
65                    Console.WriteLine("************************************************** Retry delete.");
66                    System.Threading.Thread.Sleep(200+i*200);
67                }
68            }
69        }
70
71
72        /// <summary>
73        ///   Saves the Zip archive to a file, specified by the Name property of the
74        ///   <c>ZipFile</c>.
75        /// </summary>
76        ///
77        /// <remarks>
78        /// <para>
79        ///   The <c>ZipFile</c> instance is written to storage, typically a zip file
80        ///   in a filesystem, only when the caller calls <c>Save</c>.  In the typical
81        ///   case, the Save operation writes the zip content to a temporary file, and
82        ///   then renames the temporary file to the desired name. If necessary, this
83        ///   method will delete a pre-existing file before the rename.
84        /// </para>
85        ///
86        /// <para>
87        ///   The <see cref="ZipFile.Name"/> property is specified either explicitly,
88        ///   or implicitly using one of the parameterized ZipFile constructors.  For
89        ///   COM Automation clients, the <c>Name</c> property must be set explicitly,
90        ///   because COM Automation clients cannot call parameterized constructors.
91        /// </para>
92        ///
93        /// <para>
94        ///   When using a filesystem file for the Zip output, it is possible to call
95        ///   <c>Save</c> multiple times on the <c>ZipFile</c> instance. With each
96        ///   call the zip content is re-written to the same output file.
97        /// </para>
98        ///
99        /// <para>
100        ///   Data for entries that have been added to the <c>ZipFile</c> instance is
101        ///   written to the output when the <c>Save</c> method is called. This means
102        ///   that the input streams for those entries must be available at the time
103        ///   the application calls <c>Save</c>.  If, for example, the application
104        ///   adds entries with <c>AddEntry</c> using a dynamically-allocated
105        ///   <c>MemoryStream</c>, the memory stream must not have been disposed
106        ///   before the call to <c>Save</c>. See the <see
107        ///   cref="ZipEntry.InputStream"/> property for more discussion of the
108        ///   availability requirements of the input stream for an entry, and an
109        ///   approach for providing just-in-time stream lifecycle management.
110        /// </para>
111        ///
112        /// </remarks>
113        ///
114        /// <seealso cref="Ionic.Zip.ZipFile.AddEntry(String, System.IO.Stream)"/>
115        ///
116        /// <exception cref="Ionic.Zip.BadStateException">
117        ///   Thrown if you haven't specified a location or stream for saving the zip,
118        ///   either in the constructor or by setting the Name property, or if you try
119        ///   to save a regular zip archive to a filename with a .exe extension.
120        /// </exception>
121        ///
122        /// <exception cref="System.OverflowException">
123        ///   Thrown if <see cref="MaxOutputSegmentSize"/> is non-zero, and the number
124        ///   of segments that would be generated for the spanned zip file during the
125        ///   save operation exceeds 99.  If this happens, you need to increase the
126        ///   segment size.
127        /// </exception>
128        ///
129        public void Save()
130        {
131            try
132            {
133                bool thisSaveUsedZip64 = false;
134                _saveOperationCanceled = false;
135                _numberOfSegmentsForMostRecentSave = 0;
136                OnSaveStarted();
137
138                if (WriteStream == null)
139                    throw new BadStateException("You haven't specified where to save the zip.");
140
141                if (_name != null && _name.EndsWith(".exe") && !_SavingSfx)
142                    throw new BadStateException("You specified an EXE for a plain zip file.");
143
144                // check if modified, before saving.
145                if (!_contentsChanged)
146                {
147                    OnSaveCompleted();
148                    if (Verbose) StatusMessageTextWriter.WriteLine("No save is necessary....");
149                    return;
150                }
151
152                Reset(true);
153
154                if (Verbose) StatusMessageTextWriter.WriteLine("saving....");
155
156                // validate the number of entries
157                if (_entries.Count >= 0xFFFF && _zip64 == Zip64Option.Never)
158                    throw new ZipException("The number of entries is 65535 or greater. Consider setting the UseZip64WhenSaving property on the ZipFile instance.");
159
160
161                // write an entry in the zip for each file
162                int n = 0;
163                // workitem 9831
164                ICollection<ZipEntry> c = (SortEntriesBeforeSaving) ? EntriesSorted : Entries;
165                foreach (ZipEntry e in c) // _entries.Values
166                {
167                    OnSaveEntry(n, e, true);
168                    e.Write(WriteStream);
169                    if (_saveOperationCanceled)
170                        break;
171
172                    n++;
173                    OnSaveEntry(n, e, false);
174                    if (_saveOperationCanceled)
175                        break;
176
177                    // Some entries can be skipped during the save.
178                    if (e.IncludedInMostRecentSave)
179                        thisSaveUsedZip64 |= e.OutputUsedZip64.Value;
180                }
181
182
183
184                if (_saveOperationCanceled)
185                    return;
186
187                var zss = WriteStream as ZipSegmentedStream;
188
189                _numberOfSegmentsForMostRecentSave = (zss!=null)
190                    ? zss.CurrentSegment
191                    : 1;
192
193                bool directoryNeededZip64 =
194                    ZipOutput.WriteCentralDirectoryStructure
195                    (WriteStream,
196                     c,
197                     _numberOfSegmentsForMostRecentSave,
198                     _zip64,
199                     Comment,
200                     new ZipContainer(this));
201
202                OnSaveEvent(ZipProgressEventType.Saving_AfterSaveTempArchive);
203
204                _hasBeenSaved = true;
205                _contentsChanged = false;
206
207                thisSaveUsedZip64 |= directoryNeededZip64;
208                _OutputUsesZip64 = new Nullable<bool>(thisSaveUsedZip64);
209
210
211                // do the rename as necessary
212                if (_name != null &&
213                    (_temporaryFileName!=null || zss != null))
214                {
215                    // _temporaryFileName may remain null if we are writing to a stream.
216                    // only close the stream if there is a file behind it.
217#if NETCF
218                    WriteStream.Close();
219#else
220                    WriteStream.Dispose();
221#endif
222                    if (_saveOperationCanceled)
223                        return;
224
225                    if (_fileAlreadyExists && this._readstream != null)
226                    {
227                        // This means we opened and read a zip file.
228                        // If we are now saving to the same file, we need to close the
229                        // orig file, first.
230                        this._readstream.Close();
231                        this._readstream = null;
232                        // the archiveStream for each entry needs to be null
233                        foreach (var e in c)
234                        {
235                            var zss1 = e._archiveStream as ZipSegmentedStream;
236                            if (zss1 != null)
237#if NETCF
238                                zss1.Close();
239#else
240                                zss1.Dispose();
241#endif
242                            e._archiveStream = null;
243                        }
244                    }
245
246                    string tmpName = null;
247                    if (File.Exists(_name))
248                    {
249                        // the steps:
250                        //
251                        // 1. Delete tmpName
252                        // 2. move existing zip to tmpName
253                        // 3. rename (File.Move) working file to name of existing zip
254                        // 4. delete tmpName
255                        //
256                        // This series of steps avoids the exception,
257                        // System.IO.IOException:
258                        //   "Cannot create a file when that file already exists."
259                        //
260                        // Cannot just call File.Replace() here because
261                        // there is a possibility that the TEMP volume is different
262                        // that the volume for the final file (c:\ vs d:\).
263                        // So we need to do a Delete+Move pair.
264                        //
265                        // But, when doing the delete, Windows allows a process to
266                        // delete the file, even though it is held open by, say, a
267                        // virus scanner. It gets internally marked as "delete
268                        // pending". The file does not actually get removed from the
269                        // file system, it is still there after the File.Delete
270                        // call.
271                        //
272                        // Therefore, we need to move the existing zip, which may be
273                        // held open, to some other name. Then rename our working
274                        // file to the desired name, then delete (possibly delete
275                        // pending) the "other name".
276                        //
277                        // Ideally this would be transactional. It's possible that the
278                        // delete succeeds and the move fails. Lacking transactions, if
279                        // this kind of failure happens, we're hosed, and this logic will
280                        // throw on the next File.Move().
281                        //
282                        //File.Delete(_name);
283                        // workitem 10447
284#if NETCF || SILVERLIGHT
285                        tmpName = _name + "." + SharedUtilities.GenerateRandomStringImpl(8,0) + ".tmp";
286#else
287                        tmpName = _name + "." + Path.GetRandomFileName();
288#endif
289                        if (File.Exists(tmpName))
290                            DeleteFileWithRetry(tmpName);
291                        File.Move(_name, tmpName);
292                    }
293
294                    OnSaveEvent(ZipProgressEventType.Saving_BeforeRenameTempArchive);
295                    File.Move((zss != null) ? zss.CurrentTempName : _temporaryFileName,
296                              _name);
297
298                    OnSaveEvent(ZipProgressEventType.Saving_AfterRenameTempArchive);
299
300                    if (tmpName != null)
301                    {
302                        try
303                        {
304                            // not critical
305                            if (File.Exists(tmpName))
306                                File.Delete(tmpName);
307                        }
308                        catch
309                        {
310                            // don't care about exceptions here.
311                        }
312
313                    }
314                    _fileAlreadyExists = true;
315                }
316
317                NotifyEntriesSaveComplete(c);
318                OnSaveCompleted();
319                _JustSaved = true;
320            }
321
322            // workitem 5043
323            finally
324            {
325                CleanupAfterSaveOperation();
326            }
327
328            return;
329        }
330
331
332
333        private static void NotifyEntriesSaveComplete(ICollection<ZipEntry> c)
334        {
335            foreach (ZipEntry e in  c)
336            {
337                e.NotifySaveComplete();
338            }
339        }
340
341
342        private void RemoveTempFile()
343        {
344            try
345            {
346                if (File.Exists(_temporaryFileName))
347                {
348                    File.Delete(_temporaryFileName);
349                }
350            }
351            catch (IOException ex1)
352            {
353                if (Verbose)
354                    StatusMessageTextWriter.WriteLine("ZipFile::Save: could not delete temp file: {0}.", ex1.Message);
355            }
356        }
357
358
359        private void CleanupAfterSaveOperation()
360        {
361            if (_name != null)
362            {
363                // close the stream if there is a file behind it.
364                if (_writestream != null)
365                {
366                    try
367                    {
368                        // workitem 7704
369#if NETCF
370                        _writestream.Close();
371#else
372                        _writestream.Dispose();
373#endif
374                    }
375                    catch (System.IO.IOException) { }
376                }
377                _writestream = null;
378
379                if (_temporaryFileName != null)
380                {
381                    RemoveTempFile();
382                    _temporaryFileName = null;
383                }
384            }
385        }
386
387
388        /// <summary>
389        /// Save the file to a new zipfile, with the given name.
390        /// </summary>
391        ///
392        /// <remarks>
393        /// <para>
394        /// This method allows the application to explicitly specify the name of the zip
395        /// file when saving. Use this when creating a new zip file, or when
396        /// updating a zip archive.
397        /// </para>
398        ///
399        /// <para>
400        /// An application can also save a zip archive in several places by calling this
401        /// method multiple times in succession, with different filenames.
402        /// </para>
403        ///
404        /// <para>
405        /// The <c>ZipFile</c> instance is written to storage, typically a zip file in a
406        /// filesystem, only when the caller calls <c>Save</c>.  The Save operation writes
407        /// the zip content to a temporary file, and then renames the temporary file
408        /// to the desired name. If necessary, this method will delete a pre-existing file
409        /// before the rename.
410        /// </para>
411        ///
412        /// </remarks>
413        ///
414        /// <exception cref="System.ArgumentException">
415        /// Thrown if you specify a directory for the filename.
416        /// </exception>
417        ///
418        /// <param name="fileName">
419        /// The name of the zip archive to save to. Existing files will
420        /// be overwritten with great prejudice.
421        /// </param>
422        ///
423        /// <example>
424        /// This example shows how to create and Save a zip file.
425        /// <code>
426        /// using (ZipFile zip = new ZipFile())
427        /// {
428        ///   zip.AddDirectory(@"c:\reports\January");
429        ///   zip.Save("January.zip");
430        /// }
431        /// </code>
432        ///
433        /// <code lang="VB">
434        /// Using zip As New ZipFile()
435        ///   zip.AddDirectory("c:\reports\January")
436        ///   zip.Save("January.zip")
437        /// End Using
438        /// </code>
439        ///
440        /// </example>
441        ///
442        /// <example>
443        /// This example shows how to update a zip file.
444        /// <code>
445        /// using (ZipFile zip = ZipFile.Read("ExistingArchive.zip"))
446        /// {
447        ///   zip.AddFile("NewData.csv");
448        ///   zip.Save("UpdatedArchive.zip");
449        /// }
450        /// </code>
451        ///
452        /// <code lang="VB">
453        /// Using zip As ZipFile = ZipFile.Read("ExistingArchive.zip")
454        ///   zip.AddFile("NewData.csv")
455        ///   zip.Save("UpdatedArchive.zip")
456        /// End Using
457        /// </code>
458        ///
459        /// </example>
460        public void Save(String fileName)
461        {
462            // Check for the case where we are re-saving a zip archive
463            // that was originally instantiated with a stream.  In that case,
464            // the _name will be null. If so, we set _writestream to null,
465            // which insures that we'll cons up a new WriteStream (with a filesystem
466            // file backing it) in the Save() method.
467            if (_name == null)
468                _writestream = null;
469
470            else _readName = _name; // workitem 13915
471
472            _name = fileName;
473            if (Directory.Exists(_name))
474                throw new ZipException("Bad Directory", new System.ArgumentException("That name specifies an existing directory. Please specify a filename.", "fileName"));
475            _contentsChanged = true;
476            _fileAlreadyExists = File.Exists(_name);
477            Save();
478        }
479
480
481        /// <summary>
482        ///   Save the zip archive to the specified stream.
483        /// </summary>
484        ///
485        /// <remarks>
486        /// <para>
487        ///   The <c>ZipFile</c> instance is written to storage - typically a zip file
488        ///   in a filesystem, but using this overload, the storage can be anything
489        ///   accessible via a writable stream - only when the caller calls <c>Save</c>.
490        /// </para>
491        ///
492        /// <para>
493        ///   Use this method to save the zip content to a stream directly.  A common
494        ///   scenario is an ASP.NET application that dynamically generates a zip file
495        ///   and allows the browser to download it. The application can call
496        ///   <c>Save(Response.OutputStream)</c> to write a zipfile directly to the
497        ///   output stream, without creating a zip file on the disk on the ASP.NET
498        ///   server.
499        /// </para>
500        ///
501        /// <para>
502        ///   Be careful when saving a file to a non-seekable stream, including
503        ///   <c>Response.OutputStream</c>. When DotNetZip writes to a non-seekable
504        ///   stream, the zip archive is formatted in such a way that may not be
505        ///   compatible with all zip tools on all platforms.  It's a perfectly legal
506        ///   and compliant zip file, but some people have reported problems opening
507        ///   files produced this way using the Mac OS archive utility.
508        /// </para>
509        ///
510        /// </remarks>
511        ///
512        /// <example>
513        ///
514        ///   This example saves the zipfile content into a MemoryStream, and
515        ///   then gets the array of bytes from that MemoryStream.
516        ///
517        /// <code lang="C#">
518        /// using (var zip = new Ionic.Zip.ZipFile())
519        /// {
520        ///     zip.CompressionLevel= Ionic.Zlib.CompressionLevel.BestCompression;
521        ///     zip.Password = "VerySecret.";
522        ///     zip.Encryption = EncryptionAlgorithm.WinZipAes128;
523        ///     zip.AddFile(sourceFileName);
524        ///     MemoryStream output = new MemoryStream();
525        ///     zip.Save(output);
526        ///
527        ///     byte[] zipbytes = output.ToArray();
528        /// }
529        /// </code>
530        /// </example>
531        ///
532        /// <example>
533        /// <para>
534        ///   This example shows a pitfall you should avoid. DO NOT read
535        ///   from a stream, then try to save to the same stream.  DO
536        ///   NOT DO THIS:
537        /// </para>
538        ///
539        /// <code lang="C#">
540        /// using (var fs = new FileSteeam(filename, FileMode.Open))
541        /// {
542        ///   using (var zip = Ionic.Zip.ZipFile.Read(inputStream))
543        ///   {
544        ///     zip.AddEntry("Name1.txt", "this is the content");
545        ///     zip.Save(inputStream);  // NO NO NO!!
546        ///   }
547        /// }
548        /// </code>
549        ///
550        /// <para>
551        ///   Better like this:
552        /// </para>
553        ///
554        /// <code lang="C#">
555        /// using (var zip = Ionic.Zip.ZipFile.Read(filename))
556        /// {
557        ///     zip.AddEntry("Name1.txt", "this is the content");
558        ///     zip.Save();  // YES!
559        /// }
560        /// </code>
561        ///
562        /// </example>
563        ///
564        /// <param name="outputStream">
565        ///   The <c>System.IO.Stream</c> to write to. It must be
566        ///   writable. If you created the ZipFile instanct by calling
567        ///   ZipFile.Read(), this stream must not be the same stream
568        ///   you passed to ZipFile.Read().
569        /// </param>
570        public void Save(Stream outputStream)
571        {
572            if (outputStream == null)
573                throw new ArgumentNullException("outputStream");
574            if (!outputStream.CanWrite)
575                throw new ArgumentException("Must be a writable stream.", "outputStream");
576
577            // if we had a filename to save to, we are now obliterating it.
578            _name = null;
579
580            _writestream = new CountingStream(outputStream);
581
582            _contentsChanged = true;
583            _fileAlreadyExists = false;
584            Save();
585        }
586
587
588    }
589
590
591
592    internal static class ZipOutput
593    {
594        public static bool WriteCentralDirectoryStructure(Stream s,
595                                                          ICollection<ZipEntry> entries,
596                                                          uint numSegments,
597                                                          Zip64Option zip64,
598                                                          String comment,
599                                                          ZipContainer container)
600        {
601            var zss = s as ZipSegmentedStream;
602            if (zss != null)
603                zss.ContiguousWrite = true;
604
605            // write to a memory stream in order to keep the
606            // CDR contiguous
607            Int64 aLength = 0;
608            using (var ms = new MemoryStream())
609            {
610                foreach (ZipEntry e in entries)
611                {
612                    if (e.IncludedInMostRecentSave)
613                    {
614                        // this writes a ZipDirEntry corresponding to the ZipEntry
615                        e.WriteCentralDirectoryEntry(ms);
616                    }
617                }
618                var a = ms.ToArray();
619                s.Write(a, 0, a.Length);
620                aLength = a.Length;
621            }
622
623
624            // We need to keep track of the start and
625            // Finish of the Central Directory Structure.
626
627            // Cannot always use WriteStream.Length or Position; some streams do
628            // not support these. (eg, ASP.NET Response.OutputStream) In those
629            // cases we have a CountingStream.
630
631            // Also, we cannot just set Start as s.Position bfore the write, and Finish
632            // as s.Position after the write.  In a split zip, the write may actually
633            // flip to the next segment.  In that case, Start will be zero.  But we
634            // don't know that til after we know the size of the thing to write.  So the
635            // answer is to compute the directory, then ask the ZipSegmentedStream which
636            // segment that directory would fall in, it it were written.  Then, include
637            // that data into the directory, and finally, write the directory to the
638            // output stream.
639
640            var output = s as CountingStream;
641            long Finish = (output != null) ? output.ComputedPosition : s.Position;  // BytesWritten
642            long Start = Finish - aLength;
643
644            // need to know which segment the EOCD record starts in
645            UInt32 startSegment = (zss != null)
646                ? zss.CurrentSegment
647                : 0;
648
649            Int64 SizeOfCentralDirectory = Finish - Start;
650
651            int countOfEntries = CountEntries(entries);
652
653            bool needZip64CentralDirectory =
654                zip64 == Zip64Option.Always ||
655                countOfEntries >= 0xFFFF ||
656                SizeOfCentralDirectory > 0xFFFFFFFF ||
657                Start > 0xFFFFFFFF;
658
659            byte[] a2 = null;
660
661            // emit ZIP64 extensions as required
662            if (needZip64CentralDirectory)
663            {
664                if (zip64 == Zip64Option.Never)
665                {
666#if NETCF
667                    throw new ZipException("The archive requires a ZIP64 Central Directory. Consider enabling ZIP64 extensions.");
668#else
669                    System.Diagnostics.StackFrame sf = new System.Diagnostics.StackFrame(1);
670                    if (sf.GetMethod().DeclaringType == typeof(ZipFile))
671                        throw new ZipException("The archive requires a ZIP64 Central Directory. Consider setting the ZipFile.UseZip64WhenSaving property.");
672                    else
673                        throw new ZipException("The archive requires a ZIP64 Central Directory. Consider setting the ZipOutputStream.EnableZip64 property.");
674#endif
675
676                }
677
678                var a = GenZip64EndOfCentralDirectory(Start, Finish, countOfEntries, numSegments);
679                a2 = GenCentralDirectoryFooter(Start, Finish, zip64, countOfEntries, comment, container);
680                if (startSegment != 0)
681                {
682                    UInt32 thisSegment = zss.ComputeSegment(a.Length + a2.Length);
683                    int i = 16;
684                    // number of this disk
685                    Array.Copy(BitConverter.GetBytes(thisSegment), 0, a, i, 4);
686                    i += 4;
687                    // number of the disk with the start of the central directory
688                    //Array.Copy(BitConverter.GetBytes(startSegment), 0, a, i, 4);
689                    Array.Copy(BitConverter.GetBytes(thisSegment), 0, a, i, 4);
690
691                    i = 60;
692                    // offset 60
693                    // number of the disk with the start of the zip64 eocd
694                    Array.Copy(BitConverter.GetBytes(thisSegment), 0, a, i, 4);
695                    i += 4;
696                    i += 8;
697
698                    // offset 72
699                    // total number of disks
700                    Array.Copy(BitConverter.GetBytes(thisSegment), 0, a, i, 4);
701                }
702                s.Write(a, 0, a.Length);
703            }
704            else
705                a2 = GenCentralDirectoryFooter(Start, Finish, zip64, countOfEntries, comment, container);
706
707
708            // now, the regular footer
709            if (startSegment != 0)
710            {
711                // The assumption is the central directory is never split across
712                // segment boundaries.
713
714                UInt16 thisSegment = (UInt16) zss.ComputeSegment(a2.Length);
715                int i = 4;
716                // number of this disk
717                Array.Copy(BitConverter.GetBytes(thisSegment), 0, a2, i, 2);
718                i += 2;
719                // number of the disk with the start of the central directory
720                //Array.Copy(BitConverter.GetBytes((UInt16)startSegment), 0, a2, i, 2);
721                Array.Copy(BitConverter.GetBytes(thisSegment), 0, a2, i, 2);
722                i += 2;
723            }
724
725            s.Write(a2, 0, a2.Length);
726
727            // reset the contiguous write property if necessary
728            if (zss != null)
729                zss.ContiguousWrite = false;
730
731            return needZip64CentralDirectory;
732        }
733
734
735        private static System.Text.Encoding GetEncoding(ZipContainer container, string t)
736        {
737            switch (container.AlternateEncodingUsage)
738            {
739                case ZipOption.Always:
740                    return container.AlternateEncoding;
741                case ZipOption.Never:
742                    return container.DefaultEncoding;
743            }
744
745            // AsNecessary is in force
746            var e = container.DefaultEncoding;
747            if (t == null) return e;
748
749            var bytes = e.GetBytes(t);
750            var t2 = e.GetString(bytes,0,bytes.Length);
751            if (t2.Equals(t)) return e;
752            return container.AlternateEncoding;
753        }
754
755
756
757        private static byte[] GenCentralDirectoryFooter(long StartOfCentralDirectory,
758                                                        long EndOfCentralDirectory,
759                                                        Zip64Option zip64,
760                                                        int entryCount,
761                                                        string comment,
762                                                        ZipContainer container)
763        {
764            System.Text.Encoding encoding = GetEncoding(container, comment);
765            int j = 0;
766            int bufferLength = 22;
767            byte[] block = null;
768            Int16 commentLength = 0;
769            if ((comment != null) && (comment.Length != 0))
770            {
771                block = encoding.GetBytes(comment);
772                commentLength = (Int16)block.Length;
773            }
774            bufferLength += commentLength;
775            byte[] bytes = new byte[bufferLength];
776
777            int i = 0;
778            // signature
779            byte[] sig = BitConverter.GetBytes(ZipConstants.EndOfCentralDirectorySignature);
780            Array.Copy(sig, 0, bytes, i, 4);
781            i+=4;
782
783            // number of this disk
784            // (this number may change later)
785            bytes[i++] = 0;
786            bytes[i++] = 0;
787
788            // number of the disk with the start of the central directory
789            // (this number may change later)
790            bytes[i++] = 0;
791            bytes[i++] = 0;
792
793            // handle ZIP64 extensions for the end-of-central-directory
794            if (entryCount >= 0xFFFF || zip64 == Zip64Option.Always)
795            {
796                // the ZIP64 version.
797                for (j = 0; j < 4; j++)
798                    bytes[i++] = 0xFF;
799            }
800            else
801            {
802                // the standard version.
803                // total number of entries in the central dir on this disk
804                bytes[i++] = (byte)(entryCount & 0x00FF);
805                bytes[i++] = (byte)((entryCount & 0xFF00) >> 8);
806
807                // total number of entries in the central directory
808                bytes[i++] = (byte)(entryCount & 0x00FF);
809                bytes[i++] = (byte)((entryCount & 0xFF00) >> 8);
810            }
811
812            // size of the central directory
813            Int64 SizeOfCentralDirectory = EndOfCentralDirectory - StartOfCentralDirectory;
814
815            if (SizeOfCentralDirectory >= 0xFFFFFFFF || StartOfCentralDirectory >= 0xFFFFFFFF)
816            {
817                // The actual data is in the ZIP64 central directory structure
818                for (j = 0; j < 8; j++)
819                    bytes[i++] = 0xFF;
820            }
821            else
822            {
823                // size of the central directory (we just get the low 4 bytes)
824                bytes[i++] = (byte)(SizeOfCentralDirectory & 0x000000FF);
825                bytes[i++] = (byte)((SizeOfCentralDirectory & 0x0000FF00) >> 8);
826                bytes[i++] = (byte)((SizeOfCentralDirectory & 0x00FF0000) >> 16);
827                bytes[i++] = (byte)((SizeOfCentralDirectory & 0xFF000000) >> 24);
828
829                // offset of the start of the central directory (we just get the low 4 bytes)
830                bytes[i++] = (byte)(StartOfCentralDirectory & 0x000000FF);
831                bytes[i++] = (byte)((StartOfCentralDirectory & 0x0000FF00) >> 8);
832                bytes[i++] = (byte)((StartOfCentralDirectory & 0x00FF0000) >> 16);
833                bytes[i++] = (byte)((StartOfCentralDirectory & 0xFF000000) >> 24);
834            }
835
836
837            // zip archive comment
838            if ((comment == null) || (comment.Length == 0))
839            {
840                // no comment!
841                bytes[i++] = (byte)0;
842                bytes[i++] = (byte)0;
843            }
844            else
845            {
846                // the size of our buffer defines the max length of the comment we can write
847                if (commentLength + i + 2 > bytes.Length) commentLength = (Int16)(bytes.Length - i - 2);
848                bytes[i++] = (byte)(commentLength & 0x00FF);
849                bytes[i++] = (byte)((commentLength & 0xFF00) >> 8);
850
851                if (commentLength != 0)
852                {
853                    // now actually write the comment itself into the byte buffer
854                    for (j = 0; (j < commentLength) && (i + j < bytes.Length); j++)
855                    {
856                        bytes[i + j] = block[j];
857                    }
858                    i += j;
859                }
860            }
861
862            //   s.Write(bytes, 0, i);
863            return bytes;
864        }
865
866
867
868        private static byte[] GenZip64EndOfCentralDirectory(long StartOfCentralDirectory,
869                                                            long EndOfCentralDirectory,
870                                                            int entryCount,
871                                                            uint numSegments)
872        {
873            const int bufferLength = 12 + 44 + 20;
874
875            byte[] bytes = new byte[bufferLength];
876
877            int i = 0;
878            // signature
879            byte[] sig = BitConverter.GetBytes(ZipConstants.Zip64EndOfCentralDirectoryRecordSignature);
880            Array.Copy(sig, 0, bytes, i, 4);
881            i+=4;
882
883            // There is a possibility to include "Extensible" data in the zip64
884            // end-of-central-dir record.  I cannot figure out what it might be used to
885            // store, so the size of this record is always fixed.  Maybe it is used for
886            // strong encryption data?  That is for another day.
887            long DataSize = 44;
888            Array.Copy(BitConverter.GetBytes(DataSize), 0, bytes, i, 8);
889            i += 8;
890
891            // offset 12
892            // VersionMadeBy = 45;
893            bytes[i++] = 45;
894            bytes[i++] = 0x00;
895
896            // VersionNeededToExtract = 45;
897            bytes[i++] = 45;
898            bytes[i++] = 0x00;
899
900            // offset 16
901            // number of the disk, and the disk with the start of the central dir.
902            // (this may change later)
903            for (int j = 0; j < 8; j++)
904                bytes[i++] = 0x00;
905
906            // offset 24
907            long numberOfEntries = entryCount;
908            Array.Copy(BitConverter.GetBytes(numberOfEntries), 0, bytes, i, 8);
909            i += 8;
910            Array.Copy(BitConverter.GetBytes(numberOfEntries), 0, bytes, i, 8);
911            i += 8;
912
913            // offset 40
914            Int64 SizeofCentraldirectory = EndOfCentralDirectory - StartOfCentralDirectory;
915            Array.Copy(BitConverter.GetBytes(SizeofCentraldirectory), 0, bytes, i, 8);
916            i += 8;
917            Array.Copy(BitConverter.GetBytes(StartOfCentralDirectory), 0, bytes, i, 8);
918            i += 8;
919
920            // offset 56
921            // now, the locator
922            // signature
923            sig = BitConverter.GetBytes(ZipConstants.Zip64EndOfCentralDirectoryLocatorSignature);
924            Array.Copy(sig, 0, bytes, i, 4);
925            i+=4;
926
927            // offset 60
928            // number of the disk with the start of the zip64 eocd
929            // (this will change later)  (it will?)
930            uint x2 = (numSegments==0)?0:(uint)(numSegments-1);
931            Array.Copy(BitConverter.GetBytes(x2), 0, bytes, i, 4);
932            i+=4;
933
934            // offset 64
935            // relative offset of the zip64 eocd
936            Array.Copy(BitConverter.GetBytes(EndOfCentralDirectory), 0, bytes, i, 8);
937            i += 8;
938
939            // offset 72
940            // total number of disks
941            // (this will change later)
942            Array.Copy(BitConverter.GetBytes(numSegments), 0, bytes, i, 4);
943            i+=4;
944
945            return bytes;
946        }
947
948
949
950        private static int CountEntries(ICollection<ZipEntry> _entries)
951        {
952            // Cannot just emit _entries.Count, because some of the entries
953            // may have been skipped.
954            int count = 0;
955            foreach (var entry in _entries)
956                if (entry.IncludedInMostRecentSave) count++;
957            return count;
958        }
959
960
961
962
963    }
964}
Note: See TracBrowser for help on using the repository browser.