1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * Copyright (c) 1996, 2015, 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 27 package java.util.zip; 28 29 import java.io.InputStream; 30 import java.io.IOException; 31 import java.io.EOFException; 32 import java.io.PushbackInputStream; 33 import java.nio.charset.Charset; 34 import java.nio.charset.StandardCharsets; 35 import static java.util.zip.ZipConstants64.*; 36 import static java.util.zip.ZipUtils.*; 37 38 /** 39 * This class implements an input stream filter for reading files in the 40 * ZIP file format. Includes support for both compressed and uncompressed 41 * entries. 42 * 43 * @author David Connelly 44 * @since 1.1 45 */ 46 public 47 class ZipInputStream extends InflaterInputStream implements ZipConstants { 48 private ZipEntry entry; 49 private int flag; 50 private CRC32 crc = new CRC32(); 51 private long remaining; 52 private byte[] tmpbuf = new byte[512]; 53 54 private static final int STORED = ZipEntry.STORED; 55 private static final int DEFLATED = ZipEntry.DEFLATED; 56 57 private boolean closed = false; 58 // this flag is set to true after EOF has reached for 59 // one entry 60 private boolean entryEOF = false; 61 62 private ZipCoder zc; 63 64 /** 65 * Check to make sure that this stream has not been closed 66 */ ensureOpen()67 private void ensureOpen() throws IOException { 68 if (closed) { 69 throw new IOException("Stream closed"); 70 } 71 } 72 73 /** 74 * Creates a new ZIP input stream. 75 * 76 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to 77 * decode the entry names. 78 * 79 * @param in the actual input stream 80 */ ZipInputStream(InputStream in)81 public ZipInputStream(InputStream in) { 82 this(in, StandardCharsets.UTF_8); 83 } 84 85 /** 86 * Creates a new ZIP input stream. 87 * 88 * @param in the actual input stream 89 * 90 * @param charset 91 * The {@linkplain java.nio.charset.Charset charset} to be 92 * used to decode the ZIP entry name (ignored if the 93 * <a href="package-summary.html#lang_encoding"> language 94 * encoding bit</a> of the ZIP entry's general purpose bit 95 * flag is set). 96 * 97 * @since 1.7 98 */ ZipInputStream(InputStream in, Charset charset)99 public ZipInputStream(InputStream in, Charset charset) { 100 super(new PushbackInputStream(in, 512), new Inflater(true), 512); 101 // Android-changed: Unconditionally close external inflaters (b/26462400) 102 // usesDefaultInflater = true; 103 if(in == null) { 104 throw new NullPointerException("in is null"); 105 } 106 if (charset == null) 107 throw new NullPointerException("charset is null"); 108 this.zc = ZipCoder.get(charset); 109 } 110 111 /** 112 * Reads the next ZIP file entry and positions the stream at the 113 * beginning of the entry data. 114 * @return the next ZIP file entry, or null if there are no more entries 115 * @exception ZipException if a ZIP file error has occurred 116 * @exception IOException if an I/O error has occurred 117 */ getNextEntry()118 public ZipEntry getNextEntry() throws IOException { 119 ensureOpen(); 120 if (entry != null) { 121 closeEntry(); 122 } 123 crc.reset(); 124 inf.reset(); 125 if ((entry = readLOC()) == null) { 126 return null; 127 } 128 // Android-changed: Return more accurate value from available(). 129 // Initialize the remaining field with the number of bytes that can be read from the entry 130 // for both uncompressed and compressed entries so that it can be used to provide a more 131 // accurate return value for available(). 132 // if (entry.method == STORED) { 133 if (entry.method == STORED || entry.method == DEFLATED) { 134 remaining = entry.size; 135 } 136 entryEOF = false; 137 return entry; 138 } 139 140 /** 141 * Closes the current ZIP entry and positions the stream for reading the 142 * next entry. 143 * @exception ZipException if a ZIP file error has occurred 144 * @exception IOException if an I/O error has occurred 145 */ closeEntry()146 public void closeEntry() throws IOException { 147 ensureOpen(); 148 while (read(tmpbuf, 0, tmpbuf.length) != -1) ; 149 entryEOF = true; 150 } 151 152 /** 153 * Returns 0 after EOF has reached for the current entry data, 154 * otherwise always return 1. 155 * <p> 156 * Programs should not count on this method to return the actual number 157 * of bytes that could be read without blocking. 158 * 159 * @return 1 before EOF and 0 after EOF has reached for current entry. 160 * @exception IOException if an I/O error occurs. 161 * 162 */ available()163 public int available() throws IOException { 164 ensureOpen(); 165 // Android-changed: Return more accurate value from available(). 166 // Tracks the remaining bytes in order to return a more accurate value for the available 167 // bytes. Given an entry of size N both Android and upstream will return 1 until N bytes 168 // have been read at which point Android will return 0 and upstream will return 1. 169 // Upstream will only return 0 after an attempt to read a byte fails because the EOF has 170 // been reached. See http://b/111439440 for more details. 171 // if (entryEOF) { 172 if (entryEOF || (entry != null && remaining == 0)) { 173 return 0; 174 } else { 175 return 1; 176 } 177 } 178 179 /** 180 * Reads from the current ZIP entry into an array of bytes. 181 * If <code>len</code> is not zero, the method 182 * blocks until some input is available; otherwise, no 183 * bytes are read and <code>0</code> is returned. 184 * @param b the buffer into which the data is read 185 * @param off the start offset in the destination array <code>b</code> 186 * @param len the maximum number of bytes read 187 * @return the actual number of bytes read, or -1 if the end of the 188 * entry is reached 189 * @exception NullPointerException if <code>b</code> is <code>null</code>. 190 * @exception IndexOutOfBoundsException if <code>off</code> is negative, 191 * <code>len</code> is negative, or <code>len</code> is greater than 192 * <code>b.length - off</code> 193 * @exception ZipException if a ZIP file error has occurred 194 * @exception IOException if an I/O error has occurred 195 */ read(byte[] b, int off, int len)196 public int read(byte[] b, int off, int len) throws IOException { 197 ensureOpen(); 198 if (off < 0 || len < 0 || off > b.length - len) { 199 throw new IndexOutOfBoundsException(); 200 } else if (len == 0) { 201 return 0; 202 } 203 204 if (entry == null) { 205 return -1; 206 } 207 switch (entry.method) { 208 case DEFLATED: 209 len = super.read(b, off, len); 210 if (len == -1) { 211 readEnd(entry); 212 entryEOF = true; 213 entry = null; 214 } else { 215 crc.update(b, off, len); 216 // Android-added: Return more accurate value from available(). 217 // Update the remaining field so it is an accurate count of the number of bytes 218 // remaining in this stream, after deflation. 219 remaining -= len; 220 } 221 return len; 222 case STORED: 223 if (remaining <= 0) { 224 entryEOF = true; 225 entry = null; 226 return -1; 227 } 228 if (len > remaining) { 229 len = (int)remaining; 230 } 231 len = in.read(b, off, len); 232 if (len == -1) { 233 throw new ZipException("unexpected EOF"); 234 } 235 crc.update(b, off, len); 236 remaining -= len; 237 if (remaining == 0 && entry.crc != crc.getValue()) { 238 throw new ZipException( 239 "invalid entry CRC (expected 0x" + Long.toHexString(entry.crc) + 240 " but got 0x" + Long.toHexString(crc.getValue()) + ")"); 241 } 242 return len; 243 default: 244 throw new ZipException("invalid compression method"); 245 } 246 } 247 248 /** 249 * Skips specified number of bytes in the current ZIP entry. 250 * @param n the number of bytes to skip 251 * @return the actual number of bytes skipped 252 * @exception ZipException if a ZIP file error has occurred 253 * @exception IOException if an I/O error has occurred 254 * @exception IllegalArgumentException if {@code n < 0} 255 */ skip(long n)256 public long skip(long n) throws IOException { 257 if (n < 0) { 258 throw new IllegalArgumentException("negative skip length"); 259 } 260 ensureOpen(); 261 int max = (int)Math.min(n, Integer.MAX_VALUE); 262 int total = 0; 263 while (total < max) { 264 int len = max - total; 265 if (len > tmpbuf.length) { 266 len = tmpbuf.length; 267 } 268 len = read(tmpbuf, 0, len); 269 if (len == -1) { 270 entryEOF = true; 271 break; 272 } 273 total += len; 274 } 275 return total; 276 } 277 278 /** 279 * Closes this input stream and releases any system resources associated 280 * with the stream. 281 * @exception IOException if an I/O error has occurred 282 */ close()283 public void close() throws IOException { 284 if (!closed) { 285 super.close(); 286 closed = true; 287 } 288 } 289 290 private byte[] b = new byte[256]; 291 292 /* 293 * Reads local file (LOC) header for next entry. 294 */ readLOC()295 private ZipEntry readLOC() throws IOException { 296 try { 297 readFully(tmpbuf, 0, LOCHDR); 298 } catch (EOFException e) { 299 return null; 300 } 301 if (get32(tmpbuf, 0) != LOCSIG) { 302 return null; 303 } 304 // get flag first, we need check USE_UTF8. 305 flag = get16(tmpbuf, LOCFLG); 306 // get the entry name and create the ZipEntry first 307 int len = get16(tmpbuf, LOCNAM); 308 int blen = b.length; 309 if (len > blen) { 310 do { 311 blen = blen * 2; 312 } while (len > blen); 313 b = new byte[blen]; 314 } 315 readFully(b, 0, len); 316 // Force to use UTF-8 if the USE_UTF8 bit is ON 317 ZipEntry e = createZipEntry(((flag & USE_UTF8) != 0) 318 ? zc.toStringUTF8(b, len) 319 : zc.toString(b, len)); 320 // now get the remaining fields for the entry 321 if ((flag & 1) == 1) { 322 throw new ZipException("encrypted ZIP entry not supported"); 323 } 324 e.method = get16(tmpbuf, LOCHOW); 325 e.xdostime = get32(tmpbuf, LOCTIM); 326 if ((flag & 8) == 8) { 327 // Android-changed: Remove the requirement that only DEFLATED entries 328 // can have data descriptors. This is not required by the ZIP spec and 329 // is inconsistent with the behaviour of ZipFile and versions of Android 330 // prior to Android N. 331 // 332 // /* "Data Descriptor" present */ 333 // if (e.method != DEFLATED) { 334 // throw new ZipException( 335 // "only DEFLATED entries can have EXT descriptor"); 336 // } 337 } else { 338 e.crc = get32(tmpbuf, LOCCRC); 339 e.csize = get32(tmpbuf, LOCSIZ); 340 e.size = get32(tmpbuf, LOCLEN); 341 } 342 len = get16(tmpbuf, LOCEXT); 343 if (len > 0) { 344 byte[] extra = new byte[len]; 345 readFully(extra, 0, len); 346 e.setExtra0(extra, 347 e.csize == ZIP64_MAGICVAL || e.size == ZIP64_MAGICVAL); 348 } 349 return e; 350 } 351 352 /** 353 * Creates a new <code>ZipEntry</code> object for the specified 354 * entry name. 355 * 356 * @param name the ZIP file entry name 357 * @return the ZipEntry just created 358 */ createZipEntry(String name)359 protected ZipEntry createZipEntry(String name) { 360 return new ZipEntry(name); 361 } 362 363 /** 364 * Reads end of deflated entry as well as EXT descriptor if present. 365 * 366 * Local headers for DEFLATED entries may optionally be followed by a 367 * data descriptor, and that data descriptor may optionally contain a 368 * leading signature (EXTSIG). 369 * 370 * From the zip spec http://www.pkware.com/documents/casestudies/APPNOTE.TXT 371 * 372 * """Although not originally assigned a signature, the value 0x08074b50 373 * has commonly been adopted as a signature value for the data descriptor 374 * record. Implementers should be aware that ZIP files may be 375 * encountered with or without this signature marking data descriptors 376 * and should account for either case when reading ZIP files to ensure 377 * compatibility.""" 378 */ readEnd(ZipEntry e)379 private void readEnd(ZipEntry e) throws IOException { 380 int n = inf.getRemaining(); 381 if (n > 0) { 382 ((PushbackInputStream)in).unread(buf, len - n, n); 383 } 384 if ((flag & 8) == 8) { 385 /* "Data Descriptor" present */ 386 if (inf.getBytesWritten() > ZIP64_MAGICVAL || 387 inf.getBytesRead() > ZIP64_MAGICVAL) { 388 // ZIP64 format 389 readFully(tmpbuf, 0, ZIP64_EXTHDR); 390 long sig = get32(tmpbuf, 0); 391 if (sig != EXTSIG) { // no EXTSIG present 392 e.crc = sig; 393 e.csize = get64(tmpbuf, ZIP64_EXTSIZ - ZIP64_EXTCRC); 394 e.size = get64(tmpbuf, ZIP64_EXTLEN - ZIP64_EXTCRC); 395 ((PushbackInputStream)in).unread( 396 tmpbuf, ZIP64_EXTHDR - ZIP64_EXTCRC, ZIP64_EXTCRC); 397 } else { 398 e.crc = get32(tmpbuf, ZIP64_EXTCRC); 399 e.csize = get64(tmpbuf, ZIP64_EXTSIZ); 400 e.size = get64(tmpbuf, ZIP64_EXTLEN); 401 } 402 } else { 403 readFully(tmpbuf, 0, EXTHDR); 404 long sig = get32(tmpbuf, 0); 405 if (sig != EXTSIG) { // no EXTSIG present 406 e.crc = sig; 407 e.csize = get32(tmpbuf, EXTSIZ - EXTCRC); 408 e.size = get32(tmpbuf, EXTLEN - EXTCRC); 409 ((PushbackInputStream)in).unread( 410 tmpbuf, EXTHDR - EXTCRC, EXTCRC); 411 } else { 412 e.crc = get32(tmpbuf, EXTCRC); 413 e.csize = get32(tmpbuf, EXTSIZ); 414 e.size = get32(tmpbuf, EXTLEN); 415 } 416 } 417 } 418 if (e.size != inf.getBytesWritten()) { 419 throw new ZipException( 420 "invalid entry size (expected " + e.size + 421 " but got " + inf.getBytesWritten() + " bytes)"); 422 } 423 if (e.csize != inf.getBytesRead()) { 424 throw new ZipException( 425 "invalid entry compressed size (expected " + e.csize + 426 " but got " + inf.getBytesRead() + " bytes)"); 427 } 428 if (e.crc != crc.getValue()) { 429 throw new ZipException( 430 "invalid entry CRC (expected 0x" + Long.toHexString(e.crc) + 431 " but got 0x" + Long.toHexString(crc.getValue()) + ")"); 432 } 433 } 434 435 /* 436 * Reads bytes, blocking until all bytes are read. 437 */ readFully(byte[] b, int off, int len)438 private void readFully(byte[] b, int off, int len) throws IOException { 439 while (len > 0) { 440 int n = in.read(b, off, len); 441 if (n == -1) { 442 throw new EOFException(); 443 } 444 off += n; 445 len -= n; 446 } 447 } 448 449 } 450