/** * Copyright 2012 Kamran Zafar * * Licensed 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. * */ /** * The original source has been modified by Paul Williams. */ package com.codename1.io.tar; import java.io.IOException; import java.io.InputStream; import com.codename1.io.BufferedInputStream; /** * @author Kamran Zafar * */ public class TarInputStream extends BufferedInputStream { private static final int SKIP_BUFFER_SIZE = 2048; private TarEntry currentEntry; private long currentFileSize; private long bytesRead; private boolean defaultSkip = false; public TarInputStream(InputStream in) { super( in ); currentFileSize = 0; bytesRead = 0; } @Override public boolean markSupported() { return false; } /** * Not supported * */ @Override public synchronized void mark(int readlimit) { } /** * Not supported * */ @Override public synchronized void reset() throws IOException { throw new IOException( "mark/reset not supported" ); } /** * Read a byte * * @see java.io.FilterInputStream#read() */ @Override public int read() throws IOException { byte[] buf = new byte[1]; int res = this.read( buf, 0, 1 ); if (res != -1) { return buf[0]; } return res; } /** * Checks if the bytes being read exceed the entry size and adjusts the byte * array length. Updates the byte counters * * * @see java.io.FilterInputStream#read(byte[], int, int) */ @Override public int read(byte[] b, int off, int len) throws IOException { if (currentEntry != null) { if (currentFileSize == currentEntry.getSize()) { return -1; } else if (( currentEntry.getSize() - currentFileSize ) < len) { len = (int) ( currentEntry.getSize() - currentFileSize ); } } int br = super.read( b, off, len ); if (br != -1) { if (currentEntry != null) { currentFileSize += br; } bytesRead += br; } return br; } /** * Returns the next entry in the tar file * * @return TarEntry * @throws IOException */ public TarEntry getNextEntry() throws IOException { closeCurrentEntry(); byte[] header = new byte[TarConstants.HEADER_BLOCK]; byte[] theader = new byte[TarConstants.HEADER_BLOCK]; int tr = 0; // Read full header while (tr < TarConstants.HEADER_BLOCK) { int res = read( theader, 0, TarConstants.HEADER_BLOCK - tr ); if (res < 0) { break; } System.arraycopy( theader, 0, header, tr, res ); tr += res; } // Check if record is null boolean eof = true; for (byte b : header) { if (b != 0) { eof = false; break; } } if (!eof) { bytesRead += header.length; currentEntry = new TarEntry( header ); } return currentEntry; } /** * Closes the current tar entry * * @throws IOException */ protected void closeCurrentEntry() throws IOException { if (currentEntry != null) { if (currentEntry.getSize() > currentFileSize) { // Not fully read, skip rest of the bytes long bs = 0; while (bs < currentEntry.getSize() - currentFileSize) { long res = skip( currentEntry.getSize() - currentFileSize - bs ); if (res == 0 && currentEntry.getSize() - currentFileSize > 0) { throw new IOException( "Possible tar file corruption" ); } bs += res; } } currentEntry = null; currentFileSize = 0L; skipPad(); } } /** * Skips the pad at the end of each tar entry file content * * @throws IOException */ protected void skipPad() throws IOException { if (bytesRead > 0) { int extra = (int) ( bytesRead % TarConstants.DATA_BLOCK ); if (extra > 0) { long bs = 0; while (bs < TarConstants.DATA_BLOCK - extra) { long res = skip( TarConstants.DATA_BLOCK - extra - bs ); bs += res; } } } } /** * Skips 'n' bytes on the InputStream<br> * Overrides default implementation of skip * */ @Override public long skip(long n) throws IOException { if (defaultSkip) { // use skip method of parent stream // may not work if skip not implemented by parent return super.skip( n ); } if (n <= 0) { return 0; } long left = n; byte[] sBuff = new byte[SKIP_BUFFER_SIZE]; while (left > 0) { int res = read( sBuff, 0, (int) ( left < SKIP_BUFFER_SIZE ? left : SKIP_BUFFER_SIZE ) ); if (res < 0) { break; } left -= res; } return n - left; } public boolean isDefaultSkip() { return defaultSkip; } public void setDefaultSkip(boolean defaultSkip) { this.defaultSkip = defaultSkip; } }