// 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; } } }