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 }