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    }