package org.eclipse.linuxtools.internal.docker.ui; /******************************************************************************* * Copyright (c) 2004, 2015, 2016 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 * Red Hat Inc. - modification to use in Docker UI plug-in *******************************************************************************/ 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. */ public class TarInputStream extends FilterInputStream { private final static String TarImport_invalid_tar_format = "TarImport_invalid_tar_format"; //$NON-NLS-1$ 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) { throw new TarException( Messages.getString(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) { throw new TarException( Messages.getString(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 bytesread = 0; while (bytesread < longNameData.length) { int cur = read(longNameData, bytesread, longNameData.length - bytesread); if (cur < 0) { throw new IOException("early end of stream"); //$NON-NLS-1$ } bytesread += 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; } @Override 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; } @Override public int read() throws IOException { byte[] data = new byte[1]; int size = read(data, 0, 1); if (size < 0) { return size; } return data[0]; } }