1 /* 2 * Copyright (c) 2006, 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.FilterOutputStream; 29 import java.io.IOException; 30 import java.io.OutputStream; 31 32 /** 33 * Implements an output stream filter for uncompressing data stored in the 34 * "deflate" compression format. 35 * 36 * @since 1.6 37 * @author David R Tribble (david@tribble.com) 38 * 39 * @see InflaterInputStream 40 * @see DeflaterInputStream 41 * @see DeflaterOutputStream 42 */ 43 44 public class InflaterOutputStream extends FilterOutputStream { 45 /** Decompressor for this stream. */ 46 protected final Inflater inf; 47 48 /** Output buffer for writing uncompressed data. */ 49 protected final byte[] buf; 50 51 /** Temporary write buffer. */ 52 private final byte[] wbuf = new byte[1]; 53 54 /** Default decompressor is used. */ 55 private boolean usesDefaultInflater = false; 56 57 /** true iff {@link #close()} has been called. */ 58 private boolean closed = false; 59 60 /** 61 * Checks to make sure that this stream has not been closed. 62 */ ensureOpen()63 private void ensureOpen() throws IOException { 64 if (closed) { 65 throw new IOException("Stream closed"); 66 } 67 } 68 69 /** 70 * Creates a new output stream with a default decompressor and buffer 71 * size. 72 * 73 * @param out output stream to write the uncompressed data to 74 * @throws NullPointerException if {@code out} is null 75 */ InflaterOutputStream(OutputStream out)76 public InflaterOutputStream(OutputStream out) { 77 this(out, out != null ? new Inflater() : null); 78 usesDefaultInflater = true; 79 } 80 81 /** 82 * Creates a new output stream with the specified decompressor and a 83 * default buffer size. 84 * 85 * @param out output stream to write the uncompressed data to 86 * @param infl decompressor ("inflater") for this stream 87 * @throws NullPointerException if {@code out} or {@code infl} is null 88 */ InflaterOutputStream(OutputStream out, Inflater infl)89 public InflaterOutputStream(OutputStream out, Inflater infl) { 90 this(out, infl, 512); 91 } 92 93 /** 94 * Creates a new output stream with the specified decompressor and 95 * buffer size. 96 * 97 * @param out output stream to write the uncompressed data to 98 * @param infl decompressor ("inflater") for this stream 99 * @param bufLen decompression buffer size 100 * @throws IllegalArgumentException if {@code bufLen <= 0} 101 * @throws NullPointerException if {@code out} or {@code infl} is null 102 */ InflaterOutputStream(OutputStream out, Inflater infl, int bufLen)103 public InflaterOutputStream(OutputStream out, Inflater infl, int bufLen) { 104 super(out); 105 106 // Sanity checks 107 if (out == null) 108 throw new NullPointerException("Null output"); 109 if (infl == null) 110 throw new NullPointerException("Null inflater"); 111 if (bufLen <= 0) 112 throw new IllegalArgumentException("Buffer size < 1"); 113 114 // Initialize 115 inf = infl; 116 buf = new byte[bufLen]; 117 } 118 119 /** 120 * Writes any remaining uncompressed data to the output stream and closes 121 * the underlying output stream. 122 * 123 * @throws IOException if an I/O error occurs 124 */ close()125 public void close() throws IOException { 126 if (!closed) { 127 // Complete the uncompressed output 128 try { 129 finish(); 130 } finally { 131 out.close(); 132 closed = true; 133 } 134 } 135 } 136 137 /** 138 * Flushes this output stream, forcing any pending buffered output bytes to be 139 * written. 140 * 141 * @throws IOException if an I/O error occurs or this stream is already 142 * closed 143 */ flush()144 public void flush() throws IOException { 145 ensureOpen(); 146 147 // Finish decompressing and writing pending output data 148 if (!inf.finished()) { 149 try { 150 while (!inf.finished() && !inf.needsInput()) { 151 int n; 152 153 // Decompress pending output data 154 n = inf.inflate(buf, 0, buf.length); 155 if (n < 1) { 156 break; 157 } 158 159 // Write the uncompressed output data block 160 out.write(buf, 0, n); 161 } 162 super.flush(); 163 } catch (DataFormatException ex) { 164 // Improperly formatted compressed (ZIP) data 165 String msg = ex.getMessage(); 166 if (msg == null) { 167 msg = "Invalid ZLIB data format"; 168 } 169 throw new ZipException(msg); 170 } 171 } 172 } 173 174 /** 175 * Finishes writing uncompressed data to the output stream without closing 176 * the underlying stream. Use this method when applying multiple filters in 177 * succession to the same output stream. 178 * 179 * @throws IOException if an I/O error occurs or this stream is already 180 * closed 181 */ finish()182 public void finish() throws IOException { 183 ensureOpen(); 184 185 // Finish decompressing and writing pending output data 186 flush(); 187 if (usesDefaultInflater) { 188 inf.end(); 189 } 190 } 191 192 /** 193 * Writes a byte to the uncompressed output stream. 194 * 195 * @param b a single byte of compressed data to decompress and write to 196 * the output stream 197 * @throws IOException if an I/O error occurs or this stream is already 198 * closed 199 * @throws ZipException if a compression (ZIP) format error occurs 200 */ write(int b)201 public void write(int b) throws IOException { 202 // Write a single byte of data 203 wbuf[0] = (byte) b; 204 write(wbuf, 0, 1); 205 } 206 207 /** 208 * Writes an array of bytes to the uncompressed output stream. 209 * 210 * @param b buffer containing compressed data to decompress and write to 211 * the output stream 212 * @param off starting offset of the compressed data within {@code b} 213 * @param len number of bytes to decompress from {@code b} 214 * @throws IndexOutOfBoundsException if {@code off < 0}, or if 215 * {@code len < 0}, or if {@code len > b.length - off} 216 * @throws IOException if an I/O error occurs or this stream is already 217 * closed 218 * @throws NullPointerException if {@code b} is null 219 * @throws ZipException if a compression (ZIP) format error occurs 220 */ write(byte[] b, int off, int len)221 public void write(byte[] b, int off, int len) throws IOException { 222 // Sanity checks 223 ensureOpen(); 224 if (b == null) { 225 throw new NullPointerException("Null buffer for read"); 226 } else if (off < 0 || len < 0 || len > b.length - off) { 227 throw new IndexOutOfBoundsException(); 228 } else if (len == 0) { 229 return; 230 } 231 232 // Write uncompressed data to the output stream 233 try { 234 for (;;) { 235 int n; 236 237 // Fill the decompressor buffer with output data 238 if (inf.needsInput()) { 239 inf.setInput(b, off, len); 240 // Only use input buffer once. 241 len = 0; 242 } 243 244 // Decompress and write blocks of output data 245 do { 246 n = inf.inflate(buf, 0, buf.length); 247 if (n > 0) { 248 out.write(buf, 0, n); 249 } 250 } while (n > 0); 251 252 // Check for missing dictionary first 253 if (inf.needsDictionary()) { 254 throw new ZipException("ZLIB dictionary missing"); 255 } 256 // Check the decompressor 257 if (inf.finished() || (len == 0)/* no more input */) { 258 break; 259 } 260 } 261 } catch (DataFormatException ex) { 262 // Improperly formatted compressed (ZIP) data 263 String msg = ex.getMessage(); 264 if (msg == null) { 265 msg = "Invalid ZLIB data format"; 266 } 267 throw new ZipException(msg); 268 } 269 } 270 } 271