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