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.ServiceLoader;
29 import java.util.Set;
30 import java.util.SortedMap;
31 import java.util.TreeMap;
32
33 import org.apache.commons.compress.archivers.ar.ArArchiveInputStream;
34 import org.apache.commons.compress.archivers.ar.ArArchiveOutputStream;
35 import org.apache.commons.compress.archivers.arj.ArjArchiveInputStream;
36 import org.apache.commons.compress.archivers.cpio.CpioArchiveInputStream;
37 import org.apache.commons.compress.archivers.cpio.CpioArchiveOutputStream;
38 import org.apache.commons.compress.archivers.dump.DumpArchiveInputStream;
39 import org.apache.commons.compress.archivers.jar.JarArchiveInputStream;
40 import org.apache.commons.compress.archivers.jar.JarArchiveOutputStream;
41 import org.apache.commons.compress.archivers.sevenz.SevenZFile;
42 import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
43 import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
44 import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
45 import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
46 import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
47 import org.apache.commons.compress.utils.IOUtils;
48 import org.apache.commons.compress.utils.Sets;
49 import org.apache.commons.lang3.StringUtils;
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 if (!in.markSupported()) {
216 throw new IllegalArgumentException("Mark is not supported.");
217 }
218 final byte[] signature = new byte[SIGNATURE_SIZE];
219 in.mark(signature.length);
220 int signatureLength = -1;
221 try {
222 signatureLength = IOUtils.readFully(in, signature);
223 in.reset();
224 } catch (final IOException e) {
225 throw new ArchiveException("Failure reading signature.", (Throwable) e);
226 }
227
228 if (ZipArchiveInputStream.matches(signature, signatureLength)) {
229 return ZIP;
230 }
231
232 if (JarArchiveInputStream.matches(signature, signatureLength)) {
233 return JAR;
234 }
235 if (ArArchiveInputStream.matches(signature, signatureLength)) {
236 return AR;
237 }
238 if (CpioArchiveInputStream.matches(signature, signatureLength)) {
239 return CPIO;
240 }
241 if (ArjArchiveInputStream.matches(signature, signatureLength)) {
242 return ARJ;
243 }
244 if (SevenZFile.matches(signature, signatureLength)) {
245 return SEVEN_Z;
246 }
247
248 final byte[] dumpsig = new byte[DUMP_SIGNATURE_SIZE];
249 in.mark(dumpsig.length);
250 try {
251 signatureLength = IOUtils.readFully(in, dumpsig);
252 in.reset();
253 } catch (final IOException e) {
254 throw new ArchiveException("IOException while reading dump signature", (Throwable) e);
255 }
256 if (DumpArchiveInputStream.matches(dumpsig, signatureLength)) {
257 return DUMP;
258 }
259
260 final byte[] tarHeader = new byte[TAR_HEADER_SIZE];
261 in.mark(tarHeader.length);
262 try {
263 signatureLength = IOUtils.readFully(in, tarHeader);
264 in.reset();
265 } catch (final IOException e) {
266 throw new ArchiveException("IOException while reading tar signature", (Throwable) e);
267 }
268 if (TarArchiveInputStream.matches(tarHeader, signatureLength)) {
269 return TAR;
270 }
271
272 if (signatureLength >= TAR_HEADER_SIZE) {
273 try (TarArchiveInputStream inputStream = new TarArchiveInputStream(new ByteArrayInputStream(tarHeader))) {
274
275 TarArchiveEntry entry = inputStream.getNextEntry();
276
277 int count = 0;
278 while (entry != null && entry.isDirectory() && entry.isCheckSumOK() && count++ < TAR_TEST_ENTRY_COUNT) {
279 entry = inputStream.getNextEntry();
280 }
281 if (entry != null && entry.isCheckSumOK() && !entry.isDirectory() && isName(entry.getGroupName()) && isName(entry.getName())
282 && isName(entry.getUserName()) || count > 0) {
283 return TAR;
284 }
285 } catch (final Exception ignored) {
286
287 }
288 }
289 throw new ArchiveException("No Archiver found for the stream signature");
290 }
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312 public static SortedMap<String, ArchiveStreamProvider> findAvailableArchiveInputStreamProviders() {
313 return AccessController.doPrivileged((PrivilegedAction<SortedMap<String, ArchiveStreamProvider>>) () -> {
314 final TreeMap<String, ArchiveStreamProvider> map = new TreeMap<>();
315 putAll(DEFAULT.getInputStreamArchiveNames(), DEFAULT, map);
316 archiveStreamProviderIterable().forEach(provider -> putAll(provider.getInputStreamArchiveNames(), provider, map));
317 return map;
318 });
319 }
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341 public static SortedMap<String, ArchiveStreamProvider> findAvailableArchiveOutputStreamProviders() {
342 return AccessController.doPrivileged((PrivilegedAction<SortedMap<String, ArchiveStreamProvider>>) () -> {
343 final TreeMap<String, ArchiveStreamProvider> map = new TreeMap<>();
344 putAll(DEFAULT.getOutputStreamArchiveNames(), DEFAULT, map);
345 archiveStreamProviderIterable().forEach(provider -> putAll(provider.getOutputStreamArchiveNames(), provider, map));
346 return map;
347 });
348 }
349
350 private static boolean isName(final String value) {
351
352 return value.isEmpty() || value.chars().allMatch(ch -> ch > 31 && ch < 128);
353 }
354
355 static void putAll(final Set<String> names, final ArchiveStreamProvider provider, final TreeMap<String, ArchiveStreamProvider> map) {
356 names.forEach(name -> map.put(toKey(name), provider));
357 }
358
359 private static String toKey(final String name) {
360 return StringUtils.toRootUpperCase(name);
361 }
362
363
364
365
366 private volatile String entryEncoding;
367
368 private SortedMap<String, ArchiveStreamProvider> archiveInputStreamProviders;
369
370 private SortedMap<String, ArchiveStreamProvider> archiveOutputStreamProviders;
371
372
373
374
375 public ArchiveStreamFactory() {
376 this(null);
377 }
378
379
380
381
382
383
384
385 public ArchiveStreamFactory(final String entryEncoding) {
386 this.entryEncoding = entryEncoding;
387 }
388
389
390
391
392
393
394
395
396
397
398
399
400 public <I extends ArchiveInputStream<? extends ArchiveEntry>> I createArchiveInputStream(final InputStream in) throws ArchiveException {
401 return createArchiveInputStream(detect(in), in);
402 }
403
404
405
406
407
408
409
410
411
412
413
414
415
416 public <I extends ArchiveInputStream<? extends ArchiveEntry>> I createArchiveInputStream(final String archiverName, final InputStream in)
417 throws ArchiveException {
418 return createArchiveInputStream(archiverName, in, entryEncoding);
419 }
420
421 @SuppressWarnings("unchecked")
422 @Override
423 public <I extends ArchiveInputStream<? extends ArchiveEntry>> I createArchiveInputStream(final String archiverName, final InputStream in,
424 final String actualEncoding) throws ArchiveException {
425 if (archiverName == null) {
426 throw new IllegalArgumentException("Archiver name must not be null.");
427 }
428 if (in == null) {
429 throw new IllegalArgumentException("InputStream must not be null.");
430 }
431 if (AR.equalsIgnoreCase(archiverName)) {
432 return (I) new ArArchiveInputStream(in);
433 }
434 if (ARJ.equalsIgnoreCase(archiverName)) {
435 if (actualEncoding != null) {
436 return (I) new ArjArchiveInputStream(in, actualEncoding);
437 }
438 return (I) new ArjArchiveInputStream(in);
439 }
440 if (ZIP.equalsIgnoreCase(archiverName)) {
441 if (actualEncoding != null) {
442 return (I) new ZipArchiveInputStream(in, actualEncoding);
443 }
444 return (I) new ZipArchiveInputStream(in);
445 }
446 if (TAR.equalsIgnoreCase(archiverName)) {
447 if (actualEncoding != null) {
448 return (I) new TarArchiveInputStream(in, actualEncoding);
449 }
450 return (I) new TarArchiveInputStream(in);
451 }
452 if (JAR.equalsIgnoreCase(archiverName) || APK.equalsIgnoreCase(archiverName)) {
453 if (actualEncoding != null) {
454 return (I) new JarArchiveInputStream(in, actualEncoding);
455 }
456 return (I) new JarArchiveInputStream(in);
457 }
458 if (CPIO.equalsIgnoreCase(archiverName)) {
459 if (actualEncoding != null) {
460 return (I) new CpioArchiveInputStream(in, actualEncoding);
461 }
462 return (I) new CpioArchiveInputStream(in);
463 }
464 if (DUMP.equalsIgnoreCase(archiverName)) {
465 if (actualEncoding != null) {
466 return (I) new DumpArchiveInputStream(in, actualEncoding);
467 }
468 return (I) new DumpArchiveInputStream(in);
469 }
470 if (SEVEN_Z.equalsIgnoreCase(archiverName)) {
471 throw new StreamingNotSupportedException(SEVEN_Z);
472 }
473 final ArchiveStreamProvider archiveStreamProvider = getArchiveInputStreamProviders().get(toKey(archiverName));
474 if (archiveStreamProvider != null) {
475 return archiveStreamProvider.createArchiveInputStream(archiverName, in, actualEncoding);
476 }
477 throw new ArchiveException("Archiver: " + archiverName + " not found.");
478 }
479
480
481
482
483
484
485
486
487
488
489
490
491 public <O extends ArchiveOutputStream<? extends ArchiveEntry>> O createArchiveOutputStream(final String archiverName, final OutputStream out)
492 throws ArchiveException {
493 return createArchiveOutputStream(archiverName, out, entryEncoding);
494 }
495
496 @SuppressWarnings("unchecked")
497 @Override
498 public <O extends ArchiveOutputStream<? extends ArchiveEntry>> O createArchiveOutputStream(final String archiverName, final OutputStream out,
499 final String actualEncoding) throws ArchiveException {
500 if (archiverName == null) {
501 throw new IllegalArgumentException("Archiver name must not be null.");
502 }
503 if (out == null) {
504 throw new IllegalArgumentException("OutputStream must not be null.");
505 }
506 if (AR.equalsIgnoreCase(archiverName)) {
507 return (O) new ArArchiveOutputStream(out);
508 }
509 if (ZIP.equalsIgnoreCase(archiverName)) {
510 final ZipArchiveOutputStream zip = new ZipArchiveOutputStream(out);
511 if (actualEncoding != null) {
512 zip.setEncoding(actualEncoding);
513 }
514 return (O) zip;
515 }
516 if (TAR.equalsIgnoreCase(archiverName)) {
517 if (actualEncoding != null) {
518 return (O) new TarArchiveOutputStream(out, actualEncoding);
519 }
520 return (O) new TarArchiveOutputStream(out);
521 }
522 if (JAR.equalsIgnoreCase(archiverName)) {
523 if (actualEncoding != null) {
524 return (O) new JarArchiveOutputStream(out, actualEncoding);
525 }
526 return (O) new JarArchiveOutputStream(out);
527 }
528 if (CPIO.equalsIgnoreCase(archiverName)) {
529 if (actualEncoding != null) {
530 return (O) new CpioArchiveOutputStream(out, actualEncoding);
531 }
532 return (O) new CpioArchiveOutputStream(out);
533 }
534 if (SEVEN_Z.equalsIgnoreCase(archiverName)) {
535 throw new StreamingNotSupportedException(SEVEN_Z);
536 }
537 final ArchiveStreamProvider archiveStreamProvider = getArchiveOutputStreamProviders().get(toKey(archiverName));
538 if (archiveStreamProvider != null) {
539 return archiveStreamProvider.createArchiveOutputStream(archiverName, out, actualEncoding);
540 }
541 throw new ArchiveException("Archiver: " + archiverName + " not found.");
542 }
543
544
545
546
547
548
549
550 public SortedMap<String, ArchiveStreamProvider> getArchiveInputStreamProviders() {
551 if (archiveInputStreamProviders == null) {
552 archiveInputStreamProviders = Collections.unmodifiableSortedMap(findAvailableArchiveInputStreamProviders());
553 }
554 return archiveInputStreamProviders;
555 }
556
557
558
559
560
561
562
563 public SortedMap<String, ArchiveStreamProvider> getArchiveOutputStreamProviders() {
564 if (archiveOutputStreamProviders == null) {
565 archiveOutputStreamProviders = Collections.unmodifiableSortedMap(findAvailableArchiveOutputStreamProviders());
566 }
567 return archiveOutputStreamProviders;
568 }
569
570
571
572
573
574
575
576 public String getEntryEncoding() {
577 return entryEncoding;
578 }
579
580 @Override
581 public Set<String> getInputStreamArchiveNames() {
582 return Sets.newHashSet(AR, ARJ, ZIP, TAR, JAR, CPIO, DUMP, SEVEN_Z);
583 }
584
585 @Override
586 public Set<String> getOutputStreamArchiveNames() {
587 return Sets.newHashSet(AR, ZIP, TAR, JAR, CPIO, SEVEN_Z);
588 }
589
590
591
592
593
594
595
596
597 @Deprecated
598 public void setEntryEncoding(final String entryEncoding) {
599 this.entryEncoding = entryEncoding;
600 }
601
602 }