1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.commons.compress.archivers;
20
21 import java.io.ByteArrayInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.security.AccessController;
26 import java.security.PrivilegedAction;
27 import java.util.Collections;
28 import java.util.Locale;
29 import java.util.ServiceLoader;
30 import java.util.Set;
31 import java.util.SortedMap;
32 import java.util.TreeMap;
33
34 import org.apache.commons.compress.archivers.ar.ArArchiveInputStream;
35 import org.apache.commons.compress.archivers.ar.ArArchiveOutputStream;
36 import org.apache.commons.compress.archivers.arj.ArjArchiveInputStream;
37 import org.apache.commons.compress.archivers.cpio.CpioArchiveInputStream;
38 import org.apache.commons.compress.archivers.cpio.CpioArchiveOutputStream;
39 import org.apache.commons.compress.archivers.dump.DumpArchiveInputStream;
40 import org.apache.commons.compress.archivers.jar.JarArchiveInputStream;
41 import org.apache.commons.compress.archivers.jar.JarArchiveOutputStream;
42 import org.apache.commons.compress.archivers.sevenz.SevenZFile;
43 import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
44 import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
45 import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
46 import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
47 import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
48 import org.apache.commons.compress.utils.IOUtils;
49 import org.apache.commons.compress.utils.Sets;
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 public class ArchiveStreamFactory implements ArchiveStreamProvider {
87
88 private static final int TAR_HEADER_SIZE = 512;
89
90 private static final int TAR_TEST_ENTRY_COUNT = 10;
91
92 private static final int DUMP_SIGNATURE_SIZE = 32;
93
94 private static final int SIGNATURE_SIZE = 12;
95
96
97
98
99
100
101 public static final ArchiveStreamFactory DEFAULT = new ArchiveStreamFactory();
102
103
104
105
106
107
108
109
110
111 public static final String APK = "apk";
112
113
114
115
116
117
118
119
120
121 public static final String XAPK = "xapk";
122
123
124
125
126
127
128
129
130
131 public static final String APKS = "apks";
132
133
134
135
136
137
138
139
140
141 public static final String APKM = "apkm";
142
143
144
145
146
147
148 public static final String AR = "ar";
149
150
151
152
153
154
155 public static final String ARJ = "arj";
156
157
158
159
160
161
162 public static final String CPIO = "cpio";
163
164
165
166
167
168
169 public static final String DUMP = "dump";
170
171
172
173
174
175
176 public static final String JAR = "jar";
177
178
179
180
181
182
183 public static final String TAR = "tar";
184
185
186
187
188
189
190 public static final String ZIP = "zip";
191
192
193
194
195
196
197 public static final String SEVEN_Z = "7z";
198
199 private static Iterable<ArchiveStreamProvider> archiveStreamProviderIterable() {
200 return ServiceLoader.load(ArchiveStreamProvider.class, ClassLoader.getSystemClassLoader());
201 }
202
203
204
205
206
207
208
209
210
211 public static String detect(final InputStream in) throws ArchiveException {
212 if (in == null) {
213 throw new IllegalArgumentException("Stream must not be null.");
214 }
215
216 if (!in.markSupported()) {
217 throw new IllegalArgumentException("Mark is not supported.");
218 }
219
220 final byte[] signature = new byte[SIGNATURE_SIZE];
221 in.mark(signature.length);
222 int signatureLength = -1;
223 try {
224 signatureLength = IOUtils.readFully(in, signature);
225 in.reset();
226 } catch (final IOException e) {
227 throw new ArchiveException("IOException while reading signature.", e);
228 }
229
230
231 if (ZipArchiveInputStream.matches(signature, signatureLength)) {
232 return ZIP;
233 }
234
235 if (JarArchiveInputStream.matches(signature, signatureLength)) {
236 return JAR;
237 }
238 if (ArArchiveInputStream.matches(signature, signatureLength)) {
239 return AR;
240 }
241 if (CpioArchiveInputStream.matches(signature, signatureLength)) {
242 return CPIO;
243 }
244 if (ArjArchiveInputStream.matches(signature, signatureLength)) {
245 return ARJ;
246 }
247 if (SevenZFile.matches(signature, signatureLength)) {
248 return SEVEN_Z;
249 }
250
251
252 final byte[] dumpsig = new byte[DUMP_SIGNATURE_SIZE];
253 in.mark(dumpsig.length);
254 try {
255 signatureLength = IOUtils.readFully(in, dumpsig);
256 in.reset();
257 } catch (final IOException e) {
258 throw new ArchiveException("IOException while reading dump signature", e);
259 }
260 if (DumpArchiveInputStream.matches(dumpsig, signatureLength)) {
261 return DUMP;
262 }
263
264
265 final byte[] tarHeader = new byte[TAR_HEADER_SIZE];
266 in.mark(tarHeader.length);
267 try {
268 signatureLength = IOUtils.readFully(in, tarHeader);
269 in.reset();
270 } catch (final IOException e) {
271 throw new ArchiveException("IOException while reading tar signature", e);
272 }
273 if (TarArchiveInputStream.matches(tarHeader, signatureLength)) {
274 return TAR;
275 }
276
277
278 if (signatureLength >= TAR_HEADER_SIZE) {
279 try (TarArchiveInputStream inputStream = new TarArchiveInputStream(new ByteArrayInputStream(tarHeader))) {
280
281
282 TarArchiveEntry entry = inputStream.getNextEntry();
283
284 int count = 0;
285 while (entry != null && entry.isDirectory() && entry.isCheckSumOK() && count++ < TAR_TEST_ENTRY_COUNT) {
286 entry = inputStream.getNextEntry();
287 }
288 if (entry != null && entry.isCheckSumOK() && !entry.isDirectory() && entry.getSize() > 0 || count > 0) {
289 return TAR;
290 }
291 } catch (final Exception ignored) {
292
293 }
294 }
295 throw new ArchiveException("No Archiver found for the stream signature");
296 }
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318 public static SortedMap<String, ArchiveStreamProvider> findAvailableArchiveInputStreamProviders() {
319 return AccessController.doPrivileged((PrivilegedAction<SortedMap<String, ArchiveStreamProvider>>) () -> {
320 final TreeMap<String, ArchiveStreamProvider> map = new TreeMap<>();
321 putAll(DEFAULT.getInputStreamArchiveNames(), DEFAULT, map);
322 archiveStreamProviderIterable().forEach(provider -> putAll(provider.getInputStreamArchiveNames(), provider, map));
323 return map;
324 });
325 }
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347 public static SortedMap<String, ArchiveStreamProvider> findAvailableArchiveOutputStreamProviders() {
348 return AccessController.doPrivileged((PrivilegedAction<SortedMap<String, ArchiveStreamProvider>>) () -> {
349 final TreeMap<String, ArchiveStreamProvider> map = new TreeMap<>();
350 putAll(DEFAULT.getOutputStreamArchiveNames(), DEFAULT, map);
351 archiveStreamProviderIterable().forEach(provider -> putAll(provider.getOutputStreamArchiveNames(), provider, map));
352 return map;
353 });
354 }
355
356 static void putAll(final Set<String> names, final ArchiveStreamProvider provider, final TreeMap<String, ArchiveStreamProvider> map) {
357 names.forEach(name -> map.put(toKey(name), provider));
358 }
359
360 private static String toKey(final String name) {
361 return name.toUpperCase(Locale.ROOT);
362 }
363
364
365
366
367 private volatile String entryEncoding;
368
369 private SortedMap<String, ArchiveStreamProvider> archiveInputStreamProviders;
370
371 private SortedMap<String, ArchiveStreamProvider> archiveOutputStreamProviders;
372
373
374
375
376 public ArchiveStreamFactory() {
377 this(null);
378 }
379
380
381
382
383
384
385
386
387 public ArchiveStreamFactory(final String entryEncoding) {
388 this.entryEncoding = entryEncoding;
389 }
390
391
392
393
394
395
396
397
398
399
400
401
402 public <I extends ArchiveInputStream<? extends ArchiveEntry>> I createArchiveInputStream(final InputStream in) throws ArchiveException {
403 return createArchiveInputStream(detect(in), in);
404 }
405
406
407
408
409
410
411
412
413
414
415
416
417
418 public <I extends ArchiveInputStream<? extends ArchiveEntry>> I createArchiveInputStream(final String archiverName, final InputStream in)
419 throws ArchiveException {
420 return createArchiveInputStream(archiverName, in, entryEncoding);
421 }
422
423 @SuppressWarnings("unchecked")
424 @Override
425 public <I extends ArchiveInputStream<? extends ArchiveEntry>> I createArchiveInputStream(final String archiverName, final InputStream in,
426 final String actualEncoding) throws ArchiveException {
427
428 if (archiverName == null) {
429 throw new IllegalArgumentException("Archiver name must not be null.");
430 }
431
432 if (in == null) {
433 throw new IllegalArgumentException("InputStream must not be null.");
434 }
435
436 if (AR.equalsIgnoreCase(archiverName)) {
437 return (I) new ArArchiveInputStream(in);
438 }
439 if (ARJ.equalsIgnoreCase(archiverName)) {
440 if (actualEncoding != null) {
441 return (I) new ArjArchiveInputStream(in, actualEncoding);
442 }
443 return (I) new ArjArchiveInputStream(in);
444 }
445 if (ZIP.equalsIgnoreCase(archiverName)) {
446 if (actualEncoding != null) {
447 return (I) new ZipArchiveInputStream(in, actualEncoding);
448 }
449 return (I) new ZipArchiveInputStream(in);
450 }
451 if (TAR.equalsIgnoreCase(archiverName)) {
452 if (actualEncoding != null) {
453 return (I) new TarArchiveInputStream(in, actualEncoding);
454 }
455 return (I) new TarArchiveInputStream(in);
456 }
457 if (JAR.equalsIgnoreCase(archiverName) || APK.equalsIgnoreCase(archiverName)) {
458 if (actualEncoding != null) {
459 return (I) new JarArchiveInputStream(in, actualEncoding);
460 }
461 return (I) new JarArchiveInputStream(in);
462 }
463 if (CPIO.equalsIgnoreCase(archiverName)) {
464 if (actualEncoding != null) {
465 return (I) new CpioArchiveInputStream(in, actualEncoding);
466 }
467 return (I) new CpioArchiveInputStream(in);
468 }
469 if (DUMP.equalsIgnoreCase(archiverName)) {
470 if (actualEncoding != null) {
471 return (I) new DumpArchiveInputStream(in, actualEncoding);
472 }
473 return (I) new DumpArchiveInputStream(in);
474 }
475 if (SEVEN_Z.equalsIgnoreCase(archiverName)) {
476 throw new StreamingNotSupportedException(SEVEN_Z);
477 }
478
479 final ArchiveStreamProvider archiveStreamProvider = getArchiveInputStreamProviders().get(toKey(archiverName));
480 if (archiveStreamProvider != null) {
481 return archiveStreamProvider.createArchiveInputStream(archiverName, in, actualEncoding);
482 }
483
484 throw new ArchiveException("Archiver: " + archiverName + " not found.");
485 }
486
487
488
489
490
491
492
493
494
495
496
497
498 public <O extends ArchiveOutputStream<? extends ArchiveEntry>> O createArchiveOutputStream(final String archiverName, final OutputStream out)
499 throws ArchiveException {
500 return createArchiveOutputStream(archiverName, out, entryEncoding);
501 }
502
503 @SuppressWarnings("unchecked")
504 @Override
505 public <O extends ArchiveOutputStream<? extends ArchiveEntry>> O createArchiveOutputStream(final String archiverName, final OutputStream out,
506 final String actualEncoding) throws ArchiveException {
507 if (archiverName == null) {
508 throw new IllegalArgumentException("Archiver name must not be null.");
509 }
510 if (out == null) {
511 throw new IllegalArgumentException("OutputStream must not be null.");
512 }
513
514 if (AR.equalsIgnoreCase(archiverName)) {
515 return (O) new ArArchiveOutputStream(out);
516 }
517 if (ZIP.equalsIgnoreCase(archiverName)) {
518 final ZipArchiveOutputStream zip = new ZipArchiveOutputStream(out);
519 if (actualEncoding != null) {
520 zip.setEncoding(actualEncoding);
521 }
522 return (O) zip;
523 }
524 if (TAR.equalsIgnoreCase(archiverName)) {
525 if (actualEncoding != null) {
526 return (O) new TarArchiveOutputStream(out, actualEncoding);
527 }
528 return (O) new TarArchiveOutputStream(out);
529 }
530 if (JAR.equalsIgnoreCase(archiverName)) {
531 if (actualEncoding != null) {
532 return (O) new JarArchiveOutputStream(out, actualEncoding);
533 }
534 return (O) new JarArchiveOutputStream(out);
535 }
536 if (CPIO.equalsIgnoreCase(archiverName)) {
537 if (actualEncoding != null) {
538 return (O) new CpioArchiveOutputStream(out, actualEncoding);
539 }
540 return (O) new CpioArchiveOutputStream(out);
541 }
542 if (SEVEN_Z.equalsIgnoreCase(archiverName)) {
543 throw new StreamingNotSupportedException(SEVEN_Z);
544 }
545
546 final ArchiveStreamProvider archiveStreamProvider = getArchiveOutputStreamProviders().get(toKey(archiverName));
547 if (archiveStreamProvider != null) {
548 return archiveStreamProvider.createArchiveOutputStream(archiverName, out, actualEncoding);
549 }
550
551 throw new ArchiveException("Archiver: " + archiverName + " not found.");
552 }
553
554 public SortedMap<String, ArchiveStreamProvider> getArchiveInputStreamProviders() {
555 if (archiveInputStreamProviders == null) {
556 archiveInputStreamProviders = Collections.unmodifiableSortedMap(findAvailableArchiveInputStreamProviders());
557 }
558 return archiveInputStreamProviders;
559 }
560
561 public SortedMap<String, ArchiveStreamProvider> getArchiveOutputStreamProviders() {
562 if (archiveOutputStreamProviders == null) {
563 archiveOutputStreamProviders = Collections.unmodifiableSortedMap(findAvailableArchiveOutputStreamProviders());
564 }
565 return archiveOutputStreamProviders;
566 }
567
568
569
570
571
572
573
574 public String getEntryEncoding() {
575 return entryEncoding;
576 }
577
578 @Override
579 public Set<String> getInputStreamArchiveNames() {
580 return Sets.newHashSet(AR, ARJ, ZIP, TAR, JAR, CPIO, DUMP, SEVEN_Z);
581 }
582
583 @Override
584 public Set<String> getOutputStreamArchiveNames() {
585 return Sets.newHashSet(AR, ZIP, TAR, JAR, CPIO, SEVEN_Z);
586 }
587
588
589
590
591
592
593
594
595 @Deprecated
596 public void setEntryEncoding(final String entryEncoding) {
597 this.entryEncoding = entryEncoding;
598 }
599
600 }