All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.schlichtherle.truezip.fs.archive.tar.TarInputShop Maven / Gradle / Ivy

/*
 * Copyright (C) 2006-2011 Schlichtherle IT Services
 *
 * 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.
 */
package de.schlichtherle.truezip.fs.archive.tar;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
import de.schlichtherle.truezip.entry.EntryName;
import de.schlichtherle.truezip.io.Streams;
import de.schlichtherle.truezip.rof.ReadOnlyFile;
import de.schlichtherle.truezip.socket.InputSocket;
import de.schlichtherle.truezip.socket.InputShop;
import de.schlichtherle.truezip.socket.IOPool;
import de.schlichtherle.truezip.socket.IOPool.Entry;
import java.io.OutputStream;
import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.tools.tar.TarBuffer;
import org.apache.tools.tar.TarEntry;
import org.apache.tools.tar.TarInputStream;
import org.apache.tools.tar.TarUtils;

import static de.schlichtherle.truezip.entry.EntryName.SEPARATOR;
import static de.schlichtherle.truezip.entry.EntryName.SEPARATOR_CHAR;
import static org.apache.tools.tar.TarConstants.GIDLEN;
import static org.apache.tools.tar.TarConstants.MODELEN;
import static org.apache.tools.tar.TarConstants.MODTIMELEN;
import static org.apache.tools.tar.TarConstants.NAMELEN;
import static org.apache.tools.tar.TarConstants.SIZELEN;
import static org.apache.tools.tar.TarConstants.UIDLEN;

/**
 * Presents a {@link TarInputStream} as a randomly accessible archive.
 * 

* Warning: * The constructor of this class extracts each entry in the archive to a * temporary file. * This may be very time and space consuming for large archives, but is * the fastest implementation for subsequent random access, since there * is no way the archive driver could predict the client application's * behaviour. * * @see TarOutputShop * @author Christian Schlichtherle * @version $Id$ */ @DefaultAnnotation(NonNull.class) public class TarInputShop implements InputShop { private static final byte[] NULL_RECORD = new byte[TarBuffer.DEFAULT_RCDSIZE]; private static final int CHECKSUM_OFFSET = NAMELEN + MODELEN + UIDLEN + GIDLEN + SIZELEN + MODTIMELEN; /** Maps entry names to I/O pool entries. */ private final Map entries = new LinkedHashMap(); /** * Extracts the entire TAR input stream into a temporary directory in order * to allow subsequent random access to its entries. * * @param in The input stream from which this input archive file should be * initialized. This stream is not used by any of the methods in * this class after the constructor has terminated and is * never closed! * So it is safe and recommended to close it upon termination * of this constructor. */ public TarInputShop(final TarDriver driver, final InputStream in) throws IOException { final TarInputStream tin = newValidatedTarInputStream(in); final IOPool pool = driver.getPool(); try { TarEntry tinEntry; while (null != (tinEntry = tin.getNextEntry())) { final String name = getName(tinEntry); TarArchiveEntry entry = entries.get(name); if (null != entry) entry.release(); entry = new TarArchiveEntry(name, tinEntry); if (!tinEntry.isDirectory()) { final Entry temp = pool.allocate(); entry.setTemp(temp); try { final OutputStream out = temp.getOutputSocket().newOutputStream(); try { Streams.cat(tin, out); } finally { out.close(); } } catch (IOException ex) { temp.release(); throw ex; } } entries.put(name, entry); } } catch (IOException ex) { close0(); throw ex; } } /** * Returns the fixed name of the given TAR entry, ensuring that it ends * with a {@link EntryName#SEPARATOR} if it's a directory. * * @param entry the TAR entry. * @return the fixed name of the given TAR entry. * @see Issue TRUEZIP-62 */ private static String getName(TarEntry entry) { final String name = entry.getName(); return entry.isDirectory() && !name.endsWith(SEPARATOR) ? name + SEPARATOR_CHAR : name; } /** * Returns a newly created and validated {@link TarInputStream}. * This method performs a simple validation by computing the checksum * for the first record only. * This method is required because the {@code TarInputStream} * unfortunately does not do any validation! */ private static TarInputStream newValidatedTarInputStream( final InputStream in) throws IOException { final byte[] buf = new byte[TarBuffer.DEFAULT_RCDSIZE]; final InputStream vin = readAhead(in, buf); // If the record is the null record, the TAR file is empty and we're // done with validating. if (!Arrays.equals(buf, NULL_RECORD)) { final long expected = TarUtils.parseOctal(buf, CHECKSUM_OFFSET, 8); for (int i = 0; i < 8; i++) buf[CHECKSUM_OFFSET + i] = ' '; final long is = TarUtils.computeCheckSum(buf); if (expected != is) throw new IOException( "Illegal initial record in TAR file: Expected checksum " + expected + ", is " + is + "!"); } return new TarInputStream( vin, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE); } /** * Fills {@code buf} with data from the given input stream and * returns an input stream from which you can still read all data, * including the data in buf. * * @param in The stream to read from. May not be {@code null}. * @param buf The buffer to fill entirely with data. * @return A stream which holds all the data {@code in} did. * @throws IOException If {@code buf} couldn't get filled entirely. */ static InputStream readAhead(final InputStream in, final byte[] buf) throws IOException { if (in.markSupported()) { in.mark(buf.length); readFully(in, buf); in.reset(); return in; } else { final PushbackInputStream pin = new PushbackInputStream(in, buf.length); readFully(pin, buf); pin.unread(buf); return pin; } } private static void readFully(final InputStream in, final byte[] buf) throws IOException { final int l = buf.length; int n = 0; do { final int r = in.read(buf, n, l - n); if (0 >= r) throw new EOFException(); n += r; } while (n < l); } @Override public final int getSize() { return entries.size(); } @Override public final Iterator iterator() { return entries.values().iterator(); } @Override public final TarArchiveEntry getEntry(String name) { return entries.get(name); } @Override public InputSocket getInputSocket(final String name) { if (null == name) throw new NullPointerException(); class Input extends InputSocket { @Override public TarArchiveEntry getLocalTarget() throws IOException { final TarArchiveEntry entry = getEntry(name); if (null == entry) throw new FileNotFoundException(name + " (entry not found)"); if (entry.isDirectory()) throw new FileNotFoundException(name + " (cannot read directories)"); return entry; } @Override public ReadOnlyFile newReadOnlyFile() throws IOException { return getLocalTarget().getTemp().getInputSocket().newReadOnlyFile(); } @Override public InputStream newInputStream() throws IOException { return getLocalTarget().getTemp().getInputSocket().newInputStream(); } } // class Input return new Input(); } @Override public void close() throws IOException { close0(); } private void close0() throws IOException { final Collection values = entries.values(); for (final Iterator i = values.iterator(); i.hasNext(); i.remove()) { final TarArchiveEntry entry = i.next(); entry.release(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy