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