| // 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 OfficeOpenXml.Packaging.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> | |
| internal 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; } | |
| } | |
| internal 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)"/> | |
| /// | |
| internal 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)"/> | |
| /// | |
| internal 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>(); | |
| 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); | |
| // 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 = new System.Collections.Generic.List<ZipEntry>(); | |
| zf._entries = new System.Collections.Generic.Dictionary<String,ZipEntry>(); | |
| 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); | |
| 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>(); | |
| 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; | |
| } | |
| } | |
| } |