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