/Utilities/Compression/Streams/ZipOutputStream.cs
C# | 829 lines | 507 code | 87 blank | 235 comment | 83 complexity | 37589ea4fd71f20ead507ba2e3e7d100 MD5 | raw file
Possible License(s): Apache-2.0
- // Based on Mike Krueger's SharpZipLib, Copyright (C) 2001 (GNU license).
- // Authors of the original java version: Jochen Hoenicke, John Leuner
- // See http://www.ISeeSharpCode.com for more information.
-
- using System;
- using System.Collections.Generic;
- using System.IO;
- using Delta.Utilities.Compression.Checksums;
- using Delta.Utilities.Compression.Deflaters;
- using Delta.Utilities.Helpers;
-
- namespace Delta.Utilities.Compression.Streams
- {
-
- #region Summary
- /// <summary>
- /// This is a DeflaterOutputStream that writes the files into a zip
- /// archive one after another. It has a special method to start a new
- /// zip entry. The zip entries contains information about the file name
- /// size, compressed size, CRC, etc.
- ///
- /// It includes support for Stored and Deflated entries.
- /// This class is not thread safe.
- /// </summary>
- /// <example>This sample shows how to create a zip file
- /// <code>
- /// using System;
- /// using System.IO;
- ///
- /// using Delta.Utilities.Compression;
- ///
- /// class MainClass
- /// {
- /// public static void Main(string[] args)
- /// {
- /// string[] filenames = Directory.GetFiles(args[0]);
- ///
- /// ZipOutputStream s = new ZipOutputStream(File.Create(args[1]));
- ///
- /// s.SetLevel(5); // 0 - store only to 9 - means best compression
- ///
- /// foreach (string file in filenames)
- /// {
- /// FileStream fs = File.OpenRead(file);
- ///
- /// byte[] buffer = new byte[fs.Length];
- /// fs.Read(buffer, 0, buffer.Length);
- ///
- /// ZipEntry entry = new ZipEntry(file);
- ///
- /// s.PutNextEntry(entry);
- ///
- /// s.Write(buffer, 0, buffer.Length);
- /// } // foreach
- ///
- /// s.Finish();
- /// s.Close();
- /// } // Main(args)
- /// } // class MainClass
- /// </code>
- /// </example>
- #endregion
-
- public class ZipOutputStream : DeflaterOutputStream
- {
- #region IsFinished (Public)
- /// <summary>
- /// Gets boolean indicating central header has been added for this
- /// archive... No further entries can be added once this has been done.
- /// </summary>
- public bool IsFinished
- {
- get
- {
- return entries == null;
- } // get
- }
- #endregion
-
- #region Private
-
- #region entries (Private)
- /// <summary>
- /// Entries
- /// </summary>
- private List<ZipEntry> entries = new List<ZipEntry>();
- #endregion
-
- #region crc (Private)
- /// <summary>
- /// Crc
- /// </summary>
- /// <returns>Crc 32</returns>
- private readonly Crc32 crc = new Crc32();
- #endregion
-
- #region curEntry (Private)
- /// <summary>
- /// Cur entry
- /// </summary>
- /// <returns>Null</returns>
- private ZipEntry curEntry;
- #endregion
-
- #region defaultCompressionLevel (Private)
- /// <summary>
- /// Default compression level
- /// </summary>
- /// <returns>Default compression</returns>
- private int defaultCompressionLevel;
- #endregion
-
- #region curMethod (Private)
- /// <summary>
- /// Cur method
- /// </summary>
- /// <returns>Deflated</returns>
- private CompressionMethod curMethod;
- #endregion
-
- #region size (Private)
- /// <summary>
- /// Size
- /// </summary>
- private long size;
- #endregion
-
- #region currentOffset (Private)
- /// <summary>
- /// Offset
- /// </summary>
- /// <returns>0</returns>
- private long currentOffset;
- #endregion
-
- #region zipComment (Private)
- /// <summary>
- /// Zip comment
- /// </summary>
- /// <returns>Byte</returns>
- private byte[] zipComment;
- #endregion
-
- #region patchEntryHeader (Private)
- /// <summary>
- /// Patch entry header
- /// </summary>
- /// <returns>False</returns>
- private bool patchEntryHeader;
- #endregion
-
- #region headerPatchPos (Private)
- /// <summary>
- /// Header patch pos
- /// </summary>
- /// <returns>-</returns>
- private long headerPatchPos;
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Creates a new Zip output stream, writing a zip archive.
- /// </summary>
- /// <param name="baseOutputStream">
- /// The output stream to which the archive contents are written.
- /// </param>
- internal ZipOutputStream(Stream baseOutputStream)
- : this(baseOutputStream, true)
- {
- }
-
- /// <summary>
- /// Creates a new Zip output stream, writing a zip archive.
- /// </summary>
- /// <param name="baseOutputStream">
- /// the output stream to which the zip archive is written.
- /// </param>
- /// <param name="nowrap">
- /// Specify this to skip the header and footer for the zip file.
- /// </param>
- internal ZipOutputStream(Stream baseOutputStream, bool nowrap)
- : base(baseOutputStream,
- new Deflater(Deflater.DefaultCompression, nowrap))
- {
- defaultCompressionLevel = Deflater.DefaultCompression;
- curMethod = CompressionMethod.Deflated;
- patchEntryHeader = false;
- headerPatchPos = MathHelper.InvalidIndex;
- zipComment = new byte[0];
- }
- #endregion
-
- #region SetComment (Public)
- /// <summary>
- /// Set the zip file comment.
- /// </summary>
- /// <param name="comment">
- /// The comment string
- /// </param>
- /// <exception name ="ArgumentOutOfRangeException">
- /// Encoding of comment is longer than 0xffff bytes.
- /// </exception>
- public void SetComment(string comment)
- {
- byte[] commentBytes = StringHelper.ToByteArray(comment);
- if (commentBytes.Length > 0xffff)
- {
- throw new ArgumentOutOfRangeException("comment");
- } // if
- zipComment = commentBytes;
- }
- #endregion
-
- #region SetCompressionLevel (Public)
- /// <summary>
- /// Sets default compression level (0=none - 9=best). The new level will be
- /// activated immediately.
- /// </summary>
- /// <exception cref="ArgumentOutOfRangeException">
- /// Level specified is not supported.
- /// </exception>
- /// <see cref="Deflater"/>
- public void SetCompressionLevel(int level)
- {
- defaultCompressionLevel = level;
- deflater.SetLevel(level);
- }
- #endregion
-
- #region GetLevel (Public)
- /// <summary>
- /// Get the current deflate compression level
- /// </summary>
- /// <returns>The current compression level</returns>
- public int GetLevel()
- {
- return deflater.GetLevel();
- }
- #endregion
-
- #region Write (Public)
- /// <summary>
- /// Writes the given buffer to the current entry.
- /// </summary>
- /// <exception cref="ZipException">
- /// Archive size is invalid
- /// </exception>
- /// <exception cref="System.InvalidOperationException">
- /// No entry is active.
- /// </exception>
- public override void Write(byte[] buffer, int offset, int count)
- {
- if (curEntry == null)
- {
- throw new InvalidOperationException("No open entry.");
- } // if
-
- if (count <= 0)
- {
- return;
- } // if
-
- crc.Update(buffer, offset, count);
- size += count;
-
- if (size > 0xffffffff ||
- size < 0)
- {
- throw new ZipException("Maximum entry size exceeded");
- } // if
-
- switch (curMethod)
- {
- case CompressionMethod.Deflated:
- base.Write(buffer, offset, count);
- break;
-
- case CompressionMethod.Stored:
- if (Password != null)
- {
- byte[] buf = new byte[count];
- Array.Copy(buffer, offset, buf, 0, count);
- EncryptBlock(buf, 0, count);
- baseOutputStream.Write(buf, offset, count);
- } // if
- else
- {
- baseOutputStream.Write(buffer, offset, count);
- } // else
- break;
- } // switch
- }
- #endregion
-
- #region Finish (Public)
- /// <summary>
- /// Finishes the stream. This will write the central directory at the
- /// end of the zip file and flush the stream.
- /// </summary>
- /// <remarks>
- /// This is automatically called when the stream is closed.
- /// </remarks>
- /// <exception cref="System.IO.IOException">
- /// An I/O error occurs.
- /// </exception>
- /// <exception cref="ZipException">
- /// Comment exceeds the maximum length<br/>
- /// Entry name exceeds the maximum length
- /// </exception>
- public override void Finish()
- {
- if (entries == null ||
- baseOutputStream == null ||
- baseOutputStream.CanWrite == false)
- {
- return;
- } // if
-
- if (curEntry != null)
- {
- CloseEntry();
- } // if
-
- int numEntries = 0;
- int sizeEntries = 0;
-
- foreach (ZipEntry entry in entries)
- {
- CompressionMethod method = entry.CompressionMethod;
- WriteLeInt(ZipConstants.CentralDirectorySig);
- WriteLeShort(ZipConstants.VersionMadeBy);
- WriteLeShort(entry.Version);
- WriteLeShort(entry.Flags);
- WriteLeShort((short)method);
- WriteLeInt((int)entry.DosTime);
- WriteLeInt((int)entry.Crc);
- WriteLeInt((int)entry.CompressedSize);
- WriteLeInt((int)entry.Size);
-
- byte[] name = StringHelper.ToByteArray(entry.Name);
-
- if (name.Length > 0xffff)
- {
- throw new ZipException("Name too long.");
- } // if
-
- byte[] extra = entry.GetExtraData();
- if (extra == null)
- {
- extra = new byte[0];
- } // if
-
- byte[] entryComment = (entry.Comment != null)
- ? StringHelper.ToByteArray(entry.Comment)
- : new byte[0];
- if (entryComment.Length > 0xffff)
- {
- throw new ZipException("Comment too long.");
- } // if
-
- WriteLeShort(name.Length);
- WriteLeShort(extra.Length);
- WriteLeShort(entryComment.Length);
- // disk number
- WriteLeShort(0);
- // internal file attributes
- WriteLeShort(0);
-
- // external file attribute
- if (entry.ExternalFileAttributes != MathHelper.InvalidIndex)
- {
- WriteLeInt(entry.ExternalFileAttributes);
- } // if
- else
- {
- // mark entry as directory (from nikolam.AT.perfectinfo.com)
- if (entry.IsDirectory)
- {
- WriteLeInt(16);
- } // if
- else
- {
- WriteLeInt(0);
- } // else
- } // else
-
- WriteLeInt(entry.Offset);
-
- baseOutputStream.Write(name, 0, name.Length);
- baseOutputStream.Write(extra, 0, extra.Length);
- baseOutputStream.Write(entryComment, 0, entryComment.Length);
- ++numEntries;
- sizeEntries += ZipConstants.CentralHeader + name.Length +
- extra.Length + entryComment.Length;
- } // foreach
-
- WriteLeInt(ZipConstants.EndSig);
- // number of this disk
- WriteLeShort(0);
- // no of disk with start of central dir
- WriteLeShort(0);
- // entries in central dir for this disk
- WriteLeShort(numEntries);
- // total entries in central directory
- WriteLeShort(numEntries);
- // size of the central directory
- WriteLeInt(sizeEntries);
- // offset of start of central dir
- WriteLeInt((int)currentOffset);
- WriteLeShort(zipComment.Length);
- baseOutputStream.Write(zipComment, 0, zipComment.Length);
- baseOutputStream.Flush();
- entries = null;
- }
- #endregion
-
- #region Methods (Private)
-
- #region WriteLeShort
- /// <summary>
- /// Write an unsigned short in little endian byte order.
- /// </summary>
- private void WriteLeShort(int value)
- {
- baseOutputStream.WriteByte((byte)(value & 0xff));
- baseOutputStream.WriteByte((byte)((value >> 8) & 0xff));
- }
- #endregion
-
- #region WriteLeInt
- /// <summary>
- /// Write an int in little endian byte order.
- /// </summary>
- private void WriteLeInt(int value)
- {
- WriteLeShort(value);
- WriteLeShort(value >> 16);
- }
- #endregion
-
- #region WriteLeLong
- /// <summary>
- /// Write an int in little endian byte order.
- /// </summary>
- private void WriteLeLong(long value)
- {
- WriteLeInt((int)value);
- WriteLeInt((int)(value >> 32));
- }
- #endregion
-
- #region PutNextEntry
- /// <summary>
- /// Starts a new Zip entry. It automatically closes the previous
- /// entry if present.
- /// All entry elements bar name are optional, but must be correct if present.
- /// If the compression method is stored and the output is not patchable
- /// the compression for that entry is automatically changed to deflate level 0
- /// </summary>
- /// <param name="entry">
- /// the entry.
- /// </param>
- /// <exception cref="System.IO.IOException">
- /// if an I/O error occured.
- /// </exception>
- /// <exception cref="System.InvalidOperationException">
- /// if stream was finished
- /// </exception>
- /// <exception cref="ZipException">
- /// Too many entries in the Zip file<br/>
- /// Entry name is too long<br/>
- /// Finish has already been called<br/>
- /// </exception>
- internal void PutNextEntry(ZipEntry entry)
- {
- if (entries == null)
- {
- throw new InvalidOperationException("ZipOutputStream was finished");
- } // if
-
- if (curEntry != null)
- {
- CloseEntry();
- } // if
-
- if (entries.Count >= 0xffff)
- {
- throw new ZipException("Too many entries for Zip file");
- } // if
-
- CompressionMethod method = entry.CompressionMethod;
- int compressionLevel = defaultCompressionLevel;
-
- entry.Flags = 0;
- patchEntryHeader = false;
- bool headerInfoAvailable = true;
- PrepareEntryHeader(entry, ref method, ref compressionLevel,
- ref headerInfoAvailable);
-
- curMethod = method;
-
- // Write the local file header
- WriteLeInt(ZipConstants.LocalSignature);
-
- WriteLeShort(entry.Version);
- WriteLeShort(entry.Flags);
- WriteLeShort((byte)method);
- WriteLeInt((int)entry.DosTime);
- if (headerInfoAvailable)
- {
- WriteLeInt((int)entry.Crc);
- WriteLeInt(entry.IsCrypted
- ? (int)entry.CompressedSize + ZipConstants.CryptoHeaderSize
- : (int)entry.CompressedSize);
- WriteLeInt((int)entry.Size);
- } // if
- else
- {
- if (patchEntryHeader)
- {
- headerPatchPos = baseOutputStream.Position;
- } // if
-
- // Crc
- WriteLeInt(0);
- // Compressed size
- WriteLeInt(0);
- // Uncompressed size
- WriteLeInt(0);
- } // else
-
- byte[] name = StringHelper.ToByteArray(entry.Name);
- if (name.Length > 0xFFFF)
- {
- throw new ZipException("Entry name too long.");
- } // if
-
- byte[] extra = entry.GetExtraData();
- if (extra == null)
- {
- extra = new byte[0];
- } // if
-
- if (extra.Length > 0xFFFF)
- {
- throw new ZipException("Extra data too long.");
- } // if
-
- WriteLeShort(name.Length);
- WriteLeShort(extra.Length);
- baseOutputStream.Write(name, 0, name.Length);
- baseOutputStream.Write(extra, 0, extra.Length);
-
- currentOffset += ZipConstants.LocalHeader + name.Length + extra.Length;
-
- // Activate the entry.
- curEntry = entry;
- crc.Reset();
- if (method == CompressionMethod.Deflated)
- {
- deflater.Reset();
- deflater.SetLevel(compressionLevel);
- } // if
- size = 0;
-
- if (entry.IsCrypted)
- {
- if (entry.Crc < 0)
- {
- // so testing Zip will says its ok
- WriteEncryptionHeader(entry.DosTime << 16);
- } // if
- else
- {
- WriteEncryptionHeader(entry.Crc);
- } // else
- } // if
- }
- #endregion
-
- #region PrepareEntryHeader
- /// <summary>
- /// Prepare entry header
- /// </summary>
- /// <param name="entry">Entry</param>
- /// <param name="method">Method</param>
- /// <param name="compressionLevel">Compression level</param>
- /// <param name="headerInfoAvailable">Header info available</param>
- private void PrepareEntryHeader(ZipEntry entry,
- ref CompressionMethod method, ref int compressionLevel,
- ref bool headerInfoAvailable)
- {
- if (method == CompressionMethod.Stored)
- {
- if (entry.CompressedSize >= 0)
- {
- if (entry.Size < 0)
- {
- entry.Size = entry.CompressedSize;
- } // if
- else if (entry.Size != entry.CompressedSize)
- {
- throw new ZipException(
- "Method Stored, but compressed size != size");
- } // else if
- } // if
- else
- {
- if (entry.Size >= 0)
- {
- entry.CompressedSize = entry.Size;
- } // if
- } // else
-
- if (entry.Size < 0 ||
- entry.Crc < 0)
- {
- if (CanPatchEntries)
- {
- headerInfoAvailable = false;
- } // if
- else
- {
- // Cant patch entries so storing is not possible.
- method = CompressionMethod.Deflated;
- compressionLevel = 0;
- } // else
- } // if
- } // if
-
- if (method == CompressionMethod.Deflated)
- {
- if (entry.Size == 0)
- {
- // No need to compress - no data.
- entry.CompressedSize = entry.Size;
- entry.Crc = 0;
- method = CompressionMethod.Stored;
- } // if
- else if (entry.CompressedSize < 0 ||
- entry.Size < 0 ||
- entry.Crc < 0)
- {
- headerInfoAvailable = false;
- } // else if
- } // if
-
- if (headerInfoAvailable == false)
- {
- if (CanPatchEntries == false)
- {
- entry.Flags |= 8;
- } // if
- else
- {
- patchEntryHeader = true;
- } // else
- } // if
-
- if (Password != null)
- {
- entry.IsCrypted = true;
- if (entry.Crc < 0)
- {
- // Need to append data descriptor as crc is used for encryption and
- // its not known.
- entry.Flags |= 8;
- } // if
- } // if
- entry.Offset = (int)currentOffset;
- entry.CompressionMethod = method;
- }
- #endregion
-
- #region CloseEntry
- /// <summary>
- /// Closes the current entry, updating header and footer information as
- /// required
- /// </summary>
- /// <exception cref="System.IO.IOException">
- /// An I/O error occurs.
- /// </exception>
- /// <exception cref="System.InvalidOperationException">
- /// No entry is active.
- /// </exception>
- internal void CloseEntry()
- {
- if (curEntry == null)
- {
- throw new InvalidOperationException("No open entry");
- } // if
-
- // First finish the deflater, if appropriate
- if (curMethod == CompressionMethod.Deflated)
- {
- base.Finish();
- } // if
-
- long csize = curMethod == CompressionMethod.Deflated
- ? deflater.TotalOut
- : size;
-
- if (curEntry.Size < 0)
- {
- curEntry.Size = size;
- } // if
- else if (curEntry.Size != size)
- {
- throw new ZipException("size was " + size + ", but I expected " +
- curEntry.Size);
- } // else if
-
- if (curEntry.CompressedSize < 0)
- {
- curEntry.CompressedSize = csize;
- } // if
- else if (curEntry.CompressedSize != csize)
- {
- throw new ZipException("compressed size was " + csize +
- ", but I expected " + curEntry.CompressedSize);
- } // else if
-
- if (curEntry.Crc < 0)
- {
- curEntry.Crc = crc.Value;
- } // if
- else if (curEntry.Crc != crc.Value)
- {
- throw new ZipException("crc was " + crc.Value + ", but I expected " +
- curEntry.Crc);
- } // else if
-
- currentOffset += csize;
-
- if (currentOffset > 0xffffffff)
- {
- throw new ZipException("Maximum Zip file size exceeded");
- } // if
-
- if (curEntry.IsCrypted)
- {
- curEntry.CompressedSize += ZipConstants.CryptoHeaderSize;
- } // if
-
- // Patch the header if possible
- if (patchEntryHeader)
- {
- long curPos = baseOutputStream.Position;
- baseOutputStream.Seek(headerPatchPos, SeekOrigin.Begin);
- WriteLeInt((int)curEntry.Crc);
- WriteLeInt((int)curEntry.CompressedSize);
- WriteLeInt((int)curEntry.Size);
- baseOutputStream.Seek(curPos, SeekOrigin.Begin);
- patchEntryHeader = false;
- } // if
-
- // Add data descriptor if flagged as required
- if ((curEntry.Flags & 8) != 0)
- {
- WriteLeInt(ZipConstants.ExternSig);
- WriteLeInt((int)curEntry.Crc);
- WriteLeInt((int)curEntry.CompressedSize);
- WriteLeInt((int)curEntry.Size);
- currentOffset += ZipConstants.ExternHeader;
- } // if
-
- entries.Add(curEntry);
- curEntry = null;
- }
- #endregion
-
- #region WriteEncryptionHeader
- /// <summary>
- /// Write encryption header
- /// </summary>
- /// <param name="crcValue">Crc value</param>
- private void WriteEncryptionHeader(long crcValue)
- {
- currentOffset += ZipConstants.CryptoHeaderSize;
-
- InitializePassword(Password);
-
- byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize];
- Random rnd = new Random();
- rnd.NextBytes(cryptBuffer);
- cryptBuffer[11] = (byte)(crcValue >> 24);
-
- EncryptBlock(cryptBuffer, 0, cryptBuffer.Length);
- baseOutputStream.Write(cryptBuffer, 0, cryptBuffer.Length);
- }
- #endregion
-
- #region AddFile
- /// <summary>
- /// Add file to zip stream.
- /// Will also make sure the header is updated and we store the
- /// correct crc and file length.
- /// </summary>
- /// <param name="filePath">File to store</param>
- internal void AddFile(string filePath)
- {
- // Read file data (might throw an exception if file doesn't exists)
- FileStream fs = File.OpenRead(filePath);
- byte[] buffer = new byte[fs.Length];
- fs.Read(buffer, 0, buffer.Length);
- ZipEntry entry = new ZipEntry(filePath);
-
- // Set time of storage
- entry.DateTime = DateTime.Now;
-
- // Set file size
- entry.Size = fs.Length;
- fs.Close();
-
- // And finally set crc value
- entry.Crc = Crc32.ComputeCrcValue(buffer);
-
- // Add entry
- PutNextEntry(entry);
- Write(buffer, 0, buffer.Length);
- CloseEntry();
- }
- #endregion
-
- #endregion
- }
- }