blob: 9024a27b9b167f6e436dac696e8e6d5de23f79e2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.vfs.provider.tar;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* The TarInputStream reads a UNIX tar archive as an InputStream. methods are
* provided to position at each successive entry in the archive, and the read
* each entry as a normal input stream using read().
*
* @author <a href="mailto:time@ice.com">Timothy Gerard Endres</a>
* @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
* @author <a href="mailto:peter@apache.org">Peter Donald</a>
* @version $Revision$ $Date$
* @see TarInputStream
* @see TarEntry
*/
class TarInputStream
extends FilterInputStream
{
private TarBuffer buffer;
private TarEntry currEntry;
private boolean debug;
private int entryOffset;
private long entrySize;
private boolean hasHitEOF;
private byte[] oneBuf;
private byte[] readBuf;
/**
* Construct a TarInputStream using specified input
* stream and default block and record sizes.
*
* @param input stream to create TarInputStream from
* @see TarBuffer#DEFAULT_BLOCKSIZE
* @see TarBuffer#DEFAULT_RECORDSIZE
*/
TarInputStream(final InputStream input)
{
this(input, TarBuffer.DEFAULT_BLOCKSIZE, TarBuffer.DEFAULT_RECORDSIZE);
}
/**
* Construct a TarInputStream using specified input
* stream, block size and default record sizes.
*
* @param input stream to create TarInputStream from
* @param blockSize the block size to use
* @see TarBuffer#DEFAULT_RECORDSIZE
*/
TarInputStream(final InputStream input,
final int blockSize)
{
this(input, blockSize, TarBuffer.DEFAULT_RECORDSIZE);
}
/**
* Construct a TarInputStream using specified input
* stream, block size and record sizes.
*
* @param input stream to create TarInputStream from
* @param blockSize the block size to use
* @param recordSize the record size to use
*/
TarInputStream(final InputStream input,
final int blockSize,
final int recordSize)
{
super(input);
buffer = new TarBuffer(input, blockSize, recordSize);
oneBuf = new byte[1];
}
/**
* Sets the debugging flag.
*
* @param debug The new Debug value
*/
public void setDebug(final boolean debug)
{
this.debug = debug;
buffer.setDebug(debug);
}
/**
* Get the next entry in this tar archive. This will skip over any remaining
* data in the current entry, if there is one, and place the input stream at
* the header of the next entry, and read the header and instantiate a new
* TarEntry from the header bytes and return that entry. If there are no
* more entries in the archive, null will be returned to indicate that the
* end of the archive has been reached.
*
* @return The next TarEntry in the archive, or null.
* @throws IOException Description of Exception
*/
public TarEntry getNextEntry()
throws IOException
{
if (hasHitEOF)
{
return null;
}
if (currEntry != null)
{
final long numToSkip = entrySize - entryOffset;
if (debug)
{
final String message = "TarInputStream: SKIP currENTRY '" +
currEntry.getName() + "' SZ " + entrySize +
" OFF " + entryOffset + " skipping " + numToSkip + " bytes";
debug(message);
}
if (numToSkip > 0)
{
// Use our internal skip to move to the end of the current entry
longSkip(numToSkip);
}
readBuf = null;
}
final byte[] headerBuf = buffer.readRecord();
if (headerBuf == null)
{
if (debug)
{
debug("READ NULL RECORD");
}
hasHitEOF = true;
}
else if (buffer.isEOFRecord(headerBuf))
{
if (debug)
{
debug("READ EOF RECORD");
}
hasHitEOF = true;
}
if (hasHitEOF)
{
currEntry = null;
}
else
{
currEntry = new TarEntry(headerBuf);
if (!(headerBuf[257] == 'u' && headerBuf[258] == 's' &&
headerBuf[259] == 't' && headerBuf[260] == 'a' &&
headerBuf[261] == 'r'))
{
//Must be v7Format
}
if (debug)
{
final String message = "TarInputStream: SET CURRENTRY '" +
currEntry.getName() + "' size = " + currEntry.getSize();
debug(message);
}
entryOffset = 0;
entrySize = currEntry.getSize();
}
if (null != currEntry && currEntry.isGNULongNameEntry())
{
// read in the name
final StringBuffer longName = new StringBuffer();
final byte[] buffer = new byte[256];
int length = 0;
while ((length = read(buffer)) >= 0)
{
final String str = new String(buffer, 0, length);
longName.append(str);
}
getNextEntry();
// remove trailing null terminator
if (longName.length() > 0
&& longName.charAt(longName.length() - 1) == 0)
{
longName.deleteCharAt(longName.length() - 1);
}
currEntry.setName(longName.toString());
}
return currEntry;
}
/**
* Get the record size being used by this stream's TarBuffer.
*
* @return The TarBuffer record size.
*/
public int getRecordSize()
{
return buffer.getRecordSize();
}
/**
* Get the available data that can be read from the current entry in the
* archive. This does not indicate how much data is left in the entire
* archive, only in the current entry. This value is determined from the
* entry's size header field and the amount of data already read from the
* current entry.
*
* @return The number of available bytes for the current entry.
* @throws IOException when an IO error causes operation to fail
*/
public int available()
throws IOException
{
long remaining = entrySize - entryOffset;
if(remaining > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
return (int) remaining;
}
/**
* Closes this stream. Calls the TarBuffer's close() method.
*
* @throws IOException when an IO error causes operation to fail
*/
public void close()
throws IOException
{
buffer.close();
}
/**
* Copies the contents of the current tar archive entry directly into an
* output stream.
*
* @param output The OutputStream into which to write the entry's data.
* @throws IOException when an IO error causes operation to fail
*/
public void copyEntryContents(final OutputStream output)
throws IOException
{
final byte[] buffer = new byte[32 * 1024];
while (true)
{
final int numRead = read(buffer, 0, buffer.length);
if (numRead == -1)
{
break;
}
output.write(buffer, 0, numRead);
}
}
/**
* Since we do not support marking just yet, we do nothing.
*
* @param markLimit The limit to mark.
*/
public void mark(int markLimit)
{
}
/**
* Since we do not support marking just yet, we return false.
*
* @return False.
*/
public boolean markSupported()
{
return false;
}
/**
* Reads a byte from the current tar archive entry. This method simply calls
* read( byte[], int, int ).
*
* @return The byte read, or -1 at EOF.
* @throws IOException when an IO error causes operation to fail
*/
public int read()
throws IOException
{
final int num = read(oneBuf, 0, 1);
if (num == -1)
{
return num;
}
else
{
return oneBuf[0];
}
}
/**
* Reads bytes from the current tar archive entry. This method simply calls
* read( byte[], int, int ).
*
* @param buffer The buffer into which to place bytes read.
* @return The number of bytes read, or -1 at EOF.
* @throws IOException when an IO error causes operation to fail
*/
public int read(final byte[] buffer)
throws IOException
{
return read(buffer, 0, buffer.length);
}
/**
* Reads bytes from the current tar archive entry. This method is aware of
* the boundaries of the current entry in the archive and will deal with
* them as if they were this stream's start and EOF.
*
* @param buffer The buffer into which to place bytes read.
* @param offset The offset at which to place bytes read.
* @param count The number of bytes to read.
* @return The number of bytes read, or -1 at EOF.
* @throws IOException when an IO error causes operation to fail
*/
public int read(final byte[] buffer,
final int offset,
final int count)
throws IOException
{
int position = offset;
int numToRead = count;
int totalRead = 0;
if (entryOffset >= entrySize)
{
return -1;
}
if ((numToRead + entryOffset) > entrySize)
{
numToRead = (int)(entrySize - entryOffset);
}
if (null != readBuf)
{
final int size =
(numToRead > readBuf.length) ? readBuf.length : numToRead;
System.arraycopy(readBuf, 0, buffer, position, size);
if (size >= readBuf.length)
{
readBuf = null;
}
else
{
final int newLength = readBuf.length - size;
final byte[] newBuffer = new byte[newLength];
System.arraycopy(readBuf, size, newBuffer, 0, newLength);
readBuf = newBuffer;
}
totalRead += size;
numToRead -= size;
position += size;
}
while (numToRead > 0)
{
final byte[] rec = this.buffer.readRecord();
if (null == rec)
{
// Unexpected EOF!
final String message =
"unexpected EOF with " + numToRead + " bytes unread";
throw new IOException(message);
}
int size = numToRead;
final int recordLength = rec.length;
if (recordLength > size)
{
System.arraycopy(rec, 0, buffer, position, size);
readBuf = new byte[recordLength - size];
System.arraycopy(rec, size, readBuf, 0, recordLength - size);
}
else
{
size = recordLength;
System.arraycopy(rec, 0, buffer, position, recordLength);
}
totalRead += size;
numToRead -= size;
position += size;
}
entryOffset += totalRead;
return totalRead;
}
/**
* Since we do not support marking just yet, we do nothing.
*/
public void reset()
{
}
public void longSkip(final long numToSkip) throws IOException {
for(long skipped = 0; skipped < numToSkip;) {
if(numToSkip - skipped > Integer.MAX_VALUE) {
skip(Integer.MAX_VALUE);
skipped += Integer.MAX_VALUE;
} else {
skip((int)(numToSkip - skipped));
skipped += numToSkip - skipped;
}
}
}
/**
* Skip bytes in the input buffer. This skips bytes in the current entry's
* data, not the entire archive, and will stop at the end of the current
* entry's data if the number to skip extends beyond that point.
*
* @param numToSkip The number of bytes to skip.
* @throws IOException when an IO error causes operation to fail
*/
public void skip(final int numToSkip)
throws IOException
{
// REVIEW
// This is horribly inefficient, but it ensures that we
// properly skip over bytes via the TarBuffer...
//
final byte[] skipBuf = new byte[8 * 1024];
int num = numToSkip;
while (num > 0)
{
final int count = (num > skipBuf.length) ? skipBuf.length : num;
final int numRead = read(skipBuf, 0, count);
if (numRead == -1)
{
break;
}
num -= numRead;
}
}
/**
* Utility method to do debugging.
* Capable of being overidden in sub-classes.
*
* @param message the message to use in debugging
*/
protected void debug(final String message)
{
if (debug)
{
System.err.println(message);
}
}
}