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