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 static java.util.zip.ZipConstants64.*; 35 import static java.util.zip.ZipUtils.*; 36 37 /** 38 * This class implements an input stream filter for reading files in the 39 * ZIP file format. Includes support for both compressed and uncompressed 40 * entries. 41 * 42 * @author David Connelly 43 */ 44 public 45 class ZipInputStream extends InflaterInputStream implements ZipConstants { 46 private ZipEntry entry; 47 private int flag; 48 private CRC32 crc = new CRC32(); 49 private long remaining; 50 private byte[] tmpbuf = new byte[512]; 51 52 private static final int STORED = ZipEntry.STORED; 53 private static final int DEFLATED = ZipEntry.DEFLATED; 54 55 private boolean closed = false; 56 // this flag is set to true after EOF has reached for 57 // one entry 58 private boolean entryEOF = false; 59 60 private ZipCoder zc; 61 62 private ZipCryption zipCryption; 63 64 /** 65 * Check to make sure that this stream has not been closed 66 */ 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 */ 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 */ 99 public ZipInputStream(InputStream in, Charset charset) { 100 super(new PushbackInputStream(in, 512), new Inflater(true), 512); 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 * @since 1.9 117 */ 118 public ZipEntry getNextEntry() throws IOException { 119 return getNextEntry(null); 120 } 121 122 /** 123 * Reads the next ZIP file entry and positions the stream at the 124 * beginning of the entry data. 125 * @param zipCryption ZIP encrypt/decrypt engine. zip decryption will not 126 * work if this value set to null. 127 * @return the next ZIP file entry, or null if there are no more entries 128 * @exception ZipException if a ZIP file error has occurred 129 * @exception IOException if an I/O error has occurred 130 */ 131 public ZipEntry getNextEntry(ZipCryption zipCryption) throws IOException { 132 ensureOpen(); 133 if (entry != null) { 134 closeEntry(); 135 } 136 crc.reset(); 137 inf.reset(); 138 if ((entry = readLOC(zipCryption)) == null) { 139 return null; 140 } 141 if (entry.method == STORED) { 142 remaining = entry.size; 143 } 144 entryEOF = false; 145 return entry; 146 } 147 148 /** 149 * Closes the current ZIP entry and positions the stream for reading the 150 * next entry. 151 * @exception ZipException if a ZIP file error has occurred 152 * @exception IOException if an I/O error has occurred 153 */ 154 public void closeEntry() throws IOException { 155 ensureOpen(); 156 while (read(tmpbuf, 0, tmpbuf.length) != -1) ; 157 entryEOF = true; 158 } 159 160 /** 161 * Returns 0 after EOF has reached for the current entry data, 162 * otherwise always return 1. 163 * <p> 164 * Programs should not count on this method to return the actual number 165 * of bytes that could be read without blocking. 166 * 167 * @return 1 before EOF and 0 after EOF has reached for current entry. 168 * @exception IOException if an I/O error occurs. 169 * 170 */ 171 public int available() throws IOException { 172 ensureOpen(); 173 if (entryEOF) { 174 return 0; 175 } else { 176 return 1; 177 } 178 } 179 180 /** 181 * Reads from the current ZIP entry into an array of bytes. 182 * If <code>len</code> is not zero, the method 183 * blocks until some input is available; otherwise, no 184 * bytes are read and <code>0</code> is returned. 185 * @param b the buffer into which the data is read 186 * @param off the start offset in the destination array <code>b</code> 187 * @param len the maximum number of bytes read 188 * @return the actual number of bytes read, or -1 if the end of the 189 * entry is reached 190 * @exception NullPointerException if <code>b</code> is <code>null</code>. 191 * @exception IndexOutOfBoundsException if <code>off</code> is negative, 192 * <code>len</code> is negative, or <code>len</code> is greater than 193 * <code>b.length - off</code> 194 * @exception ZipException if a ZIP file error has occurred 195 * @exception IOException if an I/O error has occurred 196 */ 197 public int read(byte[] b, int off, int len) throws IOException { 198 ensureOpen(); 199 if (off < 0 || len < 0 || off > b.length - len) { 200 throw new IndexOutOfBoundsException(); 201 } else if (len == 0) { 202 return 0; 203 } 204 205 if (entry == null) { 206 return -1; 207 } 208 switch (entry.method) { 209 case DEFLATED: 210 len = super.read(b, off, len); 211 if (len == -1) { 212 readEnd(entry); 213 entryEOF = true; 214 entry = null; 215 } else { 216 crc.update(b, off, len); 217 } 218 return len; 219 case STORED: 220 if (remaining <= 0) { 221 entryEOF = true; 222 entry = null; 223 return -1; 224 } 225 if (len > remaining) { 226 len = (int)remaining; 227 } 228 len = in.read(b, off, len); 229 if (len == -1) { 230 throw new ZipException("unexpected EOF"); 231 } 232 if (zipCryption != null) { 233 zipCryption.decryptBytes(b, off, len); 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 */ 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 */ 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 */ 295 private ZipEntry readLOC(ZipCryption zipCryption) throws IOException { 296 this.zipCryption = zipCryption; 297 298 try { 299 readFully(tmpbuf, 0, LOCHDR); 300 } catch (EOFException e) { 301 return null; 302 } 303 if (get32(tmpbuf, 0) != LOCSIG) { 304 return null; 305 } 306 // get flag first, we need check EFS and encryption. 307 flag = get16(tmpbuf, LOCFLG); 308 // get the entry name and create the ZipEntry first 309 int len = get16(tmpbuf, LOCNAM); 310 int blen = b.length; 311 if (len > blen) { 312 do { 313 blen = blen * 2; 314 } while (len > blen); 315 b = new byte[blen]; 316 } 317 readFully(b, 0, len); 318 // Force to use UTF-8 if the EFS bit is ON, even the cs is NOT UTF-8 319 ZipEntry e = createZipEntry(((flag & EFS) != 0) 320 ? zc.toStringUTF8(b, len) 321 : zc.toString(b, len)); 322 e.flag = flag; 323 // now get the remaining fields for the entry 324 if (((flag & 1) == 1) && (zipCryption == null)) { 325 throw new ZipException("ZipCryption is required."); 326 } 327 e.method = get16(tmpbuf, LOCHOW); 328 e.xdostime = get32(tmpbuf, LOCTIM); 329 if ((flag & 8) == 8) { 330 /* "Data Descriptor" present */ 331 if (e.method != DEFLATED) { 332 throw new ZipException( 333 "only DEFLATED entries can have EXT descriptor"); 334 } 335 } else { 336 e.crc = get32(tmpbuf, LOCCRC); 337 e.csize = get32(tmpbuf, LOCSIZ); 338 e.size = get32(tmpbuf, LOCLEN); 339 } 340 len = get16(tmpbuf, LOCEXT); 341 if (len > 0) { 342 byte[] extra = new byte[len]; 343 readFully(extra, 0, len); 344 e.setExtra0(extra, 345 e.csize == ZIP64_MAGICVAL || e.size == ZIP64_MAGICVAL); 346 } 347 348 if (zipCryption != null) { 349 zipCryption.reset(); 350 super.setZipCryption(zipCryption); 351 352 byte[] encryptionHeader = 353 new byte[zipCryption.getEncryptionHeaderSize()]; 354 readFully(encryptionHeader, 0, encryptionHeader.length); 355 zipCryption.decryptBytes(encryptionHeader); 356 357 if (!zipCryption.isValid(e, encryptionHeader)) { 358 throw new ZipException("possibly incorrect passphrase"); 359 } 360 361 } 362 363 return e; 364 } 365 366 /** 367 * Creates a new <code>ZipEntry</code> object for the specified 368 * entry name. 369 * 370 * @param name the ZIP file entry name 371 * @return the ZipEntry just created 372 */ 373 protected ZipEntry createZipEntry(String name) { 374 return new ZipEntry(name); 375 } 376 377 /** 378 * Reads end of deflated entry as well as EXT descriptor if present. 379 * 380 * Local headers for DEFLATED entries may optionally be followed by a 381 * data descriptor, and that data descriptor may optionally contain a 382 * leading signature (EXTSIG). 383 * 384 * From the zip spec http://www.pkware.com/documents/casestudies/APPNOTE.TXT 385 * 386 * """Although not originally assigned a signature, the value 0x08074b50 387 * has commonly been adopted as a signature value for the data descriptor 388 * record. Implementers should be aware that ZIP files may be 389 * encountered with or without this signature marking data descriptors 390 * and should account for either case when reading ZIP files to ensure 391 * compatibility.""" 392 */ 393 private void readEnd(ZipEntry e) throws IOException { 394 int n = inf.getRemaining(); 395 if (n > 0) { 396 ((PushbackInputStream)in).unread( 397 (zipCryption == null) ? buf : originBuf, 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 (zipCryption != null) { 439 e.csize -= zipCryption.getEncryptionHeaderSize(); 440 } 441 if (e.csize != inf.getBytesRead()) { 442 throw new ZipException( 443 "invalid entry compressed size (expected " + e.csize + 444 " but got " + inf.getBytesRead() + " bytes)"); 445 } 446 if (e.crc != crc.getValue()) { 447 throw new ZipException( 448 "invalid entry CRC (expected 0x" + Long.toHexString(e.crc) + 449 " but got 0x" + Long.toHexString(crc.getValue()) + ")"); 450 } 451 } 452 453 /* 454 * Reads bytes, blocking until all bytes are read. 455 */ 456 private void readFully(byte[] b, int off, int len) throws IOException { 457 while (len > 0) { 458 int n = in.read(b, off, len); 459 if (n == -1) { 460 throw new EOFException(); 461 } 462 off += n; 463 len -= n; 464 } 465 } 466 467 }