Add sources for API 35 Downloaded from https://dl.google.com/android/repository/source-35_r01.zip using SdkManager in Studio Test: None Change-Id: I83f78aa820b66edfdc9f8594d17bc7b6cacccec1
diff --git a/android-35/java/util/zip/Adler32.java b/android-35/java/util/zip/Adler32.java new file mode 100644 index 0000000..04e57d4 --- /dev/null +++ b/android-35/java/util/zip/Adler32.java
@@ -0,0 +1,154 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +import java.lang.ref.Reference; +import java.nio.ByteBuffer; +import sun.nio.ch.DirectBuffer; + +import jdk.internal.vm.annotation.IntrinsicCandidate; + +/** + * A class that can be used to compute the Adler-32 checksum of a data + * stream. An Adler-32 checksum is almost as reliable as a CRC-32 but + * can be computed much faster. + * + * <p> Passing a {@code null} argument to a method in this class will cause + * a {@link NullPointerException} to be thrown.</p> + * + * @author David Connelly + * @since 1.1 + */ +public class Adler32 implements Checksum { + + private int adler = 1; + + /** + * Creates a new Adler32 object. + */ + public Adler32() { + } + + /** + * Updates the checksum with the specified byte (the low eight + * bits of the argument b). + */ + @Override + public void update(int b) { + adler = update(adler, b); + } + + /** + * Updates the checksum with the specified array of bytes. + * + * @throws ArrayIndexOutOfBoundsException + * if {@code off} is negative, or {@code len} is negative, or + * {@code off+len} is negative or greater than the length of + * the array {@code b}. + */ + @Override + public void update(byte[] b, int off, int len) { + if (b == null) { + throw new NullPointerException(); + } + if (off < 0 || len < 0 || off > b.length - len) { + throw new ArrayIndexOutOfBoundsException(); + } + adler = updateBytes(adler, b, off, len); + } + + // Android-changed: method kept during jdk17u update for compatibility. + /** + * Updates the checksum with the specified array of bytes. + * + * @param b the byte array to update the checksum with + */ + @Override + public void update(byte[] b) { + adler = updateBytes(adler, b, 0, b.length); + } + + /** + * Updates the checksum with the bytes from the specified buffer. + * + * The checksum is updated with the remaining bytes in the buffer, starting + * at the buffer's position. Upon return, the buffer's position will be + * updated to its limit; its limit will not have been changed. + * + * @since 1.8 + */ + @Override + public void update(ByteBuffer buffer) { + int pos = buffer.position(); + int limit = buffer.limit(); + assert (pos <= limit); + int rem = limit - pos; + if (rem <= 0) + return; + if (buffer.isDirect()) { + try { + adler = updateByteBuffer(adler, ((DirectBuffer)buffer).address(), pos, rem); + } finally { + Reference.reachabilityFence(buffer); + } + } else if (buffer.hasArray()) { + adler = updateBytes(adler, buffer.array(), pos + buffer.arrayOffset(), rem); + } else { + byte[] b = new byte[Math.min(buffer.remaining(), 4096)]; + while (buffer.hasRemaining()) { + int length = Math.min(buffer.remaining(), b.length); + buffer.get(b, 0, length); + update(b, 0, length); + } + } + buffer.position(limit); + } + + /** + * Resets the checksum to initial value. + */ + @Override + public void reset() { + adler = 1; + } + + /** + * Returns the checksum value. + */ + @Override + public long getValue() { + return (long)adler & 0xffffffffL; + } + + private static native int update(int adler, int b); + + @IntrinsicCandidate + private static native int updateBytes(int adler, byte[] b, int off, + int len); + @IntrinsicCandidate + private static native int updateByteBuffer(int adler, long addr, + int off, int len); +}
diff --git a/android-35/java/util/zip/CRC32.java b/android-35/java/util/zip/CRC32.java new file mode 100644 index 0000000..49c9c95 --- /dev/null +++ b/android-35/java/util/zip/CRC32.java
@@ -0,0 +1,190 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +import java.lang.ref.Reference; +import java.nio.ByteBuffer; +import java.util.Objects; + +import sun.nio.ch.DirectBuffer; +import jdk.internal.vm.annotation.IntrinsicCandidate; + +/** + * A class that can be used to compute the CRC-32 of a data stream. + * + * <p> Passing a {@code null} argument to a method in this class will cause + * a {@link NullPointerException} to be thrown.</p> + * + * @author David Connelly + * @since 1.1 + */ +public class CRC32 implements Checksum { + private int crc; + + /** + * Creates a new CRC32 object. + */ + public CRC32() { + } + + + /** + * Updates the CRC-32 checksum with the specified byte (the low + * eight bits of the argument b). + */ + @Override + public void update(int b) { + crc = update(crc, b); + } + + /** + * Updates the CRC-32 checksum with the specified array of bytes. + * + * @throws ArrayIndexOutOfBoundsException + * if {@code off} is negative, or {@code len} is negative, or + * {@code off+len} is negative or greater than the length of + * the array {@code b}. + */ + @Override + public void update(byte[] b, int off, int len) { + if (b == null) { + throw new NullPointerException(); + } + if (off < 0 || len < 0 || off > b.length - len) { + throw new ArrayIndexOutOfBoundsException(); + } + crc = updateBytes(crc, b, off, len); + } + + // Android-changed: method kept during jdk17u update for compatibility. + /** + * Updates the CRC-32 checksum with the specified array of bytes. + * + * @param b the array of bytes to update the checksum with + */ + @Override + public void update(byte[] b) { + crc = updateBytes(crc, b, 0, b.length); + } + + /** + * Updates the CRC-32 checksum with the bytes from the specified buffer. + * + * The checksum is updated with the remaining bytes in the buffer, starting + * at the buffer's position. Upon return, the buffer's position will be + * updated to its limit; its limit will not have been changed. + * + * @since 1.8 + */ + @Override + public void update(ByteBuffer buffer) { + int pos = buffer.position(); + int limit = buffer.limit(); + assert (pos <= limit); + int rem = limit - pos; + if (rem <= 0) + return; + if (buffer.isDirect()) { + try { + crc = updateByteBuffer(crc, ((DirectBuffer)buffer).address(), pos, rem); + } finally { + Reference.reachabilityFence(buffer); + } + } else if (buffer.hasArray()) { + crc = updateBytes(crc, buffer.array(), pos + buffer.arrayOffset(), rem); + } else { + byte[] b = new byte[Math.min(buffer.remaining(), 4096)]; + while (buffer.hasRemaining()) { + int length = Math.min(buffer.remaining(), b.length); + buffer.get(b, 0, length); + update(b, 0, length); + } + } + buffer.position(limit); + } + + /** + * Resets CRC-32 to initial value. + */ + @Override + public void reset() { + crc = 0; + } + + /** + * Returns CRC-32 value. + */ + @Override + public long getValue() { + return (long)crc & 0xffffffffL; + } + + @IntrinsicCandidate + private static native int update(int crc, int b); + + private static int updateBytes(int crc, byte[] b, int off, int len) { + updateBytesCheck(b, off, len); + return updateBytes0(crc, b, off, len); + } + + @IntrinsicCandidate + private static native int updateBytes0(int crc, byte[] b, int off, int len); + + private static void updateBytesCheck(byte[] b, int off, int len) { + if (len <= 0) { + return; // not an error because updateBytesImpl won't execute if len <= 0 + } + + Objects.requireNonNull(b); + + if (off < 0 || off >= b.length) { + throw new ArrayIndexOutOfBoundsException(off); + } + + int endIndex = off + len - 1; + if (endIndex < 0 || endIndex >= b.length) { + throw new ArrayIndexOutOfBoundsException(endIndex); + } + } + + private static int updateByteBuffer(int alder, long addr, + int off, int len) { + updateByteBufferCheck(addr); + return updateByteBuffer0(alder, addr, off, len); + } + + @IntrinsicCandidate + private static native int updateByteBuffer0(int alder, long addr, + int off, int len); + + private static void updateByteBufferCheck(long addr) { + // Performs only a null check because bounds checks + // are not easy to do on raw addresses. + if (addr == 0L) { + throw new NullPointerException(); + } + } +}
diff --git a/android-35/java/util/zip/CRC32C.java b/android-35/java/util/zip/CRC32C.java new file mode 100644 index 0000000..fa138d4 --- /dev/null +++ b/android-35/java/util/zip/CRC32C.java
@@ -0,0 +1,348 @@ +/* + * Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package java.util.zip; + +import java.lang.ref.Reference; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import jdk.internal.misc.Unsafe; +import jdk.internal.vm.annotation.IntrinsicCandidate; +import sun.nio.ch.DirectBuffer; + +/** + * A class that can be used to compute the CRC-32C of a data stream. + * + * <p> + * CRC-32C is defined in <a href="http://www.ietf.org/rfc/rfc3720.txt">RFC + * 3720</a>: Internet Small Computer Systems Interface (iSCSI). + * </p> + * + * <p> + * Passing a {@code null} argument to a method in this class will cause a + * {@link NullPointerException} to be thrown. + * </p> + * + * @since 9 + */ +public final class CRC32C implements Checksum { + + /* + * This CRC-32C implementation uses the 'slicing-by-8' algorithm described + * in the paper "A Systematic Approach to Building High Performance + * Software-Based CRC Generators" by Michael E. Kounavis and Frank L. Berry, + * Intel Research and Development + */ + + /** + * CRC-32C Polynomial + */ + private static final int CRC32C_POLY = 0x1EDC6F41; + private static final int REVERSED_CRC32C_POLY = Integer.reverse(CRC32C_POLY); + + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + // Lookup tables + // Lookup table for single byte calculations + private static final int[] byteTable; + // Lookup tables for bulk operations in 'slicing-by-8' algorithm + private static final int[][] byteTables = new int[8][256]; + private static final int[] byteTable0 = byteTables[0]; + private static final int[] byteTable1 = byteTables[1]; + private static final int[] byteTable2 = byteTables[2]; + private static final int[] byteTable3 = byteTables[3]; + private static final int[] byteTable4 = byteTables[4]; + private static final int[] byteTable5 = byteTables[5]; + private static final int[] byteTable6 = byteTables[6]; + private static final int[] byteTable7 = byteTables[7]; + + static { + // Generate lookup tables + // High-order polynomial term stored in LSB of r. + for (int index = 0; index < byteTables[0].length; index++) { + int r = index; + for (int i = 0; i < Byte.SIZE; i++) { + if ((r & 1) != 0) { + r = (r >>> 1) ^ REVERSED_CRC32C_POLY; + } else { + r >>>= 1; + } + } + byteTables[0][index] = r; + } + + for (int index = 0; index < byteTables[0].length; index++) { + int r = byteTables[0][index]; + + for (int k = 1; k < byteTables.length; k++) { + r = byteTables[0][r & 0xFF] ^ (r >>> 8); + byteTables[k][index] = r; + } + } + + if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { + byteTable = byteTables[0]; + } else { // ByteOrder.BIG_ENDIAN + byteTable = new int[byteTable0.length]; + System.arraycopy(byteTable0, 0, byteTable, 0, byteTable0.length); + for (int[] table : byteTables) { + for (int index = 0; index < table.length; index++) { + table[index] = Integer.reverseBytes(table[index]); + } + } + } + } + + /** + * Calculated CRC-32C value + */ + private int crc = 0xFFFFFFFF; + + /** + * Creates a new CRC32C object. + */ + public CRC32C() { + } + + /** + * Updates the CRC-32C checksum with the specified byte (the low eight bits + * of the argument b). + */ + @Override + public void update(int b) { + crc = (crc >>> 8) ^ byteTable[(crc ^ (b & 0xFF)) & 0xFF]; + } + + /** + * Updates the CRC-32C checksum with the specified array of bytes. + * + * @throws ArrayIndexOutOfBoundsException + * if {@code off} is negative, or {@code len} is negative, or + * {@code off+len} is negative or greater than the length of + * the array {@code b}. + */ + @Override + public void update(byte[] b, int off, int len) { + if (b == null) { + throw new NullPointerException(); + } + if (off < 0 || len < 0 || off > b.length - len) { + throw new ArrayIndexOutOfBoundsException(); + } + crc = updateBytes(crc, b, off, (off + len)); + } + + /** + * Updates the CRC-32C checksum with the bytes from the specified buffer. + * + * The checksum is updated with the remaining bytes in the buffer, starting + * at the buffer's position. Upon return, the buffer's position will be + * updated to its limit; its limit will not have been changed. + */ + @Override + public void update(ByteBuffer buffer) { + int pos = buffer.position(); + int limit = buffer.limit(); + assert (pos <= limit); + int rem = limit - pos; + if (rem <= 0) { + return; + } + + if (buffer.isDirect()) { + try { + crc = updateDirectByteBuffer(crc, ((DirectBuffer) buffer).address(), + pos, limit); + } finally { + Reference.reachabilityFence(buffer); + } + } else if (buffer.hasArray()) { + crc = updateBytes(crc, buffer.array(), pos + buffer.arrayOffset(), + limit + buffer.arrayOffset()); + } else { + byte[] b = new byte[Math.min(buffer.remaining(), 4096)]; + while (buffer.hasRemaining()) { + int length = Math.min(buffer.remaining(), b.length); + buffer.get(b, 0, length); + update(b, 0, length); + } + } + buffer.position(limit); + } + + /** + * Resets CRC-32C to initial value. + */ + @Override + public void reset() { + crc = 0xFFFFFFFF; + } + + /** + * Returns CRC-32C value. + */ + @Override + public long getValue() { + return (~crc) & 0xFFFFFFFFL; + } + + /** + * Updates the CRC-32C checksum with the specified array of bytes. + */ + @IntrinsicCandidate + private static int updateBytes(int crc, byte[] b, int off, int end) { + + // Do only byte reads for arrays so short they can't be aligned + // or if bytes are stored with a larger witdh than one byte.,% + if (end - off >= 8 && Unsafe.ARRAY_BYTE_INDEX_SCALE == 1) { + + // align on 8 bytes + int alignLength + = (8 - ((Unsafe.ARRAY_BYTE_BASE_OFFSET + off) & 0x7)) & 0x7; + for (int alignEnd = off + alignLength; off < alignEnd; off++) { + crc = (crc >>> 8) ^ byteTable[(crc ^ b[off]) & 0xFF]; + } + + if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) { + crc = Integer.reverseBytes(crc); + } + + // slicing-by-8 + for (; off < (end - Long.BYTES); off += Long.BYTES) { + int firstHalf; + int secondHalf; + if (Unsafe.ADDRESS_SIZE == 4) { + // On 32 bit platforms read two ints instead of a single 64bit long + firstHalf = UNSAFE.getInt(b, (long)Unsafe.ARRAY_BYTE_BASE_OFFSET + off); + secondHalf = UNSAFE.getInt(b, (long)Unsafe.ARRAY_BYTE_BASE_OFFSET + off + + Integer.BYTES); + } else { + long value = UNSAFE.getLong(b, (long)Unsafe.ARRAY_BYTE_BASE_OFFSET + off); + if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { + firstHalf = (int) value; + secondHalf = (int) (value >>> 32); + } else { // ByteOrder.BIG_ENDIAN + firstHalf = (int) (value >>> 32); + secondHalf = (int) value; + } + } + crc ^= firstHalf; + if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { + crc = byteTable7[crc & 0xFF] + ^ byteTable6[(crc >>> 8) & 0xFF] + ^ byteTable5[(crc >>> 16) & 0xFF] + ^ byteTable4[crc >>> 24] + ^ byteTable3[secondHalf & 0xFF] + ^ byteTable2[(secondHalf >>> 8) & 0xFF] + ^ byteTable1[(secondHalf >>> 16) & 0xFF] + ^ byteTable0[secondHalf >>> 24]; + } else { // ByteOrder.BIG_ENDIAN + crc = byteTable0[secondHalf & 0xFF] + ^ byteTable1[(secondHalf >>> 8) & 0xFF] + ^ byteTable2[(secondHalf >>> 16) & 0xFF] + ^ byteTable3[secondHalf >>> 24] + ^ byteTable4[crc & 0xFF] + ^ byteTable5[(crc >>> 8) & 0xFF] + ^ byteTable6[(crc >>> 16) & 0xFF] + ^ byteTable7[crc >>> 24]; + } + } + + if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) { + crc = Integer.reverseBytes(crc); + } + } + + // Tail + for (; off < end; off++) { + crc = (crc >>> 8) ^ byteTable[(crc ^ b[off]) & 0xFF]; + } + + return crc; + } + + /** + * Updates the CRC-32C checksum reading from the specified address. + */ + @IntrinsicCandidate + private static int updateDirectByteBuffer(int crc, long address, + int off, int end) { + + // Do only byte reads for arrays so short they can't be aligned + if (end - off >= 8) { + + // align on 8 bytes + int alignLength = (8 - (int) ((address + off) & 0x7)) & 0x7; + for (int alignEnd = off + alignLength; off < alignEnd; off++) { + crc = (crc >>> 8) + ^ byteTable[(crc ^ UNSAFE.getByte(address + off)) & 0xFF]; + } + + if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) { + crc = Integer.reverseBytes(crc); + } + + // slicing-by-8 + for (; off <= (end - Long.BYTES); off += Long.BYTES) { + // Always reading two ints as reading a long followed by + // shifting and casting was slower. + int firstHalf = UNSAFE.getInt(address + off); + int secondHalf = UNSAFE.getInt(address + off + Integer.BYTES); + crc ^= firstHalf; + if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { + crc = byteTable7[crc & 0xFF] + ^ byteTable6[(crc >>> 8) & 0xFF] + ^ byteTable5[(crc >>> 16) & 0xFF] + ^ byteTable4[crc >>> 24] + ^ byteTable3[secondHalf & 0xFF] + ^ byteTable2[(secondHalf >>> 8) & 0xFF] + ^ byteTable1[(secondHalf >>> 16) & 0xFF] + ^ byteTable0[secondHalf >>> 24]; + } else { // ByteOrder.BIG_ENDIAN + crc = byteTable0[secondHalf & 0xFF] + ^ byteTable1[(secondHalf >>> 8) & 0xFF] + ^ byteTable2[(secondHalf >>> 16) & 0xFF] + ^ byteTable3[secondHalf >>> 24] + ^ byteTable4[crc & 0xFF] + ^ byteTable5[(crc >>> 8) & 0xFF] + ^ byteTable6[(crc >>> 16) & 0xFF] + ^ byteTable7[crc >>> 24]; + } + } + + if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) { + crc = Integer.reverseBytes(crc); + } + } + + // Tail + for (; off < end; off++) { + crc = (crc >>> 8) + ^ byteTable[(crc ^ UNSAFE.getByte(address + off)) & 0xFF]; + } + + return crc; + } +}
diff --git a/android-35/java/util/zip/CheckedInputStream.java b/android-35/java/util/zip/CheckedInputStream.java new file mode 100644 index 0000000..a97c589 --- /dev/null +++ b/android-35/java/util/zip/CheckedInputStream.java
@@ -0,0 +1,116 @@ +/* + * Copyright (c) 1996, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +import java.io.FilterInputStream; +import java.io.InputStream; +import java.io.IOException; + +/** + * An input stream that also maintains a checksum of the data being read. + * The checksum can then be used to verify the integrity of the input data. + * + * @see Checksum + * @author David Connelly + * @since 1.1 + */ +public class CheckedInputStream extends FilterInputStream { + private Checksum cksum; + + /** + * Creates an input stream using the specified Checksum. + * @param in the input stream + * @param cksum the Checksum + */ + public CheckedInputStream(InputStream in, Checksum cksum) { + super(in); + this.cksum = cksum; + } + + /** + * Reads a byte. Will block if no input is available. + * @return the byte read, or -1 if the end of the stream is reached. + * @throws IOException if an I/O error has occurred + */ + public int read() throws IOException { + int b = in.read(); + if (b != -1) { + cksum.update(b); + } + return b; + } + + /** + * Reads into an array of bytes. If {@code len} is not zero, the method + * blocks until some input is available; otherwise, no + * bytes are read and {@code 0} is returned. + * @param buf the buffer into which the data is read + * @param off the start offset in the destination array {@code b} + * @param len the maximum number of bytes read + * @return the actual number of bytes read, or -1 if the end + * of the stream is reached. + * @throws NullPointerException If {@code buf} is {@code null}. + * @throws IndexOutOfBoundsException If {@code off} is negative, + * {@code len} is negative, or {@code len} is greater than + * {@code buf.length - off} + * @throws IOException if an I/O error has occurred + */ + public int read(byte[] buf, int off, int len) throws IOException { + len = in.read(buf, off, len); + if (len != -1) { + cksum.update(buf, off, len); + } + return len; + } + + /** + * Skips specified number of bytes of input. + * @param n the number of bytes to skip + * @return the actual number of bytes skipped + * @throws IOException if an I/O error has occurred + */ + public long skip(long n) throws IOException { + byte[] buf = new byte[512]; + long total = 0; + while (total < n) { + long len = n - total; + len = read(buf, 0, len < buf.length ? (int)len : buf.length); + if (len == -1) { + return total; + } + total += len; + } + return total; + } + + /** + * Returns the Checksum for this input stream. + * @return the Checksum value + */ + public Checksum getChecksum() { + return cksum; + } +}
diff --git a/android-35/java/util/zip/CheckedOutputStream.java b/android-35/java/util/zip/CheckedOutputStream.java new file mode 100644 index 0000000..10a0b26 --- /dev/null +++ b/android-35/java/util/zip/CheckedOutputStream.java
@@ -0,0 +1,84 @@ +/* + * Copyright (c) 1996, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +import java.io.FilterOutputStream; +import java.io.OutputStream; +import java.io.IOException; + +/** + * An output stream that also maintains a checksum of the data being + * written. The checksum can then be used to verify the integrity of + * the output data. + * + * @see Checksum + * @author David Connelly + * @since 1.1 + */ +public class CheckedOutputStream extends FilterOutputStream { + private Checksum cksum; + + /** + * Creates an output stream with the specified Checksum. + * @param out the output stream + * @param cksum the checksum + */ + public CheckedOutputStream(OutputStream out, Checksum cksum) { + super(out); + this.cksum = cksum; + } + + /** + * Writes a byte. Will block until the byte is actually written. + * @param b the byte to be written + * @throws IOException if an I/O error has occurred + */ + public void write(int b) throws IOException { + out.write(b); + cksum.update(b); + } + + /** + * Writes an array of bytes. Will block until the bytes are + * actually written. + * @param b the data to be written + * @param off the start offset of the data + * @param len the number of bytes to be written + * @throws IOException if an I/O error has occurred + */ + public void write(byte[] b, int off, int len) throws IOException { + out.write(b, off, len); + cksum.update(b, off, len); + } + + /** + * Returns the Checksum for this output stream. + * @return the Checksum + */ + public Checksum getChecksum() { + return cksum; + } +}
diff --git a/android-35/java/util/zip/Checksum.java b/android-35/java/util/zip/Checksum.java new file mode 100644 index 0000000..db9490c --- /dev/null +++ b/android-35/java/util/zip/Checksum.java
@@ -0,0 +1,137 @@ +/* + * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package java.util.zip; + +import java.nio.ByteBuffer; + +/** + * An interface representing a data checksum. + * + * @author David Connelly + * @since 1.1 + */ +public interface Checksum { + + /** + * Updates the current checksum with the specified byte. + * + * @param b the byte to update the checksum with + */ + public void update(int b); + + /** + * Updates the current checksum with the specified array of bytes. + * + * @implSpec This default implementation is equal to calling + * {@code update(b, 0, b.length)}. + * + * @param b the array of bytes to update the checksum with + * + * @throws NullPointerException + * if {@code b} is {@code null} + * + * @since 9 + */ + default public void update(byte[] b) { + update(b, 0, b.length); + } + + /** + * Updates the current checksum with the specified array of bytes. + * + * @param b the byte array to update the checksum with + * @param off the start offset of the data + * @param len the number of bytes to use for the update + */ + public void update(byte[] b, int off, int len); + + /** + * Updates the current checksum with the bytes from the specified buffer. + * + * The checksum is updated with the remaining bytes in the buffer, starting + * at the buffer's position. Upon return, the buffer's position will be + * updated to its limit; its limit will not have been changed. + * + * @apiNote For best performance with DirectByteBuffer and other ByteBuffer + * implementations without a backing array implementers of this interface + * should override this method. + * + * @implSpec The default implementation has the following behavior.<br> + * For ByteBuffers backed by an accessible byte array. + * <pre>{@code + * update(buffer.array(), + * buffer.position() + buffer.arrayOffset(), + * buffer.remaining()); + * }</pre> + * For ByteBuffers not backed by an accessible byte array. + * <pre>{@code + * byte[] b = new byte[Math.min(buffer.remaining(), 4096)]; + * while (buffer.hasRemaining()) { + * int length = Math.min(buffer.remaining(), b.length); + * buffer.get(b, 0, length); + * update(b, 0, length); + * } + * }</pre> + * + * @param buffer the ByteBuffer to update the checksum with + * + * @throws NullPointerException + * if {@code buffer} is {@code null} + * + * @since 9 + */ + default public void update(ByteBuffer buffer) { + int pos = buffer.position(); + int limit = buffer.limit(); + assert (pos <= limit); + int rem = limit - pos; + if (rem <= 0) { + return; + } + if (buffer.hasArray()) { + update(buffer.array(), pos + buffer.arrayOffset(), rem); + } else { + byte[] b = new byte[Math.min(buffer.remaining(), 4096)]; + while (buffer.hasRemaining()) { + int length = Math.min(buffer.remaining(), b.length); + buffer.get(b, 0, length); + update(b, 0, length); + } + } + buffer.position(limit); + } + + /** + * Returns the current checksum value. + * + * @return the current checksum value + */ + public long getValue(); + + /** + * Resets the checksum to its initial value. + */ + public void reset(); +}
diff --git a/android-35/java/util/zip/DataFormatException.java b/android-35/java/util/zip/DataFormatException.java new file mode 100644 index 0000000..cfb6eb6 --- /dev/null +++ b/android-35/java/util/zip/DataFormatException.java
@@ -0,0 +1,53 @@ +/* + * Copyright (c) 1996, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +/** + * Signals that a data format error has occurred. + * + * @author David Connelly + * @since 1.1 + */ +public class DataFormatException extends Exception { + @java.io.Serial + private static final long serialVersionUID = 2219632870893641452L; + + /** + * Constructs a DataFormatException with no detail message. + */ + public DataFormatException() { + super(); + } + + /** + * Constructs a DataFormatException with the specified detail message. + * A detail message is a String that describes this particular exception. + * @param s the String containing a detail message + */ + public DataFormatException(String s) { + super(s); + } +}
diff --git a/android-35/java/util/zip/Deflater.java b/android-35/java/util/zip/Deflater.java new file mode 100644 index 0000000..9910016 --- /dev/null +++ b/android-35/java/util/zip/Deflater.java
@@ -0,0 +1,982 @@ +/* + * Copyright (c) 1996, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +import dalvik.annotation.optimization.ReachabilitySensitive; +import dalvik.system.CloseGuard; +import java.lang.ref.Cleaner.Cleanable; +import java.lang.ref.Reference; +import java.nio.ByteBuffer; +import java.nio.ReadOnlyBufferException; +import java.util.Objects; + +import jdk.internal.ref.CleanerFactory; +import sun.nio.ch.DirectBuffer; + +/** + * This class provides support for general purpose compression using the + * popular ZLIB compression library. The ZLIB compression library was + * initially developed as part of the PNG graphics standard and is not + * protected by patents. It is fully described in the specifications at + * the <a href="package-summary.html#package-description">java.util.zip + * package description</a>. + * <p> + * This class deflates sequences of bytes into ZLIB compressed data format. + * The input byte sequence is provided in either byte array or byte buffer, + * via one of the {@code setInput()} methods. The output byte sequence is + * written to the output byte array or byte buffer passed to the + * {@code deflate()} methods. + * <p> + * The following code fragment demonstrates a trivial compression + * and decompression of a string using {@code Deflater} and + * {@code Inflater}. + * + * <blockquote><pre> + * try { + * // Encode a String into bytes + * String inputString = "blahblahblah"; + * byte[] input = inputString.getBytes("UTF-8"); + * + * // Compress the bytes + * byte[] output = new byte[100]; + * Deflater compresser = new Deflater(); + * compresser.setInput(input); + * compresser.finish(); + * int compressedDataLength = compresser.deflate(output); + * compresser.end(); + * + * // Decompress the bytes + * Inflater decompresser = new Inflater(); + * decompresser.setInput(output, 0, compressedDataLength); + * byte[] result = new byte[100]; + * int resultLength = decompresser.inflate(result); + * decompresser.end(); + * + * // Decode the bytes into a String + * String outputString = new String(result, 0, resultLength, "UTF-8"); + * } catch (java.io.UnsupportedEncodingException ex) { + * // handle + * } catch (java.util.zip.DataFormatException ex) { + * // handle + * } + * </pre></blockquote> + * + * @apiNote + * To release resources used by this {@code Deflater}, the {@link #end()} method + * should be called explicitly. Subclasses are responsible for the cleanup of resources + * acquired by the subclass. Subclasses that override {@link #finalize()} in order + * to perform cleanup should be modified to use alternative cleanup mechanisms such + * as {@link java.lang.ref.Cleaner} and remove the overriding {@code finalize} method. + * + * @see Inflater + * @author David Connelly + * @since 1.1 + */ + +public class Deflater { + + private final DeflaterZStreamRef zsRef; + private ByteBuffer input = ZipUtils.defaultBuf; + private byte[] inputArray; + private int inputPos, inputLim; + private int level, strategy; + private boolean setParams; + private boolean finish, finished; + private long bytesRead; + private long bytesWritten; + + // Android-added: CloseGuard support. + @ReachabilitySensitive + private final CloseGuard guard = CloseGuard.get(); + + /** + * Compression method for the deflate algorithm (the only one currently + * supported). + */ + public static final int DEFLATED = 8; + + /** + * Compression level for no compression. + */ + public static final int NO_COMPRESSION = 0; + + /** + * Compression level for fastest compression. + */ + public static final int BEST_SPEED = 1; + + /** + * Compression level for best compression. + */ + public static final int BEST_COMPRESSION = 9; + + /** + * Default compression level. + */ + public static final int DEFAULT_COMPRESSION = -1; + + /** + * Compression strategy best used for data consisting mostly of small + * values with a somewhat random distribution. Forces more Huffman coding + * and less string matching. + */ + public static final int FILTERED = 1; + + /** + * Compression strategy for Huffman coding only. + */ + public static final int HUFFMAN_ONLY = 2; + + /** + * Default compression strategy. + */ + public static final int DEFAULT_STRATEGY = 0; + + /** + * Compression flush mode used to achieve best compression result. + * + * @see Deflater#deflate(byte[], int, int, int) + * @since 1.7 + */ + public static final int NO_FLUSH = 0; + + /** + * Compression flush mode used to flush out all pending output; may + * degrade compression for some compression algorithms. + * + * @see Deflater#deflate(byte[], int, int, int) + * @since 1.7 + */ + public static final int SYNC_FLUSH = 2; + + /** + * Compression flush mode used to flush out all pending output and + * reset the deflater. Using this mode too often can seriously degrade + * compression. + * + * @see Deflater#deflate(byte[], int, int, int) + * @since 1.7 + */ + public static final int FULL_FLUSH = 3; + + /** + * Flush mode to use at the end of output. Can only be provided by the + * user by way of {@link #finish()}. + */ + private static final int FINISH = 4; + + // Android-removed: initIDs handled in register method. + /* + static { + ZipUtils.loadLibrary(); + } + */ + + /** + * Creates a new compressor using the specified compression level. + * If 'nowrap' is true then the ZLIB header and checksum fields will + * not be used in order to support the compression format used in + * both GZIP and PKZIP. + * @param level the compression level (0-9) + * @param nowrap if true then use GZIP compatible compression + */ + public Deflater(int level, boolean nowrap) { + this.level = level; + this.strategy = DEFAULT_STRATEGY; + this.zsRef = new DeflaterZStreamRef(this, + init(level, DEFAULT_STRATEGY, nowrap)); + // Android-added: CloseGuard support. + guard.open("end"); + } + + /** + * Creates a new compressor using the specified compression level. + * Compressed data will be generated in ZLIB format. + * @param level the compression level (0-9) + */ + public Deflater(int level) { + this(level, false); + } + + /** + * Creates a new compressor with the default compression level. + * Compressed data will be generated in ZLIB format. + */ + public Deflater() { + this(DEFAULT_COMPRESSION, false); + } + + /** + * Sets input data for compression. + * <p> + * One of the {@code setInput()} methods should be called whenever + * {@code needsInput()} returns true indicating that more input data + * is required. + * @param input the input data bytes + * @param off the start offset of the data + * @param len the length of the data + * @see Deflater#needsInput + */ + public void setInput(byte[] input, int off, int len) { + if (off < 0 || len < 0 || off > input.length - len) { + throw new ArrayIndexOutOfBoundsException(); + } + synchronized (zsRef) { + this.input = null; + this.inputArray = input; + this.inputPos = off; + this.inputLim = off + len; + } + } + + /** + * Sets input data for compression. + * <p> + * One of the {@code setInput()} methods should be called whenever + * {@code needsInput()} returns true indicating that more input data + * is required. + * @param input the input data bytes + * @see Deflater#needsInput + */ + public void setInput(byte[] input) { + setInput(input, 0, input.length); + } + + /** + * Sets input data for compression. + * <p> + * One of the {@code setInput()} methods should be called whenever + * {@code needsInput()} returns true indicating that more input data + * is required. + * <p> + * The given buffer's position will be advanced as deflate + * operations are performed, up to the buffer's limit. + * The input buffer may be modified (refilled) between deflate + * operations; doing so is equivalent to creating a new buffer + * and setting it with this method. + * <p> + * Modifying the input buffer's contents, position, or limit + * concurrently with an deflate operation will result in + * undefined behavior, which may include incorrect operation + * results or operation failure. + * + * @param input the input data bytes + * @see Deflater#needsInput + * @since 11 + */ + public void setInput(ByteBuffer input) { + Objects.requireNonNull(input); + synchronized (zsRef) { + this.input = input; + this.inputArray = null; + } + } + + /** + * Sets preset dictionary for compression. A preset dictionary is used + * when the history buffer can be predetermined. When the data is later + * uncompressed with Inflater.inflate(), Inflater.getAdler() can be called + * in order to get the Adler-32 value of the dictionary required for + * decompression. + * @param dictionary the dictionary data bytes + * @param off the start offset of the data + * @param len the length of the data + * @see Inflater#inflate + * @see Inflater#getAdler + */ + public void setDictionary(byte[] dictionary, int off, int len) { + if (off < 0 || len < 0 || off > dictionary.length - len) { + throw new ArrayIndexOutOfBoundsException(); + } + synchronized (zsRef) { + ensureOpen(); + setDictionary(zsRef.address(), dictionary, off, len); + } + } + + /** + * Sets preset dictionary for compression. A preset dictionary is used + * when the history buffer can be predetermined. When the data is later + * uncompressed with Inflater.inflate(), Inflater.getAdler() can be called + * in order to get the Adler-32 value of the dictionary required for + * decompression. + * @param dictionary the dictionary data bytes + * @see Inflater#inflate + * @see Inflater#getAdler + */ + public void setDictionary(byte[] dictionary) { + setDictionary(dictionary, 0, dictionary.length); + } + + /** + * Sets preset dictionary for compression. A preset dictionary is used + * when the history buffer can be predetermined. When the data is later + * uncompressed with Inflater.inflate(), Inflater.getAdler() can be called + * in order to get the Adler-32 value of the dictionary required for + * decompression. + * <p> + * The bytes in given byte buffer will be fully consumed by this method. On + * return, its position will equal its limit. + * + * @param dictionary the dictionary data bytes + * @see Inflater#inflate + * @see Inflater#getAdler + */ + public void setDictionary(ByteBuffer dictionary) { + synchronized (zsRef) { + int position = dictionary.position(); + int remaining = Math.max(dictionary.limit() - position, 0); + ensureOpen(); + if (dictionary.isDirect()) { + long address = ((DirectBuffer) dictionary).address(); + try { + setDictionaryBuffer(zsRef.address(), address + position, remaining); + } finally { + Reference.reachabilityFence(dictionary); + } + } else { + byte[] array = ZipUtils.getBufferArray(dictionary); + int offset = ZipUtils.getBufferOffset(dictionary); + setDictionary(zsRef.address(), array, offset + position, remaining); + } + dictionary.position(position + remaining); + } + } + + /** + * Sets the compression strategy to the specified value. + * + * <p> If the compression strategy is changed, the next invocation + * of {@code deflate} will compress the input available so far with + * the old strategy (and may be flushed); the new strategy will take + * effect only after that invocation. + * + * @param strategy the new compression strategy + * @throws IllegalArgumentException if the compression strategy is + * invalid + */ + public void setStrategy(int strategy) { + switch (strategy) { + case DEFAULT_STRATEGY: + case FILTERED: + case HUFFMAN_ONLY: + break; + default: + throw new IllegalArgumentException(); + } + synchronized (zsRef) { + if (this.strategy != strategy) { + this.strategy = strategy; + setParams = true; + } + } + } + + /** + * Sets the compression level to the specified value. + * + * <p> If the compression level is changed, the next invocation + * of {@code deflate} will compress the input available so far + * with the old level (and may be flushed); the new level will + * take effect only after that invocation. + * + * @param level the new compression level (0-9) + * @throws IllegalArgumentException if the compression level is invalid + */ + public void setLevel(int level) { + if ((level < 0 || level > 9) && level != DEFAULT_COMPRESSION) { + throw new IllegalArgumentException("invalid compression level"); + } + synchronized (zsRef) { + if (this.level != level) { + this.level = level; + setParams = true; + } + } + } + + /** + * Returns true if no data remains in the input buffer. This can + * be used to determine if one of the {@code setInput()} methods should be + * called in order to provide more input. + * + * @return true if the input data buffer is empty and setInput() + * should be called in order to provide more input + */ + public boolean needsInput() { + synchronized (zsRef) { + ByteBuffer input = this.input; + return input == null ? inputLim == inputPos : ! input.hasRemaining(); + } + } + + /** + * When called, indicates that compression should end with the current + * contents of the input buffer. + */ + public void finish() { + synchronized (zsRef) { + finish = true; + } + } + + /** + * Returns true if the end of the compressed data output stream has + * been reached. + * @return true if the end of the compressed data output stream has + * been reached + */ + public boolean finished() { + synchronized (zsRef) { + return finished; + } + } + + /** + * Compresses the input data and fills specified buffer with compressed + * data. Returns actual number of bytes of compressed data. A return value + * of 0 indicates that {@link #needsInput() needsInput} should be called + * in order to determine if more input data is required. + * + * <p>This method uses {@link #NO_FLUSH} as its compression flush mode. + * An invocation of this method of the form {@code deflater.deflate(b, off, len)} + * yields the same result as the invocation of + * {@code deflater.deflate(b, off, len, Deflater.NO_FLUSH)}. + * + * @param output the buffer for the compressed data + * @param off the start offset of the data + * @param len the maximum number of bytes of compressed data + * @return the actual number of bytes of compressed data written to the + * output buffer + */ + public int deflate(byte[] output, int off, int len) { + return deflate(output, off, len, NO_FLUSH); + } + + /** + * Compresses the input data and fills specified buffer with compressed + * data. Returns actual number of bytes of compressed data. A return value + * of 0 indicates that {@link #needsInput() needsInput} should be called + * in order to determine if more input data is required. + * + * <p>This method uses {@link #NO_FLUSH} as its compression flush mode. + * An invocation of this method of the form {@code deflater.deflate(b)} + * yields the same result as the invocation of + * {@code deflater.deflate(b, 0, b.length, Deflater.NO_FLUSH)}. + * + * @param output the buffer for the compressed data + * @return the actual number of bytes of compressed data written to the + * output buffer + */ + public int deflate(byte[] output) { + return deflate(output, 0, output.length, NO_FLUSH); + } + + /** + * Compresses the input data and fills specified buffer with compressed + * data. Returns actual number of bytes of compressed data. A return value + * of 0 indicates that {@link #needsInput() needsInput} should be called + * in order to determine if more input data is required. + * + * <p>This method uses {@link #NO_FLUSH} as its compression flush mode. + * An invocation of this method of the form {@code deflater.deflate(output)} + * yields the same result as the invocation of + * {@code deflater.deflate(output, Deflater.NO_FLUSH)}. + * + * @param output the buffer for the compressed data + * @return the actual number of bytes of compressed data written to the + * output buffer + * @since 11 + */ + public int deflate(ByteBuffer output) { + return deflate(output, NO_FLUSH); + } + + /** + * Compresses the input data and fills the specified buffer with compressed + * data. Returns actual number of bytes of data compressed. + * + * <p>Compression flush mode is one of the following three modes: + * + * <ul> + * <li>{@link #NO_FLUSH}: allows the deflater to decide how much data + * to accumulate, before producing output, in order to achieve the best + * compression (should be used in normal use scenario). A return value + * of 0 in this flush mode indicates that {@link #needsInput()} should + * be called in order to determine if more input data is required. + * + * <li>{@link #SYNC_FLUSH}: all pending output in the deflater is flushed, + * to the specified output buffer, so that an inflater that works on + * compressed data can get all input data available so far (In particular + * the {@link #needsInput()} returns {@code true} after this invocation + * if enough output space is provided). Flushing with {@link #SYNC_FLUSH} + * may degrade compression for some compression algorithms and so it + * should be used only when necessary. + * + * <li>{@link #FULL_FLUSH}: all pending output is flushed out as with + * {@link #SYNC_FLUSH}. The compression state is reset so that the inflater + * that works on the compressed output data can restart from this point + * if previous compressed data has been damaged or if random access is + * desired. Using {@link #FULL_FLUSH} too often can seriously degrade + * compression. + * </ul> + * + * <p>In the case of {@link #FULL_FLUSH} or {@link #SYNC_FLUSH}, if + * the return value is {@code len}, the space available in output + * buffer {@code b}, this method should be invoked again with the same + * {@code flush} parameter and more output space. Make sure that + * {@code len} is greater than 6 to avoid flush marker (5 bytes) being + * repeatedly output to the output buffer every time this method is + * invoked. + * + * <p>If the {@link #setInput(ByteBuffer)} method was called to provide a buffer + * for input, the input buffer's position will be advanced by the number of bytes + * consumed by this operation. + * + * @param output the buffer for the compressed data + * @param off the start offset of the data + * @param len the maximum number of bytes of compressed data + * @param flush the compression flush mode + * @return the actual number of bytes of compressed data written to + * the output buffer + * + * @throws IllegalArgumentException if the flush mode is invalid + * @since 1.7 + */ + public int deflate(byte[] output, int off, int len, int flush) { + if (off < 0 || len < 0 || off > output.length - len) { + throw new ArrayIndexOutOfBoundsException(); + } + if (flush != NO_FLUSH && flush != SYNC_FLUSH && flush != FULL_FLUSH) { + throw new IllegalArgumentException(); + } + synchronized (zsRef) { + ensureOpen(); + + ByteBuffer input = this.input; + if (finish) { + // disregard given flush mode in this case + flush = FINISH; + } + int params; + if (setParams) { + // bit 0: true to set params + // bit 1-2: strategy (0, 1, or 2) + // bit 3-31: level (0..9 or -1) + params = 1 | strategy << 1 | level << 3; + } else { + params = 0; + } + int inputPos; + long result; + if (input == null) { + inputPos = this.inputPos; + result = deflateBytesBytes(zsRef.address(), + inputArray, inputPos, inputLim - inputPos, + output, off, len, + flush, params); + } else { + inputPos = input.position(); + int inputRem = Math.max(input.limit() - inputPos, 0); + if (input.isDirect()) { + try { + long inputAddress = ((DirectBuffer) input).address(); + result = deflateBufferBytes(zsRef.address(), + inputAddress + inputPos, inputRem, + output, off, len, + flush, params); + } finally { + Reference.reachabilityFence(input); + } + } else { + byte[] inputArray = ZipUtils.getBufferArray(input); + int inputOffset = ZipUtils.getBufferOffset(input); + result = deflateBytesBytes(zsRef.address(), + inputArray, inputOffset + inputPos, inputRem, + output, off, len, + flush, params); + } + } + int read = (int) (result & 0x7fff_ffffL); + int written = (int) (result >>> 31 & 0x7fff_ffffL); + if ((result >>> 62 & 1) != 0) { + finished = true; + } + if (params != 0 && (result >>> 63 & 1) == 0) { + setParams = false; + } + if (input != null) { + input.position(inputPos + read); + } else { + this.inputPos = inputPos + read; + } + bytesWritten += written; + bytesRead += read; + return written; + } + } + + /** + * Compresses the input data and fills the specified buffer with compressed + * data. Returns actual number of bytes of data compressed. + * + * <p>Compression flush mode is one of the following three modes: + * + * <ul> + * <li>{@link #NO_FLUSH}: allows the deflater to decide how much data + * to accumulate, before producing output, in order to achieve the best + * compression (should be used in normal use scenario). A return value + * of 0 in this flush mode indicates that {@link #needsInput()} should + * be called in order to determine if more input data is required. + * + * <li>{@link #SYNC_FLUSH}: all pending output in the deflater is flushed, + * to the specified output buffer, so that an inflater that works on + * compressed data can get all input data available so far (In particular + * the {@link #needsInput()} returns {@code true} after this invocation + * if enough output space is provided). Flushing with {@link #SYNC_FLUSH} + * may degrade compression for some compression algorithms and so it + * should be used only when necessary. + * + * <li>{@link #FULL_FLUSH}: all pending output is flushed out as with + * {@link #SYNC_FLUSH}. The compression state is reset so that the inflater + * that works on the compressed output data can restart from this point + * if previous compressed data has been damaged or if random access is + * desired. Using {@link #FULL_FLUSH} too often can seriously degrade + * compression. + * </ul> + * + * <p>In the case of {@link #FULL_FLUSH} or {@link #SYNC_FLUSH}, if + * the return value is equal to the {@linkplain ByteBuffer#remaining() remaining space} + * of the buffer, this method should be invoked again with the same + * {@code flush} parameter and more output space. Make sure that + * the buffer has at least 6 bytes of remaining space to avoid the + * flush marker (5 bytes) being repeatedly output to the output buffer + * every time this method is invoked. + * + * <p>On success, the position of the given {@code output} byte buffer will be + * advanced by as many bytes as were produced by the operation, which is equal + * to the number returned by this method. + * + * <p>If the {@link #setInput(ByteBuffer)} method was called to provide a buffer + * for input, the input buffer's position will be advanced by the number of bytes + * consumed by this operation. + * + * @param output the buffer for the compressed data + * @param flush the compression flush mode + * @return the actual number of bytes of compressed data written to + * the output buffer + * + * @throws IllegalArgumentException if the flush mode is invalid + * @since 11 + */ + public int deflate(ByteBuffer output, int flush) { + if (output.isReadOnly()) { + throw new ReadOnlyBufferException(); + } + if (flush != NO_FLUSH && flush != SYNC_FLUSH && flush != FULL_FLUSH) { + throw new IllegalArgumentException(); + } + synchronized (zsRef) { + ensureOpen(); + + ByteBuffer input = this.input; + if (finish) { + // disregard given flush mode in this case + flush = FINISH; + } + int params; + if (setParams) { + // bit 0: true to set params + // bit 1-2: strategy (0, 1, or 2) + // bit 3-31: level (0..9 or -1) + params = 1 | strategy << 1 | level << 3; + } else { + params = 0; + } + int outputPos = output.position(); + int outputRem = Math.max(output.limit() - outputPos, 0); + int inputPos; + long result; + if (input == null) { + inputPos = this.inputPos; + if (output.isDirect()) { + long outputAddress = ((DirectBuffer) output).address(); + try { + result = deflateBytesBuffer(zsRef.address(), + inputArray, inputPos, inputLim - inputPos, + outputAddress + outputPos, outputRem, + flush, params); + } finally { + Reference.reachabilityFence(output); + } + } else { + byte[] outputArray = ZipUtils.getBufferArray(output); + int outputOffset = ZipUtils.getBufferOffset(output); + result = deflateBytesBytes(zsRef.address(), + inputArray, inputPos, inputLim - inputPos, + outputArray, outputOffset + outputPos, outputRem, + flush, params); + } + } else { + inputPos = input.position(); + int inputRem = Math.max(input.limit() - inputPos, 0); + if (input.isDirect()) { + long inputAddress = ((DirectBuffer) input).address(); + try { + if (output.isDirect()) { + long outputAddress = outputPos + ((DirectBuffer) output).address(); + try { + result = deflateBufferBuffer(zsRef.address(), + inputAddress + inputPos, inputRem, + outputAddress, outputRem, + flush, params); + } finally { + Reference.reachabilityFence(output); + } + } else { + byte[] outputArray = ZipUtils.getBufferArray(output); + int outputOffset = ZipUtils.getBufferOffset(output); + result = deflateBufferBytes(zsRef.address(), + inputAddress + inputPos, inputRem, + outputArray, outputOffset + outputPos, outputRem, + flush, params); + } + } finally { + Reference.reachabilityFence(input); + } + } else { + byte[] inputArray = ZipUtils.getBufferArray(input); + int inputOffset = ZipUtils.getBufferOffset(input); + if (output.isDirect()) { + long outputAddress = ((DirectBuffer) output).address(); + try { + result = deflateBytesBuffer(zsRef.address(), + inputArray, inputOffset + inputPos, inputRem, + outputAddress + outputPos, outputRem, + flush, params); + } finally { + Reference.reachabilityFence(output); + } + } else { + byte[] outputArray = ZipUtils.getBufferArray(output); + int outputOffset = ZipUtils.getBufferOffset(output); + result = deflateBytesBytes(zsRef.address(), + inputArray, inputOffset + inputPos, inputRem, + outputArray, outputOffset + outputPos, outputRem, + flush, params); + } + } + } + int read = (int) (result & 0x7fff_ffffL); + int written = (int) (result >>> 31 & 0x7fff_ffffL); + if ((result >>> 62 & 1) != 0) { + finished = true; + } + if (params != 0 && (result >>> 63 & 1) == 0) { + setParams = false; + } + if (input != null) { + input.position(inputPos + read); + } else { + this.inputPos = inputPos + read; + } + output.position(outputPos + written); + bytesWritten += written; + bytesRead += read; + return written; + } + } + + /** + * Returns the ADLER-32 value of the uncompressed data. + * @return the ADLER-32 value of the uncompressed data + */ + public int getAdler() { + synchronized (zsRef) { + ensureOpen(); + return getAdler(zsRef.address()); + } + } + + /** + * Returns the total number of uncompressed bytes input so far. + * + * <p>Since the number of bytes may be greater than + * Integer.MAX_VALUE, the {@link #getBytesRead()} method is now + * the preferred means of obtaining this information.</p> + * + * @return the total number of uncompressed bytes input so far + */ + public int getTotalIn() { + return (int) getBytesRead(); + } + + /** + * Returns the total number of uncompressed bytes input so far. + * + * @return the total (non-negative) number of uncompressed bytes input so far + * @since 1.5 + */ + public long getBytesRead() { + synchronized (zsRef) { + ensureOpen(); + return bytesRead; + } + } + + /** + * Returns the total number of compressed bytes output so far. + * + * <p>Since the number of bytes may be greater than + * Integer.MAX_VALUE, the {@link #getBytesWritten()} method is now + * the preferred means of obtaining this information.</p> + * + * @return the total number of compressed bytes output so far + */ + public int getTotalOut() { + return (int) getBytesWritten(); + } + + /** + * Returns the total number of compressed bytes output so far. + * + * @return the total (non-negative) number of compressed bytes output so far + * @since 1.5 + */ + public long getBytesWritten() { + synchronized (zsRef) { + ensureOpen(); + return bytesWritten; + } + } + + /** + * Resets deflater so that a new set of input data can be processed. + * Keeps current compression level and strategy settings. + */ + public void reset() { + synchronized (zsRef) { + ensureOpen(); + reset(zsRef.address()); + finish = false; + finished = false; + input = ZipUtils.defaultBuf; + inputArray = null; + bytesRead = bytesWritten = 0; + } + } + + /** + * Closes the compressor and discards any unprocessed input. + * + * This method should be called when the compressor is no longer + * being used. Once this method is called, the behavior of the + * Deflater object is undefined. + */ + public void end() { + synchronized (zsRef) { + zsRef.clean(); + // Android-added: CloseGuard support. + if (guard != null) { + guard.close(); + } + input = ZipUtils.defaultBuf; + } + } + + private void ensureOpen() { + assert Thread.holdsLock(zsRef); + if (zsRef.address() == 0) + throw new NullPointerException("Deflater has been closed"); + } + + /** + * Returns the value of 'finish' flag. + * 'finish' will be set to true if def.finish() method is called. + */ + boolean shouldFinish() { + synchronized (zsRef) { + return finish; + } + } + + private static native long init(int level, int strategy, boolean nowrap); + private static native void setDictionary(long addr, byte[] b, int off, + int len); + private static native void setDictionaryBuffer(long addr, long bufAddress, int len); + private native long deflateBytesBytes(long addr, + byte[] inputArray, int inputOff, int inputLen, + byte[] outputArray, int outputOff, int outputLen, + int flush, int params); + private native long deflateBytesBuffer(long addr, + byte[] inputArray, int inputOff, int inputLen, + long outputAddress, int outputLen, + int flush, int params); + private native long deflateBufferBytes(long addr, + long inputAddress, int inputLen, + byte[] outputArray, int outputOff, int outputLen, + int flush, int params); + private native long deflateBufferBuffer(long addr, + long inputAddress, int inputLen, + long outputAddress, int outputLen, + int flush, int params); + private static native int getAdler(long addr); + private static native void reset(long addr); + private static native void end(long addr); + + /** + * A reference to the native zlib's z_stream structure. It also + * serves as the "cleaner" to clean up the native resource when + * the Deflater is ended, closed or cleaned. + */ + static class DeflaterZStreamRef implements Runnable { + + private long address; + private final Cleanable cleanable; + + private DeflaterZStreamRef(Deflater owner, long addr) { + this.cleanable = (owner != null) ? CleanerFactory.cleaner().register(owner, this) : null; + this.address = addr; + } + + long address() { + return address; + } + + void clean() { + cleanable.clean(); + } + + public synchronized void run() { + long addr = address; + address = 0; + if (addr != 0) { + end(addr); + } + } + + } +}
diff --git a/android-35/java/util/zip/DeflaterInputStream.java b/android-35/java/util/zip/DeflaterInputStream.java new file mode 100644 index 0000000..1408c61 --- /dev/null +++ b/android-35/java/util/zip/DeflaterInputStream.java
@@ -0,0 +1,307 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * Copyright (c) 2006, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +import java.io.FilterInputStream; +import java.io.InputStream; +import java.io.IOException; + +/** + * Implements an input stream filter for compressing data in the "deflate" + * compression format. + * + * @since 1.6 + * @author David R Tribble ([email protected]) + * + * @see DeflaterOutputStream + * @see InflaterOutputStream + * @see InflaterInputStream + */ + +public class DeflaterInputStream extends FilterInputStream { + /** Compressor for this stream. */ + protected final Deflater def; + + /** Input buffer for reading compressed data. */ + protected final byte[] buf; + + /** Temporary read buffer. */ + private byte[] rbuf = new byte[1]; + + /** Default compressor is used. */ + private boolean usesDefaultDeflater = false; + + /** End of the underlying input stream has been reached. */ + private boolean reachEOF = false; + + /** + * Check to make sure that this stream has not been closed. + */ + private void ensureOpen() throws IOException { + if (in == null) { + throw new IOException("Stream closed"); + } + } + + /** + * Creates a new input stream with a default compressor and buffer + * size. + * + * @param in input stream to read the uncompressed data to + * @throws NullPointerException if {@code in} is null + */ + public DeflaterInputStream(InputStream in) { + this(in, in != null ? new Deflater() : null); + usesDefaultDeflater = true; + } + + /** + * Creates a new input stream with the specified compressor and a + * default buffer size. + * + * @param in input stream to read the uncompressed data to + * @param defl compressor ("deflater") for this stream + * @throws NullPointerException if {@code in} or {@code defl} is null + */ + public DeflaterInputStream(InputStream in, Deflater defl) { + this(in, defl, 512); + } + + /** + * Creates a new input stream with the specified compressor and buffer + * size. + * + * @param in input stream to read the uncompressed data to + * @param defl compressor ("deflater") for this stream + * @param bufLen compression buffer size + * @throws IllegalArgumentException if {@code bufLen <= 0} + * @throws NullPointerException if {@code in} or {@code defl} is null + */ + public DeflaterInputStream(InputStream in, Deflater defl, int bufLen) { + super(in); + + // Sanity checks + if (in == null) + throw new NullPointerException("Null input"); + if (defl == null) + throw new NullPointerException("Null deflater"); + if (bufLen < 1) + throw new IllegalArgumentException("Buffer size < 1"); + + // Initialize + def = defl; + buf = new byte[bufLen]; + } + + /** + * Closes this input stream and its underlying input stream, discarding + * any pending uncompressed data. + * + * @throws IOException if an I/O error occurs + */ + public void close() throws IOException { + if (in != null) { + try { + // Clean up + if (usesDefaultDeflater) { + def.end(); + } + + in.close(); + } finally { + in = null; + } + } + } + + /** + * Reads a single byte of compressed data from the input stream. + * This method will block until some input can be read and compressed. + * + * @return a single byte of compressed data, or -1 if the end of the + * uncompressed input stream is reached + * @throws IOException if an I/O error occurs or if this stream is + * already closed + */ + public int read() throws IOException { + // Read a single byte of compressed data + int len = read(rbuf, 0, 1); + if (len <= 0) + return -1; + return (rbuf[0] & 0xFF); + } + + /** + * Reads compressed data into a byte array. + * This method will block until some input can be read and compressed. + * + * @param b buffer into which the data is read + * @param off starting offset of the data within {@code b} + * @param len maximum number of compressed bytes to read into {@code b} + * @return the actual number of bytes read, or -1 if the end of the + * uncompressed input stream is reached + * @throws IndexOutOfBoundsException if {@code len > b.length - off} + * @throws IOException if an I/O error occurs or if this input stream is + * already closed + */ + public int read(byte[] b, int off, int len) throws IOException { + // Sanity checks + ensureOpen(); + if (b == null) { + throw new NullPointerException("Null buffer for read"); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + + // Read and compress (deflate) input data bytes + int cnt = 0; + while (len > 0 && !def.finished()) { + int n; + + // Read data from the input stream + if (def.needsInput()) { + n = in.read(buf, 0, buf.length); + if (n < 0) { + // End of the input stream reached + def.finish(); + } else if (n > 0) { + def.setInput(buf, 0, n); + } + } + + // Compress the input data, filling the read buffer + n = def.deflate(b, off, len); + cnt += n; + off += n; + len -= n; + } + // BEGIN Android-changed: Return more accurate value from available(). + // Set reachEOF eagerly when the Deflater has finished, and not just when the number of + // bytes is zero so that available is more accurate. + // See http://b/111589691 + /* + if (cnt == 0 && def.finished()) { + reachEOF = true; + cnt = -1; + } + */ + if (def.finished()) { + reachEOF = true; + if (cnt == 0) { + cnt = -1; + } + } + // END Android-changed: Return more accurate value from available(). + + return cnt; + } + + /** + * Skips over and discards data from the input stream. + * This method may block until the specified number of bytes are read and + * skipped. <em>Note:</em> While {@code n} is given as a {@code long}, + * the maximum number of bytes which can be skipped is + * {@code Integer.MAX_VALUE}. + * + * @param n number of bytes to be skipped + * @return the actual number of bytes skipped + * @throws IOException if an I/O error occurs or if this stream is + * already closed + */ + public long skip(long n) throws IOException { + if (n < 0) { + throw new IllegalArgumentException("negative skip length"); + } + ensureOpen(); + + // Skip bytes by repeatedly decompressing small blocks + if (rbuf.length < 512) + rbuf = new byte[512]; + + int total = (int)Math.min(n, Integer.MAX_VALUE); + long cnt = 0; + while (total > 0) { + // Read a small block of uncompressed bytes + int len = read(rbuf, 0, (total <= rbuf.length ? total : rbuf.length)); + + if (len < 0) { + break; + } + cnt += len; + total -= len; + } + return cnt; + } + + /** + * Returns 0 after EOF has been reached, otherwise always return 1. + * <p> + * Programs should not count on this method to return the actual number + * of bytes that could be read without blocking + * @return zero after the end of the underlying input stream has been + * reached, otherwise always returns 1 + * @throws IOException if an I/O error occurs or if this stream is + * already closed + */ + public int available() throws IOException { + ensureOpen(); + if (reachEOF) { + return 0; + } + return 1; + } + + /** + * Always returns {@code false} because this input stream does not support + * the {@link #mark mark()} and {@link #reset reset()} methods. + * + * @return false, always + */ + public boolean markSupported() { + return false; + } + + /** + * <i>This operation is not supported</i>. + * + * @param limit maximum bytes that can be read before invalidating the position marker + */ + public void mark(int limit) { + // Operation not supported + } + + /** + * <i>This operation is not supported</i>. + * + * @throws IOException always thrown + */ + public void reset() throws IOException { + throw new IOException("mark/reset not supported"); + } +}
diff --git a/android-35/java/util/zip/DeflaterOutputStream.java b/android-35/java/util/zip/DeflaterOutputStream.java new file mode 100644 index 0000000..8f3379f --- /dev/null +++ b/android-35/java/util/zip/DeflaterOutputStream.java
@@ -0,0 +1,299 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * Copyright (c) 1996, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +import java.io.FilterOutputStream; +import java.io.OutputStream; +import java.io.InputStream; +import java.io.IOException; + +/** + * This class implements an output stream filter for compressing data in + * the "deflate" compression format. It is also used as the basis for other + * types of compression filters, such as GZIPOutputStream. + * + * @see Deflater + * @author David Connelly + * @since 1.1 + */ +public class DeflaterOutputStream extends FilterOutputStream { + /** + * Compressor for this stream. + */ + protected Deflater def; + + /** + * Output buffer for writing compressed data. + */ + protected byte[] buf; + + /** + * Indicates that the stream has been closed. + */ + private boolean closed = false; + + private final boolean syncFlush; + + /** + * Creates a new output stream with the specified compressor, + * buffer size and flush mode. + * + * @param out the output stream + * @param def the compressor ("deflater") + * @param size the output buffer size + * @param syncFlush + * if {@code true} the {@link #flush()} method of this + * instance flushes the compressor with flush mode + * {@link Deflater#SYNC_FLUSH} before flushing the output + * stream, otherwise only flushes the output stream + * + * @throws IllegalArgumentException if {@code size <= 0} + * + * @since 1.7 + */ + public DeflaterOutputStream(OutputStream out, + Deflater def, + int size, + boolean syncFlush) { + super(out); + if (out == null || def == null) { + throw new NullPointerException(); + } else if (size <= 0) { + throw new IllegalArgumentException("buffer size <= 0"); + } + this.def = def; + this.buf = new byte[size]; + this.syncFlush = syncFlush; + } + + + /** + * Creates a new output stream with the specified compressor and + * buffer size. + * + * <p>The new output stream instance is created as if by invoking + * the 4-argument constructor DeflaterOutputStream(out, def, size, false). + * + * @param out the output stream + * @param def the compressor ("deflater") + * @param size the output buffer size + * @throws IllegalArgumentException if {@code size <= 0} + */ + public DeflaterOutputStream(OutputStream out, Deflater def, int size) { + this(out, def, size, false); + } + + /** + * Creates a new output stream with the specified compressor, flush + * mode and a default buffer size. + * + * @param out the output stream + * @param def the compressor ("deflater") + * @param syncFlush + * if {@code true} the {@link #flush()} method of this + * instance flushes the compressor with flush mode + * {@link Deflater#SYNC_FLUSH} before flushing the output + * stream, otherwise only flushes the output stream + * + * @since 1.7 + */ + public DeflaterOutputStream(OutputStream out, + Deflater def, + boolean syncFlush) { + this(out, def, 512, syncFlush); + } + + + /** + * Creates a new output stream with the specified compressor and + * a default buffer size. + * + * <p>The new output stream instance is created as if by invoking + * the 3-argument constructor DeflaterOutputStream(out, def, false). + * + * @param out the output stream + * @param def the compressor ("deflater") + */ + public DeflaterOutputStream(OutputStream out, Deflater def) { + this(out, def, 512, false); + } + + boolean usesDefaultDeflater = false; + + + /** + * Creates a new output stream with a default compressor, a default + * buffer size and the specified flush mode. + * + * @param out the output stream + * @param syncFlush + * if {@code true} the {@link #flush()} method of this + * instance flushes the compressor with flush mode + * {@link Deflater#SYNC_FLUSH} before flushing the output + * stream, otherwise only flushes the output stream + * + * @since 1.7 + */ + public DeflaterOutputStream(OutputStream out, boolean syncFlush) { + this(out, out != null ? new Deflater() : null, 512, syncFlush); + usesDefaultDeflater = true; + } + + /** + * Creates a new output stream with a default compressor and buffer size. + * + * <p>The new output stream instance is created as if by invoking + * the 2-argument constructor DeflaterOutputStream(out, false). + * + * @param out the output stream + */ + public DeflaterOutputStream(OutputStream out) { + this(out, false); + usesDefaultDeflater = true; + } + + /** + * Writes a byte to the compressed output stream. This method will + * block until the byte can be written. + * @param b the byte to be written + * @throws IOException if an I/O error has occurred + */ + public void write(int b) throws IOException { + byte[] buf = new byte[1]; + buf[0] = (byte)(b & 0xff); + write(buf, 0, 1); + } + + /** + * Writes an array of bytes to the compressed output stream. This + * method will block until all the bytes are written. + * @param b the data to be written + * @param off the start offset of the data + * @param len the length of the data + * @throws IOException if an I/O error has occurred + */ + public void write(byte[] b, int off, int len) throws IOException { + if (def.finished()) { + throw new IOException("write beyond end of stream"); + } + if ((off | len | (off + len) | (b.length - (off + len))) < 0) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + if (!def.finished()) { + def.setInput(b, off, len); + while (!def.needsInput()) { + deflate(); + } + } + } + + /** + * Finishes writing compressed data to the output stream without closing + * the underlying stream. Use this method when applying multiple filters + * in succession to the same output stream. + * @throws IOException if an I/O error has occurred + */ + public void finish() throws IOException { + if (!def.finished()) { + try{ + def.finish(); + while (!def.finished()) { + deflate(); + } + } catch(IOException e) { + if (usesDefaultDeflater) + def.end(); + throw e; + } + } + } + + /** + * Writes remaining compressed data to the output stream and closes the + * underlying stream. + * @throws IOException if an I/O error has occurred + */ + public void close() throws IOException { + if (!closed) { + try { + finish(); + } finally { + if (usesDefaultDeflater) + def.end(); + } + out.close(); + closed = true; + } + } + + /** + * Writes next block of compressed data to the output stream. + * @throws IOException if an I/O error has occurred + */ + protected void deflate() throws IOException { + // Android-changed: Output all available compressed data (b/4005091). + // See http://b/111496419 for more details. + // int len = def.deflate(buf, 0, buf.length); + // if (len > 0) { + // out.write(buf, 0, len); + // } + int len = 0; + while ((len = def.deflate(buf, 0, buf.length)) > 0) { + out.write(buf, 0, len); + } + } + + /** + * Flushes the compressed output stream. + * + * If {@link #DeflaterOutputStream(OutputStream, Deflater, int, boolean) + * syncFlush} is {@code true} when this compressed output stream is + * constructed, this method first flushes the underlying {@code compressor} + * with the flush mode {@link Deflater#SYNC_FLUSH} to force + * all pending data to be flushed out to the output stream and then + * flushes the output stream. Otherwise this method only flushes the + * output stream without flushing the {@code compressor}. + * + * @throws IOException if an I/O error has occurred + * + * @since 1.7 + */ + public void flush() throws IOException { + if (syncFlush && !def.finished()) { + int len = 0; + while ((len = def.deflate(buf, 0, buf.length, Deflater.SYNC_FLUSH)) > 0) + { + out.write(buf, 0, len); + if (len < buf.length) + break; + } + } + out.flush(); + } +}
diff --git a/android-35/java/util/zip/GZIPInputStream.java b/android-35/java/util/zip/GZIPInputStream.java new file mode 100644 index 0000000..871e9d3 --- /dev/null +++ b/android-35/java/util/zip/GZIPInputStream.java
@@ -0,0 +1,310 @@ +/* + * Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +import java.io.SequenceInputStream; +import java.io.ByteArrayInputStream; +import java.io.FilterInputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.EOFException; + +/** + * This class implements a stream filter for reading compressed data in + * the GZIP file format. + * + * @see InflaterInputStream + * @author David Connelly + * @since 1.1 + * + */ +public class GZIPInputStream extends InflaterInputStream { + /** + * CRC-32 for uncompressed data. + */ + protected CRC32 crc = new CRC32(); + + /** + * Indicates end of input stream. + */ + protected boolean eos; + + private boolean closed = false; + + /** + * Check to make sure that this stream has not been closed + */ + private void ensureOpen() throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + } + + /** + * Creates a new input stream with the specified buffer size. + * + * Android-note: Android limits the number of UnbufferedIO operations that can be performed, so + * consider using buffered inputs with this class. More information can be found in the + * <a href="https://developer.android.com/reference/android/os/StrictMode.ThreadPolicy.Builder#detectUnbufferedIo()"> + * UnbufferedIO</a> and + * <a href="https://developer.android.com/reference/android/os/StrictMode"> StrictMode</a> + * documentation. + * + * @param in the input stream + * @param size the input buffer size + * + * @throws ZipException if a GZIP format error has occurred or the + * compression method used is unsupported + * @throws IOException if an I/O error has occurred + * @throws IllegalArgumentException if {@code size <= 0} + */ + public GZIPInputStream(InputStream in, int size) throws IOException { + super(in, in != null ? new Inflater(true) : null, size); + // Android-removed: Unconditionally close external inflaters (b/26462400) + // usesDefaultInflater = true; + // BEGIN Android-changed: Do not rely on finalization to inf.end(). + // readHeader(in); + try { + readHeader(in); + } catch (Exception e) { + inf.end(); + throw e; + } + // END Android-changed: Do not rely on finalization to inf.end(). + } + + /** + * Creates a new input stream with a default buffer size. + * @param in the input stream + * + * @throws ZipException if a GZIP format error has occurred or the + * compression method used is unsupported + * @throws IOException if an I/O error has occurred + */ + public GZIPInputStream(InputStream in) throws IOException { + this(in, 512); + } + + /** + * Reads uncompressed data into an array of bytes. If {@code len} is not + * zero, the method will block until some input can be decompressed; otherwise, + * no bytes are read and {@code 0} is returned. + * @param buf the buffer into which the data is read + * @param off the start offset in the destination array {@code b} + * @param len the maximum number of bytes read + * @return the actual number of bytes read, or -1 if the end of the + * compressed input stream is reached + * + * @throws NullPointerException If {@code buf} is {@code null}. + * @throws IndexOutOfBoundsException If {@code off} is negative, + * {@code len} is negative, or {@code len} is greater than + * {@code buf.length - off} + * @throws ZipException if the compressed input data is corrupt. + * @throws IOException if an I/O error has occurred. + * + */ + public int read(byte[] buf, int off, int len) throws IOException { + ensureOpen(); + if (eos) { + return -1; + } + int n = super.read(buf, off, len); + if (n == -1) { + if (readTrailer()) + eos = true; + else + return this.read(buf, off, len); + } else { + crc.update(buf, off, n); + } + return n; + } + + /** + * Closes this input stream and releases any system resources associated + * with the stream. + * @throws IOException if an I/O error has occurred + */ + public void close() throws IOException { + if (!closed) { + super.close(); + eos = true; + closed = true; + } + } + + /** + * GZIP header magic number. + */ + public static final int GZIP_MAGIC = 0x8b1f; + + /* + * File header flags. + */ + private static final int FTEXT = 1; // Extra text + private static final int FHCRC = 2; // Header CRC + private static final int FEXTRA = 4; // Extra field + private static final int FNAME = 8; // File name + private static final int FCOMMENT = 16; // File comment + + /* + * Reads GZIP member header and returns the total byte number + * of this member header. + */ + private int readHeader(InputStream this_in) throws IOException { + CheckedInputStream in = new CheckedInputStream(this_in, crc); + crc.reset(); + // Check header magic + if (readUShort(in) != GZIP_MAGIC) { + throw new ZipException("Not in GZIP format"); + } + // Check compression method + if (readUByte(in) != 8) { + throw new ZipException("Unsupported compression method"); + } + // Read flags + int flg = readUByte(in); + // Skip MTIME, XFL, and OS fields + skipBytes(in, 6); + int n = 2 + 2 + 6; + // Skip optional extra field + if ((flg & FEXTRA) == FEXTRA) { + int m = readUShort(in); + skipBytes(in, m); + n += m + 2; + } + // Skip optional file name + if ((flg & FNAME) == FNAME) { + do { + n++; + } while (readUByte(in) != 0); + } + // Skip optional file comment + if ((flg & FCOMMENT) == FCOMMENT) { + do { + n++; + } while (readUByte(in) != 0); + } + // Check optional header CRC + if ((flg & FHCRC) == FHCRC) { + int v = (int)crc.getValue() & 0xffff; + if (readUShort(in) != v) { + throw new ZipException("Corrupt GZIP header"); + } + n += 2; + } + crc.reset(); + return n; + } + + /* + * Reads GZIP member trailer and returns true if the eos + * reached, false if there are more (concatenated gzip + * data set) + */ + private boolean readTrailer() throws IOException { + InputStream in = this.in; + int n = inf.getRemaining(); + if (n > 0) { + in = new SequenceInputStream( + new ByteArrayInputStream(buf, len - n, n), + new FilterInputStream(in) { + public void close() throws IOException {} + }); + } + // Uses left-to-right evaluation order + if ((readUInt(in) != crc.getValue()) || + // rfc1952; ISIZE is the input size modulo 2^32 + (readUInt(in) != (inf.getBytesWritten() & 0xffffffffL))) + throw new ZipException("Corrupt GZIP trailer"); + + // If there are more bytes available in "in" or + // the leftover in the "inf" is > 26 bytes: + // this.trailer(8) + next.header.min(10) + next.trailer(8) + // try concatenated case + if (this.in.available() > 0 || n > 26) { + int m = 8; // this.trailer + try { + m += readHeader(in); // next.header + } catch (IOException ze) { + return true; // ignore any malformed, do nothing + } + inf.reset(); + if (n > m) + inf.setInput(buf, len - n + m, n - m); + return false; + } + return true; + } + + /* + * Reads unsigned integer in Intel byte order. + */ + private long readUInt(InputStream in) throws IOException { + long s = readUShort(in); + return ((long)readUShort(in) << 16) | s; + } + + /* + * Reads unsigned short in Intel byte order. + */ + private int readUShort(InputStream in) throws IOException { + int b = readUByte(in); + return (readUByte(in) << 8) | b; + } + + /* + * Reads unsigned byte. + */ + private int readUByte(InputStream in) throws IOException { + int b = in.read(); + if (b == -1) { + throw new EOFException(); + } + if (b < -1 || b > 255) { + // Report on this.in, not argument in; see read{Header, Trailer}. + throw new IOException(this.in.getClass().getName() + + ".read() returned value out of range -1..255: " + b); + } + return b; + } + + private byte[] tmpbuf = new byte[128]; + + /* + * Skips bytes of input data blocking until all bytes are skipped. + * Does not assume that the input stream is capable of seeking. + */ + private void skipBytes(InputStream in, int n) throws IOException { + while (n > 0) { + int len = in.read(tmpbuf, 0, n < tmpbuf.length ? n : tmpbuf.length); + if (len == -1) { + throw new EOFException(); + } + n -= len; + } + } +}
diff --git a/android-35/java/util/zip/GZIPOutputStream.java b/android-35/java/util/zip/GZIPOutputStream.java new file mode 100644 index 0000000..6752092 --- /dev/null +++ b/android-35/java/util/zip/GZIPOutputStream.java
@@ -0,0 +1,238 @@ +/* + * Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +import java.io.OutputStream; +import java.io.IOException; + +/** + * This class implements a stream filter for writing compressed data in + * the GZIP file format. + * @author David Connelly + * @since 1.1 + * + */ +public class GZIPOutputStream extends DeflaterOutputStream { + /** + * CRC-32 of uncompressed data. + */ + protected CRC32 crc = new CRC32(); + + /* + * GZIP header magic number. + */ + private static final int GZIP_MAGIC = 0x8b1f; + + /* + * Trailer size in bytes. + * + */ + private static final int TRAILER_SIZE = 8; + + // Represents the default "unknown" value for OS header, per RFC-1952 + private static final byte OS_UNKNOWN = (byte) 255; + + /** + * Creates a new output stream with the specified buffer size. + * + * <p>The new output stream instance is created as if by invoking + * the 3-argument constructor GZIPOutputStream(out, size, false). + * + * Android-note: Android limits the number of UnbufferedIO operations that can be performed, so + * consider using buffered inputs with this class. More information can be found in the + * <a href="https://developer.android.com/reference/android/os/StrictMode.ThreadPolicy.Builder#detectUnbufferedIo()"> + * UnbufferedIO</a> and + * <a href="https://developer.android.com/reference/android/os/StrictMode"> StrictMode</a> + * documentation. + * + * @param out the output stream + * @param size the output buffer size + * @throws IOException If an I/O error has occurred. + * @throws IllegalArgumentException if {@code size <= 0} + */ + public GZIPOutputStream(OutputStream out, int size) throws IOException { + this(out, size, false); + } + + /** + * Creates a new output stream with the specified buffer size and + * flush mode. + * + * @param out the output stream + * @param size the output buffer size + * @param syncFlush + * if {@code true} invocation of the inherited + * {@link DeflaterOutputStream#flush() flush()} method of + * this instance flushes the compressor with flush mode + * {@link Deflater#SYNC_FLUSH} before flushing the output + * stream, otherwise only flushes the output stream + * @throws IOException If an I/O error has occurred. + * @throws IllegalArgumentException if {@code size <= 0} + * + * @since 1.7 + */ + public GZIPOutputStream(OutputStream out, int size, boolean syncFlush) + throws IOException + { + super(out, out != null ? new Deflater(Deflater.DEFAULT_COMPRESSION, true) : null, + size, + syncFlush); + usesDefaultDeflater = true; + writeHeader(); + crc.reset(); + } + + + /** + * Creates a new output stream with a default buffer size. + * + * <p>The new output stream instance is created as if by invoking + * the 2-argument constructor GZIPOutputStream(out, false). + * + * @param out the output stream + * @throws IOException If an I/O error has occurred. + */ + public GZIPOutputStream(OutputStream out) throws IOException { + this(out, 512, false); + } + + /** + * Creates a new output stream with a default buffer size and + * the specified flush mode. + * + * @param out the output stream + * @param syncFlush + * if {@code true} invocation of the inherited + * {@link DeflaterOutputStream#flush() flush()} method of + * this instance flushes the compressor with flush mode + * {@link Deflater#SYNC_FLUSH} before flushing the output + * stream, otherwise only flushes the output stream + * + * @throws IOException If an I/O error has occurred. + * + * @since 1.7 + */ + public GZIPOutputStream(OutputStream out, boolean syncFlush) + throws IOException + { + this(out, 512, syncFlush); + } + + /** + * Writes array of bytes to the compressed output stream. This method + * will block until all the bytes are written. + * @param buf the data to be written + * @param off the start offset of the data + * @param len the length of the data + * @throws IOException If an I/O error has occurred. + */ + public synchronized void write(byte[] buf, int off, int len) + throws IOException + { + super.write(buf, off, len); + crc.update(buf, off, len); + } + + /** + * Finishes writing compressed data to the output stream without closing + * the underlying stream. Use this method when applying multiple filters + * in succession to the same output stream. + * @throws IOException if an I/O error has occurred + */ + public void finish() throws IOException { + if (!def.finished()) { + try { + def.finish(); + while (!def.finished()) { + int len = def.deflate(buf, 0, buf.length); + if (def.finished() && len <= buf.length - TRAILER_SIZE) { + // last deflater buffer. Fit trailer at the end + writeTrailer(buf, len); + len = len + TRAILER_SIZE; + out.write(buf, 0, len); + return; + } + if (len > 0) + out.write(buf, 0, len); + } + // if we can't fit the trailer at the end of the last + // deflater buffer, we write it separately + byte[] trailer = new byte[TRAILER_SIZE]; + writeTrailer(trailer, 0); + out.write(trailer); + } catch (IOException e) { + if (usesDefaultDeflater) + def.end(); + throw e; + } + } + } + + /* + * Writes GZIP member header. + */ + private void writeHeader() throws IOException { + out.write(new byte[] { + (byte) GZIP_MAGIC, // Magic number (short) + (byte)(GZIP_MAGIC >> 8), // Magic number (short) + Deflater.DEFLATED, // Compression method (CM) + 0, // Flags (FLG) + 0, // Modification time MTIME (int) + 0, // Modification time MTIME (int) + 0, // Modification time MTIME (int) + 0, // Modification time MTIME (int) + 0, // Extra flags (XFLG) + OS_UNKNOWN // Operating system (OS) + }); + } + + /* + * Writes GZIP member trailer to a byte array, starting at a given + * offset. + */ + private void writeTrailer(byte[] buf, int offset) throws IOException { + writeInt((int)crc.getValue(), buf, offset); // CRC-32 of uncompr. data + writeInt(def.getTotalIn(), buf, offset + 4); // Number of uncompr. bytes + } + + /* + * Writes integer in Intel byte order to a byte array, starting at a + * given offset. + */ + private void writeInt(int i, byte[] buf, int offset) throws IOException { + writeShort(i & 0xffff, buf, offset); + writeShort((i >> 16) & 0xffff, buf, offset + 2); + } + + /* + * Writes short integer in Intel byte order to a byte array, starting + * at a given offset + */ + private void writeShort(int s, byte[] buf, int offset) throws IOException { + buf[offset] = (byte)(s & 0xff); + buf[offset + 1] = (byte)((s >> 8) & 0xff); + } +}
diff --git a/android-35/java/util/zip/Inflater.java b/android-35/java/util/zip/Inflater.java new file mode 100644 index 0000000..6e6c83e --- /dev/null +++ b/android-35/java/util/zip/Inflater.java
@@ -0,0 +1,787 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * Copyright (c) 1996, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +import dalvik.annotation.optimization.ReachabilitySensitive; +import dalvik.system.CloseGuard; +import java.lang.ref.Cleaner.Cleanable; +import java.lang.ref.Reference; +import java.nio.ByteBuffer; +import java.nio.ReadOnlyBufferException; +import java.util.Objects; + +import jdk.internal.ref.CleanerFactory; +import sun.nio.ch.DirectBuffer; + +/** + * This class provides support for general purpose decompression using the + * popular ZLIB compression library. The ZLIB compression library was + * initially developed as part of the PNG graphics standard and is not + * protected by patents. It is fully described in the specifications at + * the <a href="package-summary.html#package-description">java.util.zip + * package description</a>. + * <p> + * This class inflates sequences of ZLIB compressed bytes. The input byte + * sequence is provided in either byte array or byte buffer, via one of the + * {@code setInput()} methods. The output byte sequence is written to the + * output byte array or byte buffer passed to the {@code deflate()} methods. + * <p> + * The following code fragment demonstrates a trivial compression + * and decompression of a string using {@code Deflater} and + * {@code Inflater}. + * + * <blockquote><pre> + * try { + * // Encode a String into bytes + * String inputString = "blahblahblah\u20AC\u20AC"; + * byte[] input = inputString.getBytes("UTF-8"); + * + * // Compress the bytes + * byte[] output = new byte[100]; + * Deflater compresser = new Deflater(); + * compresser.setInput(input); + * compresser.finish(); + * int compressedDataLength = compresser.deflate(output); + * + * // Decompress the bytes + * Inflater decompresser = new Inflater(); + * decompresser.setInput(output, 0, compressedDataLength); + * byte[] result = new byte[100]; + * int resultLength = decompresser.inflate(result); + * decompresser.end(); + * + * // Decode the bytes into a String + * String outputString = new String(result, 0, resultLength, "UTF-8"); + * } catch (java.io.UnsupportedEncodingException ex) { + * // handle + * } catch (java.util.zip.DataFormatException ex) { + * // handle + * } + * </pre></blockquote> + * + * @apiNote + * To release resources used by this {@code Inflater}, the {@link #end()} method + * should be called explicitly. Subclasses are responsible for the cleanup of resources + * acquired by the subclass. Subclasses that override {@link #finalize()} in order + * to perform cleanup should be modified to use alternative cleanup mechanisms such + * as {@link java.lang.ref.Cleaner} and remove the overriding {@code finalize} method. + * + * @see Deflater + * @author David Connelly + * @since 1.1 + * + */ + +public class Inflater { + + private final InflaterZStreamRef zsRef; + private ByteBuffer input = ZipUtils.defaultBuf; + private byte[] inputArray; + private int inputPos, inputLim; + private boolean finished; + private boolean needDict; + private long bytesRead; + private long bytesWritten; + + // Android-added: CloseGuard support. + @ReachabilitySensitive + private final CloseGuard guard = CloseGuard.get(); + + /* + * These fields are used as an "out" parameter from JNI when a + * DataFormatException is thrown during the inflate operation. + */ + private int inputConsumed; + private int outputConsumed; + + // Android-removed: initIDs handled in register method. + /* + static { + ZipUtils.loadLibrary(); + initIDs(); + } + */ + + /** + * Creates a new decompressor. If the parameter 'nowrap' is true then + * the ZLIB header and checksum fields will not be used. This provides + * compatibility with the compression format used by both GZIP and PKZIP. + * <p> + * Note: When using the 'nowrap' option it is also necessary to provide + * an extra "dummy" byte as input. This is required by the ZLIB native + * library in order to support certain optimizations. + * + * @param nowrap if true then support GZIP compatible compression + */ + public Inflater(boolean nowrap) { + this.zsRef = new InflaterZStreamRef(this, init(nowrap)); + // Android-added: CloseGuard support. + guard.open("end"); + } + + /** + * Creates a new decompressor. + */ + public Inflater() { + this(false); + } + + /** + * Sets input data for decompression. + * <p> + * One of the {@code setInput()} methods should be called whenever + * {@code needsInput()} returns true indicating that more input data + * is required. + * + * @param input the input data bytes + * @param off the start offset of the input data + * @param len the length of the input data + * @see Inflater#needsInput + */ + public void setInput(byte[] input, int off, int len) { + if (off < 0 || len < 0 || off > input.length - len) { + throw new ArrayIndexOutOfBoundsException(); + } + synchronized (zsRef) { + this.input = null; + this.inputArray = input; + this.inputPos = off; + this.inputLim = off + len; + } + } + + /** + * Sets input data for decompression. + * <p> + * One of the {@code setInput()} methods should be called whenever + * {@code needsInput()} returns true indicating that more input data + * is required. + * + * @param input the input data bytes + * @see Inflater#needsInput + */ + public void setInput(byte[] input) { + setInput(input, 0, input.length); + } + + /** + * Sets input data for decompression. + * <p> + * One of the {@code setInput()} methods should be called whenever + * {@code needsInput()} returns true indicating that more input data + * is required. + * <p> + * The given buffer's position will be advanced as inflate + * operations are performed, up to the buffer's limit. + * The input buffer may be modified (refilled) between inflate + * operations; doing so is equivalent to creating a new buffer + * and setting it with this method. + * <p> + * Modifying the input buffer's contents, position, or limit + * concurrently with an inflate operation will result in + * undefined behavior, which may include incorrect operation + * results or operation failure. + * + * @param input the input data bytes + * @see Inflater#needsInput + * @since 11 + */ + public void setInput(ByteBuffer input) { + Objects.requireNonNull(input); + synchronized (zsRef) { + this.input = input; + this.inputArray = null; + } + } + + /** + * Sets the preset dictionary to the given array of bytes. Should be + * called when inflate() returns 0 and needsDictionary() returns true + * indicating that a preset dictionary is required. The method getAdler() + * can be used to get the Adler-32 value of the dictionary needed. + * @param dictionary the dictionary data bytes + * @param off the start offset of the data + * @param len the length of the data + * @see Inflater#needsDictionary + * @see Inflater#getAdler + */ + public void setDictionary(byte[] dictionary, int off, int len) { + if (off < 0 || len < 0 || off > dictionary.length - len) { + throw new ArrayIndexOutOfBoundsException(); + } + synchronized (zsRef) { + ensureOpen(); + setDictionary(zsRef.address(), dictionary, off, len); + needDict = false; + } + } + + /** + * Sets the preset dictionary to the given array of bytes. Should be + * called when inflate() returns 0 and needsDictionary() returns true + * indicating that a preset dictionary is required. The method getAdler() + * can be used to get the Adler-32 value of the dictionary needed. + * @param dictionary the dictionary data bytes + * @see Inflater#needsDictionary + * @see Inflater#getAdler + */ + public void setDictionary(byte[] dictionary) { + setDictionary(dictionary, 0, dictionary.length); + } + + /** + * Sets the preset dictionary to the bytes in the given buffer. Should be + * called when inflate() returns 0 and needsDictionary() returns true + * indicating that a preset dictionary is required. The method getAdler() + * can be used to get the Adler-32 value of the dictionary needed. + * <p> + * The bytes in given byte buffer will be fully consumed by this method. On + * return, its position will equal its limit. + * + * @param dictionary the dictionary data bytes + * @see Inflater#needsDictionary + * @see Inflater#getAdler + * @since 11 + */ + public void setDictionary(ByteBuffer dictionary) { + synchronized (zsRef) { + int position = dictionary.position(); + int remaining = Math.max(dictionary.limit() - position, 0); + ensureOpen(); + if (dictionary.isDirect()) { + long address = ((DirectBuffer) dictionary).address(); + try { + setDictionaryBuffer(zsRef.address(), address + position, remaining); + } finally { + Reference.reachabilityFence(dictionary); + } + } else { + byte[] array = ZipUtils.getBufferArray(dictionary); + int offset = ZipUtils.getBufferOffset(dictionary); + setDictionary(zsRef.address(), array, offset + position, remaining); + } + dictionary.position(position + remaining); + needDict = false; + } + } + + /** + * Returns the total number of bytes remaining in the input buffer. + * This can be used to find out what bytes still remain in the input + * buffer after decompression has finished. + * @return the total number of bytes remaining in the input buffer + */ + public int getRemaining() { + synchronized (zsRef) { + ByteBuffer input = this.input; + return input == null ? inputLim - inputPos : input.remaining(); + } + } + + /** + * Returns true if no data remains in the input buffer. This can + * be used to determine if one of the {@code setInput()} methods should be + * called in order to provide more input. + * + * @return true if no data remains in the input buffer + */ + public boolean needsInput() { + synchronized (zsRef) { + ByteBuffer input = this.input; + return input == null ? inputLim == inputPos : ! input.hasRemaining(); + } + } + + /** + * Returns true if a preset dictionary is needed for decompression. + * @return true if a preset dictionary is needed for decompression + * @see Inflater#setDictionary + */ + public boolean needsDictionary() { + synchronized (zsRef) { + return needDict; + } + } + + /** + * Returns true if the end of the compressed data stream has been + * reached. + * @return true if the end of the compressed data stream has been + * reached + */ + public boolean finished() { + synchronized (zsRef) { + return finished; + } + } + + /** + * Uncompresses bytes into specified buffer. Returns actual number + * of bytes uncompressed. A return value of 0 indicates that + * needsInput() or needsDictionary() should be called in order to + * determine if more input data or a preset dictionary is required. + * In the latter case, getAdler() can be used to get the Adler-32 + * value of the dictionary required. + * <p> + * If the {@link #setInput(ByteBuffer)} method was called to provide a buffer + * for input, the input buffer's position will be advanced by the number of bytes + * consumed by this operation, even in the event that a {@link DataFormatException} + * is thrown. + * <p> + * The {@linkplain #getRemaining() remaining byte count} will be reduced by + * the number of consumed input bytes. If the {@link #setInput(ByteBuffer)} + * method was called to provide a buffer for input, the input buffer's position + * will be advanced the number of consumed bytes. + * <p> + * These byte totals, as well as + * the {@linkplain #getBytesRead() total bytes read} + * and the {@linkplain #getBytesWritten() total bytes written} + * values, will be updated even in the event that a {@link DataFormatException} + * is thrown to reflect the amount of data consumed and produced before the + * exception occurred. + * + * @param output the buffer for the uncompressed data + * @param off the start offset of the data + * @param len the maximum number of uncompressed bytes + * @return the actual number of uncompressed bytes + * @throws DataFormatException if the compressed data format is invalid + * @see Inflater#needsInput + * @see Inflater#needsDictionary + */ + public int inflate(byte[] output, int off, int len) + throws DataFormatException + { + if (off < 0 || len < 0 || off > output.length - len) { + throw new ArrayIndexOutOfBoundsException(); + } + synchronized (zsRef) { + ensureOpen(); + ByteBuffer input = this.input; + long result; + int inputPos; + try { + if (input == null) { + inputPos = this.inputPos; + try { + result = inflateBytesBytes(zsRef.address(), + inputArray, inputPos, inputLim - inputPos, + output, off, len); + } catch (DataFormatException e) { + this.inputPos = inputPos + inputConsumed; + throw e; + } + } else { + inputPos = input.position(); + try { + int inputRem = Math.max(input.limit() - inputPos, 0); + if (input.isDirect()) { + try { + long inputAddress = ((DirectBuffer) input).address(); + result = inflateBufferBytes(zsRef.address(), + inputAddress + inputPos, inputRem, + output, off, len); + } finally { + Reference.reachabilityFence(input); + } + } else { + byte[] inputArray = ZipUtils.getBufferArray(input); + int inputOffset = ZipUtils.getBufferOffset(input); + result = inflateBytesBytes(zsRef.address(), + inputArray, inputOffset + inputPos, inputRem, + output, off, len); + } + } catch (DataFormatException e) { + input.position(inputPos + inputConsumed); + throw e; + } + } + } catch (DataFormatException e) { + bytesRead += inputConsumed; + inputConsumed = 0; + int written = outputConsumed; + bytesWritten += written; + outputConsumed = 0; + throw e; + } + int read = (int) (result & 0x7fff_ffffL); + int written = (int) (result >>> 31 & 0x7fff_ffffL); + if ((result >>> 62 & 1) != 0) { + finished = true; + } + if ((result >>> 63 & 1) != 0) { + needDict = true; + } + if (input != null) { + input.position(inputPos + read); + } else { + this.inputPos = inputPos + read; + } + bytesWritten += written; + bytesRead += read; + return written; + } + } + + /** + * Uncompresses bytes into specified buffer. Returns actual number + * of bytes uncompressed. A return value of 0 indicates that + * needsInput() or needsDictionary() should be called in order to + * determine if more input data or a preset dictionary is required. + * In the latter case, getAdler() can be used to get the Adler-32 + * value of the dictionary required. + * <p> + * The {@linkplain #getRemaining() remaining byte count} will be reduced by + * the number of consumed input bytes. If the {@link #setInput(ByteBuffer)} + * method was called to provide a buffer for input, the input buffer's position + * will be advanced the number of consumed bytes. + * <p> + * These byte totals, as well as + * the {@linkplain #getBytesRead() total bytes read} + * and the {@linkplain #getBytesWritten() total bytes written} + * values, will be updated even in the event that a {@link DataFormatException} + * is thrown to reflect the amount of data consumed and produced before the + * exception occurred. + * + * @param output the buffer for the uncompressed data + * @return the actual number of uncompressed bytes + * @throws DataFormatException if the compressed data format is invalid + * @see Inflater#needsInput + * @see Inflater#needsDictionary + */ + public int inflate(byte[] output) throws DataFormatException { + return inflate(output, 0, output.length); + } + + /** + * Uncompresses bytes into specified buffer. Returns actual number + * of bytes uncompressed. A return value of 0 indicates that + * needsInput() or needsDictionary() should be called in order to + * determine if more input data or a preset dictionary is required. + * In the latter case, getAdler() can be used to get the Adler-32 + * value of the dictionary required. + * <p> + * On success, the position of the given {@code output} byte buffer will be + * advanced by as many bytes as were produced by the operation, which is equal + * to the number returned by this method. Note that the position of the + * {@code output} buffer will be advanced even in the event that a + * {@link DataFormatException} is thrown. + * <p> + * The {@linkplain #getRemaining() remaining byte count} will be reduced by + * the number of consumed input bytes. If the {@link #setInput(ByteBuffer)} + * method was called to provide a buffer for input, the input buffer's position + * will be advanced the number of consumed bytes. + * <p> + * These byte totals, as well as + * the {@linkplain #getBytesRead() total bytes read} + * and the {@linkplain #getBytesWritten() total bytes written} + * values, will be updated even in the event that a {@link DataFormatException} + * is thrown to reflect the amount of data consumed and produced before the + * exception occurred. + * + * @param output the buffer for the uncompressed data + * @return the actual number of uncompressed bytes + * @throws DataFormatException if the compressed data format is invalid + * @throws ReadOnlyBufferException if the given output buffer is read-only + * @see Inflater#needsInput + * @see Inflater#needsDictionary + * @since 11 + */ + public int inflate(ByteBuffer output) throws DataFormatException { + if (output.isReadOnly()) { + throw new ReadOnlyBufferException(); + } + synchronized (zsRef) { + ensureOpen(); + ByteBuffer input = this.input; + long result; + int inputPos; + int outputPos = output.position(); + int outputRem = Math.max(output.limit() - outputPos, 0); + try { + if (input == null) { + inputPos = this.inputPos; + try { + if (output.isDirect()) { + long outputAddress = ((DirectBuffer) output).address(); + try { + result = inflateBytesBuffer(zsRef.address(), + inputArray, inputPos, inputLim - inputPos, + outputAddress + outputPos, outputRem); + } finally { + Reference.reachabilityFence(output); + } + } else { + byte[] outputArray = ZipUtils.getBufferArray(output); + int outputOffset = ZipUtils.getBufferOffset(output); + result = inflateBytesBytes(zsRef.address(), + inputArray, inputPos, inputLim - inputPos, + outputArray, outputOffset + outputPos, outputRem); + } + } catch (DataFormatException e) { + this.inputPos = inputPos + inputConsumed; + throw e; + } + } else { + inputPos = input.position(); + int inputRem = Math.max(input.limit() - inputPos, 0); + try { + if (input.isDirect()) { + long inputAddress = ((DirectBuffer) input).address(); + try { + if (output.isDirect()) { + long outputAddress = ((DirectBuffer) output).address(); + try { + result = inflateBufferBuffer(zsRef.address(), + inputAddress + inputPos, inputRem, + outputAddress + outputPos, outputRem); + } finally { + Reference.reachabilityFence(output); + } + } else { + byte[] outputArray = ZipUtils.getBufferArray(output); + int outputOffset = ZipUtils.getBufferOffset(output); + result = inflateBufferBytes(zsRef.address(), + inputAddress + inputPos, inputRem, + outputArray, outputOffset + outputPos, outputRem); + } + } finally { + Reference.reachabilityFence(input); + } + } else { + byte[] inputArray = ZipUtils.getBufferArray(input); + int inputOffset = ZipUtils.getBufferOffset(input); + if (output.isDirect()) { + long outputAddress = ((DirectBuffer) output).address(); + try { + result = inflateBytesBuffer(zsRef.address(), + inputArray, inputOffset + inputPos, inputRem, + outputAddress + outputPos, outputRem); + } finally { + Reference.reachabilityFence(output); + } + } else { + byte[] outputArray = ZipUtils.getBufferArray(output); + int outputOffset = ZipUtils.getBufferOffset(output); + result = inflateBytesBytes(zsRef.address(), + inputArray, inputOffset + inputPos, inputRem, + outputArray, outputOffset + outputPos, outputRem); + } + } + } catch (DataFormatException e) { + input.position(inputPos + inputConsumed); + throw e; + } + } + } catch (DataFormatException e) { + bytesRead += inputConsumed; + inputConsumed = 0; + int written = outputConsumed; + output.position(outputPos + written); + bytesWritten += written; + outputConsumed = 0; + throw e; + } + int read = (int) (result & 0x7fff_ffffL); + int written = (int) (result >>> 31 & 0x7fff_ffffL); + if ((result >>> 62 & 1) != 0) { + finished = true; + } + if ((result >>> 63 & 1) != 0) { + needDict = true; + } + if (input != null) { + input.position(inputPos + read); + } else { + this.inputPos = inputPos + read; + } + // Note: this method call also serves to keep the byteBuffer ref alive + output.position(outputPos + written); + bytesWritten += written; + bytesRead += read; + return written; + } + } + + /** + * Returns the ADLER-32 value of the uncompressed data. + * @return the ADLER-32 value of the uncompressed data + */ + public int getAdler() { + synchronized (zsRef) { + ensureOpen(); + return getAdler(zsRef.address()); + } + } + + /** + * Returns the total number of compressed bytes input so far. + * + * <p>Since the number of bytes may be greater than + * Integer.MAX_VALUE, the {@link #getBytesRead()} method is now + * the preferred means of obtaining this information.</p> + * + * @return the total number of compressed bytes input so far + */ + public int getTotalIn() { + return (int) getBytesRead(); + } + + /** + * Returns the total number of compressed bytes input so far. + * + * @return the total (non-negative) number of compressed bytes input so far + * @since 1.5 + */ + public long getBytesRead() { + synchronized (zsRef) { + ensureOpen(); + return bytesRead; + } + } + + /** + * Returns the total number of uncompressed bytes output so far. + * + * <p>Since the number of bytes may be greater than + * Integer.MAX_VALUE, the {@link #getBytesWritten()} method is now + * the preferred means of obtaining this information.</p> + * + * @return the total number of uncompressed bytes output so far + */ + public int getTotalOut() { + return (int) getBytesWritten(); + } + + /** + * Returns the total number of uncompressed bytes output so far. + * + * @return the total (non-negative) number of uncompressed bytes output so far + * @since 1.5 + */ + public long getBytesWritten() { + synchronized (zsRef) { + ensureOpen(); + return bytesWritten; + } + } + + /** + * Resets inflater so that a new set of input data can be processed. + */ + public void reset() { + synchronized (zsRef) { + ensureOpen(); + reset(zsRef.address()); + // Android-added: CloseGuard support. + guard.close(); + input = ZipUtils.defaultBuf; + inputArray = null; + finished = false; + needDict = false; + bytesRead = bytesWritten = 0; + } + } + + /** + * Closes the decompressor and discards any unprocessed input. + * + * This method should be called when the decompressor is no longer + * being used. Once this method is called, the behavior of the + * Inflater object is undefined. + */ + public void end() { + synchronized (zsRef) { + zsRef.clean(); + // Android-added: CloseGuard support. + guard.close(); + input = ZipUtils.defaultBuf; + inputArray = null; + } + } + + + private void ensureOpen () { + assert Thread.holdsLock(zsRef); + if (zsRef.address() == 0) + throw new NullPointerException("Inflater has been closed"); + } + + // Android-changed: initIDs handled in register method. + // private native static void initIDs(); + private static native long init(boolean nowrap); + private static native void setDictionary(long addr, byte[] b, int off, + int len); + private static native void setDictionaryBuffer(long addr, long bufAddress, int len); + private native long inflateBytesBytes(long addr, + byte[] inputArray, int inputOff, int inputLen, + byte[] outputArray, int outputOff, int outputLen) throws DataFormatException; + private native long inflateBytesBuffer(long addr, + byte[] inputArray, int inputOff, int inputLen, + long outputAddress, int outputLen) throws DataFormatException; + private native long inflateBufferBytes(long addr, + long inputAddress, int inputLen, + byte[] outputArray, int outputOff, int outputLen) throws DataFormatException; + private native long inflateBufferBuffer(long addr, + long inputAddress, int inputLen, + long outputAddress, int outputLen) throws DataFormatException; + private static native int getAdler(long addr); + private static native void reset(long addr); + private static native void end(long addr); + + /** + * A reference to the native zlib's z_stream structure. It also + * serves as the "cleaner" to clean up the native resource when + * the Inflater is ended, closed or cleaned. + */ + static class InflaterZStreamRef implements Runnable { + + private long address; + private final Cleanable cleanable; + + private InflaterZStreamRef(Inflater owner, long addr) { + this.cleanable = (owner != null) ? CleanerFactory.cleaner().register(owner, this) : null; + this.address = addr; + } + + long address() { + return address; + } + + void clean() { + cleanable.clean(); + } + + public synchronized void run() { + long addr = address; + address = 0; + if (addr != 0) { + end(addr); + } + } + + } +}
diff --git a/android-35/java/util/zip/InflaterInputStream.java b/android-35/java/util/zip/InflaterInputStream.java new file mode 100644 index 0000000..0eb85ad --- /dev/null +++ b/android-35/java/util/zip/InflaterInputStream.java
@@ -0,0 +1,353 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +import java.io.FilterInputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.EOFException; + +/** + * This class implements a stream filter for uncompressing data in the + * "deflate" compression format. It is also used as the basis for other + * decompression filters, such as GZIPInputStream. + * + * @see Inflater + * @author David Connelly + * @since 1.1 + */ +public class InflaterInputStream extends FilterInputStream { + /** + * Decompressor for this stream. + */ + protected Inflater inf; + + /** + * Input buffer for decompression. + */ + protected byte[] buf; + + /** + * Length of input buffer. + */ + protected int len; + + // Android-changed: Make closed accessible to subclasses. + // This was made protected because it needed to be accessed by + // StrictJarFile.ZipInflaterInputStream. Unfortunately, it was not marked as @hide and so it + // inadvertently became part of the public API. It will be marked as @removed to remove it from + // the public API in a future release of Android. See http://b/111592689 for more information. + // private boolean closed = false; + /** + * Indicates whether the {@link #close()} method has been called, internal use only. + * + * @deprecated This field will be removed from a future version of Android and should not be + * used. Subclasses that access this field need to be modified to keep track of their own + * closed state by overriding close(). + */ + @Deprecated + protected boolean closed = false; + + // this flag is set to true after EOF has reached + private boolean reachEOF = false; + + /** + * Check to make sure that this stream has not been closed + */ + private void ensureOpen() throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + } + + // Android-added: constructor which explicitly sets whether Inflater is owned by this stream. + /** + * Creates a new input stream with the specified decompressor and + * buffer size. + * @param in the input stream + * @param inf the decompressor ("inflater") + * @param size the input buffer size + * @param ownsInflater whether this {@code InflaterInputStream} controls its inflater + * lifetime and should call {@link Inflater#end} when it is closed. + * @throws IllegalArgumentException if {@code size <= 0} + * @hide + */ + InflaterInputStream(InputStream in, Inflater inf, int size, boolean ownsInflater) { + super(in); + if (in == null || inf == null) { + throw new NullPointerException(); + } else if (size <= 0) { + throw new IllegalArgumentException("buffer size <= 0"); + } + this.inf = inf; + buf = new byte[size]; + this.ownsInflater = ownsInflater; + } + + /** + * Creates a new input stream with the specified decompressor and + * buffer size. + * @param in the input stream + * @param inf the decompressor ("inflater") + * @param size the input buffer size + * @throws IllegalArgumentException if {@code size <= 0} + */ + public InflaterInputStream(InputStream in, Inflater inf, int size) { + // Android-changed: refer initialization to constructor which specifies ownInflater. + /* + super(in); + if (in == null || inf == null) { + throw new NullPointerException(); + } else if (size <= 0) { + throw new IllegalArgumentException("buffer size <= 0"); + } + this.inf = inf; + buf = new byte[size]; + */ + this(in, inf, size, /* ownsInflater= */ true); + } + + /** + * Creates a new input stream with the specified decompressor and a + * default buffer size. + * @param in the input stream + * @param inf the decompressor ("inflater") + */ + public InflaterInputStream(InputStream in, Inflater inf) { + this(in, inf, 512); + } + + // Android-changed: Unconditionally close external inflaters (b/26462400) + // See http://b/111630946 for more details. + // boolean usesDefaultInflater = false; + + // Android-added: functionally this is identical to usesDefaultInflater, but re-using + // it will be confusing. Setting it to true keeps old Android behaviour. + // This is added just for ZipFileInflaterInputStream - moving to usesDefaultInflater + // is trickier. + private final boolean ownsInflater; + + /** + * Creates a new input stream with a default decompressor and buffer size. + * @param in the input stream + */ + public InflaterInputStream(InputStream in) { + this(in, in != null ? new Inflater() : null); + // Android-changed: Unconditionally close external inflaters (b/26462400) + // usesDefaultInflater = true; + } + + private byte[] singleByteBuf = new byte[1]; + + /** + * Reads a byte of uncompressed data. This method will block until + * enough input is available for decompression. + * @return the byte read, or -1 if end of compressed input is reached + * @throws IOException if an I/O error has occurred + */ + public int read() throws IOException { + ensureOpen(); + return read(singleByteBuf, 0, 1) == -1 ? -1 : Byte.toUnsignedInt(singleByteBuf[0]); + } + + /** + * Reads uncompressed data into an array of bytes. If {@code len} is not + * zero, the method will block until some input can be decompressed; otherwise, + * no bytes are read and {@code 0} is returned. + * @param b the buffer into which the data is read + * @param off the start offset in the destination array {@code b} + * @param len the maximum number of bytes read + * @return the actual number of bytes read, or -1 if the end of the + * compressed input is reached or a preset dictionary is needed + * @throws NullPointerException If {@code b} is {@code null}. + * @throws IndexOutOfBoundsException If {@code off} is negative, + * {@code len} is negative, or {@code len} is greater than + * {@code b.length - off} + * @throws ZipException if a ZIP format error has occurred + * @throws IOException if an I/O error has occurred + */ + public int read(byte[] b, int off, int len) throws IOException { + ensureOpen(); + if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + try { + int n; + while ((n = inf.inflate(b, off, len)) == 0) { + if (inf.finished() || inf.needsDictionary()) { + reachEOF = true; + return -1; + } + if (inf.needsInput()) { + fill(); + } + } + return n; + } catch (DataFormatException e) { + String s = e.getMessage(); + throw new ZipException(s != null ? s : "Invalid ZLIB data format"); + } + } + + /** + * Returns 0 after EOF has been reached, otherwise always return 1. + * <p> + * Programs should not count on this method to return the actual number + * of bytes that could be read without blocking. + * + * @return 1 before EOF and 0 after EOF. + * @throws IOException if an I/O error occurs. + * + */ + public int available() throws IOException { + ensureOpen(); + if (reachEOF) { + return 0; + } else if (inf.finished()) { + // the end of the compressed data stream has been reached + reachEOF = true; + return 0; + } else { + return 1; + } + } + + private byte[] b = new byte[512]; + + /** + * Skips specified number of bytes of uncompressed data. + * @param n the number of bytes to skip + * @return the actual number of bytes skipped. + * @throws IOException if an I/O error has occurred + * @throws IllegalArgumentException if {@code n < 0} + */ + public long skip(long n) throws IOException { + if (n < 0) { + throw new IllegalArgumentException("negative skip length"); + } + ensureOpen(); + int max = (int)Math.min(n, Integer.MAX_VALUE); + int total = 0; + while (total < max) { + int len = max - total; + if (len > b.length) { + len = b.length; + } + len = read(b, 0, len); + if (len == -1) { + reachEOF = true; + break; + } + total += len; + } + return total; + } + + /** + * Closes this input stream and releases any system resources associated + * with the stream. + * @throws IOException if an I/O error has occurred + */ + public void close() throws IOException { + if (!closed) { + // BEGIN Android-changed: close inflater only if it is owned by this + // InflaterInputStream. (b/26462400) + /* + if (usesDefaultInflater) + inf.end(); + */ + if (ownsInflater) { + inf.end(); + } + // END Android-changed: close inflater only if it is owned by this + // InflaterInputStream. (b/26462400) + in.close(); + closed = true; + } + } + + /** + * Fills input buffer with more data to decompress. + * @throws IOException if an I/O error has occurred + */ + protected void fill() throws IOException { + ensureOpen(); + len = in.read(buf, 0, buf.length); + if (len == -1) { + throw new EOFException("Unexpected end of ZLIB input stream"); + } + inf.setInput(buf, 0, len); + } + + /** + * Tests if this input stream supports the {@code mark} and + * {@code reset} methods. The {@code markSupported} + * method of {@code InflaterInputStream} returns + * {@code false}. + * + * @return a {@code boolean} indicating if this stream type supports + * the {@code mark} and {@code reset} methods. + * @see java.io.InputStream#mark(int) + * @see java.io.InputStream#reset() + */ + public boolean markSupported() { + return false; + } + + /** + * Marks the current position in this input stream. + * + * <p> The {@code mark} method of {@code InflaterInputStream} + * does nothing. + * + * @param readlimit the maximum limit of bytes that can be read before + * the mark position becomes invalid. + * @see java.io.InputStream#reset() + */ + public synchronized void mark(int readlimit) { + } + + /** + * Repositions this stream to the position at the time the + * {@code mark} method was last called on this input stream. + * + * <p> The method {@code reset} for class + * {@code InflaterInputStream} does nothing except throw an + * {@code IOException}. + * + * @throws IOException if this method is invoked. + * @see java.io.InputStream#mark(int) + * @see java.io.IOException + */ + public synchronized void reset() throws IOException { + throw new IOException("mark/reset not supported"); + } +}
diff --git a/android-35/java/util/zip/InflaterOutputStream.java b/android-35/java/util/zip/InflaterOutputStream.java new file mode 100644 index 0000000..aa30623 --- /dev/null +++ b/android-35/java/util/zip/InflaterOutputStream.java
@@ -0,0 +1,270 @@ +/* + * Copyright (c) 2006, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Implements an output stream filter for uncompressing data stored in the + * "deflate" compression format. + * + * @since 1.6 + * @author David R Tribble ([email protected]) + * + * @see InflaterInputStream + * @see DeflaterInputStream + * @see DeflaterOutputStream + */ + +public class InflaterOutputStream extends FilterOutputStream { + /** Decompressor for this stream. */ + protected final Inflater inf; + + /** Output buffer for writing uncompressed data. */ + protected final byte[] buf; + + /** Temporary write buffer. */ + private final byte[] wbuf = new byte[1]; + + /** Default decompressor is used. */ + private boolean usesDefaultInflater = false; + + /** true iff {@link #close()} has been called. */ + private boolean closed = false; + + /** + * Checks to make sure that this stream has not been closed. + */ + private void ensureOpen() throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + } + + /** + * Creates a new output stream with a default decompressor and buffer + * size. + * + * @param out output stream to write the uncompressed data to + * @throws NullPointerException if {@code out} is null + */ + public InflaterOutputStream(OutputStream out) { + this(out, out != null ? new Inflater() : null); + usesDefaultInflater = true; + } + + /** + * Creates a new output stream with the specified decompressor and a + * default buffer size. + * + * @param out output stream to write the uncompressed data to + * @param infl decompressor ("inflater") for this stream + * @throws NullPointerException if {@code out} or {@code infl} is null + */ + public InflaterOutputStream(OutputStream out, Inflater infl) { + this(out, infl, 512); + } + + /** + * Creates a new output stream with the specified decompressor and + * buffer size. + * + * @param out output stream to write the uncompressed data to + * @param infl decompressor ("inflater") for this stream + * @param bufLen decompression buffer size + * @throws IllegalArgumentException if {@code bufLen <= 0} + * @throws NullPointerException if {@code out} or {@code infl} is null + */ + public InflaterOutputStream(OutputStream out, Inflater infl, int bufLen) { + super(out); + + // Sanity checks + if (out == null) + throw new NullPointerException("Null output"); + if (infl == null) + throw new NullPointerException("Null inflater"); + if (bufLen <= 0) + throw new IllegalArgumentException("Buffer size < 1"); + + // Initialize + inf = infl; + buf = new byte[bufLen]; + } + + /** + * Writes any remaining uncompressed data to the output stream and closes + * the underlying output stream. + * + * @throws IOException if an I/O error occurs + */ + public void close() throws IOException { + if (!closed) { + // Complete the uncompressed output + try { + finish(); + } finally { + out.close(); + closed = true; + } + } + } + + /** + * Flushes this output stream, forcing any pending buffered output bytes to be + * written. + * + * @throws IOException if an I/O error occurs or this stream is already + * closed + */ + public void flush() throws IOException { + ensureOpen(); + + // Finish decompressing and writing pending output data + if (!inf.finished()) { + try { + while (!inf.finished() && !inf.needsInput()) { + int n; + + // Decompress pending output data + n = inf.inflate(buf, 0, buf.length); + if (n < 1) { + break; + } + + // Write the uncompressed output data block + out.write(buf, 0, n); + } + super.flush(); + } catch (DataFormatException ex) { + // Improperly formatted compressed (ZIP) data + String msg = ex.getMessage(); + if (msg == null) { + msg = "Invalid ZLIB data format"; + } + throw new ZipException(msg); + } + } + } + + /** + * Finishes writing uncompressed data to the output stream without closing + * the underlying stream. Use this method when applying multiple filters in + * succession to the same output stream. + * + * @throws IOException if an I/O error occurs or this stream is already + * closed + */ + public void finish() throws IOException { + ensureOpen(); + + // Finish decompressing and writing pending output data + flush(); + if (usesDefaultInflater) { + inf.end(); + } + } + + /** + * Writes a byte to the uncompressed output stream. + * + * @param b a single byte of compressed data to decompress and write to + * the output stream + * @throws IOException if an I/O error occurs or this stream is already + * closed + * @throws ZipException if a compression (ZIP) format error occurs + */ + public void write(int b) throws IOException { + // Write a single byte of data + wbuf[0] = (byte) b; + write(wbuf, 0, 1); + } + + /** + * Writes an array of bytes to the uncompressed output stream. + * + * @param b buffer containing compressed data to decompress and write to + * the output stream + * @param off starting offset of the compressed data within {@code b} + * @param len number of bytes to decompress from {@code b} + * @throws IndexOutOfBoundsException if {@code off < 0}, or if + * {@code len < 0}, or if {@code len > b.length - off} + * @throws IOException if an I/O error occurs or this stream is already + * closed + * @throws NullPointerException if {@code b} is null + * @throws ZipException if a compression (ZIP) format error occurs + */ + public void write(byte[] b, int off, int len) throws IOException { + // Sanity checks + ensureOpen(); + if (b == null) { + throw new NullPointerException("Null buffer for read"); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + + // Write uncompressed data to the output stream + try { + for (;;) { + int n; + + // Fill the decompressor buffer with output data + if (inf.needsInput()) { + inf.setInput(b, off, len); + // Only use input buffer once. + len = 0; + } + + // Decompress and write blocks of output data + do { + n = inf.inflate(buf, 0, buf.length); + if (n > 0) { + out.write(buf, 0, n); + } + } while (n > 0); + + // Check for missing dictionary first + if (inf.needsDictionary()) { + throw new ZipException("ZLIB dictionary missing"); + } + // Check the decompressor + if (inf.finished() || (len == 0)/* no more input */) { + break; + } + } + } catch (DataFormatException ex) { + // Improperly formatted compressed (ZIP) data + String msg = ex.getMessage(); + if (msg == null) { + msg = "Invalid ZLIB data format"; + } + throw new ZipException(msg); + } + } +}
diff --git a/android-35/java/util/zip/ZipCoder.java b/android-35/java/util/zip/ZipCoder.java new file mode 100644 index 0000000..450d7a6 --- /dev/null +++ b/android-35/java/util/zip/ZipCoder.java
@@ -0,0 +1,252 @@ +/* + * Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +/** + * Utility class for zipfile name and comment decoding and encoding + */ +class ZipCoder { + + // Android-removed: + // private static final jdk.internal.access.JavaLangAccess JLA = + // jdk.internal.access.SharedSecrets.getJavaLangAccess(); + + // Encoding/decoding is stateless, so make it singleton. + // Android-changed: use StandardCharsets. + // static final UTF8ZipCoder UTF8 = new UTF8ZipCoder(UTF_8.INSTANCE); + static final UTF8ZipCoder UTF8 = new UTF8ZipCoder(StandardCharsets.UTF_8); + + public static ZipCoder get(Charset charset) { + // Android-changed: use equals method, not reference comparison. + // if (charset == UTF_8.INSTANCE) { + if (StandardCharsets.UTF_8.equals(charset)) { + return UTF8; + } + return new ZipCoder(charset); + } + + String toString(byte[] ba, int off, int length) { + try { + return decoder().decode(ByteBuffer.wrap(ba, off, length)).toString(); + } catch (CharacterCodingException x) { + throw new IllegalArgumentException(x); + } + } + + String toString(byte[] ba, int length) { + return toString(ba, 0, length); + } + + String toString(byte[] ba) { + return toString(ba, 0, ba.length); + } + + byte[] getBytes(String s) { + try { + ByteBuffer bb = encoder().encode(CharBuffer.wrap(s)); + int pos = bb.position(); + int limit = bb.limit(); + if (bb.hasArray() && pos == 0 && limit == bb.capacity()) { + return bb.array(); + } + byte[] bytes = new byte[bb.limit() - bb.position()]; + bb.get(bytes); + return bytes; + } catch (CharacterCodingException x) { + throw new IllegalArgumentException(x); + } + } + + static String toStringUTF8(byte[] ba, int len) { + return UTF8.toString(ba, 0, len); + } + + boolean isUTF8() { + return false; + } + + // Hash code functions for ZipFile entry names. We generate the hash as-if + // we first decoded the byte sequence to a String, then appended '/' if no + // trailing slash was found, then called String.hashCode(). This + // normalization ensures we can simplify and speed up lookups. + // + // Does encoding error checking and hashing in a single pass for efficiency. + // On an error, this function will throw CharacterCodingException while the + // UTF8ZipCoder override will throw IllegalArgumentException, so we declare + // throws Exception to keep things simple. + int checkedHash(byte[] a, int off, int len) throws Exception { + if (len == 0) { + return 0; + } + + int h = 0; + // cb will be a newly allocated CharBuffer with pos == 0, + // arrayOffset == 0, backed by an array. + CharBuffer cb = decoder().decode(ByteBuffer.wrap(a, off, len)); + int limit = cb.limit(); + char[] decoded = cb.array(); + for (int i = 0; i < limit; i++) { + h = 31 * h + decoded[i]; + } + if (limit > 0 && decoded[limit - 1] != '/') { + h = 31 * h + '/'; + } + return h; + } + + // Hash function equivalent of checkedHash for String inputs + static int hash(String name) { + int hsh = name.hashCode(); + int len = name.length(); + if (len > 0 && name.charAt(len - 1) != '/') { + hsh = hsh * 31 + '/'; + } + return hsh; + } + + boolean hasTrailingSlash(byte[] a, int end) { + byte[] slashBytes = slashBytes(); + return end >= slashBytes.length && + Arrays.mismatch(a, end - slashBytes.length, end, slashBytes, 0, slashBytes.length) == -1; + } + + private byte[] slashBytes; + private final Charset cs; + protected CharsetDecoder dec; + private CharsetEncoder enc; + + private ZipCoder(Charset cs) { + this.cs = cs; + } + + protected CharsetDecoder decoder() { + if (dec == null) { + dec = cs.newDecoder() + .onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT); + } + return dec; + } + + private CharsetEncoder encoder() { + if (enc == null) { + enc = cs.newEncoder() + .onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT); + } + return enc; + } + + // This method produces an array with the bytes that will correspond to a + // trailing '/' in the chosen character encoding. + // + // While in most charsets a trailing slash will be encoded as the byte + // value of '/', this does not hold in the general case. E.g., in charsets + // such as UTF-16 and UTF-32 it will be represented by a sequence of 2 or 4 + // bytes, respectively. + private byte[] slashBytes() { + if (slashBytes == null) { + // Take into account charsets that produce a BOM, e.g., UTF-16 + byte[] slash = "/".getBytes(cs); + byte[] doubleSlash = "//".getBytes(cs); + slashBytes = Arrays.copyOfRange(doubleSlash, slash.length, doubleSlash.length); + } + return slashBytes; + } + + static final class UTF8ZipCoder extends ZipCoder { + + private UTF8ZipCoder(Charset utf8) { + super(utf8); + } + + @Override + boolean isUTF8() { + return true; + } + + @Override + String toString(byte[] ba, int off, int length) { + // Android-changed: JLA is not yet available. + // return JLA.newStringUTF8NoRepl(ba, off, length); + return new String(ba, off, length, StandardCharsets.UTF_8); + } + + @Override + byte[] getBytes(String s) { + // Android-changed: JLA is not yet available. + // return JLA.getBytesUTF8NoRepl(s); + return s.getBytes(StandardCharsets.UTF_8); + } + + @Override + int checkedHash(byte[] a, int off, int len) throws Exception { + if (len == 0) { + return 0; + } + + int end = off + len; + int h = 0; + while (off < end) { + byte b = a[off]; + if (b >= 0) { + // ASCII, keep going + h = 31 * h + b; + off++; + } else { + // Non-ASCII, fall back to decoding a String + // We avoid using decoder() here since the UTF8ZipCoder is + // shared and that decoder is not thread safe. + // We use the JLA.newStringUTF8NoRepl variant to throw + // exceptions eagerly when opening ZipFiles + // Android-changed: JLA is not yet available. + // return hash(JLA.newStringUTF8NoRepl(a, end - len, len)); + return hash(new String(a, end - len, len, StandardCharsets.UTF_8)); + } + } + + if (a[end - 1] != '/') { + h = 31 * h + '/'; + } + return h; + } + + @Override + boolean hasTrailingSlash(byte[] a, int end) { + return end > 0 && a[end - 1] == '/'; + } + } +}
diff --git a/android-35/java/util/zip/ZipConstants.java b/android-35/java/util/zip/ZipConstants.java new file mode 100644 index 0000000..9eac888 --- /dev/null +++ b/android-35/java/util/zip/ZipConstants.java
@@ -0,0 +1,236 @@ +/* + * Copyright (c) 1995, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +/* + * This interface defines the constants that are used by the classes + * which manipulate ZIP files. + * + * @author David Connelly + * @since 1.1 + */ +interface ZipConstants { + + /** + * Local file (LOC) header signature. + */ + static long LOCSIG = 0x04034b50L; // "PK\003\004" + + /** + * Extra local (EXT) header signature. + */ + static long EXTSIG = 0x08074b50L; // "PK\007\008" + + /** + * Central directory (CEN) header signature. + */ + static long CENSIG = 0x02014b50L; // "PK\001\002" + + /** + * End of central directory (END) header signature. + */ + static long ENDSIG = 0x06054b50L; // "PK\005\006" + + /** + * Local file (LOC) header size in bytes (including signature). + */ + static final int LOCHDR = 30; + + /** + * Extra local (EXT) header size in bytes (including signature). + */ + static final int EXTHDR = 16; + + /** + * Central directory (CEN) header size in bytes (including signature). + */ + static final int CENHDR = 46; + + /** + * End of central directory (END) header size in bytes (including signature). + */ + static final int ENDHDR = 22; + + /** + * Local file (LOC) header version needed to extract field offset. + */ + static final int LOCVER = 4; + + /** + * Local file (LOC) header general purpose bit flag field offset. + */ + static final int LOCFLG = 6; + + /** + * Local file (LOC) header compression method field offset. + */ + static final int LOCHOW = 8; + + /** + * Local file (LOC) header modification time field offset. + */ + static final int LOCTIM = 10; + + /** + * Local file (LOC) header uncompressed file crc-32 value field offset. + */ + static final int LOCCRC = 14; + + /** + * Local file (LOC) header compressed size field offset. + */ + static final int LOCSIZ = 18; + + /** + * Local file (LOC) header uncompressed size field offset. + */ + static final int LOCLEN = 22; + + /** + * Local file (LOC) header filename length field offset. + */ + static final int LOCNAM = 26; + + /** + * Local file (LOC) header extra field length field offset. + */ + static final int LOCEXT = 28; + + /** + * Extra local (EXT) header uncompressed file crc-32 value field offset. + */ + static final int EXTCRC = 4; + + /** + * Extra local (EXT) header compressed size field offset. + */ + static final int EXTSIZ = 8; + + /** + * Extra local (EXT) header uncompressed size field offset. + */ + static final int EXTLEN = 12; + + /** + * Central directory (CEN) header version made by field offset. + */ + static final int CENVEM = 4; + + /** + * Central directory (CEN) header version needed to extract field offset. + */ + static final int CENVER = 6; + + /** + * Central directory (CEN) header encrypt, decrypt flags field offset. + */ + static final int CENFLG = 8; + + /** + * Central directory (CEN) header compression method field offset. + */ + static final int CENHOW = 10; + + /** + * Central directory (CEN) header modification time field offset. + */ + static final int CENTIM = 12; + + /** + * Central directory (CEN) header uncompressed file crc-32 value field offset. + */ + static final int CENCRC = 16; + + /** + * Central directory (CEN) header compressed size field offset. + */ + static final int CENSIZ = 20; + + /** + * Central directory (CEN) header uncompressed size field offset. + */ + static final int CENLEN = 24; + + /** + * Central directory (CEN) header filename length field offset. + */ + static final int CENNAM = 28; + + /** + * Central directory (CEN) header extra field length field offset. + */ + static final int CENEXT = 30; + + /** + * Central directory (CEN) header comment length field offset. + */ + static final int CENCOM = 32; + + /** + * Central directory (CEN) header disk number start field offset. + */ + static final int CENDSK = 34; + + /** + * Central directory (CEN) header internal file attributes field offset. + */ + static final int CENATT = 36; + + /** + * Central directory (CEN) header external file attributes field offset. + */ + static final int CENATX = 38; + + /** + * Central directory (CEN) header LOC header offset field offset. + */ + static final int CENOFF = 42; + + /** + * End of central directory (END) header number of entries on this disk field offset. + */ + static final int ENDSUB = 8; + + /** + * End of central directory (END) header total number of entries field offset. + */ + static final int ENDTOT = 10; + + /** + * End of central directory (END) header central directory size in bytes field offset. + */ + static final int ENDSIZ = 12; + + /** + * End of central directory (END) header offset for the first CEN header field offset. + */ + static final int ENDOFF = 16; + + /** + * End of central directory (END) header zip file comment length field offset. + */ + static final int ENDCOM = 20; +}
diff --git a/android-35/java/util/zip/ZipConstants64.java b/android-35/java/util/zip/ZipConstants64.java new file mode 100644 index 0000000..d78eaf4 --- /dev/null +++ b/android-35/java/util/zip/ZipConstants64.java
@@ -0,0 +1,106 @@ +/* + * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +/* + * This class defines the constants that are used by the classes + * which manipulate Zip64 files. + */ + +class ZipConstants64 { + + /* + * ZIP64 constants + */ + static final long ZIP64_ENDSIG = 0x06064b50L; // "PK\006\006" + static final long ZIP64_LOCSIG = 0x07064b50L; // "PK\006\007" + static final int ZIP64_ENDHDR = 56; // ZIP64 end header size + static final int ZIP64_LOCHDR = 20; // ZIP64 end loc header size + static final int ZIP64_EXTHDR = 24; // EXT header size + static final int ZIP64_EXTID = 0x0001; // Extra field Zip64 header ID + + static final int ZIP64_MAGICCOUNT = 0xFFFF; + static final long ZIP64_MAGICVAL = 0xFFFFFFFFL; + + /* + * Zip64 End of central directory (END) header field offsets + */ + static final int ZIP64_ENDLEN = 4; // size of zip64 end of central dir + static final int ZIP64_ENDVEM = 12; // version made by + static final int ZIP64_ENDVER = 14; // version needed to extract + static final int ZIP64_ENDNMD = 16; // number of this disk + static final int ZIP64_ENDDSK = 20; // disk number of start + static final int ZIP64_ENDTOD = 24; // total number of entries on this disk + static final int ZIP64_ENDTOT = 32; // total number of entries + static final int ZIP64_ENDSIZ = 40; // central directory size in bytes + static final int ZIP64_ENDOFF = 48; // offset of first CEN header + static final int ZIP64_ENDEXT = 56; // zip64 extensible data sector + + /* + * Zip64 End of central directory locator field offsets + */ + static final int ZIP64_LOCDSK = 4; // disk number start + static final int ZIP64_LOCOFF = 8; // offset of zip64 end + static final int ZIP64_LOCTOT = 16; // total number of disks + + /* + * Zip64 Extra local (EXT) header field offsets + */ + static final int ZIP64_EXTCRC = 4; // uncompressed file crc-32 value + static final int ZIP64_EXTSIZ = 8; // compressed size, 8-byte + static final int ZIP64_EXTLEN = 16; // uncompressed size, 8-byte + + /* + * Language encoding flag (general purpose flag bit 11) + * + * If this bit is set the filename and comment fields for this + * entry must be encoded using UTF-8. + */ + static final int USE_UTF8 = 0x800; + + /* + * Constants below are defined here (instead of in ZipConstants) + * to avoid being exposed as public fields of ZipFile, ZipEntry, + * ZipInputStream and ZipOutputstream. + */ + + /* + * Extra field header ID + */ + static final int EXTID_ZIP64 = 0x0001; // Zip64 + static final int EXTID_NTFS = 0x000a; // NTFS + static final int EXTID_UNIX = 0x000d; // UNIX + static final int EXTID_EXTT = 0x5455; // Info-ZIP Extended Timestamp + + /* + * EXTT timestamp flags + */ + static final int EXTT_FLAG_LMT = 0x1; // LastModifiedTime + static final int EXTT_FLAG_LAT = 0x2; // LastAccessTime + static final int EXTT_FLAT_CT = 0x4; // CreationTime + + private ZipConstants64() {} +}
diff --git a/android-35/java/util/zip/ZipEntry.java b/android-35/java/util/zip/ZipEntry.java new file mode 100644 index 0000000..3d1d522 --- /dev/null +++ b/android-35/java/util/zip/ZipEntry.java
@@ -0,0 +1,756 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * Copyright (c) 1995, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +import static java.util.zip.ZipUtils.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.attribute.FileTime; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.time.ZoneId; + +import static java.util.zip.ZipConstants64.*; + +/** + * This class is used to represent a ZIP file entry. + * + * @author David Connelly + * @since 1.1 + */ +public class ZipEntry implements ZipConstants, Cloneable { + + String name; // entry name + long xdostime = -1; // last modification time (in extended DOS time, + // where milliseconds lost in conversion might + // be encoded into the upper half) + FileTime mtime; // last modification time, from extra field data + FileTime atime; // last access time, from extra field data + FileTime ctime; // creation time, from extra field data + long crc = -1; // crc-32 of entry data + long size = -1; // uncompressed size of entry data + long csize = -1; // compressed size of entry data + boolean csizeSet = false; // Only true if csize was explicitely set by + // a call to setCompressedSize() + int method = -1; // compression method + int flag = 0; // general purpose flag + byte[] extra; // optional extra field data for entry + String comment; // optional comment string for entry + int extraAttributes = -1; // e.g. POSIX permissions, sym links. + // Android-added: Add dataOffset for internal use. + // Used by android.util.jar.StrictJarFile from frameworks. + long dataOffset; + + /** + * Compression method for uncompressed entries. + */ + public static final int STORED = 0; + + /** + * Compression method for compressed (deflated) entries. + */ + public static final int DEFLATED = 8; + + /** + * DOS time constant for representing timestamps before 1980. + */ + static final long DOSTIME_BEFORE_1980 = (1 << 21) | (1 << 16); + + /** + * Approximately 128 years, in milliseconds (ignoring leap years etc). + * + * This establish an approximate high-bound value for DOS times in + * milliseconds since epoch, used to enable an efficient but + * sufficient bounds check to avoid generating extended last modified + * time entries. + * + * Calculating the exact number is locale dependent, would require loading + * TimeZone data eagerly, and would make little practical sense. Since DOS + * times theoretically go to 2107 - with compatibility not guaranteed + * after 2099 - setting this to a time that is before but near 2099 + * should be sufficient. + * @hide + */ + // Android-changed: Make UPPER_DOSTIME_BOUND public hidden for testing purposes. + public static final long UPPER_DOSTIME_BOUND = + 128L * 365 * 24 * 60 * 60 * 1000; + + // Android-added: New constructor for use by StrictJarFile native code. + /** @hide */ + public ZipEntry(String name, String comment, long crc, long compressedSize, + long size, int compressionMethod, int xdostime, byte[] extra, + long dataOffset) { + this.name = name; + this.comment = comment; + this.crc = crc; + this.csize = compressedSize; + this.size = size; + this.method = compressionMethod; + this.xdostime = xdostime; + this.dataOffset = dataOffset; + this.setExtra0(extra, false, false); + } + + /** + * Creates a new zip entry with the specified name. + * + * @param name + * The entry name + * + * @throws NullPointerException if the entry name is null + * @throws IllegalArgumentException if the entry name is longer than + * 0xFFFF bytes + */ + public ZipEntry(String name) { + Objects.requireNonNull(name, "name"); + // Android-changed: Explicitly use UTF_8 instead of the default charset. + // if (name.length() > 0xFFFF) { + // throw new IllegalArgumentException("entry name too long"); + // } + if (name.getBytes(StandardCharsets.UTF_8).length > 0xffff) { + throw new IllegalArgumentException(name + " too long: " + + name.getBytes(StandardCharsets.UTF_8).length); + } + this.name = name; + } + + /** + * Creates a new zip entry with fields taken from the specified + * zip entry. + * + * @param e + * A zip Entry object + * + * @throws NullPointerException if the entry object is null + */ + public ZipEntry(ZipEntry e) { + Objects.requireNonNull(e, "entry"); + name = e.name; + xdostime = e.xdostime; + mtime = e.mtime; + atime = e.atime; + ctime = e.ctime; + crc = e.crc; + size = e.size; + csize = e.csize; + csizeSet = e.csizeSet; + method = e.method; + flag = e.flag; + extra = e.extra; + comment = e.comment; + extraAttributes = e.extraAttributes; + // Android-added: Add dataOffset for internal use. + dataOffset = e.dataOffset; + } + + /** + * Creates a new un-initialized zip entry + */ + ZipEntry() {} + + // BEGIN Android-added: Add dataOffset for internal use. + /** @hide */ + public long getDataOffset() { + return dataOffset; + } + // END Android-added: Add dataOffset for internal use. + + /** + * Returns the name of the entry. + * @return the name of the entry + */ + public String getName() { + return name; + } + + /** + * Sets the last modification time of the entry. + * + * <p> If the entry is output to a ZIP file or ZIP file formatted + * output stream the last modification time set by this method will + * be stored into the {@code date and time fields} of the zip file + * entry and encoded in standard {@code MS-DOS date and time format}. + * The {@link java.util.TimeZone#getDefault() default TimeZone} is + * used to convert the epoch time to the MS-DOS data and time. + * + * @param time + * The last modification time of the entry in milliseconds + * since the epoch + * + * @see #getTime() + * @see #getLastModifiedTime() + */ + public void setTime(long time) { + this.xdostime = javaToExtendedDosTime(time); + // Avoid setting the mtime field if time is in the valid + // range for a DOS time + if (this.xdostime != DOSTIME_BEFORE_1980 && time <= UPPER_DOSTIME_BOUND) { + this.mtime = null; + } else { + int localYear = javaEpochToLocalDateTime(time).getYear(); + if (localYear >= 1980 && localYear <= 2099) { + this.mtime = null; + } else { + this.mtime = FileTime.from(time, TimeUnit.MILLISECONDS); + } + } + } + + /** + * Returns the last modification time of the entry. + * + * <p> If the entry is read from a ZIP file or ZIP file formatted + * input stream, this is the last modification time from the {@code + * date and time fields} of the zip file entry. The + * {@link java.util.TimeZone#getDefault() default TimeZone} is used + * to convert the standard MS-DOS formatted date and time to the + * epoch time. + * + * @return The last modification time of the entry in milliseconds + * since the epoch, or -1 if not specified + * + * @see #setTime(long) + * @see #setLastModifiedTime(FileTime) + */ + public long getTime() { + if (mtime != null) { + return mtime.toMillis(); + } + return (xdostime != -1) ? extendedDosToJavaTime(xdostime) : -1; + } + + /** + * Sets the last modification time of the entry in local date-time. + * + * <p> If the entry is output to a ZIP file or ZIP file formatted + * output stream the last modification time set by this method will + * be stored into the {@code date and time fields} of the zip file + * entry and encoded in standard {@code MS-DOS date and time format}. + * If the date-time set is out of the range of the standard {@code + * MS-DOS date and time format}, the time will also be stored into + * zip file entry's extended timestamp fields in {@code optional + * extra data} in UTC time. The {@link java.time.ZoneId#systemDefault() + * system default TimeZone} is used to convert the local date-time + * to UTC time. + * + * <p> {@code LocalDateTime} uses a precision of nanoseconds, whereas + * this class uses a precision of milliseconds. The conversion will + * truncate any excess precision information as though the amount in + * nanoseconds was subject to integer division by one million. + * + * @param time + * The last modification time of the entry in local date-time + * + * @see #getTimeLocal() + * @since 9 + */ + public void setTimeLocal(LocalDateTime time) { + int year = time.getYear() - 1980; + if (year < 0) { + this.xdostime = DOSTIME_BEFORE_1980; + } else { + this.xdostime = ((year << 25 | + time.getMonthValue() << 21 | + time.getDayOfMonth() << 16 | + time.getHour() << 11 | + time.getMinute() << 5 | + time.getSecond() >> 1) & 0xffffffffL) + + ((long)(((time.getSecond() & 0x1) * 1000) + + time.getNano() / 1000_000) << 32); + } + if (xdostime != DOSTIME_BEFORE_1980 && year <= 0x7f) { + this.mtime = null; + } else { + this.mtime = FileTime.from( + ZonedDateTime.of(time, ZoneId.systemDefault()).toInstant()); + } + } + + /** + * Returns the last modification time of the entry in local date-time. + * + * <p> If the entry is read from a ZIP file or ZIP file formatted + * input stream, this is the last modification time from the zip + * file entry's {@code optional extra data} if the extended timestamp + * fields are present. Otherwise, the last modification time is read + * from entry's standard MS-DOS formatted {@code date and time fields}. + * + * <p> The {@link java.time.ZoneId#systemDefault() system default TimeZone} + * is used to convert the UTC time to local date-time. + * + * @return The last modification time of the entry in local date-time + * + * @see #setTimeLocal(LocalDateTime) + * @since 9 + */ + public LocalDateTime getTimeLocal() { + if (mtime != null) { + return LocalDateTime.ofInstant(mtime.toInstant(), ZoneId.systemDefault()); + } + int ms = (int)(xdostime >> 32); + return LocalDateTime.of((int)(((xdostime >> 25) & 0x7f) + 1980), + (int)((xdostime >> 21) & 0x0f), + (int)((xdostime >> 16) & 0x1f), + (int)((xdostime >> 11) & 0x1f), + (int)((xdostime >> 5) & 0x3f), + (int)((xdostime << 1) & 0x3e) + ms / 1000, + (ms % 1000) * 1000_000); + } + + + /** + * Sets the last modification time of the entry. + * + * <p> When output to a ZIP file or ZIP file formatted output stream + * the last modification time set by this method will be stored into + * zip file entry's {@code date and time fields} in {@code standard + * MS-DOS date and time format}), and the extended timestamp fields + * in {@code optional extra data} in UTC time. + * + * @param time + * The last modification time of the entry + * @return This zip entry + * + * @throws NullPointerException if the {@code time} is null + * + * @see #getLastModifiedTime() + * @since 1.8 + */ + public ZipEntry setLastModifiedTime(FileTime time) { + this.mtime = Objects.requireNonNull(time, "lastModifiedTime"); + this.xdostime = javaToExtendedDosTime(time.to(TimeUnit.MILLISECONDS)); + return this; + } + + /** + * Returns the last modification time of the entry. + * + * <p> If the entry is read from a ZIP file or ZIP file formatted + * input stream, this is the last modification time from the zip + * file entry's {@code optional extra data} if the extended timestamp + * fields are present. Otherwise the last modification time is read + * from the entry's {@code date and time fields}, the {@link + * java.util.TimeZone#getDefault() default TimeZone} is used to convert + * the standard MS-DOS formatted date and time to the epoch time. + * + * @return The last modification time of the entry, null if not specified + * + * @see #setLastModifiedTime(FileTime) + * @since 1.8 + */ + public FileTime getLastModifiedTime() { + if (mtime != null) + return mtime; + if (xdostime == -1) + return null; + return FileTime.from(getTime(), TimeUnit.MILLISECONDS); + } + + /** + * Sets the last access time of the entry. + * + * <p> If set, the last access time will be stored into the extended + * timestamp fields of entry's {@code optional extra data}, when output + * to a ZIP file or ZIP file formatted stream. + * + * @param time + * The last access time of the entry + * @return This zip entry + * + * @throws NullPointerException if the {@code time} is null + * + * @see #getLastAccessTime() + * @since 1.8 + */ + public ZipEntry setLastAccessTime(FileTime time) { + this.atime = Objects.requireNonNull(time, "lastAccessTime"); + return this; + } + + /** + * Returns the last access time of the entry. + * + * <p> The last access time is from the extended timestamp fields + * of entry's {@code optional extra data} when read from a ZIP file + * or ZIP file formatted stream. + * + * @return The last access time of the entry, null if not specified + * @see #setLastAccessTime(FileTime) + * @since 1.8 + */ + public FileTime getLastAccessTime() { + return atime; + } + + /** + * Sets the creation time of the entry. + * + * <p> If set, the creation time will be stored into the extended + * timestamp fields of entry's {@code optional extra data}, when + * output to a ZIP file or ZIP file formatted stream. + * + * @param time + * The creation time of the entry + * @return This zip entry + * + * @throws NullPointerException if the {@code time} is null + * + * @see #getCreationTime() + * @since 1.8 + */ + public ZipEntry setCreationTime(FileTime time) { + this.ctime = Objects.requireNonNull(time, "creationTime"); + return this; + } + + /** + * Returns the creation time of the entry. + * + * <p> The creation time is from the extended timestamp fields of + * entry's {@code optional extra data} when read from a ZIP file + * or ZIP file formatted stream. + * + * @return the creation time of the entry, null if not specified + * @see #setCreationTime(FileTime) + * @since 1.8 + */ + public FileTime getCreationTime() { + return ctime; + } + + /** + * Sets the uncompressed size of the entry data. + * + * @param size the uncompressed size in bytes + * + * @throws IllegalArgumentException if the specified size is less + * than 0, is greater than 0xFFFFFFFF when + * <a href="package-summary.html#zip64">ZIP64 format</a> is not supported, + * or is less than 0 when ZIP64 is supported + * @see #getSize() + */ + public void setSize(long size) { + if (size < 0) { + throw new IllegalArgumentException("invalid entry size"); + } + this.size = size; + } + + /** + * Returns the uncompressed size of the entry data. + * + * @return the uncompressed size of the entry data, or -1 if not known + * @see #setSize(long) + */ + public long getSize() { + return size; + } + + /** + * Returns the size of the compressed entry data. + * + * <p> In the case of a stored entry, the compressed size will be the same + * as the uncompressed size of the entry. + * + * @return the size of the compressed entry data, or -1 if not known + * @see #setCompressedSize(long) + */ + public long getCompressedSize() { + return csize; + } + + /** + * Sets the size of the compressed entry data. + * + * @param csize the compressed size to set + * + * @see #getCompressedSize() + */ + public void setCompressedSize(long csize) { + this.csize = csize; + this.csizeSet = true; + } + + /** + * Sets the CRC-32 checksum of the uncompressed entry data. + * + * @param crc the CRC-32 value + * + * @throws IllegalArgumentException if the specified CRC-32 value is + * less than 0 or greater than 0xFFFFFFFF + * @see #getCrc() + */ + public void setCrc(long crc) { + if (crc < 0 || crc > 0xFFFFFFFFL) { + throw new IllegalArgumentException("invalid entry crc-32"); + } + this.crc = crc; + } + + /** + * Returns the CRC-32 checksum of the uncompressed entry data. + * + * @return the CRC-32 checksum of the uncompressed entry data, or -1 if + * not known + * + * @see #setCrc(long) + */ + public long getCrc() { + return crc; + } + + /** + * Sets the compression method for the entry. + * + * @param method the compression method, either STORED or DEFLATED + * + * @throws IllegalArgumentException if the specified compression + * method is invalid + * @see #getMethod() + */ + public void setMethod(int method) { + if (method != STORED && method != DEFLATED) { + throw new IllegalArgumentException("invalid compression method"); + } + this.method = method; + } + + /** + * Returns the compression method of the entry. + * + * @return the compression method of the entry, or -1 if not specified + * @see #setMethod(int) + */ + public int getMethod() { + return method; + } + + /** + * Sets the optional extra field data for the entry. + * + * <p> Invoking this method may change this entry's last modification + * time, last access time and creation time, if the {@code extra} field + * data includes the extensible timestamp fields, such as {@code NTFS tag + * 0x0001} or {@code Info-ZIP Extended Timestamp}, as specified in + * <a href="http://www.info-zip.org/doc/appnote-19970311-iz.zip">Info-ZIP + * Application Note 970311</a>. + * + * @param extra + * The extra field data bytes + * + * @throws IllegalArgumentException if the length of the specified + * extra field data is greater than 0xFFFF bytes + * + * @see #getExtra() + */ + public void setExtra(byte[] extra) { + setExtra0(extra, false, true); + } + + /** + * Sets the optional extra field data for the entry. + * + * @param extra + * the extra field data bytes + * @param doZIP64 + * if true, set size and csize from ZIP64 fields if present + * @param isLOC + * true if setting the extra field for a LOC, false if for + * a CEN + */ + void setExtra0(byte[] extra, boolean doZIP64, boolean isLOC) { + if (extra != null) { + if (extra.length > 0xFFFF) { + throw new IllegalArgumentException("invalid extra field length"); + } + // extra fields are in "HeaderID(2)DataSize(2)Data... format + int off = 0; + int len = extra.length; + while (off + 4 < len) { + int tag = get16(extra, off); + int sz = get16(extra, off + 2); + off += 4; + if (off + sz > len) // invalid data + break; + switch (tag) { + case EXTID_ZIP64: + if (doZIP64) { + if (isLOC) { + // LOC extra zip64 entry MUST include BOTH original + // and compressed file size fields. + // If invalid zip64 extra fields, simply skip. Even + // it's rare, it's possible the entry size happens to + // be the magic value and it "accidently" has some + // bytes in extra match the id. + if (sz >= 16) { + size = get64(extra, off); + csize = get64(extra, off + 8); + } + } else { + // CEN extra zip64 + if (size == ZIP64_MAGICVAL) { + if (off + 8 > len) // invalid zip64 extra + break; // fields, just skip + size = get64(extra, off); + } + if (csize == ZIP64_MAGICVAL) { + if (off + 16 > len) // invalid zip64 extra + break; // fields, just skip + csize = get64(extra, off + 8); + } + } + } + break; + case EXTID_NTFS: + if (sz < 32) // reserved 4 bytes + tag 2 bytes + size 2 bytes + break; // m[a|c]time 24 bytes + int pos = off + 4; // reserved 4 bytes + if (get16(extra, pos) != 0x0001 || get16(extra, pos + 2) != 24) + break; + long wtime = get64(extra, pos + 4); + if (wtime != WINDOWS_TIME_NOT_AVAILABLE) { + mtime = winTimeToFileTime(wtime); + } + wtime = get64(extra, pos + 12); + if (wtime != WINDOWS_TIME_NOT_AVAILABLE) { + atime = winTimeToFileTime(wtime); + } + wtime = get64(extra, pos + 20); + if (wtime != WINDOWS_TIME_NOT_AVAILABLE) { + ctime = winTimeToFileTime(wtime); + } + break; + case EXTID_EXTT: + int flag = Byte.toUnsignedInt(extra[off]); + int sz0 = 1; + // The CEN-header extra field contains the modification + // time only, or no timestamp at all. 'sz' is used to + // flag its presence or absence. But if mtime is present + // in LOC it must be present in CEN as well. + if ((flag & 0x1) != 0 && (sz0 + 4) <= sz) { + mtime = unixTimeToFileTime(get32S(extra, off + sz0)); + sz0 += 4; + } + if ((flag & 0x2) != 0 && (sz0 + 4) <= sz) { + atime = unixTimeToFileTime(get32S(extra, off + sz0)); + sz0 += 4; + } + if ((flag & 0x4) != 0 && (sz0 + 4) <= sz) { + ctime = unixTimeToFileTime(get32S(extra, off + sz0)); + sz0 += 4; + } + break; + default: + } + off += sz; + } + } + this.extra = extra; + } + + /** + * Returns the extra field data for the entry. + * + * @return the extra field data for the entry, or null if none + * + * @see #setExtra(byte[]) + */ + public byte[] getExtra() { + return extra; + } + + /** + * Sets the optional comment string for the entry. + * + * <p>ZIP entry comments have maximum length of 0xffff. If the length of the + * specified comment string is greater than 0xFFFF bytes after encoding, only + * the first 0xFFFF bytes are output to the ZIP file entry. + * + * @param comment the comment string + * + * @see #getComment() + */ + public void setComment(String comment) { + // BEGIN Android-added: Explicitly use UTF_8 instead of the default charset. + if (comment != null && comment.getBytes(StandardCharsets.UTF_8).length > 0xffff) { + throw new IllegalArgumentException(comment + " too long: " + + comment.getBytes(StandardCharsets.UTF_8).length); + } + // END Android-added: Explicitly use UTF_8 instead of the default charset. + + this.comment = comment; + } + + /** + * Returns the comment string for the entry. + * + * @return the comment string for the entry, or null if none + * + * @see #setComment(String) + */ + public String getComment() { + return comment; + } + + /** + * Returns true if this is a directory entry. A directory entry is + * defined to be one whose name ends with a '/'. + * @return true if this is a directory entry + */ + public boolean isDirectory() { + return name.endsWith("/"); + } + + /** + * Returns a string representation of the ZIP entry. + */ + public String toString() { + return getName(); + } + + /** + * Returns the hash code value for this entry. + */ + public int hashCode() { + return name.hashCode(); + } + + /** + * Returns a copy of this entry. + */ + public Object clone() { + try { + ZipEntry e = (ZipEntry)super.clone(); + e.extra = (extra == null) ? null : extra.clone(); + return e; + } catch (CloneNotSupportedException e) { + // This should never happen, since we are Cloneable + throw new InternalError(e); + } + } +}
diff --git a/android-35/java/util/zip/ZipError.java b/android-35/java/util/zip/ZipError.java new file mode 100644 index 0000000..2aa37be --- /dev/null +++ b/android-35/java/util/zip/ZipError.java
@@ -0,0 +1,45 @@ +/* + * Copyright (c) 2006, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +/** + * Signals that an unrecoverable error has occurred. + * + * @author Dave Bristor + * @since 1.6 + */ +public class ZipError extends InternalError { + @java.io.Serial + private static final long serialVersionUID = 853973422266861979L; + + /** + * Constructs a ZipError with the given detail message. + * @param s the {@code String} containing a detail message + */ + public ZipError(String s) { + super(s); + } +}
diff --git a/android-35/java/util/zip/ZipException.java b/android-35/java/util/zip/ZipException.java new file mode 100644 index 0000000..45048a4 --- /dev/null +++ b/android-35/java/util/zip/ZipException.java
@@ -0,0 +1,59 @@ +/* + * Copyright (c) 1995, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +import java.io.IOException; + +/** + * Signals that a Zip exception of some sort has occurred. + * + * @see java.io.IOException + * @since 1.1 + */ + +public class ZipException extends IOException { + @java.io.Serial + private static final long serialVersionUID = 8000196834066748623L; + + /** + * Constructs a {@code ZipException} with {@code null} + * as its error detail message. + */ + public ZipException() { + super(); + } + + /** + * Constructs a {@code ZipException} with the specified detail + * message. + * + * @param s the detail message. + */ + + public ZipException(String s) { + super(s); + } +}
diff --git a/android-35/java/util/zip/ZipFile.java b/android-35/java/util/zip/ZipFile.java new file mode 100644 index 0000000..ee23e55 --- /dev/null +++ b/android-35/java/util/zip/ZipFile.java
@@ -0,0 +1,1941 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * Copyright (c) 1995, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +import java.io.Closeable; +import java.io.InputStream; +import java.io.IOException; +import java.io.EOFException; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.RandomAccessFile; +import java.io.UncheckedIOException; +import java.lang.ref.Cleaner.Cleanable; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.InvalidPathException; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.Files; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Deque; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.TreeSet; +import java.util.WeakHashMap; +import java.util.function.Consumer; +import java.util.function.IntFunction; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import jdk.internal.access.SharedSecrets; +import jdk.internal.misc.VM; +import jdk.internal.ref.CleanerFactory; +import jdk.internal.vm.annotation.Stable; +import sun.security.util.SignatureFileVerifier; + +import dalvik.system.CloseGuard; +import dalvik.system.ZipPathValidator; + +import static java.util.zip.ZipConstants64.*; +import static java.util.zip.ZipUtils.*; + +/** + * This class is used to read entries from a zip file. + * + * <p> Unless otherwise noted, passing a {@code null} argument to a constructor + * or method in this class will cause a {@link NullPointerException} to be + * thrown. + * + * @apiNote + * To release resources used by this {@code ZipFile}, the {@link #close()} method + * should be called explicitly or by try-with-resources. Subclasses are responsible + * for the cleanup of resources acquired by the subclass. Subclasses that override + * {@link #finalize()} in order to perform cleanup should be modified to use alternative + * cleanup mechanisms such as {@link java.lang.ref.Cleaner} and remove the overriding + * {@code finalize} method. + * + * @author David Connelly + * @since 1.1 + */ +public class ZipFile implements ZipConstants, Closeable { + + private final String name; // zip file name + private volatile boolean closeRequested; + + // The "resource" used by this zip file that needs to be + // cleaned after use. + // a) the input streams that need to be closed + // b) the list of cached Inflater objects + // c) the "native" source of this zip file. + private final @Stable CleanableResource res; + + // Android-added: CloseGuard support. + private final CloseGuard guard = CloseGuard.get(); + + private static final int STORED = ZipEntry.STORED; + private static final int DEFLATED = ZipEntry.DEFLATED; + + /** + * Mode flag to open a zip file for reading. + */ + public static final int OPEN_READ = 0x1; + + /** + * Mode flag to open a zip file and mark it for deletion. The file will be + * deleted some time between the moment that it is opened and the moment + * that it is closed, but its contents will remain accessible via the + * {@code ZipFile} object until either the close method is invoked or the + * virtual machine exits. + */ + public static final int OPEN_DELETE = 0x4; + + // Android-changed: Additional ZipException throw scenario with ZipPathValidator. + /** + * Opens a zip file for reading. + * + * <p>First, if there is a security manager, its {@code checkRead} + * method is called with the {@code name} argument as its argument + * to ensure the read is allowed. + * + * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to + * decode the entry names and comments. + * + * <p>If the app targets Android U or above, zip file entry names containing + * ".." or starting with "/" passed here will throw a {@link ZipException}. + * For more details, see {@link dalvik.system.ZipPathValidator}. + * + * @param name the name of the zip file + * @throws ZipException if (1) a ZIP format error has occurred or + * (2) <code>targetSdkVersion >= BUILD.VERSION_CODES.UPSIDE_DOWN_CAKE</code> + * and (the <code>name</code> argument contains ".." or starts with "/"). + * @throws IOException if an I/O error has occurred + * @throws SecurityException if a security manager exists and its + * {@code checkRead} method doesn't allow read access to the file. + * + * @see SecurityManager#checkRead(java.lang.String) + */ + public ZipFile(String name) throws IOException { + this(new File(name), OPEN_READ); + } + + /** + * Opens a new {@code ZipFile} to read from the specified + * {@code File} object in the specified mode. The mode argument + * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}. + * + * <p>First, if there is a security manager, its {@code checkRead} + * method is called with the {@code name} argument as its argument to + * ensure the read is allowed. + * + * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to + * decode the entry names and comments + * + * @param file the ZIP file to be opened for reading + * @param mode the mode in which the file is to be opened + * @throws ZipException if a ZIP format error has occurred + * @throws IOException if an I/O error has occurred + * @throws SecurityException if a security manager exists and + * its {@code checkRead} method + * doesn't allow read access to the file, + * or its {@code checkDelete} method doesn't allow deleting + * the file when the {@code OPEN_DELETE} flag is set. + * @throws IllegalArgumentException if the {@code mode} argument is invalid + * @see SecurityManager#checkRead(java.lang.String) + * @since 1.3 + */ + public ZipFile(File file, int mode) throws IOException { + // Android-changed: Use StandardCharsets.UTF_8. + // this(file, mode, UTF_8.INSTANCE); + this(file, mode, StandardCharsets.UTF_8); + } + + /** + * Opens a ZIP file for reading given the specified File object. + * + * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to + * decode the entry names and comments. + * + * @param file the ZIP file to be opened for reading + * @throws ZipException if a ZIP format error has occurred + * @throws IOException if an I/O error has occurred + */ + public ZipFile(File file) throws ZipException, IOException { + this(file, OPEN_READ); + } + + // Android-changed: Use of the hidden constructor with a new argument for zip path validation. + /** + * Opens a new {@code ZipFile} to read from the specified + * {@code File} object in the specified mode. The mode argument + * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}. + * + * <p>First, if there is a security manager, its {@code checkRead} + * method is called with the {@code name} argument as its argument to + * ensure the read is allowed. + * + * @param file the ZIP file to be opened for reading + * @param mode the mode in which the file is to be opened + * @param charset + * the {@linkplain java.nio.charset.Charset charset} to + * be used to decode the ZIP entry name and comment that are not + * encoded by using UTF-8 encoding (indicated by entry's general + * purpose flag). + * + * @throws ZipException if a ZIP format error has occurred + * @throws IOException if an I/O error has occurred + * + * @throws SecurityException + * if a security manager exists and its {@code checkRead} + * method doesn't allow read access to the file,or its + * {@code checkDelete} method doesn't allow deleting the + * file when the {@code OPEN_DELETE} flag is set + * + * @throws IllegalArgumentException if the {@code mode} argument is invalid + * + * @see SecurityManager#checkRead(java.lang.String) + * + * @since 1.7 + */ + public ZipFile(File file, int mode, Charset charset) throws IOException + { + this(file, mode, charset, /* enableZipPathValidator */ true); + } + + // Android-added: New hidden constructor with an argument for zip path validation. + /** @hide */ + public ZipFile(File file, int mode, boolean enableZipPathValidator) throws IOException { + this(file, mode, StandardCharsets.UTF_8, enableZipPathValidator); + } + + // Android-changed: Change existing constructor ZipFile(File file, int mode, Charset charset) + // to have a new argument enableZipPathValidator in order to set the isZipPathValidatorEnabled + // variable before calling the native method open(). + /** @hide */ + public ZipFile(File file, int mode, Charset charset, boolean enableZipPathValidator) + throws IOException { + if (((mode & OPEN_READ) == 0) || + ((mode & ~(OPEN_READ | OPEN_DELETE)) != 0)) { + throw new IllegalArgumentException("Illegal mode: 0x"+ + Integer.toHexString(mode)); + } + String name = file.getPath(); + file = new File(name); + // Android-removed: SecurityManager is always null. + /* + @SuppressWarnings("removal") + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkRead(name); + if ((mode & OPEN_DELETE) != 0) { + sm.checkDelete(name); + } + } + */ + + Objects.requireNonNull(charset, "charset"); + + this.name = name; + // Android-removed: Skip perf counters. + // long t0 = System.nanoTime(); + + // Android-changed: pass isZipPathValidatorEnabled flag. + // this.res = new CleanableResource(this, ZipCoder.get(charset), file, mode); + boolean isZipPathValidatorEnabled = enableZipPathValidator && !ZipPathValidator.isClear(); + this.res = new CleanableResource( + this, ZipCoder.get(charset), file, mode, isZipPathValidatorEnabled); + + // Android-removed: Skip perf counters. + // PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0); + // PerfCounter.getZipFileCount().increment(); + } + + /** + * Opens a zip file for reading. + * + * <p>First, if there is a security manager, its {@code checkRead} + * method is called with the {@code name} argument as its argument + * to ensure the read is allowed. + * + * @param name the name of the zip file + * @param charset + * the {@linkplain java.nio.charset.Charset charset} to + * be used to decode the ZIP entry name and comment that are not + * encoded by using UTF-8 encoding (indicated by entry's general + * purpose flag). + * + * @throws ZipException if a ZIP format error has occurred + * @throws IOException if an I/O error has occurred + * @throws SecurityException + * if a security manager exists and its {@code checkRead} + * method doesn't allow read access to the file + * + * @see SecurityManager#checkRead(java.lang.String) + * + * @since 1.7 + */ + public ZipFile(String name, Charset charset) throws IOException + { + this(new File(name), OPEN_READ, charset); + } + + /** + * Opens a ZIP file for reading given the specified File object. + * + * @param file the ZIP file to be opened for reading + * @param charset + * The {@linkplain java.nio.charset.Charset charset} to be + * used to decode the ZIP entry name and comment (ignored if + * the <a href="package-summary.html#lang_encoding"> language + * encoding bit</a> of the ZIP entry's general purpose bit + * flag is set). + * + * @throws ZipException if a ZIP format error has occurred + * @throws IOException if an I/O error has occurred + * + * @since 1.7 + */ + public ZipFile(File file, Charset charset) throws IOException + { + this(file, OPEN_READ, charset); + } + + /** + * Returns the zip file comment, or null if none. + * + * @return the comment string for the zip file, or null if none + * + * @throws IllegalStateException if the zip file has been closed + * + * @since 1.7 + */ + public String getComment() { + synchronized (this) { + ensureOpen(); + if (res.zsrc.comment == null) { + return null; + } + return res.zsrc.zc.toString(res.zsrc.comment); + } + } + + /** + * Returns the zip file entry for the specified name, or null + * if not found. + * + * @param name the name of the entry + * @return the zip file entry, or null if not found + * @throws IllegalStateException if the zip file has been closed + */ + public ZipEntry getEntry(String name) { + Objects.requireNonNull(name, "name"); + ZipEntry entry = null; + synchronized (this) { + ensureOpen(); + int pos = res.zsrc.getEntryPos(name, true); + if (pos != -1) { + entry = getZipEntry(name, pos); + } + } + return entry; + } + + /** + * Returns an input stream for reading the contents of the specified + * zip file entry. + * <p> + * Closing this ZIP file will, in turn, close all input streams that + * have been returned by invocations of this method. + * + * @param entry the zip file entry + * @return the input stream for reading the contents of the specified + * zip file entry. + * @throws ZipException if a ZIP format error has occurred + * @throws IOException if an I/O error has occurred + * @throws IllegalStateException if the zip file has been closed + */ + public InputStream getInputStream(ZipEntry entry) throws IOException { + Objects.requireNonNull(entry, "entry"); + int pos; + ZipFileInputStream in; + Source zsrc = res.zsrc; + Set<InputStream> istreams = res.istreams; + synchronized (this) { + ensureOpen(); + if (Objects.equals(lastEntryName, entry.name)) { + pos = lastEntryPos; + } else { + pos = zsrc.getEntryPos(entry.name, false); + } + if (pos == -1) { + return null; + } + in = new ZipFileInputStream(zsrc.cen, pos); + switch (CENHOW(zsrc.cen, pos)) { + case STORED: + synchronized (istreams) { + istreams.add(in); + } + return in; + case DEFLATED: + // Inflater likes a bit of slack + // MORE: Compute good size for inflater stream: + long size = CENLEN(zsrc.cen, pos) + 2; + if (size > 65536) { + // Android-changed: Use 64k buffer size, performs + // better than 8k. See http://b/65491407. + // size = 8192; + size = 65536; + } + if (size <= 0) { + size = 4096; + } + InputStream is = new ZipFileInflaterInputStream(in, res, (int)size); + synchronized (istreams) { + istreams.add(is); + } + return is; + default: + throw new ZipException("invalid compression method"); + } + } + } + + private static class InflaterCleanupAction implements Runnable { + private final Inflater inf; + private final CleanableResource res; + + InflaterCleanupAction(Inflater inf, CleanableResource res) { + this.inf = inf; + this.res = res; + } + + @Override + public void run() { + res.releaseInflater(inf); + } + } + + private class ZipFileInflaterInputStream extends InflaterInputStream { + private volatile boolean closeRequested; + private boolean eof = false; + private final Cleanable cleanable; + + ZipFileInflaterInputStream(ZipFileInputStream zfin, + CleanableResource res, int size) { + this(zfin, res, res.getInflater(), size); + } + + private ZipFileInflaterInputStream(ZipFileInputStream zfin, + CleanableResource res, + Inflater inf, int size) { + // Android-changed: ZipFileInflaterInputStream does not control its inflater's lifetime + // and hence it shouldn't be closed when the stream is closed. + // super(zfin, inf, size); + super(zfin, inf, size, /* ownsInflater */ false); + this.cleanable = CleanerFactory.cleaner().register(this, + new InflaterCleanupAction(inf, res)); + } + + public void close() throws IOException { + if (closeRequested) + return; + closeRequested = true; + super.close(); + synchronized (res.istreams) { + res.istreams.remove(this); + } + cleanable.clean(); + } + + // Override fill() method to provide an extra "dummy" byte + // at the end of the input stream. This is required when + // using the "nowrap" Inflater option. + protected void fill() throws IOException { + if (eof) { + throw new EOFException("Unexpected end of ZLIB input stream"); + } + len = in.read(buf, 0, buf.length); + if (len == -1) { + buf[0] = 0; + len = 1; + eof = true; + } + inf.setInput(buf, 0, len); + } + + public int available() throws IOException { + if (closeRequested) + return 0; + long avail = ((ZipFileInputStream)in).size() - inf.getBytesWritten(); + return (avail > (long) Integer.MAX_VALUE ? + Integer.MAX_VALUE : (int) avail); + } + } + + /** + * Returns the path name of the ZIP file. + * @return the path name of the ZIP file + */ + public String getName() { + return name; + } + + private class ZipEntryIterator<T extends ZipEntry> + implements Enumeration<T>, Iterator<T> { + + private int i = 0; + private final int entryCount; + + public ZipEntryIterator(int entryCount) { + this.entryCount = entryCount; + } + + @Override + public boolean hasMoreElements() { + return hasNext(); + } + + @Override + public boolean hasNext() { + // Android-changed: check that file is open. + // return i < entryCount; + synchronized (ZipFile.this) { + ensureOpen(); + return i < entryCount; + } + } + + @Override + public T nextElement() { + return next(); + } + + @Override + @SuppressWarnings("unchecked") + public T next() { + synchronized (ZipFile.this) { + ensureOpen(); + if (!hasNext()) { + throw new NoSuchElementException(); + } + // each "entry" has 3 ints in table entries + return (T)getZipEntry(null, res.zsrc.getEntryPos(i++ * 3)); + } + } + + @Override + public Iterator<T> asIterator() { + return this; + } + } + + /** + * Returns an enumeration of the ZIP file entries. + * @return an enumeration of the ZIP file entries + * @throws IllegalStateException if the zip file has been closed + */ + public Enumeration<? extends ZipEntry> entries() { + synchronized (this) { + ensureOpen(); + return new ZipEntryIterator<ZipEntry>(res.zsrc.total); + } + } + + private Enumeration<JarEntry> jarEntries() { + synchronized (this) { + ensureOpen(); + return new ZipEntryIterator<JarEntry>(res.zsrc.total); + } + } + + private class EntrySpliterator<T> extends Spliterators.AbstractSpliterator<T> { + private int index; + private final int fence; + private final IntFunction<T> gen; + + EntrySpliterator(int index, int fence, IntFunction<T> gen) { + super((long)fence, + Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.IMMUTABLE | + Spliterator.NONNULL); + this.index = index; + this.fence = fence; + this.gen = gen; + } + + @Override + public boolean tryAdvance(Consumer<? super T> action) { + if (action == null) + throw new NullPointerException(); + if (index >= 0 && index < fence) { + synchronized (ZipFile.this) { + ensureOpen(); + action.accept(gen.apply(res.zsrc.getEntryPos(index++ * 3))); + } + return true; + } + return false; + } + } + + /** + * Returns an ordered {@code Stream} over the ZIP file entries. + * + * Entries appear in the {@code Stream} in the order they appear in + * the central directory of the ZIP file. + * + * @return an ordered {@code Stream} of entries in this ZIP file + * @throws IllegalStateException if the zip file has been closed + * @since 1.8 + */ + public Stream<? extends ZipEntry> stream() { + synchronized (this) { + ensureOpen(); + return StreamSupport.stream(new EntrySpliterator<>(0, res.zsrc.total, + pos -> getZipEntry(null, pos)), false); + } + } + + private String getEntryName(int pos) { + byte[] cen = res.zsrc.cen; + int nlen = CENNAM(cen, pos); + ZipCoder zc = res.zsrc.zipCoderForPos(pos); + return zc.toString(cen, pos + CENHDR, nlen); + } + + /* + * Returns an ordered {@code Stream} over the zip file entry names. + * + * Entry names appear in the {@code Stream} in the order they appear in + * the central directory of the ZIP file. + * + * @return an ordered {@code Stream} of entry names in this zip file + * @throws IllegalStateException if the zip file has been closed + * @since 10 + */ + private Stream<String> entryNameStream() { + synchronized (this) { + ensureOpen(); + return StreamSupport.stream( + new EntrySpliterator<>(0, res.zsrc.total, this::getEntryName), false); + } + } + + /* + * Returns an ordered {@code Stream} over the zip file entries. + * + * Entries appear in the {@code Stream} in the order they appear in + * the central directory of the jar file. + * + * @return an ordered {@code Stream} of entries in this zip file + * @throws IllegalStateException if the zip file has been closed + * @since 10 + */ + private Stream<JarEntry> jarStream() { + synchronized (this) { + ensureOpen(); + return StreamSupport.stream(new EntrySpliterator<>(0, res.zsrc.total, + pos -> (JarEntry)getZipEntry(null, pos)), false); + } + } + + private String lastEntryName; + private int lastEntryPos; + + /* Check ensureOpen() before invoking this method */ + private ZipEntry getZipEntry(String name, int pos) { + byte[] cen = res.zsrc.cen; + int nlen = CENNAM(cen, pos); + int elen = CENEXT(cen, pos); + int clen = CENCOM(cen, pos); + + ZipCoder zc = res.zsrc.zipCoderForPos(pos); + if (name != null) { + // only need to check for mismatch of trailing slash + if (nlen > 0 && + !name.isEmpty() && + zc.hasTrailingSlash(cen, pos + CENHDR + nlen) && + !name.endsWith("/")) + { + name += '/'; + } + } else { + // invoked from iterator, use the entry name stored in cen + name = zc.toString(cen, pos + CENHDR, nlen); + } + ZipEntry e; + if (this instanceof JarFile) { + // Android-changed: access method directly. + // e = Source.JUJA.entryFor((JarFile)this, name); + e = ((JarFile) this).entryFor(name); + } else { + e = new ZipEntry(name); + } + e.flag = CENFLG(cen, pos); + e.xdostime = CENTIM(cen, pos); + e.crc = CENCRC(cen, pos); + e.size = CENLEN(cen, pos); + e.csize = CENSIZ(cen, pos); + e.method = CENHOW(cen, pos); + if (CENVEM_FA(cen, pos) == FILE_ATTRIBUTES_UNIX) { + // read all bits in this field, including sym link attributes + e.extraAttributes = CENATX_PERMS(cen, pos) & 0xFFFF; + } + + if (elen != 0) { + int start = pos + CENHDR + nlen; + e.setExtra0(Arrays.copyOfRange(cen, start, start + elen), true, false); + } + if (clen != 0) { + int start = pos + CENHDR + nlen + elen; + e.comment = zc.toString(cen, start, clen); + } + lastEntryName = e.name; + lastEntryPos = pos; + return e; + } + + /** + * Returns the number of entries in the ZIP file. + * + * @return the number of entries in the ZIP file + * @throws IllegalStateException if the zip file has been closed + */ + public int size() { + synchronized (this) { + ensureOpen(); + return res.zsrc.total; + } + } + + private static class CleanableResource implements Runnable { + // The outstanding inputstreams that need to be closed + final Set<InputStream> istreams; + + // List of cached Inflater objects for decompression + Deque<Inflater> inflaterCache; + + final Cleanable cleanable; + + Source zsrc; + + CleanableResource(ZipFile zf, ZipCoder zc, File file, int mode) throws IOException { + this(zf, zc, file, mode, false); + } + + // Android-added: added extra enableZipPathValidator argument. + CleanableResource(ZipFile zf, ZipCoder zc, File file, + int mode, boolean enableZipPathValidator) throws IOException { + this.cleanable = CleanerFactory.cleaner().register(zf, this); + this.istreams = Collections.newSetFromMap(new WeakHashMap<>()); + this.inflaterCache = new ArrayDeque<>(); + this.zsrc = Source.get(file, (mode & OPEN_DELETE) != 0, zc, enableZipPathValidator); + } + + void clean() { + cleanable.clean(); + } + + /* + * Gets an inflater from the list of available inflaters or allocates + * a new one. + */ + Inflater getInflater() { + Inflater inf; + synchronized (inflaterCache) { + if ((inf = inflaterCache.poll()) != null) { + return inf; + } + } + return new Inflater(true); + } + + /* + * Releases the specified inflater to the list of available inflaters. + */ + void releaseInflater(Inflater inf) { + Deque<Inflater> inflaters = this.inflaterCache; + if (inflaters != null) { + synchronized (inflaters) { + // double checked! + if (inflaters == this.inflaterCache) { + inf.reset(); + inflaters.add(inf); + return; + } + } + } + // inflaters cache already closed - just end it. + inf.end(); + } + + public void run() { + IOException ioe = null; + + // Release cached inflaters and close the cache first + Deque<Inflater> inflaters = this.inflaterCache; + if (inflaters != null) { + synchronized (inflaters) { + // no need to double-check as only one thread gets a + // chance to execute run() (Cleaner guarantee)... + Inflater inf; + while ((inf = inflaters.poll()) != null) { + inf.end(); + } + // close inflaters cache + this.inflaterCache = null; + } + } + + // Close streams, release their inflaters + if (istreams != null) { + synchronized (istreams) { + if (!istreams.isEmpty()) { + InputStream[] copy = istreams.toArray(new InputStream[0]); + istreams.clear(); + for (InputStream is : copy) { + try { + is.close(); + } catch (IOException e) { + if (ioe == null) ioe = e; + else ioe.addSuppressed(e); + } + } + } + } + } + + // Release zip src + if (zsrc != null) { + synchronized (zsrc) { + try { + Source.release(zsrc); + zsrc = null; + } catch (IOException e) { + if (ioe == null) ioe = e; + else ioe.addSuppressed(e); + } + } + } + if (ioe != null) { + throw new UncheckedIOException(ioe); + } + } + } + + /** + * Closes the ZIP file. + * + * <p> Closing this ZIP file will close all of the input streams + * previously returned by invocations of the {@link #getInputStream + * getInputStream} method. + * + * @throws IOException if an I/O error has occurred + */ + public void close() throws IOException { + if (closeRequested) { + return; + } + // Android-added: CloseGuard support. + if (guard != null) { + guard.close(); + } + closeRequested = true; + + synchronized (this) { + // Close streams, release their inflaters, release cached inflaters + // and release zip source + try { + res.clean(); + } catch (UncheckedIOException ioe) { + throw ioe.getCause(); + } + } + } + + private void ensureOpen() { + if (closeRequested) { + throw new IllegalStateException("zip file closed"); + } + if (res.zsrc == null) { + throw new IllegalStateException("The object is not initialized."); + } + } + + private void ensureOpenOrZipException() throws IOException { + if (closeRequested) { + throw new ZipException("ZipFile closed"); + } + } + + /* + * Inner class implementing the input stream used to read a + * (possibly compressed) zip file entry. + */ + private class ZipFileInputStream extends InputStream { + private volatile boolean closeRequested; + private long pos; // current position within entry data + private long startingPos; // Start position for the entry data + protected long rem; // number of remaining bytes within entry + protected long size; // uncompressed size of this entry + + ZipFileInputStream(byte[] cen, int cenpos) { + rem = CENSIZ(cen, cenpos); + size = CENLEN(cen, cenpos); + pos = CENOFF(cen, cenpos); + // zip64 + if (rem == ZIP64_MAGICVAL || size == ZIP64_MAGICVAL || + pos == ZIP64_MAGICVAL) { + checkZIP64(cen, cenpos); + } + // negative for lazy initialization, see getDataOffset(); + pos = - (pos + ZipFile.this.res.zsrc.locpos); + } + + private void checkZIP64(byte[] cen, int cenpos) { + int off = cenpos + CENHDR + CENNAM(cen, cenpos); + int end = off + CENEXT(cen, cenpos); + while (off + 4 < end) { + int tag = get16(cen, off); + int sz = get16(cen, off + 2); + off += 4; + if (off + sz > end) // invalid data + break; + if (tag == EXTID_ZIP64) { + if (size == ZIP64_MAGICVAL) { + if (sz < 8 || (off + 8) > end) + break; + size = get64(cen, off); + sz -= 8; + off += 8; + } + if (rem == ZIP64_MAGICVAL) { + if (sz < 8 || (off + 8) > end) + break; + rem = get64(cen, off); + sz -= 8; + off += 8; + } + if (pos == ZIP64_MAGICVAL) { + if (sz < 8 || (off + 8) > end) + break; + pos = get64(cen, off); + sz -= 8; + off += 8; + } + break; + } + off += sz; + } + } + + /* + * The Zip file spec explicitly allows the LOC extra data size to + * be different from the CEN extra data size. Since we cannot trust + * the CEN extra data size, we need to read the LOC to determine + * the entry data offset. + */ + private long initDataOffset() throws IOException { + if (pos <= 0) { + byte[] loc = new byte[LOCHDR]; + pos = -pos; + int len = ZipFile.this.res.zsrc.readFullyAt(loc, 0, loc.length, pos); + if (len != LOCHDR) { + throw new ZipException("ZipFile error reading zip file"); + } + if (LOCSIG(loc) != LOCSIG) { + throw new ZipException("ZipFile invalid LOC header (bad signature)"); + } + pos += LOCHDR + LOCNAM(loc) + LOCEXT(loc); + startingPos = pos; // Save starting position for the entry + } + return pos; + } + + public int read(byte b[], int off, int len) throws IOException { + synchronized (ZipFile.this) { + ensureOpenOrZipException(); + initDataOffset(); + if (rem == 0) { + return -1; + } + if (len > rem) { + len = (int) rem; + } + if (len <= 0) { + return 0; + } + len = ZipFile.this.res.zsrc.readAt(b, off, len, pos); + if (len > 0) { + pos += len; + rem -= len; + } + } + if (rem == 0) { + close(); + } + return len; + } + + public int read() throws IOException { + byte[] b = new byte[1]; + if (read(b, 0, 1) == 1) { + return b[0] & 0xff; + } else { + return -1; + } + } + + public long skip(long n) throws IOException { + synchronized (ZipFile.this) { + initDataOffset(); + long newPos = pos + n; + if (n > 0) { + // If we overflowed adding the skip value or are moving + // past EOF, set the skip value to number of bytes remaining + // to reach EOF + if (newPos < 0 || n > rem) { + n = rem; + } + } else if (newPos < startingPos) { + // Tried to position before BOF so set position to the + // BOF and return the number of bytes we moved backwards + // to reach BOF + n = startingPos - pos; + } + pos += n; + rem -= n; + } + if (rem == 0) { + close(); + } + return n; + } + + public int available() { + return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem; + } + + public long size() { + return size; + } + + public void close() { + if (closeRequested) { + return; + } + closeRequested = true; + rem = 0; + synchronized (res.istreams) { + res.istreams.remove(this); + } + } + + } + + /** + * Returns {@code true} if, and only if, the zip file begins with {@code + * LOCSIG}. + * @hide + */ + // Android-added: Access startsWithLocHeader() directly. + // Make hidden public for use by sun.misc.URLClassPath + public boolean startsWithLocHeader() { + return res.zsrc.startsWithLoc; + } + + // Android-changed: marked as protected so JarFile can access it. + /** + * Returns the names of the META-INF/MANIFEST.MF entry - if exists - + * and any signature-related files under META-INF. This method is used in + * JarFile, via SharedSecrets, as an optimization. + * @hide + */ + protected List<String> getManifestAndSignatureRelatedFiles() { + synchronized (this) { + ensureOpen(); + Source zsrc = res.zsrc; + int[] metanames = zsrc.signatureMetaNames; + List<String> files = null; + if (zsrc.manifestPos >= 0) { + files = new ArrayList<>(); + files.add(getEntryName(zsrc.manifestPos)); + } + if (metanames != null) { + if (files == null) { + files = new ArrayList<>(); + } + for (int i = 0; i < metanames.length; i++) { + files.add(getEntryName(metanames[i])); + } + } + return files == null ? List.of() : files; + } + } + + /** + * Returns the number of the META-INF/MANIFEST.MF entries, case insensitive. + * When this number is greater than 1, JarVerifier will treat a file as + * unsigned. + */ + private int getManifestNum() { + synchronized (this) { + ensureOpen(); + return res.zsrc.manifestNum; + } + } + + // Android-changed: marked public and @hide as alternative to JavaUtilZipFileAccess.getManifestName. + /** + * Returns the name of the META-INF/MANIFEST.MF entry, ignoring + * case. If {@code onlyIfSignatureRelatedFiles} is true, we only return the + * manifest if there is also at least one signature-related file. + * This method is used in JarFile, via SharedSecrets, as an optimization + * when looking up the manifest file. + * @hide + */ + protected String getManifestName(boolean onlyIfSignatureRelatedFiles) { + synchronized (this) { + ensureOpen(); + Source zsrc = res.zsrc; + int pos = zsrc.manifestPos; + if (pos >= 0 && (!onlyIfSignatureRelatedFiles || zsrc.signatureMetaNames != null)) { + return getEntryName(pos); + } + } + return null; + } + + /** + * Returns the versions for which there exists a non-directory + * entry that begin with "META-INF/versions/" (case ignored). + * This method is used in JarFile, via SharedSecrets, as an + * optimization when looking up potentially versioned entries. + * Returns an empty array if no versioned entries exist. + */ + private int[] getMetaInfVersions() { + synchronized (this) { + ensureOpen(); + return res.zsrc.metaVersions; + } + } + + // Android-removed: this code does not run on Windows and JavaUtilZipFileAccess is not imported. + /* + private static boolean isWindows; + + static { + SharedSecrets.setJavaUtilZipFileAccess( + new JavaUtilZipFileAccess() { + @Override + public boolean startsWithLocHeader(ZipFile zip) { + return zip.res.zsrc.startsWithLoc; + } + @Override + public List<String> getManifestAndSignatureRelatedFiles(JarFile jar) { + return ((ZipFile)jar).getManifestAndSignatureRelatedFiles(); + } + @Override + public int getManifestNum(JarFile jar) { + return ((ZipFile)jar).getManifestNum(); + } + @Override + public String getManifestName(JarFile jar, boolean onlyIfHasSignatureRelatedFiles) { + return ((ZipFile)jar).getManifestName(onlyIfHasSignatureRelatedFiles); + } + @Override + public int[] getMetaInfVersions(JarFile jar) { + return ((ZipFile)jar).getMetaInfVersions(); + } + @Override + public Enumeration<JarEntry> entries(ZipFile zip) { + return zip.jarEntries(); + } + @Override + public Stream<JarEntry> stream(ZipFile zip) { + return zip.jarStream(); + } + @Override + public Stream<String> entryNameStream(ZipFile zip) { + return zip.entryNameStream(); + } + @Override + public int getExtraAttributes(ZipEntry ze) { + return ze.extraAttributes; + } + @Override + public void setExtraAttributes(ZipEntry ze, int extraAttrs) { + ze.extraAttributes = extraAttrs; + } + + } + ); + isWindows = VM.getSavedProperty("os.name").contains("Windows"); + } + */ + + private static class Source { + // While this is only used from ZipFile, defining it there would cause + // a bootstrap cycle that would leave this initialized as null + // Android-removed: JavaUtilJarAccess is not available. + // private static final JavaUtilJarAccess JUJA = SharedSecrets.javaUtilJarAccess(); + // "META-INF/".length() + private static final int META_INF_LEN = 9; + private static final int[] EMPTY_META_VERSIONS = new int[0]; + + private final Key key; // the key in files + private final @Stable ZipCoder zc; // zip coder used to decode/encode + + private int refs = 1; + + private RandomAccessFile zfile; // zfile of the underlying zip file + private byte[] cen; // CEN & ENDHDR + private long locpos; // position of first LOC header (usually 0) + private byte[] comment; // zip file comment + // list of meta entries in META-INF dir + private int manifestPos = -1; // position of the META-INF/MANIFEST.MF, if exists + private int manifestNum = 0; // number of META-INF/MANIFEST.MF, case insensitive + private int[] signatureMetaNames; // positions of signature related entries, if such exist + private int[] metaVersions; // list of unique versions found in META-INF/versions/ + private final boolean startsWithLoc; // true, if zip file starts with LOCSIG (usually true) + + // A Hashmap for all entries. + // + // A cen entry of Zip/JAR file. As we have one for every entry in every active Zip/JAR, + // We might have a lot of these in a typical system. In order to save space we don't + // keep the name in memory, but merely remember a 32 bit {@code hash} value of the + // entry name and its offset {@code pos} in the central directory hdeader. + // + // private static class Entry { + // int hash; // 32 bit hashcode on name + // int next; // hash chain: index into entries + // int pos; // Offset of central directory file header + // } + // private Entry[] entries; // array of hashed cen entry + // + // To reduce the total size of entries further, we use a int[] here to store 3 "int" + // {@code hash}, {@code next} and {@code pos} for each entry. The entry can then be + // referred by their index of their positions in the {@code entries}. + // + private int[] entries; // array of hashed cen entry + + // Checks the entry at offset pos in the CEN, calculates the Entry values as per above, + // then returns the length of the entry name. + private int checkAndAddEntry(int pos, int index) + throws ZipException + { + byte[] cen = this.cen; + if (CENSIG(cen, pos) != CENSIG) { + zerror("invalid CEN header (bad signature)"); + } + int method = CENHOW(cen, pos); + int flag = CENFLG(cen, pos); + if ((flag & 1) != 0) { + zerror("invalid CEN header (encrypted entry)"); + } + if (method != STORED && method != DEFLATED) { + zerror("invalid CEN header (bad compression method: " + method + ")"); + } + int entryPos = pos + CENHDR; + int nlen = CENNAM(cen, pos); + if (entryPos + nlen > cen.length - ENDHDR) { + zerror("invalid CEN header (bad header size)"); + } + try { + ZipCoder zcp = zipCoderForPos(pos); + int hash = zcp.checkedHash(cen, entryPos, nlen); + int hsh = (hash & 0x7fffffff) % tablelen; + int next = table[hsh]; + table[hsh] = index; + // Record the CEN offset and the name hash in our hash cell. + entries[index++] = hash; + entries[index++] = next; + entries[index ] = pos; + } catch (Exception e) { + zerror("invalid CEN header (bad entry name)"); + } + return nlen; + } + + private int getEntryHash(int index) { return entries[index]; } + private int getEntryNext(int index) { return entries[index + 1]; } + private int getEntryPos(int index) { return entries[index + 2]; } + private static final int ZIP_ENDCHAIN = -1; + private int total; // total number of entries + private int[] table; // Hash chain heads: indexes into entries + private int tablelen; // number of hash heads + + // Android-changed: isZipFilePathValidatorEnabled added as Key part. Key is used as key in + // files HashMap, so not including it could lead to opening ZipFile w/o entry names + // validation. + private static class Key { + final BasicFileAttributes attrs; + File file; + final boolean utf8; + // Android-added: isZipFilePathValidatorEnabled added as Key part. + final boolean isZipFilePathValidatorEnabled; + + public Key(File file, BasicFileAttributes attrs, ZipCoder zc) { + this(file, attrs, zc, /* isZipFilePathValidatorEnabled= */ false); + } + + // Android-added: added constructor with isZipFilePathValidatorEnabled argument. + public Key(File file, BasicFileAttributes attrs, ZipCoder zc, + boolean isZipFilePathValidatorEnabled) { + this.attrs = attrs; + this.file = file; + this.utf8 = zc.isUTF8(); + this.isZipFilePathValidatorEnabled = isZipFilePathValidatorEnabled; + } + + public int hashCode() { + long t = utf8 ? 0 : Long.MAX_VALUE; + t += attrs.lastModifiedTime().toMillis(); + // Android-changed: include izZipFilePathValidatorEnabled in hash computation. + // return ((int)(t ^ (t >>> 32))) + file.hashCode(); + return ((int)(t ^ (t >>> 32))) + file.hashCode() + + Boolean.hashCode(isZipFilePathValidatorEnabled); + } + + public boolean equals(Object obj) { + if (obj instanceof Key key) { + if (key.utf8 != utf8) { + return false; + } + if (!attrs.lastModifiedTime().equals(key.attrs.lastModifiedTime())) { + return false; + } + // Android-added: include isZipFilePathValidatorEnabled as equality part. + if (key.isZipFilePathValidatorEnabled != isZipFilePathValidatorEnabled) { + return false; + } + Object fk = attrs.fileKey(); + if (fk != null) { + return fk.equals(key.attrs.fileKey()); + } else { + return file.equals(key.file); + } + } + return false; + } + } + private static final HashMap<Key, Source> files = new HashMap<>(); + + + // Android-changed: pass izZipFilePathValidatorEnabled argument. + // static Source get(File file, boolean toDelete, ZipCoder zc) throws IOException { + static Source get(File file, boolean toDelete, ZipCoder zc, + boolean isZipPathValidatorEnabled) throws IOException { + final Key key; + try { + // BEGIN Android-changed: isZipFilePathValidatorEnabled passed as part of Key. + /* + key = new Key(file, + Files.readAttributes(file.toPath(), BasicFileAttributes.class), + zc); + */ + key = new Key(file, + Files.readAttributes(file.toPath(), BasicFileAttributes.class), + zc, isZipPathValidatorEnabled); + // END Android-changed: isZipFilePathValidatorEnabled passed as part of Key. + } catch (InvalidPathException ipe) { + throw new IOException(ipe); + } + Source src; + synchronized (files) { + src = files.get(key); + if (src != null) { + src.refs++; + return src; + } + } + src = new Source(key, toDelete, zc); + + synchronized (files) { + if (files.containsKey(key)) { // someone else put in first + src.close(); // close the newly created one + src = files.get(key); + src.refs++; + return src; + } + files.put(key, src); + return src; + } + } + + static void release(Source src) throws IOException { + synchronized (files) { + if (src != null && --src.refs == 0) { + files.remove(src.key); + src.close(); + } + } + } + + private Source(Key key, boolean toDelete, ZipCoder zc) throws IOException { + this.zc = zc; + this.key = key; + if (toDelete) { + // BEGIN Android-changed: we are not targeting Windows, keep else branch only. Also + // open file with O_CLOEXEC flag set. + /* + if (isWindows) { + this.zfile = SharedSecrets.getJavaIORandomAccessFileAccess() + .openAndDelete(key.file, "r"); + } else { + this.zfile = new RandomAccessFile(key.file, "r"); + key.file.delete(); + } + */ + this.zfile = new RandomAccessFile(key.file, "r", /* setCloExecFlag= */ true); + key.file.delete(); + // END Android-changed: we are not targeting Windows, keep else branch only. + } else { + // Android-changed: open with O_CLOEXEC flag set. + // this.zfile = new RandomAccessFile(key.file, "r"); + this.zfile = new RandomAccessFile(key.file, "r", /* setCloExecFlag= */ true); + } + try { + initCEN(-1); + byte[] buf = new byte[4]; + readFullyAt(buf, 0, 4, 0); + // BEGIN Android-changed: do not accept files with invalid header + // this.startsWithLoc = (LOCSIG(buf) == LOCSIG); + long locsig = LOCSIG(buf); + this.startsWithLoc = (locsig == LOCSIG); + // If a zip file starts with "end of central directory record" it means that such + // file is empty. + if (locsig != LOCSIG && locsig != ENDSIG) { + String msg = "Entry at offset zero has invalid LFH signature " + + Long.toHexString(locsig); + throw new ZipException(msg); + } + // END Android-changed: do not accept files with invalid header + } catch (IOException x) { + try { + this.zfile.close(); + } catch (IOException xx) {} + throw x; + } + } + + private void close() throws IOException { + zfile.close(); + zfile = null; + cen = null; + entries = null; + table = null; + manifestPos = -1; + manifestNum = 0; + signatureMetaNames = null; + metaVersions = EMPTY_META_VERSIONS; + } + + private static final int BUF_SIZE = 8192; + private final int readFullyAt(byte[] buf, int off, int len, long pos) + throws IOException + { + synchronized (zfile) { + zfile.seek(pos); + int N = len; + while (N > 0) { + int n = Math.min(BUF_SIZE, N); + zfile.readFully(buf, off, n); + off += n; + N -= n; + } + return len; + } + } + + private final int readAt(byte[] buf, int off, int len, long pos) + throws IOException + { + synchronized (zfile) { + zfile.seek(pos); + return zfile.read(buf, off, len); + } + } + + + private static class End { + int centot; // 4 bytes + long cenlen; // 4 bytes + long cenoff; // 4 bytes + long endpos; // 4 bytes + } + + /* + * Searches for end of central directory (END) header. The contents of + * the END header will be read and placed in endbuf. Returns the file + * position of the END header, otherwise returns -1 if the END header + * was not found or an error occurred. + */ + private End findEND() throws IOException { + long ziplen = zfile.length(); + if (ziplen <= 0) + zerror("zip file is empty"); + End end = new End(); + byte[] buf = new byte[READBLOCKSZ]; + long minHDR = (ziplen - END_MAXLEN) > 0 ? ziplen - END_MAXLEN : 0; + long minPos = minHDR - (buf.length - ENDHDR); + for (long pos = ziplen - buf.length; pos >= minPos; pos -= (buf.length - ENDHDR)) { + int off = 0; + if (pos < 0) { + // Pretend there are some NUL bytes before start of file + off = (int)-pos; + Arrays.fill(buf, 0, off, (byte)0); + } + int len = buf.length - off; + if (readFullyAt(buf, off, len, pos + off) != len ) { + zerror("zip END header not found"); + } + // Now scan the block backwards for END header signature + for (int i = buf.length - ENDHDR; i >= 0; i--) { + if (buf[i+0] == (byte)'P' && + buf[i+1] == (byte)'K' && + buf[i+2] == (byte)'\005' && + buf[i+3] == (byte)'\006') { + // Found ENDSIG header + byte[] endbuf = Arrays.copyOfRange(buf, i, i + ENDHDR); + end.centot = ENDTOT(endbuf); + end.cenlen = ENDSIZ(endbuf); + end.cenoff = ENDOFF(endbuf); + end.endpos = pos + i; + int comlen = ENDCOM(endbuf); + if (end.endpos + ENDHDR + comlen != ziplen) { + // ENDSIG matched, however the size of file comment in it does + // not match the real size. One "common" cause for this problem + // is some "extra" bytes are padded at the end of the zipfile. + // Let's do some extra verification, we don't care about the + // performance in this situation. + byte[] sbuf = new byte[4]; + long cenpos = end.endpos - end.cenlen; + long locpos = cenpos - end.cenoff; + if (cenpos < 0 || + locpos < 0 || + readFullyAt(sbuf, 0, sbuf.length, cenpos) != 4 || + GETSIG(sbuf) != CENSIG || + readFullyAt(sbuf, 0, sbuf.length, locpos) != 4 || + GETSIG(sbuf) != LOCSIG) { + continue; + } + } + if (comlen > 0) { // this zip file has comlen + comment = new byte[comlen]; + if (readFullyAt(comment, 0, comlen, end.endpos + ENDHDR) != comlen) { + zerror("zip comment read failed"); + } + } + // must check for a zip64 end record; it is always permitted to be present + try { + byte[] loc64 = new byte[ZIP64_LOCHDR]; + if (end.endpos < ZIP64_LOCHDR || + readFullyAt(loc64, 0, loc64.length, end.endpos - ZIP64_LOCHDR) + != loc64.length || GETSIG(loc64) != ZIP64_LOCSIG) { + return end; + } + long end64pos = ZIP64_LOCOFF(loc64); + byte[] end64buf = new byte[ZIP64_ENDHDR]; + if (readFullyAt(end64buf, 0, end64buf.length, end64pos) + != end64buf.length || GETSIG(end64buf) != ZIP64_ENDSIG) { + return end; + } + // end64 candidate found, + long cenlen64 = ZIP64_ENDSIZ(end64buf); + long cenoff64 = ZIP64_ENDOFF(end64buf); + long centot64 = ZIP64_ENDTOT(end64buf); + // double-check + if (cenlen64 != end.cenlen && end.cenlen != ZIP64_MAGICVAL || + cenoff64 != end.cenoff && end.cenoff != ZIP64_MAGICVAL || + centot64 != end.centot && end.centot != ZIP64_MAGICCOUNT) { + return end; + } + // to use the end64 values + end.cenlen = cenlen64; + end.cenoff = cenoff64; + end.centot = (int)centot64; // assume total < 2g + end.endpos = end64pos; + } catch (IOException x) {} // no zip64 loc/end + return end; + } + } + } + throw new ZipException("zip END header not found"); + } + + // Reads zip file central directory. + private void initCEN(int knownTotal) throws IOException { + // Prefer locals for better performance during startup + byte[] cen; + if (knownTotal == -1) { + End end = findEND(); + if (end.endpos == 0) { + locpos = 0; + total = 0; + entries = new int[0]; + this.cen = null; + return; // only END header present + } + if (end.cenlen > end.endpos) + zerror("invalid END header (bad central directory size)"); + long cenpos = end.endpos - end.cenlen; // position of CEN table + // Get position of first local file (LOC) header, taking into + // account that there may be a stub prefixed to the zip file. + locpos = cenpos - end.cenoff; + if (locpos < 0) { + zerror("invalid END header (bad central directory offset)"); + } + // read in the CEN and END + cen = this.cen = new byte[(int)(end.cenlen + ENDHDR)]; + if (readFullyAt(cen, 0, cen.length, cenpos) != end.cenlen + ENDHDR) { + zerror("read CEN tables failed"); + } + this.total = end.centot; + } else { + cen = this.cen; + this.total = knownTotal; + } + // hash table for entries + int entriesLength = this.total * 3; + entries = new int[entriesLength]; + + int tablelen = ((total/2) | 1); // Odd -> fewer collisions + this.tablelen = tablelen; + + int[] table = new int[tablelen]; + this.table = table; + + Arrays.fill(table, ZIP_ENDCHAIN); + + // list for all meta entries + ArrayList<Integer> signatureNames = null; + // Set of all version numbers seen in META-INF/versions/ + Set<Integer> metaVersionsSet = null; + + // Iterate through the entries in the central directory + int idx = 0; // Index into the entries array + int pos = 0; + int entryPos = CENHDR; + int limit = cen.length - ENDHDR; + manifestNum = 0; + // Android-added: duplicate entries are not allowed. See CVE-2013-4787 and b/8219321 + Set<String> entriesNames = new HashSet<>(); + while (entryPos <= limit) { + if (idx >= entriesLength) { + // This will only happen if the zip file has an incorrect + // ENDTOT field, which usually means it contains more than + // 65535 entries. + initCEN(countCENHeaders(cen, limit)); + return; + } + + // Checks the entry and adds values to entries[idx ... idx+2] + int nlen = checkAndAddEntry(pos, idx); + + // BEGIN Android-added: duplicate entries are not allowed. See CVE-2013-4787 + // and b/8219321. + // zipCoderForPos takes USE_UTF8 flag into account. + ZipCoder zcp = zipCoderForPos(entryPos); + String name = zcp.toString(cen, pos + CENHDR, nlen); + if (!entriesNames.add(name)) { + zerror("Duplicate entry name: " + name); + } + // END Android-added: duplicate entries are not allowed. See CVE-2013-4787 + // and b/8219321 + // BEGIN Android-added: don't allow NUL in entry names. We can handle it in Java fine, + // but it is of questionable utility as a valid pathname can't contain NUL. + for (int nameIdx = 0; nameIdx < nlen; ++nameIdx) { + byte b = cen[pos + CENHDR + nameIdx]; + + if (b == 0) { + zerror("Filename contains NUL byte: " + name); + } + } + // END Android-added: don't allow NUL in entry names. + // BEGIN Android-changed: validation of zip entry names. + if (key.isZipFilePathValidatorEnabled && !ZipPathValidator.isClear()) { + ZipPathValidator.getInstance().onZipEntryAccess(name); + } + // END Android-changed: validation of zip entry names. + idx += 3; + + // Adds name to metanames. + if (isMetaName(cen, entryPos, nlen)) { + // nlen is at least META_INF_LENGTH + if (isManifestName(entryPos + META_INF_LEN, nlen - META_INF_LEN)) { + manifestPos = pos; + manifestNum++; + } else { + if (isSignatureRelated(entryPos, nlen)) { + if (signatureNames == null) + signatureNames = new ArrayList<>(4); + signatureNames.add(pos); + } + + // If this is a versioned entry, parse the version + // and store it for later. This optimizes lookup + // performance in multi-release jar files + int version = getMetaVersion(entryPos + META_INF_LEN, nlen - META_INF_LEN); + if (version > 0) { + if (metaVersionsSet == null) + metaVersionsSet = new TreeSet<>(); + metaVersionsSet.add(version); + } + } + } + // skip to the start of the next entry + pos = nextEntryPos(pos, entryPos, nlen); + entryPos = pos + CENHDR; + } + + // Adjust the total entries + this.total = idx / 3; + + if (signatureNames != null) { + int len = signatureNames.size(); + signatureMetaNames = new int[len]; + for (int j = 0; j < len; j++) { + signatureMetaNames[j] = signatureNames.get(j); + } + } + if (metaVersionsSet != null) { + metaVersions = new int[metaVersionsSet.size()]; + int c = 0; + for (Integer version : metaVersionsSet) { + metaVersions[c++] = version; + } + } else { + metaVersions = EMPTY_META_VERSIONS; + } + if (pos + ENDHDR != cen.length) { + zerror("invalid CEN header (bad header size)"); + } + } + + private int nextEntryPos(int pos, int entryPos, int nlen) { + return entryPos + nlen + CENCOM(cen, pos) + CENEXT(cen, pos); + } + + private static void zerror(String msg) throws ZipException { + throw new ZipException(msg); + } + + /* + * Returns the {@code pos} of the zip cen entry corresponding to the + * specified entry name, or -1 if not found. + */ + private int getEntryPos(String name, boolean addSlash) { + if (total == 0) { + return -1; + } + + int hsh = ZipCoder.hash(name); + int idx = table[(hsh & 0x7fffffff) % tablelen]; + + // Search down the target hash chain for a entry whose + // 32 bit hash matches the hashed name. + while (idx != ZIP_ENDCHAIN) { + if (getEntryHash(idx) == hsh) { + // The CEN name must match the specfied one + int pos = getEntryPos(idx); + + try { + ZipCoder zc = zipCoderForPos(pos); + String entry = zc.toString(cen, pos + CENHDR, CENNAM(cen, pos)); + + // If addSlash is true we'll test for name+/ in addition to + // name, unless name is the empty string or already ends with a + // slash + int entryLen = entry.length(); + int nameLen = name.length(); + if ((entryLen == nameLen && entry.equals(name)) || + (addSlash && + nameLen + 1 == entryLen && + entry.startsWith(name) && + entry.charAt(entryLen - 1) == '/')) { + return pos; + } + } catch (IllegalArgumentException iae) { + // Ignore + } + } + idx = getEntryNext(idx); + } + return -1; + } + + private ZipCoder zipCoderForPos(int pos) { + if (zc.isUTF8()) { + return zc; + } + if ((CENFLG(cen, pos) & USE_UTF8) != 0) { + return ZipCoder.UTF8; + } + return zc; + } + + /** + * Returns true if the bytes represent a non-directory name + * beginning with "META-INF/", disregarding ASCII case. + */ + private static boolean isMetaName(byte[] name, int off, int len) { + // Use the "oldest ASCII trick in the book": + // ch | 0x20 == Character.toLowerCase(ch) + return len > META_INF_LEN // "META-INF/".length() + && name[off + len - 1] != '/' // non-directory + && (name[off++] | 0x20) == 'm' + && (name[off++] | 0x20) == 'e' + && (name[off++] | 0x20) == 't' + && (name[off++] | 0x20) == 'a' + && (name[off++] ) == '-' + && (name[off++] | 0x20) == 'i' + && (name[off++] | 0x20) == 'n' + && (name[off++] | 0x20) == 'f' + && (name[off] ) == '/'; + } + + /* + * Check if the bytes represents a name equals to MANIFEST.MF + */ + private boolean isManifestName(int off, int len) { + byte[] name = cen; + return (len == 11 // "MANIFEST.MF".length() + && (name[off++] | 0x20) == 'm' + && (name[off++] | 0x20) == 'a' + && (name[off++] | 0x20) == 'n' + && (name[off++] | 0x20) == 'i' + && (name[off++] | 0x20) == 'f' + && (name[off++] | 0x20) == 'e' + && (name[off++] | 0x20) == 's' + && (name[off++] | 0x20) == 't' + && (name[off++] ) == '.' + && (name[off++] | 0x20) == 'm' + && (name[off] | 0x20) == 'f'); + } + + private boolean isSignatureRelated(int off, int len) { + // Only called when isMetaName(name, off, len) is true, which means + // len is at least META_INF_LENGTH + // assert isMetaName(name, off, len) + boolean signatureRelated = false; + byte[] name = cen; + if (name[off + len - 3] == '.') { + // Check if entry ends with .EC and .SF + int b1 = name[off + len - 2] | 0x20; + int b2 = name[off + len - 1] | 0x20; + if ((b1 == 'e' && b2 == 'c') || (b1 == 's' && b2 == 'f')) { + signatureRelated = true; + } + } else if (name[off + len - 4] == '.') { + // Check if entry ends with .DSA and .RSA + int b1 = name[off + len - 3] | 0x20; + int b2 = name[off + len - 2] | 0x20; + int b3 = name[off + len - 1] | 0x20; + if ((b1 == 'r' || b1 == 'd') && b2 == 's' && b3 == 'a') { + signatureRelated = true; + } + } + // Above logic must match SignatureFileVerifier.isBlockOrSF + assert(signatureRelated == SignatureFileVerifier + // Android-changed: use StandardCharsets. + // .isBlockOrSF(new String(name, off, len, UTF_8.INSTANCE) + .isBlockOrSF(new String(name, off, len, StandardCharsets.UTF_8) + .toUpperCase(Locale.ENGLISH))); + return signatureRelated; + } + + /* + * If the bytes represents a non-directory name beginning + * with "versions/", continuing with a positive integer, + * followed by a '/', then return that integer value. + * Otherwise, return 0 + */ + private int getMetaVersion(int off, int len) { + byte[] name = cen; + int nend = off + len; + if (!(len > 10 // "versions//".length() + && name[off + len - 1] != '/' // non-directory + && (name[off++] | 0x20) == 'v' + && (name[off++] | 0x20) == 'e' + && (name[off++] | 0x20) == 'r' + && (name[off++] | 0x20) == 's' + && (name[off++] | 0x20) == 'i' + && (name[off++] | 0x20) == 'o' + && (name[off++] | 0x20) == 'n' + && (name[off++] | 0x20) == 's' + && (name[off++] ) == '/')) { + return 0; + } + int version = 0; + while (off < nend) { + final byte c = name[off++]; + if (c == '/') { + return version; + } + if (c < '0' || c > '9') { + return 0; + } + version = version * 10 + c - '0'; + // Check for overflow and leading zeros + if (version <= 0) { + return 0; + } + } + return 0; + } + + /** + * Returns the number of CEN headers in a central directory. + * Will not throw, even if the zip file is corrupt. + * + * @param cen copy of the bytes in a zip file's central directory + * @param size number of bytes in central directory + */ + private static int countCENHeaders(byte[] cen, int size) { + int count = 0; + for (int p = 0; + p + CENHDR <= size; + p += CENHDR + CENNAM(cen, p) + CENEXT(cen, p) + CENCOM(cen, p)) + count++; + return count; + } + } +}
diff --git a/android-35/java/util/zip/ZipInputStream.java b/android-35/java/util/zip/ZipInputStream.java new file mode 100644 index 0000000..617e7ff --- /dev/null +++ b/android-35/java/util/zip/ZipInputStream.java
@@ -0,0 +1,464 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +import java.io.InputStream; +import java.io.IOException; +import java.io.EOFException; +import java.io.PushbackInputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import static java.util.zip.ZipConstants64.*; +import static java.util.zip.ZipUtils.*; + +import dalvik.system.ZipPathValidator; + +/** + * This class implements an input stream filter for reading files in the + * ZIP file format. Includes support for both compressed and uncompressed + * entries. + * + * @author David Connelly + * @since 1.1 + */ +public class ZipInputStream extends InflaterInputStream implements ZipConstants { + private ZipEntry entry; + private int flag; + private CRC32 crc = new CRC32(); + private long remaining; + private byte[] tmpbuf = new byte[512]; + + private static final int STORED = ZipEntry.STORED; + private static final int DEFLATED = ZipEntry.DEFLATED; + + private boolean closed = false; + // this flag is set to true after EOF has reached for + // one entry + private boolean entryEOF = false; + + private ZipCoder zc; + + /** + * Check to make sure that this stream has not been closed + */ + private void ensureOpen() throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + } + + /** + * Creates a new ZIP input stream. + * + * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to + * decode the entry names. + * + * @param in the actual input stream + */ + public ZipInputStream(InputStream in) { + // Android-changed: use StandardCharsets. + // this(in, UTF_8.INSTANCE); + this(in, StandardCharsets.UTF_8); + } + + /** + * Creates a new ZIP input stream. + * + * @param in the actual input stream + * + * @param charset + * The {@linkplain java.nio.charset.Charset charset} to be + * used to decode the ZIP entry name (ignored if the + * <a href="package-summary.html#lang_encoding"> language + * encoding bit</a> of the ZIP entry's general purpose bit + * flag is set). + * + * @since 1.7 + */ + public ZipInputStream(InputStream in, Charset charset) { + super(new PushbackInputStream(in, 512), new Inflater(true), 512); + // Android-changed: Unconditionally close external inflaters (b/26462400) + // usesDefaultInflater = true; + if(in == null) { + throw new NullPointerException("in is null"); + } + if (charset == null) + throw new NullPointerException("charset is null"); + this.zc = ZipCoder.get(charset); + } + + // Android-changed: Additional ZipException throw scenario with ZipPathValidator. + /** + * Reads the next ZIP file entry and positions the stream at the + * beginning of the entry data. + * + * <p>If the app targets Android U or above, zip file entry names containing + * ".." or starting with "/" passed here will throw a {@link ZipException}. + * For more details, see {@link dalvik.system.ZipPathValidator}. + * + * @return the next ZIP file entry, or null if there are no more entries + * @throws ZipException if (1) a ZIP file error has occurred or + * (2) <code>targetSdkVersion >= BUILD.VERSION_CODES.UPSIDE_DOWN_CAKE</code> + * and (the <code>name</code> argument contains ".." or starts with "/"). + * @throws IOException if an I/O error has occurred + */ + public ZipEntry getNextEntry() throws IOException { + ensureOpen(); + if (entry != null) { + closeEntry(); + } + crc.reset(); + inf.reset(); + if ((entry = readLOC()) == null) { + return null; + } + // Android-changed: Return more accurate value from available(). + // Initialize the remaining field with the number of bytes that can be read from the entry + // for both uncompressed and compressed entries so that it can be used to provide a more + // accurate return value for available(). + // if (entry.method == STORED) { + if (entry.method == STORED || entry.method == DEFLATED) { + remaining = entry.size; + } + entryEOF = false; + return entry; + } + + /** + * Closes the current ZIP entry and positions the stream for reading the + * next entry. + * @throws ZipException if a ZIP file error has occurred + * @throws IOException if an I/O error has occurred + */ + public void closeEntry() throws IOException { + ensureOpen(); + while (read(tmpbuf, 0, tmpbuf.length) != -1) ; + entryEOF = true; + } + + /** + * Returns 0 after EOF has reached for the current entry data, + * otherwise always return 1. + * <p> + * Programs should not count on this method to return the actual number + * of bytes that could be read without blocking. + * + * @return 1 before EOF and 0 after EOF has reached for current entry. + * @throws IOException if an I/O error occurs. + * + */ + public int available() throws IOException { + ensureOpen(); + // Android-changed: Return more accurate value from available(). + // Tracks the remaining bytes in order to return a more accurate value for the available + // bytes. Given an entry of size N both Android and upstream will return 1 until N bytes + // have been read at which point Android will return 0 and upstream will return 1. + // Upstream will only return 0 after an attempt to read a byte fails because the EOF has + // been reached. See http://b/111439440 for more details. + // if (entryEOF) { + if (entryEOF || (entry != null && remaining == 0)) { + return 0; + } else { + return 1; + } + } + + /** + * Reads from the current ZIP entry into an array of bytes. + * If {@code len} is not zero, the method + * blocks until some input is available; otherwise, no + * bytes are read and {@code 0} is returned. + * @param b the buffer into which the data is read + * @param off the start offset in the destination array {@code b} + * @param len the maximum number of bytes read + * @return the actual number of bytes read, or -1 if the end of the + * entry is reached + * @throws NullPointerException if {@code b} is {@code null}. + * @throws IndexOutOfBoundsException if {@code off} is negative, + * {@code len} is negative, or {@code len} is greater than + * {@code b.length - off} + * @throws ZipException if a ZIP file error has occurred + * @throws IOException if an I/O error has occurred + */ + public int read(byte[] b, int off, int len) throws IOException { + ensureOpen(); + if (off < 0 || len < 0 || off > b.length - len) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + + if (entry == null) { + return -1; + } + switch (entry.method) { + case DEFLATED: + len = super.read(b, off, len); + if (len == -1) { + readEnd(entry); + entryEOF = true; + entry = null; + } else { + crc.update(b, off, len); + // Android-added: Return more accurate value from available(). + // Update the remaining field so it is an accurate count of the number of bytes + // remaining in this stream, after deflation. + remaining -= len; + } + return len; + case STORED: + if (remaining <= 0) { + entryEOF = true; + entry = null; + return -1; + } + if (len > remaining) { + len = (int)remaining; + } + len = in.read(b, off, len); + if (len == -1) { + throw new ZipException("unexpected EOF"); + } + crc.update(b, off, len); + remaining -= len; + if (remaining == 0 && entry.crc != crc.getValue()) { + throw new ZipException( + "invalid entry CRC (expected 0x" + Long.toHexString(entry.crc) + + " but got 0x" + Long.toHexString(crc.getValue()) + ")"); + } + return len; + default: + throw new ZipException("invalid compression method"); + } + } + + /** + * Skips specified number of bytes in the current ZIP entry. + * @param n the number of bytes to skip + * @return the actual number of bytes skipped + * @throws ZipException if a ZIP file error has occurred + * @throws IOException if an I/O error has occurred + * @throws IllegalArgumentException if {@code n < 0} + */ + public long skip(long n) throws IOException { + if (n < 0) { + throw new IllegalArgumentException("negative skip length"); + } + ensureOpen(); + int max = (int)Math.min(n, Integer.MAX_VALUE); + int total = 0; + while (total < max) { + int len = max - total; + if (len > tmpbuf.length) { + len = tmpbuf.length; + } + len = read(tmpbuf, 0, len); + if (len == -1) { + entryEOF = true; + break; + } + total += len; + } + return total; + } + + /** + * Closes this input stream and releases any system resources associated + * with the stream. + * @throws IOException if an I/O error has occurred + */ + public void close() throws IOException { + if (!closed) { + super.close(); + closed = true; + } + } + + private byte[] b = new byte[256]; + + /* + * Reads local file (LOC) header for next entry. + */ + private ZipEntry readLOC() throws IOException { + try { + readFully(tmpbuf, 0, LOCHDR); + } catch (EOFException e) { + return null; + } + if (get32(tmpbuf, 0) != LOCSIG) { + return null; + } + // get flag first, we need check USE_UTF8. + flag = get16(tmpbuf, LOCFLG); + // get the entry name and create the ZipEntry first + int len = get16(tmpbuf, LOCNAM); + int blen = b.length; + if (len > blen) { + do { + blen = blen * 2; + } while (len > blen); + b = new byte[blen]; + } + readFully(b, 0, len); + // Force to use UTF-8 if the USE_UTF8 bit is ON + ZipEntry e = createZipEntry(((flag & USE_UTF8) != 0) + ? ZipCoder.toStringUTF8(b, len) + : zc.toString(b, len)); + // now get the remaining fields for the entry + if ((flag & 1) == 1) { + throw new ZipException("encrypted ZIP entry not supported"); + } + // BEGIN Android-added: Use ZipPathValidator to validate zip entry name. + ZipPathValidator.getInstance().onZipEntryAccess(e.name); + // END Android-added: Use ZipPathValidator to validate zip entry name. + e.method = get16(tmpbuf, LOCHOW); + e.xdostime = get32(tmpbuf, LOCTIM); + if ((flag & 8) == 8) { + // Android-changed: Remove the requirement that only DEFLATED entries + // can have data descriptors. This is not required by the ZIP spec and + // is inconsistent with the behaviour of ZipFile and versions of Android + // prior to Android N. + // + // /* "Data Descriptor" present */ + // if (e.method != DEFLATED) { + // throw new ZipException( + // "only DEFLATED entries can have EXT descriptor"); + // } + } else { + e.crc = get32(tmpbuf, LOCCRC); + e.csize = get32(tmpbuf, LOCSIZ); + e.size = get32(tmpbuf, LOCLEN); + } + len = get16(tmpbuf, LOCEXT); + if (len > 0) { + byte[] extra = new byte[len]; + readFully(extra, 0, len); + e.setExtra0(extra, + e.csize == ZIP64_MAGICVAL || e.size == ZIP64_MAGICVAL, true); + } + return e; + } + + /** + * Creates a new {@code ZipEntry} object for the specified + * entry name. + * + * @param name the ZIP file entry name + * @return the ZipEntry just created + */ + protected ZipEntry createZipEntry(String name) { + return new ZipEntry(name); + } + + /** + * Reads end of deflated entry as well as EXT descriptor if present. + * + * Local headers for DEFLATED entries may optionally be followed by a + * data descriptor, and that data descriptor may optionally contain a + * leading signature (EXTSIG). + * + * From the zip spec http://www.pkware.com/documents/casestudies/APPNOTE.TXT + * + * """Although not originally assigned a signature, the value 0x08074b50 + * has commonly been adopted as a signature value for the data descriptor + * record. Implementers should be aware that ZIP files may be + * encountered with or without this signature marking data descriptors + * and should account for either case when reading ZIP files to ensure + * compatibility.""" + */ + private void readEnd(ZipEntry e) throws IOException { + int n = inf.getRemaining(); + if (n > 0) { + ((PushbackInputStream)in).unread(buf, len - n, n); + } + if ((flag & 8) == 8) { + /* "Data Descriptor" present */ + if (inf.getBytesWritten() > ZIP64_MAGICVAL || + inf.getBytesRead() > ZIP64_MAGICVAL) { + // ZIP64 format + readFully(tmpbuf, 0, ZIP64_EXTHDR); + long sig = get32(tmpbuf, 0); + if (sig != EXTSIG) { // no EXTSIG present + e.crc = sig; + e.csize = get64(tmpbuf, ZIP64_EXTSIZ - ZIP64_EXTCRC); + e.size = get64(tmpbuf, ZIP64_EXTLEN - ZIP64_EXTCRC); + ((PushbackInputStream)in).unread( + tmpbuf, ZIP64_EXTHDR - ZIP64_EXTCRC, ZIP64_EXTCRC); + } else { + e.crc = get32(tmpbuf, ZIP64_EXTCRC); + e.csize = get64(tmpbuf, ZIP64_EXTSIZ); + e.size = get64(tmpbuf, ZIP64_EXTLEN); + } + } else { + readFully(tmpbuf, 0, EXTHDR); + long sig = get32(tmpbuf, 0); + if (sig != EXTSIG) { // no EXTSIG present + e.crc = sig; + e.csize = get32(tmpbuf, EXTSIZ - EXTCRC); + e.size = get32(tmpbuf, EXTLEN - EXTCRC); + ((PushbackInputStream)in).unread( + tmpbuf, EXTHDR - EXTCRC, EXTCRC); + } else { + e.crc = get32(tmpbuf, EXTCRC); + e.csize = get32(tmpbuf, EXTSIZ); + e.size = get32(tmpbuf, EXTLEN); + } + } + } + if (e.size != inf.getBytesWritten()) { + throw new ZipException( + "invalid entry size (expected " + e.size + + " but got " + inf.getBytesWritten() + " bytes)"); + } + if (e.csize != inf.getBytesRead()) { + throw new ZipException( + "invalid entry compressed size (expected " + e.csize + + " but got " + inf.getBytesRead() + " bytes)"); + } + if (e.crc != crc.getValue()) { + throw new ZipException( + "invalid entry CRC (expected 0x" + Long.toHexString(e.crc) + + " but got 0x" + Long.toHexString(crc.getValue()) + ")"); + } + } + + /* + * Reads bytes, blocking until all bytes are read. + */ + private void readFully(byte[] b, int off, int len) throws IOException { + while (len > 0) { + int n = in.read(b, off, len); + if (n == -1) { + throw new EOFException(); + } + off += n; + len -= n; + } + } + +}
diff --git a/android-35/java/util/zip/ZipOutputStream.java b/android-35/java/util/zip/ZipOutputStream.java new file mode 100644 index 0000000..6b6a68c --- /dev/null +++ b/android-35/java/util/zip/ZipOutputStream.java
@@ -0,0 +1,836 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * Copyright (c) 1996, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +import java.io.OutputStream; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Vector; +import java.util.HashSet; +import static java.util.zip.ZipConstants64.*; +import static java.util.zip.ZipUtils.*; +import sun.security.action.GetPropertyAction; + +/** + * This class implements an output stream filter for writing files in the + * ZIP file format. Includes support for both compressed and uncompressed + * entries. + * + * @author David Connelly + * @since 1.1 + */ +public class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { + + /** + * Whether to use ZIP64 for zip files with more than 64k entries. + * Until ZIP64 support in zip implementations is ubiquitous, this + * system property allows the creation of zip files which can be + * read by legacy zip implementations which tolerate "incorrect" + * total entry count fields, such as the ones in jdk6, and even + * some in jdk7. + */ + // Android-changed: Always allow use of Zip64. + private static final boolean inhibitZip64 = false; + // Boolean.parseBoolean( + // GetPropertyAction.privilegedGetProperty("jdk.util.zip.inhibitZip64")); + + private static class XEntry { + final ZipEntry entry; + final long offset; + public XEntry(ZipEntry entry, long offset) { + this.entry = entry; + this.offset = offset; + } + } + + private XEntry current; + private Vector<XEntry> xentries = new Vector<>(); + private HashSet<String> names = new HashSet<>(); + private CRC32 crc = new CRC32(); + private long written = 0; + private long locoff = 0; + private byte[] comment; + private int method = DEFLATED; + private boolean finished; + + private boolean closed = false; + + private final ZipCoder zc; + + private static int version(ZipEntry e) throws ZipException { + return switch (e.method) { + case DEFLATED -> 20; + case STORED -> 10; + default -> throw new ZipException("unsupported compression method"); + }; + } + + /** + * Checks to make sure that this stream has not been closed. + */ + private void ensureOpen() throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + } + + /** + * Compression method for uncompressed (STORED) entries. + */ + public static final int STORED = ZipEntry.STORED; + + /** + * Compression method for compressed (DEFLATED) entries. + */ + public static final int DEFLATED = ZipEntry.DEFLATED; + + /** + * Creates a new ZIP output stream. + * + * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used + * to encode the entry names and comments. + * + * @param out the actual output stream + */ + public ZipOutputStream(OutputStream out) { + this(out, StandardCharsets.UTF_8); + } + + /** + * Creates a new ZIP output stream. + * + * @param out the actual output stream + * + * @param charset the {@linkplain java.nio.charset.Charset charset} + * to be used to encode the entry names and comments + * + * @since 1.7 + */ + public ZipOutputStream(OutputStream out, Charset charset) { + super(out, out != null ? new Deflater(Deflater.DEFAULT_COMPRESSION, true) : null); + if (charset == null) + throw new NullPointerException("charset is null"); + this.zc = ZipCoder.get(charset); + usesDefaultDeflater = true; + } + + /** + * Sets the ZIP file comment. + * @param comment the comment string + * @throws IllegalArgumentException if the length of the specified + * ZIP file comment is greater than 0xFFFF bytes + */ + public void setComment(String comment) { + byte[] bytes = null; + if (comment != null) { + bytes = zc.getBytes(comment); + if (bytes.length > 0xffff) { + throw new IllegalArgumentException("ZIP file comment too long"); + } + } + this.comment = bytes; + } + + /** + * Sets the default compression method for subsequent entries. This + * default will be used whenever the compression method is not specified + * for an individual ZIP file entry, and is initially set to DEFLATED. + * @param method the default compression method + * @throws IllegalArgumentException if the specified compression method + * is invalid + */ + public void setMethod(int method) { + if (method != DEFLATED && method != STORED) { + throw new IllegalArgumentException("invalid compression method"); + } + this.method = method; + } + + /** + * Sets the compression level for subsequent entries which are DEFLATED. + * The default setting is DEFAULT_COMPRESSION. + * @param level the compression level (0-9) + * @throws IllegalArgumentException if the compression level is invalid + */ + public void setLevel(int level) { + def.setLevel(level); + } + + /** + * Begins writing a new ZIP file entry and positions the stream to the + * start of the entry data. Closes the current entry if still active. + * <p> + * The default compression method will be used if no compression method + * was specified for the entry. When writing a compressed (DEFLATED) + * entry, and the compressed size has not been explicitly set with the + * {@link ZipEntry#setCompressedSize(long)} method, then the compressed + * size will be set to the actual compressed size after deflation. + * <p> + * The current time will be used if the entry has no set modification time. + * + * @param e the ZIP entry to be written + * @throws ZipException if a ZIP format error has occurred + * @throws IOException if an I/O error has occurred + */ + public void putNextEntry(ZipEntry e) throws IOException { + ensureOpen(); + if (current != null) { + closeEntry(); // close previous entry + } + if (e.xdostime == -1) { + // by default, do NOT use extended timestamps in extra + // data, for now. + e.setTime(System.currentTimeMillis()); + } + if (e.method == -1) { + e.method = method; // use default method + } + // store size, compressed size, and crc-32 in LOC header + e.flag = 0; + switch (e.method) { + case DEFLATED: + // If not set, store size, compressed size, and crc-32 in data + // descriptor immediately following the compressed entry data. + // Ignore the compressed size of a ZipEntry if it was implcitely set + // while reading that ZipEntry from a ZipFile or ZipInputStream because + // we can't know the compression level of the source zip file/stream. + if (e.size == -1 || e.csize == -1 || e.crc == -1 || !e.csizeSet) { + e.flag = 8; + } + break; + case STORED: + // compressed size, uncompressed size, and crc-32 must all be + // set for entries using STORED compression method + if (e.size == -1) { + e.size = e.csize; + } else if (e.csize == -1) { + e.csize = e.size; + } else if (e.size != e.csize) { + throw new ZipException( + "STORED entry where compressed != uncompressed size"); + } + if (e.size == -1 || e.crc == -1) { + throw new ZipException( + "STORED entry missing size, compressed size, or crc-32"); + } + break; + default: + throw new ZipException("unsupported compression method"); + } + if (! names.add(e.name)) { + throw new ZipException("duplicate entry: " + e.name); + } + if (zc.isUTF8()) + e.flag |= USE_UTF8; + current = new XEntry(e, written); + xentries.add(current); + writeLOC(current); + } + + /** + * Closes the current ZIP entry and positions the stream for writing + * the next entry. + * @throws ZipException if a ZIP format error has occurred + * @throws IOException if an I/O error has occurred + */ + public void closeEntry() throws IOException { + ensureOpen(); + if (current != null) { + try { + ZipEntry e = current.entry; + switch (e.method) { + case DEFLATED -> { + def.finish(); + while (!def.finished()) { + deflate(); + } + if ((e.flag & 8) == 0) { + // verify size, compressed size, and crc-32 settings + if (e.size != def.getBytesRead()) { + throw new ZipException( + "invalid entry size (expected " + e.size + + " but got " + def.getBytesRead() + " bytes)"); + } + if (e.csize != def.getBytesWritten()) { + throw new ZipException( + "invalid entry compressed size (expected " + + e.csize + " but got " + def.getBytesWritten() + " bytes)"); + } + if (e.crc != crc.getValue()) { + throw new ZipException( + "invalid entry CRC-32 (expected 0x" + + Long.toHexString(e.crc) + " but got 0x" + + Long.toHexString(crc.getValue()) + ")"); + } + } else { + e.size = def.getBytesRead(); + e.csize = def.getBytesWritten(); + e.crc = crc.getValue(); + writeEXT(e); + } + def.reset(); + written += e.csize; + } + case STORED -> { + // we already know that both e.size and e.csize are the same + if (e.size != written - locoff) { + throw new ZipException( + "invalid entry size (expected " + e.size + + " but got " + (written - locoff) + " bytes)"); + } + if (e.crc != crc.getValue()) { + throw new ZipException( + "invalid entry crc-32 (expected 0x" + + Long.toHexString(e.crc) + " but got 0x" + + Long.toHexString(crc.getValue()) + ")"); + } + } + default -> throw new ZipException("invalid compression method"); + } + crc.reset(); + current = null; + } catch (IOException e) { + if (def.shouldFinish() && usesDefaultDeflater && !(e instanceof ZipException)) + def.end(); + throw e; + } + } + } + + /** + * Writes an array of bytes to the current ZIP entry data. This method + * will block until all the bytes are written. + * @param b the data to be written + * @param off the start offset in the data + * @param len the number of bytes that are written + * @throws ZipException if a ZIP file error has occurred + * @throws IOException if an I/O error has occurred + */ + public synchronized void write(byte[] b, int off, int len) + throws IOException + { + ensureOpen(); + if (off < 0 || len < 0 || off > b.length - len) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + + if (current == null) { + throw new ZipException("no current ZIP entry"); + } + ZipEntry entry = current.entry; + switch (entry.method) { + case DEFLATED -> super.write(b, off, len); + case STORED -> { + written += len; + if (written - locoff > entry.size) { + throw new ZipException( + "attempt to write past end of STORED entry"); + } + out.write(b, off, len); + } + default -> throw new ZipException("invalid compression method"); + } + crc.update(b, off, len); + } + + /** + * Finishes writing the contents of the ZIP output stream without closing + * the underlying stream. Use this method when applying multiple filters + * in succession to the same output stream. + * @throws ZipException if a ZIP file error has occurred + * @throws IOException if an I/O exception has occurred + */ + public void finish() throws IOException { + ensureOpen(); + if (finished) { + return; + } + if (current != null) { + closeEntry(); + } + // write central directory + long off = written; + for (XEntry xentry : xentries) + writeCEN(xentry); + writeEND(off, written - off); + finished = true; + } + + /** + * Closes the ZIP output stream as well as the stream being filtered. + * @throws ZipException if a ZIP file error has occurred + * @throws IOException if an I/O error has occurred + */ + public void close() throws IOException { + if (!closed) { + super.close(); + closed = true; + } + } + + /* + * Writes local file (LOC) header for specified entry. + */ + private void writeLOC(XEntry xentry) throws IOException { + ZipEntry e = xentry.entry; + int flag = e.flag; + boolean hasZip64 = false; + int elen = getExtraLen(e.extra); + + writeInt(LOCSIG); // LOC header signature + if ((flag & 8) == 8) { + writeShort(version(e)); // version needed to extract + writeShort(flag); // general purpose bit flag + writeShort(e.method); // compression method + writeInt(e.xdostime); // last modification time + // store size, uncompressed size, and crc-32 in data descriptor + // immediately following compressed entry data + writeInt(0); + writeInt(0); + writeInt(0); + } else { + if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) { + hasZip64 = true; + writeShort(45); // ver 4.5 for zip64 + } else { + writeShort(version(e)); // version needed to extract + } + writeShort(flag); // general purpose bit flag + writeShort(e.method); // compression method + writeInt(e.xdostime); // last modification time + writeInt(e.crc); // crc-32 + if (hasZip64) { + writeInt(ZIP64_MAGICVAL); + writeInt(ZIP64_MAGICVAL); + elen += 20; //headid(2) + size(2) + size(8) + csize(8) + } else { + writeInt(e.csize); // compressed size + writeInt(e.size); // uncompressed size + } + } + byte[] nameBytes = zc.getBytes(e.name); + writeShort(nameBytes.length); + + int elenEXTT = 0; // info-zip extended timestamp + int flagEXTT = 0; + long umtime = -1; + long uatime = -1; + long uctime = -1; + if (e.mtime != null) { + elenEXTT += 4; + flagEXTT |= EXTT_FLAG_LMT; + umtime = fileTimeToUnixTime(e.mtime); + } + if (e.atime != null) { + elenEXTT += 4; + flagEXTT |= EXTT_FLAG_LAT; + uatime = fileTimeToUnixTime(e.atime); + } + if (e.ctime != null) { + elenEXTT += 4; + flagEXTT |= EXTT_FLAT_CT; + uctime = fileTimeToUnixTime(e.ctime); + } + if (flagEXTT != 0) { + // to use ntfs time if any m/a/ctime is beyond unixtime upper bound + if (umtime > UPPER_UNIXTIME_BOUND || + uatime > UPPER_UNIXTIME_BOUND || + uctime > UPPER_UNIXTIME_BOUND) { + elen += 36; // NTFS time, total 36 bytes + } else { + elen += (elenEXTT + 5); // headid(2) + size(2) + flag(1) + data + } + } + writeShort(elen); + writeBytes(nameBytes, 0, nameBytes.length); + if (hasZip64) { + writeShort(ZIP64_EXTID); + writeShort(16); + writeLong(e.size); + writeLong(e.csize); + } + if (flagEXTT != 0) { + if (umtime > UPPER_UNIXTIME_BOUND || + uatime > UPPER_UNIXTIME_BOUND || + uctime > UPPER_UNIXTIME_BOUND) { + writeShort(EXTID_NTFS); // id + writeShort(32); // data size + writeInt(0); // reserved + writeShort(0x0001); // NTFS attr tag + writeShort(24); + writeLong(e.mtime == null ? WINDOWS_TIME_NOT_AVAILABLE + : fileTimeToWinTime(e.mtime)); + writeLong(e.atime == null ? WINDOWS_TIME_NOT_AVAILABLE + : fileTimeToWinTime(e.atime)); + writeLong(e.ctime == null ? WINDOWS_TIME_NOT_AVAILABLE + : fileTimeToWinTime(e.ctime)); + } else { + writeShort(EXTID_EXTT); + writeShort(elenEXTT + 1); // flag + data + writeByte(flagEXTT); + if (e.mtime != null) + writeInt(umtime); + if (e.atime != null) + writeInt(uatime); + if (e.ctime != null) + writeInt(uctime); + } + } + writeExtra(e.extra); + locoff = written; + } + + /* + * Writes extra data descriptor (EXT) for specified entry. + */ + private void writeEXT(ZipEntry e) throws IOException { + writeInt(EXTSIG); // EXT header signature + writeInt(e.crc); // crc-32 + if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) { + writeLong(e.csize); + writeLong(e.size); + } else { + writeInt(e.csize); // compressed size + writeInt(e.size); // uncompressed size + } + } + + /** + * Adds information about compatibility of file attribute information + * to a version value. + */ + private int versionMadeBy(ZipEntry e, int version) { + return (e.extraAttributes < 0) ? version : + VERSION_MADE_BY_BASE_UNIX | (version & 0xff); + } + + /* + * Write central directory (CEN) header for specified entry. + * REMIND: add support for file attributes + */ + private void writeCEN(XEntry xentry) throws IOException { + ZipEntry e = xentry.entry; + int flag = e.flag; + int version = version(e); + long csize = e.csize; + long size = e.size; + long offset = xentry.offset; + int elenZIP64 = 0; + boolean hasZip64 = false; + + if (e.csize >= ZIP64_MAGICVAL) { + csize = ZIP64_MAGICVAL; + elenZIP64 += 8; // csize(8) + hasZip64 = true; + } + if (e.size >= ZIP64_MAGICVAL) { + size = ZIP64_MAGICVAL; // size(8) + elenZIP64 += 8; + hasZip64 = true; + } + if (xentry.offset >= ZIP64_MAGICVAL) { + offset = ZIP64_MAGICVAL; + elenZIP64 += 8; // offset(8) + hasZip64 = true; + } + writeInt(CENSIG); // CEN header signature + if (hasZip64) { + writeShort(versionMadeBy(e,45)); // ver 4.5 for zip64 + writeShort(45); + } else { + writeShort(versionMadeBy(e, version)); // version made by + writeShort(version); // version needed to extract + } + writeShort(flag); // general purpose bit flag + writeShort(e.method); // compression method + writeInt(e.xdostime); // last modification time + writeInt(e.crc); // crc-32 + writeInt(csize); // compressed size + writeInt(size); // uncompressed size + byte[] nameBytes = zc.getBytes(e.name); + writeShort(nameBytes.length); + + int elen = getExtraLen(e.extra); + if (hasZip64) { + elen += (elenZIP64 + 4);// + headid(2) + datasize(2) + } + // cen info-zip extended timestamp only outputs mtime + // but set the flag for a/ctime, if present in loc + int flagEXTT = 0; + long umtime = -1; + long uatime = -1; + long uctime = -1; + if (e.mtime != null) { + flagEXTT |= EXTT_FLAG_LMT; + umtime = fileTimeToUnixTime(e.mtime); + } + if (e.atime != null) { + flagEXTT |= EXTT_FLAG_LAT; + uatime = fileTimeToUnixTime(e.atime); + } + if (e.ctime != null) { + flagEXTT |= EXTT_FLAT_CT; + uctime = fileTimeToUnixTime(e.ctime); + } + if (flagEXTT != 0) { + // to use ntfs time if any m/a/ctime is beyond unixtime upper bound + if (umtime > UPPER_UNIXTIME_BOUND || + uatime > UPPER_UNIXTIME_BOUND || + uctime > UPPER_UNIXTIME_BOUND) { + elen += 36; // NTFS time total 36 bytes + } else { + elen += 5; // headid(2) + sz(2) + flag(1) + if (e.mtime != null) + elen += 4; // + mtime (4) + } + } + writeShort(elen); + byte[] commentBytes; + if (e.comment != null) { + commentBytes = zc.getBytes(e.comment); + writeShort(Math.min(commentBytes.length, 0xffff)); + } else { + commentBytes = null; + writeShort(0); + } + writeShort(0); // starting disk number + writeShort(0); // internal file attributes (unused) + // extra file attributes, used for storing posix permissions etc. + writeInt(e.extraAttributes > 0 ? e.extraAttributes << 16 : 0); + writeInt(offset); // relative offset of local header + writeBytes(nameBytes, 0, nameBytes.length); + + // take care of EXTID_ZIP64 and EXTID_EXTT + if (hasZip64) { + writeShort(ZIP64_EXTID);// Zip64 extra + writeShort(elenZIP64); + if (size == ZIP64_MAGICVAL) + writeLong(e.size); + if (csize == ZIP64_MAGICVAL) + writeLong(e.csize); + if (offset == ZIP64_MAGICVAL) + writeLong(xentry.offset); + } + if (flagEXTT != 0) { + if (umtime > UPPER_UNIXTIME_BOUND || + uatime > UPPER_UNIXTIME_BOUND || + uctime > UPPER_UNIXTIME_BOUND) { + writeShort(EXTID_NTFS); // id + writeShort(32); // data size + writeInt(0); // reserved + writeShort(0x0001); // NTFS attr tag + writeShort(24); + writeLong(e.mtime == null ? WINDOWS_TIME_NOT_AVAILABLE + : fileTimeToWinTime(e.mtime)); + writeLong(e.atime == null ? WINDOWS_TIME_NOT_AVAILABLE + : fileTimeToWinTime(e.atime)); + writeLong(e.ctime == null ? WINDOWS_TIME_NOT_AVAILABLE + : fileTimeToWinTime(e.ctime)); + } else { + writeShort(EXTID_EXTT); + if (e.mtime != null) { + writeShort(5); // flag + mtime + writeByte(flagEXTT); + writeInt(umtime); + } else { + writeShort(1); // flag only + writeByte(flagEXTT); + } + } + } + writeExtra(e.extra); + if (commentBytes != null) { + writeBytes(commentBytes, 0, Math.min(commentBytes.length, 0xffff)); + } + } + + /* + * Writes end of central directory (END) header. + */ + private void writeEND(long off, long len) throws IOException { + boolean hasZip64 = false; + long xlen = len; + long xoff = off; + if (xlen >= ZIP64_MAGICVAL) { + xlen = ZIP64_MAGICVAL; + hasZip64 = true; + } + if (xoff >= ZIP64_MAGICVAL) { + xoff = ZIP64_MAGICVAL; + hasZip64 = true; + } + int count = xentries.size(); + if (count >= ZIP64_MAGICCOUNT) { + hasZip64 |= !inhibitZip64; + if (hasZip64) { + count = ZIP64_MAGICCOUNT; + } + } + if (hasZip64) { + long off64 = written; + //zip64 end of central directory record + writeInt(ZIP64_ENDSIG); // zip64 END record signature + writeLong(ZIP64_ENDHDR - 12); // size of zip64 end + writeShort(45); // version made by + writeShort(45); // version needed to extract + writeInt(0); // number of this disk + writeInt(0); // central directory start disk + writeLong(xentries.size()); // number of directory entires on disk + writeLong(xentries.size()); // number of directory entires + writeLong(len); // length of central directory + writeLong(off); // offset of central directory + + //zip64 end of central directory locator + writeInt(ZIP64_LOCSIG); // zip64 END locator signature + writeInt(0); // zip64 END start disk + writeLong(off64); // offset of zip64 END + writeInt(1); // total number of disks (?) + } + writeInt(ENDSIG); // END record signature + writeShort(0); // number of this disk + writeShort(0); // central directory start disk + writeShort(count); // number of directory entries on disk + writeShort(count); // total number of directory entries + writeInt(xlen); // length of central directory + writeInt(xoff); // offset of central directory + if (comment != null) { // zip file comment + writeShort(comment.length); + writeBytes(comment, 0, comment.length); + } else { + writeShort(0); + } + } + + /* + * Returns the length of extra data without EXTT and ZIP64. + */ + private int getExtraLen(byte[] extra) { + if (extra == null) + return 0; + int skipped = 0; + int len = extra.length; + int off = 0; + while (off + 4 <= len) { + int tag = get16(extra, off); + int sz = get16(extra, off + 2); + if (sz < 0 || (off + 4 + sz) > len) { + break; + } + if (tag == EXTID_EXTT || tag == EXTID_ZIP64) { + skipped += (sz + 4); + } + off += (sz + 4); + } + return len - skipped; + } + + /* + * Writes extra data without EXTT and ZIP64. + * + * Extra timestamp and ZIP64 data is handled/output separately + * in writeLOC and writeCEN. + */ + private void writeExtra(byte[] extra) throws IOException { + if (extra != null) { + int len = extra.length; + int off = 0; + while (off + 4 <= len) { + int tag = get16(extra, off); + int sz = get16(extra, off + 2); + if (sz < 0 || (off + 4 + sz) > len) { + writeBytes(extra, off, len - off); + return; + } + if (tag != EXTID_EXTT && tag != EXTID_ZIP64) { + writeBytes(extra, off, sz + 4); + } + off += (sz + 4); + } + if (off < len) { + writeBytes(extra, off, len - off); + } + } + } + + /* + * Writes a 8-bit byte to the output stream. + */ + private void writeByte(int v) throws IOException { + OutputStream out = this.out; + out.write(v & 0xff); + written += 1; + } + + /* + * Writes a 16-bit short to the output stream in little-endian byte order. + */ + private void writeShort(int v) throws IOException { + OutputStream out = this.out; + out.write((v >>> 0) & 0xff); + out.write((v >>> 8) & 0xff); + written += 2; + } + + /* + * Writes a 32-bit int to the output stream in little-endian byte order. + */ + private void writeInt(long v) throws IOException { + OutputStream out = this.out; + out.write((int)((v >>> 0) & 0xff)); + out.write((int)((v >>> 8) & 0xff)); + out.write((int)((v >>> 16) & 0xff)); + out.write((int)((v >>> 24) & 0xff)); + written += 4; + } + + /* + * Writes a 64-bit int to the output stream in little-endian byte order. + */ + private void writeLong(long v) throws IOException { + OutputStream out = this.out; + out.write((int)((v >>> 0) & 0xff)); + out.write((int)((v >>> 8) & 0xff)); + out.write((int)((v >>> 16) & 0xff)); + out.write((int)((v >>> 24) & 0xff)); + out.write((int)((v >>> 32) & 0xff)); + out.write((int)((v >>> 40) & 0xff)); + out.write((int)((v >>> 48) & 0xff)); + out.write((int)((v >>> 56) & 0xff)); + written += 8; + } + + /* + * Writes an array of bytes to the output stream. + */ + private void writeBytes(byte[] b, int off, int len) throws IOException { + super.out.write(b, off, len); + written += len; + } +}
diff --git a/android-35/java/util/zip/ZipUtils.java b/android-35/java/util/zip/ZipUtils.java new file mode 100644 index 0000000..dd5106e --- /dev/null +++ b/android-35/java/util/zip/ZipUtils.java
@@ -0,0 +1,306 @@ +/* + * Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.zip; + +import java.nio.ByteBuffer; +import java.nio.file.attribute.FileTime; +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import static java.util.zip.ZipConstants.ENDHDR; + +import jdk.internal.misc.Unsafe; + +class ZipUtils { + + // used to adjust values between Windows and java epoch + private static final long WINDOWS_EPOCH_IN_MICROSECONDS = -11644473600000000L; + + // used to indicate the corresponding windows time is not available + public static final long WINDOWS_TIME_NOT_AVAILABLE = Long.MIN_VALUE; + + // static final ByteBuffer defaultBuf = ByteBuffer.allocateDirect(0); + static final ByteBuffer defaultBuf = ByteBuffer.allocate(0); + + /** + * Converts Windows time (in microseconds, UTC/GMT) time to FileTime. + */ + public static final FileTime winTimeToFileTime(long wtime) { + return FileTime.from(wtime / 10 + WINDOWS_EPOCH_IN_MICROSECONDS, + TimeUnit.MICROSECONDS); + } + + /** + * Converts FileTime to Windows time. + */ + public static final long fileTimeToWinTime(FileTime ftime) { + return (ftime.to(TimeUnit.MICROSECONDS) - WINDOWS_EPOCH_IN_MICROSECONDS) * 10; + } + + /** + * The upper bound of the 32-bit unix time, the "year 2038 problem". + */ + public static final long UPPER_UNIXTIME_BOUND = 0x7fffffff; + + /** + * Converts "standard Unix time"(in seconds, UTC/GMT) to FileTime + */ + public static final FileTime unixTimeToFileTime(long utime) { + return FileTime.from(utime, TimeUnit.SECONDS); + } + + /** + * Converts FileTime to "standard Unix time". + */ + public static final long fileTimeToUnixTime(FileTime ftime) { + return ftime.to(TimeUnit.SECONDS); + } + + /** + * Converts DOS time to Java time (number of milliseconds since epoch). + */ + public static long dosToJavaTime(long dtime) { + int year = (int) (((dtime >> 25) & 0x7f) + 1980); + int month = (int) ((dtime >> 21) & 0x0f); + int day = (int) ((dtime >> 16) & 0x1f); + int hour = (int) ((dtime >> 11) & 0x1f); + int minute = (int) ((dtime >> 5) & 0x3f); + int second = (int) ((dtime << 1) & 0x3e); + + if (month > 0 && month < 13 && day > 0 && hour < 24 && minute < 60 && second < 60) { + try { + LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, minute, second); + return TimeUnit.MILLISECONDS.convert(ldt.toEpochSecond( + ZoneId.systemDefault().getRules().getOffset(ldt)), TimeUnit.SECONDS); + } catch (DateTimeException dte) { + // ignore + } + } + return overflowDosToJavaTime(year, month, day, hour, minute, second); + } + + /* + * Deal with corner cases where an arguably mal-formed DOS time is used + */ + @SuppressWarnings("deprecation") // Use of Date constructor + private static long overflowDosToJavaTime(int year, int month, int day, + int hour, int minute, int second) { + return new Date(year - 1900, month - 1, day, hour, minute, second).getTime(); + } + + + /** + * Converts extended DOS time to Java time, where up to 1999 milliseconds + * might be encoded into the upper half of the returned long. + * + * @param xdostime the extended DOS time value + * @return milliseconds since epoch + */ + public static long extendedDosToJavaTime(long xdostime) { + long time = dosToJavaTime(xdostime); + return time + (xdostime >> 32); + } + + /** + * Converts Java time to DOS time. + */ + private static long javaToDosTime(LocalDateTime ldt) { + int year = ldt.getYear() - 1980; + return (year << 25 | + ldt.getMonthValue() << 21 | + ldt.getDayOfMonth() << 16 | + ldt.getHour() << 11 | + ldt.getMinute() << 5 | + ldt.getSecond() >> 1) & 0xffffffffL; + } + + /** + * Converts Java time to DOS time, encoding any milliseconds lost + * in the conversion into the upper half of the returned long. + * + * @param time milliseconds since epoch + * @return DOS time with 2s remainder encoded into upper half + */ + static long javaToExtendedDosTime(long time) { + LocalDateTime ldt = javaEpochToLocalDateTime(time); + if (ldt.getYear() >= 1980) { + return javaToDosTime(ldt) + ((time % 2000) << 32); + } + return ZipEntry.DOSTIME_BEFORE_1980; + } + + static LocalDateTime javaEpochToLocalDateTime(long time) { + Instant instant = Instant.ofEpochMilli(time); + return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); + } + + /** + * Fetches unsigned 16-bit value from byte array at specified offset. + * The bytes are assumed to be in Intel (little-endian) byte order. + */ + public static final int get16(byte b[], int off) { + return (b[off] & 0xff) | ((b[off + 1] & 0xff) << 8); + } + + /** + * Fetches unsigned 32-bit value from byte array at specified offset. + * The bytes are assumed to be in Intel (little-endian) byte order. + */ + public static final long get32(byte b[], int off) { + return (get16(b, off) | ((long)get16(b, off+2) << 16)) & 0xffffffffL; + } + + /** + * Fetches signed 64-bit value from byte array at specified offset. + * The bytes are assumed to be in Intel (little-endian) byte order. + */ + public static final long get64(byte b[], int off) { + return get32(b, off) | (get32(b, off+4) << 32); + } + + /** + * Fetches signed 32-bit value from byte array at specified offset. + * The bytes are assumed to be in Intel (little-endian) byte order. + * + */ + public static final int get32S(byte b[], int off) { + return (get16(b, off) | (get16(b, off+2) << 16)); + } + + // fields access methods + static final int CH(byte[] b, int n) { + return b[n] & 0xff ; + } + + static final int SH(byte[] b, int n) { + return (b[n] & 0xff) | ((b[n + 1] & 0xff) << 8); + } + + static final long LG(byte[] b, int n) { + return ((SH(b, n)) | (SH(b, n + 2) << 16)) & 0xffffffffL; + } + + static final long LL(byte[] b, int n) { + return (LG(b, n)) | (LG(b, n + 4) << 32); + } + + static final long GETSIG(byte[] b) { + return LG(b, 0); + } + + /* + * File attribute compatibility types of CEN field "version made by" + */ + static final int FILE_ATTRIBUTES_UNIX = 3; // Unix + + /* + * Base values for CEN field "version made by" + */ + static final int VERSION_MADE_BY_BASE_UNIX = FILE_ATTRIBUTES_UNIX << 8; // Unix + + + // local file (LOC) header fields + static final long LOCSIG(byte[] b) { return LG(b, 0); } // signature + static final int LOCVER(byte[] b) { return SH(b, 4); } // version needed to extract + static final int LOCFLG(byte[] b) { return SH(b, 6); } // general purpose bit flags + static final int LOCHOW(byte[] b) { return SH(b, 8); } // compression method + static final long LOCTIM(byte[] b) { return LG(b, 10);} // modification time + static final long LOCCRC(byte[] b) { return LG(b, 14);} // crc of uncompressed data + static final long LOCSIZ(byte[] b) { return LG(b, 18);} // compressed data size + static final long LOCLEN(byte[] b) { return LG(b, 22);} // uncompressed data size + static final int LOCNAM(byte[] b) { return SH(b, 26);} // filename length + static final int LOCEXT(byte[] b) { return SH(b, 28);} // extra field length + + // extra local (EXT) header fields + static final long EXTCRC(byte[] b) { return LG(b, 4);} // crc of uncompressed data + static final long EXTSIZ(byte[] b) { return LG(b, 8);} // compressed size + static final long EXTLEN(byte[] b) { return LG(b, 12);} // uncompressed size + + // end of central directory header (END) fields + static final int ENDSUB(byte[] b) { return SH(b, 8); } // number of entries on this disk + static final int ENDTOT(byte[] b) { return SH(b, 10);} // total number of entries + static final long ENDSIZ(byte[] b) { return LG(b, 12);} // central directory size + static final long ENDOFF(byte[] b) { return LG(b, 16);} // central directory offset + static final int ENDCOM(byte[] b) { return SH(b, 20);} // size of zip file comment + static final int ENDCOM(byte[] b, int off) { return SH(b, off + 20);} + + // zip64 end of central directory recoder fields + static final long ZIP64_ENDTOD(byte[] b) { return LL(b, 24);} // total number of entries on disk + static final long ZIP64_ENDTOT(byte[] b) { return LL(b, 32);} // total number of entries + static final long ZIP64_ENDSIZ(byte[] b) { return LL(b, 40);} // central directory size + static final long ZIP64_ENDOFF(byte[] b) { return LL(b, 48);} // central directory offset + static final long ZIP64_LOCOFF(byte[] b) { return LL(b, 8);} // zip64 end offset + + // central directory header (CEN) fields + static final long CENSIG(byte[] b, int pos) { return LG(b, pos + 0); } + static final int CENVEM(byte[] b, int pos) { return SH(b, pos + 4); } + static final int CENVEM_FA(byte[] b, int pos) { return CH(b, pos + 5); } // file attribute compatibility + static final int CENVER(byte[] b, int pos) { return SH(b, pos + 6); } + static final int CENFLG(byte[] b, int pos) { return SH(b, pos + 8); } + static final int CENHOW(byte[] b, int pos) { return SH(b, pos + 10);} + static final long CENTIM(byte[] b, int pos) { return LG(b, pos + 12);} + static final long CENCRC(byte[] b, int pos) { return LG(b, pos + 16);} + static final long CENSIZ(byte[] b, int pos) { return LG(b, pos + 20);} + static final long CENLEN(byte[] b, int pos) { return LG(b, pos + 24);} + static final int CENNAM(byte[] b, int pos) { return SH(b, pos + 28);} + static final int CENEXT(byte[] b, int pos) { return SH(b, pos + 30);} + static final int CENCOM(byte[] b, int pos) { return SH(b, pos + 32);} + static final int CENDSK(byte[] b, int pos) { return SH(b, pos + 34);} + static final int CENATT(byte[] b, int pos) { return SH(b, pos + 36);} + static final long CENATX(byte[] b, int pos) { return LG(b, pos + 38);} + static final int CENATX_PERMS(byte[] b, int pos) { return SH(b, pos + 40);} // posix permission data + static final long CENOFF(byte[] b, int pos) { return LG(b, pos + 42);} + + // The END header is followed by a variable length comment of size < 64k. + static final long END_MAXLEN = 0xFFFF + ENDHDR; + static final int READBLOCKSZ = 128; + + // Android-removed: not available on Android. + /* + * Loads zip native library, if not already laoded + * + static void loadLibrary() { + jdk.internal.loader.BootLoader.loadLibrary("zip"); + } + */ + + private static final Unsafe unsafe = Unsafe.getUnsafe(); + + private static final long byteBufferArrayOffset = unsafe.objectFieldOffset(ByteBuffer.class, "hb"); + private static final long byteBufferOffsetOffset = unsafe.objectFieldOffset(ByteBuffer.class, "offset"); + + static byte[] getBufferArray(ByteBuffer byteBuffer) { + return (byte[]) unsafe.getReference(byteBuffer, byteBufferArrayOffset); + } + + static int getBufferOffset(ByteBuffer byteBuffer) { + return unsafe.getInt(byteBuffer, byteBufferOffsetOffset); + } +}
diff --git a/android-35/java/util/zip/package-info.java b/android-35/java/util/zip/package-info.java new file mode 100644 index 0000000..f4a38d6 --- /dev/null +++ b/android-35/java/util/zip/package-info.java
@@ -0,0 +1,77 @@ +/* + * Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * Provides classes for reading and writing the standard ZIP and GZIP file + * formats. Also includes classes for compressing and decompressing data using + * the DEFLATE compression algorithm, which is used by the ZIP and GZIP file + * formats. Additionally, there are utility classes for computing the CRC-32, + * CRC-32C and Adler-32 checksums of arbitrary input streams. + * + * <h2>Package Specification</h2> + * + * <ul> + * <li><a href="http://www.info-zip.org/doc/appnote-19970311-iz.zip"> + * Info-ZIP Application Note 970311</a> - a detailed description of + * the Info-ZIP format upon which the {@code java.util.zip} classes + * are based. + * <li><a id="zip64">An implementation may optionally support the + * ZIP64(tm) format extensions defined by the</a> + * <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT"> + * PKWARE ZIP File Format Specification</a>. The ZIP64(tm) format + * extensions are used to overcome the size limitations of the + * original ZIP format. + * <li><a id="lang_encoding">APPENDIX D of</a> + * <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT"> + * PKWARE ZIP File Format Specification</a> - Language Encoding Flag + * to encode ZIP entry filename and comment fields using UTF-8. + * <li><a href="http://www.ietf.org/rfc/rfc1950.txt"> + * ZLIB Compressed Data Format Specification version 3.3</a> + * + * <a href="http://www.ietf.org/rfc/rfc1950.txt.pdf">(pdf)</a> + * (RFC 1950) + * <li><a href="http://www.ietf.org/rfc/rfc1951.txt"> + * DEFLATE Compressed Data Format Specification version 1.3</a> + * + * <a href="http://www.ietf.org/rfc/rfc1951.txt.pdf">(pdf)</a> + * (RFC 1951) + * <li><a href="http://www.ietf.org/rfc/rfc1952.txt"> + * GZIP file format specification version 4.3</a> + * + * <a href="http://www.ietf.org/rfc/rfc1952.txt.pdf">(pdf)</a> + * (RFC 1952) + * <li>CRC-32 checksum is described in RFC 1952 (above) + * <li>CRC-32C checksum is described in + * <a href="http://www.ietf.org/rfc/rfc3720.txt">Internet Small + * Computer Systems Interface (iSCSI)</a> + * + * <a href="http://www.ietf.org/rfc/rfc3720.txt.pdf">(pdf)</a> + * (RFC 3720) + * <li>Adler-32 checksum is described in RFC 1950 (above) + * </ul> + * + * @since 1.1 + */ +package java.util.zip;