1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.commons.compress.compressors;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.security.AccessController;
25 import java.security.PrivilegedAction;
26 import java.util.Collections;
27 import java.util.ServiceLoader;
28 import java.util.Set;
29 import java.util.SortedMap;
30 import java.util.TreeMap;
31
32 import org.apache.commons.compress.compressors.brotli.BrotliCompressorInputStream;
33 import org.apache.commons.compress.compressors.brotli.BrotliUtils;
34 import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
35 import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
36 import org.apache.commons.compress.compressors.deflate.DeflateCompressorInputStream;
37 import org.apache.commons.compress.compressors.deflate.DeflateCompressorOutputStream;
38 import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream;
39 import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
40 import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
41 import org.apache.commons.compress.compressors.lz4.BlockLZ4CompressorInputStream;
42 import org.apache.commons.compress.compressors.lz4.BlockLZ4CompressorOutputStream;
43 import org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorInputStream;
44 import org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorOutputStream;
45 import org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream;
46 import org.apache.commons.compress.compressors.lzma.LZMACompressorOutputStream;
47 import org.apache.commons.compress.compressors.lzma.LZMAUtils;
48 import org.apache.commons.compress.compressors.pack200.Pack200CompressorInputStream;
49 import org.apache.commons.compress.compressors.pack200.Pack200CompressorOutputStream;
50 import org.apache.commons.compress.compressors.snappy.FramedSnappyCompressorInputStream;
51 import org.apache.commons.compress.compressors.snappy.FramedSnappyCompressorOutputStream;
52 import org.apache.commons.compress.compressors.snappy.SnappyCompressorInputStream;
53 import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
54 import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
55 import org.apache.commons.compress.compressors.xz.XZUtils;
56 import org.apache.commons.compress.compressors.z.ZCompressorInputStream;
57 import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream;
58 import org.apache.commons.compress.compressors.zstandard.ZstdCompressorOutputStream;
59 import org.apache.commons.compress.compressors.zstandard.ZstdUtils;
60 import org.apache.commons.compress.utils.IOUtils;
61 import org.apache.commons.compress.utils.Sets;
62 import org.apache.commons.lang3.StringUtils;
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91 public class CompressorStreamFactory implements CompressorStreamProvider {
92
93 private static final CompressorStreamFactory SINGLETON = new CompressorStreamFactory();
94
95
96
97
98
99
100 public static final String BROTLI = "br";
101
102
103
104
105
106
107 public static final String BZIP2 = "bzip2";
108
109
110
111
112
113
114 public static final String GZIP = "gz";
115
116
117
118
119
120
121 public static final String PACK200 = "pack200";
122
123
124
125
126
127
128 public static final String XZ = "xz";
129
130
131
132
133
134
135 public static final String LZMA = "lzma";
136
137
138
139
140
141
142 public static final String SNAPPY_FRAMED = "snappy-framed";
143
144
145
146
147
148
149 public static final String SNAPPY_RAW = "snappy-raw";
150
151
152
153
154
155
156 public static final String Z = "z";
157
158
159
160
161
162
163 public static final String DEFLATE = "deflate";
164
165
166
167
168
169
170 public static final String DEFLATE64 = "deflate64";
171
172
173
174
175
176
177 public static final String LZ4_BLOCK = "lz4-block";
178
179
180
181
182
183
184 public static final String LZ4_FRAMED = "lz4-framed";
185
186
187
188
189
190
191 public static final String ZSTANDARD = "zstd";
192
193 private static final String YOU_NEED_BROTLI_DEC = youNeed("Google Brotli Dec", "https://github.com/google/brotli/");
194 private static final String YOU_NEED_XZ_JAVA = youNeed("XZ for Java", "https://tukaani.org/xz/java.html");
195 private static final String YOU_NEED_ZSTD_JNI = youNeed("Zstd JNI", "https://github.com/luben/zstd-jni");
196
197 private static final Set<String> ALL_NAMES = Sets.newHashSet(BZIP2, GZIP, PACK200, SNAPPY_FRAMED, Z, DEFLATE, XZ, LZMA, LZ4_FRAMED, ZSTANDARD);
198
199 private static Iterable<CompressorStreamProvider> archiveStreamProviderIterable() {
200 return ServiceLoader.load(CompressorStreamProvider.class, ClassLoader.getSystemClassLoader());
201 }
202
203
204
205
206
207
208
209
210
211
212 public static String detect(final InputStream inputStream) throws CompressorException {
213 return detect(inputStream, ALL_NAMES);
214 }
215
216
217
218
219
220
221
222
223
224
225 static String detect(final InputStream inputStream, final Set<String> compressorNames) throws CompressorException {
226 if (inputStream == null) {
227 throw new IllegalArgumentException("Stream must not be null.");
228 }
229 if (compressorNames == null || compressorNames.isEmpty()) {
230 throw new IllegalArgumentException("Compressor names cannot be null or empty");
231 }
232 if (!inputStream.markSupported()) {
233 throw new IllegalArgumentException("Mark is not supported.");
234 }
235 final byte[] signature = new byte[12];
236 inputStream.mark(signature.length);
237 int signatureLength = -1;
238 try {
239 signatureLength = IOUtils.readFully(inputStream, signature);
240 inputStream.reset();
241 } catch (final IOException e) {
242 throw new CompressorException("Failed to read signature.", e);
243 }
244 if (compressorNames.contains(BZIP2) && BZip2CompressorInputStream.matches(signature, signatureLength)) {
245 return BZIP2;
246 }
247 if (compressorNames.contains(GZIP) && GzipCompressorInputStream.matches(signature, signatureLength)) {
248 return GZIP;
249 }
250 if (compressorNames.contains(PACK200) && Pack200CompressorInputStream.matches(signature, signatureLength)) {
251 return PACK200;
252 }
253 if (compressorNames.contains(SNAPPY_FRAMED) && FramedSnappyCompressorInputStream.matches(signature, signatureLength)) {
254 return SNAPPY_FRAMED;
255 }
256 if (compressorNames.contains(Z) && ZCompressorInputStream.matches(signature, signatureLength)) {
257 return Z;
258 }
259 if (compressorNames.contains(DEFLATE) && DeflateCompressorInputStream.matches(signature, signatureLength)) {
260 return DEFLATE;
261 }
262 if (compressorNames.contains(XZ) && XZUtils.matches(signature, signatureLength)) {
263 return XZ;
264 }
265 if (compressorNames.contains(LZMA) && LZMAUtils.matches(signature, signatureLength)) {
266 return LZMA;
267 }
268 if (compressorNames.contains(LZ4_FRAMED) && FramedLZ4CompressorInputStream.matches(signature, signatureLength)) {
269 return LZ4_FRAMED;
270 }
271 if (compressorNames.contains(ZSTANDARD) && ZstdUtils.matches(signature, signatureLength)) {
272 return ZSTANDARD;
273 }
274 throw new CompressorException("No Compressor found for the stream signature.");
275 }
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297 public static SortedMap<String, CompressorStreamProvider> findAvailableCompressorInputStreamProviders() {
298 return AccessController.doPrivileged((PrivilegedAction<SortedMap<String, CompressorStreamProvider>>) () -> {
299 final TreeMap<String, CompressorStreamProvider> map = new TreeMap<>();
300 putAll(SINGLETON.getInputStreamCompressorNames(), SINGLETON, map);
301 archiveStreamProviderIterable().forEach(provider -> putAll(provider.getInputStreamCompressorNames(), provider, map));
302 return map;
303 });
304 }
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326 public static SortedMap<String, CompressorStreamProvider> findAvailableCompressorOutputStreamProviders() {
327 return AccessController.doPrivileged((PrivilegedAction<SortedMap<String, CompressorStreamProvider>>) () -> {
328 final TreeMap<String, CompressorStreamProvider> map = new TreeMap<>();
329 putAll(SINGLETON.getOutputStreamCompressorNames(), SINGLETON, map);
330 archiveStreamProviderIterable().forEach(provider -> putAll(provider.getOutputStreamCompressorNames(), provider, map));
331 return map;
332 });
333 }
334
335
336
337
338
339
340 public static String getBrotli() {
341 return BROTLI;
342 }
343
344
345
346
347
348
349 public static String getBzip2() {
350 return BZIP2;
351 }
352
353
354
355
356
357
358 public static String getDeflate() {
359 return DEFLATE;
360 }
361
362
363
364
365
366
367
368 public static String getDeflate64() {
369 return DEFLATE64;
370 }
371
372
373
374
375
376
377 public static String getGzip() {
378 return GZIP;
379 }
380
381
382
383
384
385
386 public static String getLZ4Block() {
387 return LZ4_BLOCK;
388 }
389
390
391
392
393
394
395 public static String getLZ4Framed() {
396 return LZ4_FRAMED;
397 }
398
399
400
401
402
403
404 public static String getLzma() {
405 return LZMA;
406 }
407
408
409
410
411
412
413 public static String getPack200() {
414 return PACK200;
415 }
416
417
418
419
420
421
422 public static CompressorStreamFactory getSingleton() {
423 return SINGLETON;
424 }
425
426
427
428
429
430
431 public static String getSnappyFramed() {
432 return SNAPPY_FRAMED;
433 }
434
435
436
437
438
439
440 public static String getSnappyRaw() {
441 return SNAPPY_RAW;
442 }
443
444
445
446
447
448
449 public static String getXz() {
450 return XZ;
451 }
452
453
454
455
456
457
458 public static String getZ() {
459 return Z;
460 }
461
462
463
464
465
466
467 public static String getZstandard() {
468 return ZSTANDARD;
469 }
470
471 static void putAll(final Set<String> names, final CompressorStreamProvider provider, final TreeMap<String, CompressorStreamProvider> map) {
472 names.forEach(name -> map.put(toKey(name), provider));
473 }
474
475 private static String toKey(final String name) {
476 return StringUtils.toRootUpperCase(name);
477 }
478
479 private static String youNeed(final String name, final String url) {
480 return " In addition to Apache Commons Compress you need the " + name + " library - see " + url;
481 }
482
483
484
485
486
487 private final Boolean decompressUntilEof;
488
489
490
491
492
493 private SortedMap<String, CompressorStreamProvider> compressorInputStreamProviders;
494
495 private SortedMap<String, CompressorStreamProvider> compressorOutputStreamProviders;
496
497
498
499
500
501 private volatile boolean decompressConcatenated;
502
503 private final int memoryLimitInKb;
504
505
506
507
508 public CompressorStreamFactory() {
509 this.decompressUntilEof = null;
510 this.memoryLimitInKb = -1;
511 }
512
513
514
515
516
517
518
519
520 public CompressorStreamFactory(final boolean decompressUntilEOF) {
521 this(decompressUntilEOF, -1);
522 }
523
524
525
526
527
528
529
530
531
532
533
534 public CompressorStreamFactory(final boolean decompressUntilEOF, final int memoryLimitInKb) {
535 this.decompressUntilEof = decompressUntilEOF;
536
537
538 this.decompressConcatenated = decompressUntilEOF;
539 this.memoryLimitInKb = memoryLimitInKb;
540 }
541
542
543
544
545
546
547
548
549
550
551
552 public CompressorInputStream createCompressorInputStream(final InputStream in) throws CompressorException {
553 return createCompressorInputStream(detect(in), in);
554 }
555
556
557
558
559
560
561
562
563
564
565
566
567 public CompressorInputStream createCompressorInputStream(final InputStream in, final Set<String> compressorNames) throws CompressorException {
568 return createCompressorInputStream(detect(in, compressorNames), in);
569 }
570
571
572
573
574
575
576
577
578
579
580
581
582
583 public CompressorInputStream createCompressorInputStream(final String name, final InputStream in) throws CompressorException {
584 return createCompressorInputStream(name, in, decompressConcatenated);
585 }
586
587 @Override
588 public CompressorInputStream createCompressorInputStream(final String name, final InputStream in, final boolean actualDecompressConcatenated)
589 throws CompressorException {
590 if (name == null || in == null) {
591 throw new IllegalArgumentException("Compressor name and stream must not be null.");
592 }
593 try {
594 if (GZIP.equalsIgnoreCase(name)) {
595
596 return GzipCompressorInputStream.builder()
597 .setInputStream(in)
598 .setDecompressConcatenated(actualDecompressConcatenated)
599 .get();
600
601 }
602 if (BZIP2.equalsIgnoreCase(name)) {
603 return new BZip2CompressorInputStream(in, actualDecompressConcatenated);
604 }
605 if (BROTLI.equalsIgnoreCase(name)) {
606 if (!BrotliUtils.isBrotliCompressionAvailable()) {
607 throw new CompressorException("Brotli compression is not available." + YOU_NEED_BROTLI_DEC);
608 }
609 return new BrotliCompressorInputStream(in);
610 }
611 if (XZ.equalsIgnoreCase(name)) {
612 if (!XZUtils.isXZCompressionAvailable()) {
613 throw new CompressorException("XZ compression is not available." + YOU_NEED_XZ_JAVA);
614 }
615
616 return XZCompressorInputStream.builder()
617 .setInputStream(in)
618 .setDecompressConcatenated(actualDecompressConcatenated)
619 .setMemoryLimitKiB(memoryLimitInKb)
620 .get();
621
622 }
623 if (ZSTANDARD.equalsIgnoreCase(name)) {
624 if (!ZstdUtils.isZstdCompressionAvailable()) {
625 throw new CompressorException("Zstandard compression is not available." + YOU_NEED_ZSTD_JNI);
626 }
627 return new ZstdCompressorInputStream(in);
628 }
629 if (LZMA.equalsIgnoreCase(name)) {
630 if (!LZMAUtils.isLZMACompressionAvailable()) {
631 throw new CompressorException("LZMA compression is not available" + YOU_NEED_XZ_JAVA);
632 }
633 return LZMACompressorInputStream.builder().setInputStream(in).setMemoryLimitKiB(memoryLimitInKb).get();
634 }
635 if (PACK200.equalsIgnoreCase(name)) {
636 return new Pack200CompressorInputStream(in);
637 }
638 if (SNAPPY_RAW.equalsIgnoreCase(name)) {
639 return new SnappyCompressorInputStream(in);
640 }
641 if (SNAPPY_FRAMED.equalsIgnoreCase(name)) {
642 return new FramedSnappyCompressorInputStream(in);
643 }
644 if (Z.equalsIgnoreCase(name)) {
645 return new ZCompressorInputStream(in, memoryLimitInKb);
646 }
647 if (DEFLATE.equalsIgnoreCase(name)) {
648 return new DeflateCompressorInputStream(in);
649 }
650 if (DEFLATE64.equalsIgnoreCase(name)) {
651 return new Deflate64CompressorInputStream(in);
652 }
653 if (LZ4_BLOCK.equalsIgnoreCase(name)) {
654 return new BlockLZ4CompressorInputStream(in);
655 }
656 if (LZ4_FRAMED.equalsIgnoreCase(name)) {
657 return new FramedLZ4CompressorInputStream(in, actualDecompressConcatenated);
658 }
659
660 } catch (final IOException e) {
661 throw new CompressorException("Could not create CompressorInputStream.", e);
662 }
663 final CompressorStreamProvider compressorStreamProvider = getCompressorInputStreamProviders().get(toKey(name));
664 if (compressorStreamProvider != null) {
665 return compressorStreamProvider.createCompressorInputStream(name, in, actualDecompressConcatenated);
666 }
667 throw new CompressorException("Compressor: " + name + " not found.");
668 }
669
670
671
672
673
674
675
676
677
678
679
680 @SuppressWarnings("unchecked")
681 @Override
682 public CompressorOutputStream<? extends OutputStream> createCompressorOutputStream(final String name, final OutputStream out) throws CompressorException {
683 if (name == null || out == null) {
684 throw new IllegalArgumentException("Compressor name and stream must not be null.");
685 }
686 try {
687 if (GZIP.equalsIgnoreCase(name)) {
688 return new GzipCompressorOutputStream(out);
689 }
690 if (BZIP2.equalsIgnoreCase(name)) {
691 return new BZip2CompressorOutputStream(out);
692 }
693 if (XZ.equalsIgnoreCase(name)) {
694 return new XZCompressorOutputStream(out);
695 }
696 if (PACK200.equalsIgnoreCase(name)) {
697 return new Pack200CompressorOutputStream(out);
698 }
699 if (LZMA.equalsIgnoreCase(name)) {
700 return new LZMACompressorOutputStream(out);
701 }
702 if (DEFLATE.equalsIgnoreCase(name)) {
703 return new DeflateCompressorOutputStream(out);
704 }
705 if (SNAPPY_FRAMED.equalsIgnoreCase(name)) {
706 return new FramedSnappyCompressorOutputStream(out);
707 }
708 if (LZ4_BLOCK.equalsIgnoreCase(name)) {
709 return new BlockLZ4CompressorOutputStream(out);
710 }
711 if (LZ4_FRAMED.equalsIgnoreCase(name)) {
712 return new FramedLZ4CompressorOutputStream(out);
713 }
714 if (ZSTANDARD.equalsIgnoreCase(name)) {
715 return new ZstdCompressorOutputStream(out);
716 }
717 } catch (final IOException e) {
718 throw new CompressorException("Could not create CompressorOutputStream.", e);
719 }
720 final CompressorStreamProvider compressorStreamProvider = getCompressorOutputStreamProviders().get(toKey(name));
721 if (compressorStreamProvider != null) {
722 return compressorStreamProvider.createCompressorOutputStream(name, out);
723 }
724 throw new CompressorException("Compressor: " + name + " not found.");
725 }
726
727
728
729
730
731
732 public SortedMap<String, CompressorStreamProvider> getCompressorInputStreamProviders() {
733 if (compressorInputStreamProviders == null) {
734 compressorInputStreamProviders = Collections.unmodifiableSortedMap(findAvailableCompressorInputStreamProviders());
735 }
736 return compressorInputStreamProviders;
737 }
738
739
740
741
742
743
744 public SortedMap<String, CompressorStreamProvider> getCompressorOutputStreamProviders() {
745 if (compressorOutputStreamProviders == null) {
746 compressorOutputStreamProviders = Collections.unmodifiableSortedMap(findAvailableCompressorOutputStreamProviders());
747 }
748 return compressorOutputStreamProviders;
749 }
750
751
752 boolean getDecompressConcatenated() {
753 return decompressConcatenated;
754 }
755
756
757
758
759
760
761
762 public Boolean getDecompressUntilEOF() {
763 return decompressUntilEof;
764 }
765
766 @Override
767 public Set<String> getInputStreamCompressorNames() {
768 return Sets.newHashSet(GZIP, BROTLI, BZIP2, XZ, LZMA, PACK200, DEFLATE, SNAPPY_RAW, SNAPPY_FRAMED, Z, LZ4_BLOCK, LZ4_FRAMED, ZSTANDARD, DEFLATE64);
769 }
770
771 @Override
772 public Set<String> getOutputStreamCompressorNames() {
773 return Sets.newHashSet(GZIP, BZIP2, XZ, LZMA, PACK200, DEFLATE, SNAPPY_FRAMED, LZ4_BLOCK, LZ4_FRAMED, ZSTANDARD);
774 }
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789 @Deprecated
790 public void setDecompressConcatenated(final boolean decompressConcatenated) {
791 if (this.decompressUntilEof != null) {
792 throw new IllegalStateException("Cannot override the setting defined by the constructor");
793 }
794 this.decompressConcatenated = decompressConcatenated;
795 }
796
797 }