// 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
{
///
/// A class for collecting the various options that can be used when
/// Reading zip files for extraction or update.
///
///
///
///
/// 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.
///
///
///
/// Pass an instance of the ReadOptions class into the
/// ZipFile.Read() method.
///
///
/// .
/// .
///
internal class ReadOptions
{
///
/// 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 Read(), the progress event is invoked as
/// necessary.
///
public EventHandler ReadProgress { get; set; }
///
/// The System.IO.TextWriter to use for writing verbose status messages
/// during operations on the zip archive. A console application may wish to
/// pass System.Console.Out to get messages on the Console. A graphical
/// or headless application may wish to capture the messages in a different
/// TextWriter, such as a System.IO.StringWriter.
///
public TextWriter StatusMessageWriter { get; set; }
///
/// The System.Text.Encoding 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.
///
///
///
///
public System.Text.Encoding @Encoding { get; set; }
}
internal partial class ZipFile
{
///
/// Reads a zip file archive and returns the instance.
///
///
///
///
/// The stream is read using the default System.Text.Encoding, which is the
/// IBM437 codepage.
///
///
///
///
/// Thrown if the ZipFile cannot be read. The implementation of this method
/// relies on System.IO.File.OpenRead, 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.
///
///
///
/// The name of the zip archive to open. This can be a fully-qualified or relative
/// pathname.
///
///
/// .
///
/// The instance read from the zip archive.
///
public static ZipFile Read(string fileName)
{
return ZipFile.Read(fileName, null, null, null);
}
///
/// Reads a zip file archive from the named filesystem file using the
/// specified options.
///
///
///
///
/// This version of the Read() method allows the caller to pass
/// in a TextWriter an Encoding, via an instance of the
/// ReadOptions class. The ZipFile is read in using the
/// specified encoding for entries where UTF-8 encoding is not
/// explicitly specified.
///
///
///
///
///
///
/// 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.
///
///
///
/// 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.
///
///
///
/// 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);
/// }
/// }
///
///
///
///
/// 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
///
///
///
///
///
///
///
/// 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 StringWriter.
///
///
///
/// 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
///
///
///
/// 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
///
///
///
///
/// Thrown if the zipfile cannot be read. The implementation of
/// this method relies on System.IO.File.OpenRead, 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.
///
///
///
/// The name of the zip archive to open.
/// This can be a fully-qualified or relative pathname.
///
///
///
/// The set of options to use when reading the zip file.
///
///
/// The ZipFile instance read from the zip archive.
///
///
///
internal static ZipFile Read(string fileName,
ReadOptions options)
{
if (options == null)
throw new ArgumentNullException("options");
return Read(fileName,
options.StatusMessageWriter,
options.Encoding,
options.ReadProgress);
}
///
/// 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.
///
///
///
/// The name of the zip archive to open.
/// This can be a fully-qualified or relative pathname.
///
///
///
/// An event handler for Read operations.
///
///
///
/// The System.IO.TextWriter to use for writing verbose status messages
/// during operations on the zip archive. A console application may wish to
/// pass System.Console.Out to get messages on the Console. A graphical
/// or headless application may wish to capture the messages in a different
/// TextWriter, such as a System.IO.StringWriter.
///
///
///
/// The System.Text.Encoding 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.
///
///
/// The instance read from the zip archive.
///
private static ZipFile Read(string fileName,
TextWriter statusMessageWriter,
System.Text.Encoding encoding,
EventHandler 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;
}
///
/// Reads a zip archive from a stream.
///
///
///
///
///
/// When reading from a file, it's probably easier to just use
/// ZipFile.Read(String, ReadOptions). 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.
///
///
///
/// Using this overload, the stream is read using the default
/// System.Text.Encoding, which is the IBM437
/// codepage. If you want to specify the encoding to use when
/// reading the zipfile content, see
/// ZipFile.Read(Stream, ReadOptions). This
///
///
///
/// 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 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.
///
///
///
///
///
///
/// 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.
///
///
///
/// using (ZipFile zip = ZipFile.Read(InputStream))
/// {
/// zip.Extract("NameOfEntryInArchive.doc", OutputStream);
/// }
///
///
///
/// Using zip as ZipFile = ZipFile.Read(InputStream)
/// zip.Extract("NameOfEntryInArchive.doc", OutputStream)
/// End Using
///
///
///
/// the stream containing the zip data.
///
/// The ZipFile instance read from the stream
///
public static ZipFile Read(Stream zipStream)
{
return Read(zipStream, null, null, null);
}
///
/// Reads a zip file archive from the given stream using the
/// specified options.
///
///
///
///
///
/// When reading from a file, it's probably easier to just use
/// ZipFile.Read(String, ReadOptions). 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.
///
///
///
/// 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 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.
///
///
///
/// the stream containing the zip data.
///
///
/// The set of options to use when reading the zip file.
///
///
///
/// Thrown if the zip archive cannot be read.
///
///
/// The ZipFile instance read from the stream.
///
///
///
internal static ZipFile Read(Stream zipStream, ReadOptions options)
{
if (options == null)
throw new ArgumentNullException("options");
return Read(zipStream,
options.StatusMessageWriter,
options.Encoding,
options.ReadProgress);
}
///
/// Reads a zip archive from a stream, using the specified text Encoding, the
/// specified TextWriter for status messages,
/// and the specified ReadProgress event handler.
///
///
///
///
/// 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 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.
///
///
///
/// the stream containing the zip data.
///
///
/// The System.IO.TextWriter to which verbose status messages are written
/// during operations on the ZipFile. 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 null, no verbose messages
/// are written.
///
///
///
/// 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
/// property for more information.
///
///
///
/// An event handler for Read operations.
///
///
/// an instance of ZipFile
private static ZipFile Read(Stream zipStream,
TextWriter statusMessageWriter,
System.Text.Encoding encoding,
EventHandler 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();
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();
zf._entries = new System.Collections.Generic.Dictionary();
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();
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;
// }
///
/// Checks the given file to see if it appears to be a valid zip file.
///
///
///
///
/// Calling this method is equivalent to calling with the testExtract parameter set to false.
///
///
///
/// The file to check.
/// true if the file appears to be a zip file.
public static bool IsZipFile(string fileName)
{
return IsZipFile(fileName, false);
}
///
/// Checks a file to see if it is a valid zip file.
///
///
///
///
/// This method opens the specified zip file, reads in the zip archive,
/// verifying the ZIP metadata as it reads.
///
///
///
/// 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.
///
///
///
/// If 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.
///
///
///
/// If 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.
///
///
///
///
/// The zip file to check.
/// true if the caller wants to extract each entry.
/// true if the file contains a valid zip file.
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;
}
///
/// Checks a stream to see if it contains a valid zip archive.
///
///
///
///
/// 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 .
///
///
///
/// 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.
///
///
///
/// If testExtract 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.
///
///
///
/// If 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
/// ZipFile that has entries which are protected with a password, you will need
/// to do that manually.
///
///
///
///
///
/// The stream to check.
/// true if the caller wants to extract each entry.
/// true if the stream contains a valid zip archive.
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;
}
}
}