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