| 1: | // ZipOutputStream.cs | |
| 2: | // Copyright (C) 2001 Mike Krueger | |
| 3: | // | |
| 4: | // This file was translated from java, it was part of the GNU Classpath | |
| 5: | // Copyright (C) 2001 Free Software Foundation, Inc. | |
| 6: | // | |
| 7: | // This program is free software; you can redistribute it and/or | |
| 8: | // modify it under the terms of the GNU General Public License | |
| 9: | // as published by the Free Software Foundation; either version 2 | |
| 10: | // of the License, or (at your option) any later version. | |
| 11: | // | |
| 12: | // This program is distributed in the hope that it will be useful, | |
| 13: | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 14: | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 15: | // GNU General Public License for more details. | |
| 16: | // | |
| 17: | // You should have received a copy of the GNU General Public License | |
| 18: | // along with this program; if not, write to the Free Software | |
| 19: | // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
| 20: | // | |
| 21: | // Linking this library statically or dynamically with other modules is | |
| 22: | // making a combined work based on this library. Thus, the terms and | |
| 23: | // conditions of the GNU General Public License cover the whole | |
| 24: | // combination. | |
| 25: | // | |
| 26: | // As a special exception, the copyright holders of this library give you | |
| 27: | // permission to link this library with independent modules to produce an | |
| 28: | // executable, regardless of the license terms of these independent | |
| 29: | // modules, and to copy and distribute the resulting executable under | |
| 30: | // terms of your choice, provided that you also meet, for each linked | |
| 31: | // independent module, the terms and conditions of the license of that | |
| 32: | // module. An independent module is a module which is not derived from | |
| 33: | // or based on this library. If you modify this library, you may extend | |
| 34: | // this exception to your version of the library, but you are not | |
| 35: | // obligated to do so. If you do not wish to do so, delete this | |
| 36: | // exception statement from your version. | |
| 37: | ||
| 38: | using System; | |
| 39: | using System.IO; | |
| 40: | using System.Collections; | |
| 41: | using System.Text; | |
| 42: | ||
| 43: | using ICSharpCode.SharpZipLib.Checksums; | |
| 44: | using ICSharpCode.SharpZipLib.Zip.Compression; | |
| 45: | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; | |
| 46: | ||
| 47: | namespace ICSharpCode.SharpZipLib.Zip { | |
| 48: | ||
| 49: | /// <summary> | |
| 50: | /// This is a FilterOutputStream that writes the files into a zip | |
| 51: | /// archive one after another. It has a special method to start a new | |
| 52: | /// zip entry. The zip entries contains information about the file name | |
| 53: | /// size, compressed size, CRC, etc. | |
| 54: | /// | |
| 55: | /// It includes support for STORED and DEFLATED entries. | |
| 56: | /// This class is not thread safe. | |
| 57: | /// | |
| 58: | /// author of the original java version : Jochen Hoenicke | |
| 59: | /// </summary> | |
| 60: | /// <example> This sample shows how to create a zip file | |
| 61: | /// <code> | |
| 62: | /// using System; | |
| 63: | /// using System.IO; | |
| 64: | /// | |
| 65: | /// using NZlib.Zip; | |
| 66: | /// | |
| 67: | /// class MainClass | |
| 68: | /// { | |
| 69: | /// public static void Main(string[] args) | |
| 70: | /// { | |
| 71: | /// string[] filenames = Directory.GetFiles(args[0]); | |
| 72: | /// | |
| 73: | /// ZipOutputStream s = new ZipOutputStream(File.Create(args[1])); | |
| 74: | /// | |
| 75: | /// s.SetLevel(5); // 0 - store only to 9 - means best compression | |
| 76: | /// | |
| 77: | /// foreach (string file in filenames) { | |
| 78: | /// FileStream fs = File.OpenRead(file); | |
| 79: | /// | |
| 80: | /// byte[] buffer = new byte[fs.Length]; | |
| 81: | /// fs.Read(buffer, 0, buffer.Length); | |
| 82: | /// | |
| 83: | /// ZipEntry entry = new ZipEntry(file); | |
| 84: | /// | |
| 85: | /// s.PutNextEntry(entry); | |
| 86: | /// | |
| 87: | /// s.Write(buffer, 0, buffer.Length); | |
| 88: | /// | |
| 89: | /// } | |
| 90: | /// | |
| 91: | /// s.Finish(); | |
| 92: | /// s.Close(); | |
| 93: | /// } | |
| 94: | /// } | |
| 95: | /// </code> | |
| 96: | /// </example> | |
| 97: | public class ZipOutputStream : DeflaterOutputStream | |
| 98: | { | |
| 99: | private ArrayList entries = new ArrayList(); | |
| 100: | private Crc32 crc = new Crc32(); | |
| 101: | private ZipEntry curEntry = null; | |
| 102: | ||
| 103: | private CompressionMethod curMethod; | |
| 104: | private int size; | |
| 105: | private int offset = 0; | |
| 106: | ||
| 107: | private byte[] zipComment = new byte[0]; | |
| 108: | private int defaultMethod = DEFLATED; | |
| 109: | ||
| 110: | ||
| 111: | /// <summary> | |
| 112: | /// Our Zip version is hard coded to 1.0 resp. 2.0 | |
| 113: | /// </summary> | |
| 114: | private const int ZIP_STORED_VERSION = 10; | |
| 115: | private const int ZIP_DEFLATED_VERSION = 20; | |
| 116: | ||
| 117: | /// <summary> | |
| 118: | /// Compression method. This method doesn't compress at all. | |
| 119: | /// </summary> | |
| 120: | public const int STORED = 0; | |
| 121: | ||
| 122: | /// <summary> | |
| 123: | /// Compression method. This method uses the Deflater. | |
| 124: | /// </summary> | |
| 125: | public const int DEFLATED = 8; | |
| 126: | ||
| 127: | /// <summary> | |
| 128: | /// Creates a new Zip output stream, writing a zip archive. | |
| 129: | /// </summary> | |
| 130: | /// <param name="baseOutputStream"> | |
| 131: | /// the output stream to which the zip archive is written. | |
| 132: | /// </param> | |
| 133: | public ZipOutputStream(Stream baseOutputStream) : base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true)) | |
| 134: | { | |
| 135: | } | |
| 136: | ||
| 137: | /// <summary> | |
| 138: | /// Set the zip file comment. | |
| 139: | /// </summary> | |
| 140: | /// <param name="comment"> | |
| 141: | /// the comment. | |
| 142: | /// </param> | |
| 143: | /// <exception name ="ArgumentException"> | |
| 144: | /// if UTF8 encoding of comment is longer than 0xffff bytes. | |
| 145: | /// </exception> | |
| 146: | public void SetComment(string comment) | |
| 147: | { | |
| 148: | byte[] commentBytes = ZipConstants.ConvertToArray(comment); | |
| 149: | if (commentBytes.Length > 0xffff) { | |
| 150: | throw new ArgumentException("Comment too long."); | |
| 151: | } | |
| 152: | zipComment = commentBytes; | |
| 153: | } | |
| 154: | ||
| 155: | /// <summary> | |
| 156: | /// Sets default compression method. If the Zip entry specifies | |
| 157: | /// another method its method takes precedence. | |
| 158: | /// </summary> | |
| 159: | /// <param name = "method"> | |
| 160: | /// the method. | |
| 161: | /// </param> | |
| 162: | /// <exception name = "ArgumentException"> | |
| 163: | /// if method is not supported. | |
| 164: | /// </exception> | |
| 165: | public void SetMethod(int method) | |
| 166: | { | |
| 167: | if (method != STORED && method != DEFLATED) { | |
| 168: | throw new ArgumentException("Method not supported."); | |
| 169: | } | |
| 170: | defaultMethod = method; | |
| 171: | } | |
| 172: | ||
| 173: | /// <summary> | |
| 174: | /// Sets default compression level. The new level will be activated | |
| 175: | /// immediately. | |
| 176: | /// </summary> | |
| 177: | /// <exception cref="System.ArgumentOutOfRangeException"> | |
| 178: | /// if level is not supported. | |
| 179: | /// </exception> | |
| 180: | /// <see cref="Deflater"/> | |
| 181: | public void SetLevel(int level) | |
| 182: | { | |
| 183: | def.SetLevel(level); | |
| 184: | } | |
| 185: | ||
| 186: | /// <summary> | |
| 187: | /// Write an unsigned short in little endian byte order. | |
| 188: | /// </summary> | |
| 189: | private void WriteLeShort(int value) | |
| 190: | { | |
| 191: | baseOutputStream.WriteByte((byte)value); | |
| 192: | baseOutputStream.WriteByte((byte)(value >> 8)); | |
| 193: | } | |
| 194: | ||
| 195: | /// <summary> | |
| 196: | /// Write an int in little endian byte order. | |
| 197: | /// </summary> | |
| 198: | private void WriteLeInt(int value) | |
| 199: | { | |
| 200: | WriteLeShort(value); | |
| 201: | WriteLeShort(value >> 16); | |
| 202: | } | |
| 203: | ||
| 204: | /// <summary> | |
| 205: | /// Write an int in little endian byte order. | |
| 206: | /// </summary> | |
| 207: | private void WriteLeLong(long value) | |
| 208: | { | |
| 209: | WriteLeInt((int)value); | |
| 210: | WriteLeInt((int)(value >> 32)); | |
| 211: | } | |
| 212: | ||
| 213: | ||
| 214: | bool shouldWriteBack = false; | |
| 215: | long seekPos = -1; | |
| 216: | /// <summary> | |
| 217: | /// Starts a new Zip entry. It automatically closes the previous | |
| 218: | /// entry if present. If the compression method is stored, the entry | |
| 219: | /// must have a valid size and crc, otherwise all elements (except | |
| 220: | /// name) are optional, but must be correct if present. If the time | |
| 221: | /// is not set in the entry, the current time is used. | |
| 222: | /// </summary> | |
| 223: | /// <param name="entry"> | |
| 224: | /// the entry. | |
| 225: | /// </param> | |
| 226: | /// <exception cref="System.IO.IOException"> | |
| 227: | /// if an I/O error occured. | |
| 228: | /// </exception> | |
| 229: | /// <exception cref="System.InvalidOperationException"> | |
| 230: | /// if stream was finished | |
| 231: | /// </exception> | |
| 232: | public void PutNextEntry(ZipEntry entry) | |
| 233: | { | |
| 234: | if (entries == null) { | |
| 235: | throw new InvalidOperationException("ZipOutputStream was finished"); | |
| 236: | } | |
| 237: | ||
| 238: | CompressionMethod method = entry.CompressionMethod; | |
| 239: | int flags = 0; | |
| 240: | ||
| 241: | switch (method) { | |
| 242: | case CompressionMethod.Stored: | |
| 243: | if (entry.CompressedSize >= 0) { | |
| 244: | if (entry.Size < 0) { | |
| 245: | entry.Size = entry.CompressedSize; | |
| 246: | } else if (entry.Size != entry.CompressedSize) { | |
| 247: | throw new ZipException("Method STORED, but compressed size != size"); | |
| 248: | } | |
| 249: | } else { | |
| 250: | entry.CompressedSize = entry.Size; | |
| 251: | } | |
| 252: | ||
| 253: | if (entry.Size < 0) { | |
| 254: | throw new ZipException("Method STORED, but size not set"); | |
| 255: | } else if (entry.Crc < 0) { | |
| 256: | throw new ZipException("Method STORED, but crc not set"); | |
| 257: | } | |
| 258: | break; | |
| 259: | case CompressionMethod.Deflated: | |
| 260: | if (entry.CompressedSize < 0 || entry.Size < 0 || entry.Crc < 0) { | |
| 261: | flags |= 8; | |
| 262: | } | |
| 263: | break; | |
| 264: | } | |
| 265: | ||
| 266: | if (curEntry != null) { | |
| 267: | CloseEntry(); | |
| 268: | } | |
| 269: | ||
| 270: | // if (entry.DosTime < 0) { | |
| 271: | // entry.Time = System.Environment.TickCount; | |
| 272: | // } | |
| 273: | ||
| 274: | entry.flags = flags; | |
| 275: | entry.offset = offset; | |
| 276: | entry.CompressionMethod = (CompressionMethod)method; | |
| 277: | ||
| 278: | curMethod = method; | |
| 279: | // Write the local file header | |
| 280: | WriteLeInt(ZipConstants.LOCSIG); | |
| 281: | ||
| 282: | // write ZIP version | |
| 283: | WriteLeShort(method == CompressionMethod.Stored ? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION); | |
| 284: | if ((flags & 8) == 0) { | |
| 285: | WriteLeShort(flags); | |
| 286: | WriteLeShort((byte)method); | |
| 287: | WriteLeInt(entry.DosTime); | |
| 288: | WriteLeInt((int)entry.Crc); | |
| 289: | WriteLeInt((int)entry.CompressedSize); | |
| 290: | WriteLeInt((int)entry.Size); | |
| 291: | } else { | |
| 292: | if (baseOutputStream.CanSeek) { | |
| 293: | shouldWriteBack = true; | |
| 294: | WriteLeShort((short)(flags & ~8)); | |
| 295: | } else { | |
| 296: | shouldWriteBack = false; | |
| 297: | WriteLeShort(flags); | |
| 298: | } | |
| 299: | WriteLeShort((byte)method); | |
| 300: | WriteLeInt(entry.DosTime); | |
| 301: | seekPos = baseOutputStream.Position; | |
| 302: | WriteLeInt(0); | |
| 303: | WriteLeInt(0); | |
| 304: | WriteLeInt(0); | |
| 305: | } | |
| 306: | byte[] name = ZipConstants.ConvertToArray(entry.Name); | |
| 307: | ||
| 308: | if (name.Length > 0xFFFF) { | |
| 309: | throw new ZipException("Name too long."); | |
| 310: | } | |
| 311: | byte[] extra = entry.ExtraData; | |
| 312: | if (extra == null) { | |
| 313: | extra = new byte[0]; | |
| 314: | } | |
| 315: | if (extra.Length > 0xFFFF) { | |
| 316: | throw new ZipException("Extra data too long."); | |
| 317: | } | |
| 318: | ||
| 319: | WriteLeShort(name.Length); | |
| 320: | WriteLeShort(extra.Length); | |
| 321: | baseOutputStream.Write(name, 0, name.Length); | |
| 322: | baseOutputStream.Write(extra, 0, extra.Length); | |
| 323: | ||
| 324: | offset += ZipConstants.LOCHDR + name.Length + extra.Length; | |
| 325: | ||
| 326: | /* Activate the entry. */ | |
| 327: | curEntry = entry; | |
| 328: | crc.Reset(); | |
| 329: | if (method == CompressionMethod.Deflated) { | |
| 330: | def.Reset(); | |
| 331: | } | |
| 332: | size = 0; | |
| 333: | } | |
| 334: | ||
| 335: | /// <summary> | |
| 336: | /// Closes the current entry. | |
| 337: | /// </summary> | |
| 338: | /// <exception cref="System.IO.IOException"> | |
| 339: | /// if an I/O error occured. | |
| 340: | /// </exception> | |
| 341: | /// <exception cref="System.InvalidOperationException"> | |
| 342: | /// if no entry is active. | |
| 343: | /// </exception> | |
| 344: | public void CloseEntry() | |
| 345: | { | |
| 346: | if (curEntry == null) { | |
| 347: | throw new InvalidOperationException("No open entry"); | |
| 348: | } | |
| 349: | ||
| 350: | /* First finish the deflater, if appropriate */ | |
| 351: | if (curMethod == CompressionMethod.Deflated) { | |
| 352: | base.Finish(); | |
| 353: | } | |
| 354: | ||
| 355: | int csize = curMethod == CompressionMethod.Deflated ? def.TotalOut : size; | |
| 356: | ||
| 357: | if (curEntry.Size < 0) { | |
| 358: | curEntry.Size = size; | |
| 359: | } else if (curEntry.Size != size) { | |
| 360: | throw new ZipException("size was " + size + | |
| 361: | ", but I expected " + curEntry.Size); | |
| 362: | } | |
| 363: | ||
| 364: | if (curEntry.CompressedSize < 0) { | |
| 365: | curEntry.CompressedSize = csize; | |
| 366: | } else if (curEntry.CompressedSize != csize) { | |
| 367: | throw new ZipException("compressed size was " + csize + | |
| 368: | ", but I expected " + curEntry.CompressedSize); | |
| 369: | } | |
| 370: | ||
| 371: | if (curEntry.Crc < 0) { | |
| 372: | curEntry.Crc = crc.Value; | |
| 373: | } else if (curEntry.Crc != crc.Value) { | |
| 374: | throw new ZipException("crc was " + crc.Value + | |
| 375: | ", but I expected " + | |
| 376: | curEntry.Crc); | |
| 377: | } | |
| 378: | ||
| 379: | offset += csize; | |
| 380: | ||
| 381: | /* Now write the data descriptor entry if needed. */ | |
| 382: | if (curMethod == CompressionMethod.Deflated && (curEntry.flags & 8) != 0) { | |
| 383: | if (shouldWriteBack) { | |
| 384: | long curPos = baseOutputStream.Position; | |
| 385: | baseOutputStream.Seek(seekPos |