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