/*
* 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); } } }