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