1 /* 2 * Copyright (c) 1996, 2013, 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.SequenceInputStream; 29 import java.io.ByteArrayInputStream; 30 import java.io.FilterInputStream; 31 import java.io.InputStream; 32 import java.io.IOException; 33 import java.io.EOFException; 34 35 /** 36 * This class implements a stream filter for reading compressed data in 37 * the GZIP file format. 38 * 39 * @see InflaterInputStream 40 * @author David Connelly 41 * @since 1.1 42 * 43 */ 44 public 45 class GZIPInputStream extends InflaterInputStream { 46 /** 47 * CRC-32 for uncompressed data. 48 */ 49 protected CRC32 crc = new CRC32(); 50 51 /** 52 * Indicates end of input stream. 53 */ 54 protected boolean eos; 55 56 private boolean closed = false; 57 58 /** 59 * Check to make sure that this stream has not been closed 60 */ ensureOpen()61 private void ensureOpen() throws IOException { 62 if (closed) { 63 throw new IOException("Stream closed"); 64 } 65 } 66 67 /** 68 * Creates a new input stream with the specified buffer size. 69 * 70 * Android-note: Android limits the number of UnbufferedIO operations that can be performed, so 71 * consider using buffered inputs with this class. More information can be found in the 72 * <a href="https://developer.android.com/reference/android/os/StrictMode.ThreadPolicy.Builder#detectUnbufferedIo()"> 73 * UnbufferedIO</a> and 74 * <a href="https://developer.android.com/reference/android/os/StrictMode"> StrictMode</a> 75 * documentation. 76 * 77 * @param in the input stream 78 * @param size the input buffer size 79 * 80 * @exception ZipException if a GZIP format error has occurred or the 81 * compression method used is unsupported 82 * @exception IOException if an I/O error has occurred 83 * @exception IllegalArgumentException if {@code size <= 0} 84 */ GZIPInputStream(InputStream in, int size)85 public GZIPInputStream(InputStream in, int size) throws IOException { 86 super(in, new Inflater(true), size); 87 // Android-removed: Unconditionally close external inflaters (b/26462400) 88 // usesDefaultInflater = true; 89 // BEGIN Android-changed: Do not rely on finalization to inf.end(). 90 // readHeader(in); 91 try { 92 readHeader(in); 93 } catch (Exception e) { 94 inf.end(); 95 throw e; 96 } 97 // END Android-changed: Do not rely on finalization to inf.end(). 98 } 99 100 /** 101 * Creates a new input stream with a default buffer size. 102 * @param in the input stream 103 * 104 * @exception ZipException if a GZIP format error has occurred or the 105 * compression method used is unsupported 106 * @exception IOException if an I/O error has occurred 107 */ GZIPInputStream(InputStream in)108 public GZIPInputStream(InputStream in) throws IOException { 109 this(in, 512); 110 } 111 112 /** 113 * Reads uncompressed data into an array of bytes. If <code>len</code> is not 114 * zero, the method will block until some input can be decompressed; otherwise, 115 * no bytes are read and <code>0</code> is returned. 116 * @param buf the buffer into which the data is read 117 * @param off the start offset in the destination array <code>b</code> 118 * @param len the maximum number of bytes read 119 * @return the actual number of bytes read, or -1 if the end of the 120 * compressed input stream is reached 121 * 122 * @exception NullPointerException If <code>buf</code> is <code>null</code>. 123 * @exception IndexOutOfBoundsException If <code>off</code> is negative, 124 * <code>len</code> is negative, or <code>len</code> is greater than 125 * <code>buf.length - off</code> 126 * @exception ZipException if the compressed input data is corrupt. 127 * @exception IOException if an I/O error has occurred. 128 * 129 */ read(byte[] buf, int off, int len)130 public int read(byte[] buf, int off, int len) throws IOException { 131 ensureOpen(); 132 if (eos) { 133 return -1; 134 } 135 int n = super.read(buf, off, len); 136 if (n == -1) { 137 if (readTrailer()) 138 eos = true; 139 else 140 return this.read(buf, off, len); 141 } else { 142 crc.update(buf, off, n); 143 } 144 return n; 145 } 146 147 /** 148 * Closes this input stream and releases any system resources associated 149 * with the stream. 150 * @exception IOException if an I/O error has occurred 151 */ close()152 public void close() throws IOException { 153 if (!closed) { 154 super.close(); 155 eos = true; 156 closed = true; 157 } 158 } 159 160 /** 161 * GZIP header magic number. 162 */ 163 public static final int GZIP_MAGIC = 0x8b1f; 164 165 /* 166 * File header flags. 167 */ 168 private static final int FTEXT = 1; // Extra text 169 private static final int FHCRC = 2; // Header CRC 170 private static final int FEXTRA = 4; // Extra field 171 private static final int FNAME = 8; // File name 172 private static final int FCOMMENT = 16; // File comment 173 174 /* 175 * Reads GZIP member header and returns the total byte number 176 * of this member header. 177 */ readHeader(InputStream this_in)178 private int readHeader(InputStream this_in) throws IOException { 179 CheckedInputStream in = new CheckedInputStream(this_in, crc); 180 crc.reset(); 181 // Check header magic 182 if (readUShort(in) != GZIP_MAGIC) { 183 throw new ZipException("Not in GZIP format"); 184 } 185 // Check compression method 186 if (readUByte(in) != 8) { 187 throw new ZipException("Unsupported compression method"); 188 } 189 // Read flags 190 int flg = readUByte(in); 191 // Skip MTIME, XFL, and OS fields 192 skipBytes(in, 6); 193 int n = 2 + 2 + 6; 194 // Skip optional extra field 195 if ((flg & FEXTRA) == FEXTRA) { 196 int m = readUShort(in); 197 skipBytes(in, m); 198 n += m + 2; 199 } 200 // Skip optional file name 201 if ((flg & FNAME) == FNAME) { 202 do { 203 n++; 204 } while (readUByte(in) != 0); 205 } 206 // Skip optional file comment 207 if ((flg & FCOMMENT) == FCOMMENT) { 208 do { 209 n++; 210 } while (readUByte(in) != 0); 211 } 212 // Check optional header CRC 213 if ((flg & FHCRC) == FHCRC) { 214 int v = (int)crc.getValue() & 0xffff; 215 if (readUShort(in) != v) { 216 throw new ZipException("Corrupt GZIP header"); 217 } 218 n += 2; 219 } 220 crc.reset(); 221 return n; 222 } 223 224 /* 225 * Reads GZIP member trailer and returns true if the eos 226 * reached, false if there are more (concatenated gzip 227 * data set) 228 */ readTrailer()229 private boolean readTrailer() throws IOException { 230 InputStream in = this.in; 231 int n = inf.getRemaining(); 232 if (n > 0) { 233 in = new SequenceInputStream( 234 new ByteArrayInputStream(buf, len - n, n), 235 new FilterInputStream(in) { 236 public void close() throws IOException {} 237 }); 238 } 239 // Uses left-to-right evaluation order 240 if ((readUInt(in) != crc.getValue()) || 241 // rfc1952; ISIZE is the input size modulo 2^32 242 (readUInt(in) != (inf.getBytesWritten() & 0xffffffffL))) 243 throw new ZipException("Corrupt GZIP trailer"); 244 245 // If there are more bytes available in "in" or 246 // the leftover in the "inf" is > 26 bytes: 247 // this.trailer(8) + next.header.min(10) + next.trailer(8) 248 // try concatenated case 249 if (this.in.available() > 0 || n > 26) { 250 int m = 8; // this.trailer 251 try { 252 m += readHeader(in); // next.header 253 } catch (IOException ze) { 254 return true; // ignore any malformed, do nothing 255 } 256 inf.reset(); 257 if (n > m) 258 inf.setInput(buf, len - n + m, n - m); 259 return false; 260 } 261 return true; 262 } 263 264 /* 265 * Reads unsigned integer in Intel byte order. 266 */ readUInt(InputStream in)267 private long readUInt(InputStream in) throws IOException { 268 long s = readUShort(in); 269 return ((long)readUShort(in) << 16) | s; 270 } 271 272 /* 273 * Reads unsigned short in Intel byte order. 274 */ readUShort(InputStream in)275 private int readUShort(InputStream in) throws IOException { 276 int b = readUByte(in); 277 return (readUByte(in) << 8) | b; 278 } 279 280 /* 281 * Reads unsigned byte. 282 */ readUByte(InputStream in)283 private int readUByte(InputStream in) throws IOException { 284 int b = in.read(); 285 if (b == -1) { 286 throw new EOFException(); 287 } 288 if (b < -1 || b > 255) { 289 // Report on this.in, not argument in; see read{Header, Trailer}. 290 throw new IOException(this.in.getClass().getName() 291 + ".read() returned value out of range -1..255: " + b); 292 } 293 return b; 294 } 295 296 private byte[] tmpbuf = new byte[128]; 297 298 /* 299 * Skips bytes of input data blocking until all bytes are skipped. 300 * Does not assume that the input stream is capable of seeking. 301 */ skipBytes(InputStream in, int n)302 private void skipBytes(InputStream in, int n) throws IOException { 303 while (n > 0) { 304 int len = in.read(tmpbuf, 0, n < tmpbuf.length ? n : tmpbuf.length); 305 if (len == -1) { 306 throw new EOFException(); 307 } 308 n -= len; 309 } 310 } 311 } 312