// ZipOutputStream.cs // // ------------------------------------------------------------------ // // Copyright (c) 2009 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-July-28 06:34:30> // // ------------------------------------------------------------------ // // This module defines the ZipOutputStream class, which is a stream metaphor for // generating zip files. This class does not depend on Ionic.Zip.ZipFile, but rather // stands alongside it as an alternative "container" for ZipEntry. It replicates a // subset of the properties, including these: // // - Comment // - Encryption // - Password // - CodecBufferSize // - CompressionLevel // - CompressionMethod // - EnableZip64 (UseZip64WhenSaving) // - IgnoreCase (!CaseSensitiveRetrieval) // // It adds these novel methods: // // - PutNextEntry // // // ------------------------------------------------------------------ // using System; using System.Threading; using System.Collections.Generic; using System.IO; using Ionic.Zip; using OfficeOpenXml.Packaging.Ionic.Zlib; namespace OfficeOpenXml.Packaging.Ionic.Zip { /// /// Provides a stream metaphor for generating zip files. /// /// /// /// /// This class writes zip files, as defined in the specification /// for zip files described by PKWare. The compression for this /// implementation is provided by a managed-code version of Zlib, included with /// DotNetZip in the classes in the Ionic.Zlib namespace. /// /// /// /// This class provides an alternative programming model to the one enabled by the /// class. Use this when creating zip files, as an /// alternative to the class, when you would like to use a /// Stream type to write the zip file. /// /// /// /// Both the ZipOutputStream class and the ZipFile class can be used /// to create zip files. Both of them support many of the common zip features, /// including Unicode, different compression levels, and ZIP64. They provide /// very similar performance when creating zip files. /// /// /// /// The ZipFile class is generally easier to use than /// ZipOutputStream and should be considered a higher-level interface. For /// example, when creating a zip file via calls to the PutNextEntry() and /// Write() methods on the ZipOutputStream class, the caller is /// responsible for opening the file, reading the bytes from the file, writing /// those bytes into the ZipOutputStream, setting the attributes on the /// ZipEntry, and setting the created, last modified, and last accessed /// timestamps on the zip entry. All of these things are done automatically by a /// call to ZipFile.AddFile(). /// For this reason, the ZipOutputStream is generally recommended for use /// only when your application emits arbitrary data, not necessarily data from a /// filesystem file, directly into a zip file, and does so using a Stream /// metaphor. /// /// /// /// Aside from the differences in programming model, there are other /// differences in capability between the two classes. /// /// /// /// /// ZipFile can be used to read and extract zip files, in addition to /// creating zip files. ZipOutputStream cannot read zip files. If you want /// to use a stream to read zip files, check out the class. /// /// /// /// ZipOutputStream does not support the creation of segmented or spanned /// zip files. /// /// /// /// ZipOutputStream cannot produce a self-extracting archive. /// /// /// /// /// Be aware that the ZipOutputStream class implements the interface. In order for /// ZipOutputStream to produce a valid zip file, you use use it within /// a using clause (Using in VB), or call the Dispose() method /// explicitly. See the examples for how to employ a using clause. /// /// /// /// Also, a note regarding compression performance: On the desktop .NET /// Framework, DotNetZip can use a multi-threaded compression implementation /// that provides significant speed increases on large files, over 300k or so, /// at the cost of increased memory use at runtime. (The output of the /// compression is almost exactly the same size). But, the multi-threaded /// approach incurs a performance hit on smaller files. There's no way for the /// ZipOutputStream to know whether parallel compression will be beneficial, /// because the ZipOutputStream does not know how much data you will write /// through the stream. You may wish to set the property to zero, if you are compressing /// large files through ZipOutputStream. This will cause parallel /// compression to be used, always. /// /// internal class ZipOutputStream : Stream { /// /// Create a ZipOutputStream, wrapping an existing stream. /// /// /// /// /// The class is generally easier to use when creating /// zip files. The ZipOutputStream offers a different metaphor for creating a /// zip file, based on the class. /// /// /// /// /// /// The stream to wrap. It must be writable. This stream will be closed at /// the time the ZipOutputStream is closed. /// /// /// /// /// This example shows how to create a zip file, using the /// ZipOutputStream class. /// /// /// private void Zipup() /// { /// if (filesToZip.Count == 0) /// { /// System.Console.WriteLine("Nothing to do."); /// return; /// } /// /// using (var raw = File.Open(_outputFileName, FileMode.Create, FileAccess.ReadWrite )) /// { /// using (var output= new ZipOutputStream(raw)) /// { /// output.Password = "VerySecret!"; /// output.Encryption = EncryptionAlgorithm.WinZipAes256; /// /// foreach (string inputFileName in filesToZip) /// { /// System.Console.WriteLine("file: {0}", inputFileName); /// /// output.PutNextEntry(inputFileName); /// using (var input = File.Open(inputFileName, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Write )) /// { /// byte[] buffer= new byte[2048]; /// int n; /// while ((n= input.Read(buffer,0,buffer.Length)) > 0) /// { /// output.Write(buffer,0,n); /// } /// } /// } /// } /// } /// } /// /// /// /// Private Sub Zipup() /// Dim outputFileName As String = "XmlData.zip" /// Dim filesToZip As String() = Directory.GetFiles(".", "*.xml") /// If (filesToZip.Length = 0) Then /// Console.WriteLine("Nothing to do.") /// Else /// Using raw As FileStream = File.Open(outputFileName, FileMode.Create, FileAccess.ReadWrite) /// Using output As ZipOutputStream = New ZipOutputStream(raw) /// output.Password = "VerySecret!" /// output.Encryption = EncryptionAlgorithm.WinZipAes256 /// Dim inputFileName As String /// For Each inputFileName In filesToZip /// Console.WriteLine("file: {0}", inputFileName) /// output.PutNextEntry(inputFileName) /// Using input As FileStream = File.Open(inputFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite) /// Dim n As Integer /// Dim buffer As Byte() = New Byte(2048) {} /// Do While (n = input.Read(buffer, 0, buffer.Length) > 0) /// output.Write(buffer, 0, n) /// Loop /// End Using /// Next /// End Using /// End Using /// End If /// End Sub /// /// public ZipOutputStream(Stream stream) : this(stream, false) { } /// /// Create a ZipOutputStream that writes to a filesystem file. /// /// /// /// The class is generally easier to use when creating /// zip files. The ZipOutputStream offers a different metaphor for creating a /// zip file, based on the class. /// /// /// /// The name of the zip file to create. /// /// /// /// /// This example shows how to create a zip file, using the /// ZipOutputStream class. /// /// /// private void Zipup() /// { /// if (filesToZip.Count == 0) /// { /// System.Console.WriteLine("Nothing to do."); /// return; /// } /// /// using (var output= new ZipOutputStream(outputFileName)) /// { /// output.Password = "VerySecret!"; /// output.Encryption = EncryptionAlgorithm.WinZipAes256; /// /// foreach (string inputFileName in filesToZip) /// { /// System.Console.WriteLine("file: {0}", inputFileName); /// /// output.PutNextEntry(inputFileName); /// using (var input = File.Open(inputFileName, FileMode.Open, FileAccess.Read, /// FileShare.Read | FileShare.Write )) /// { /// byte[] buffer= new byte[2048]; /// int n; /// while ((n= input.Read(buffer,0,buffer.Length)) > 0) /// { /// output.Write(buffer,0,n); /// } /// } /// } /// } /// } /// /// /// /// Private Sub Zipup() /// Dim outputFileName As String = "XmlData.zip" /// Dim filesToZip As String() = Directory.GetFiles(".", "*.xml") /// If (filesToZip.Length = 0) Then /// Console.WriteLine("Nothing to do.") /// Else /// Using output As ZipOutputStream = New ZipOutputStream(outputFileName) /// output.Password = "VerySecret!" /// output.Encryption = EncryptionAlgorithm.WinZipAes256 /// Dim inputFileName As String /// For Each inputFileName In filesToZip /// Console.WriteLine("file: {0}", inputFileName) /// output.PutNextEntry(inputFileName) /// Using input As FileStream = File.Open(inputFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite) /// Dim n As Integer /// Dim buffer As Byte() = New Byte(2048) {} /// Do While (n = input.Read(buffer, 0, buffer.Length) > 0) /// output.Write(buffer, 0, n) /// Loop /// End Using /// Next /// End Using /// End If /// End Sub /// /// public ZipOutputStream(String fileName) { Stream stream = File.Open(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.None); _Init(stream, false, fileName); } /// /// Create a ZipOutputStream. /// /// /// /// See the documentation for the ZipOutputStream(Stream) /// constructor for an example. /// /// /// /// The stream to wrap. It must be writable. /// /// /// /// true if the application would like the stream /// to remain open after the ZipOutputStream has been closed. /// public ZipOutputStream(Stream stream, bool leaveOpen) { _Init(stream, leaveOpen, null); } private void _Init(Stream stream, bool leaveOpen, string name) { // workitem 9307 _outputStream = stream.CanRead ? stream : new CountingStream(stream); CompressionLevel = OfficeOpenXml.Packaging.Ionic.Zlib.CompressionLevel.Default; CompressionMethod = OfficeOpenXml.Packaging.Ionic.Zip.CompressionMethod.Deflate; _encryption = EncryptionAlgorithm.None; _entriesWritten = new Dictionary(StringComparer.Ordinal); _zip64 = Zip64Option.Never; _leaveUnderlyingStreamOpen = leaveOpen; Strategy = Ionic.Zlib.CompressionStrategy.Default; _name = name ?? "(stream)"; #if !NETCF ParallelDeflateThreshold = -1L; #endif } /// Provides a string representation of the instance. /// /// /// This can be useful for debugging purposes. /// /// /// a string representation of the instance. public override String ToString() { return String.Format ("ZipOutputStream::{0}(leaveOpen({1})))", _name, _leaveUnderlyingStreamOpen); } /// /// Sets the password to be used on the ZipOutputStream instance. /// /// /// /// /// /// When writing a zip archive, this password is applied to the entries, not /// to the zip archive itself. It applies to any ZipEntry subsequently /// written to the ZipOutputStream. /// /// /// /// Using a password does not encrypt or protect the "directory" of the /// archive - the list of entries contained in the archive. If you set the /// Password property, the password actually applies to individual /// entries that are added to the archive, subsequent to the setting of this /// property. The list of filenames in the archive that is eventually created /// will appear in clear text, but the contents of the individual files are /// encrypted. This is how Zip encryption works. /// /// /// /// If you set this property, and then add a set of entries to the archive via /// calls to PutNextEntry, then each entry is encrypted with that /// password. You may also want to change the password between adding /// different entries. If you set the password, add an entry, then set the /// password to null (Nothing in VB), and add another entry, the /// first entry is encrypted and the second is not. /// /// /// /// When setting the Password, you may also want to explicitly set the property, to specify how to encrypt the entries added /// to the ZipFile. If you set the Password to a non-null value and do not /// set , then PKZip 2.0 ("Weak") encryption is used. /// This encryption is relatively weak but is very interoperable. If /// you set the password to a null value (Nothing in VB), /// Encryption is reset to None. /// /// /// /// Special case: if you wrap a ZipOutputStream around a non-seekable stream, /// and use encryption, and emit an entry of zero bytes, the Close() or /// PutNextEntry() following the entry will throw an exception. /// /// /// public String Password { set { if (_disposed) { _exceptionPending = true; throw new System.InvalidOperationException("The stream has been closed."); } _password = value; if (_password == null) { _encryption = EncryptionAlgorithm.None; } else if (_encryption == EncryptionAlgorithm.None) { _encryption = EncryptionAlgorithm.PkzipWeak; } } } /// /// The Encryption to use for entries added to the ZipOutputStream. /// /// /// /// /// The specified Encryption is applied to the entries subsequently /// written to the ZipOutputStream instance. /// /// /// /// If you set this to something other than /// EncryptionAlgorithm.None, you will also need to set the /// to a non-null, non-empty value in /// order to actually get encryption on the entry. /// /// /// /// /// ZipOutputStream.Password /// ZipEntry.Encryption public EncryptionAlgorithm Encryption { get { return _encryption; } set { if (_disposed) { _exceptionPending = true; throw new System.InvalidOperationException("The stream has been closed."); } if (value == EncryptionAlgorithm.Unsupported) { _exceptionPending = true; throw new InvalidOperationException("You may not set Encryption to that value."); } _encryption = value; } } /// /// Size of the work buffer to use for the ZLIB codec during compression. /// /// /// /// Setting this may affect performance. For larger files, setting this to a /// larger size may improve performance, but I'm not sure. Sorry, I don't /// currently have good recommendations on how to set it. You can test it if /// you like. /// public int CodecBufferSize { get; set; } /// /// The compression strategy to use for all entries. /// /// /// /// Set the Strategy used by the ZLIB-compatible compressor, when compressing /// data for the entries in the zip archive. Different compression strategies /// work better on different sorts of data. The strategy parameter can affect /// the compression ratio and the speed of compression but not the correctness /// of the compresssion. For more information see . /// public CompressionStrategy Strategy { get; set; } /// /// The type of timestamp attached to the ZipEntry. /// /// /// /// Set this in order to specify the kind of timestamp that should be emitted /// into the zip file for each entry. /// public ZipEntryTimestamp Timestamp { get { return _timestamp; } set { if (_disposed) { _exceptionPending = true; throw new System.InvalidOperationException("The stream has been closed."); } _timestamp = value; } } /// /// Sets the compression level to be used for entries subsequently added to /// the zip archive. /// /// /// /// /// Varying the compression level used on entries can affect the /// size-vs-speed tradeoff when compression and decompressing data streams /// or files. /// /// /// /// As with some other properties on the ZipOutputStream class, like , and , /// setting this property on a ZipOutputStream /// instance will cause the specified CompressionLevel to be used on all /// items that are subsequently added to the /// ZipOutputStream instance. /// /// /// /// If you do not set this property, the default compression level is used, /// which normally gives a good balance of compression efficiency and /// compression speed. In some tests, using BestCompression can /// double the time it takes to compress, while delivering just a small /// increase in compression efficiency. This behavior will vary with the /// type of data you compress. If you are in doubt, just leave this setting /// alone, and accept the default. /// /// public OfficeOpenXml.Packaging.Ionic.Zlib.CompressionLevel CompressionLevel { get; set; } /// /// The compression method used on each entry added to the ZipOutputStream. /// public CompressionMethod CompressionMethod { get; set; } /// /// A comment attached to the zip archive. /// /// /// /// /// /// The application sets this property to specify a comment to be embedded /// into the generated zip archive. /// /// /// /// According to PKWARE's /// zip specification, the comment is not encrypted, even if there is a /// password set on the zip file. /// /// /// /// The specification does not describe how to indicate the encoding used /// on a comment string. Many "compliant" zip tools and libraries use /// IBM437 as the code page for comments; DotNetZip, too, follows that /// practice. On the other hand, there are situations where you want a /// Comment to be encoded with something else, for example using code page /// 950 "Big-5 Chinese". To fill that need, DotNetZip will encode the /// comment following the same procedure it follows for encoding /// filenames: (a) if is /// Never, it uses the default encoding (IBM437). (b) if is Always, it always uses the /// alternate encoding (). (c) if is AsNecessary, it uses the /// alternate encoding only if the default encoding is not sufficient for /// encoding the comment - in other words if decoding the result does not /// produce the original string. This decision is taken at the time of /// the call to ZipFile.Save(). /// /// /// public string Comment { get { return _comment; } set { if (_disposed) { _exceptionPending = true; throw new System.InvalidOperationException("The stream has been closed."); } _comment = value; } } /// /// Specify whether to use ZIP64 extensions when saving a zip archive. /// /// /// /// /// The default value for the property is . is /// safest, in the sense that you will not get an Exception if a /// pre-ZIP64 limit is exceeded. /// /// /// /// You must set this property before calling Write(). /// /// /// public Zip64Option EnableZip64 { get { return _zip64; } set { if (_disposed) { _exceptionPending = true; throw new System.InvalidOperationException("The stream has been closed."); } _zip64 = value; } } /// /// Indicates whether ZIP64 extensions were used when saving the zip archive. /// /// /// /// The value is defined only after the ZipOutputStream has been closed. /// public bool OutputUsedZip64 { get { return _anyEntriesUsedZip64 || _directoryNeededZip64; } } /// /// Whether the ZipOutputStream should use case-insensitive comparisons when /// checking for uniqueness of zip entries. /// /// /// /// /// Though the zip specification doesn't prohibit zipfiles with duplicate /// entries, Sane zip files have no duplicates, and the DotNetZip library /// cannot create zip files with duplicate entries. If an application attempts /// to call with a name that duplicates one /// already used within the archive, the library will throw an Exception. /// /// /// This property allows the application to specify whether the /// ZipOutputStream instance considers ordinal case when checking for /// uniqueness of zip entries. /// /// public bool IgnoreCase { get { return !_DontIgnoreCase; } set { _DontIgnoreCase = !value; } } /// /// Indicates whether to encode entry filenames and entry comments using /// Unicode (UTF-8). /// /// /// /// /// The /// PKWare zip specification provides for encoding file names and file /// comments in either the IBM437 code page, or in UTF-8. This flag selects /// the encoding according to that specification. By default, this flag is /// false, and filenames and comments are encoded into the zip file in the /// IBM437 codepage. Setting this flag to true will specify that filenames /// and comments that cannot be encoded with IBM437 will be encoded with /// UTF-8. /// /// /// /// Zip files created with strict adherence to the PKWare specification with /// respect to UTF-8 encoding can contain entries with filenames containing /// any combination of Unicode characters, including the full range of /// characters from Chinese, Latin, Hebrew, Greek, Cyrillic, and many other /// alphabets. However, because at this time, the UTF-8 portion of the PKWare /// specification is not broadly supported by other zip libraries and /// utilities, such zip files may not be readable by your favorite zip tool or /// archiver. In other words, interoperability will decrease if you set this /// flag to true. /// /// /// /// In particular, Zip files created with strict adherence to the PKWare /// specification with respect to UTF-8 encoding will not work well with /// Explorer in Windows XP or Windows Vista, because Windows compressed /// folders, as far as I know, do not support UTF-8 in zip files. Vista can /// read the zip files, but shows the filenames incorrectly. Unpacking from /// Windows Vista Explorer will result in filenames that have rubbish /// characters in place of the high-order UTF-8 bytes. /// /// /// /// Also, zip files that use UTF-8 encoding will not work well with Java /// applications that use the java.util.zip classes, as of v5.0 of the Java /// runtime. The Java runtime does not correctly implement the PKWare /// specification in this regard. /// /// /// /// As a result, we have the unfortunate situation that "correct" behavior by /// the DotNetZip library with regard to Unicode encoding of filenames during /// zip creation will result in zip files that are readable by strictly /// compliant and current tools (for example the most recent release of the /// commercial WinZip tool); but these zip files will not be readable by /// various other tools or libraries, including Windows Explorer. /// /// /// /// The DotNetZip library can read and write zip files with UTF8-encoded /// entries, according to the PKware spec. If you use DotNetZip for both /// creating and reading the zip file, and you use UTF-8, there will be no /// loss of information in the filenames. For example, using a self-extractor /// created by this library will allow you to unpack files correctly with no /// loss of information in the filenames. /// /// /// /// If you do not set this flag, it will remain false. If this flag is false, /// the ZipOutputStream will encode all filenames and comments using /// the IBM437 codepage. This can cause "loss of information" on some /// filenames, but the resulting zipfile will be more interoperable with other /// utilities. As an example of the loss of information, diacritics can be /// lost. The o-tilde character will be down-coded to plain o. The c with a /// cedilla (Unicode 0xE7) used in Portugese will be downcoded to a c. /// Likewise, the O-stroke character (Unicode 248), used in Danish and /// Norwegian, will be down-coded to plain o. Chinese characters cannot be /// represented in codepage IBM437; when using the default encoding, Chinese /// characters in filenames will be represented as ?. These are all examples /// of "information loss". /// /// /// /// The loss of information associated to the use of the IBM437 encoding is /// inconvenient, and can also lead to runtime errors. For example, using /// IBM437, any sequence of 4 Chinese characters will be encoded as ????. If /// your application creates a ZipOutputStream, does not set the /// encoding, then adds two files, each with names of four Chinese characters /// each, this will result in a duplicate filename exception. In the case /// where you add a single file with a name containing four Chinese /// characters, the zipfile will save properly, but extracting that file /// later, with any zip tool, will result in an error, because the question /// mark is not legal for use within filenames on Windows. These are just a /// few examples of the problems associated to loss of information. /// /// /// /// This flag is independent of the encoding of the content within the entries /// in the zip file. Think of the zip file as a container - it supports an /// encoding. Within the container are other "containers" - the file entries /// themselves. The encoding within those entries is independent of the /// encoding of the zip archive container for those entries. /// /// /// /// Rather than specify the encoding in a binary fashion using this flag, an /// application can specify an arbitrary encoding via the property. Setting the encoding /// explicitly when creating zip archives will result in non-compliant zip /// files that, curiously, are fairly interoperable. The challenge is, the /// PKWare specification does not provide for a way to specify that an entry /// in a zip archive uses a code page that is neither IBM437 nor UTF-8. /// Therefore if you set the encoding explicitly when creating a zip archive, /// you must take care upon reading the zip archive to use the same code page. /// If you get it wrong, the behavior is undefined and may result in incorrect /// filenames, exceptions, stomach upset, hair loss, and acne. /// /// /// [Obsolete("Beginning with v1.9.1.6 of DotNetZip, this property is obsolete. It will be removed in a future version of the library. Use AlternateEncoding and AlternateEncodingUsage instead.")] public bool UseUnicodeAsNecessary { get { return (_alternateEncoding == System.Text.Encoding.UTF8) && (AlternateEncodingUsage == ZipOption.AsNecessary); } set { if (value) { _alternateEncoding = System.Text.Encoding.UTF8; _alternateEncodingUsage = ZipOption.AsNecessary; } else { _alternateEncoding = Ionic.Zip.ZipOutputStream.DefaultEncoding; _alternateEncodingUsage = ZipOption.Never; } } } /// /// The text encoding to use when emitting entries into the zip archive, for /// those entries whose filenames or comments cannot be encoded with the /// default (IBM437) encoding. /// /// /// /// /// In its /// zip specification, PKWare describes two options for encoding /// filenames and comments: using IBM437 or UTF-8. But, some archiving tools /// or libraries do not follow the specification, and instead encode /// characters using the system default code page. For example, WinRAR when /// run on a machine in Shanghai may encode filenames with the Big-5 Chinese /// (950) code page. This behavior is contrary to the Zip specification, but /// it occurs anyway. /// /// /// /// When using DotNetZip to write zip archives that will be read by one of /// these other archivers, set this property to specify the code page to use /// when encoding the and for each ZipEntry in the zip file, for /// values that cannot be encoded with the default codepage for zip files, /// IBM437. This is why this property is "provisional". In all cases, IBM437 /// is used where possible, in other words, where no loss of data would /// result. It is possible, therefore, to have a given entry with a /// Comment encoded in IBM437 and a FileName encoded with the /// specified "provisional" codepage. /// /// /// /// Be aware that a zip file created after you've explicitly set the /// ProvisionalAlternateEncoding property to a value other than /// IBM437 may not be compliant to the PKWare specification, and may not be /// readable by compliant archivers. On the other hand, many (most?) /// archivers are non-compliant and can read zip files created in arbitrary /// code pages. The trick is to use or specify the proper codepage when /// reading the zip. /// /// /// /// When creating a zip archive using this library, it is possible to change /// the value of ProvisionalAlternateEncoding between each entry you /// add, and between adding entries and the call to Close(). Don't do /// this. It will likely result in a zipfile that is not readable. For best /// interoperability, either leave ProvisionalAlternateEncoding /// alone, or specify it only once, before adding any entries to the /// ZipOutputStream instance. There is one exception to this /// recommendation, described later. /// /// /// /// When using an arbitrary, non-UTF8 code page for encoding, there is no /// standard way for the creator application - whether DotNetZip, WinZip, /// WinRar, or something else - to formally specify in the zip file which /// codepage has been used for the entries. As a result, readers of zip files /// are not able to inspect the zip file and determine the codepage that was /// used for the entries contained within it. It is left to the application /// or user to determine the necessary codepage when reading zip files encoded /// this way. If you use an incorrect codepage when reading a zipfile, you /// will get entries with filenames that are incorrect, and the incorrect /// filenames may even contain characters that are not legal for use within /// filenames in Windows. Extracting entries with illegal characters in the /// filenames will lead to exceptions. It's too bad, but this is just the way /// things are with code pages in zip files. Caveat Emptor. /// /// /// /// One possible approach for specifying the code page for a given zip file is /// to describe the code page in a human-readable form in the Zip comment. For /// example, the comment may read "Entries in this archive are encoded in the /// Big5 code page". For maximum interoperability, the zip comment in this /// case should be encoded in the default, IBM437 code page. In this case, /// the zip comment is encoded using a different page than the filenames. To /// do this, Specify ProvisionalAlternateEncoding to your desired /// region-specific code page, once before adding any entries, and then set /// the property and reset /// ProvisionalAlternateEncoding to IBM437 before calling Close(). /// /// [Obsolete("use AlternateEncoding and AlternateEncodingUsage instead.")] public System.Text.Encoding ProvisionalAlternateEncoding { get { if (_alternateEncodingUsage == ZipOption.AsNecessary) return _alternateEncoding; return null; } set { _alternateEncoding = value; _alternateEncodingUsage = ZipOption.AsNecessary; } } /// /// A Text Encoding to use when encoding the filenames and comments for /// all the ZipEntry items, during a ZipFile.Save() operation. /// /// /// /// Whether the encoding specified here is used during the save depends /// on . /// /// public System.Text.Encoding AlternateEncoding { get { return _alternateEncoding; } set { _alternateEncoding = value; } } /// /// A flag that tells if and when this instance should apply /// AlternateEncoding to encode the filenames and comments associated to /// of ZipEntry objects contained within this instance. /// public ZipOption AlternateEncodingUsage { get { return _alternateEncodingUsage; } set { _alternateEncodingUsage = value; } } /// /// The default text encoding used in zip archives. It is numeric 437, also /// known as IBM437. /// /// public static System.Text.Encoding DefaultEncoding { get { return System.Text.Encoding.GetEncoding("IBM437"); } } #if !NETCF /// /// The size threshold for an entry, above which a parallel deflate is used. /// /// /// /// /// /// DotNetZip will use multiple threads to compress any ZipEntry, when /// the CompressionMethod is Deflate, and if the entry is /// larger than the given size. Zero means "always use parallel /// deflate", while -1 means "never use parallel deflate". /// /// /// /// If the entry size cannot be known before compression, as with any entry /// added via a ZipOutputStream, then Parallel deflate will never be /// performed, unless the value of this property is zero. /// /// /// /// A parallel deflate operations will speed up the compression of /// large files, on computers with multiple CPUs or multiple CPU /// cores. For files above 1mb, on a dual core or dual-cpu (2p) /// machine, the time required to compress the file can be 70% of the /// single-threaded deflate. For very large files on 4p machines the /// compression can be done in 30% of the normal time. The downside /// is that parallel deflate consumes extra memory during the deflate, /// and the deflation is slightly less effective. /// /// /// /// Parallel deflate tends to not be as effective as single-threaded deflate /// because the original data stream is split into multiple independent /// buffers, each of which is compressed in parallel. But because they are /// treated independently, there is no opportunity to share compression /// dictionaries, and additional framing bytes must be added to the output /// stream. For that reason, a deflated stream may be slightly larger when /// compressed using parallel deflate, as compared to a traditional /// single-threaded deflate. For files of about 512k, the increase over the /// normal deflate is as much as 5% of the total compressed size. For larger /// files, the difference can be as small as 0.1%. /// /// /// /// Multi-threaded compression does not give as much an advantage when using /// Encryption. This is primarily because encryption tends to slow down /// the entire pipeline. Also, multi-threaded compression gives less of an /// advantage when using lower compression levels, for example . You may have to perform /// some tests to determine the best approach for your situation. /// /// /// /// The default value for this property is -1, which means parallel /// compression will not be performed unless you set it to zero. /// /// /// public long ParallelDeflateThreshold { set { if ((value != 0) && (value != -1) && (value < 64 * 1024)) throw new ArgumentOutOfRangeException("value must be greater than 64k, or 0, or -1"); _ParallelDeflateThreshold = value; } get { return _ParallelDeflateThreshold; } } /// /// The maximum number of buffer pairs to use when performing /// parallel compression. /// /// /// /// /// This property sets an upper limit on the number of memory /// buffer pairs to create when performing parallel /// compression. The implementation of the parallel /// compression stream allocates multiple buffers to /// facilitate parallel compression. As each buffer fills up, /// the stream uses /// ThreadPool.QueueUserWorkItem() to compress those /// buffers in a background threadpool thread. After a buffer /// is compressed, it is re-ordered and written to the output /// stream. /// /// /// /// A higher number of buffer pairs enables a higher degree of /// parallelism, which tends to increase the speed of compression on /// multi-cpu computers. On the other hand, a higher number of buffer /// pairs also implies a larger memory consumption, more active worker /// threads, and a higher cpu utilization for any compression. This /// property enables the application to limit its memory consumption and /// CPU utilization behavior depending on requirements. /// /// /// /// For each compression "task" that occurs in parallel, there are 2 /// buffers allocated: one for input and one for output. This property /// sets a limit for the number of pairs. The total amount of storage /// space allocated for buffering will then be (N*S*2), where N is the /// number of buffer pairs, S is the size of each buffer (). By default, DotNetZip allocates 4 buffer /// pairs per CPU core, so if your machine has 4 cores, and you retain /// the default buffer size of 128k, then the /// ParallelDeflateOutputStream will use 4 * 4 * 2 * 128kb of buffer /// memory in total, or 4mb, in blocks of 128kb. If you then set this /// property to 8, then the number will be 8 * 2 * 128kb of buffer /// memory, or 2mb. /// /// /// /// CPU utilization will also go up with additional buffers, because a /// larger number of buffer pairs allows a larger number of background /// threads to compress in parallel. If you find that parallel /// compression is consuming too much memory or CPU, you can adjust this /// value downward. /// /// /// /// The default value is 16. Different values may deliver better or /// worse results, depending on your priorities and the dynamic /// performance characteristics of your storage and compute resources. /// /// /// /// This property is not the number of buffer pairs to use; it is an /// upper limit. An illustration: Suppose you have an application that /// uses the default value of this property (which is 16), and it runs /// on a machine with 2 CPU cores. In that case, DotNetZip will allocate /// 4 buffer pairs per CPU core, for a total of 8 pairs. The upper /// limit specified by this property has no effect. /// /// /// /// The application can set this value at any time, but it is /// effective only if set before calling /// ZipOutputStream.Write() for the first time. /// /// /// /// /// public int ParallelDeflateMaxBufferPairs { get { return _maxBufferPairs; } set { if (value < 4) throw new ArgumentOutOfRangeException("ParallelDeflateMaxBufferPairs", "Value must be 4 or greater."); _maxBufferPairs = value; } } #endif private void InsureUniqueEntry(ZipEntry ze1) { if (_entriesWritten.ContainsKey(ze1.FileName)) { _exceptionPending = true; throw new ArgumentException(String.Format("The entry '{0}' already exists in the zip archive.", ze1.FileName)); } } internal Stream OutputStream { get { return _outputStream; } } internal String Name { get { return _name; } } /// /// Returns true if an entry by the given name has already been written /// to the ZipOutputStream. /// /// /// /// The name of the entry to scan for. /// /// /// /// true if an entry by the given name has already been written. /// public bool ContainsEntry(string name) { return _entriesWritten.ContainsKey(SharedUtilities.NormalizePathForUseInZipFile(name)); } /// /// Write the data from the buffer to the stream. /// /// /// /// As the application writes data into this stream, the data may be /// compressed and encrypted before being written out to the underlying /// stream, depending on the settings of the /// and the properties. /// /// /// The buffer holding data to write to the stream. /// the offset within that data array to find the first byte to write. /// the number of bytes to write. public override void Write(byte[] buffer, int offset, int count) { if (_disposed) { _exceptionPending = true; throw new System.InvalidOperationException("The stream has been closed."); } if (buffer==null) { _exceptionPending = true; throw new System.ArgumentNullException("buffer"); } if (_currentEntry == null) { _exceptionPending = true; throw new System.InvalidOperationException("You must call PutNextEntry() before calling Write()."); } if (_currentEntry.IsDirectory) { _exceptionPending = true; throw new System.InvalidOperationException("You cannot Write() data for an entry that is a directory."); } if (_needToWriteEntryHeader) _InitiateCurrentEntry(false); if (count != 0) _entryOutputStream.Write(buffer, offset, count); } /// /// Specify the name of the next entry that will be written to the zip file. /// /// /// /// /// Call this method just before calling , to /// specify the name of the entry that the next set of bytes written to /// the ZipOutputStream belongs to. All subsequent calls to Write, /// until the next call to PutNextEntry, /// will be inserted into the named entry in the zip file. /// /// /// /// If the used in PutNextEntry() ends in /// a slash, then the entry added is marked as a directory. Because directory /// entries do not contain data, a call to Write(), before an /// intervening additional call to PutNextEntry(), will throw an /// exception. /// /// /// /// If you don't call Write() between two calls to /// PutNextEntry(), the first entry is inserted into the zip file as a /// file of zero size. This may be what you want. /// /// /// /// Because PutNextEntry() closes out the prior entry, if any, this /// method may throw if there is a problem with the prior entry. /// /// /// /// This method returns the ZipEntry. You can modify public properties /// on the ZipEntry, such as , , and so on, until the first call to /// ZipOutputStream.Write(), or until the next call to /// PutNextEntry(). If you modify the ZipEntry after /// having called Write(), you may get a runtime exception, or you may /// silently get an invalid zip archive. /// /// /// /// /// /// /// This example shows how to create a zip file, using the /// ZipOutputStream class. /// /// /// private void Zipup() /// { /// using (FileStream fs raw = File.Open(_outputFileName, FileMode.Create, FileAccess.ReadWrite )) /// { /// using (var output= new ZipOutputStream(fs)) /// { /// output.Password = "VerySecret!"; /// output.Encryption = EncryptionAlgorithm.WinZipAes256; /// output.PutNextEntry("entry1.txt"); /// byte[] buffer= System.Text.Encoding.ASCII.GetBytes("This is the content for entry #1."); /// output.Write(buffer,0,buffer.Length); /// output.PutNextEntry("entry2.txt"); // this will be zero length /// output.PutNextEntry("entry3.txt"); /// buffer= System.Text.Encoding.ASCII.GetBytes("This is the content for entry #3."); /// output.Write(buffer,0,buffer.Length); /// } /// } /// } /// /// /// /// /// The name of the entry to be added, including any path to be used /// within the zip file. /// /// /// /// The ZipEntry created. /// /// public ZipEntry PutNextEntry(String entryName) { if (String.IsNullOrEmpty(entryName)) throw new ArgumentNullException("entryName"); if (_disposed) { _exceptionPending = true; throw new System.InvalidOperationException("The stream has been closed."); } _FinishCurrentEntry(); _currentEntry = ZipEntry.CreateForZipOutputStream(entryName); _currentEntry._container = new ZipContainer(this); _currentEntry._BitField |= 0x0008; // workitem 8932 _currentEntry.SetEntryTimes(DateTime.Now, DateTime.Now, DateTime.Now); _currentEntry.CompressionLevel = this.CompressionLevel; _currentEntry.CompressionMethod = this.CompressionMethod; _currentEntry.Password = _password; // workitem 13909 _currentEntry.Encryption = this.Encryption; // workitem 12634 _currentEntry.AlternateEncoding = this.AlternateEncoding; _currentEntry.AlternateEncodingUsage = this.AlternateEncodingUsage; if (entryName.EndsWith("/")) _currentEntry.MarkAsDirectory(); _currentEntry.EmitTimesInWindowsFormatWhenSaving = ((_timestamp & ZipEntryTimestamp.Windows) != 0); _currentEntry.EmitTimesInUnixFormatWhenSaving = ((_timestamp & ZipEntryTimestamp.Unix) != 0); InsureUniqueEntry(_currentEntry); _needToWriteEntryHeader = true; return _currentEntry; } private void _InitiateCurrentEntry(bool finishing) { // If finishing==true, this means we're initiating the entry at the time of // Close() or PutNextEntry(). If this happens, it means no data was written // for the entry - Write() was never called. (The usual case us to call // _InitiateCurrentEntry(bool) from within Write().) If finishing==true, // the entry could be either a zero-byte file or a directory. _entriesWritten.Add(_currentEntry.FileName,_currentEntry); _entryCount++; // could use _entriesWritten.Count, but I don't want to incur // the cost. if (_entryCount > 65534 && _zip64 == Zip64Option.Never) { _exceptionPending = true; throw new System.InvalidOperationException("Too many entries. Consider setting ZipOutputStream.EnableZip64."); } // Write out the header. // // If finishing, and encryption is in use, then we don't want to emit the // normal encryption header. Signal that with a cycle=99 to turn off // encryption for zero-byte entries or directories. // // If finishing, then we know the stream length is zero. Else, unknown // stream length. Passing stream length == 0 allows an optimization so as // not to setup an encryption or deflation stream, when stream length is // zero. _currentEntry.WriteHeader(_outputStream, finishing ? 99 : 0); _currentEntry.StoreRelativeOffset(); if (!_currentEntry.IsDirectory) { _currentEntry.WriteSecurityMetadata(_outputStream); _currentEntry.PrepOutputStream(_outputStream, finishing ? 0 : -1, out _outputCounter, out _encryptor, out _deflater, out _entryOutputStream); } _needToWriteEntryHeader = false; } private void _FinishCurrentEntry() { if (_currentEntry != null) { if (_needToWriteEntryHeader) _InitiateCurrentEntry(true); // an empty entry - no writes _currentEntry.FinishOutputStream(_outputStream, _outputCounter, _encryptor, _deflater, _entryOutputStream); _currentEntry.PostProcessOutput(_outputStream); // workitem 12964 if (_currentEntry.OutputUsedZip64!=null) _anyEntriesUsedZip64 |= _currentEntry.OutputUsedZip64.Value; // reset all the streams _outputCounter = null; _encryptor = _deflater = null; _entryOutputStream = null; } } /// /// Dispose the stream /// /// /// /// /// This method writes the Zip Central directory, then closes the stream. The /// application must call Dispose() (or Close) in order to produce a valid zip file. /// /// /// /// Typically the application will call Dispose() implicitly, via a using /// statement in C#, or a Using statement in VB. /// /// /// /// /// set this to true, always. protected override void Dispose(bool disposing) { if (_disposed) return; if (disposing) // not called from finalizer { // handle pending exceptions if (!_exceptionPending) { _FinishCurrentEntry(); _directoryNeededZip64 = ZipOutput.WriteCentralDirectoryStructure(_outputStream, _entriesWritten.Values, 1, // _numberOfSegmentsForMostRecentSave, _zip64, Comment, new ZipContainer(this)); Stream wrappedStream = null; CountingStream cs = _outputStream as CountingStream; if (cs != null) { wrappedStream = cs.WrappedStream; #if NETCF cs.Close(); #else cs.Dispose(); #endif } else { wrappedStream = _outputStream; } if (!_leaveUnderlyingStreamOpen) { #if NETCF wrappedStream.Close(); #else wrappedStream.Dispose(); #endif } _outputStream = null; } } _disposed = true; } /// /// Always returns false. /// public override bool CanRead { get { return false; } } /// /// Always returns false. /// public override bool CanSeek { get { return false; } } /// /// Always returns true. /// public override bool CanWrite { get { return true; } } /// /// Always returns a NotSupportedException. /// public override long Length { get { throw new NotSupportedException(); } } /// /// Setting this property always returns a NotSupportedException. Getting it /// returns the value of the Position on the underlying stream. /// public override long Position { get { return _outputStream.Position; } set { throw new NotSupportedException(); } } /// /// This is a no-op. /// public override void Flush() { } /// /// This method always throws a NotSupportedException. /// /// ignored /// ignored /// ignored /// nothing public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException("Read"); } /// /// This method always throws a NotSupportedException. /// /// ignored /// ignored /// nothing public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException("Seek"); } /// /// This method always throws a NotSupportedException. /// /// ignored public override void SetLength(long value) { throw new NotSupportedException(); } private EncryptionAlgorithm _encryption; private ZipEntryTimestamp _timestamp; internal String _password; private String _comment; private Stream _outputStream; private ZipEntry _currentEntry; internal Zip64Option _zip64; private Dictionary _entriesWritten; private int _entryCount; private ZipOption _alternateEncodingUsage = ZipOption.Never; private System.Text.Encoding _alternateEncoding = System.Text.Encoding.GetEncoding("IBM437"); // default = IBM437 private bool _leaveUnderlyingStreamOpen; private bool _disposed; private bool _exceptionPending; // **see note below private bool _anyEntriesUsedZip64, _directoryNeededZip64; private CountingStream _outputCounter; private Stream _encryptor; private Stream _deflater; private Ionic.Crc.CrcCalculatorStream _entryOutputStream; private bool _needToWriteEntryHeader; private string _name; private bool _DontIgnoreCase; #if !NETCF internal ParallelDeflateOutputStream ParallelDeflater; private long _ParallelDeflateThreshold; private int _maxBufferPairs = 16; #endif // **Note regarding exceptions: // When ZipOutputStream is employed within a using clause, which // is the typical scenario, and an exception is thrown within // the scope of the using, Close()/Dispose() is invoked // implicitly before processing the initial exception. In that // case, _exceptionPending is true, and we don't want to try to // write anything in the Close/Dispose logic. Doing so can // cause additional exceptions that mask the original one. So, // the _exceptionPending flag is used to track that, and to // allow the original exception to be propagated to the // application without extra "noise." } internal class ZipContainer { private ZipFile _zf; private ZipOutputStream _zos; private ZipInputStream _zis; public ZipContainer(Object o) { _zf = (o as ZipFile); _zos = (o as ZipOutputStream); _zis = (o as ZipInputStream); } public ZipFile ZipFile { get { return _zf; } } public ZipOutputStream ZipOutputStream { get { return _zos; } } public string Name { get { if (_zf != null) return _zf.Name; if (_zis != null) throw new NotSupportedException(); return _zos.Name; } } public string Password { get { if (_zf != null) return _zf._Password; if (_zis != null) return _zis._Password; return _zos._password; } } public Zip64Option Zip64 { get { if (_zf != null) return _zf._zip64; if (_zis != null) throw new NotSupportedException(); return _zos._zip64; } } public int BufferSize { get { if (_zf != null) return _zf.BufferSize; if (_zis != null) throw new NotSupportedException(); return 0; } } #if !NETCF public Ionic.Zlib.ParallelDeflateOutputStream ParallelDeflater { get { if (_zf != null) return _zf.ParallelDeflater; if (_zis != null) return null; return _zos.ParallelDeflater; } set { if (_zf != null) _zf.ParallelDeflater = value; else if (_zos != null) _zos.ParallelDeflater = value; } } public long ParallelDeflateThreshold { get { if (_zf != null) return _zf.ParallelDeflateThreshold; return _zos.ParallelDeflateThreshold; } } public int ParallelDeflateMaxBufferPairs { get { if (_zf != null) return _zf.ParallelDeflateMaxBufferPairs; return _zos.ParallelDeflateMaxBufferPairs; } } #endif public int CodecBufferSize { get { if (_zf != null) return _zf.CodecBufferSize; if (_zis != null) return _zis.CodecBufferSize; return _zos.CodecBufferSize; } } public Ionic.Zlib.CompressionStrategy Strategy { get { if (_zf != null) return _zf.Strategy; return _zos.Strategy; } } public Zip64Option UseZip64WhenSaving { get { if (_zf != null) return _zf.UseZip64WhenSaving; return _zos.EnableZip64; } } public System.Text.Encoding AlternateEncoding { get { if (_zf != null) return _zf.AlternateEncoding; if (_zos!=null) return _zos.AlternateEncoding; return null; } } public System.Text.Encoding DefaultEncoding { get { if (_zf != null) return ZipFile.DefaultEncoding; if (_zos!=null) return ZipOutputStream.DefaultEncoding; return null; } } public ZipOption AlternateEncodingUsage { get { if (_zf != null) return _zf.AlternateEncodingUsage; if (_zos!=null) return _zos.AlternateEncodingUsage; return ZipOption.Never; // n/a } } public Stream ReadStream { get { if (_zf != null) return _zf.ReadStream; return _zis.ReadStream; } } } }