Rebasing the Inflater support on jzlib which, unlike GNU ClassPath, has correct implementation of Huffman code. Making the implementation more easily testable by turning Inflater and ZipInputStream into pure delegates. Current implementation is going to need proper long support.
2 * Copyright (c) 1996, 2009, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
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.
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).
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.
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
26 package org.apidesign.bck2brwsr.emul.zip;
28 import java.util.zip.*;
29 import java.io.InputStream;
30 import java.io.IOException;
31 import java.io.EOFException;
32 import java.io.PushbackInputStream;
33 import static org.apidesign.bck2brwsr.emul.zip.ZipConstants64.*;
34 import static java.util.zip.ZipInputStream.*;
37 * This class implements an input stream filter for reading files in the
38 * ZIP file format. Includes support for both compressed and uncompressed
41 * @author David Connelly
44 class ZipInputStream extends InflaterInputStream {
45 private ZipEntry entry;
47 private CRC32 crc = new CRC32();
48 private long remaining;
49 private byte[] tmpbuf = new byte[512];
51 private static final int STORED = ZipEntry.STORED;
52 private static final int DEFLATED = ZipEntry.DEFLATED;
54 private boolean closed = false;
55 // this flag is set to true after EOF has reached for
57 private boolean entryEOF = false;
60 * Check to make sure that this stream has not been closed
62 private void ensureOpen() throws IOException {
64 throw new IOException("Stream closed");
69 * Creates a new ZIP input stream.
71 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to
72 * decode the entry names.
74 * @param in the actual input stream
76 public ZipInputStream(InputStream in) {
78 super(new PushbackInputStream(in, 512), new Inflater(true), 512);
79 //usesDefaultInflater = true;
81 throw new NullPointerException("in is null");
86 * Creates a new ZIP input stream.
88 * @param in the actual input stream
91 * The {@linkplain java.nio.charset.Charset charset} to be
92 * used to decode the ZIP entry name (ignored if the
93 * <a href="package-summary.html#lang_encoding"> language
94 * encoding bit</a> of the ZIP entry's general purpose bit
99 public ZipInputStream(InputStream in, Charset charset) {
100 super(new PushbackInputStream(in, 512), new Inflater(true), 512);
101 usesDefaultInflater = true;
103 throw new NullPointerException("in is null");
106 throw new NullPointerException("charset is null");
107 this.zc = ZipCoder.get(charset);
112 * Reads the next ZIP file entry and positions the stream at the
113 * beginning of the entry data.
114 * @return the next ZIP file entry, or null if there are no more entries
115 * @exception ZipException if a ZIP file error has occurred
116 * @exception IOException if an I/O error has occurred
118 public ZipEntry getNextEntry() throws IOException {
125 if ((entry = readLOC()) == null) {
128 if (entry.getMethod() == STORED) {
129 remaining = entry.getSize();
136 * Closes the current ZIP entry and positions the stream for reading the
138 * @exception ZipException if a ZIP file error has occurred
139 * @exception IOException if an I/O error has occurred
141 public void closeEntry() throws IOException {
143 while (read(tmpbuf, 0, tmpbuf.length) != -1) ;
148 * Returns 0 after EOF has reached for the current entry data,
149 * otherwise always return 1.
151 * Programs should not count on this method to return the actual number
152 * of bytes that could be read without blocking.
154 * @return 1 before EOF and 0 after EOF has reached for current entry.
155 * @exception IOException if an I/O error occurs.
158 public int available() throws IOException {
168 * Reads from the current ZIP entry into an array of bytes.
169 * If <code>len</code> is not zero, the method
170 * blocks until some input is available; otherwise, no
171 * bytes are read and <code>0</code> is returned.
172 * @param b the buffer into which the data is read
173 * @param off the start offset in the destination array <code>b</code>
174 * @param len the maximum number of bytes read
175 * @return the actual number of bytes read, or -1 if the end of the
177 * @exception NullPointerException if <code>b</code> is <code>null</code>.
178 * @exception IndexOutOfBoundsException if <code>off</code> is negative,
179 * <code>len</code> is negative, or <code>len</code> is greater than
180 * <code>b.length - off</code>
181 * @exception ZipException if a ZIP file error has occurred
182 * @exception IOException if an I/O error has occurred
184 public int read(byte[] b, int off, int len) throws IOException {
186 if (off < 0 || len < 0 || off > b.length - len) {
187 throw new IndexOutOfBoundsException();
188 } else if (len == 0) {
195 switch (entry.getMethod()) {
197 len = super.read(b, off, len);
203 crc.update(b, off, len);
207 if (remaining <= 0) {
212 if (len > remaining) {
213 len = (int)remaining;
215 len = in.read(b, off, len);
217 throw new ZipException("unexpected EOF");
219 crc.update(b, off, len);
221 if (remaining == 0 && entry.getCrc() != crc.getValue()) {
222 throw new ZipException(
223 "invalid entry CRC (expected 0x" + Long.toHexString(entry.getCrc()) +
224 " but got 0x" + Long.toHexString(crc.getValue()) + ")");
228 throw new ZipException("invalid compression method");
233 * Skips specified number of bytes in the current ZIP entry.
234 * @param n the number of bytes to skip
235 * @return the actual number of bytes skipped
236 * @exception ZipException if a ZIP file error has occurred
237 * @exception IOException if an I/O error has occurred
238 * @exception IllegalArgumentException if n < 0
240 public long skip(long n) throws IOException {
242 throw new IllegalArgumentException("negative skip length");
245 int max = (int)Math.min(n, Integer.MAX_VALUE);
247 while (total < max) {
248 int len = max - total;
249 if (len > tmpbuf.length) {
252 len = read(tmpbuf, 0, len);
263 * Closes this input stream and releases any system resources associated
265 * @exception IOException if an I/O error has occurred
267 public void close() throws IOException {
274 private byte[] b = new byte[256];
277 * Reads local file (LOC) header for next entry.
279 private ZipEntry readLOC() throws IOException {
281 readFully(tmpbuf, 0, LOCHDR);
282 } catch (EOFException e) {
285 if (get32(tmpbuf, 0) != LOCSIG) {
288 // get flag first, we need check EFS.
289 flag = get16(tmpbuf, LOCFLG);
290 // get the entry name and create the ZipEntry first
291 int len = get16(tmpbuf, LOCNAM);
299 readFully(b, 0, len);
300 // Force to use UTF-8 if the EFS bit is ON, even the cs is NOT UTF-8
301 ZipEntry e = createZipEntry(((flag & EFS) != 0)
302 ? toStringUTF8(b, len)
304 // now get the remaining fields for the entry
305 if ((flag & 1) == 1) {
306 throw new ZipException("encrypted ZIP entry not supported");
308 e.setMethod(get16(tmpbuf, LOCHOW));
309 e.setTime(get32(tmpbuf, LOCTIM));
310 if ((flag & 8) == 8) {
311 /* "Data Descriptor" present */
312 if (e.getMethod() != DEFLATED) {
313 throw new ZipException(
314 "only DEFLATED entries can have EXT descriptor");
317 e.setCrc(get32(tmpbuf, LOCCRC));
318 e.setCompressedSize(get32(tmpbuf, LOCSIZ));
319 e.setSize(get32(tmpbuf, LOCLEN));
321 len = get16(tmpbuf, LOCEXT);
323 byte[] bb = new byte[len];
324 readFully(bb, 0, len);
326 // extra fields are in "HeaderID(2)DataSize(2)Data... format
327 if (e.getCompressedSize() == ZIP64_MAGICVAL || e.getCompressedSize() == ZIP64_MAGICVAL) {
329 while (off + 4 < len) {
330 int sz = get16(bb, off + 2);
331 if (get16(bb, off) == ZIP64_EXTID) {
333 // LOC extra zip64 entry MUST include BOTH original and
334 // compressed file size fields
335 if (sz < 16 || (off + sz) > len ) {
336 // Invalid zip64 extra fields, simply skip. Even it's
337 // rare, it's possible the entry size happens to be
338 // the magic value and it "accidnetly" has some bytes
339 // in extra match the id.
342 e.setSize(get64(bb, off));
343 e.setCompressedSize(get64(bb, off + 8));
354 * Creates a new <code>ZipEntry</code> object for the specified
357 * @param name the ZIP file entry name
358 * @return the ZipEntry just created
360 protected ZipEntry createZipEntry(String name) {
361 return new ZipEntry(name);
365 * Reads end of deflated entry as well as EXT descriptor if present.
367 private void readEnd(ZipEntry e) throws IOException {
368 int n = inf.getRemaining();
370 ((PushbackInputStream)in).unread(buf, len - n, n);
372 if ((flag & 8) == 8) {
373 /* "Data Descriptor" present */
374 if (inf.getBytesWritten() > ZIP64_MAGICVAL ||
375 inf.getBytesRead() > ZIP64_MAGICVAL) {
377 readFully(tmpbuf, 0, ZIP64_EXTHDR);
378 long sig = get32(tmpbuf, 0);
379 if (sig != EXTSIG) { // no EXTSIG present
381 e.setCompressedSize(get64(tmpbuf, ZIP64_EXTSIZ - ZIP64_EXTCRC));
382 e.setSize(get64(tmpbuf, ZIP64_EXTLEN - ZIP64_EXTCRC));
383 ((PushbackInputStream)in).unread(
384 tmpbuf, ZIP64_EXTHDR - ZIP64_EXTCRC - 1, ZIP64_EXTCRC);
386 e.setCrc(get32(tmpbuf, ZIP64_EXTCRC));
387 e.setCompressedSize(get64(tmpbuf, ZIP64_EXTSIZ));
388 e.setSize(get64(tmpbuf, ZIP64_EXTLEN));
391 readFully(tmpbuf, 0, EXTHDR);
392 long sig = get32(tmpbuf, 0);
393 if (sig != EXTSIG) { // no EXTSIG present
395 e.setCompressedSize(get32(tmpbuf, EXTSIZ - EXTCRC));
396 e.setSize(get32(tmpbuf, EXTLEN - EXTCRC));
397 ((PushbackInputStream)in).unread(
398 tmpbuf, EXTHDR - EXTCRC - 1, EXTCRC);
400 e.setCrc(get32(tmpbuf, EXTCRC));
401 e.setCompressedSize(get32(tmpbuf, EXTSIZ));
402 e.setSize(get32(tmpbuf, EXTLEN));
406 if (e.getSize() != inf.getBytesWritten()) {
407 throw new ZipException(
408 "invalid entry size (expected " + e.getSize() +
409 " but got " + inf.getBytesWritten() + " bytes)");
411 if (e.getCompressedSize() != inf.getBytesRead()) {
412 throw new ZipException(
413 "invalid entry compressed size (expected " + e.getCompressedSize() +
414 " but got " + inf.getBytesRead() + " bytes)");
416 if (e.getCrc() != crc.getValue()) {
417 throw new ZipException(
418 "invalid entry CRC (expected 0x" + Long.toHexString(e.getCrc()) +
419 " but got 0x" + Long.toHexString(crc.getValue()) + ")");
424 * Reads bytes, blocking until all bytes are read.
426 private void readFully(byte[] b, int off, int len) throws IOException {
428 int n = in.read(b, off, len);
430 throw new EOFException();
438 * Fetches unsigned 16-bit value from byte array at specified offset.
439 * The bytes are assumed to be in Intel (little-endian) byte order.
441 private static final int get16(byte b[], int off) {
442 return (b[off] & 0xff) | ((b[off+1] & 0xff) << 8);
446 * Fetches unsigned 32-bit value from byte array at specified offset.
447 * The bytes are assumed to be in Intel (little-endian) byte order.
449 private static final long get32(byte b[], int off) {
450 return (get16(b, off) | ((long)get16(b, off+2) << 16)) & 0xffffffffL;
454 * Fetches signed 64-bit value from byte array at specified offset.
455 * The bytes are assumed to be in Intel (little-endian) byte order.
457 private static final long get64(byte b[], int off) {
458 return get32(b, off) | (get32(b, off+4) << 32);
461 private static String toStringUTF8(byte[] arr, int len) {
462 return new String(arr, 0, len);
465 private static String toString(byte[] b, int len) {
466 return new String(b, 0, len);