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