1 /* 2 * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.util.zip; 27 28 import java.io.InputStream; 29 import java.io.IOException; 30 import java.io.EOFException; 31 import java.io.PushbackInputStream; 32 import java.nio.charset.Charset; 33 import java.nio.charset.StandardCharsets; 34 import java.util.Objects; 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 private ZipCryption zipCryption; 64 65 /** 66 * Check to make sure that this stream has not been closed 67 */ 68 private void ensureOpen() throws IOException { 69 if (closed) { 70 throw new IOException("Stream closed"); 71 } 72 } 73 74 /** 75 * Creates a new ZIP input stream. 76 * 77 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to 78 * decode the entry names. 79 * 80 * @param in the actual input stream 81 */ 82 public ZipInputStream(InputStream in) { 83 this(in, StandardCharsets.UTF_8); 84 } 85 86 /** 87 * Creates a new ZIP input stream. 88 * 89 * @param in the actual input stream 90 * 91 * @param charset 92 * The {@linkplain java.nio.charset.Charset charset} to be 93 * used to decode the ZIP entry name (ignored if the 94 * <a href="package-summary.html#lang_encoding"> language 95 * encoding bit</a> of the ZIP entry's general purpose bit 96 * flag is set). 97 * 98 * @since 1.7 99 */ 100 public ZipInputStream(InputStream in, Charset charset) { 101 super(new PushbackInputStream(in, 512), new Inflater(true), 512); 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 */ 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 if (entry.method == STORED) { 129 remaining = entry.size; 130 } 131 entryEOF = false; 132 return entry; 133 } 134 135 /** 136 * Closes the current ZIP entry and positions the stream for reading the 137 * next entry. 138 * @exception ZipException if a ZIP file error has occurred 139 * @exception IOException if an I/O error has occurred 140 */ 141 public void closeEntry() throws IOException { 142 ensureOpen(); 143 while (read(tmpbuf, 0, tmpbuf.length) != -1) ; 144 entryEOF = true; 145 } 146 147 /** 148 * Returns 0 after EOF has reached for the current entry data, 149 * otherwise always return 1. 150 * <p> 151 * Programs should not count on this method to return the actual number 152 * of bytes that could be read without blocking. 153 * 154 * @return 1 before EOF and 0 after EOF has reached for current entry. 155 * @exception IOException if an I/O error occurs. 156 * 157 */ 158 public int available() throws IOException { 159 ensureOpen(); 160 if (entryEOF) { 161 return 0; 162 } else { 163 return 1; 164 } 165 } 166 167 /** 168 * Reads from the current ZIP entry into an array of bytes. 169 * If <code>len</code> is not zero, the method 170 * blocks until some input is available; otherwise, no 171 * bytes are read and <code>0</code> is returned. 172 * @param b the buffer into which the data is read 173 * @param off the start offset in the destination array <code>b</code> 174 * @param len the maximum number of bytes read 175 * @return the actual number of bytes read, or -1 if the end of the 176 * entry is reached 177 * @exception NullPointerException if <code>b</code> is <code>null</code>. 178 * @exception IndexOutOfBoundsException if <code>off</code> is negative, 179 * <code>len</code> is negative, or <code>len</code> is greater than 180 * <code>b.length - off</code> 181 * @exception ZipException if a ZIP file error has occurred 182 * @exception IOException if an I/O error has occurred 183 */ 184 public int read(byte[] b, int off, int len) throws IOException { 185 ensureOpen(); 186 if (off < 0 || len < 0 || off > b.length - len) { 187 throw new IndexOutOfBoundsException(); 188 } else if (len == 0) { 189 return 0; 190 } 191 192 if (entry == null) { 193 return -1; 194 } 195 196 if (entry.isPassphraseRequired()) { 197 if (!entry.isValidPassphrase()) { 198 throw new ZipException("possibly incorrect passphrase"); 199 } 200 201 zipCryption = entry.zipCryption; 202 super.setZipCryption(zipCryption); 203 } else { 204 zipCryption = null; 205 } 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 } 217 return len; 218 case STORED: 219 if (remaining <= 0) { 220 entryEOF = true; 221 entry = null; 222 return -1; 223 } 224 if (len > remaining) { 225 len = (int)remaining; 226 } 227 len = in.read(b, off, len); 228 if (len == -1) { 229 throw new ZipException("unexpected EOF"); 230 } 231 if (zipCryption != null) { 232 zipCryption.decryptBytes(b, off, len); 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 */ 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 */ 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 */ 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 and encryption. 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 e.flag = flag; 320 // now get the remaining fields for the entry 321 e.method = get16(tmpbuf, LOCHOW); 322 e.xdostime = get32(tmpbuf, LOCTIM); 323 if ((flag & 8) == 8) { 324 /* "Data Descriptor" present */ 325 if (e.method != DEFLATED) { 326 throw new ZipException( 327 "only DEFLATED entries can have EXT descriptor"); 328 } 329 } else { 330 e.crc = get32(tmpbuf, LOCCRC); 331 e.csize = get32(tmpbuf, LOCSIZ); 332 e.size = get32(tmpbuf, LOCLEN); 333 } 334 len = get16(tmpbuf, LOCEXT); 335 if (len > 0) { 336 byte[] extra = new byte[len]; 337 readFully(extra, 0, len); 338 e.setExtra0(extra, 339 e.csize == ZIP64_MAGICVAL || e.size == ZIP64_MAGICVAL); 340 } 341 342 if (e.isPassphraseRequired()) { 343 e.encryptionHeader = 344 new byte[TraditionalZipCryption.ENCRYPTION_HEADER_SIZE]; 345 readFully(e.encryptionHeader, 0, e.encryptionHeader.length); 346 } 347 348 return e; 349 } 350 351 /** 352 * Creates a new <code>ZipEntry</code> object for the specified 353 * entry name. 354 * 355 * @param name the ZIP file entry name 356 * @return the ZipEntry just created 357 */ 358 protected ZipEntry createZipEntry(String name) { 359 return new ZipEntry(name); 360 } 361 362 /** 363 * Reads end of deflated entry as well as EXT descriptor if present. 364 * 365 * Local headers for DEFLATED entries may optionally be followed by a 366 * data descriptor, and that data descriptor may optionally contain a 367 * leading signature (EXTSIG). 368 * 369 * From the zip spec http://www.pkware.com/documents/casestudies/APPNOTE.TXT 370 * 371 * """Although not originally assigned a signature, the value 0x08074b50 372 * has commonly been adopted as a signature value for the data descriptor 373 * record. Implementers should be aware that ZIP files may be 374 * encountered with or without this signature marking data descriptors 375 * and should account for either case when reading ZIP files to ensure 376 * compatibility.""" 377 */ 378 private void readEnd(ZipEntry e) throws IOException { 379 int n = inf.getRemaining(); 380 if (n > 0) { 381 ((PushbackInputStream)in).unread( 382 (zipCryption == null) ? buf : originBuf, 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 (zipCryption != null) { 424 e.csize -= zipCryption.getEncryptionHeaderSize(); 425 } 426 if (e.csize != inf.getBytesRead()) { 427 throw new ZipException( 428 "invalid entry compressed size (expected " + e.csize + 429 " but got " + inf.getBytesRead() + " bytes)"); 430 } 431 if (e.crc != crc.getValue()) { 432 throw new ZipException( 433 "invalid entry CRC (expected 0x" + Long.toHexString(e.crc) + 434 " but got 0x" + Long.toHexString(crc.getValue()) + ")"); 435 } 436 } 437 438 /* 439 * Reads bytes, blocking until all bytes are read. 440 */ 441 private void readFully(byte[] b, int off, int len) throws IOException { 442 while (len > 0) { 443 int n = in.read(b, off, len); 444 if (n == -1) { 445 throw new EOFException(); 446 } 447 off += n; 448 len -= n; 449 } 450 } 451 452 }