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