1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package java.util.zip; 19 20 import java.io.EOFException; 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.io.PushbackInputStream; 24 import java.nio.charset.ModifiedUtf8; 25 import java.util.jar.Attributes; 26 import java.util.jar.JarEntry; 27 28 /** 29 * This class provides an implementation of {@code FilterInputStream} that 30 * uncompresses data from a <i>ZIP-archive</i> input stream. 31 * <p> 32 * A <i>ZIP-archive</i> is a collection of compressed (or uncompressed) files - 33 * the so called ZIP entries. Therefore when reading from a {@code 34 * ZipInputStream} first the entry's attributes will be retrieved with {@code 35 * getNextEntry} before its data is read. 36 * <p> 37 * While {@code InflaterInputStream} can read a compressed <i>ZIP-archive</i> 38 * entry, this extension can read uncompressed entries as well. 39 * <p> 40 * Use {@code ZipFile} if you can access the archive as a file directly. 41 * 42 * @see ZipEntry 43 * @see ZipFile 44 */ 45 public class ZipInputStream extends InflaterInputStream implements ZipConstants { 46 static final int DEFLATED = 8; 47 48 static final int STORED = 0; 49 50 static final int ZIPLocalHeaderVersionNeeded = 20; 51 52 private boolean entriesEnd = false; 53 54 private boolean hasDD = false; 55 56 private int entryIn = 0; 57 58 private int inRead, lastRead = 0; 59 60 ZipEntry currentEntry; 61 62 private final byte[] hdrBuf = new byte[LOCHDR - LOCVER]; 63 64 private final CRC32 crc = new CRC32(); 65 66 private byte[] nameBuf = new byte[256]; 67 68 private char[] charBuf = new char[256]; 69 70 /** 71 * Constructs a new {@code ZipInputStream} from the specified input stream. 72 * 73 * @param stream 74 * the input stream to representing a ZIP archive. 75 */ 76 public ZipInputStream(InputStream stream) { 77 super(new PushbackInputStream(stream, BUF_SIZE), new Inflater(true)); 78 if (stream == null) { 79 throw new NullPointerException(); 80 } 81 } 82 83 /** 84 * Closes this {@code ZipInputStream}. 85 * 86 * @throws IOException 87 * if an {@code IOException} occurs. 88 */ 89 @Override 90 public void close() throws IOException { 91 if (!closed) { 92 closeEntry(); // Close the current entry 93 super.close(); 94 } 95 } 96 97 /** 98 * Closes the current ZIP entry and positions to read the next entry. 99 * 100 * @throws IOException 101 * if an {@code IOException} occurs. 102 */ 103 public void closeEntry() throws IOException { 104 checkClosed(); 105 if (currentEntry == null) { 106 return; 107 } 108 if (currentEntry instanceof java.util.jar.JarEntry) { 109 Attributes temp = ((JarEntry) currentEntry).getAttributes(); 110 if (temp != null && temp.containsKey("hidden")) { 111 return; 112 } 113 } 114 115 /* 116 * The following code is careful to leave the ZipInputStream in a 117 * consistent state, even when close() results in an exception. It does 118 * so by: 119 * - pushing bytes back into the source stream 120 * - reading a data descriptor footer from the source stream 121 * - resetting fields that manage the entry being closed 122 */ 123 124 // Ensure all entry bytes are read 125 Exception failure = null; 126 try { 127 skip(Long.MAX_VALUE); 128 } catch (Exception e) { 129 failure = e; 130 } 131 132 int inB, out; 133 if (currentEntry.compressionMethod == DEFLATED) { 134 inB = inf.getTotalIn(); 135 out = inf.getTotalOut(); 136 } else { 137 inB = inRead; 138 out = inRead; 139 } 140 int diff = entryIn - inB; 141 // Pushback any required bytes 142 if (diff != 0) { 143 ((PushbackInputStream) in).unread(buf, len - diff, diff); 144 } 145 146 try { 147 readAndVerifyDataDescriptor(inB, out); 148 } catch (Exception e) { 149 if (failure == null) { // otherwise we're already going to throw 150 failure = e; 151 } 152 } 153 154 inf.reset(); 155 lastRead = inRead = entryIn = len = 0; 156 crc.reset(); 157 currentEntry = null; 158 159 if (failure != null) { 160 if (failure instanceof IOException) { 161 throw (IOException) failure; 162 } else if (failure instanceof RuntimeException) { 163 throw (RuntimeException) failure; 164 } 165 AssertionError error = new AssertionError(); 166 error.initCause(failure); 167 throw error; 168 } 169 } 170 171 private void readAndVerifyDataDescriptor(int inB, int out) throws IOException { 172 if (hasDD) { 173 in.read(hdrBuf, 0, EXTHDR); 174 long sig = getLong(hdrBuf, 0); 175 if (sig != EXTSIG) { 176 throw new ZipException(String.format("unknown format (EXTSIG=%x)", sig)); 177 } 178 currentEntry.crc = getLong(hdrBuf, EXTCRC); 179 currentEntry.compressedSize = getLong(hdrBuf, EXTSIZ); 180 currentEntry.size = getLong(hdrBuf, EXTLEN); 181 } 182 if (currentEntry.crc != crc.getValue()) { 183 throw new ZipException("CRC mismatch"); 184 } 185 if (currentEntry.compressedSize != inB || currentEntry.size != out) { 186 throw new ZipException("Size mismatch"); 187 } 188 } 189 190 /** 191 * Reads the next entry from this {@code ZipInputStream} or {@code null} if 192 * no more entries are present. 193 * 194 * @return the next {@code ZipEntry} contained in the input stream. 195 * @throws IOException 196 * if an {@code IOException} occurs. 197 * @see ZipEntry 198 */ 199 public ZipEntry getNextEntry() throws IOException { 200 closeEntry(); 201 if (entriesEnd) { 202 return null; 203 } 204 205 int x = 0, count = 0; 206 while (count != 4) { 207 count += x = in.read(hdrBuf, count, 4 - count); 208 if (x == -1) { 209 return null; 210 } 211 } 212 long hdr = getLong(hdrBuf, 0); 213 if (hdr == CENSIG) { 214 entriesEnd = true; 215 return null; 216 } 217 if (hdr != LOCSIG) { 218 return null; 219 } 220 221 // Read the local header 222 count = 0; 223 while (count != (LOCHDR - LOCVER)) { 224 count += x = in.read(hdrBuf, count, (LOCHDR - LOCVER) - count); 225 if (x == -1) { 226 throw new EOFException(); 227 } 228 } 229 int version = getShort(hdrBuf, 0) & 0xff; 230 if (version > ZIPLocalHeaderVersionNeeded) { 231 throw new ZipException("Cannot read local header version " + version); 232 } 233 int flags = getShort(hdrBuf, LOCFLG - LOCVER); 234 hasDD = ((flags & ZipFile.GPBF_DATA_DESCRIPTOR_FLAG) != 0); 235 int cetime = getShort(hdrBuf, LOCTIM - LOCVER); 236 int cemodDate = getShort(hdrBuf, LOCTIM - LOCVER + 2); 237 int cecompressionMethod = getShort(hdrBuf, LOCHOW - LOCVER); 238 long cecrc = 0, cecompressedSize = 0, cesize = -1; 239 if (!hasDD) { 240 cecrc = getLong(hdrBuf, LOCCRC - LOCVER); 241 cecompressedSize = getLong(hdrBuf, LOCSIZ - LOCVER); 242 cesize = getLong(hdrBuf, LOCLEN - LOCVER); 243 } 244 int flen = getShort(hdrBuf, LOCNAM - LOCVER); 245 if (flen == 0) { 246 throw new ZipException("Entry is not named"); 247 } 248 int elen = getShort(hdrBuf, LOCEXT - LOCVER); 249 250 count = 0; 251 if (flen > nameBuf.length) { 252 nameBuf = new byte[flen]; 253 charBuf = new char[flen]; 254 } 255 while (count != flen) { 256 count += x = in.read(nameBuf, count, flen - count); 257 if (x == -1) { 258 throw new EOFException(); 259 } 260 } 261 currentEntry = createZipEntry(ModifiedUtf8.decode(nameBuf, charBuf, 0, flen)); 262 currentEntry.time = cetime; 263 currentEntry.modDate = cemodDate; 264 currentEntry.setMethod(cecompressionMethod); 265 if (cesize != -1) { 266 currentEntry.setCrc(cecrc); 267 currentEntry.setSize(cesize); 268 currentEntry.setCompressedSize(cecompressedSize); 269 } 270 if (elen > 0) { 271 count = 0; 272 byte[] e = new byte[elen]; 273 while (count != elen) { 274 count += x = in.read(e, count, elen - count); 275 if (x == -1) { 276 throw new EOFException(); 277 } 278 } 279 currentEntry.setExtra(e); 280 } 281 return currentEntry; 282 } 283 284 /* Read 4 bytes from the buffer and store it as an int */ 285 286 /** 287 * Reads up to the specified number of uncompressed bytes into the buffer 288 * starting at the offset. 289 * 290 * @param buffer 291 * a byte array 292 * @param start 293 * the starting offset into the buffer 294 * @param length 295 * the number of bytes to read 296 * @return the number of bytes read 297 */ 298 @Override 299 public int read(byte[] buffer, int start, int length) throws IOException { 300 checkClosed(); 301 if (inf.finished() || currentEntry == null) { 302 return -1; 303 } 304 // avoid int overflow, check null buffer 305 if (start > buffer.length || length < 0 || start < 0 306 || buffer.length - start < length) { 307 throw new ArrayIndexOutOfBoundsException(); 308 } 309 310 if (currentEntry.compressionMethod == STORED) { 311 int csize = (int) currentEntry.size; 312 if (inRead >= csize) { 313 return -1; 314 } 315 if (lastRead >= len) { 316 lastRead = 0; 317 if ((len = in.read(buf)) == -1) { 318 eof = true; 319 return -1; 320 } 321 entryIn += len; 322 } 323 int toRead = length > (len - lastRead) ? len - lastRead : length; 324 if ((csize - inRead) < toRead) { 325 toRead = csize - inRead; 326 } 327 System.arraycopy(buf, lastRead, buffer, start, toRead); 328 lastRead += toRead; 329 inRead += toRead; 330 crc.update(buffer, start, toRead); 331 return toRead; 332 } 333 if (inf.needsInput()) { 334 fill(); 335 if (len > 0) { 336 entryIn += len; 337 } 338 } 339 int read; 340 try { 341 read = inf.inflate(buffer, start, length); 342 } catch (DataFormatException e) { 343 throw new ZipException(e.getMessage()); 344 } 345 if (read == 0 && inf.finished()) { 346 return -1; 347 } 348 crc.update(buffer, start, read); 349 return read; 350 } 351 352 /** 353 * Skips up to the specified number of bytes in the current ZIP entry. 354 * 355 * @param value 356 * the number of bytes to skip. 357 * @return the number of bytes skipped. 358 * @throws IOException 359 * if an {@code IOException} occurs. 360 */ 361 @Override 362 public long skip(long value) throws IOException { 363 if (value < 0) { 364 throw new IllegalArgumentException(); 365 } 366 367 long skipped = 0; 368 byte[] b = new byte[(int)Math.min(value, 2048L)]; 369 while (skipped != value) { 370 long rem = value - skipped; 371 int x = read(b, 0, (int) (b.length > rem ? rem : b.length)); 372 if (x == -1) { 373 return skipped; 374 } 375 skipped += x; 376 } 377 return skipped; 378 } 379 380 @Override 381 public int available() throws IOException { 382 checkClosed(); 383 // The InflaterInputStream contract says we must only return 0 or 1. 384 return (currentEntry == null || inRead < currentEntry.size) ? 1 : 0; 385 } 386 387 /** 388 * creates a {@link ZipEntry } with the given name. 389 * 390 * @param name 391 * the name of the entry. 392 * @return the created {@code ZipEntry}. 393 */ 394 protected ZipEntry createZipEntry(String name) { 395 return new ZipEntry(name); 396 } 397 398 private int getShort(byte[] buffer, int off) { 399 return (buffer[off] & 0xFF) | ((buffer[off + 1] & 0xFF) << 8); 400 } 401 402 private long getLong(byte[] buffer, int off) { 403 long l = 0; 404 l |= (buffer[off] & 0xFF); 405 l |= (buffer[off + 1] & 0xFF) << 8; 406 l |= (buffer[off + 2] & 0xFF) << 16; 407 l |= ((long) (buffer[off + 3] & 0xFF)) << 24; 408 return l; 409 } 410 411 private void checkClosed() throws IOException { 412 if (closed) { 413 throw new IOException("Stream is closed"); 414 } 415 } 416 } 417