// 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 { /// /// Extract the entry to the filesystem, starting at the current /// working directory. /// /// /// /// 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 ExtractWithPassword() methods. /// /// /// /// /// /// /// /// /// 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. /// /// /// /// The action taken when extraction an entry would overwrite an /// existing file is determined by the property. /// /// /// /// Within the call to Extract(), 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 property on /// the entry. See the remarks the property for /// some details about the last modified time. /// /// /// internal void Extract() { InternalExtract(".", null, null); } /// /// Extract the entry to a file in the filesystem, using the specified /// behavior when extraction would overwrite an existing file. /// /// /// /// /// See the remarks on the property, for some /// details about how the last modified time of the file is set after /// extraction. /// /// /// /// /// The action to take if extraction would overwrite an existing file. /// internal void Extract(ExtractExistingFileAction extractExistingFile) { ExtractExistingFile = extractExistingFile; InternalExtract(".", null, null); } /// /// Extracts the entry to the specified stream. /// /// /// /// /// The caller can specify any write-able stream, for example a , a , or ASP.NET's /// Response.OutputStream. The content will be decrypted and /// decompressed as necessary. If the entry is encrypted and no password /// is provided, this method will throw. /// /// /// 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(). /// /// /// /// /// the stream to which the entry should be extracted. /// /// public void Extract(Stream stream) { InternalExtract(null, stream, null); } /// /// Extract the entry to the filesystem, starting at the specified base /// directory. /// /// /// the pathname of the base directory /// /// /// /// /// /// This example extracts only the entries in a zip file that are .txt files, /// into a directory called "textfiles". /// /// using (ZipFile zip = ZipFile.Read("PackedDocuments.zip")) /// { /// foreach (string s1 in zip.EntryFilenames) /// { /// if (s1.EndsWith(".txt")) /// { /// zip[s1].Extract("textfiles"); /// } /// } /// } /// /// /// 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 /// /// /// /// /// /// /// 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 property, or call /// . /// /// /// /// See the remarks on the property, for some /// details about how the last modified time of the created file is set. /// /// public void Extract(string baseDirectory) { InternalExtract(baseDirectory, null, null); } /// /// Extract the entry to the filesystem, starting at the specified base /// directory, and using the specified behavior when extraction would /// overwrite an existing file. /// /// /// /// /// See the remarks on the property, for some /// details about how the last modified time of the created file is set. /// /// /// /// /// /// 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); /// } /// } /// /// /// /// 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 /// /// /// /// the pathname of the base directory /// /// The action to take if extraction would overwrite an existing file. /// internal void Extract(string baseDirectory, ExtractExistingFileAction extractExistingFile) { ExtractExistingFile = extractExistingFile; InternalExtract(baseDirectory, null, null); } /// /// Extract the entry to the filesystem, using the current working directory /// and the specified password. /// /// /// /// This method has a bunch of overloads! One of them is sure to be /// the right one for you... /// /// /// /// /// /// /// /// /// Existing entries in the filesystem will not be overwritten. If you /// would like to force the overwrite of existing files, see the property, or call /// . /// /// /// /// See the remarks on the property for some /// details about how the "last modified" time of the created file is /// set. /// /// /// /// /// In this example, entries that use encryption are extracted using a /// particular password. /// /// using (var zip = ZipFile.Read(FilePath)) /// { /// foreach (ZipEntry e in zip) /// { /// if (e.UsesEncryption) /// e.ExtractWithPassword("Secret!"); /// else /// e.Extract(); /// } /// } /// /// /// 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 /// /// /// The Password to use for decrypting the entry. public void ExtractWithPassword(string password) { InternalExtract(".", null, password); } /// /// Extract the entry to the filesystem, starting at the specified base /// directory, and using the specified password. /// /// /// /// /// /// /// /// Existing entries in the filesystem will not be overwritten. If you /// would like to force the overwrite of existing files, see the property, or call /// . /// /// /// /// See the remarks on the property, for some /// details about how the last modified time of the created file is set. /// /// /// /// The pathname of the base directory. /// The Password to use for decrypting the entry. public void ExtractWithPassword(string baseDirectory, string password) { InternalExtract(baseDirectory, null, password); } /// /// 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. /// /// /// /// /// See the remarks on the property, for some /// details about how the last modified time of the created file is set. /// /// /// /// The Password to use for decrypting the entry. /// /// /// The action to take if extraction would overwrite an existing file. /// internal void ExtractWithPassword(ExtractExistingFileAction extractExistingFile, string password) { ExtractExistingFile = extractExistingFile; InternalExtract(".", null, password); } /// /// Extract the entry to the filesystem, starting at the specified base /// directory, and using the specified behavior when extraction would /// overwrite an existing file. /// /// /// /// See the remarks on the property, for some /// details about how the last modified time of the created file is set. /// /// /// the pathname of the base directory /// /// The action to take if extraction would /// overwrite an existing file. /// /// The Password to use for decrypting the entry. internal void ExtractWithPassword(string baseDirectory, ExtractExistingFileAction extractExistingFile, string password) { ExtractExistingFile = extractExistingFile; InternalExtract(baseDirectory, null, password); } /// /// Extracts the entry to the specified stream, using the specified /// Password. For example, the caller could extract to Console.Out, or /// to a MemoryStream. /// /// /// /// /// The caller can specify any write-able stream, for example a , a , or ASP.NET's /// Response.OutputStream. The content will be decrypted and /// decompressed as necessary. If the entry is encrypted and no password /// is provided, this method will throw. /// /// /// 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(). /// /// /// /// /// /// the stream to which the entry should be extracted. /// /// /// The password to use for decrypting the entry. /// public void ExtractWithPassword(Stream stream, string password) { InternalExtract(null, stream, password); } /// /// Opens a readable stream corresponding to the zip entry in the /// archive. The stream decompresses and decrypts as necessary, as it /// is read. /// /// /// /// /// /// 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 . /// /// /// /// The return value is of type . Use it as you would any /// stream for reading. When an application calls on that stream, it will /// receive data from the zip entry that is decrypted and decompressed /// as necessary. /// /// /// /// CrcCalculatorStream 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 property on the /// CrcCalculatorStream. When the read is complete, your /// application /// should check this CRC against the /// property on the ZipEntry 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. /// /// /// /// If the entry is protected with a password, then you need to provide /// a password prior to calling , either by /// setting the property on the entry, or the /// property on the ZipFile /// itself. Or, you can use , the /// overload of OpenReader that accepts a password parameter. /// /// /// /// If you want to extract entry data into a write-able stream that is /// already opened, like a , do not /// use this method. Instead, use . /// /// /// /// 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(). /// /// /// /// The OpenReader() method works only when the ZipEntry is /// obtained from an instance of ZipFile. This method will throw /// an exception if the ZipEntry is obtained from a ZipInputStream. /// /// /// /// /// 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 ZipEntry, to verify the extraction. /// /// 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)); /// } /// } /// /// /// 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 /// /// /// /// The Stream for reading. 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); } /// /// Opens a readable stream for an encrypted zip entry in the archive. /// The stream decompresses and decrypts as necessary, as it is read. /// /// /// /// /// See the documentation on the method for /// full details. This overload allows the application to specify a /// password for the ZipEntry to be read. /// /// /// /// The password to use for decrypting the entry. /// The Stream for reading. 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 } /// /// Validates that the args are consistent. /// /// /// Only one of {baseDir, outStream} can be non-null. /// If baseDir is non-null, then the outputFile is created. /// 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 } }