1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114 |
- // ZipFile.Read.cs
- // ------------------------------------------------------------------
- //
- // Copyright (c) 2009-2011 Dino Chiesa.
- // All rights reserved.
- //
- // This code module is part of DotNetZip, a zipfile class library.
- //
- // ------------------------------------------------------------------
- //
- // This code is licensed under the Microsoft Public License.
- // See the file License.txt for the license details.
- // More info on: http://dotnetzip.codeplex.com
- //
- // ------------------------------------------------------------------
- //
- // last saved (in emacs):
- // Time-stamp: <2011-August-05 11:38:59>
- //
- // ------------------------------------------------------------------
- //
- // This module defines the methods for Reading zip files.
- //
- // ------------------------------------------------------------------
- //
- using System;
- using System.IO;
- using System.Collections.Generic;
- namespace Ionic.Zip
- {
- /// <summary>
- /// A class for collecting the various options that can be used when
- /// Reading zip files for extraction or update.
- /// </summary>
- ///
- /// <remarks>
- /// <para>
- /// When reading a zip file, there are several options an
- /// application can set, to modify how the file is read, or what
- /// the library does while reading. This class collects those
- /// options into one container.
- /// </para>
- ///
- /// <para>
- /// Pass an instance of the <c>ReadOptions</c> class into the
- /// <c>ZipFile.Read()</c> method.
- /// </para>
- ///
- /// <seealso cref="ZipFile.Read(String, ReadOptions)"/>.
- /// <seealso cref="ZipFile.Read(Stream, ReadOptions)"/>.
- /// </remarks>
- public class ReadOptions
- {
- /// <summary>
- /// An event handler for Read operations. When opening large zip
- /// archives, you may want to display a progress bar or other
- /// indicator of status progress while reading. This parameter
- /// allows you to specify a ReadProgress Event Handler directly.
- /// When you call <c>Read()</c>, the progress event is invoked as
- /// necessary.
- /// </summary>
- public EventHandler<ReadProgressEventArgs> ReadProgress { get; set; }
- /// <summary>
- /// The <c>System.IO.TextWriter</c> to use for writing verbose status messages
- /// during operations on the zip archive. A console application may wish to
- /// pass <c>System.Console.Out</c> to get messages on the Console. A graphical
- /// or headless application may wish to capture the messages in a different
- /// <c>TextWriter</c>, such as a <c>System.IO.StringWriter</c>.
- /// </summary>
- public TextWriter StatusMessageWriter { get; set; }
- /// <summary>
- /// The <c>System.Text.Encoding</c> to use when reading in the zip archive. Be
- /// careful specifying the encoding. If the value you use here is not the same
- /// as the Encoding used when the zip archive was created (possibly by a
- /// different archiver) you will get unexpected results and possibly exceptions.
- /// </summary>
- ///
- /// <seealso cref="ZipFile.ProvisionalAlternateEncoding"/>
- ///
- public System.Text.Encoding @Encoding { get; set; }
- }
- public partial class ZipFile
- {
- /// <summary>
- /// Reads a zip file archive and returns the instance.
- /// </summary>
- ///
- /// <remarks>
- /// <para>
- /// The stream is read using the default <c>System.Text.Encoding</c>, which is the
- /// <c>IBM437</c> codepage.
- /// </para>
- /// </remarks>
- ///
- /// <exception cref="System.Exception">
- /// Thrown if the <c>ZipFile</c> cannot be read. The implementation of this method
- /// relies on <c>System.IO.File.OpenRead</c>, which can throw a variety of exceptions,
- /// including specific exceptions if a file is not found, an unauthorized access
- /// exception, exceptions for poorly formatted filenames, and so on.
- /// </exception>
- ///
- /// <param name="fileName">
- /// The name of the zip archive to open. This can be a fully-qualified or relative
- /// pathname.
- /// </param>
- ///
- /// <seealso cref="ZipFile.Read(String, ReadOptions)"/>.
- ///
- /// <returns>The instance read from the zip archive.</returns>
- ///
- public static ZipFile Read(string fileName)
- {
- return ZipFile.Read(fileName, null, null, null);
- }
- /// <summary>
- /// Reads a zip file archive from the named filesystem file using the
- /// specified options.
- /// </summary>
- ///
- /// <remarks>
- /// <para>
- /// This version of the <c>Read()</c> method allows the caller to pass
- /// in a <c>TextWriter</c> an <c>Encoding</c>, via an instance of the
- /// <c>ReadOptions</c> class. The <c>ZipFile</c> is read in using the
- /// specified encoding for entries where UTF-8 encoding is not
- /// explicitly specified.
- /// </para>
- /// </remarks>
- ///
- /// <example>
- ///
- /// <para>
- /// This example shows how to read a zip file using the Big-5 Chinese
- /// code page (950), and extract each entry in the zip file, while
- /// sending status messages out to the Console.
- /// </para>
- ///
- /// <para>
- /// For this code to work as intended, the zipfile must have been
- /// created using the big5 code page (CP950). This is typical, for
- /// example, when using WinRar on a machine with CP950 set as the
- /// default code page. In that case, the names of entries within the
- /// Zip archive will be stored in that code page, and reading the zip
- /// archive must be done using that code page. If the application did
- /// not use the correct code page in ZipFile.Read(), then names of
- /// entries within the zip archive would not be correctly retrieved.
- /// </para>
- ///
- /// <code lang="C#">
- /// string zipToExtract = "MyArchive.zip";
- /// string extractDirectory = "extract";
- /// var options = new ReadOptions
- /// {
- /// StatusMessageWriter = System.Console.Out,
- /// Encoding = System.Text.Encoding.GetEncoding(950)
- /// };
- /// using (ZipFile zip = ZipFile.Read(zipToExtract, options))
- /// {
- /// foreach (ZipEntry e in zip)
- /// {
- /// e.Extract(extractDirectory);
- /// }
- /// }
- /// </code>
- ///
- ///
- /// <code lang="VB">
- /// Dim zipToExtract as String = "MyArchive.zip"
- /// Dim extractDirectory as String = "extract"
- /// Dim options as New ReadOptions
- /// options.Encoding = System.Text.Encoding.GetEncoding(950)
- /// options.StatusMessageWriter = System.Console.Out
- /// Using zip As ZipFile = ZipFile.Read(zipToExtract, options)
- /// Dim e As ZipEntry
- /// For Each e In zip
- /// e.Extract(extractDirectory)
- /// Next
- /// End Using
- /// </code>
- /// </example>
- ///
- ///
- /// <example>
- ///
- /// <para>
- /// This example shows how to read a zip file using the default
- /// code page, to remove entries that have a modified date before a given threshold,
- /// sending status messages out to a <c>StringWriter</c>.
- /// </para>
- ///
- /// <code lang="C#">
- /// var options = new ReadOptions
- /// {
- /// StatusMessageWriter = new System.IO.StringWriter()
- /// };
- /// using (ZipFile zip = ZipFile.Read("PackedDocuments.zip", options))
- /// {
- /// var Threshold = new DateTime(2007,7,4);
- /// // We cannot remove the entry from the list, within the context of
- /// // an enumeration of said list.
- /// // So we add the doomed entry to a list to be removed later.
- /// // pass 1: mark the entries for removal
- /// var MarkedEntries = new System.Collections.Generic.List<ZipEntry>();
- /// foreach (ZipEntry e in zip)
- /// {
- /// if (e.LastModified < Threshold)
- /// MarkedEntries.Add(e);
- /// }
- /// // pass 2: actually remove the entry.
- /// foreach (ZipEntry zombie in MarkedEntries)
- /// zip.RemoveEntry(zombie);
- /// zip.Comment = "This archive has been updated.";
- /// zip.Save();
- /// }
- /// // can now use contents of sw, eg store in an audit log
- /// </code>
- ///
- /// <code lang="VB">
- /// Dim options as New ReadOptions
- /// options.StatusMessageWriter = New System.IO.StringWriter
- /// Using zip As ZipFile = ZipFile.Read("PackedDocuments.zip", options)
- /// Dim Threshold As New DateTime(2007, 7, 4)
- /// ' We cannot remove the entry from the list, within the context of
- /// ' an enumeration of said list.
- /// ' So we add the doomed entry to a list to be removed later.
- /// ' pass 1: mark the entries for removal
- /// Dim MarkedEntries As New System.Collections.Generic.List(Of ZipEntry)
- /// Dim e As ZipEntry
- /// For Each e In zip
- /// If (e.LastModified < Threshold) Then
- /// MarkedEntries.Add(e)
- /// End If
- /// Next
- /// ' pass 2: actually remove the entry.
- /// Dim zombie As ZipEntry
- /// For Each zombie In MarkedEntries
- /// zip.RemoveEntry(zombie)
- /// Next
- /// zip.Comment = "This archive has been updated."
- /// zip.Save
- /// End Using
- /// ' can now use contents of sw, eg store in an audit log
- /// </code>
- /// </example>
- ///
- /// <exception cref="System.Exception">
- /// Thrown if the zipfile cannot be read. The implementation of
- /// this method relies on <c>System.IO.File.OpenRead</c>, which
- /// can throw a variety of exceptions, including specific
- /// exceptions if a file is not found, an unauthorized access
- /// exception, exceptions for poorly formatted filenames, and so
- /// on.
- /// </exception>
- ///
- /// <param name="fileName">
- /// The name of the zip archive to open.
- /// This can be a fully-qualified or relative pathname.
- /// </param>
- ///
- /// <param name="options">
- /// The set of options to use when reading the zip file.
- /// </param>
- ///
- /// <returns>The ZipFile instance read from the zip archive.</returns>
- ///
- /// <seealso cref="ZipFile.Read(Stream, ReadOptions)"/>
- ///
- public static ZipFile Read(string fileName,
- ReadOptions options)
- {
- if (options == null)
- throw new ArgumentNullException("options");
- return Read(fileName,
- options.StatusMessageWriter,
- options.Encoding,
- options.ReadProgress);
- }
- /// <summary>
- /// Reads a zip file archive using the specified text encoding, the specified
- /// TextWriter for status messages, and the specified ReadProgress event handler,
- /// and returns the instance.
- /// </summary>
- ///
- /// <param name="fileName">
- /// The name of the zip archive to open.
- /// This can be a fully-qualified or relative pathname.
- /// </param>
- ///
- /// <param name="readProgress">
- /// An event handler for Read operations.
- /// </param>
- ///
- /// <param name="statusMessageWriter">
- /// The <c>System.IO.TextWriter</c> to use for writing verbose status messages
- /// during operations on the zip archive. A console application may wish to
- /// pass <c>System.Console.Out</c> to get messages on the Console. A graphical
- /// or headless application may wish to capture the messages in a different
- /// <c>TextWriter</c>, such as a <c>System.IO.StringWriter</c>.
- /// </param>
- ///
- /// <param name="encoding">
- /// The <c>System.Text.Encoding</c> to use when reading in the zip archive. Be
- /// careful specifying the encoding. If the value you use here is not the same
- /// as the Encoding used when the zip archive was created (possibly by a
- /// different archiver) you will get unexpected results and possibly exceptions.
- /// </param>
- ///
- /// <returns>The instance read from the zip archive.</returns>
- ///
- private static ZipFile Read(string fileName,
- TextWriter statusMessageWriter,
- System.Text.Encoding encoding,
- EventHandler<ReadProgressEventArgs> readProgress)
- {
- ZipFile zf = new ZipFile();
- zf.AlternateEncoding = encoding ?? DefaultEncoding;
- zf.AlternateEncodingUsage = ZipOption.Always;
- zf._StatusMessageTextWriter = statusMessageWriter;
- zf._name = fileName;
- if (readProgress != null)
- zf.ReadProgress = readProgress;
- if (zf.Verbose) zf._StatusMessageTextWriter.WriteLine("reading from {0}...", fileName);
- ReadIntoInstance(zf);
- zf._fileAlreadyExists = true;
- return zf;
- }
- /// <summary>
- /// Reads a zip archive from a stream.
- /// </summary>
- ///
- /// <remarks>
- ///
- /// <para>
- /// When reading from a file, it's probably easier to just use
- /// <see cref="ZipFile.Read(String,
- /// ReadOptions)">ZipFile.Read(String, ReadOptions)</see>. This
- /// overload is useful when when the zip archive content is
- /// available from an already-open stream. The stream must be
- /// open and readable and seekable when calling this method. The
- /// stream is left open when the reading is completed.
- /// </para>
- ///
- /// <para>
- /// Using this overload, the stream is read using the default
- /// <c>System.Text.Encoding</c>, which is the <c>IBM437</c>
- /// codepage. If you want to specify the encoding to use when
- /// reading the zipfile content, see
- /// <see cref="ZipFile.Read(Stream,
- /// ReadOptions)">ZipFile.Read(Stream, ReadOptions)</see>. This
- /// </para>
- ///
- /// <para>
- /// Reading of zip content begins at the current position in the
- /// stream. This means if you have a stream that concatenates
- /// regular data and zip data, if you position the open, readable
- /// stream at the start of the zip data, you will be able to read
- /// the zip archive using this constructor, or any of the ZipFile
- /// constructors that accept a <see cref="System.IO.Stream" /> as
- /// input. Some examples of where this might be useful: the zip
- /// content is concatenated at the end of a regular EXE file, as
- /// some self-extracting archives do. (Note: SFX files produced
- /// by DotNetZip do not work this way; they can be read as normal
- /// ZIP files). Another example might be a stream being read from
- /// a database, where the zip content is embedded within an
- /// aggregate stream of data.
- /// </para>
- ///
- /// </remarks>
- ///
- /// <example>
- /// <para>
- /// This example shows how to Read zip content from a stream, and
- /// extract one entry into a different stream. In this example,
- /// the filename "NameOfEntryInArchive.doc", refers only to the
- /// name of the entry within the zip archive. A file by that
- /// name is not created in the filesystem. The I/O is done
- /// strictly with the given streams.
- /// </para>
- ///
- /// <code>
- /// using (ZipFile zip = ZipFile.Read(InputStream))
- /// {
- /// zip.Extract("NameOfEntryInArchive.doc", OutputStream);
- /// }
- /// </code>
- ///
- /// <code lang="VB">
- /// Using zip as ZipFile = ZipFile.Read(InputStream)
- /// zip.Extract("NameOfEntryInArchive.doc", OutputStream)
- /// End Using
- /// </code>
- /// </example>
- ///
- /// <param name="zipStream">the stream containing the zip data.</param>
- ///
- /// <returns>The ZipFile instance read from the stream</returns>
- ///
- public static ZipFile Read(Stream zipStream)
- {
- return Read(zipStream, null, null, null);
- }
- /// <summary>
- /// Reads a zip file archive from the given stream using the
- /// specified options.
- /// </summary>
- ///
- /// <remarks>
- ///
- /// <para>
- /// When reading from a file, it's probably easier to just use
- /// <see cref="ZipFile.Read(String,
- /// ReadOptions)">ZipFile.Read(String, ReadOptions)</see>. This
- /// overload is useful when when the zip archive content is
- /// available from an already-open stream. The stream must be
- /// open and readable and seekable when calling this method. The
- /// stream is left open when the reading is completed.
- /// </para>
- ///
- /// <para>
- /// Reading of zip content begins at the current position in the
- /// stream. This means if you have a stream that concatenates
- /// regular data and zip data, if you position the open, readable
- /// stream at the start of the zip data, you will be able to read
- /// the zip archive using this constructor, or any of the ZipFile
- /// constructors that accept a <see cref="System.IO.Stream" /> as
- /// input. Some examples of where this might be useful: the zip
- /// content is concatenated at the end of a regular EXE file, as
- /// some self-extracting archives do. (Note: SFX files produced
- /// by DotNetZip do not work this way; they can be read as normal
- /// ZIP files). Another example might be a stream being read from
- /// a database, where the zip content is embedded within an
- /// aggregate stream of data.
- /// </para>
- /// </remarks>
- ///
- /// <param name="zipStream">the stream containing the zip data.</param>
- ///
- /// <param name="options">
- /// The set of options to use when reading the zip file.
- /// </param>
- ///
- /// <exception cref="System.Exception">
- /// Thrown if the zip archive cannot be read.
- /// </exception>
- ///
- /// <returns>The ZipFile instance read from the stream.</returns>
- ///
- /// <seealso cref="ZipFile.Read(String, ReadOptions)"/>
- ///
- public static ZipFile Read(Stream zipStream, ReadOptions options)
- {
- if (options == null)
- throw new ArgumentNullException("options");
- return Read(zipStream,
- options.StatusMessageWriter,
- options.Encoding,
- options.ReadProgress);
- }
- /// <summary>
- /// Reads a zip archive from a stream, using the specified text Encoding, the
- /// specified TextWriter for status messages,
- /// and the specified ReadProgress event handler.
- /// </summary>
- ///
- /// <remarks>
- /// <para>
- /// Reading of zip content begins at the current position in the stream. This
- /// means if you have a stream that concatenates regular data and zip data, if
- /// you position the open, readable stream at the start of the zip data, you
- /// will be able to read the zip archive using this constructor, or any of the
- /// ZipFile constructors that accept a <see cref="System.IO.Stream" /> as
- /// input. Some examples of where this might be useful: the zip content is
- /// concatenated at the end of a regular EXE file, as some self-extracting
- /// archives do. (Note: SFX files produced by DotNetZip do not work this
- /// way). Another example might be a stream being read from a database, where
- /// the zip content is embedded within an aggregate stream of data.
- /// </para>
- /// </remarks>
- ///
- /// <param name="zipStream">the stream containing the zip data.</param>
- ///
- /// <param name="statusMessageWriter">
- /// The <c>System.IO.TextWriter</c> to which verbose status messages are written
- /// during operations on the <c>ZipFile</c>. For example, in a console
- /// application, System.Console.Out works, and will get a message for each entry
- /// added to the ZipFile. If the TextWriter is <c>null</c>, no verbose messages
- /// are written.
- /// </param>
- ///
- /// <param name="encoding">
- /// The text encoding to use when reading entries that do not have the UTF-8
- /// encoding bit set. Be careful specifying the encoding. If the value you use
- /// here is not the same as the Encoding used when the zip archive was created
- /// (possibly by a different archiver) you will get unexpected results and
- /// possibly exceptions. See the <see cref="ProvisionalAlternateEncoding"/>
- /// property for more information.
- /// </param>
- ///
- /// <param name="readProgress">
- /// An event handler for Read operations.
- /// </param>
- ///
- /// <returns>an instance of ZipFile</returns>
- private static ZipFile Read(Stream zipStream,
- TextWriter statusMessageWriter,
- System.Text.Encoding encoding,
- EventHandler<ReadProgressEventArgs> readProgress)
- {
- if (zipStream == null)
- throw new ArgumentNullException("zipStream");
- ZipFile zf = new ZipFile();
- zf._StatusMessageTextWriter = statusMessageWriter;
- zf._alternateEncoding = encoding ?? ZipFile.DefaultEncoding;
- zf._alternateEncodingUsage = ZipOption.Always;
- if (readProgress != null)
- zf.ReadProgress += readProgress;
- zf._readstream = (zipStream.Position == 0L)
- ? zipStream
- : new OffsetStream(zipStream);
- zf._ReadStreamIsOurs = false;
- if (zf.Verbose) zf._StatusMessageTextWriter.WriteLine("reading from stream...");
- ReadIntoInstance(zf);
- return zf;
- }
- private static void ReadIntoInstance(ZipFile zf)
- {
- Stream s = zf.ReadStream;
- try
- {
- zf._readName = zf._name; // workitem 13915
- if (!s.CanSeek)
- {
- ReadIntoInstance_Orig(zf);
- return;
- }
- zf.OnReadStarted();
- // change for workitem 8098
- //zf._originPosition = s.Position;
- // Try reading the central directory, rather than scanning the file.
- uint datum = ReadFirstFourBytes(s);
- if (datum == ZipConstants.EndOfCentralDirectorySignature)
- return;
- // start at the end of the file...
- // seek backwards a bit, then look for the EoCD signature.
- int nTries = 0;
- bool success = false;
- // The size of the end-of-central-directory-footer plus 2 bytes is 18.
- // This implies an archive comment length of 0. We'll add a margin of
- // safety and start "in front" of that, when looking for the
- // EndOfCentralDirectorySignature
- long posn = s.Length - 64;
- long maxSeekback = Math.Max(s.Length - 0x4000, 10);
- do
- {
- if (posn < 0) posn = 0; // BOF
- s.Seek(posn, SeekOrigin.Begin);
- long bytesRead = SharedUtilities.FindSignature(s, (int)ZipConstants.EndOfCentralDirectorySignature);
- if (bytesRead != -1)
- success = true;
- else
- {
- if (posn==0) break; // started at the BOF and found nothing
- nTries++;
- // Weird: with NETCF, negative offsets from SeekOrigin.End DO
- // NOT WORK. So rather than seek a negative offset, we seek
- // from SeekOrigin.Begin using a smaller number.
- posn -= (32 * (nTries + 1) * nTries);
- }
- }
- while (!success && posn > maxSeekback);
- if (success)
- {
- // workitem 8299
- zf._locEndOfCDS = s.Position - 4;
- byte[] block = new byte[16];
- s.Read(block, 0, block.Length);
- zf._diskNumberWithCd = BitConverter.ToUInt16(block, 2);
- if (zf._diskNumberWithCd == 0xFFFF)
- throw new ZipException("Spanned archives with more than 65534 segments are not supported at this time.");
- zf._diskNumberWithCd++; // I think the number in the file differs from reality by 1
- int i = 12;
- uint offset32 = (uint) BitConverter.ToUInt32(block, i);
- if (offset32 == 0xFFFFFFFF)
- {
- Zip64SeekToCentralDirectory(zf);
- }
- else
- {
- zf._OffsetOfCentralDirectory = offset32;
- // change for workitem 8098
- s.Seek(offset32, SeekOrigin.Begin);
- }
- ReadCentralDirectory(zf);
- }
- else
- {
- // Could not find the central directory.
- // Fallback to the old method.
- // workitem 8098: ok
- //s.Seek(zf._originPosition, SeekOrigin.Begin);
- s.Seek(0L, SeekOrigin.Begin);
- ReadIntoInstance_Orig(zf);
- }
- }
- catch (Exception ex1)
- {
- if (zf._ReadStreamIsOurs && zf._readstream != null)
- {
- try
- {
- #if NETCF
- zf._readstream.Close();
- #else
- zf._readstream.Dispose();
- #endif
- zf._readstream = null;
- }
- finally { }
- }
- throw new ZipException("Cannot read that as a ZipFile", ex1);
- }
- // the instance has been read in
- zf._contentsChanged = false;
- }
- private static void Zip64SeekToCentralDirectory(ZipFile zf)
- {
- Stream s = zf.ReadStream;
- byte[] block = new byte[16];
- // seek back to find the ZIP64 EoCD.
- // I think this might not work for .NET CF ?
- s.Seek(-40, SeekOrigin.Current);
- s.Read(block, 0, 16);
- Int64 offset64 = BitConverter.ToInt64(block, 8);
- zf._OffsetOfCentralDirectory = 0xFFFFFFFF;
- zf._OffsetOfCentralDirectory64 = offset64;
- // change for workitem 8098
- s.Seek(offset64, SeekOrigin.Begin);
- //zf.SeekFromOrigin(Offset64);
- uint datum = (uint)Ionic.Zip.SharedUtilities.ReadInt(s);
- if (datum != ZipConstants.Zip64EndOfCentralDirectoryRecordSignature)
- throw new BadReadException(String.Format(" Bad signature (0x{0:X8}) looking for ZIP64 EoCD Record at position 0x{1:X8}", datum, s.Position));
- s.Read(block, 0, 8);
- Int64 Size = BitConverter.ToInt64(block, 0);
- block = new byte[Size];
- s.Read(block, 0, block.Length);
- offset64 = BitConverter.ToInt64(block, 36);
- // change for workitem 8098
- s.Seek(offset64, SeekOrigin.Begin);
- //zf.SeekFromOrigin(Offset64);
- }
- private static uint ReadFirstFourBytes(Stream s)
- {
- uint datum = (uint)Ionic.Zip.SharedUtilities.ReadInt(s);
- return datum;
- }
- private static void ReadCentralDirectory(ZipFile zf)
- {
- // We must have the central directory footer record, in order to properly
- // read zip dir entries from the central directory. This because the logic
- // knows when to open a spanned file when the volume number for the central
- // directory differs from the volume number for the zip entry. The
- // _diskNumberWithCd was set when originally finding the offset for the
- // start of the Central Directory.
- // workitem 9214
- bool inputUsesZip64 = false;
- ZipEntry de;
- // in lieu of hashset, use a dictionary
- var previouslySeen = new Dictionary<String, object>(StringComparer.Ordinal);
- while ((de = ZipEntry.ReadDirEntry(zf, previouslySeen)) != null)
- {
- de.ResetDirEntry();
- zf.OnReadEntry(true, null);
- if (zf.Verbose)
- zf.StatusMessageTextWriter.WriteLine("entry {0}", de.FileName);
- zf._entries.Add(de.FileName,de);
- if (!zf._entriesInsensitive.ContainsKey(de.FileName))
- zf._entriesInsensitive.Add(de.FileName,de);
- // workitem 9214
- if (de._InputUsesZip64) inputUsesZip64 = true;
- previouslySeen.Add(de.FileName, null); // to prevent dupes
- }
- // workitem 9214; auto-set the zip64 flag
- if (inputUsesZip64) zf.UseZip64WhenSaving = Zip64Option.Always;
- // workitem 8299
- if (zf._locEndOfCDS > 0)
- zf.ReadStream.Seek(zf._locEndOfCDS, SeekOrigin.Begin);
- ReadCentralDirectoryFooter(zf);
- if (zf.Verbose && !String.IsNullOrEmpty(zf.Comment))
- zf.StatusMessageTextWriter.WriteLine("Zip file Comment: {0}", zf.Comment);
- // We keep the read stream open after reading.
- if (zf.Verbose)
- zf.StatusMessageTextWriter.WriteLine("read in {0} entries.", zf._entries.Count);
- zf.OnReadCompleted();
- }
- // build the TOC by reading each entry in the file.
- private static void ReadIntoInstance_Orig(ZipFile zf)
- {
- zf.OnReadStarted();
- zf._entries.Clear();
- zf._entriesInsensitive.Clear();
- ZipEntry e;
- if (zf.Verbose)
- if (zf.Name == null)
- zf.StatusMessageTextWriter.WriteLine("Reading zip from stream...");
- else
- zf.StatusMessageTextWriter.WriteLine("Reading zip {0}...", zf.Name);
- // work item 6647: PK00 (packed to removable disk)
- bool firstEntry = true;
- ZipContainer zc = new ZipContainer(zf);
- while ((e = ZipEntry.ReadEntry(zc, firstEntry)) != null)
- {
- if (zf.Verbose)
- zf.StatusMessageTextWriter.WriteLine(" {0}", e.FileName);
- zf._entries.Add(e.FileName,e);
- if (!zf._entriesInsensitive.ContainsKey(e.FileName))
- zf._entriesInsensitive.Add(e.FileName,e);
- firstEntry = false;
- }
- // read the zipfile's central directory structure here.
- // workitem 9912
- // But, because it may be corrupted, ignore errors.
- try
- {
- ZipEntry de;
- // in lieu of hashset, use a dictionary
- var previouslySeen = new Dictionary<String,Object>(StringComparer.Ordinal);
- while ((de = ZipEntry.ReadDirEntry(zf, previouslySeen)) != null)
- {
- // Housekeeping: Since ZipFile exposes ZipEntry elements in the enumerator,
- // we need to copy the comment that we grab from the ZipDirEntry
- // into the ZipEntry, so the application can access the comment.
- // Also since ZipEntry is used to Write zip files, we need to copy the
- // file attributes to the ZipEntry as appropriate.
- ZipEntry e1 = zf._entries[de.FileName];
- if (e1 != null)
- {
- e1._Comment = de.Comment;
- if (de.IsDirectory) e1.MarkAsDirectory();
- }
- previouslySeen.Add(de.FileName,null); // to prevent dupes
- }
- // workitem 8299
- if (zf._locEndOfCDS > 0)
- zf.ReadStream.Seek(zf._locEndOfCDS, SeekOrigin.Begin);
- ReadCentralDirectoryFooter(zf);
- if (zf.Verbose && !String.IsNullOrEmpty(zf.Comment))
- zf.StatusMessageTextWriter.WriteLine("Zip file Comment: {0}", zf.Comment);
- }
- catch (ZipException) { }
- catch (IOException) { }
- zf.OnReadCompleted();
- }
- private static void ReadCentralDirectoryFooter(ZipFile zf)
- {
- Stream s = zf.ReadStream;
- int signature = Ionic.Zip.SharedUtilities.ReadSignature(s);
- byte[] block = null;
- int j = 0;
- if (signature == ZipConstants.Zip64EndOfCentralDirectoryRecordSignature)
- {
- // We have a ZIP64 EOCD
- // This data block is 4 bytes sig, 8 bytes size, 44 bytes fixed data,
- // followed by a variable-sized extension block. We have read the sig already.
- // 8 - datasize (64 bits)
- // 2 - version made by
- // 2 - version needed to extract
- // 4 - number of this disk
- // 4 - number of the disk with the start of the CD
- // 8 - total number of entries in the CD on this disk
- // 8 - total number of entries in the CD
- // 8 - size of the CD
- // 8 - offset of the CD
- // -----------------------
- // 52 bytes
- block = new byte[8 + 44];
- s.Read(block, 0, block.Length);
- Int64 DataSize = BitConverter.ToInt64(block, 0); // == 44 + the variable length
- if (DataSize < 44)
- throw new ZipException("Bad size in the ZIP64 Central Directory.");
- zf._versionMadeBy = BitConverter.ToUInt16(block, j);
- j += 2;
- zf._versionNeededToExtract = BitConverter.ToUInt16(block, j);
- j += 2;
- zf._diskNumberWithCd = BitConverter.ToUInt32(block, j);
- j += 2;
- //zf._diskNumberWithCd++; // hack!!
- // read the extended block
- block = new byte[DataSize - 44];
- s.Read(block, 0, block.Length);
- // discard the result
- signature = Ionic.Zip.SharedUtilities.ReadSignature(s);
- if (signature != ZipConstants.Zip64EndOfCentralDirectoryLocatorSignature)
- throw new ZipException("Inconsistent metadata in the ZIP64 Central Directory.");
- block = new byte[16];
- s.Read(block, 0, block.Length);
- // discard the result
- signature = Ionic.Zip.SharedUtilities.ReadSignature(s);
- }
- // Throw if this is not a signature for "end of central directory record"
- // This is a sanity check.
- if (signature != ZipConstants.EndOfCentralDirectorySignature)
- {
- s.Seek(-4, SeekOrigin.Current);
- throw new BadReadException(String.Format("Bad signature ({0:X8}) at position 0x{1:X8}",
- signature, s.Position));
- }
- // read the End-of-Central-Directory-Record
- block = new byte[16];
- zf.ReadStream.Read(block, 0, block.Length);
- // off sz data
- // -------------------------------------------------------
- // 0 4 end of central dir signature (0x06054b50)
- // 4 2 number of this disk
- // 6 2 number of the disk with start of the central directory
- // 8 2 total number of entries in the central directory on this disk
- // 10 2 total number of entries in the central directory
- // 12 4 size of the central directory
- // 16 4 offset of start of central directory with respect to the starting disk number
- // 20 2 ZIP file comment length
- // 22 ?? ZIP file comment
- if (zf._diskNumberWithCd == 0)
- {
- zf._diskNumberWithCd = BitConverter.ToUInt16(block, 2);
- //zf._diskNumberWithCd++; // hack!!
- }
- // read the comment here
- ReadZipFileComment(zf);
- }
- private static void ReadZipFileComment(ZipFile zf)
- {
- // read the comment here
- byte[] block = new byte[2];
- zf.ReadStream.Read(block, 0, block.Length);
- Int16 commentLength = (short)(block[0] + block[1] * 256);
- if (commentLength > 0)
- {
- block = new byte[commentLength];
- zf.ReadStream.Read(block, 0, block.Length);
- // workitem 10392 - prefer ProvisionalAlternateEncoding,
- // first. The fix for workitem 6513 tried to use UTF8
- // only as necessary, but that is impossible to test
- // for, in this direction. There's no way to know what
- // characters the already-encoded bytes refer
- // to. Therefore, must do what the user tells us.
- string s1 = zf.AlternateEncoding.GetString(block, 0, block.Length);
- zf.Comment = s1;
- }
- }
- // private static bool BlocksAreEqual(byte[] a, byte[] b)
- // {
- // if (a.Length != b.Length) return false;
- // for (int i = 0; i < a.Length; i++)
- // {
- // if (a[i] != b[i]) return false;
- // }
- // return true;
- // }
- /// <summary>
- /// Checks the given file to see if it appears to be a valid zip file.
- /// </summary>
- /// <remarks>
- ///
- /// <para>
- /// Calling this method is equivalent to calling <see cref="IsZipFile(string,
- /// bool)"/> with the testExtract parameter set to false.
- /// </para>
- /// </remarks>
- ///
- /// <param name="fileName">The file to check.</param>
- /// <returns>true if the file appears to be a zip file.</returns>
- public static bool IsZipFile(string fileName)
- {
- return IsZipFile(fileName, false);
- }
- /// <summary>
- /// Checks a file to see if it is a valid zip file.
- /// </summary>
- ///
- /// <remarks>
- /// <para>
- /// This method opens the specified zip file, reads in the zip archive,
- /// verifying the ZIP metadata as it reads.
- /// </para>
- ///
- /// <para>
- /// If everything succeeds, then the method returns true. If anything fails -
- /// for example if an incorrect signature or CRC is found, indicating a
- /// corrupt file, the the method returns false. This method also returns
- /// false for a file that does not exist.
- /// </para>
- ///
- /// <para>
- /// If <paramref name="testExtract"/> is true, as part of its check, this
- /// method reads in the content for each entry, expands it, and checks CRCs.
- /// This provides an additional check beyond verifying the zip header and
- /// directory data.
- /// </para>
- ///
- /// <para>
- /// If <paramref name="testExtract"/> is true, and if any of the zip entries
- /// are protected with a password, this method will return false. If you want
- /// to verify a <c>ZipFile</c> that has entries which are protected with a
- /// password, you will need to do that manually.
- /// </para>
- ///
- /// </remarks>
- ///
- /// <param name="fileName">The zip file to check.</param>
- /// <param name="testExtract">true if the caller wants to extract each entry.</param>
- /// <returns>true if the file contains a valid zip file.</returns>
- public static bool IsZipFile(string fileName, bool testExtract)
- {
- bool result = false;
- try
- {
- if (!File.Exists(fileName)) return false;
- using (var s = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
- {
- result = IsZipFile(s, testExtract);
- }
- }
- catch (IOException) { }
- catch (ZipException) { }
- return result;
- }
- /// <summary>
- /// Checks a stream to see if it contains a valid zip archive.
- /// </summary>
- ///
- /// <remarks>
- /// <para>
- /// This method reads the zip archive contained in the specified stream, verifying
- /// the ZIP metadata as it reads. If testExtract is true, this method also extracts
- /// each entry in the archive, dumping all the bits into <see cref="Stream.Null"/>.
- /// </para>
- ///
- /// <para>
- /// If everything succeeds, then the method returns true. If anything fails -
- /// for example if an incorrect signature or CRC is found, indicating a corrupt
- /// file, the the method returns false. This method also returns false for a
- /// file that does not exist.
- /// </para>
- ///
- /// <para>
- /// If <c>testExtract</c> is true, this method reads in the content for each
- /// entry, expands it, and checks CRCs. This provides an additional check
- /// beyond verifying the zip header data.
- /// </para>
- ///
- /// <para>
- /// If <c>testExtract</c> is true, and if any of the zip entries are protected
- /// with a password, this method will return false. If you want to verify a
- /// ZipFile that has entries which are protected with a password, you will need
- /// to do that manually.
- /// </para>
- /// </remarks>
- ///
- /// <seealso cref="IsZipFile(string, bool)"/>
- ///
- /// <param name="stream">The stream to check.</param>
- /// <param name="testExtract">true if the caller wants to extract each entry.</param>
- /// <returns>true if the stream contains a valid zip archive.</returns>
- public static bool IsZipFile(Stream stream, bool testExtract)
- {
- if (stream == null)
- throw new ArgumentNullException("stream");
- bool result = false;
- try
- {
- if (!stream.CanRead) return false;
- var bitBucket = Stream.Null;
- using (ZipFile zip1 = ZipFile.Read(stream, null, null, null))
- {
- if (testExtract)
- {
- foreach (var e in zip1)
- {
- if (!e.IsDirectory)
- {
- e.Extract(bitBucket);
- }
- }
- }
- }
- result = true;
- }
- catch (IOException) { }
- catch (ZipException) { }
- return result;
- }
- }
- }
|