    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one
    003     * or more contributor license agreements.  See the NOTICE file
    004     * distributed with this work for additional information
    005     * regarding copyright ownership.  The ASF licenses this file
    006     * to you under the Apache License, Version 2.0 (the
    007     * "License"); you may not use this file except in compliance
    008     * with the License.  You may obtain a copy of the License at
    009     *
    010     * http://www.apache.org/licenses/LICENSE-2.0
    011     *
    012     * Unless required by applicable law or agreed to in writing,
    013     * software distributed under the License is distributed on an
    014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015     * KIND, either express or implied.  See the License for the
    016     * specific language governing permissions and limitations
    017     * under the License.
    018     */
    019    package org.apache.commons.compress.archivers;
    020
    021    import java.io.IOException;
    022    import java.io.InputStream;
    023    import java.io.OutputStream;
    024
    025    import org.apache.commons.compress.archivers.ar.ArArchiveInputStream;
    026    import org.apache.commons.compress.archivers.ar.ArArchiveOutputStream;
    027    import org.apache.commons.compress.archivers.cpio.CpioArchiveInputStream;
    028    import org.apache.commons.compress.archivers.cpio.CpioArchiveOutputStream;
    029    import org.apache.commons.compress.archivers.jar.JarArchiveInputStream;
    030    import org.apache.commons.compress.archivers.jar.JarArchiveOutputStream;
    031    import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
    032    import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
    033    import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
    034    import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
    035
    036    /**
    037     * <p>Factory to create Archive[In|Out]putStreams from names or the first bytes of
    038     * the InputStream. In order add other implementations you should extend
    039     * ArchiveStreamFactory and override the appropriate methods (and call their
    040     * implementation from super of course).</p>
    041     *
    042     * Compressing a ZIP-File:
    043     *
    044     * <pre>
    045     * final OutputStream out = new FileOutputStream(output);
    046     * ArchiveOutputStream os = new ArchiveStreamFactory().createArchiveOutputStream("zip", out);
    047     *
    048     * os.putArchiveEntry(new ZipArchiveEntry("testdata/test1.xml"));
    049     * IOUtils.copy(new FileInputStream(file1), os);
    050     * os.closeArchiveEntry();
    051     *
    052     * os.putArchiveEntry(new ZipArchiveEntry("testdata/test2.xml"));
    053     * IOUtils.copy(new FileInputStream(file2), os);
    054     * os.closeArchiveEntry();
    055     * os.close();
    056     * </pre>
    057     *
    058     * Decompressing a ZIP-File:
    059     *
    060     * <pre>
    061     * final InputStream is = new FileInputStream(input);
    062     * ArchiveInputStream in = new ArchiveStreamFactory().createArchiveInputStream("zip", is);
    063     * ZipArchiveEntry entry = (ZipArchiveEntry)in.getNextEntry();
    064     * OutputStream out = new FileOutputStream(new File(dir, entry.getName()));
    065     * IOUtils.copy(in, out);
    066     * out.close();
    067     * in.close();
    068     * </pre>
    069     *
    070     * @Immutable
    071     */
    072    public class ArchiveStreamFactory {
    073
    074        /**
    075         * Create an archive input stream from an archiver name and an input stream.
    076         *
    077         * @param archiverName the archive name, i.e. "ar", "zip", "tar", "jar" or "cpio"
    078         * @param in the input stream
    079         * @return the archive input stream
    080         * @throws ArchiveException if the archiver name is not known
    081         * @throws IllegalArgumentException if the archiver name or stream is null
    082         */
    083        public ArchiveInputStream createArchiveInputStream(
    084                final String archiverName, final InputStream in)
    085                throws ArchiveException {
    086            if (archiverName == null || in == null) {
    087                throw new IllegalArgumentException("Archivername must not be null.");
    088            }
    089
    090            if ("ar".equalsIgnoreCase(archiverName)) {
    091                return new ArArchiveInputStream(in);
    092            } else if ("zip".equalsIgnoreCase(archiverName)) {
    093                return new ZipArchiveInputStream(in);
    094            } else if ("tar".equalsIgnoreCase(archiverName)) {
    095                return new TarArchiveInputStream(in);
    096            } else if ("jar".equalsIgnoreCase(archiverName)) {
    097                return new JarArchiveInputStream(in);
    098            } else if ("cpio".equalsIgnoreCase(archiverName)) {
    099                return new CpioArchiveInputStream(in);
    100            }
    101            throw new ArchiveException("Archiver: " + archiverName + " not found.");
    102        }
    103
    104        /**
    105         * Create an archive output stream from an archiver name and an input stream.
    106         *
    107         * @param archiverName the archive name, i.e. "ar", "zip", "tar", "jar" or "cpio"
    108         * @param out the output stream
    109         * @return the archive output stream
    110         * @throws ArchiveException if the archiver name is not known
    111         * @throws IllegalArgumentException if the archiver name or stream is null
    112         */
    113        public ArchiveOutputStream createArchiveOutputStream(
    114                final String archiverName, final OutputStream out)
    115                throws ArchiveException {
    116            if (archiverName == null || out == null) {
    117                throw new IllegalArgumentException(
    118                        "Archivername and stream must not be null.");
    119            }
    120
    121            if ("ar".equalsIgnoreCase(archiverName)) {
    122                return new ArArchiveOutputStream(out);
    123            } else if ("zip".equalsIgnoreCase(archiverName)) {
    124                return new ZipArchiveOutputStream(out);
    125            } else if ("tar".equalsIgnoreCase(archiverName)) {
    126                return new TarArchiveOutputStream(out);
    127            } else if ("jar".equalsIgnoreCase(archiverName)) {
    128                return new JarArchiveOutputStream(out);
    129            } else if ("cpio".equalsIgnoreCase(archiverName)) {
    130                return new CpioArchiveOutputStream(out);
    131            }
    132            throw new ArchiveException("Archiver: " + archiverName + " not found.");
    133        }
    134
    135        /**
    136         * Create an archive input stream from an input stream, autodetecting
    137         * the archive type from the first few bytes of the stream. The InputStream
    138         * must support marks, like BufferedInputStream.
    139         *
    140         * @param in the input stream
    141         * @return the archive input stream
    142         * @throws ArchiveException if the archiver name is not known
    143         * @throws IllegalArgumentException if the stream is null or does not support mark
    144         */
    145        public ArchiveInputStream createArchiveInputStream(final InputStream in)
    146                throws ArchiveException {
    147            if (in == null) {
    148                throw new IllegalArgumentException("Stream must not be null.");
    149            }
    150
    151            if (!in.markSupported()) {
    152                throw new IllegalArgumentException("Mark is not supported.");
    153            }
    154
    155            final byte[] signature = new byte[12];
    156            in.mark(signature.length);
    157            try {
    158                int signatureLength = in.read(signature);
    159                in.reset();
    160                if (ZipArchiveInputStream.matches(signature, signatureLength)) {
    161                    return new ZipArchiveInputStream(in);
    162                } else if (JarArchiveInputStream.matches(signature, signatureLength)) {
    163                    return new JarArchiveInputStream(in);
    164                } else if (ArArchiveInputStream.matches(signature, signatureLength)) {
    165                    return new ArArchiveInputStream(in);
    166                } else if (CpioArchiveInputStream.matches(signature, signatureLength)) {
    167                    return new CpioArchiveInputStream(in);
    168                }
    169                // Tar needs a bigger buffer to check the signature; read the first block
    170                final byte[] tarheader = new byte[512];
    171                in.mark(tarheader.length);
    172                signatureLength = in.read(tarheader);
    173                in.reset();
    174                if (TarArchiveInputStream.matches(tarheader, signatureLength)) {
    175                    return new TarArchiveInputStream(in);
    176                }
    177            } catch (IOException e) {
    178                throw new ArchiveException("Could not use reset and mark operations.", e);
    179            }
    180
    181            throw new ArchiveException("No Archiver found for the stream signature");
    182        }
    183    }