/* * Copyright (c) 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 build.tools.tzdb; import java.io.File; import java.io.IOException; import java.io.FilterInputStream; import java.io.InputStream; /** * This class implements an input stream filter for reading files in the * UNIX tar file format. * * @implNote * This class does not handle symbol link, gnu long name and check checksum. * * @author Xueming Shen * * @since 1.9 */ class TarInputStream extends FilterInputStream { /** Default block size */ private static final int DEFAULT_BLKSIZE = 512; private int blockSize; private TarEntry entry; // the current entry private long remaining; // the remaining bytes in current entry private boolean closed; /** * Creates a new Tar input stream. * * @param is the source input stream to use */ public TarInputStream(InputStream is) { this(is, DEFAULT_BLKSIZE); } /** * Creates a new Tar input stream. * * @param is the input stream to use * @param blockSize the block size to use */ public TarInputStream(InputStream is, int blockSize) { super(is); this.blockSize = blockSize; this.remaining = 0; this.closed = false; } /** * Check to make sure that this stream has not been closed */ private void ensureOpen() throws IOException { if (closed) { throw new IOException("Input stream closed"); } } /** * Closes this input stream and releases any system resource associated. * * @throws IOException if an on error has occured */ @Override public void close() throws IOException { if (!closed) { super.close(); closed = true; } } private boolean isEOF(byte[] block) { for (int i = 0; i < block.length; i++) { if (block[i] != 0) { return false; } } return true; } /** * Skip the specified number of bytes in the current Tar entry. * * @param n the number of bytes to skip. * @return the number actually skipped * @throws IOException if an IO error has occurred * @throws IllegalArgumentException if {@code n < 0} */ @Override 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); byte[] tmpbuf = new byte[Math.min(max, 8192)]; 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) { break; } total += len; } return total; } /** * Reads the next Tar file entry and positions the stream at * the beginning of the entry data. * * @return the next Tar file entry, or null, if there are no * more entries * @throws IOException if an I/O error has occured */ public TarEntry getNextEntry() throws IOException { ensureOpen(); if (entry != null) { closeEntry(); } byte[] header = new byte[blockSize]; int off = 0; // read until we get blockSize of bytes or EOF while (off < blockSize) { int n = super.read(header, off, blockSize - off); if (n == -1) { // unexpected EOF, return null; } off += n; } if (isEOF(header)) { return null; } entry = new TarEntry(header); remaining = entry.getSize(); return entry; } /** * Closes the current Tar entry and positions the stream for reading * the next entry. * * @exception IOException if an I/O error has occurred */ public void closeEntry() throws IOException { ensureOpen(); byte[] tmpbuf = new byte[blockSize]; while (read(tmpbuf, 0, tmpbuf.length) != -1) ; } /** * Returns the available data that can be read from the current entry * data. * * @return the number of available bytes for the current entry, 0 * if EOF of current entry has reached, or Integer.MAX_VALUE, * if more than Integer.MAX_VALUE is available * @exception IOException if an I/O error occurs. * */ public int available() throws IOException { ensureOpen(); if (remaining > Integer.MAX_VALUE) { return Integer.MAX_VALUE; } return (int)remaining; } /** * Reads bytes from the current Tar entry. * * If {@code len} is not zero, this 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 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 IOException on error */ @Override 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 && remaining <= 0) { return -1; } if (len > remaining) { len = (int)remaining; } int n = super.read(b, off, len); if (n == -1) { entry = null; return -1; // throw new IOException("unexpected EOF"); } remaining -= n; if (remaining == 0) { int leftover = blockSize - (int)(entry.getSize() % blockSize); if (leftover > 0) { byte[] buf = new byte[leftover]; while (leftover > 0) { int m = super.read(buf, 0, leftover); if (m == -1) { break; } leftover -= m; } } entry = null; } return n; } public static class TarEntry { /* * The C structure for a Tar Entry's header is: *
         * struct header {
         * char name[100];
         * char mode[8];
         * char uid[8];
         * char gid[8];
         * char size[12];
         * char mtime[12];
         * char chksum[8];
         * char linkflag;
         * char linkname[100];
         * char magic[8];
         * char uname[32];
         * char gname[32];
         * char devmajor[8];
         * char devminor[8];
         * } header;
         * 
* */ private static final int NAME_OFF = 0; private static final int NAME_LEN = 100; /* private static final int MODE_OFF = 100; private static final int MODE_LEN = 8; private static final int UID_OFF = 108; private static final int UID_LEN = 8; private static final int GID_OFF = 116; private static final int GID_LEN = 8; */ private static final int SIZE_OFF = 124; private static final int SIZE_LEN = 12; /* private static final int MTIME_OFF = 136; private static final int MTIME_LEN = 12; private static final int CHECKSUM_OFF = 148; private static final int CHECKSUM_LEN = 8; private static final int TYPEFLAG_OFF = 156; private static final int TYPEFLAG_LEN = 1; private static final int LINKNAME_OFF = 157; private static final int LINKNAME_LEN = 100; */ private static final int MAGIC_OFF = 257; private static final int MAGIC_LEN = 6; /* private static final int VERSION_OFF = 263; private static final int VERSION_LEN = 2; private static final int UNAME_OFF = 265; private static final int UNAME_LEN = 32; private static final int GNAME_OFF = 297; private static final int GNAME_LEN = 32; private static final int DEVMAJOR_OFF = 329; private static final int DEVMAJOR_LEN = 8; private static final int DEVMINOR_OFF = 337; private static final int DEVMINOR_LEN = 8; private static final int PREFIX_OFF = 345; private static final int PREFIX_LEN = 155; */ // The maximum size of a file in a tar archive. private static final long MAXSIZE = 077777777777L; //private byte[] header; private String name; private long size; /** * Construct an entry from an archive's header bytes. * * @param headerBuf The header bytes from a tar archive entry. */ public TarEntry(byte[] header) { //this.header = header; this.name = toString(header, NAME_OFF, NAME_LEN); this.size = toNumber(header, SIZE_OFF, SIZE_LEN); // verify magic and size String magic = toString(header, MAGIC_OFF, MAGIC_LEN); if (!magic.startsWith("ustar") || size < 0 || size > MAXSIZE) { throw new IllegalArgumentException("Illegal tar entry: magic=" + magic + ", size=" + size); } } public long getSize() { return size; } public String getName() { return name; } private static long toNumber(byte[] header, int off, int len) { long ret = 0; int limit = off + len; int b; while (off < limit && ((b = header[off]) == ' ' || b == '0')) { off++; } while (off < limit && ((b = header[off++]) >= '0' && b < '8')) { ret = (ret << 3) + (b - '0'); } return ret; } private static String toString(byte[] header, int off, int len) { int off0 = off; int limit = off + len; while (off < limit && header[off] != 0) { off++; } return new String(header, off0, off - off0); } } }