    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.ByteArrayInputStream;
    022    import java.io.IOException;
    023    import java.io.InputStream;
    024    import java.io.OutputStream;
    025
    026    import org.apache.commons.compress.archivers.ar.ArArchiveInputStream;
    027    import org.apache.commons.compress.archivers.ar.ArArchiveOutputStream;
    028    import org.apache.commons.compress.archivers.cpio.CpioArchiveInputStream;
    029    import org.apache.commons.compress.archivers.cpio.CpioArchiveOutputStream;
    030    import org.apache.commons.compress.archivers.dump.DumpArchiveInputStream;
    031    import org.apache.commons.compress.archivers.jar.JarArchiveInputStream;
    032    import org.apache.commons.compress.archivers.jar.JarArchiveOutputStream;
    033    import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
    034    import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
    035    import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
    036    import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
    037
    038    /**
    039     * <p>Factory to create Archive[In|Out]putStreams from names or the first bytes of
    040     * the InputStream. In order add other implementations you should extend
    041     * ArchiveStreamFactory and override the appropriate methods (and call their
    042     * implementation from super of course).</p>
    043     *
    044     * Compressing a ZIP-File:
    045     *
    046     * <pre>
    047     * final OutputStream out = new FileOutputStream(output);
    048     * ArchiveOutputStream os = new ArchiveStreamFactory().createArchiveOutputStream(ArchiveStreamFactory.ZIP, out);
    049     *
    050     * os.putArchiveEntry(new ZipArchiveEntry("testdata/test1.xml"));
    051     * IOUtils.copy(new FileInputStream(file1), os);
    052     * os.closeArchiveEntry();
    053     *
    054     * os.putArchiveEntry(new ZipArchiveEntry("testdata/test2.xml"));
    055     * IOUtils.copy(new FileInputStream(file2), os);
    056     * os.closeArchiveEntry();
    057     * os.close();
    058     * </pre>
    059     *
    060     * Decompressing a ZIP-File:
    061     *
    062     * <pre>
    063     * final InputStream is = new FileInputStream(input);
    064     * ArchiveInputStream in = new ArchiveStreamFactory().createArchiveInputStream(ArchiveStreamFactory.ZIP, is);
    065     * ZipArchiveEntry entry = (ZipArchiveEntry)in.getNextEntry();
    066     * OutputStream out = new FileOutputStream(new File(dir, entry.getName()));
    067     * IOUtils.copy(in, out);
    068     * out.close();
    069     * in.close();
    070     * </pre>
    071     *
    072     * @Immutable
    073     */
    074    public class ArchiveStreamFactory {
    075
    076        /**
    077         * Constant used to identify the AR archive format.
    078         * @since Commons Compress 1.1
    079         */
    080        public static final String AR = "ar";
    081        /**
    082         * Constant used to identify the CPIO archive format.
    083         * @since Commons Compress 1.1
    084         */
    085        public static final String CPIO = "cpio";
    086        /**
    087         * Constant used to identify the Unix DUMP archive format.
    088         * @since Commons Compress 1.3
    089         */
    090        public static final String DUMP = "dump";
    091        /**
    092         * Constant used to identify the JAR archive format.
    093         * @since Commons Compress 1.1
    094         */
    095        public static final String JAR = "jar";
    096        /**
    097         * Constant used to identify the TAR archive format.
    098         * @since Commons Compress 1.1
    099         */
    100        public static final String TAR = "tar";
    101        /**
    102         * Constant used to identify the ZIP archive format.
    103         * @since Commons Compress 1.1
    104         */
    105        public static final String ZIP = "zip";
    106
    107        /**
    108         * Create an archive input stream from an archiver name and an input stream.
    109         *
    110         * @param archiverName the archive name, i.e. "ar", "zip", "tar", "jar", "dump" or "cpio"
    111         * @param in the input stream
    112         * @return the archive input stream
    113         * @throws ArchiveException if the archiver name is not known
    114         * @throws IllegalArgumentException if the archiver name or stream is null
    115         */
    116        public ArchiveInputStream createArchiveInputStream(
    117                final String archiverName, final InputStream in)
    118                throws ArchiveException {
    119
    120            if (archiverName == null) {
    121                throw new IllegalArgumentException("Archivername must not be null.");
    122            }
    123
    124            if (in == null) {
    125                throw new IllegalArgumentException("InputStream must not be null.");
    126            }
    127
    128            if (AR.equalsIgnoreCase(archiverName)) {
    129                return new ArArchiveInputStream(in);
    130            }
    131            if (ZIP.equalsIgnoreCase(archiverName)) {
    132                return new ZipArchiveInputStream(in);
    133            }
    134            if (TAR.equalsIgnoreCase(archiverName)) {
    135                return new TarArchiveInputStream(in);
    136            }
    137            if (JAR.equalsIgnoreCase(archiverName)) {
    138                return new JarArchiveInputStream(in);
    139            }
    140            if (CPIO.equalsIgnoreCase(archiverName)) {
    141                return new CpioArchiveInputStream(in);
    142            }
    143            if (DUMP.equalsIgnoreCase(archiverName)) {
    144                return new DumpArchiveInputStream(in);
    145            }
    146
    147            throw new ArchiveException("Archiver: " + archiverName + " not found.");
    148        }
    149
    150        /**
    151         * Create an archive output stream from an archiver name and an input stream.
    152         *
    153         * @param archiverName the archive name, i.e. "ar", "zip", "tar", "jar" or "cpio"
    154         * @param out the output stream
    155         * @return the archive output stream
    156         * @throws ArchiveException if the archiver name is not known
    157         * @throws IllegalArgumentException if the archiver name or stream is null
    158         */
    159        public ArchiveOutputStream createArchiveOutputStream(
    160                final String archiverName, final OutputStream out)
    161                throws ArchiveException {
    162            if (archiverName == null) {
    163                throw new IllegalArgumentException("Archivername must not be null.");
    164            }
    165            if (out == null) {
    166                throw new IllegalArgumentException("OutputStream must not be null.");
    167            }
    168
    169            if (AR.equalsIgnoreCase(archiverName)) {
    170                return new ArArchiveOutputStream(out);
    171            }
    172            if (ZIP.equalsIgnoreCase(archiverName)) {
    173                return new ZipArchiveOutputStream(out);
    174            }
    175            if (TAR.equalsIgnoreCase(archiverName)) {
    176                return new TarArchiveOutputStream(out);
    177            }
    178            if (JAR.equalsIgnoreCase(archiverName)) {
    179                return new JarArchiveOutputStream(out);
    180            }
    181            if (CPIO.equalsIgnoreCase(archiverName)) {
    182                return new CpioArchiveOutputStream(out);
    183            }
    184            throw new ArchiveException("Archiver: " + archiverName + " not found.");
    185        }
    186
    187        /**
    188         * Create an archive input stream from an input stream, autodetecting
    189         * the archive type from the first few bytes of the stream. The InputStream
    190         * must support marks, like BufferedInputStream.
    191         *
    192         * @param in the input stream
    193         * @return the archive input stream
    194         * @throws ArchiveException if the archiver name is not known
    195         * @throws IllegalArgumentException if the stream is null or does not support mark
    196         */
    197        public ArchiveInputStream createArchiveInputStream(final InputStream in)
    198                throws ArchiveException {
    199            if (in == null) {
    200                throw new IllegalArgumentException("Stream must not be null.");
    201            }
    202
    203            if (!in.markSupported()) {
    204                throw new IllegalArgumentException("Mark is not supported.");
    205            }
    206
    207            final byte[] signature = new byte[12];
    208            in.mark(signature.length);
    209            try {
    210                int signatureLength = in.read(signature);
    211                in.reset();
    212                if (ZipArchiveInputStream.matches(signature, signatureLength)) {
    213                    return new ZipArchiveInputStream(in);
    214                } else if (JarArchiveInputStream.matches(signature, signatureLength)) {
    215                    return new JarArchiveInputStream(in);
    216                } else if (ArArchiveInputStream.matches(signature, signatureLength)) {
    217                    return new ArArchiveInputStream(in);
    218                } else if (CpioArchiveInputStream.matches(signature, signatureLength)) {
    219                    return new CpioArchiveInputStream(in);
    220                }
    221
    222                // Dump needs a bigger buffer to check the signature;
    223                final byte[] dumpsig = new byte[32];
    224                in.mark(dumpsig.length);
    225                signatureLength = in.read(dumpsig);
    226                in.reset();
    227                if (DumpArchiveInputStream.matches(dumpsig, signatureLength)) {
    228                    return new DumpArchiveInputStream(in);
    229                }
    230
    231                // Tar needs an even bigger buffer to check the signature; read the first block
    232                final byte[] tarheader = new byte[512];
    233                in.mark(tarheader.length);
    234                signatureLength = in.read(tarheader);
    235                in.reset();
    236                if (TarArchiveInputStream.matches(tarheader, signatureLength)) {
    237                    return new TarArchiveInputStream(in);
    238                }
    239                // COMPRESS-117 - improve auto-recognition
    240                try {
    241                    TarArchiveInputStream tais = new TarArchiveInputStream(new ByteArrayInputStream(tarheader));
    242                    tais.getNextEntry();
    243                    return new TarArchiveInputStream(in);
    244                } catch (Exception e) { // NOPMD
    245                    // can generate IllegalArgumentException as well as IOException
    246                    // autodetection, simply not a TAR
    247                    // ignored
    248                }
    249            } catch (IOException e) {
    250                throw new ArchiveException("Could not use reset and mark operations.", e);
    251            }
    252
    253            throw new ArchiveException("No Archiver found for the stream signature");
    254        }
    255    }