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