001 /* ZipOutputStream.java -- 002 Copyright (C) 2001, 2004, 2005, 2006 Free Software Foundation, Inc. 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 039 package java.util.zip; 040 041 import java.io.IOException; 042 import java.io.OutputStream; 043 import java.io.UnsupportedEncodingException; 044 import java.util.Enumeration; 045 import java.util.Vector; 046 047 /** 048 * This is a FilterOutputStream that writes the files into a zip 049 * archive one after another. It has a special method to start a new 050 * zip entry. The zip entries contains information about the file name 051 * size, compressed size, CRC, etc. 052 * 053 * It includes support for STORED and DEFLATED entries. 054 * 055 * This class is not thread safe. 056 * 057 * @author Jochen Hoenicke 058 */ 059 public class ZipOutputStream extends DeflaterOutputStream implements ZipConstants 060 { 061 private Vector entries = new Vector(); 062 private CRC32 crc = new CRC32(); 063 private ZipEntry curEntry = null; 064 065 private int curMethod; 066 private int size; 067 private int offset = 0; 068 069 private byte[] zipComment = new byte[0]; 070 private int defaultMethod = DEFLATED; 071 072 /** 073 * Our Zip version is hard coded to 1.0 resp. 2.0 074 */ 075 private static final int ZIP_STORED_VERSION = 10; 076 private static final int ZIP_DEFLATED_VERSION = 20; 077 078 /** 079 * Compression method. This method doesn't compress at all. 080 */ 081 public static final int STORED = 0; 082 083 /** 084 * Compression method. This method uses the Deflater. 085 */ 086 public static final int DEFLATED = 8; 087 088 /** 089 * Creates a new Zip output stream, writing a zip archive. 090 * @param out the output stream to which the zip archive is written. 091 */ 092 public ZipOutputStream(OutputStream out) 093 { 094 super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true)); 095 } 096 097 /** 098 * Set the zip file comment. 099 * @param comment the comment. 100 * @exception IllegalArgumentException if encoding of comment is 101 * longer than 0xffff bytes. 102 */ 103 public void setComment(String comment) 104 { 105 byte[] commentBytes; 106 try 107 { 108 commentBytes = comment.getBytes("UTF-8"); 109 } 110 catch (UnsupportedEncodingException uee) 111 { 112 throw new AssertionError(uee); 113 } 114 if (commentBytes.length > 0xffff) 115 throw new IllegalArgumentException("Comment too long."); 116 zipComment = commentBytes; 117 } 118 119 /** 120 * Sets default compression method. If the Zip entry specifies 121 * another method its method takes precedence. 122 * @param method the method. 123 * @exception IllegalArgumentException if method is not supported. 124 * @see #STORED 125 * @see #DEFLATED 126 */ 127 public void setMethod(int method) 128 { 129 if (method != STORED && method != DEFLATED) 130 throw new IllegalArgumentException("Method not supported."); 131 defaultMethod = method; 132 } 133 134 /** 135 * Sets default compression level. The new level will be activated 136 * immediately. 137 * @exception IllegalArgumentException if level is not supported. 138 * @see Deflater 139 */ 140 public void setLevel(int level) 141 { 142 def.setLevel(level); 143 } 144 145 /** 146 * Write an unsigned short in little endian byte order. 147 */ 148 private void writeLeShort(int value) throws IOException 149 { 150 out.write(value & 0xff); 151 out.write((value >> 8) & 0xff); 152 } 153 154 /** 155 * Write an int in little endian byte order. 156 */ 157 private void writeLeInt(int value) throws IOException 158 { 159 writeLeShort(value); 160 writeLeShort(value >> 16); 161 } 162 163 /** 164 * Write a long value as an int. Some of the zip constants 165 * are declared as longs even though they fit perfectly well 166 * into integers. 167 */ 168 private void writeLeInt(long value) throws IOException 169 { 170 writeLeInt((int) value); 171 } 172 173 /** 174 * Starts a new Zip entry. It automatically closes the previous 175 * entry if present. If the compression method is stored, the entry 176 * must have a valid size and crc, otherwise all elements (except 177 * name) are optional, but must be correct if present. If the time 178 * is not set in the entry, the current time is used. 179 * @param entry the entry. 180 * @exception IOException if an I/O error occured. 181 * @exception ZipException if stream was finished. 182 */ 183 public void putNextEntry(ZipEntry entry) throws IOException 184 { 185 if (entries == null) 186 throw new ZipException("ZipOutputStream was finished"); 187 188 int method = entry.getMethod(); 189 int flags = 0; 190 if (method == -1) 191 method = defaultMethod; 192 193 if (method == STORED) 194 { 195 if (entry.getCompressedSize() >= 0) 196 { 197 if (entry.getSize() < 0) 198 entry.setSize(entry.getCompressedSize()); 199 else if (entry.getSize() != entry.getCompressedSize()) 200 throw new ZipException 201 ("Method STORED, but compressed size != size"); 202 } 203 else 204 entry.setCompressedSize(entry.getSize()); 205 206 if (entry.getSize() < 0) 207 throw new ZipException("Method STORED, but size not set"); 208 if (entry.getCrc() < 0) 209 throw new ZipException("Method STORED, but crc not set"); 210 } 211 else if (method == DEFLATED) 212 { 213 if (entry.getCompressedSize() < 0 214 || entry.getSize() < 0 || entry.getCrc() < 0) 215 flags |= 8; 216 } 217 218 if (curEntry != null) 219 closeEntry(); 220 221 if (entry.getTime() < 0) 222 entry.setTime(System.currentTimeMillis()); 223 224 entry.flags = flags; 225 entry.offset = offset; 226 entry.setMethod(method); 227 curMethod = method; 228 /* Write the local file header */ 229 writeLeInt(LOCSIG); 230 writeLeShort(method == STORED 231 ? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION); 232 writeLeShort(flags); 233 writeLeShort(method); 234 writeLeInt(entry.getDOSTime()); 235 if ((flags & 8) == 0) 236 { 237 writeLeInt((int)entry.getCrc()); 238 writeLeInt((int)entry.getCompressedSize()); 239 writeLeInt((int)entry.getSize()); 240 } 241 else 242 { 243 writeLeInt(0); 244 writeLeInt(0); 245 writeLeInt(0); 246 } 247 byte[] name; 248 try 249 { 250 name = entry.getName().getBytes("UTF-8"); 251 } 252 catch (UnsupportedEncodingException uee) 253 { 254 throw new AssertionError(uee); 255 } 256 if (name.length > 0xffff) 257 throw new ZipException("Name too long."); 258 byte[] extra = entry.getExtra(); 259 if (extra == null) 260 extra = new byte[0]; 261 writeLeShort(name.length); 262 writeLeShort(extra.length); 263 out.write(name); 264 out.write(extra); 265 266 offset += LOCHDR + name.length + extra.length; 267 268 /* Activate the entry. */ 269 270 curEntry = entry; 271 crc.reset(); 272 if (method == DEFLATED) 273 def.reset(); 274 size = 0; 275 } 276 277 /** 278 * Closes the current entry. 279 * @exception IOException if an I/O error occured. 280 * @exception ZipException if no entry is active. 281 */ 282 public void closeEntry() throws IOException 283 { 284 if (curEntry == null) 285 throw new ZipException("No open entry"); 286 287 /* First finish the deflater, if appropriate */ 288 if (curMethod == DEFLATED) 289 super.finish(); 290 291 int csize = curMethod == DEFLATED ? def.getTotalOut() : size; 292 293 if (curEntry.getSize() < 0) 294 curEntry.setSize(size); 295 else if (curEntry.getSize() != size) 296 throw new ZipException("size was "+size 297 +", but I expected "+curEntry.getSize()); 298 299 if (curEntry.getCompressedSize() < 0) 300 curEntry.setCompressedSize(csize); 301 else if (curEntry.getCompressedSize() != csize) 302 throw new ZipException("compressed size was "+csize 303 +", but I expected "+curEntry.getSize()); 304 305 if (curEntry.getCrc() < 0) 306 curEntry.setCrc(crc.getValue()); 307 else if (curEntry.getCrc() != crc.getValue()) 308 throw new ZipException("crc was " + Long.toHexString(crc.getValue()) 309 + ", but I expected " 310 + Long.toHexString(curEntry.getCrc())); 311 312 offset += csize; 313 314 /* Now write the data descriptor entry if needed. */ 315 if (curMethod == DEFLATED && (curEntry.flags & 8) != 0) 316 { 317 writeLeInt(EXTSIG); 318 writeLeInt((int)curEntry.getCrc()); 319 writeLeInt((int)curEntry.getCompressedSize()); 320 writeLeInt((int)curEntry.getSize()); 321 offset += EXTHDR; 322 } 323 324 entries.addElement(curEntry); 325 curEntry = null; 326 } 327 328 /** 329 * Writes the given buffer to the current entry. 330 * @exception IOException if an I/O error occured. 331 * @exception ZipException if no entry is active. 332 */ 333 public void write(byte[] b, int off, int len) throws IOException 334 { 335 if (curEntry == null) 336 throw new ZipException("No open entry."); 337 338 switch (curMethod) 339 { 340 case DEFLATED: 341 super.write(b, off, len); 342 break; 343 344 case STORED: 345 out.write(b, off, len); 346 break; 347 } 348 349 crc.update(b, off, len); 350 size += len; 351 } 352 353 /** 354 * Finishes the stream. This will write the central directory at the 355 * end of the zip file and flush the stream. 356 * @exception IOException if an I/O error occured. 357 */ 358 public void finish() throws IOException 359 { 360 if (entries == null) 361 return; 362 if (curEntry != null) 363 closeEntry(); 364 365 int numEntries = 0; 366 int sizeEntries = 0; 367 368 Enumeration e = entries.elements(); 369 while (e.hasMoreElements()) 370 { 371 ZipEntry entry = (ZipEntry) e.nextElement(); 372 373 int method = entry.getMethod(); 374 writeLeInt(CENSIG); 375 writeLeShort(method == STORED 376 ? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION); 377 writeLeShort(method == STORED 378 ? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION); 379 writeLeShort(entry.flags); 380 writeLeShort(method); 381 writeLeInt(entry.getDOSTime()); 382 writeLeInt((int)entry.getCrc()); 383 writeLeInt((int)entry.getCompressedSize()); 384 writeLeInt((int)entry.getSize()); 385 386 byte[] name; 387 try 388 { 389 name = entry.getName().getBytes("UTF-8"); 390 } 391 catch (UnsupportedEncodingException uee) 392 { 393 throw new AssertionError(uee); 394 } 395 if (name.length > 0xffff) 396 throw new ZipException("Name too long."); 397 byte[] extra = entry.getExtra(); 398 if (extra == null) 399 extra = new byte[0]; 400 String str = entry.getComment(); 401 byte[] comment; 402 try 403 { 404 comment = str != null ? str.getBytes("UTF-8") : new byte[0]; 405 } 406 catch (UnsupportedEncodingException uee) 407 { 408 throw new AssertionError(uee); 409 } 410 if (comment.length > 0xffff) 411 throw new ZipException("Comment too long."); 412 413 writeLeShort(name.length); 414 writeLeShort(extra.length); 415 writeLeShort(comment.length); 416 writeLeShort(0); /* disk number */ 417 writeLeShort(0); /* internal file attr */ 418 writeLeInt(0); /* external file attr */ 419 writeLeInt(entry.offset); 420 421 out.write(name); 422 out.write(extra); 423 out.write(comment); 424 numEntries++; 425 sizeEntries += CENHDR + name.length + extra.length + comment.length; 426 } 427 428 writeLeInt(ENDSIG); 429 writeLeShort(0); /* disk number */ 430 writeLeShort(0); /* disk with start of central dir */ 431 writeLeShort(numEntries); 432 writeLeShort(numEntries); 433 writeLeInt(sizeEntries); 434 writeLeInt(offset); 435 writeLeShort(zipComment.length); 436 out.write(zipComment); 437 out.flush(); 438 entries = null; 439 } 440 }