Free cookie consent management tool by TermsFeed Policy Generator

source: branches/RemoveBackwardsCompatibility/HeuristicLab.ExtLibs/HeuristicLab.EPPlus/4.0.3/EPPlus-4.0.3/Packaging/DotNetZip/ZipFile.Read.cs @ 13401

Last change on this file since 13401 was 12074, checked in by sraggl, 9 years ago

#2341: Added EPPlus-4.0.3 to ExtLibs

File size: 45.0 KB
Line 
1// ZipFile.Read.cs
2// ------------------------------------------------------------------
3//
4// Copyright (c) 2009-2011 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 11:38:59>
19//
20// ------------------------------------------------------------------
21//
22// This module defines the methods for Reading zip files.
23//
24// ------------------------------------------------------------------
25//
26
27
28using System;
29using System.IO;
30using System.Collections.Generic;
31
32namespace OfficeOpenXml.Packaging.Ionic.Zip
33{
34    /// <summary>
35    ///   A class for collecting the various options that can be used when
36    ///   Reading zip files for extraction or update.
37    /// </summary>
38    ///
39    /// <remarks>
40    ///   <para>
41    ///     When reading a zip file, there are several options an
42    ///     application can set, to modify how the file is read, or what
43    ///     the library does while reading.  This class collects those
44    ///     options into one container.
45    ///   </para>
46    ///
47    ///   <para>
48    ///     Pass an instance of the <c>ReadOptions</c> class into the
49    ///     <c>ZipFile.Read()</c> method.
50    ///   </para>
51    ///
52    /// <seealso cref="ZipFile.Read(String, ReadOptions)"/>.
53    /// <seealso cref="ZipFile.Read(Stream, ReadOptions)"/>.
54    /// </remarks>
55    internal class ReadOptions
56    {
57        /// <summary>
58        /// An event handler for Read operations.  When opening large zip
59        /// archives, you may want to display a progress bar or other
60        /// indicator of status progress while reading.  This parameter
61        /// allows you to specify a ReadProgress Event Handler directly.
62        /// When you call <c>Read()</c>, the progress event is invoked as
63        /// necessary.
64        /// </summary>
65        public EventHandler<ReadProgressEventArgs> ReadProgress { get; set; }
66
67        /// <summary>
68        /// The <c>System.IO.TextWriter</c> to use for writing verbose status messages
69        /// during operations on the zip archive.  A console application may wish to
70        /// pass <c>System.Console.Out</c> to get messages on the Console. A graphical
71        /// or headless application may wish to capture the messages in a different
72        /// <c>TextWriter</c>, such as a <c>System.IO.StringWriter</c>.
73        /// </summary>
74        public TextWriter StatusMessageWriter { get; set; }
75
76        /// <summary>
77        /// The <c>System.Text.Encoding</c> to use when reading in the zip archive. Be
78        /// careful specifying the encoding.  If the value you use here is not the same
79        /// as the Encoding used when the zip archive was created (possibly by a
80        /// different archiver) you will get unexpected results and possibly exceptions.
81        /// </summary>
82        ///
83        /// <seealso cref="ZipFile.ProvisionalAlternateEncoding"/>
84        ///
85        public System.Text.Encoding @Encoding { get; set; }
86    }
87
88
89    internal partial class ZipFile
90    {
91        /// <summary>
92        /// Reads a zip file archive and returns the instance.
93        /// </summary>
94        ///
95        /// <remarks>
96        /// <para>
97        /// The stream is read using the default <c>System.Text.Encoding</c>, which is the
98        /// <c>IBM437</c> codepage.
99        /// </para>
100        /// </remarks>
101        ///
102        /// <exception cref="System.Exception">
103        /// Thrown if the <c>ZipFile</c> cannot be read. The implementation of this method
104        /// relies on <c>System.IO.File.OpenRead</c>, which can throw a variety of exceptions,
105        /// including specific exceptions if a file is not found, an unauthorized access
106        /// exception, exceptions for poorly formatted filenames, and so on.
107        /// </exception>
108        ///
109        /// <param name="fileName">
110        /// The name of the zip archive to open.  This can be a fully-qualified or relative
111        /// pathname.
112        /// </param>
113        ///
114        /// <seealso cref="ZipFile.Read(String, ReadOptions)"/>.
115        ///
116        /// <returns>The instance read from the zip archive.</returns>
117        ///
118        public static ZipFile Read(string fileName)
119        {
120            return ZipFile.Read(fileName, null, null, null);
121        }
122
123
124        /// <summary>
125        ///   Reads a zip file archive from the named filesystem file using the
126        ///   specified options.
127        /// </summary>
128        ///
129        /// <remarks>
130        /// <para>
131        ///   This version of the <c>Read()</c> method allows the caller to pass
132        ///   in a <c>TextWriter</c> an <c>Encoding</c>, via an instance of the
133        ///   <c>ReadOptions</c> class.  The <c>ZipFile</c> is read in using the
134        ///   specified encoding for entries where UTF-8 encoding is not
135        ///   explicitly specified.
136        /// </para>
137        /// </remarks>
138        ///
139        /// <example>
140        ///
141        /// <para>
142        ///   This example shows how to read a zip file using the Big-5 Chinese
143        ///   code page (950), and extract each entry in the zip file, while
144        ///   sending status messages out to the Console.
145        /// </para>
146        ///
147        /// <para>
148        ///   For this code to work as intended, the zipfile must have been
149        ///   created using the big5 code page (CP950). This is typical, for
150        ///   example, when using WinRar on a machine with CP950 set as the
151        ///   default code page.  In that case, the names of entries within the
152        ///   Zip archive will be stored in that code page, and reading the zip
153        ///   archive must be done using that code page.  If the application did
154        ///   not use the correct code page in ZipFile.Read(), then names of
155        ///   entries within the zip archive would not be correctly retrieved.
156        /// </para>
157        ///
158        /// <code lang="C#">
159        /// string zipToExtract = "MyArchive.zip";
160        /// string extractDirectory = "extract";
161        /// var options = new ReadOptions
162        /// {
163        ///   StatusMessageWriter = System.Console.Out,
164        ///   Encoding = System.Text.Encoding.GetEncoding(950)
165        /// };
166        /// using (ZipFile zip = ZipFile.Read(zipToExtract, options))
167        /// {
168        ///   foreach (ZipEntry e in zip)
169        ///   {
170        ///      e.Extract(extractDirectory);
171        ///   }
172        /// }
173        /// </code>
174        ///
175        ///
176        /// <code lang="VB">
177        /// Dim zipToExtract as String = "MyArchive.zip"
178        /// Dim extractDirectory as String = "extract"
179        /// Dim options as New ReadOptions
180        /// options.Encoding = System.Text.Encoding.GetEncoding(950)
181        /// options.StatusMessageWriter = System.Console.Out
182        /// Using zip As ZipFile = ZipFile.Read(zipToExtract, options)
183        ///     Dim e As ZipEntry
184        ///     For Each e In zip
185        ///      e.Extract(extractDirectory)
186        ///     Next
187        /// End Using
188        /// </code>
189        /// </example>
190        ///
191        ///
192        /// <example>
193        ///
194        /// <para>
195        ///   This example shows how to read a zip file using the default
196        ///   code page, to remove entries that have a modified date before a given threshold,
197        ///   sending status messages out to a <c>StringWriter</c>.
198        /// </para>
199        ///
200        /// <code lang="C#">
201        /// var options = new ReadOptions
202        /// {
203        ///   StatusMessageWriter = new System.IO.StringWriter()
204        /// };
205        /// using (ZipFile zip =  ZipFile.Read("PackedDocuments.zip", options))
206        /// {
207        ///   var Threshold = new DateTime(2007,7,4);
208        ///   // We cannot remove the entry from the list, within the context of
209        ///   // an enumeration of said list.
210        ///   // So we add the doomed entry to a list to be removed later.
211        ///   // pass 1: mark the entries for removal
212        ///   var MarkedEntries = new System.Collections.Generic.List&lt;ZipEntry&gt;();
213        ///   foreach (ZipEntry e in zip)
214        ///   {
215        ///     if (e.LastModified &lt; Threshold)
216        ///       MarkedEntries.Add(e);
217        ///   }
218        ///   // pass 2: actually remove the entry.
219        ///   foreach (ZipEntry zombie in MarkedEntries)
220        ///      zip.RemoveEntry(zombie);
221        ///   zip.Comment = "This archive has been updated.";
222        ///   zip.Save();
223        /// }
224        /// // can now use contents of sw, eg store in an audit log
225        /// </code>
226        ///
227        /// <code lang="VB">
228        /// Dim options as New ReadOptions
229        /// options.StatusMessageWriter = New System.IO.StringWriter
230        /// Using zip As ZipFile = ZipFile.Read("PackedDocuments.zip", options)
231        ///     Dim Threshold As New DateTime(2007, 7, 4)
232        ///     ' We cannot remove the entry from the list, within the context of
233        ///     ' an enumeration of said list.
234        ///     ' So we add the doomed entry to a list to be removed later.
235        ///     ' pass 1: mark the entries for removal
236        ///     Dim MarkedEntries As New System.Collections.Generic.List(Of ZipEntry)
237        ///     Dim e As ZipEntry
238        ///     For Each e In zip
239        ///         If (e.LastModified &lt; Threshold) Then
240        ///             MarkedEntries.Add(e)
241        ///         End If
242        ///     Next
243        ///     ' pass 2: actually remove the entry.
244        ///     Dim zombie As ZipEntry
245        ///     For Each zombie In MarkedEntries
246        ///         zip.RemoveEntry(zombie)
247        ///     Next
248        ///     zip.Comment = "This archive has been updated."
249        ///     zip.Save
250        /// End Using
251        /// ' can now use contents of sw, eg store in an audit log
252        /// </code>
253        /// </example>
254        ///
255        /// <exception cref="System.Exception">
256        ///   Thrown if the zipfile cannot be read. The implementation of
257        ///   this method relies on <c>System.IO.File.OpenRead</c>, which
258        ///   can throw a variety of exceptions, including specific
259        ///   exceptions if a file is not found, an unauthorized access
260        ///   exception, exceptions for poorly formatted filenames, and so
261        ///   on.
262        /// </exception>
263        ///
264        /// <param name="fileName">
265        /// The name of the zip archive to open.
266        /// This can be a fully-qualified or relative pathname.
267        /// </param>
268        ///
269        /// <param name="options">
270        /// The set of options to use when reading the zip file.
271        /// </param>
272        ///
273        /// <returns>The ZipFile instance read from the zip archive.</returns>
274        ///
275        /// <seealso cref="ZipFile.Read(Stream, ReadOptions)"/>
276        ///
277        internal static ZipFile Read(string fileName,
278                                   ReadOptions options)
279        {
280            if (options == null)
281                throw new ArgumentNullException("options");
282            return Read(fileName,
283                        options.StatusMessageWriter,
284                        options.Encoding,
285                        options.ReadProgress);
286        }
287
288        /// <summary>
289        /// Reads a zip file archive using the specified text encoding,  the specified
290        /// TextWriter for status messages, and the specified ReadProgress event handler,
291        /// and returns the instance.
292        /// </summary>
293        ///
294        /// <param name="fileName">
295        /// The name of the zip archive to open.
296        /// This can be a fully-qualified or relative pathname.
297        /// </param>
298        ///
299        /// <param name="readProgress">
300        /// An event handler for Read operations.
301        /// </param>
302        ///
303        /// <param name="statusMessageWriter">
304        /// The <c>System.IO.TextWriter</c> to use for writing verbose status messages
305        /// during operations on the zip archive.  A console application may wish to
306        /// pass <c>System.Console.Out</c> to get messages on the Console. A graphical
307        /// or headless application may wish to capture the messages in a different
308        /// <c>TextWriter</c>, such as a <c>System.IO.StringWriter</c>.
309        /// </param>
310        ///
311        /// <param name="encoding">
312        /// The <c>System.Text.Encoding</c> to use when reading in the zip archive. Be
313        /// careful specifying the encoding.  If the value you use here is not the same
314        /// as the Encoding used when the zip archive was created (possibly by a
315        /// different archiver) you will get unexpected results and possibly exceptions.
316        /// </param>
317        ///
318        /// <returns>The instance read from the zip archive.</returns>
319        ///
320        private static ZipFile Read(string fileName,
321                                   TextWriter statusMessageWriter,
322                                   System.Text.Encoding encoding,
323                                   EventHandler<ReadProgressEventArgs> readProgress)
324        {
325            ZipFile zf = new ZipFile();
326            zf.AlternateEncoding = encoding ?? DefaultEncoding;
327            zf.AlternateEncodingUsage = ZipOption.Always;
328            zf._StatusMessageTextWriter = statusMessageWriter;
329            zf._name = fileName;
330            if (readProgress != null)
331                zf.ReadProgress = readProgress;
332
333            if (zf.Verbose) zf._StatusMessageTextWriter.WriteLine("reading from {0}...", fileName);
334
335            ReadIntoInstance(zf);
336            zf._fileAlreadyExists = true;
337
338            return zf;
339        }
340
341        /// <summary>
342        ///   Reads a zip archive from a stream.
343        /// </summary>
344        ///
345        /// <remarks>
346        ///
347        /// <para>
348        ///   When reading from a file, it's probably easier to just use
349        ///   <see cref="ZipFile.Read(String,
350        ///   ReadOptions)">ZipFile.Read(String, ReadOptions)</see>.  This
351        ///   overload is useful when when the zip archive content is
352        ///   available from an already-open stream. The stream must be
353        ///   open and readable and seekable when calling this method.  The
354        ///   stream is left open when the reading is completed.
355        /// </para>
356        ///
357        /// <para>
358        ///   Using this overload, the stream is read using the default
359        ///   <c>System.Text.Encoding</c>, which is the <c>IBM437</c>
360        ///   codepage. If you want to specify the encoding to use when
361        ///   reading the zipfile content, see
362        ///   <see cref="ZipFile.Read(Stream,
363        ///   ReadOptions)">ZipFile.Read(Stream, ReadOptions)</see>.  This
364        /// </para>
365        ///
366        /// <para>
367        ///   Reading of zip content begins at the current position in the
368        ///   stream.  This means if you have a stream that concatenates
369        ///   regular data and zip data, if you position the open, readable
370        ///   stream at the start of the zip data, you will be able to read
371        ///   the zip archive using this constructor, or any of the ZipFile
372        ///   constructors that accept a <see cref="System.IO.Stream" /> as
373        ///   input. Some examples of where this might be useful: the zip
374        ///   content is concatenated at the end of a regular EXE file, as
375        ///   some self-extracting archives do.  (Note: SFX files produced
376        ///   by DotNetZip do not work this way; they can be read as normal
377        ///   ZIP files). Another example might be a stream being read from
378        ///   a database, where the zip content is embedded within an
379        ///   aggregate stream of data.
380        /// </para>
381        ///
382        /// </remarks>
383        ///
384        /// <example>
385        /// <para>
386        ///   This example shows how to Read zip content from a stream, and
387        ///   extract one entry into a different stream. In this example,
388        ///   the filename "NameOfEntryInArchive.doc", refers only to the
389        ///   name of the entry within the zip archive.  A file by that
390        ///   name is not created in the filesystem.  The I/O is done
391        ///   strictly with the given streams.
392        /// </para>
393        ///
394        /// <code>
395        /// using (ZipFile zip = ZipFile.Read(InputStream))
396        /// {
397        ///    zip.Extract("NameOfEntryInArchive.doc", OutputStream);
398        /// }
399        /// </code>
400        ///
401        /// <code lang="VB">
402        /// Using zip as ZipFile = ZipFile.Read(InputStream)
403        ///    zip.Extract("NameOfEntryInArchive.doc", OutputStream)
404        /// End Using
405        /// </code>
406        /// </example>
407        ///
408        /// <param name="zipStream">the stream containing the zip data.</param>
409        ///
410        /// <returns>The ZipFile instance read from the stream</returns>
411        ///
412        public static ZipFile Read(Stream zipStream)
413        {
414            return Read(zipStream, null, null, null);
415        }
416
417        /// <summary>
418        ///   Reads a zip file archive from the given stream using the
419        ///   specified options.
420        /// </summary>
421        ///
422        /// <remarks>
423        ///
424        /// <para>
425        ///   When reading from a file, it's probably easier to just use
426        ///   <see cref="ZipFile.Read(String,
427        ///   ReadOptions)">ZipFile.Read(String, ReadOptions)</see>.  This
428        ///   overload is useful when when the zip archive content is
429        ///   available from an already-open stream. The stream must be
430        ///   open and readable and seekable when calling this method.  The
431        ///   stream is left open when the reading is completed.
432        /// </para>
433        ///
434        /// <para>
435        ///   Reading of zip content begins at the current position in the
436        ///   stream.  This means if you have a stream that concatenates
437        ///   regular data and zip data, if you position the open, readable
438        ///   stream at the start of the zip data, you will be able to read
439        ///   the zip archive using this constructor, or any of the ZipFile
440        ///   constructors that accept a <see cref="System.IO.Stream" /> as
441        ///   input. Some examples of where this might be useful: the zip
442        ///   content is concatenated at the end of a regular EXE file, as
443        ///   some self-extracting archives do.  (Note: SFX files produced
444        ///   by DotNetZip do not work this way; they can be read as normal
445        ///   ZIP files). Another example might be a stream being read from
446        ///   a database, where the zip content is embedded within an
447        ///   aggregate stream of data.
448        /// </para>
449        /// </remarks>
450        ///
451        /// <param name="zipStream">the stream containing the zip data.</param>
452        ///
453        /// <param name="options">
454        ///   The set of options to use when reading the zip file.
455        /// </param>
456        ///
457        /// <exception cref="System.Exception">
458        ///   Thrown if the zip archive cannot be read.
459        /// </exception>
460        ///
461        /// <returns>The ZipFile instance read from the stream.</returns>
462        ///
463        /// <seealso cref="ZipFile.Read(String, ReadOptions)"/>
464        ///
465        internal static ZipFile Read(Stream zipStream, ReadOptions options)
466        {
467            if (options == null)
468                throw new ArgumentNullException("options");
469
470            return Read(zipStream,
471                        options.StatusMessageWriter,
472                        options.Encoding,
473                        options.ReadProgress);
474        }
475
476
477
478        /// <summary>
479        /// Reads a zip archive from a stream, using the specified text Encoding, the
480        /// specified TextWriter for status messages,
481        /// and the specified ReadProgress event handler.
482        /// </summary>
483        ///
484        /// <remarks>
485        /// <para>
486        /// Reading of zip content begins at the current position in the stream.  This
487        /// means if you have a stream that concatenates regular data and zip data, if
488        /// you position the open, readable stream at the start of the zip data, you
489        /// will be able to read the zip archive using this constructor, or any of the
490        /// ZipFile constructors that accept a <see cref="System.IO.Stream" /> as
491        /// input. Some examples of where this might be useful: the zip content is
492        /// concatenated at the end of a regular EXE file, as some self-extracting
493        /// archives do.  (Note: SFX files produced by DotNetZip do not work this
494        /// way). Another example might be a stream being read from a database, where
495        /// the zip content is embedded within an aggregate stream of data.
496        /// </para>
497        /// </remarks>
498        ///
499        /// <param name="zipStream">the stream containing the zip data.</param>
500        ///
501        /// <param name="statusMessageWriter">
502        /// The <c>System.IO.TextWriter</c> to which verbose status messages are written
503        /// during operations on the <c>ZipFile</c>.  For example, in a console
504        /// application, System.Console.Out works, and will get a message for each entry
505        /// added to the ZipFile.  If the TextWriter is <c>null</c>, no verbose messages
506        /// are written.
507        /// </param>
508        ///
509        /// <param name="encoding">
510        /// The text encoding to use when reading entries that do not have the UTF-8
511        /// encoding bit set.  Be careful specifying the encoding.  If the value you use
512        /// here is not the same as the Encoding used when the zip archive was created
513        /// (possibly by a different archiver) you will get unexpected results and
514        /// possibly exceptions.  See the <see cref="ProvisionalAlternateEncoding"/>
515        /// property for more information.
516        /// </param>
517        ///
518        /// <param name="readProgress">
519        /// An event handler for Read operations.
520        /// </param>
521        ///
522        /// <returns>an instance of ZipFile</returns>
523        private static ZipFile Read(Stream zipStream,
524                                   TextWriter statusMessageWriter,
525                                   System.Text.Encoding encoding,
526                                   EventHandler<ReadProgressEventArgs> readProgress)
527        {
528            if (zipStream == null)
529                throw new ArgumentNullException("zipStream");
530
531            ZipFile zf = new ZipFile();
532            zf._StatusMessageTextWriter = statusMessageWriter;
533            zf._alternateEncoding = encoding ?? ZipFile.DefaultEncoding;
534            zf._alternateEncodingUsage = ZipOption.Always;
535            if (readProgress != null)
536                zf.ReadProgress += readProgress;
537            zf._readstream = (zipStream.Position == 0L)
538                ? zipStream
539                : new OffsetStream(zipStream);
540            zf._ReadStreamIsOurs = false;
541            if (zf.Verbose) zf._StatusMessageTextWriter.WriteLine("reading from stream...");
542
543            ReadIntoInstance(zf);
544            return zf;
545        }
546
547
548
549        private static void ReadIntoInstance(ZipFile zf)
550        {
551            Stream s = zf.ReadStream;
552            try
553            {
554                zf._readName = zf._name; // workitem 13915
555                if (!s.CanSeek)
556                {
557                    ReadIntoInstance_Orig(zf);
558                    return;
559                }
560
561                zf.OnReadStarted();
562
563                // change for workitem 8098
564                //zf._originPosition = s.Position;
565
566                // Try reading the central directory, rather than scanning the file.
567
568                uint datum = ReadFirstFourBytes(s);
569
570                if (datum == ZipConstants.EndOfCentralDirectorySignature)
571                    return;
572
573
574                // start at the end of the file...
575                // seek backwards a bit, then look for the EoCD signature.
576                int nTries = 0;
577                bool success = false;
578
579                // The size of the end-of-central-directory-footer plus 2 bytes is 18.
580                // This implies an archive comment length of 0.  We'll add a margin of
581                // safety and start "in front" of that, when looking for the
582                // EndOfCentralDirectorySignature
583                long posn = s.Length - 64;
584                long maxSeekback = Math.Max(s.Length - 0x4000, 10);
585                do
586                {
587                    if (posn < 0) posn = 0;  // BOF
588                    s.Seek(posn, SeekOrigin.Begin);
589                    long bytesRead = SharedUtilities.FindSignature(s, (int)ZipConstants.EndOfCentralDirectorySignature);
590                    if (bytesRead != -1)
591                        success = true;
592                    else
593                    {
594                        if (posn==0) break; // started at the BOF and found nothing
595                        nTries++;
596                        // Weird: with NETCF, negative offsets from SeekOrigin.End DO
597                        // NOT WORK. So rather than seek a negative offset, we seek
598                        // from SeekOrigin.Begin using a smaller number.
599                        posn -= (32 * (nTries + 1) * nTries);
600                    }
601                }
602                while (!success && posn > maxSeekback);
603
604                if (success)
605                {
606                    // workitem 8299
607                    zf._locEndOfCDS = s.Position - 4;
608
609                    byte[] block = new byte[16];
610                    s.Read(block, 0, block.Length);
611
612                    zf._diskNumberWithCd = BitConverter.ToUInt16(block, 2);
613
614                    if (zf._diskNumberWithCd == 0xFFFF)
615                        throw new ZipException("Spanned archives with more than 65534 segments are not supported at this time.");
616
617                    zf._diskNumberWithCd++; // I think the number in the file differs from reality by 1
618
619                    int i = 12;
620
621                    uint offset32 = (uint) BitConverter.ToUInt32(block, i);
622                    if (offset32 == 0xFFFFFFFF)
623                    {
624                        Zip64SeekToCentralDirectory(zf);
625                    }
626                    else
627                    {
628                        zf._OffsetOfCentralDirectory = offset32;
629                        // change for workitem 8098
630                        s.Seek(offset32, SeekOrigin.Begin);
631                    }
632
633                    ReadCentralDirectory(zf);
634                }
635                else
636                {
637                    // Could not find the central directory.
638                    // Fallback to the old method.
639                    // workitem 8098: ok
640                    //s.Seek(zf._originPosition, SeekOrigin.Begin);
641                    s.Seek(0L, SeekOrigin.Begin);
642                    ReadIntoInstance_Orig(zf);
643                }
644            }
645            catch (Exception ex1)
646            {
647                if (zf._ReadStreamIsOurs && zf._readstream != null)
648                {
649                    try
650                    {
651#if NETCF
652                        zf._readstream.Close();
653#else
654                        zf._readstream.Dispose();
655#endif
656                        zf._readstream = null;
657                    }
658                    finally { }
659                }
660
661                throw new ZipException("Cannot read that as a ZipFile", ex1);
662            }
663
664            // the instance has been read in
665            zf._contentsChanged = false;
666        }
667
668
669
670        private static void Zip64SeekToCentralDirectory(ZipFile zf)
671        {
672            Stream s = zf.ReadStream;
673            byte[] block = new byte[16];
674
675            // seek back to find the ZIP64 EoCD.
676            // I think this might not work for .NET CF ?
677            s.Seek(-40, SeekOrigin.Current);
678            s.Read(block, 0, 16);
679
680            Int64 offset64 = BitConverter.ToInt64(block, 8);
681            zf._OffsetOfCentralDirectory = 0xFFFFFFFF;
682            zf._OffsetOfCentralDirectory64 = offset64;
683            // change for workitem 8098
684            s.Seek(offset64, SeekOrigin.Begin);
685            //zf.SeekFromOrigin(Offset64);
686
687            uint datum = (uint)Ionic.Zip.SharedUtilities.ReadInt(s);
688            if (datum != ZipConstants.Zip64EndOfCentralDirectoryRecordSignature)
689                throw new BadReadException(String.Format("  Bad signature (0x{0:X8}) looking for ZIP64 EoCD Record at position 0x{1:X8}", datum, s.Position));
690
691            s.Read(block, 0, 8);
692            Int64 Size = BitConverter.ToInt64(block, 0);
693
694            block = new byte[Size];
695            s.Read(block, 0, block.Length);
696
697            offset64 = BitConverter.ToInt64(block, 36);
698            // change for workitem 8098
699            s.Seek(offset64, SeekOrigin.Begin);
700            //zf.SeekFromOrigin(Offset64);
701        }
702
703
704        private static uint ReadFirstFourBytes(Stream s)
705        {
706            uint datum = (uint)Ionic.Zip.SharedUtilities.ReadInt(s);
707            return datum;
708        }
709
710
711
712        private static void ReadCentralDirectory(ZipFile zf)
713        {
714            // We must have the central directory footer record, in order to properly
715            // read zip dir entries from the central directory.  This because the logic
716            // knows when to open a spanned file when the volume number for the central
717            // directory differs from the volume number for the zip entry.  The
718            // _diskNumberWithCd was set when originally finding the offset for the
719            // start of the Central Directory.
720
721            // workitem 9214
722            bool inputUsesZip64 = false;
723            ZipEntry de;
724            // in lieu of hashset, use a dictionary
725            var previouslySeen = new Dictionary<String,object>();
726            while ((de = ZipEntry.ReadDirEntry(zf, previouslySeen)) != null)
727            {
728                de.ResetDirEntry();
729                zf.OnReadEntry(true, null);
730
731                if (zf.Verbose)
732                    zf.StatusMessageTextWriter.WriteLine("entry {0}", de.FileName);
733
734                zf._entries.Add(de.FileName,de);
735
736                // workitem 9214
737                if (de._InputUsesZip64) inputUsesZip64 = true;
738                previouslySeen.Add(de.FileName, null); // to prevent dupes
739            }
740
741            // workitem 9214; auto-set the zip64 flag
742            if (inputUsesZip64) zf.UseZip64WhenSaving = Zip64Option.Always;
743
744            // workitem 8299
745            if (zf._locEndOfCDS > 0)
746                zf.ReadStream.Seek(zf._locEndOfCDS, SeekOrigin.Begin);
747
748            ReadCentralDirectoryFooter(zf);
749
750            if (zf.Verbose && !String.IsNullOrEmpty(zf.Comment))
751                zf.StatusMessageTextWriter.WriteLine("Zip file Comment: {0}", zf.Comment);
752
753            // We keep the read stream open after reading.
754
755            if (zf.Verbose)
756                zf.StatusMessageTextWriter.WriteLine("read in {0} entries.", zf._entries.Count);
757
758            zf.OnReadCompleted();
759        }
760
761
762
763
764        // build the TOC by reading each entry in the file.
765        private static void ReadIntoInstance_Orig(ZipFile zf)
766        {
767            zf.OnReadStarted();
768            //zf._entries = new System.Collections.Generic.List<ZipEntry>();
769            zf._entries = new System.Collections.Generic.Dictionary<String,ZipEntry>();
770
771            ZipEntry e;
772            if (zf.Verbose)
773                if (zf.Name == null)
774                    zf.StatusMessageTextWriter.WriteLine("Reading zip from stream...");
775                else
776                    zf.StatusMessageTextWriter.WriteLine("Reading zip {0}...", zf.Name);
777
778            // work item 6647:  PK00 (packed to removable disk)
779            bool firstEntry = true;
780            ZipContainer zc = new ZipContainer(zf);
781            while ((e = ZipEntry.ReadEntry(zc, firstEntry)) != null)
782            {
783                if (zf.Verbose)
784                    zf.StatusMessageTextWriter.WriteLine("  {0}", e.FileName);
785
786                zf._entries.Add(e.FileName,e);
787                firstEntry = false;
788            }
789
790            // read the zipfile's central directory structure here.
791            // workitem 9912
792            // But, because it may be corrupted, ignore errors.
793            try
794            {
795                ZipEntry de;
796                // in lieu of hashset, use a dictionary
797                var previouslySeen = new Dictionary<String,Object>();
798                while ((de = ZipEntry.ReadDirEntry(zf, previouslySeen)) != null)
799                {
800                    // Housekeeping: Since ZipFile exposes ZipEntry elements in the enumerator,
801                    // we need to copy the comment that we grab from the ZipDirEntry
802                    // into the ZipEntry, so the application can access the comment.
803                    // Also since ZipEntry is used to Write zip files, we need to copy the
804                    // file attributes to the ZipEntry as appropriate.
805                    ZipEntry e1 = zf._entries[de.FileName];
806                    if (e1 != null)
807                    {
808                        e1._Comment = de.Comment;
809                        if (de.IsDirectory) e1.MarkAsDirectory();
810                    }
811                    previouslySeen.Add(de.FileName,null); // to prevent dupes
812                }
813
814                // workitem 8299
815                if (zf._locEndOfCDS > 0)
816                    zf.ReadStream.Seek(zf._locEndOfCDS, SeekOrigin.Begin);
817
818                ReadCentralDirectoryFooter(zf);
819
820                if (zf.Verbose && !String.IsNullOrEmpty(zf.Comment))
821                    zf.StatusMessageTextWriter.WriteLine("Zip file Comment: {0}", zf.Comment);
822            }
823            catch (ZipException) { }
824            catch (IOException) { }
825
826            zf.OnReadCompleted();
827        }
828
829
830
831
832        private static void ReadCentralDirectoryFooter(ZipFile zf)
833        {
834            Stream s = zf.ReadStream;
835            int signature = Ionic.Zip.SharedUtilities.ReadSignature(s);
836
837            byte[] block = null;
838            int j = 0;
839            if (signature == ZipConstants.Zip64EndOfCentralDirectoryRecordSignature)
840            {
841                // We have a ZIP64 EOCD
842                // This data block is 4 bytes sig, 8 bytes size, 44 bytes fixed data,
843                // followed by a variable-sized extension block.  We have read the sig already.
844                // 8 - datasize (64 bits)
845                // 2 - version made by
846                // 2 - version needed to extract
847                // 4 - number of this disk
848                // 4 - number of the disk with the start of the CD
849                // 8 - total number of entries in the CD on this disk
850                // 8 - total number of entries in the CD
851                // 8 - size of the CD
852                // 8 - offset of the CD
853                // -----------------------
854                // 52 bytes
855
856                block = new byte[8 + 44];
857                s.Read(block, 0, block.Length);
858
859                Int64 DataSize = BitConverter.ToInt64(block, 0);  // == 44 + the variable length
860
861                if (DataSize < 44)
862                    throw new ZipException("Bad size in the ZIP64 Central Directory.");
863
864                zf._versionMadeBy = BitConverter.ToUInt16(block, j);
865                j += 2;
866                zf._versionNeededToExtract = BitConverter.ToUInt16(block, j);
867                j += 2;
868                zf._diskNumberWithCd = BitConverter.ToUInt32(block, j);
869                j += 2;
870
871                //zf._diskNumberWithCd++; // hack!!
872
873                // read the extended block
874                block = new byte[DataSize - 44];
875                s.Read(block, 0, block.Length);
876                // discard the result
877
878                signature = Ionic.Zip.SharedUtilities.ReadSignature(s);
879                if (signature != ZipConstants.Zip64EndOfCentralDirectoryLocatorSignature)
880                    throw new ZipException("Inconsistent metadata in the ZIP64 Central Directory.");
881
882                block = new byte[16];
883                s.Read(block, 0, block.Length);
884                // discard the result
885
886                signature = Ionic.Zip.SharedUtilities.ReadSignature(s);
887            }
888
889            // Throw if this is not a signature for "end of central directory record"
890            // This is a sanity check.
891            if (signature != ZipConstants.EndOfCentralDirectorySignature)
892            {
893                s.Seek(-4, SeekOrigin.Current);
894                throw new BadReadException(String.Format("Bad signature ({0:X8}) at position 0x{1:X8}",
895                                                         signature, s.Position));
896            }
897
898            // read the End-of-Central-Directory-Record
899            block = new byte[16];
900            zf.ReadStream.Read(block, 0, block.Length);
901
902            // off sz  data
903            // -------------------------------------------------------
904            //  0   4  end of central dir signature (0x06054b50)
905            //  4   2  number of this disk
906            //  6   2  number of the disk with start of the central directory
907            //  8   2  total number of entries in the  central directory on this disk
908            // 10   2  total number of entries in  the central directory
909            // 12   4  size of the central directory
910            // 16   4  offset of start of central directory with respect to the starting disk number
911            // 20   2  ZIP file comment length
912            // 22  ??  ZIP file comment
913
914            if (zf._diskNumberWithCd == 0)
915            {
916                zf._diskNumberWithCd = BitConverter.ToUInt16(block, 2);
917                //zf._diskNumberWithCd++; // hack!!
918            }
919
920            // read the comment here
921            ReadZipFileComment(zf);
922        }
923
924
925
926        private static void ReadZipFileComment(ZipFile zf)
927        {
928            // read the comment here
929            byte[] block = new byte[2];
930            zf.ReadStream.Read(block, 0, block.Length);
931
932            Int16 commentLength = (short)(block[0] + block[1] * 256);
933            if (commentLength > 0)
934            {
935                block = new byte[commentLength];
936                zf.ReadStream.Read(block, 0, block.Length);
937
938                // workitem 10392 - prefer ProvisionalAlternateEncoding,
939                // first.  The fix for workitem 6513 tried to use UTF8
940                // only as necessary, but that is impossible to test
941                // for, in this direction. There's no way to know what
942                // characters the already-encoded bytes refer
943                // to. Therefore, must do what the user tells us.
944
945                string s1 = zf.AlternateEncoding.GetString(block, 0, block.Length);
946                zf.Comment = s1;
947            }
948        }
949
950
951        // private static bool BlocksAreEqual(byte[] a, byte[] b)
952        // {
953        //     if (a.Length != b.Length) return false;
954        //     for (int i = 0; i < a.Length; i++)
955        //     {
956        //         if (a[i] != b[i]) return false;
957        //     }
958        //     return true;
959        // }
960
961
962
963        /// <summary>
964        /// Checks the given file to see if it appears to be a valid zip file.
965        /// </summary>
966        /// <remarks>
967        ///
968        /// <para>
969        ///   Calling this method is equivalent to calling <see cref="IsZipFile(string,
970        ///   bool)"/> with the testExtract parameter set to false.
971        /// </para>
972        /// </remarks>
973        ///
974        /// <param name="fileName">The file to check.</param>
975        /// <returns>true if the file appears to be a zip file.</returns>
976        public static bool IsZipFile(string fileName)
977        {
978            return IsZipFile(fileName, false);
979        }
980
981
982        /// <summary>
983        /// Checks a file to see if it is a valid zip file.
984        /// </summary>
985        ///
986        /// <remarks>
987        /// <para>
988        ///   This method opens the specified zip file, reads in the zip archive,
989        ///   verifying the ZIP metadata as it reads.
990        /// </para>
991        ///
992        /// <para>
993        ///   If everything succeeds, then the method returns true.  If anything fails -
994        ///   for example if an incorrect signature or CRC is found, indicating a
995        ///   corrupt file, the the method returns false.  This method also returns
996        ///   false for a file that does not exist.
997        /// </para>
998        ///
999        /// <para>
1000        ///   If <paramref name="testExtract"/> is true, as part of its check, this
1001        ///   method reads in the content for each entry, expands it, and checks CRCs.
1002        ///   This provides an additional check beyond verifying the zip header and
1003        ///   directory data.
1004        /// </para>
1005        ///
1006        /// <para>
1007        ///   If <paramref name="testExtract"/> is true, and if any of the zip entries
1008        ///   are protected with a password, this method will return false.  If you want
1009        ///   to verify a <c>ZipFile</c> that has entries which are protected with a
1010        ///   password, you will need to do that manually.
1011        /// </para>
1012        ///
1013        /// </remarks>
1014        ///
1015        /// <param name="fileName">The zip file to check.</param>
1016        /// <param name="testExtract">true if the caller wants to extract each entry.</param>
1017        /// <returns>true if the file contains a valid zip file.</returns>
1018        public static bool IsZipFile(string fileName, bool testExtract)
1019        {
1020            bool result = false;
1021            try
1022            {
1023                if (!File.Exists(fileName)) return false;
1024
1025                using (var s = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
1026                {
1027                    result = IsZipFile(s, testExtract);
1028                }
1029            }
1030            catch (IOException) { }
1031            catch (ZipException) { }
1032            return result;
1033        }
1034
1035
1036        /// <summary>
1037        /// Checks a stream to see if it contains a valid zip archive.
1038        /// </summary>
1039        ///
1040        /// <remarks>
1041        /// <para>
1042        /// This method reads the zip archive contained in the specified stream, verifying
1043        /// the ZIP metadata as it reads.  If testExtract is true, this method also extracts
1044        /// each entry in the archive, dumping all the bits into <see cref="Stream.Null"/>.
1045        /// </para>
1046        ///
1047        /// <para>
1048        /// If everything succeeds, then the method returns true.  If anything fails -
1049        /// for example if an incorrect signature or CRC is found, indicating a corrupt
1050        /// file, the the method returns false.  This method also returns false for a
1051        /// file that does not exist.
1052        /// </para>
1053        ///
1054        /// <para>
1055        /// If <c>testExtract</c> is true, this method reads in the content for each
1056        /// entry, expands it, and checks CRCs.  This provides an additional check
1057        /// beyond verifying the zip header data.
1058        /// </para>
1059        ///
1060        /// <para>
1061        /// If <c>testExtract</c> is true, and if any of the zip entries are protected
1062        /// with a password, this method will return false.  If you want to verify a
1063        /// ZipFile that has entries which are protected with a password, you will need
1064        /// to do that manually.
1065        /// </para>
1066        /// </remarks>
1067        ///
1068        /// <seealso cref="IsZipFile(string, bool)"/>
1069        ///
1070        /// <param name="stream">The stream to check.</param>
1071        /// <param name="testExtract">true if the caller wants to extract each entry.</param>
1072        /// <returns>true if the stream contains a valid zip archive.</returns>
1073        public static bool IsZipFile(Stream stream, bool testExtract)
1074        {
1075            if (stream == null)
1076                throw new ArgumentNullException("stream");
1077
1078            bool result = false;
1079            try
1080            {
1081                if (!stream.CanRead) return false;
1082
1083                var bitBucket = Stream.Null;
1084
1085                using (ZipFile zip1 = ZipFile.Read(stream, null, null, null))
1086                {
1087                    if (testExtract)
1088                    {
1089                        foreach (var e in zip1)
1090                        {
1091                            if (!e.IsDirectory)
1092                            {
1093                                e.Extract(bitBucket);
1094                            }
1095                        }
1096                    }
1097                }
1098                result = true;
1099            }
1100            catch (IOException) { }
1101            catch (ZipException) { }
1102            return result;
1103        }
1104
1105
1106
1107
1108    }
1109
1110}
Note: See TracBrowser for help on using the repository browser.