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