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