blob: afd41553e6fc83fdbd140d9d79b1774465476c09 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2007 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.wst.server.core.internal.tar;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Input stream for reading files in ustar format (tar) compatible
* with the specification in IEEE Std 1003.1-2001. Also supports
* long filenames encoded using the GNU @LongLink extension.
* <p>
* Copied from org.eclipse.ui.internal.wizards.datatransfer.
* </p>
*/
public class TarInputStream extends FilterInputStream {
private int nextEntry = 0;
private int nextEOF = 0;
private int filepos = 0;
private int bytesread = 0;
private TarEntry firstEntry = null;
private String longLinkName = null;
/**
* Creates a new tar input stream on the given input stream.
*
* @param in input stream
* @throws TarException
* @throws IOException
*/
public TarInputStream(InputStream in) throws TarException, IOException {
super(in);
// Read in the first TarEntry to make sure
// the input is a valid tar file stream.
firstEntry = getNextEntry();
}
/**
* Create a new tar input stream, skipping ahead to the given entry
* in the file.
*
* @param in input stream
* @param entry skips to this entry in the file
* @throws TarException
* @throws IOException
*/
TarInputStream(InputStream in, TarEntry entry) throws TarException, IOException {
super(in);
skipToEntry(entry);
}
/**
* The checksum of a tar file header is simply the sum of the bytes in
* the header.
*
* @param header
* @return checksum
*/
private long headerChecksum(byte[] header) {
long sum = 0;
for(int i = 0; i < 512; i++) {
sum += header[i] & 0xff;
}
return sum;
}
/**
* Skips ahead to the position of the given entry in the file.
*
* @param entry
* @returns false if the entry has already been passed
* @throws TarException
* @throws IOException
*/
boolean skipToEntry(TarEntry entry) throws TarException, IOException {
int bytestoskip = entry.filepos - bytesread;
if(bytestoskip < 0) {
return false;
}
while(bytestoskip > 0) {
long ret = in.skip(bytestoskip);
if(ret < 0) {
throw new IOException("early end of stream"); //$NON-NLS-1$
}
bytestoskip -= ret;
bytesread += ret;
}
filepos = entry.filepos;
nextEntry = 0;
nextEOF = 0;
// Read next header to seek to file data.
getNextEntry();
return true;
}
/**
* Returns true if the header checksum is correct.
*
* @param header
* @return true if this header has a valid checksum
*/
private boolean isValidTarHeader(byte[] header) {
long fileChecksum, calculatedChecksum;
int pos, i;
pos = 148;
StringBuffer checksumString = new StringBuffer();
for(i = 0; i < 8; i++) {
if(header[pos + i] == ' ') {
continue;
}
if(header[pos + i] == 0 || !Character.isDigit((char) header[pos + i])) {
break;
}
checksumString.append((char) header[pos + i]);
}
if(checksumString.length() == 0) {
return false;
}
if(checksumString.charAt(0) != '0') {
checksumString.insert(0, '0');
}
try {
fileChecksum = Long.decode(checksumString.toString()).longValue();
} catch(NumberFormatException exception) {
//This is not valid if it cannot be parsed
return false;
}
// Blank out the checksum.
for(i = 0; i < 8; i++) {
header[pos + i] = ' ';
}
calculatedChecksum = headerChecksum(header);
return (fileChecksum == calculatedChecksum);
}
/**
* Returns the next entry in the tar file. Does not handle
* GNU @LongLink extensions.
*
* @return the next entry in the tar file
* @throws TarException
* @throws IOException
*/
TarEntry getNextEntryInternal() throws TarException, IOException {
byte[] header = new byte[512];
int pos = 0;
int i;
if(firstEntry != null) {
TarEntry entryReturn = firstEntry;
firstEntry = null;
return entryReturn;
}
while(nextEntry > 0) {
long ret = in.skip(nextEntry);
if(ret < 0) {
throw new IOException("early end of stream"); //$NON-NLS-1$
}
nextEntry -= ret;
bytesread += ret;
}
int bytestoread = 512;
while(bytestoread > 0) {
int ret = super.read(header, 512 - bytestoread, bytestoread);
if( ret < 0 ) {
throw new IOException("early end of stream"); //$NON-NLS-1$
}
bytestoread -= ret;
bytesread += ret;
}
// If we have a header of all zeros, this marks the end of the file.
if(headerChecksum(header) == 0) {
// We are at the end of the file.
if(filepos > 0) {
return null;
}
// Invalid stream.
throw new TarException("not in tar format"); //$NON-NLS-1$
}
// Validate checksum.
if(!isValidTarHeader(header)) {
throw new TarException("not in tar format"); //$NON-NLS-1$
}
while (pos < 100 && header[pos] != 0) {
pos++;
}
String name = new String(header, 0, pos, "UTF8"); //$NON-NLS-1$
// Prepend the prefix here.
pos = 345;
if(header[pos] != 0) {
while (pos < 500 && header[pos] != 0) {
pos++;
}
String prefix = new String(header, 345, pos - 345, "UTF8"); //$NON-NLS-1$
name = prefix + "/" + name; //$NON-NLS-1$
}
TarEntry entry;
if(longLinkName != null) {
entry = new TarEntry(longLinkName, filepos);
longLinkName = null;
} else {
entry = new TarEntry(name, filepos);
}
if(header[156] != 0) {
entry.setFileType(header[156]);
}
pos = 100;
StringBuffer mode = new StringBuffer();
for(i = 0; i < 8; i++) {
if(header[pos + i] == 0) {
break;
}
if(header[pos + i] == ' ') {
continue;
}
mode.append((char) header[pos + i]);
}
if(mode.length() > 0 && mode.charAt(0) != '0') {
mode.insert(0, '0');
}
try {
long fileMode = Long.decode(mode.toString()).longValue();
entry.setMode(fileMode);
} catch(NumberFormatException nfe) { // TODO
throw new TarException("TarImport_invalid_tar_format" + nfe);
}
pos = 100 + 24;
StringBuffer size = new StringBuffer();
for(i = 0; i < 12; i++) {
if(header[pos + i] == 0) {
break;
}
if(header[pos + i] == ' ') {
continue;
}
size.append((char) header[pos + i]);
}
if(size.charAt(0) != '0') {
size.insert(0, '0');
}
int fileSize;
try {
fileSize = Integer.decode(size.toString()).intValue();
} catch(NumberFormatException nfe) { // TODO
throw new TarException("DataTransferMessages.TarImport_invalid_tar_format" + nfe);
}
entry.setSize(fileSize);
nextEOF = fileSize;
if(fileSize % 512 > 0) {
nextEntry = fileSize + (512 - (fileSize % 512));
} else {
nextEntry = fileSize;
}
filepos += (nextEntry + 512);
return entry;
}
/**
* Moves ahead to the next file in the tar archive and returns
* a TarEntry object describing it.
*
* @return the next entry in the tar file
* @throws TarException
* @throws IOException
*/
public TarEntry getNextEntry() throws TarException, IOException {
TarEntry entry = getNextEntryInternal();
if(entry != null && entry.getName().equals("././@LongLink")) { //$NON-NLS-1$
// This is a GNU extension for doing long filenames.
// We get a file called ././@LongLink which just contains
// the real pathname.
byte[] longNameData = new byte[(int) entry.getSize()];
int bytesread2 = 0;
while (bytesread2 < longNameData.length) {
int cur = read(longNameData, bytesread2, longNameData.length - bytesread2);
if (cur < 0) {
throw new IOException("early end of stream"); //$NON-NLS-1$
}
bytesread2 += cur;
}
int pos = 0;
while (pos < longNameData.length && longNameData[pos] != 0) {
pos++;
}
longLinkName = new String(longNameData, 0, pos, "UTF8"); //$NON-NLS-1$
return getNextEntryInternal();
}
return entry;
}
/* (non-Javadoc)
* @see java.io.FilterInputStream#read(byte[], int, int)
*/
public int read(byte[] b, int off, int len) throws IOException {
if(nextEOF == 0) {
return -1;
}
if(len > nextEOF) {
len = nextEOF;
}
int size = super.read(b, off, len);
nextEntry -= size;
nextEOF -= size;
bytesread += size;
return size;
}
/* (non-Javadoc)
* @see java.io.FilterInputStream#read()
*/
public int read() throws IOException {
byte[] data = new byte[1];
int size = read(data, 0, 1);
if (size < 0) {
return size;
}
return data[0];
}
}