blob: 21e2551037fe91dd56be55aa0ea2d13e4831475f [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.compress.archivers.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().
*/
public class TarInputStream
extends FilterInputStream
{
private TarBuffer m_buffer;
private TarArchiveEntry m_currEntry;
private boolean m_debug;
private int m_entryOffset;
private int m_entrySize;
private boolean m_hasHitEOF;
private byte[] m_oneBuf;
private byte[] m_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
*/
public 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
*/
public 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
*/
public TarInputStream( final InputStream input,
final int blockSize,
final int recordSize )
{
super( input );
m_buffer = new TarBuffer( input, blockSize, recordSize );
m_oneBuf = new byte[ 1 ];
}
/**
* Sets the debugging flag.
*
* @param debug The new Debug value
*/
public void setDebug( final boolean debug )
{
m_debug = debug;
m_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.
* @exception IOException Description of Exception
*/
public TarArchiveEntry getNextEntry()
throws IOException
{
if( m_hasHitEOF )
{
return null;
}
if( m_currEntry != null )
{
final int numToSkip = m_entrySize - m_entryOffset;
if( m_debug )
{
final String message = "TarInputStream: SKIP currENTRY '" +
m_currEntry.getName() + "' SZ " + m_entrySize +
" OFF " + m_entryOffset + " skipping " + numToSkip + " bytes";
debug( message );
}
if( numToSkip > 0 )
{
skip( numToSkip );
}
m_readBuf = null;
}
final byte[] headerBuf = m_buffer.readRecord();
if( headerBuf == null )
{
if( m_debug )
{
debug( "READ NULL RECORD" );
}
m_hasHitEOF = true;
}
else if( m_buffer.isEOFRecord( headerBuf ) )
{
if( m_debug )
{
debug( "READ EOF RECORD" );
}
m_hasHitEOF = true;
}
if( m_hasHitEOF )
{
m_currEntry = null;
}
else
{
m_currEntry = new TarArchiveEntry( headerBuf );
if( !( headerBuf[ 257 ] == 'u' && headerBuf[ 258 ] == 's' &&
headerBuf[ 259 ] == 't' && headerBuf[ 260 ] == 'a' &&
headerBuf[ 261 ] == 'r' ) )
{
//Must be v7Format
}
if( m_debug )
{
final String message = "TarInputStream: SET CURRENTRY '" +
m_currEntry.getName() + "' size = " + m_currEntry.getSize();
debug( message );
}
m_entryOffset = 0;
// REVIEW How do we resolve this discrepancy?!
m_entrySize = (int)m_currEntry.getSize();
}
if( null != m_currEntry && m_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);
}
m_currEntry.setName( longName.toString() );
}
return m_currEntry;
}
/**
* Get the record size being used by this stream's TarBuffer.
*
* @return The TarBuffer record size.
*/
public int getRecordSize()
{
return m_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.
* @exception IOException when an IO error causes operation to fail
*/
public int available()
throws IOException
{
return m_entrySize - m_entryOffset;
}
/**
* Closes this stream. Calls the TarBuffer's close() method.
*
* @exception IOException when an IO error causes operation to fail
*/
public void close()
throws IOException
{
m_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.
* @exception 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.
* @exception IOException when an IO error causes operation to fail
*/
public int read()
throws IOException
{
final int num = read( m_oneBuf, 0, 1 );
if( num == -1 )
{
return num;
}
else
{
return (int)m_oneBuf[ 0 ] & 0xFF;
}
}
/**
* 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.
* @exception 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.
* @exception 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( m_entryOffset >= m_entrySize )
{
return -1;
}
if( ( numToRead + m_entryOffset ) > m_entrySize )
{
numToRead = ( m_entrySize - m_entryOffset );
}
if( null != m_readBuf )
{
final int size =
( numToRead > m_readBuf.length ) ? m_readBuf.length : numToRead;
System.arraycopy( m_readBuf, 0, buffer, position, size );
if( size >= m_readBuf.length )
{
m_readBuf = null;
}
else
{
final int newLength = m_readBuf.length - size;
final byte[] newBuffer = new byte[ newLength ];
System.arraycopy( m_readBuf, size, newBuffer, 0, newLength );
m_readBuf = newBuffer;
}
totalRead += size;
numToRead -= size;
position += size;
}
while( numToRead > 0 )
{
final byte[] rec = m_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 );
m_readBuf = new byte[ recordLength - size ];
System.arraycopy( rec, size, m_readBuf, 0, recordLength - size );
}
else
{
size = recordLength;
System.arraycopy( rec, 0, buffer, position, recordLength );
}
totalRead += size;
numToRead -= size;
position += size;
}
m_entryOffset += totalRead;
return totalRead;
}
/**
* Since we do not support marking just yet, we do nothing.
*/
public void reset()
{
}
/**
* 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.
* @exception 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( m_debug )
{
System.err.println( message );
}
}
}