|  | // ZipEntry.Extract.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-06 18:08:21> | 
|  | // | 
|  | // ------------------------------------------------------------------ | 
|  | // | 
|  | // This module defines logic for Extract methods on the ZipEntry class. | 
|  | // | 
|  | // ------------------------------------------------------------------ | 
|  |  | 
|  |  | 
|  | using System; | 
|  | using System.IO; | 
|  |  | 
|  | namespace OfficeOpenXml.Packaging.Ionic.Zip | 
|  | { | 
|  |  | 
|  | internal partial class ZipEntry | 
|  | { | 
|  | /// <summary> | 
|  | ///   Extract the entry to the filesystem, starting at the current | 
|  | ///   working directory. | 
|  | /// </summary> | 
|  | /// | 
|  | /// <overloads> | 
|  | ///   This method has a bunch of overloads! One of them is sure to | 
|  | ///   be the right one for you... If you don't like these, check | 
|  | ///   out the <c>ExtractWithPassword()</c> methods. | 
|  | /// </overloads> | 
|  | /// | 
|  | /// <seealso cref="ZipEntry.ExtractExistingFile"/> | 
|  | /// <seealso cref="ZipEntry.Extract(ExtractExistingFileAction)"/> | 
|  | /// | 
|  | /// <remarks> | 
|  | /// | 
|  | /// <para> | 
|  | ///   This method extracts an entry from a zip file into the current | 
|  | ///   working directory.  The path of the entry as extracted is the full | 
|  | ///   path as specified in the zip archive, relative to the current | 
|  | ///   working directory.  After the file is extracted successfully, the | 
|  | ///   file attributes and timestamps are set. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   The action taken when extraction an entry would overwrite an | 
|  | ///   existing file is determined by the <see cref="ExtractExistingFile" | 
|  | ///   /> property. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   Within the call to <c>Extract()</c>, the content for the entry is | 
|  | ///   written into a filesystem file, and then the last modified time of the | 
|  | ///   file is set according to the <see cref="LastModified"/> property on | 
|  | ///   the entry. See the remarks the <see cref="LastModified"/> property for | 
|  | ///   some details about the last modified time. | 
|  | /// </para> | 
|  | /// | 
|  | /// </remarks> | 
|  | internal void Extract() | 
|  | { | 
|  | InternalExtract(".", null, null); | 
|  | } | 
|  |  | 
|  |  | 
|  | /// <summary> | 
|  | ///   Extract the entry to a file in the filesystem, using the specified | 
|  | ///   behavior when extraction would overwrite an existing file. | 
|  | /// </summary> | 
|  | /// | 
|  | /// <remarks> | 
|  | /// <para> | 
|  | ///   See the remarks on the <see cref="LastModified"/> property, for some | 
|  | ///   details about how the last modified time of the file is set after | 
|  | ///   extraction. | 
|  | /// </para> | 
|  | /// </remarks> | 
|  | /// | 
|  | /// <param name="extractExistingFile"> | 
|  | ///   The action to take if extraction would overwrite an existing file. | 
|  | /// </param> | 
|  | internal void Extract(ExtractExistingFileAction extractExistingFile) | 
|  | { | 
|  | ExtractExistingFile = extractExistingFile; | 
|  | InternalExtract(".", null, null); | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | ///   Extracts the entry to the specified stream. | 
|  | /// </summary> | 
|  | /// | 
|  | /// <remarks> | 
|  | /// <para> | 
|  | ///   The caller can specify any write-able stream, for example a <see | 
|  | ///   cref="System.IO.FileStream"/>, a <see | 
|  | ///   cref="System.IO.MemoryStream"/>, or ASP.NET's | 
|  | ///   <c>Response.OutputStream</c>.  The content will be decrypted and | 
|  | ///   decompressed as necessary. If the entry is encrypted and no password | 
|  | ///   is provided, this method will throw. | 
|  | /// </para> | 
|  | /// <para> | 
|  | ///   The position on the stream is not reset by this method before it extracts. | 
|  | ///   You may want to call stream.Seek() before calling ZipEntry.Extract(). | 
|  | /// </para> | 
|  | /// </remarks> | 
|  | /// | 
|  | /// <param name="stream"> | 
|  | ///   the stream to which the entry should be extracted. | 
|  | /// </param> | 
|  | /// | 
|  | public void Extract(Stream stream) | 
|  | { | 
|  | InternalExtract(null, stream, null); | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | ///   Extract the entry to the filesystem, starting at the specified base | 
|  | ///   directory. | 
|  | /// </summary> | 
|  | /// | 
|  | /// <param name="baseDirectory">the pathname of the base directory</param> | 
|  | /// | 
|  | /// <seealso cref="ZipEntry.ExtractExistingFile"/> | 
|  | /// <seealso cref="ZipEntry.Extract(string, ExtractExistingFileAction)"/> | 
|  | /// | 
|  | /// <example> | 
|  | /// This example extracts only the entries in a zip file that are .txt files, | 
|  | /// into a directory called "textfiles". | 
|  | /// <code lang="C#"> | 
|  | /// using (ZipFile zip = ZipFile.Read("PackedDocuments.zip")) | 
|  | /// { | 
|  | ///   foreach (string s1 in zip.EntryFilenames) | 
|  | ///   { | 
|  | ///     if (s1.EndsWith(".txt")) | 
|  | ///     { | 
|  | ///       zip[s1].Extract("textfiles"); | 
|  | ///     } | 
|  | ///   } | 
|  | /// } | 
|  | /// </code> | 
|  | /// <code lang="VB"> | 
|  | ///   Using zip As ZipFile = ZipFile.Read("PackedDocuments.zip") | 
|  | ///       Dim s1 As String | 
|  | ///       For Each s1 In zip.EntryFilenames | 
|  | ///           If s1.EndsWith(".txt") Then | 
|  | ///               zip(s1).Extract("textfiles") | 
|  | ///           End If | 
|  | ///       Next | 
|  | ///   End Using | 
|  | /// </code> | 
|  | /// </example> | 
|  | /// | 
|  | /// <remarks> | 
|  | /// | 
|  | /// <para> | 
|  | ///   Using this method, existing entries in the filesystem will not be | 
|  | ///   overwritten. If you would like to force the overwrite of existing | 
|  | ///   files, see the <see cref="ExtractExistingFile"/> property, or call | 
|  | ///   <see cref="Extract(string, ExtractExistingFileAction)"/>. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   See the remarks on the <see cref="LastModified"/> property, for some | 
|  | ///   details about how the last modified time of the created file is set. | 
|  | /// </para> | 
|  | /// </remarks> | 
|  | public void Extract(string baseDirectory) | 
|  | { | 
|  | InternalExtract(baseDirectory, null, null); | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  | /// <summary> | 
|  | ///   Extract the entry to the filesystem, starting at the specified base | 
|  | ///   directory, and using the specified behavior when extraction would | 
|  | ///   overwrite an existing file. | 
|  | /// </summary> | 
|  | /// | 
|  | /// <remarks> | 
|  | /// <para> | 
|  | ///   See the remarks on the <see cref="LastModified"/> property, for some | 
|  | ///   details about how the last modified time of the created file is set. | 
|  | /// </para> | 
|  | /// </remarks> | 
|  | /// | 
|  | /// <example> | 
|  | /// <code lang="C#"> | 
|  | /// String sZipPath = "Airborne.zip"; | 
|  | /// String sFilePath = "Readme.txt"; | 
|  | /// String sRootFolder = "Digado"; | 
|  | /// using (ZipFile zip = ZipFile.Read(sZipPath)) | 
|  | /// { | 
|  | ///   if (zip.EntryFileNames.Contains(sFilePath)) | 
|  | ///   { | 
|  | ///     // use the string indexer on the zip file | 
|  | ///     zip[sFileName].Extract(sRootFolder, | 
|  | ///                            ExtractExistingFileAction.OverwriteSilently); | 
|  | ///   } | 
|  | /// } | 
|  | /// </code> | 
|  | /// | 
|  | /// <code lang="VB"> | 
|  | /// Dim sZipPath as String = "Airborne.zip" | 
|  | /// Dim sFilePath As String = "Readme.txt" | 
|  | /// Dim sRootFolder As String = "Digado" | 
|  | /// Using zip As ZipFile = ZipFile.Read(sZipPath) | 
|  | ///   If zip.EntryFileNames.Contains(sFilePath) | 
|  | ///     ' use the string indexer on the zip file | 
|  | ///     zip(sFilePath).Extract(sRootFolder, _ | 
|  | ///                            ExtractExistingFileAction.OverwriteSilently) | 
|  | ///   End If | 
|  | /// End Using | 
|  | /// </code> | 
|  | /// </example> | 
|  | /// | 
|  | /// <param name="baseDirectory">the pathname of the base directory</param> | 
|  | /// <param name="extractExistingFile"> | 
|  | /// The action to take if extraction would overwrite an existing file. | 
|  | /// </param> | 
|  | internal void Extract(string baseDirectory, ExtractExistingFileAction extractExistingFile) | 
|  | { | 
|  | ExtractExistingFile = extractExistingFile; | 
|  | InternalExtract(baseDirectory, null, null); | 
|  | } | 
|  |  | 
|  |  | 
|  | /// <summary> | 
|  | ///   Extract the entry to the filesystem, using the current working directory | 
|  | ///   and the specified password. | 
|  | /// </summary> | 
|  | /// | 
|  | /// <overloads> | 
|  | ///   This method has a bunch of overloads! One of them is sure to be | 
|  | ///   the right one for you... | 
|  | /// </overloads> | 
|  | /// | 
|  | /// <seealso cref="ZipEntry.ExtractExistingFile"/> | 
|  | /// <seealso cref="ZipEntry.ExtractWithPassword(ExtractExistingFileAction, string)"/> | 
|  | /// | 
|  | /// <remarks> | 
|  | /// | 
|  | /// <para> | 
|  | ///   Existing entries in the filesystem will not be overwritten. If you | 
|  | ///   would like to force the overwrite of existing files, see the <see | 
|  | ///   cref="ZipEntry.ExtractExistingFile"/>property, or call | 
|  | ///   <see | 
|  | ///   cref="ExtractWithPassword(ExtractExistingFileAction,string)"/>. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   See the remarks on the <see cref="LastModified"/> property for some | 
|  | ///   details about how the "last modified" time of the created file is | 
|  | ///   set. | 
|  | /// </para> | 
|  | /// </remarks> | 
|  | /// | 
|  | /// <example> | 
|  | ///   In this example, entries that use encryption are extracted using a | 
|  | ///   particular password. | 
|  | /// <code> | 
|  | /// using (var zip = ZipFile.Read(FilePath)) | 
|  | /// { | 
|  | ///     foreach (ZipEntry e in zip) | 
|  | ///     { | 
|  | ///         if (e.UsesEncryption) | 
|  | ///             e.ExtractWithPassword("Secret!"); | 
|  | ///         else | 
|  | ///             e.Extract(); | 
|  | ///     } | 
|  | /// } | 
|  | /// </code> | 
|  | /// <code lang="VB"> | 
|  | /// Using zip As ZipFile = ZipFile.Read(FilePath) | 
|  | ///     Dim e As ZipEntry | 
|  | ///     For Each e In zip | 
|  | ///         If (e.UsesEncryption) | 
|  | ///           e.ExtractWithPassword("Secret!") | 
|  | ///         Else | 
|  | ///           e.Extract | 
|  | ///         End If | 
|  | ///     Next | 
|  | /// End Using | 
|  | /// </code> | 
|  | /// </example> | 
|  | /// <param name="password">The Password to use for decrypting the entry.</param> | 
|  | public void ExtractWithPassword(string password) | 
|  | { | 
|  | InternalExtract(".", null, password); | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | ///   Extract the entry to the filesystem, starting at the specified base | 
|  | ///   directory, and using the specified password. | 
|  | /// </summary> | 
|  | /// | 
|  | /// <seealso cref="ZipEntry.ExtractExistingFile"/> | 
|  | /// <seealso cref="ZipEntry.ExtractWithPassword(string, ExtractExistingFileAction, string)"/> | 
|  | /// | 
|  | /// <remarks> | 
|  | /// <para> | 
|  | ///   Existing entries in the filesystem will not be overwritten. If you | 
|  | ///   would like to force the overwrite of existing files, see the <see | 
|  | ///   cref="ZipEntry.ExtractExistingFile"/>property, or call | 
|  | ///   <see | 
|  | ///   cref="ExtractWithPassword(ExtractExistingFileAction,string)"/>. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   See the remarks on the <see cref="LastModified"/> property, for some | 
|  | ///   details about how the last modified time of the created file is set. | 
|  | /// </para> | 
|  | /// </remarks> | 
|  | /// | 
|  | /// <param name="baseDirectory">The pathname of the base directory.</param> | 
|  | /// <param name="password">The Password to use for decrypting the entry.</param> | 
|  | public void ExtractWithPassword(string baseDirectory, string password) | 
|  | { | 
|  | InternalExtract(baseDirectory, null, password); | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  | /// <summary> | 
|  | ///   Extract the entry to a file in the filesystem, relative to the | 
|  | ///   current directory, using the specified behavior when extraction | 
|  | ///   would overwrite an existing file. | 
|  | /// </summary> | 
|  | /// | 
|  | /// <remarks> | 
|  | /// <para> | 
|  | ///   See the remarks on the <see cref="LastModified"/> property, for some | 
|  | ///   details about how the last modified time of the created file is set. | 
|  | /// </para> | 
|  | /// </remarks> | 
|  | /// | 
|  | /// <param name="password">The Password to use for decrypting the entry.</param> | 
|  | /// | 
|  | /// <param name="extractExistingFile"> | 
|  | /// The action to take if extraction would overwrite an existing file. | 
|  | /// </param> | 
|  | internal void ExtractWithPassword(ExtractExistingFileAction extractExistingFile, string password) | 
|  | { | 
|  | ExtractExistingFile = extractExistingFile; | 
|  | InternalExtract(".", null, password); | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /// <summary> | 
|  | ///   Extract the entry to the filesystem, starting at the specified base | 
|  | ///   directory, and using the specified behavior when extraction would | 
|  | ///   overwrite an existing file. | 
|  | /// </summary> | 
|  | /// | 
|  | /// <remarks> | 
|  | ///   See the remarks on the <see cref="LastModified"/> property, for some | 
|  | ///   details about how the last modified time of the created file is set. | 
|  | /// </remarks> | 
|  | /// | 
|  | /// <param name="baseDirectory">the pathname of the base directory</param> | 
|  | /// | 
|  | /// <param name="extractExistingFile">The action to take if extraction would | 
|  | /// overwrite an existing file.</param> | 
|  | /// | 
|  | /// <param name="password">The Password to use for decrypting the entry.</param> | 
|  | internal void ExtractWithPassword(string baseDirectory, ExtractExistingFileAction extractExistingFile, string password) | 
|  | { | 
|  | ExtractExistingFile = extractExistingFile; | 
|  | InternalExtract(baseDirectory, null, password); | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | ///   Extracts the entry to the specified stream, using the specified | 
|  | ///   Password.  For example, the caller could extract to Console.Out, or | 
|  | ///   to a MemoryStream. | 
|  | /// </summary> | 
|  | /// | 
|  | /// <remarks> | 
|  | /// <para> | 
|  | ///   The caller can specify any write-able stream, for example a <see | 
|  | ///   cref="System.IO.FileStream"/>, a <see | 
|  | ///   cref="System.IO.MemoryStream"/>, or ASP.NET's | 
|  | ///   <c>Response.OutputStream</c>.  The content will be decrypted and | 
|  | ///   decompressed as necessary. If the entry is encrypted and no password | 
|  | ///   is provided, this method will throw. | 
|  | /// </para> | 
|  | /// <para> | 
|  | ///   The position on the stream is not reset by this method before it extracts. | 
|  | ///   You may want to call stream.Seek() before calling ZipEntry.Extract(). | 
|  | /// </para> | 
|  | /// </remarks> | 
|  | /// | 
|  | /// | 
|  | /// <param name="stream"> | 
|  | ///   the stream to which the entry should be extracted. | 
|  | /// </param> | 
|  | /// <param name="password"> | 
|  | ///   The password to use for decrypting the entry. | 
|  | /// </param> | 
|  | public void ExtractWithPassword(Stream stream, string password) | 
|  | { | 
|  | InternalExtract(null, stream, password); | 
|  | } | 
|  |  | 
|  |  | 
|  | /// <summary> | 
|  | ///   Opens a readable stream corresponding to the zip entry in the | 
|  | ///   archive.  The stream decompresses and decrypts as necessary, as it | 
|  | ///   is read. | 
|  | /// </summary> | 
|  | /// | 
|  | /// <remarks> | 
|  | /// | 
|  | /// <para> | 
|  | ///   DotNetZip offers a variety of ways to extract entries from a zip | 
|  | ///   file.  This method allows an application to extract an entry by | 
|  | ///   reading a <see cref="System.IO.Stream"/>. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   The return value is of type <see | 
|  | ///   cref="Ionic.Crc.CrcCalculatorStream"/>.  Use it as you would any | 
|  | ///   stream for reading.  When an application calls <see | 
|  | ///   cref="Stream.Read(byte[], int, int)"/> on that stream, it will | 
|  | ///   receive data from the zip entry that is decrypted and decompressed | 
|  | ///   as necessary. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   <c>CrcCalculatorStream</c> adds one additional feature: it keeps a | 
|  | ///   CRC32 checksum on the bytes of the stream as it is read.  The CRC | 
|  | ///   value is available in the <see | 
|  | ///   cref="Ionic.Crc.CrcCalculatorStream.Crc"/> property on the | 
|  | ///   <c>CrcCalculatorStream</c>.  When the read is complete, your | 
|  | ///   application | 
|  | ///   <em>should</em> check this CRC against the <see cref="ZipEntry.Crc"/> | 
|  | ///   property on the <c>ZipEntry</c> to validate the content of the | 
|  | ///   ZipEntry. You don't have to validate the entry using the CRC, but | 
|  | ///   you should, to verify integrity. Check the example for how to do | 
|  | ///   this. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   If the entry is protected with a password, then you need to provide | 
|  | ///   a password prior to calling <see cref="OpenReader()"/>, either by | 
|  | ///   setting the <see cref="Password"/> property on the entry, or the | 
|  | ///   <see cref="ZipFile.Password"/> property on the <c>ZipFile</c> | 
|  | ///   itself. Or, you can use <see cref="OpenReader(String)" />, the | 
|  | ///   overload of OpenReader that accepts a password parameter. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   If you want to extract entry data into a write-able stream that is | 
|  | ///   already opened, like a <see cref="System.IO.FileStream"/>, do not | 
|  | ///   use this method. Instead, use <see cref="Extract(Stream)"/>. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   Your application may use only one stream created by OpenReader() at | 
|  | ///   a time, and you should not call other Extract methods before | 
|  | ///   completing your reads on a stream obtained from OpenReader().  This | 
|  | ///   is because there is really only one source stream for the compressed | 
|  | ///   content.  A call to OpenReader() seeks in the source stream, to the | 
|  | ///   beginning of the compressed content.  A subsequent call to | 
|  | ///   OpenReader() on a different entry will seek to a different position | 
|  | ///   in the source stream, as will a call to Extract() or one of its | 
|  | ///   overloads.  This will corrupt the state for the decompressing stream | 
|  | ///   from the original call to OpenReader(). | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///    The <c>OpenReader()</c> method works only when the ZipEntry is | 
|  | ///    obtained from an instance of <c>ZipFile</c>. This method will throw | 
|  | ///    an exception if the ZipEntry is obtained from a ZipInputStream. | 
|  | /// </para> | 
|  | /// </remarks> | 
|  | /// | 
|  | /// <example> | 
|  | ///   This example shows how to open a zip archive, then read in a named | 
|  | ///   entry via a stream. After the read loop is complete, the code | 
|  | ///   compares the calculated during the read loop with the expected CRC | 
|  | ///   on the <c>ZipEntry</c>, to verify the extraction. | 
|  | /// <code> | 
|  | /// using (ZipFile zip = new ZipFile(ZipFileToRead)) | 
|  | /// { | 
|  | ///   ZipEntry e1= zip["Elevation.mp3"]; | 
|  | ///   using (Ionic.Zlib.CrcCalculatorStream s = e1.OpenReader()) | 
|  | ///   { | 
|  | ///     byte[] buffer = new byte[4096]; | 
|  | ///     int n, totalBytesRead= 0; | 
|  | ///     do { | 
|  | ///       n = s.Read(buffer,0, buffer.Length); | 
|  | ///       totalBytesRead+=n; | 
|  | ///     } while (n>0); | 
|  | ///      if (s.Crc32 != e1.Crc32) | 
|  | ///       throw new Exception(string.Format("The Zip Entry failed the CRC Check. (0x{0:X8}!=0x{1:X8})", s.Crc32, e1.Crc32)); | 
|  | ///      if (totalBytesRead != e1.UncompressedSize) | 
|  | ///       throw new Exception(string.Format("We read an unexpected number of bytes. ({0}!={1})", totalBytesRead, e1.UncompressedSize)); | 
|  | ///   } | 
|  | /// } | 
|  | /// </code> | 
|  | /// <code lang="VB"> | 
|  | ///   Using zip As New ZipFile(ZipFileToRead) | 
|  | ///       Dim e1 As ZipEntry = zip.Item("Elevation.mp3") | 
|  | ///       Using s As Ionic.Zlib.CrcCalculatorStream = e1.OpenReader | 
|  | ///           Dim n As Integer | 
|  | ///           Dim buffer As Byte() = New Byte(4096) {} | 
|  | ///           Dim totalBytesRead As Integer = 0 | 
|  | ///           Do | 
|  | ///               n = s.Read(buffer, 0, buffer.Length) | 
|  | ///               totalBytesRead = (totalBytesRead + n) | 
|  | ///           Loop While (n > 0) | 
|  | ///           If (s.Crc32 <> e1.Crc32) Then | 
|  | ///               Throw New Exception(String.Format("The Zip Entry failed the CRC Check. (0x{0:X8}!=0x{1:X8})", s.Crc32, e1.Crc32)) | 
|  | ///           End If | 
|  | ///           If (totalBytesRead <> e1.UncompressedSize) Then | 
|  | ///               Throw New Exception(String.Format("We read an unexpected number of bytes. ({0}!={1})", totalBytesRead, e1.UncompressedSize)) | 
|  | ///           End If | 
|  | ///       End Using | 
|  | ///   End Using | 
|  | /// </code> | 
|  | /// </example> | 
|  | /// <seealso cref="ZipEntry.Extract(System.IO.Stream)"/> | 
|  | /// <returns>The Stream for reading.</returns> | 
|  | internal Ionic.Crc.CrcCalculatorStream OpenReader() | 
|  | { | 
|  | // workitem 10923 | 
|  | if (_container.ZipFile == null) | 
|  | throw new InvalidOperationException("Use OpenReader() only with ZipFile."); | 
|  |  | 
|  | // use the entry password if it is non-null, | 
|  | // else use the zipfile password, which is possibly null | 
|  | return InternalOpenReader(this._Password ?? this._container.Password); | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | ///   Opens a readable stream for an encrypted zip entry in the archive. | 
|  | ///   The stream decompresses and decrypts as necessary, as it is read. | 
|  | /// </summary> | 
|  | /// | 
|  | /// <remarks> | 
|  | /// <para> | 
|  | ///   See the documentation on the <see cref="OpenReader()"/> method for | 
|  | ///   full details. This overload allows the application to specify a | 
|  | ///   password for the <c>ZipEntry</c> to be read. | 
|  | /// </para> | 
|  | /// </remarks> | 
|  | /// | 
|  | /// <param name="password">The password to use for decrypting the entry.</param> | 
|  | /// <returns>The Stream for reading.</returns> | 
|  | internal Ionic.Crc.CrcCalculatorStream OpenReader(string password) | 
|  | { | 
|  | // workitem 10923 | 
|  | if (_container.ZipFile == null) | 
|  | throw new InvalidOperationException("Use OpenReader() only with ZipFile."); | 
|  |  | 
|  | return InternalOpenReader(password); | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | internal Ionic.Crc.CrcCalculatorStream InternalOpenReader(string password) | 
|  | { | 
|  | ValidateCompression(); | 
|  | ValidateEncryption(); | 
|  | SetupCryptoForExtract(password); | 
|  |  | 
|  | // workitem 7958 | 
|  | if (this._Source != ZipEntrySource.ZipFile) | 
|  | throw new BadStateException("You must call ZipFile.Save before calling OpenReader"); | 
|  |  | 
|  | // LeftToRead is a count of bytes remaining to be read (out) | 
|  | // from the stream AFTER decompression and decryption. | 
|  | // It is the uncompressed size, unless ... there is no compression in which | 
|  | // case ...?  :< I'm not sure why it's not always UncompressedSize | 
|  | Int64 LeftToRead = (_CompressionMethod_FromZipFile == (short)CompressionMethod.None) | 
|  | ? this._CompressedFileDataSize | 
|  | : this.UncompressedSize; | 
|  |  | 
|  | Stream input = this.ArchiveStream; | 
|  |  | 
|  | this.ArchiveStream.Seek(this.FileDataPosition, SeekOrigin.Begin); | 
|  | // workitem 10178 | 
|  | Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(this.ArchiveStream); | 
|  |  | 
|  | _inputDecryptorStream = GetExtractDecryptor(input); | 
|  | Stream input3 = GetExtractDecompressor(_inputDecryptorStream); | 
|  |  | 
|  | return new Ionic.Crc.CrcCalculatorStream(input3, LeftToRead); | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | private void OnExtractProgress(Int64 bytesWritten, Int64 totalBytesToWrite) | 
|  | { | 
|  | if (_container.ZipFile != null) | 
|  | _ioOperationCanceled = _container.ZipFile.OnExtractBlock(this, bytesWritten, totalBytesToWrite); | 
|  | } | 
|  |  | 
|  |  | 
|  | private void OnBeforeExtract(string path) | 
|  | { | 
|  | // When in the context of a ZipFile.ExtractAll, the events are generated from | 
|  | // the ZipFile method, not from within the ZipEntry instance. (why?) | 
|  | // Therefore we suppress the events originating from the ZipEntry method. | 
|  | if (_container.ZipFile != null) | 
|  | { | 
|  | if (!_container.ZipFile._inExtractAll) | 
|  | { | 
|  | _ioOperationCanceled = _container.ZipFile.OnSingleEntryExtract(this, path, true); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private void OnAfterExtract(string path) | 
|  | { | 
|  | // When in the context of a ZipFile.ExtractAll, the events are generated from | 
|  | // the ZipFile method, not from within the ZipEntry instance. (why?) | 
|  | // Therefore we suppress the events originating from the ZipEntry method. | 
|  | if (_container.ZipFile != null) | 
|  | { | 
|  | if (!_container.ZipFile._inExtractAll) | 
|  | { | 
|  | _container.ZipFile.OnSingleEntryExtract(this, path, false); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private void OnExtractExisting(string path) | 
|  | { | 
|  | if (_container.ZipFile != null) | 
|  | _ioOperationCanceled = _container.ZipFile.OnExtractExisting(this, path); | 
|  | } | 
|  |  | 
|  | private static void ReallyDelete(string fileName) | 
|  | { | 
|  | // workitem 7881 | 
|  | // reset ReadOnly bit if necessary | 
|  | #if NETCF | 
|  | if ( (NetCfFile.GetAttributes(fileName) & (uint)FileAttributes.ReadOnly) == (uint)FileAttributes.ReadOnly) | 
|  | NetCfFile.SetAttributes(fileName, (uint)FileAttributes.Normal); | 
|  | #elif SILVERLIGHT | 
|  | #else | 
|  | if ((File.GetAttributes(fileName) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) | 
|  | File.SetAttributes(fileName, FileAttributes.Normal); | 
|  | #endif | 
|  | File.Delete(fileName); | 
|  | } | 
|  |  | 
|  |  | 
|  | private void WriteStatus(string format, params Object[] args) | 
|  | { | 
|  | if (_container.ZipFile != null && _container.ZipFile.Verbose) _container.ZipFile.StatusMessageTextWriter.WriteLine(format, args); | 
|  | } | 
|  |  | 
|  |  | 
|  | // Pass in either basedir or s, but not both. | 
|  | // In other words, you can extract to a stream or to a directory (filesystem), but not both! | 
|  | // The Password param is required for encrypted entries. | 
|  | private void InternalExtract(string baseDir, Stream outstream, string password) | 
|  | { | 
|  | // workitem 7958 | 
|  | if (_container == null) | 
|  | throw new BadStateException("This entry is an orphan"); | 
|  |  | 
|  | // workitem 10355 | 
|  | if (_container.ZipFile == null) | 
|  | throw new InvalidOperationException("Use Extract() only with ZipFile."); | 
|  |  | 
|  | _container.ZipFile.Reset(false); | 
|  |  | 
|  | if (this._Source != ZipEntrySource.ZipFile) | 
|  | throw new BadStateException("You must call ZipFile.Save before calling any Extract method"); | 
|  |  | 
|  | OnBeforeExtract(baseDir); | 
|  | _ioOperationCanceled = false; | 
|  | string targetFileName = null; | 
|  | Stream output = null; | 
|  | bool fileExistsBeforeExtraction = false; | 
|  | bool checkLaterForResetDirTimes = false; | 
|  | try | 
|  | { | 
|  | ValidateCompression(); | 
|  | ValidateEncryption(); | 
|  |  | 
|  | if (ValidateOutput(baseDir, outstream, out targetFileName)) | 
|  | { | 
|  | WriteStatus("extract dir {0}...", targetFileName); | 
|  | // if true, then the entry was a directory and has been created. | 
|  | // We need to fire the Extract Event. | 
|  | OnAfterExtract(baseDir); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // workitem 10639 | 
|  | // do we want to extract to a regular filesystem file? | 
|  | if (targetFileName != null) | 
|  | { | 
|  | // Check for extracting to a previously extant file. The user | 
|  | // can specify bejavior for that case: overwrite, don't | 
|  | // overwrite, and throw.  Also, if the file exists prior to | 
|  | // extraction, it affects exception handling: whether to delete | 
|  | // the target of extraction or not.  This check needs to be done | 
|  | // before the password check is done, because password check may | 
|  | // throw a BadPasswordException, which triggers the catch, | 
|  | // wherein the extant file may be deleted if not flagged as | 
|  | // pre-existing. | 
|  | if (File.Exists(targetFileName)) | 
|  | { | 
|  | fileExistsBeforeExtraction = true; | 
|  | int rc = CheckExtractExistingFile(baseDir, targetFileName); | 
|  | if (rc == 2) goto ExitTry; // cancel | 
|  | if (rc == 1) return; // do not overwrite | 
|  | } | 
|  | } | 
|  |  | 
|  | // If no password explicitly specified, use the password on the entry itself, | 
|  | // or on the zipfile itself. | 
|  | string p = password ?? this._Password ?? this._container.Password; | 
|  | if (_Encryption_FromZipFile != EncryptionAlgorithm.None) | 
|  | { | 
|  | if (p == null) | 
|  | throw new BadPasswordException(); | 
|  | SetupCryptoForExtract(p); | 
|  | } | 
|  |  | 
|  |  | 
|  | // set up the output stream | 
|  | if (targetFileName != null) | 
|  | { | 
|  | WriteStatus("extract file {0}...", targetFileName); | 
|  | targetFileName += ".tmp"; | 
|  | var dirName = Path.GetDirectoryName(targetFileName); | 
|  | // ensure the target path exists | 
|  | if (!Directory.Exists(dirName)) | 
|  | { | 
|  | // we create the directory here, but we do not set the | 
|  | // create/modified/accessed times on it because it is being | 
|  | // created implicitly, not explcitly. There's no entry in the | 
|  | // zip archive for the directory. | 
|  | Directory.CreateDirectory(dirName); | 
|  | } | 
|  | else | 
|  | { | 
|  | // workitem 8264 | 
|  | if (_container.ZipFile != null) | 
|  | checkLaterForResetDirTimes = _container.ZipFile._inExtractAll; | 
|  | } | 
|  |  | 
|  | // File.Create(CreateNew) will overwrite any existing file. | 
|  | output = new FileStream(targetFileName, FileMode.CreateNew); | 
|  | } | 
|  | else | 
|  | { | 
|  | WriteStatus("extract entry {0} to stream...", FileName); | 
|  | output = outstream; | 
|  | } | 
|  |  | 
|  |  | 
|  | if (_ioOperationCanceled) | 
|  | goto ExitTry; | 
|  |  | 
|  | Int32 ActualCrc32 = ExtractOne(output); | 
|  |  | 
|  | if (_ioOperationCanceled) | 
|  | goto ExitTry; | 
|  |  | 
|  | VerifyCrcAfterExtract(ActualCrc32); | 
|  |  | 
|  | if (targetFileName != null) | 
|  | { | 
|  | output.Close(); | 
|  | output = null; | 
|  |  | 
|  | // workitem 10639 | 
|  | // move file to permanent home | 
|  | string tmpName = targetFileName; | 
|  | string zombie = null; | 
|  | targetFileName = tmpName.Substring(0,tmpName.Length-4); | 
|  |  | 
|  | if (fileExistsBeforeExtraction) | 
|  | { | 
|  | // An AV program may hold the target file open, which means | 
|  | // File.Delete() will succeed, though the actual deletion | 
|  | // remains pending. This will prevent a subsequent | 
|  | // File.Move() from succeeding. To avoid this, when the file | 
|  | // already exists, we need to replace it in 3 steps: | 
|  | // | 
|  | //     1. rename the existing file to a zombie name; | 
|  | //     2. rename the extracted file from the temp name to | 
|  | //        the target file name; | 
|  | //     3. delete the zombie. | 
|  | // | 
|  | zombie = targetFileName + ".PendingOverwrite"; | 
|  | File.Move(targetFileName, zombie); | 
|  | } | 
|  |  | 
|  | File.Move(tmpName, targetFileName); | 
|  | _SetTimes(targetFileName, true); | 
|  |  | 
|  | if (zombie != null && File.Exists(zombie)) | 
|  | ReallyDelete(zombie); | 
|  |  | 
|  | // workitem 8264 | 
|  | if (checkLaterForResetDirTimes) | 
|  | { | 
|  | // This is sort of a hack.  What I do here is set the time on | 
|  | // the parent directory, every time a file is extracted into | 
|  | // it.  If there is a directory with 1000 files, then I set | 
|  | // the time on the dir, 1000 times. This allows the directory | 
|  | // to have times that reflect the actual time on the entry in | 
|  | // the zip archive. | 
|  |  | 
|  | // String.Contains is not available on .NET CF 2.0 | 
|  | if (this.FileName.IndexOf('/') != -1) | 
|  | { | 
|  | string dirname = Path.GetDirectoryName(this.FileName); | 
|  | if (this._container.ZipFile[dirname] == null) | 
|  | { | 
|  | _SetTimes(Path.GetDirectoryName(targetFileName), false); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | #if NETCF | 
|  | // workitem 7926 - version made by OS can be zero or 10 | 
|  | if ((_VersionMadeBy & 0xFF00) == 0x0a00 || (_VersionMadeBy & 0xFF00) == 0x0000) | 
|  | NetCfFile.SetAttributes(targetFileName, (uint)_ExternalFileAttrs); | 
|  |  | 
|  | #else | 
|  | // workitem 7071 | 
|  | // | 
|  | // We can only apply attributes if they are relevant to the NTFS | 
|  | // OS.  Must do this LAST because it may involve a ReadOnly bit, | 
|  | // which would prevent us from setting the time, etc. | 
|  | // | 
|  | // workitem 7926 - version made by OS can be zero (FAT) or 10 | 
|  | // (NTFS) | 
|  | if ((_VersionMadeBy & 0xFF00) == 0x0a00 || (_VersionMadeBy & 0xFF00) == 0x0000) | 
|  | File.SetAttributes(targetFileName, (FileAttributes)_ExternalFileAttrs); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | OnAfterExtract(baseDir); | 
|  |  | 
|  | ExitTry: ; | 
|  | } | 
|  | catch (Exception) | 
|  | { | 
|  | _ioOperationCanceled = true; | 
|  | throw; | 
|  | } | 
|  | finally | 
|  | { | 
|  | if (_ioOperationCanceled) | 
|  | { | 
|  | if (targetFileName != null) | 
|  | { | 
|  | try | 
|  | { | 
|  | if (output != null) output.Close(); | 
|  | // An exception has occurred. If the file exists, check | 
|  | // to see if it existed before we tried extracting.  If | 
|  | // it did not, attempt to remove the target file. There | 
|  | // is a small possibility that the existing file has | 
|  | // been extracted successfully, overwriting a previously | 
|  | // existing file, and an exception was thrown after that | 
|  | // but before final completion (setting times, etc). In | 
|  | // that case the file will remain, even though some | 
|  | // error occurred.  Nothing to be done about it. | 
|  | if (File.Exists(targetFileName) && !fileExistsBeforeExtraction) | 
|  | File.Delete(targetFileName); | 
|  |  | 
|  | } | 
|  | finally { } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | #if NOT | 
|  | internal void CalcWinZipAesMac(Stream input) | 
|  | { | 
|  | if (Encryption == EncryptionAlgorithm.WinZipAes128 || | 
|  | Encryption == EncryptionAlgorithm.WinZipAes256) | 
|  | { | 
|  | if (input is WinZipAesCipherStream) | 
|  | wzs = input as WinZipAesCipherStream; | 
|  |  | 
|  | else if (input is Ionic.Zlib.CrcCalculatorStream) | 
|  | { | 
|  | xxx; | 
|  | } | 
|  |  | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  |  | 
|  | internal void VerifyCrcAfterExtract(Int32 actualCrc32) | 
|  | { | 
|  |  | 
|  | #if AESCRYPTO | 
|  | // After extracting, Validate the CRC32 | 
|  | if (actualCrc32 != _Crc32) | 
|  | { | 
|  | // CRC is not meaningful with WinZipAES and AES method 2 (AE-2) | 
|  | if ((Encryption != EncryptionAlgorithm.WinZipAes128 && | 
|  | Encryption != EncryptionAlgorithm.WinZipAes256) | 
|  | || _WinZipAesMethod != 0x02) | 
|  | throw new BadCrcException("CRC error: the file being extracted appears to be corrupted. " + | 
|  | String.Format("Expected 0x{0:X8}, Actual 0x{1:X8}", _Crc32, actualCrc32)); | 
|  | } | 
|  |  | 
|  | // ignore MAC if the size of the file is zero | 
|  | if (this.UncompressedSize == 0) | 
|  | return; | 
|  |  | 
|  | // calculate the MAC | 
|  | if (Encryption == EncryptionAlgorithm.WinZipAes128 || | 
|  | Encryption == EncryptionAlgorithm.WinZipAes256) | 
|  | { | 
|  | WinZipAesCipherStream wzs = _inputDecryptorStream as WinZipAesCipherStream; | 
|  | _aesCrypto_forExtract.CalculatedMac = wzs.FinalAuthentication; | 
|  |  | 
|  | _aesCrypto_forExtract.ReadAndVerifyMac(this.ArchiveStream); // throws if MAC is bad | 
|  | // side effect: advances file position. | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  | #else | 
|  | if (actualCrc32 != _Crc32) | 
|  | throw new BadCrcException("CRC error: the file being extracted appears to be corrupted. " + | 
|  | String.Format("Expected 0x{0:X8}, Actual 0x{1:X8}", _Crc32, actualCrc32)); | 
|  | #endif | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  | private int CheckExtractExistingFile(string baseDir, string targetFileName) | 
|  | { | 
|  | int loop = 0; | 
|  | // returns: 0 == extract, 1 = don't, 2 = cancel | 
|  | do | 
|  | { | 
|  | switch (ExtractExistingFile) | 
|  | { | 
|  | case ExtractExistingFileAction.OverwriteSilently: | 
|  | WriteStatus("the file {0} exists; will overwrite it...", targetFileName); | 
|  | return 0; | 
|  |  | 
|  | case ExtractExistingFileAction.DoNotOverwrite: | 
|  | WriteStatus("the file {0} exists; not extracting entry...", FileName); | 
|  | OnAfterExtract(baseDir); | 
|  | return 1; | 
|  |  | 
|  | case ExtractExistingFileAction.InvokeExtractProgressEvent: | 
|  | if (loop>0) | 
|  | throw new ZipException(String.Format("The file {0} already exists.", targetFileName)); | 
|  | OnExtractExisting(baseDir); | 
|  | if (_ioOperationCanceled) | 
|  | return 2; | 
|  |  | 
|  | // loop around | 
|  | break; | 
|  |  | 
|  | case ExtractExistingFileAction.Throw: | 
|  | default: | 
|  | throw new ZipException(String.Format("The file {0} already exists.", targetFileName)); | 
|  | } | 
|  | loop++; | 
|  | } | 
|  | while (true); | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  | private void _CheckRead(int nbytes) | 
|  | { | 
|  | if (nbytes == 0) | 
|  | throw new BadReadException(String.Format("bad read of entry {0} from compressed archive.", | 
|  | this.FileName)); | 
|  | } | 
|  |  | 
|  |  | 
|  | private Stream _inputDecryptorStream; | 
|  |  | 
|  | private Int32 ExtractOne(Stream output) | 
|  | { | 
|  | Int32 CrcResult = 0; | 
|  | Stream input = this.ArchiveStream; | 
|  |  | 
|  | try | 
|  | { | 
|  | // change for workitem 8098 | 
|  | input.Seek(this.FileDataPosition, SeekOrigin.Begin); | 
|  | // workitem 10178 | 
|  | Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(input); | 
|  |  | 
|  | byte[] bytes = new byte[BufferSize]; | 
|  |  | 
|  | // The extraction process varies depending on how the entry was | 
|  | // stored.  It could have been encrypted, and it coould have | 
|  | // been compressed, or both, or neither. So we need to check | 
|  | // both the encryption flag and the compression flag, and take | 
|  | // the proper action in all cases. | 
|  |  | 
|  | Int64 LeftToRead = (_CompressionMethod_FromZipFile != (short)CompressionMethod.None) | 
|  | ? this.UncompressedSize | 
|  | : this._CompressedFileDataSize; | 
|  |  | 
|  | // Get a stream that either decrypts or not. | 
|  | _inputDecryptorStream = GetExtractDecryptor(input); | 
|  |  | 
|  | Stream input3 = GetExtractDecompressor( _inputDecryptorStream ); | 
|  |  | 
|  | Int64 bytesWritten = 0; | 
|  | // As we read, we maybe decrypt, and then we maybe decompress. Then we write. | 
|  | using (var s1 = new Ionic.Crc.CrcCalculatorStream(input3)) | 
|  | { | 
|  | while (LeftToRead > 0) | 
|  | { | 
|  | //Console.WriteLine("ExtractOne: LeftToRead {0}", LeftToRead); | 
|  |  | 
|  | // Casting LeftToRead down to an int is ok here in the else clause, | 
|  | // because that only happens when it is less than bytes.Length, | 
|  | // which is much less than MAX_INT. | 
|  | int len = (LeftToRead > bytes.Length) ? bytes.Length : (int)LeftToRead; | 
|  | int n = s1.Read(bytes, 0, len); | 
|  |  | 
|  | // must check data read - essential for detecting corrupt zip files | 
|  | _CheckRead(n); | 
|  |  | 
|  | output.Write(bytes, 0, n); | 
|  | LeftToRead -= n; | 
|  | bytesWritten += n; | 
|  |  | 
|  | // fire the progress event, check for cancels | 
|  | OnExtractProgress(bytesWritten, UncompressedSize); | 
|  | if (_ioOperationCanceled) | 
|  | { | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | CrcResult = s1.Crc; | 
|  | } | 
|  | } | 
|  | finally | 
|  | { | 
|  | var zss = input as ZipSegmentedStream; | 
|  | if (zss != null) | 
|  | { | 
|  | #if NETCF | 
|  | zss.Close(); | 
|  | #else | 
|  | // need to dispose it | 
|  | zss.Dispose(); | 
|  | #endif | 
|  | _archiveStream = null; | 
|  | } | 
|  | } | 
|  |  | 
|  | return CrcResult; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | internal Stream GetExtractDecompressor(Stream input2) | 
|  | { | 
|  | // get a stream that either decompresses or not. | 
|  | switch (_CompressionMethod_FromZipFile) | 
|  | { | 
|  | case (short)CompressionMethod.None: | 
|  | return input2; | 
|  | case (short)CompressionMethod.Deflate: | 
|  | return new Ionic.Zlib.DeflateStream(input2, Ionic.Zlib.CompressionMode.Decompress, true); | 
|  | #if BZIP | 
|  | case (short)CompressionMethod.BZip2: | 
|  | return new Ionic.BZip2.BZip2InputStream(input2, true); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | return null; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | internal Stream GetExtractDecryptor(Stream input) | 
|  | { | 
|  | Stream input2 = null; | 
|  | if (_Encryption_FromZipFile == EncryptionAlgorithm.PkzipWeak) | 
|  | input2 = new ZipCipherStream(input, _zipCrypto_forExtract, CryptoMode.Decrypt); | 
|  |  | 
|  | #if AESCRYPTO | 
|  | else if (_Encryption_FromZipFile == EncryptionAlgorithm.WinZipAes128 || | 
|  | _Encryption_FromZipFile == EncryptionAlgorithm.WinZipAes256) | 
|  | input2 = new WinZipAesCipherStream(input, _aesCrypto_forExtract, _CompressedFileDataSize, CryptoMode.Decrypt); | 
|  | #endif | 
|  |  | 
|  | else | 
|  | input2 = input; | 
|  |  | 
|  | return input2; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  | internal void _SetTimes(string fileOrDirectory, bool isFile) | 
|  | { | 
|  | #if SILVERLIGHT | 
|  | // punt on setting file times | 
|  | #else | 
|  | // workitem 8807: | 
|  | // Because setting the time is not considered to be a fatal error, | 
|  | // and because other applications can interfere with the setting | 
|  | // of a time on a directory, we're going to swallow IO exceptions | 
|  | // in this method. | 
|  |  | 
|  | try | 
|  | { | 
|  | if (_ntfsTimesAreSet) | 
|  | { | 
|  | #if NETCF | 
|  | // workitem 7944: set time should not be a fatal error on CF | 
|  | int rc = NetCfFile.SetTimes(fileOrDirectory, _Ctime, _Atime, _Mtime); | 
|  | if ( rc != 0) | 
|  | { | 
|  | WriteStatus("Warning: SetTimes failed.  entry({0})  file({1})  rc({2})", | 
|  | FileName, fileOrDirectory, rc); | 
|  | } | 
|  | #else | 
|  | if (isFile) | 
|  | { | 
|  | // It's possible that the extract was cancelled, in which case, | 
|  | // the file does not exist. | 
|  | if (File.Exists(fileOrDirectory)) | 
|  | { | 
|  | File.SetCreationTimeUtc(fileOrDirectory, _Ctime); | 
|  | File.SetLastAccessTimeUtc(fileOrDirectory, _Atime); | 
|  | File.SetLastWriteTimeUtc(fileOrDirectory, _Mtime); | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | // It's possible that the extract was cancelled, in which case, | 
|  | // the directory does not exist. | 
|  | if (Directory.Exists(fileOrDirectory)) | 
|  | { | 
|  | Directory.SetCreationTimeUtc(fileOrDirectory, _Ctime); | 
|  | Directory.SetLastAccessTimeUtc(fileOrDirectory, _Atime); | 
|  | Directory.SetLastWriteTimeUtc(fileOrDirectory, _Mtime); | 
|  | } | 
|  | } | 
|  | #endif | 
|  | } | 
|  | else | 
|  | { | 
|  | // workitem 6191 | 
|  | DateTime AdjustedLastModified = Ionic.Zip.SharedUtilities.AdjustTime_Reverse(LastModified); | 
|  |  | 
|  | #if NETCF | 
|  | int rc = NetCfFile.SetLastWriteTime(fileOrDirectory, AdjustedLastModified); | 
|  |  | 
|  | if ( rc != 0) | 
|  | { | 
|  | WriteStatus("Warning: SetLastWriteTime failed.  entry({0})  file({1})  rc({2})", | 
|  | FileName, fileOrDirectory, rc); | 
|  | } | 
|  | #else | 
|  | if (isFile) | 
|  | File.SetLastWriteTime(fileOrDirectory, AdjustedLastModified); | 
|  | else | 
|  | Directory.SetLastWriteTime(fileOrDirectory, AdjustedLastModified); | 
|  | #endif | 
|  | } | 
|  | } | 
|  | catch (System.IO.IOException ioexc1) | 
|  | { | 
|  | WriteStatus("failed to set time on {0}: {1}", fileOrDirectory, ioexc1.Message); | 
|  | } | 
|  | #endif | 
|  | } | 
|  |  | 
|  |  | 
|  | #region Support methods | 
|  |  | 
|  |  | 
|  | // workitem 7968 | 
|  | private string UnsupportedAlgorithm | 
|  | { | 
|  | get | 
|  | { | 
|  | string alg = String.Empty; | 
|  | switch (_UnsupportedAlgorithmId) | 
|  | { | 
|  | case 0: | 
|  | alg = "--"; | 
|  | break; | 
|  | case 0x6601: | 
|  | alg = "DES"; | 
|  | break; | 
|  | case 0x6602: // - RC2 (version needed to extract < 5.2) | 
|  | alg = "RC2"; | 
|  | break; | 
|  | case 0x6603: // - 3DES 168 | 
|  | alg = "3DES-168"; | 
|  | break; | 
|  | case 0x6609: // - 3DES 112 | 
|  | alg = "3DES-112"; | 
|  | break; | 
|  | case 0x660E: // - AES 128 | 
|  | alg = "PKWare AES128"; | 
|  | break; | 
|  | case 0x660F: // - AES 192 | 
|  | alg = "PKWare AES192"; | 
|  | break; | 
|  | case 0x6610: // - AES 256 | 
|  | alg = "PKWare AES256"; | 
|  | break; | 
|  | case 0x6702: // - RC2 (version needed to extract >= 5.2) | 
|  | alg = "RC2"; | 
|  | break; | 
|  | case 0x6720: // - Blowfish | 
|  | alg = "Blowfish"; | 
|  | break; | 
|  | case 0x6721: // - Twofish | 
|  | alg = "Twofish"; | 
|  | break; | 
|  | case 0x6801: // - RC4 | 
|  | alg = "RC4"; | 
|  | break; | 
|  | case 0xFFFF: // - Unknown algorithm | 
|  | default: | 
|  | alg = String.Format("Unknown (0x{0:X4})", _UnsupportedAlgorithmId); | 
|  | break; | 
|  | } | 
|  | return alg; | 
|  | } | 
|  | } | 
|  |  | 
|  | // workitem 7968 | 
|  | private string UnsupportedCompressionMethod | 
|  | { | 
|  | get | 
|  | { | 
|  | string meth = String.Empty; | 
|  | switch ((int)_CompressionMethod) | 
|  | { | 
|  | case 0: | 
|  | meth = "Store"; | 
|  | break; | 
|  | case 1: | 
|  | meth = "Shrink"; | 
|  | break; | 
|  | case 8: | 
|  | meth = "DEFLATE"; | 
|  | break; | 
|  | case 9: | 
|  | meth = "Deflate64"; | 
|  | break; | 
|  | case 12: | 
|  | meth = "BZIP2"; // only if BZIP not compiled in | 
|  | break; | 
|  | case 14: | 
|  | meth = "LZMA"; | 
|  | break; | 
|  | case 19: | 
|  | meth = "LZ77"; | 
|  | break; | 
|  | case 98: | 
|  | meth = "PPMd"; | 
|  | break; | 
|  | default: | 
|  | meth = String.Format("Unknown (0x{0:X4})", _CompressionMethod); | 
|  | break; | 
|  | } | 
|  | return meth; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | internal void ValidateEncryption() | 
|  | { | 
|  | if (Encryption != EncryptionAlgorithm.PkzipWeak && | 
|  | #if AESCRYPTO | 
|  | Encryption != EncryptionAlgorithm.WinZipAes128 && | 
|  | Encryption != EncryptionAlgorithm.WinZipAes256 && | 
|  | #endif | 
|  | Encryption != EncryptionAlgorithm.None) | 
|  | { | 
|  | // workitem 7968 | 
|  | if (_UnsupportedAlgorithmId != 0) | 
|  | throw new ZipException(String.Format("Cannot extract: Entry {0} is encrypted with an algorithm not supported by DotNetZip: {1}", | 
|  | FileName, UnsupportedAlgorithm)); | 
|  | else | 
|  | throw new ZipException(String.Format("Cannot extract: Entry {0} uses an unsupported encryption algorithm ({1:X2})", | 
|  | FileName, (int)Encryption)); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | private void ValidateCompression() | 
|  | { | 
|  | if ((_CompressionMethod_FromZipFile != (short)CompressionMethod.None) && | 
|  | (_CompressionMethod_FromZipFile != (short)CompressionMethod.Deflate) | 
|  | #if BZIP | 
|  | && (_CompressionMethod_FromZipFile != (short)CompressionMethod.BZip2) | 
|  | #endif | 
|  | ) | 
|  | throw new ZipException(String.Format("Entry {0} uses an unsupported compression method (0x{1:X2}, {2})", | 
|  | FileName, _CompressionMethod_FromZipFile, UnsupportedCompressionMethod)); | 
|  | } | 
|  |  | 
|  |  | 
|  | private void SetupCryptoForExtract(string password) | 
|  | { | 
|  | //if (password == null) return; | 
|  | if (_Encryption_FromZipFile == EncryptionAlgorithm.None) return; | 
|  |  | 
|  | if (_Encryption_FromZipFile == EncryptionAlgorithm.PkzipWeak) | 
|  | { | 
|  | if (password == null) | 
|  | throw new ZipException("Missing password."); | 
|  |  | 
|  | this.ArchiveStream.Seek(this.FileDataPosition - 12, SeekOrigin.Begin); | 
|  | // workitem 10178 | 
|  | Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(this.ArchiveStream); | 
|  | _zipCrypto_forExtract = ZipCrypto.ForRead(password, this); | 
|  | } | 
|  |  | 
|  | #if AESCRYPTO | 
|  | else if (_Encryption_FromZipFile == EncryptionAlgorithm.WinZipAes128 || | 
|  | _Encryption_FromZipFile == EncryptionAlgorithm.WinZipAes256) | 
|  | { | 
|  | if (password == null) | 
|  | throw new ZipException("Missing password."); | 
|  |  | 
|  | // If we already have a WinZipAesCrypto object in place, use it. | 
|  | // It can be set up in the ReadDirEntry(), or during a previous Extract. | 
|  | if (_aesCrypto_forExtract != null) | 
|  | { | 
|  | _aesCrypto_forExtract.Password = password; | 
|  | } | 
|  | else | 
|  | { | 
|  | int sizeOfSaltAndPv = GetLengthOfCryptoHeaderBytes(_Encryption_FromZipFile); | 
|  | this.ArchiveStream.Seek(this.FileDataPosition - sizeOfSaltAndPv, SeekOrigin.Begin); | 
|  | // workitem 10178 | 
|  | Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(this.ArchiveStream); | 
|  | int keystrength = GetKeyStrengthInBits(_Encryption_FromZipFile); | 
|  | _aesCrypto_forExtract = WinZipAesCrypto.ReadFromStream(password, keystrength, this.ArchiveStream); | 
|  | } | 
|  | } | 
|  | #endif | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /// <summary> | 
|  | /// Validates that the args are consistent. | 
|  | /// </summary> | 
|  | /// <remarks> | 
|  | /// Only one of {baseDir, outStream} can be non-null. | 
|  | /// If baseDir is non-null, then the outputFile is created. | 
|  | /// </remarks> | 
|  | private bool ValidateOutput(string basedir, Stream outstream, out string outFileName) | 
|  | { | 
|  | if (basedir != null) | 
|  | { | 
|  | // Sometimes the name on the entry starts with a slash. | 
|  | // Rather than unpack to the root of the volume, we're going to | 
|  | // drop the slash and unpack to the specified base directory. | 
|  | string f = this.FileName.Replace("\\","/"); | 
|  |  | 
|  | // workitem 11772: remove drive letter with separator | 
|  | if (f.IndexOf(':') == 1) | 
|  | f= f.Substring(2); | 
|  |  | 
|  | if (f.StartsWith("/")) | 
|  | f= f.Substring(1); | 
|  |  | 
|  | // String.Contains is not available on .NET CF 2.0 | 
|  |  | 
|  | if (_container.ZipFile.FlattenFoldersOnExtract) | 
|  | outFileName = Path.Combine(basedir, | 
|  | (f.IndexOf('/') != -1) ? Path.GetFileName(f) : f); | 
|  | else | 
|  | outFileName = Path.Combine(basedir, f); | 
|  |  | 
|  | // workitem 10639 | 
|  | outFileName = outFileName.Replace("/","\\"); | 
|  |  | 
|  | // check if it is a directory | 
|  | if ((IsDirectory) || (FileName.EndsWith("/"))) | 
|  | { | 
|  | if (!Directory.Exists(outFileName)) | 
|  | { | 
|  | Directory.CreateDirectory(outFileName); | 
|  | _SetTimes(outFileName, false); | 
|  | } | 
|  | else | 
|  | { | 
|  | // the dir exists, maybe we want to overwrite times. | 
|  | if (ExtractExistingFile == ExtractExistingFileAction.OverwriteSilently) | 
|  | _SetTimes(outFileName, false); | 
|  | } | 
|  | return true;  // true == all done, caller will return | 
|  | } | 
|  | return false;  // false == work to do by caller. | 
|  | } | 
|  |  | 
|  | if (outstream != null) | 
|  | { | 
|  | outFileName = null; | 
|  | if ((IsDirectory) || (FileName.EndsWith("/"))) | 
|  | { | 
|  | // extract a directory to streamwriter?  nothing to do! | 
|  | return true;  // true == all done!  caller can return | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | throw new ArgumentNullException("outstream"); | 
|  | } | 
|  |  | 
|  |  | 
|  | #endregion | 
|  |  | 
|  | } | 
|  | } |