    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(ArchiveStreamFactory.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(ArchiveStreamFactory.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         * Constant used to identify the AR archive format.
    076         * @since Commons Compress 1.1
    077         */
    078        public static final String AR = "ar";
    079        /**
    080         * Constant used to identify the CPIO archive format.
    081         * @since Commons Compress 1.1
    082         */
    083        public static final String CPIO = "cpio";
    084        /**
    085         * Constant used to identify the JAR archive format.
    086         * @since Commons Compress 1.1
    087         */
    088        public static final String JAR = "jar";
    089        /**
    090         * Constant used to identify the TAR archive format.
    091         * @since Commons Compress 1.1
    092         */
    093        public static final String TAR = "tar";
    094        /**
    095         * Constant used to identify the ZIP archive format.
    096         * @since Commons Compress 1.1
    097         */
    098        public static final String ZIP = "zip";
    099
    100        /**
    101         * Create an archive input stream from an archiver name and an input stream.
    102         *
    103         * @param archiverName the archive name, i.e. "ar", "zip", "tar", "jar" or "cpio"
    104         * @param in the input stream
    105         * @return the archive input stream
    106         * @throws ArchiveException if the archiver name is not known
    107         * @throws IllegalArgumentException if the archiver name or stream is null
    108         */
    109        public ArchiveInputStream createArchiveInputStream(
    110                final String archiverName, final InputStream in)
    111                throws ArchiveException {
    112
    113            if (archiverName == null) {
    114                throw new IllegalArgumentException("Archivername must not be null.");
    115            }
    116
    117            if (in == null) {
    118                throw new IllegalArgumentException("InputStream must not be null.");
    119            }
    120
    121            if (AR.equalsIgnoreCase(archiverName)) {
    122                return new ArArchiveInputStream(in);
    123            }
    124            if (ZIP.equalsIgnoreCase(archiverName)) {
    125                return new ZipArchiveInputStream(in);
    126            }
    127            if (TAR.equalsIgnoreCase(archiverName)) {
    128                return new TarArchiveInputStream(in);
    129            }
    130            if (JAR.equalsIgnoreCase(archiverName)) {
    131                return new JarArchiveInputStream(in);
    132            }
    133            if (CPIO.equalsIgnoreCase(archiverName)) {
    134                return new CpioArchiveInputStream(in);
    135            }
    136
    137            throw new ArchiveException("Archiver: " + archiverName + " not found.");
    138        }
    139
    140        /**
    141         * Create an archive output stream from an archiver name and an input stream.
    142         *
    143         * @param archiverName the archive name, i.e. "ar", "zip", "tar", "jar" or "cpio"
    144         * @param out the output stream
    145         * @return the archive output stream
    146         * @throws ArchiveException if the archiver name is not known
    147         * @throws IllegalArgumentException if the archiver name or stream is null
    148         */
    149        public ArchiveOutputStream createArchiveOutputStream(
    150                final String archiverName, final OutputStream out)
    151                throws ArchiveException {
    152            if (archiverName == null) {
    153                throw new IllegalArgumentException("Archivername must not be null.");
    154            }
    155            if (out == null) {
    156                throw new IllegalArgumentException("OutputStream must not be null.");
    157            }
    158
    159            if (AR.equalsIgnoreCase(archiverName)) {
    160                return new ArArchiveOutputStream(out);
    161            }
    162            if (ZIP.equalsIgnoreCase(archiverName)) {
    163                return new ZipArchiveOutputStream(out);
    164            }
    165            if (TAR.equalsIgnoreCase(archiverName)) {
    166                return new TarArchiveOutputStream(out);
    167            }
    168            if (JAR.equalsIgnoreCase(archiverName)) {
    169                return new JarArchiveOutputStream(out);
    170            }
    171            if (CPIO.equalsIgnoreCase(archiverName)) {
    172                return new CpioArchiveOutputStream(out);
    173            }
    174            throw new ArchiveException("Archiver: " + archiverName + " not found.");
    175        }
    176
    177        /**
    178         * Create an archive input stream from an input stream, autodetecting
    179         * the archive type from the first few bytes of the stream. The InputStream
    180         * must support marks, like BufferedInputStream.
    181         *
    182         * @param in the input stream
    183         * @return the archive input stream
    184         * @throws ArchiveException if the archiver name is not known
    185         * @throws IllegalArgumentException if the stream is null or does not support mark
    186         */
    187        public ArchiveInputStream createArchiveInputStream(final InputStream in)
    188                throws ArchiveException {
    189            if (in == null) {
    190                throw new IllegalArgumentException("Stream must not be null.");
    191            }
    192
    193            if (!in.markSupported()) {
    194                throw new IllegalArgumentException("Mark is not supported.");
    195            }
    196
    197            final byte[] signature = new byte[12];
    198            in.mark(signature.length);
    199            try {
    200                int signatureLength = in.read(signature);
    201                in.reset();
    202                if (ZipArchiveInputStream.matches(signature, signatureLength)) {
    203                    return new ZipArchiveInputStream(in);
    204                } else if (JarArchiveInputStream.matches(signature, signatureLength)) {
    205                    return new JarArchiveInputStream(in);
    206                } else if (ArArchiveInputStream.matches(signature, signatureLength)) {
    207                    return new ArArchiveInputStream(in);
    208                } else if (CpioArchiveInputStream.matches(signature, signatureLength)) {
    209                    return new CpioArchiveInputStream(in);
    210                }
    211                // Tar needs a bigger buffer to check the signature; read the first block
    212                final byte[] tarheader = new byte[512];
    213                in.mark(tarheader.length);
    214                signatureLength = in.read(tarheader);
    215                in.reset();
    216                if (TarArchiveInputStream.matches(tarheader, signatureLength)) {
    217                    return new TarArchiveInputStream(in);
    218                }
    219            } catch (IOException e) {
    220                throw new ArchiveException("Could not use reset and mark operations.", e);
    221            }
    222
    223            throw new ArchiveException("No Archiver found for the stream signature");
    224        }
    225    }