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:       /// <exampleThis 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 baseOutputStreambase(baseOutputStreamnew Deflater(Deflater.DEFAULT_COMPRESSIONtrue))
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 || entry.Size || 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(name0name.Length);
322:               baseOutputStream.Write(extra0extra.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