using System; using System.IO; using ICSharpCode.SharpZipLib.Core; using ICSharpCode.SharpZipLib.Zip.Compression; namespace ICSharpCode.SharpZipLib.Zip { /// /// FastZipEvents supports all events applicable to FastZip operations. /// public class FastZipEvents { /// /// Delegate to invoke when processing directories. /// public event EventHandler ProcessDirectory; /// /// Delegate to invoke when processing files. /// public ProcessFileHandler ProcessFile; /// /// Delegate to invoke during processing of files. /// public ProgressHandler Progress; /// /// Delegate to invoke when processing for a file has been completed. /// public CompletedFileHandler CompletedFile; /// /// Delegate to invoke when processing directory failures. /// public DirectoryFailureHandler DirectoryFailure; /// /// Delegate to invoke when processing file failures. /// public FileFailureHandler FileFailure; /// /// Raise the directory failure event. /// /// The directory causing the failure. /// The exception for this event. /// A boolean indicating if execution should continue or not. public bool OnDirectoryFailure(string directory, Exception e) { bool result = false; DirectoryFailureHandler handler = DirectoryFailure; if (handler != null) { var args = new ScanFailureEventArgs(directory, e); handler(this, args); result = args.ContinueRunning; } return result; } /// /// Fires the file failure handler delegate. /// /// The file causing the failure. /// The exception for this failure. /// A boolean indicating if execution should continue or not. public bool OnFileFailure(string file, Exception e) { FileFailureHandler handler = FileFailure; bool result = (handler != null); if (result) { var args = new ScanFailureEventArgs(file, e); handler(this, args); result = args.ContinueRunning; } return result; } /// /// Fires the ProcessFile delegate. /// /// The file being processed. /// A boolean indicating if execution should continue or not. public bool OnProcessFile(string file) { bool result = true; ProcessFileHandler handler = ProcessFile; if (handler != null) { var args = new ScanEventArgs(file); handler(this, args); result = args.ContinueRunning; } return result; } /// /// Fires the delegate /// /// The file whose processing has been completed. /// A boolean indicating if execution should continue or not. public bool OnCompletedFile(string file) { bool result = true; CompletedFileHandler handler = CompletedFile; if (handler != null) { var args = new ScanEventArgs(file); handler(this, args); result = args.ContinueRunning; } return result; } /// /// Fires the process directory delegate. /// /// The directory being processed. /// Flag indicating if the directory has matching files as determined by the current filter. /// A of true if the operation should continue; false otherwise. public bool OnProcessDirectory(string directory, bool hasMatchingFiles) { bool result = true; EventHandler handler = ProcessDirectory; if (handler != null) { var args = new DirectoryEventArgs(directory, hasMatchingFiles); handler(this, args); result = args.ContinueRunning; } return result; } /// /// The minimum timespan between events. /// /// The minimum period of time between events. /// /// The default interval is three seconds. public TimeSpan ProgressInterval { get { return progressInterval_; } set { progressInterval_ = value; } } #region Instance Fields TimeSpan progressInterval_ = TimeSpan.FromSeconds(3); #endregion } /// /// FastZip provides facilities for creating and extracting zip files. /// public class FastZip { #region Enumerations /// /// Defines the desired handling when overwriting files during extraction. /// public enum Overwrite { /// /// Prompt the user to confirm overwriting /// Prompt, /// /// Never overwrite files. /// Never, /// /// Always overwrite files. /// Always } #endregion #region Constructors /// /// Initialise a default instance of . /// public FastZip() { } /// /// Initialise a new instance of /// /// The events to use during operations. public FastZip(FastZipEvents events) { events_ = events; } #endregion #region Properties /// /// Get/set a value indicating wether empty directories should be created. /// public bool CreateEmptyDirectories { get { return createEmptyDirectories_; } set { createEmptyDirectories_ = value; } } /// /// Get / set the password value. /// public string Password { get { return password_; } set { password_ = value; } } /// /// Get or set the active when creating Zip files. /// /// public INameTransform NameTransform { get { return entryFactory_.NameTransform; } set { entryFactory_.NameTransform = value; } } /// /// Get or set the active when creating Zip files. /// public IEntryFactory EntryFactory { get { return entryFactory_; } set { if (value == null) { entryFactory_ = new ZipEntryFactory(); } else { entryFactory_ = value; } } } /// /// Gets or sets the setting for Zip64 handling when writing. /// /// /// The default value is dynamic which is not backwards compatible with old /// programs and can cause problems with XP's built in compression which cant /// read Zip64 archives. However it does avoid the situation were a large file /// is added and cannot be completed correctly. /// NOTE: Setting the size for entries before they are added is the best solution! /// By default the EntryFactory used by FastZip will set fhe file size. /// public UseZip64 UseZip64 { get { return useZip64_; } set { useZip64_ = value; } } /// /// Get/set a value indicating wether file dates and times should /// be restored when extracting files from an archive. /// /// The default value is false. public bool RestoreDateTimeOnExtract { get { return restoreDateTimeOnExtract_; } set { restoreDateTimeOnExtract_ = value; } } /// /// Get/set a value indicating whether file attributes should /// be restored during extract operations /// public bool RestoreAttributesOnExtract { get { return restoreAttributesOnExtract_; } set { restoreAttributesOnExtract_ = value; } } /// /// Get/set the Compression Level that will be used /// when creating the zip /// public Deflater.CompressionLevel CompressionLevel{ get { return compressionLevel_; } set { compressionLevel_ = value; } } #endregion #region Delegates /// /// Delegate called when confirming overwriting of files. /// public delegate bool ConfirmOverwriteDelegate(string fileName); #endregion #region CreateZip /// /// Create a zip file. /// /// The name of the zip file to create. /// The directory to source files from. /// True to recurse directories, false for no recursion. /// The file filter to apply. /// The directory filter to apply. public void CreateZip(string zipFileName, string sourceDirectory, bool recurse, string fileFilter, string directoryFilter) { CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, directoryFilter); } /// /// Create a zip file/archive. /// /// The name of the zip file to create. /// The directory to obtain files and directories from. /// True to recurse directories, false for no recursion. /// The file filter to apply. public void CreateZip(string zipFileName, string sourceDirectory, bool recurse, string fileFilter) { CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, null); } /// /// Create a zip archive sending output to the passed. /// /// The stream to write archive data to. /// The directory to source files from. /// True to recurse directories, false for no recursion. /// The file filter to apply. /// The directory filter to apply. /// The is closed after creation. public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, string fileFilter, string directoryFilter) { NameTransform = new ZipNameTransform(sourceDirectory); sourceDirectory_ = sourceDirectory; using (outputStream_ = new ZipOutputStream(outputStream)) { outputStream_.SetLevel((int)CompressionLevel); if (password_ != null) { outputStream_.Password = password_; } outputStream_.UseZip64 = UseZip64; var scanner = new FileSystemScanner(fileFilter, directoryFilter); scanner.ProcessFile += ProcessFile; if (this.CreateEmptyDirectories) { scanner.ProcessDirectory += ProcessDirectory; } if (events_ != null) { if (events_.FileFailure != null) { scanner.FileFailure += events_.FileFailure; } if (events_.DirectoryFailure != null) { scanner.DirectoryFailure += events_.DirectoryFailure; } } scanner.Scan(sourceDirectory, recurse); } } #endregion #region ExtractZip /// /// Extract the contents of a zip file. /// /// The zip file to extract from. /// The directory to save extracted information in. /// A filter to apply to files. public void ExtractZip(string zipFileName, string targetDirectory, string fileFilter) { ExtractZip(zipFileName, targetDirectory, Overwrite.Always, null, fileFilter, null, restoreDateTimeOnExtract_); } /// /// Extract the contents of a zip file. /// /// The zip file to extract from. /// The directory to save extracted information in. /// The style of overwriting to apply. /// A delegate to invoke when confirming overwriting. /// A filter to apply to files. /// A filter to apply to directories. /// Flag indicating whether to restore the date and time for extracted files. public void ExtractZip(string zipFileName, string targetDirectory, Overwrite overwrite, ConfirmOverwriteDelegate confirmDelegate, string fileFilter, string directoryFilter, bool restoreDateTime) { Stream inputStream = File.Open(zipFileName, FileMode.Open, FileAccess.Read, FileShare.Read); ExtractZip(inputStream, targetDirectory, overwrite, confirmDelegate, fileFilter, directoryFilter, restoreDateTime, true); } /// /// Extract the contents of a zip file held in a stream. /// /// The seekable input stream containing the zip to extract from. /// The directory to save extracted information in. /// The style of overwriting to apply. /// A delegate to invoke when confirming overwriting. /// A filter to apply to files. /// A filter to apply to directories. /// Flag indicating whether to restore the date and time for extracted files. /// Flag indicating whether the inputStream will be closed by this method. public void ExtractZip(Stream inputStream, string targetDirectory, Overwrite overwrite, ConfirmOverwriteDelegate confirmDelegate, string fileFilter, string directoryFilter, bool restoreDateTime, bool isStreamOwner) { if ((overwrite == Overwrite.Prompt) && (confirmDelegate == null)) { throw new ArgumentNullException("nameof(confirmDelegate)"); } continueRunning_ = true; overwrite_ = overwrite; confirmDelegate_ = confirmDelegate; extractNameTransform_ = new WindowsNameTransform(targetDirectory); fileFilter_ = new NameFilter(fileFilter); directoryFilter_ = new NameFilter(directoryFilter); restoreDateTimeOnExtract_ = restoreDateTime; using (zipFile_ = new ZipFile(inputStream)) { if (password_ != null) { zipFile_.Password = password_; } zipFile_.IsStreamOwner = isStreamOwner; System.Collections.IEnumerator enumerator = zipFile_.GetEnumerator(); while (continueRunning_ && enumerator.MoveNext()) { var entry = (ZipEntry)enumerator.Current; if (entry.IsFile) { // TODO Path.GetDirectory can fail here on invalid characters. if (directoryFilter_.IsMatch(Path.GetDirectoryName(entry.Name)) && fileFilter_.IsMatch(entry.Name)) { ExtractEntry(entry); } } else if (entry.IsDirectory) { if (directoryFilter_.IsMatch(entry.Name) && CreateEmptyDirectories) { ExtractEntry(entry); } } else { // Do nothing for volume labels etc... } } } } #endregion #region Internal Processing void ProcessDirectory(object sender, DirectoryEventArgs e) { if (!e.HasMatchingFiles && CreateEmptyDirectories) { if (events_ != null) { events_.OnProcessDirectory(e.Name, e.HasMatchingFiles); } if (e.ContinueRunning) { if (e.Name != sourceDirectory_) { ZipEntry entry = entryFactory_.MakeDirectoryEntry(e.Name); outputStream_.PutNextEntry(entry); } } } } void ProcessFile(object sender, ScanEventArgs e) { if ((events_ != null) && (events_.ProcessFile != null)) { events_.ProcessFile(sender, e); } if (e.ContinueRunning) { try { // The open below is equivalent to OpenRead which gaurantees that if opened the // file will not be changed by subsequent openers, but precludes opening in some cases // were it could succeed. ie the open may fail as its already open for writing and the share mode should reflect that. using (FileStream stream = File.Open(e.Name, FileMode.Open, FileAccess.Read, FileShare.Read)) { ZipEntry entry = entryFactory_.MakeFileEntry(e.Name); outputStream_.PutNextEntry(entry); AddFileContents(e.Name, stream); } } catch (Exception ex) { if (events_ != null) { continueRunning_ = events_.OnFileFailure(e.Name, ex); } else { continueRunning_ = false; throw; } } } } void AddFileContents(string name, Stream stream) { if (stream == null) { throw new ArgumentNullException("nameof(stream)"); } if (buffer_ == null) { buffer_ = new byte[4096]; } if ((events_ != null) && (events_.Progress != null)) { StreamUtils.Copy(stream, outputStream_, buffer_, events_.Progress, events_.ProgressInterval, this, name); } else { StreamUtils.Copy(stream, outputStream_, buffer_); } if (events_ != null) { continueRunning_ = events_.OnCompletedFile(name); } } void ExtractFileEntry(ZipEntry entry, string targetName) { bool proceed = true; if (overwrite_ != Overwrite.Always) { if (File.Exists(targetName)) { if ((overwrite_ == Overwrite.Prompt) && (confirmDelegate_ != null)) { proceed = confirmDelegate_(targetName); } else { proceed = false; } } } if (proceed) { if (events_ != null) { continueRunning_ = events_.OnProcessFile(entry.Name); } if (continueRunning_) { try { using (FileStream outputStream = File.Create(targetName)) { if (buffer_ == null) { buffer_ = new byte[4096]; } if ((events_ != null) && (events_.Progress != null)) { StreamUtils.Copy(zipFile_.GetInputStream(entry), outputStream, buffer_, events_.Progress, events_.ProgressInterval, this, entry.Name, entry.Size); } else { StreamUtils.Copy(zipFile_.GetInputStream(entry), outputStream, buffer_); } if (events_ != null) { continueRunning_ = events_.OnCompletedFile(entry.Name); } } if (restoreDateTimeOnExtract_) { File.SetLastWriteTime(targetName, entry.DateTime); } if (RestoreAttributesOnExtract && entry.IsDOSEntry && (entry.ExternalFileAttributes != -1)) { var fileAttributes = (FileAttributes)entry.ExternalFileAttributes; // TODO: FastZip - Setting of other file attributes on extraction is a little trickier. fileAttributes &= (FileAttributes.Archive | FileAttributes.Normal | FileAttributes.ReadOnly | FileAttributes.Hidden); File.SetAttributes(targetName, fileAttributes); } } catch (Exception ex) { if (events_ != null) { continueRunning_ = events_.OnFileFailure(targetName, ex); } else { continueRunning_ = false; throw; } } } } } void ExtractEntry(ZipEntry entry) { bool doExtraction = entry.IsCompressionMethodSupported(); string targetName = entry.Name; if (doExtraction) { if (entry.IsFile) { targetName = extractNameTransform_.TransformFile(targetName); } else if (entry.IsDirectory) { targetName = extractNameTransform_.TransformDirectory(targetName); } doExtraction = !(string.IsNullOrEmpty(targetName)); } // TODO: Fire delegate/throw exception were compression method not supported, or name is invalid? string dirName = null; if (doExtraction) { if (entry.IsDirectory) { dirName = targetName; } else { dirName = Path.GetDirectoryName(Path.GetFullPath(targetName)); } } if (doExtraction && !Directory.Exists(dirName)) { if (!entry.IsDirectory || CreateEmptyDirectories) { try { Directory.CreateDirectory(dirName); } catch (Exception ex) { doExtraction = false; if (events_ != null) { if (entry.IsDirectory) { continueRunning_ = events_.OnDirectoryFailure(targetName, ex); } else { continueRunning_ = events_.OnFileFailure(targetName, ex); } } else { continueRunning_ = false; throw; } } } } if (doExtraction && entry.IsFile) { ExtractFileEntry(entry, targetName); } } static int MakeExternalAttributes(FileInfo info) { return (int)info.Attributes; } static bool NameIsValid(string name) { return !string.IsNullOrEmpty(name) && (name.IndexOfAny(Path.GetInvalidPathChars()) < 0); } #endregion #region Instance Fields bool continueRunning_; byte[] buffer_; ZipOutputStream outputStream_; ZipFile zipFile_; string sourceDirectory_; NameFilter fileFilter_; NameFilter directoryFilter_; Overwrite overwrite_; ConfirmOverwriteDelegate confirmDelegate_; bool restoreDateTimeOnExtract_; bool restoreAttributesOnExtract_; bool createEmptyDirectories_; FastZipEvents events_; IEntryFactory entryFactory_ = new ZipEntryFactory(); INameTransform extractNameTransform_; UseZip64 useZip64_ = UseZip64.Dynamic; Deflater.CompressionLevel compressionLevel_ = Deflater.CompressionLevel.DEFAULT_COMPRESSION; string password_; #endregion } }