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