// 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 HH.WMS.Utils.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)HH.WMS.Utils.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)HH.WMS.Utils.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 = HH.WMS.Utils.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 = HH.WMS.Utils.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 = HH.WMS.Utils.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;
|
}
|
|
|
|
|
}
|
|
}
|