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