1 /* 2 * Copyright (c) 1996, 2009, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.util.zip; 27 28 import java.io.InputStream; 29 import java.io.IOException; 30 import java.io.EOFException; 31 import java.io.PushbackInputStream; 32 import java.nio.charset.Charset; 33 import static java.util.zip.ZipConstants64.*; 34 35 /** 36 * This class implements an input stream filter for reading files in the 37 * ZIP file format. Includes support for both compressed and uncompressed 38 * entries. 39 * 40 * @author David Connelly 41 */ 42 public 43 class ZipInputStream extends InflaterInputStream implements ZipConstants { 44 private ZipEntry entry; 45 private int flag; 46 private CRC32 crc = new CRC32(); 47 private long remaining; 48 private byte[] tmpbuf = new byte[512]; 49 50 private static final int STORED = ZipEntry.STORED; 51 private static final int DEFLATED = ZipEntry.DEFLATED; 52 53 private boolean closed = false; 54 // this flag is set to true after EOF has reached for 55 // one entry 56 private boolean entryEOF = false; 57 58 private ZipCoder zc; 59 60 /** 61 * Check to make sure that this stream has not been closed 62 */ 63 private void ensureOpen() throws IOException { 64 if (closed) { 65 throw new IOException("Stream closed"); 66 } 67 } 68 69 /** 70 * Creates a new ZIP input stream. 71 * 72 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to 73 * decode the entry names. 74 * 75 * @param in the actual input stream 76 */ 77 public ZipInputStream(InputStream in) { 78 this(in, Charset.forName("UTF-8")); 79 } 80 81 /** 82 * Creates a new ZIP input stream. 83 * 84 * @param in the actual input stream 85 * 86 * @param charset 87 * The {@linkplain java.nio.charset.Charset charset} to be 88 * used to decode the ZIP entry name (ignored if the 89 * <a href="package-summary.html#lang_encoding"> language 90 * encoding bit</a> of the ZIP entry's general purpose bit 91 * flag is set). 92 * 93 * @since 1.7 94 */ 95 public ZipInputStream(InputStream in, Charset charset) { 96 super(new PushbackInputStream(in, 512), new Inflater(true), 512); 97 usesDefaultInflater = true; 98 if(in == null) { 99 throw new NullPointerException("in is null"); 100 } 101 if (charset == null) 102 throw new NullPointerException("charset is null"); 103 this.zc = ZipCoder.get(charset); 104 } 105 106 /** 107 * Reads the next ZIP file entry and positions the stream at the 108 * beginning of the entry data. 109 * @return the next ZIP file entry, or null if there are no more entries 110 * @exception ZipException if a ZIP file error has occurred 111 * @exception IOException if an I/O error has occurred 112 */ 113 public ZipEntry getNextEntry() throws IOException { 114 ensureOpen(); 115 if (entry != null) { 116 closeEntry(); 117 } 118 crc.reset(); 119 inf.reset(); 120 if ((entry = readLOC()) == null) { 121 return null; 122 } 123 if (entry.method == STORED) { 124 remaining = entry.size; 125 } 126 entryEOF = false; 127 return entry; 128 } 129 130 /** 131 * Closes the current ZIP entry and positions the stream for reading the 132 * next entry. 133 * @exception ZipException if a ZIP file error has occurred 134 * @exception IOException if an I/O error has occurred 135 */ 136 public void closeEntry() throws IOException { 137 ensureOpen(); 138 while (read(tmpbuf, 0, tmpbuf.length) != -1) ; 139 entryEOF = true; 140 } 141 142 /** 143 * Returns 0 after EOF has reached for the current entry data, 144 * otherwise always return 1. 145 * <p> 146 * Programs should not count on this method to return the actual number 147 * of bytes that could be read without blocking. 148 * 149 * @return 1 before EOF and 0 after EOF has reached for current entry. 150 * @exception IOException if an I/O error occurs. 151 * 152 */ 153 public int available() throws IOException { 154 ensureOpen(); 155 if (entryEOF) { 156 return 0; 157 } else { 158 return 1; 159 } 160 } 161 162 /** 163 * Reads from the current ZIP entry into an array of bytes. 164 * If <code>len</code> is not zero, the method 165 * blocks until some input is available; otherwise, no 166 * bytes are read and <code>0</code> is returned. 167 * @param b the buffer into which the data is read 168 * @param off the start offset in the destination array <code>b</code> 169 * @param len the maximum number of bytes read 170 * @return the actual number of bytes read, or -1 if the end of the 171 * entry is reached 172 * @exception NullPointerException if <code>b</code> is <code>null</code>. 173 * @exception IndexOutOfBoundsException if <code>off</code> is negative, 174 * <code>len</code> is negative, or <code>len</code> is greater than 175 * <code>b.length - off</code> 176 * @exception ZipException if a ZIP file error has occurred 177 * @exception IOException if an I/O error has occurred 178 */ 179 public int read(byte[] b, int off, int len) throws IOException { 180 ensureOpen(); 181 if (off < 0 || len < 0 || off > b.length - len) { 182 throw new IndexOutOfBoundsException(); 183 } else if (len == 0) { 184 return 0; 185 } 186 187 if (entry == null) { 188 return -1; 189 } 190 switch (entry.method) { 191 case DEFLATED: 192 len = super.read(b, off, len); 193 if (len == -1) { 194 readEnd(entry); 195 entryEOF = true; 196 entry = null; 197 } else { 198 crc.update(b, off, len); 199 } 200 return len; 201 case STORED: 202 if (remaining <= 0) { 203 entryEOF = true; 204 entry = null; 205 return -1; 206 } 207 if (len > remaining) { 208 len = (int)remaining; 209 } 210 len = in.read(b, off, len); 211 if (len == -1) { 212 throw new ZipException("unexpected EOF"); 213 } 214 crc.update(b, off, len); 215 remaining -= len; 216 if (remaining == 0 && entry.crc != crc.getValue()) { 217 throw new ZipException( 218 "invalid entry CRC (expected 0x" + Long.toHexString(entry.crc) + 219 " but got 0x" + Long.toHexString(crc.getValue()) + ")"); 220 } 221 return len; 222 default: 223 throw new ZipException("invalid compression method"); 224 } 225 } 226 227 /** 228 * Skips specified number of bytes in the current ZIP entry. 229 * @param n the number of bytes to skip 230 * @return the actual number of bytes skipped 231 * @exception ZipException if a ZIP file error has occurred 232 * @exception IOException if an I/O error has occurred 233 * @exception IllegalArgumentException if n < 0 234 */ 235 public long skip(long n) throws IOException { 236 if (n < 0) { 237 throw new IllegalArgumentException("negative skip length"); 238 } 239 ensureOpen(); 240 int max = (int)Math.min(n, Integer.MAX_VALUE); 241 int total = 0; 242 while (total < max) { 243 int len = max - total; 244 if (len > tmpbuf.length) { 245 len = tmpbuf.length; 246 } 247 len = read(tmpbuf, 0, len); 248 if (len == -1) { 249 entryEOF = true; 250 break; 251 } 252 total += len; 253 } 254 return total; 255 } 256 257 /** 258 * Closes this input stream and releases any system resources associated 259 * with the stream. 260 * @exception IOException if an I/O error has occurred 261 */ 262 public void close() throws IOException { 263 if (!closed) { 264 super.close(); 265 closed = true; 266 } 267 } 268 269 private byte[] b = new byte[256]; 270 271 /* 272 * Reads local file (LOC) header for next entry. 273 */ 274 private ZipEntry readLOC() throws IOException { 275 try { 276 readFully(tmpbuf, 0, LOCHDR); 277 } catch (EOFException e) { 278 return null; 279 } 280 if (get32(tmpbuf, 0) != LOCSIG) { 281 return null; 282 } 283 // get flag first, we need check EFS. 284 flag = get16(tmpbuf, LOCFLG); 285 // get the entry name and create the ZipEntry first 286 int len = get16(tmpbuf, LOCNAM); 287 int blen = b.length; 288 if (len > blen) { 289 do 290 blen = blen * 2; 291 while (len > blen); 292 b = new byte[blen]; 293 } 294 readFully(b, 0, len); 295 // Force to use UTF-8 if the EFS bit is ON, even the cs is NOT UTF-8 296 ZipEntry e = createZipEntry(((flag & EFS) != 0) 297 ? zc.toStringUTF8(b, len) 298 : zc.toString(b, len)); 299 // now get the remaining fields for the entry 300 if ((flag & 1) == 1) { 301 throw new ZipException("encrypted ZIP entry not supported"); 302 } 303 e.method = get16(tmpbuf, LOCHOW); 304 e.time = get32(tmpbuf, LOCTIM); 305 if ((flag & 8) == 8) { 306 /* "Data Descriptor" present */ 307 if (e.method != DEFLATED) { 308 throw new ZipException( 309 "only DEFLATED entries can have EXT descriptor"); 310 } 311 } else { 312 e.crc = get32(tmpbuf, LOCCRC); 313 e.csize = get32(tmpbuf, LOCSIZ); 314 e.size = get32(tmpbuf, LOCLEN); 315 } 316 len = get16(tmpbuf, LOCEXT); 317 if (len > 0) { 318 byte[] bb = new byte[len]; 319 readFully(bb, 0, len); 320 e.setExtra(bb); 321 // extra fields are in "HeaderID(2)DataSize(2)Data... format 322 if (e.csize == ZIP64_MAGICVAL || e.size == ZIP64_MAGICVAL) { 323 int off = 0; 324 while (off + 4 < len) { 325 int sz = get16(bb, off + 2); 326 if (get16(bb, off) == ZIP64_EXTID) { 327 off += 4; 328 // LOC extra zip64 entry MUST include BOTH original and 329 // compressed file size fields 330 if (sz < 16 || (off + sz) > len ) { 331 // Invalid zip64 extra fields, simply skip. Even it's 332 // rare, it's possible the entry size happens to be 333 // the magic value and it "accidnetly" has some bytes 334 // in extra match the id. 335 return e; 336 } 337 e.size = get64(bb, off); 338 e.csize = get64(bb, off + 8); 339 break; 340 } 341 off += (sz + 4); 342 } 343 } 344 } 345 return e; 346 } 347 348 /** 349 * Creates a new <code>ZipEntry</code> object for the specified 350 * entry name. 351 * 352 * @param name the ZIP file entry name 353 * @return the ZipEntry just created 354 */ 355 protected ZipEntry createZipEntry(String name) { 356 return new ZipEntry(name); 357 } 358 359 /* 360 * Reads end of deflated entry as well as EXT descriptor if present. 361 */ 362 private void readEnd(ZipEntry e) throws IOException { 363 int n = inf.getRemaining(); 364 if (n > 0) { 365 ((PushbackInputStream)in).unread(buf, len - n, n); 366 } 367 if ((flag & 8) == 8) { 368 /* "Data Descriptor" present */ 369 if (inf.getBytesWritten() > ZIP64_MAGICVAL || 370 inf.getBytesRead() > ZIP64_MAGICVAL) { 371 // ZIP64 format 372 readFully(tmpbuf, 0, ZIP64_EXTHDR); 373 long sig = get32(tmpbuf, 0); 374 if (sig != EXTSIG) { // no EXTSIG present 375 e.crc = sig; 376 e.csize = get64(tmpbuf, ZIP64_EXTSIZ - ZIP64_EXTCRC); 377 e.size = get64(tmpbuf, ZIP64_EXTLEN - ZIP64_EXTCRC); 378 ((PushbackInputStream)in).unread( 379 tmpbuf, ZIP64_EXTHDR - ZIP64_EXTCRC - 1, ZIP64_EXTCRC); 380 } else { 381 e.crc = get32(tmpbuf, ZIP64_EXTCRC); 382 e.csize = get64(tmpbuf, ZIP64_EXTSIZ); 383 e.size = get64(tmpbuf, ZIP64_EXTLEN); 384 } 385 } else { 386 readFully(tmpbuf, 0, EXTHDR); 387 long sig = get32(tmpbuf, 0); 388 if (sig != EXTSIG) { // no EXTSIG present 389 e.crc = sig; 390 e.csize = get32(tmpbuf, EXTSIZ - EXTCRC); 391 e.size = get32(tmpbuf, EXTLEN - EXTCRC); 392 ((PushbackInputStream)in).unread( 393 tmpbuf, EXTHDR - EXTCRC - 1, EXTCRC); 394 } else { 395 e.crc = get32(tmpbuf, EXTCRC); 396 e.csize = get32(tmpbuf, EXTSIZ); 397 e.size = get32(tmpbuf, EXTLEN); 398 } 399 } 400 } 401 if (e.size != inf.getBytesWritten()) { 402 throw new ZipException( 403 "invalid entry size (expected " + e.size + 404 " but got " + inf.getBytesWritten() + " bytes)"); 405 } 406 if (e.csize != inf.getBytesRead()) { 407 throw new ZipException( 408 "invalid entry compressed size (expected " + e.csize + 409 " but got " + inf.getBytesRead() + " bytes)"); 410 } 411 if (e.crc != crc.getValue()) { 412 throw new ZipException( 413 "invalid entry CRC (expected 0x" + Long.toHexString(e.crc) + 414 " but got 0x" + Long.toHexString(crc.getValue()) + ")"); 415 } 416 } 417 418 /* 419 * Reads bytes, blocking until all bytes are read. 420 */ 421 private void readFully(byte[] b, int off, int len) throws IOException { 422 while (len > 0) { 423 int n = in.read(b, off, len); 424 if (n == -1) { 425 throw new EOFException(); 426 } 427 off += n; 428 len -= n; 429 } 430 } 431 432 /* 433 * Fetches unsigned 16-bit value from byte array at specified offset. 434 * The bytes are assumed to be in Intel (little-endian) byte order. 435 */ 436 private static final int get16(byte b[], int off) { 437 return (b[off] & 0xff) | ((b[off+1] & 0xff) << 8); 438 } 439 440 /* 441 * Fetches unsigned 32-bit value from byte array at specified offset. 442 * The bytes are assumed to be in Intel (little-endian) byte order. 443 */ 444 private static final long get32(byte b[], int off) { 445 return (get16(b, off) | ((long)get16(b, off+2) << 16)) & 0xffffffffL; 446 } 447 448 /* 449 * Fetches signed 64-bit value from byte array at specified offset. 450 * The bytes are assumed to be in Intel (little-endian) byte order. 451 */ 452 private static final long get64(byte b[], int off) { 453 return get32(b, off) | (get32(b, off+4) << 32); 454 } 455 }