1 /* java.util.zip.ZipOutputStream
2    Copyright (C) 2001 Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 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 
39 package java.util.zip;
40 
41 import java.io.IOException;
42 import java.io.OutputStream;
43 import java.util.Enumeration;
44 import java.util.Vector;
45 
46 /**
47  * This is a FilterOutputStream that writes the files into a zip
48  * archive one after another.  It has a special method to start a new
49  * zip entry.  The zip entries contains information about the file name
50  * size, compressed size, CRC, etc.
51  *
52  * It includes support for STORED and DEFLATED entries.
53  *
54  * This class is not thread safe.
55  *
56  * @author Jochen Hoenicke
57  */
58 public class ZipOutputStream extends DeflaterOutputStream implements ZipConstants
59 {
60   private Vector entries = new Vector();
61   private CRC32 crc = new CRC32();
62   private ZipEntry curEntry = null;
63 
64   private int curMethod;
65   private int size;
66   private int offset = 0;
67 
68   private byte[] zipComment = new byte[0];
69   private int defaultMethod = DEFLATED;
70 
71   /**
72    * Our Zip version is hard coded to 1.0 resp. 2.0
73    */
74   private final static int ZIP_STORED_VERSION   = 10;
75   private final static int ZIP_DEFLATED_VERSION = 20;
76 
77   /**
78    * Compression method.  This method doesn't compress at all.
79    */
80   public final static int STORED      =  0;
81   /**
82    * Compression method.  This method uses the Deflater.
83    */
84   public final static int DEFLATED    =  8;
85 
86   /**
87    * Creates a new Zip output stream, writing a zip archive.
88    * @param out the output stream to which the zip archive is written.
89    */
ZipOutputStream(OutputStream out)90   public ZipOutputStream(OutputStream out)
91   {
92     super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
93   }
94 
95   /**
96    * Set the zip file comment.
97    * @param comment the comment.
98    * @exception IllegalArgumentException if encoding of comment is
99    * longer than 0xffff bytes.
100    */
setComment(String comment)101   public void setComment(String comment)
102   {
103     byte[] commentBytes;
104     commentBytes = comment.getBytes();
105     if (commentBytes.length > 0xffff)
106       throw new IllegalArgumentException("Comment too long.");
107     zipComment = commentBytes;
108   }
109 
110   /**
111    * Sets default compression method.  If the Zip entry specifies
112    * another method its method takes precedence.
113    * @param method the method.
114    * @exception IllegalArgumentException if method is not supported.
115    * @see #STORED
116    * @see #DEFLATED
117    */
setMethod(int method)118   public void setMethod(int method)
119   {
120     if (method != STORED && method != DEFLATED)
121       throw new IllegalArgumentException("Method not supported.");
122     defaultMethod = method;
123   }
124 
125   /**
126    * Sets default compression level.  The new level will be activated
127    * immediately.
128    * @exception IllegalArgumentException if level is not supported.
129    * @see Deflater
130    */
setLevel(int level)131   public void setLevel(int level)
132   {
133     def.setLevel(level);
134   }
135 
136   /**
137    * Write an unsigned short in little endian byte order.
138    */
writeLeShort(int value)139   private final void writeLeShort(int value) throws IOException
140   {
141     out.write(value & 0xff);
142     out.write((value >> 8) & 0xff);
143   }
144 
145   /**
146    * Write an int in little endian byte order.
147    */
writeLeInt(int value)148   private final void writeLeInt(int value) throws IOException
149   {
150     writeLeShort(value);
151     writeLeShort(value >> 16);
152   }
153 
154   /**
155    * Starts a new Zip entry. It automatically closes the previous
156    * entry if present.  If the compression method is stored, the entry
157    * must have a valid size and crc, otherwise all elements (except
158    * name) are optional, but must be correct if present.  If the time
159    * is not set in the entry, the current time is used.
160    * @param entry the entry.
161    * @exception IOException if an I/O error occured.
162    * @exception ZipException if stream was finished.
163    */
putNextEntry(ZipEntry entry)164   public void putNextEntry(ZipEntry entry) throws IOException
165   {
166     if (entries == null)
167       throw new ZipException("ZipOutputStream was finished");
168 
169     int method = entry.getMethod();
170     int flags = 0;
171     if (method == -1)
172       method = defaultMethod;
173 
174     if (method == STORED)
175       {
176 	if (entry.getCompressedSize() >= 0)
177 	  {
178 	    if (entry.getSize() < 0)
179 	      entry.setSize(entry.getCompressedSize());
180 	    else if (entry.getSize() != entry.getCompressedSize())
181 	      throw new ZipException
182 		("Method STORED, but compressed size != size");
183 	  }
184 	else
185 	  entry.setCompressedSize(entry.getSize());
186 
187 	if (entry.getSize() < 0)
188 	  throw new ZipException("Method STORED, but size not set");
189 	if (entry.getCrc() < 0)
190 	  throw new ZipException("Method STORED, but crc not set");
191       }
192     else if (method == DEFLATED)
193       {
194 	if (entry.getCompressedSize() < 0
195 	    || entry.getSize() < 0 || entry.getCrc() < 0)
196 	  flags |= 8;
197       }
198 
199     if (curEntry != null)
200       closeEntry();
201 
202     if (entry.getTime() < 0)
203       entry.setTime(System.currentTimeMillis());
204 
205     entry.flags = flags;
206     entry.offset = offset;
207     entry.setMethod(method);
208     curMethod = method;
209     /* Write the local file header */
210     writeLeInt(LOCSIG);
211     writeLeShort(method == STORED
212 		 ? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION);
213     writeLeShort(flags);
214     writeLeShort(method);
215     writeLeInt(entry.getDOSTime());
216     if ((flags & 8) == 0)
217       {
218 	writeLeInt((int)entry.getCrc());
219 	writeLeInt((int)entry.getCompressedSize());
220 	writeLeInt((int)entry.getSize());
221       }
222     else
223       {
224 	writeLeInt(0);
225 	writeLeInt(0);
226 	writeLeInt(0);
227       }
228     byte[] name = entry.getName().getBytes();
229     if (name.length > 0xffff)
230       throw new ZipException("Name too long.");
231     byte[] extra = entry.getExtra();
232     if (extra == null)
233       extra = new byte[0];
234     writeLeShort(name.length);
235     writeLeShort(extra.length);
236     out.write(name);
237     out.write(extra);
238 
239     offset += LOCHDR + name.length + extra.length;
240 
241     /* Activate the entry. */
242 
243     curEntry = entry;
244     crc.reset();
245     if (method == DEFLATED)
246       def.reset();
247     size = 0;
248   }
249 
250   /**
251    * Closes the current entry.
252    * @exception IOException if an I/O error occured.
253    * @exception ZipException if no entry is active.
254    */
closeEntry()255   public void closeEntry() throws IOException
256   {
257     if (curEntry == null)
258       throw new ZipException("No open entry");
259 
260     /* First finish the deflater, if appropriate */
261     if (curMethod == DEFLATED)
262       super.finish();
263 
264     int csize = curMethod == DEFLATED ? def.getTotalOut() : size;
265 
266     if (curEntry.getSize() < 0)
267       curEntry.setSize(size);
268     else if (curEntry.getSize() != size)
269       throw new ZipException("size was "+size
270 			     +", but I expected "+curEntry.getSize());
271 
272     if (curEntry.getCompressedSize() < 0)
273       curEntry.setCompressedSize(csize);
274     else if (curEntry.getCompressedSize() != csize)
275       throw new ZipException("compressed size was "+csize
276 			     +", but I expected "+curEntry.getSize());
277 
278     if (curEntry.getCrc() < 0)
279       curEntry.setCrc(crc.getValue());
280     else if (curEntry.getCrc() != crc.getValue())
281       throw new ZipException("crc was " + Long.toHexString(crc.getValue())
282 			     + ", but I expected "
283 			     + Long.toHexString(curEntry.getCrc()));
284 
285     offset += csize;
286 
287     /* Now write the data descriptor entry if needed. */
288     if (curMethod == DEFLATED && (curEntry.flags & 8) != 0)
289       {
290 	writeLeInt(EXTSIG);
291 	writeLeInt((int)curEntry.getCrc());
292 	writeLeInt((int)curEntry.getCompressedSize());
293 	writeLeInt((int)curEntry.getSize());
294 	offset += EXTHDR;
295       }
296 
297     entries.addElement(curEntry);
298     curEntry = null;
299   }
300 
301   /**
302    * Writes the given buffer to the current entry.
303    * @exception IOException if an I/O error occured.
304    * @exception ZipException if no entry is active.
305    */
write(byte[] b, int off, int len)306   public void write(byte[] b, int off, int len) throws IOException
307   {
308     if (curEntry == null)
309       throw new ZipException("No open entry.");
310 
311     switch (curMethod)
312       {
313       case DEFLATED:
314 	super.write(b, off, len);
315 	break;
316 
317       case STORED:
318 	out.write(b, off, len);
319 	break;
320       }
321 
322     crc.update(b, off, len);
323     size += len;
324   }
325 
326   /**
327    * Finishes the stream.  This will write the central directory at the
328    * end of the zip file and flush the stream.
329    * @exception IOException if an I/O error occured.
330    */
finish()331   public void finish() throws IOException
332   {
333     if (entries == null)
334       return;
335     if (curEntry != null)
336       closeEntry();
337 
338     int numEntries = 0;
339     int sizeEntries = 0;
340 
341     Enumeration enum = entries.elements();
342     while (enum.hasMoreElements())
343       {
344 	ZipEntry entry = (ZipEntry) enum.nextElement();
345 
346 	int method = entry.getMethod();
347 	writeLeInt(CENSIG);
348 	writeLeShort(method == STORED
349 		     ? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION);
350 	writeLeShort(method == STORED
351 		     ? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION);
352 	writeLeShort(entry.flags);
353 	writeLeShort(method);
354 	writeLeInt(entry.getDOSTime());
355 	writeLeInt((int)entry.getCrc());
356 	writeLeInt((int)entry.getCompressedSize());
357 	writeLeInt((int)entry.getSize());
358 
359 	byte[] name = entry.getName().getBytes();
360 	if (name.length > 0xffff)
361 	  throw new ZipException("Name too long.");
362 	byte[] extra = entry.getExtra();
363 	if (extra == null)
364 	  extra = new byte[0];
365 	String strComment = entry.getComment();
366 	byte[] comment = strComment != null
367 	  ? strComment.getBytes() : new byte[0];
368 	if (comment.length > 0xffff)
369 	  throw new ZipException("Comment too long.");
370 
371 	writeLeShort(name.length);
372 	writeLeShort(extra.length);
373 	writeLeShort(comment.length);
374 	writeLeShort(0); /* disk number */
375 	writeLeShort(0); /* internal file attr */
376 	writeLeInt(0);   /* external file attr */
377 	writeLeInt(entry.offset);
378 
379 	out.write(name);
380 	out.write(extra);
381 	out.write(comment);
382 	numEntries++;
383 	sizeEntries += CENHDR + name.length + extra.length + comment.length;
384       }
385 
386     writeLeInt(ENDSIG);
387     writeLeShort(0); /* disk number */
388     writeLeShort(0); /* disk with start of central dir */
389     writeLeShort(numEntries);
390     writeLeShort(numEntries);
391     writeLeInt(sizeEntries);
392     writeLeInt(offset);
393     writeLeShort(zipComment.length);
394     out.write(zipComment);
395     out.flush();
396     entries = null;
397   }
398 }
399