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