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    }