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