View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.commons.compress.archivers.sevenz;
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.FilterInputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.io.SequenceInputStream;
25  import java.util.Arrays;
26  import java.util.HashMap;
27  import java.util.Map;
28  import java.util.zip.Deflater;
29  import java.util.zip.DeflaterOutputStream;
30  import java.util.zip.Inflater;
31  import java.util.zip.InflaterInputStream;
32  
33  import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
34  import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
35  import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream;
36  import org.apache.commons.compress.utils.FlushShieldFilterOutputStream;
37  import org.tukaani.xz.ARMOptions;
38  import org.tukaani.xz.ARMThumbOptions;
39  import org.tukaani.xz.FilterOptions;
40  import org.tukaani.xz.FinishableWrapperOutputStream;
41  import org.tukaani.xz.IA64Options;
42  import org.tukaani.xz.PowerPCOptions;
43  import org.tukaani.xz.SPARCOptions;
44  import org.tukaani.xz.X86Options;
45  
46  final class Coders {
47      static class BCJDecoder extends AbstractCoder {
48          private final FilterOptions opts;
49  
50          BCJDecoder(final FilterOptions opts) {
51              this.opts = opts;
52          }
53  
54          @Override
55          InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, final Coder coder, final byte[] password,
56                  final int maxMemoryLimitInKb) throws IOException {
57              try {
58                  return opts.getInputStream(in);
59              } catch (final AssertionError e) {
60                  throw new IOException("BCJ filter used in " + archiveName + " needs XZ for Java > 1.4 - see "
61                          + "https://commons.apache.org/proper/commons-compress/limitations.html#7Z", e);
62              }
63          }
64  
65          @SuppressWarnings("resource")
66          @Override
67          OutputStream encode(final OutputStream out, final Object options) {
68              return new FlushShieldFilterOutputStream(opts.getOutputStream(new FinishableWrapperOutputStream(out)));
69          }
70      }
71  
72      static class BZIP2Decoder extends AbstractCoder {
73          BZIP2Decoder() {
74              super(Number.class);
75          }
76  
77          @Override
78          InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, final Coder coder, final byte[] password,
79                  final int maxMemoryLimitInKb) throws IOException {
80              return new BZip2CompressorInputStream(in);
81          }
82  
83          @Override
84          OutputStream encode(final OutputStream out, final Object options) throws IOException {
85              final int blockSize = toInt(options, BZip2CompressorOutputStream.MAX_BLOCKSIZE);
86              return new BZip2CompressorOutputStream(out, blockSize);
87          }
88      }
89  
90      static class CopyDecoder extends AbstractCoder {
91          @Override
92          InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, final Coder coder, final byte[] password,
93                  final int maxMemoryLimitInKb) throws IOException {
94              return in;
95          }
96  
97          @Override
98          OutputStream encode(final OutputStream out, final Object options) {
99              return out;
100         }
101     }
102 
103     static class Deflate64Decoder extends AbstractCoder {
104         Deflate64Decoder() {
105             super(Number.class);
106         }
107 
108         @Override
109         InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, final Coder coder, final byte[] password,
110                 final int maxMemoryLimitInKb) throws IOException {
111             return new Deflate64CompressorInputStream(in);
112         }
113     }
114 
115     static class DeflateDecoder extends AbstractCoder {
116         static class DeflateDecoderInputStream extends FilterInputStream {
117 
118             Inflater inflater;
119 
120             DeflateDecoderInputStream(final InflaterInputStream inflaterInputStream, final Inflater inflater) {
121                 super(inflaterInputStream);
122                 this.inflater = inflater;
123             }
124 
125             @Override
126             public void close() throws IOException {
127                 try {
128                     super.close();
129                 } finally {
130                     inflater.end();
131                 }
132             }
133 
134         }
135 
136         static class DeflateDecoderOutputStream extends OutputStream {
137 
138             final DeflaterOutputStream deflaterOutputStream;
139             Deflater deflater;
140 
141             DeflateDecoderOutputStream(final DeflaterOutputStream deflaterOutputStream, final Deflater deflater) {
142                 this.deflaterOutputStream = deflaterOutputStream;
143                 this.deflater = deflater;
144             }
145 
146             @Override
147             public void close() throws IOException {
148                 try {
149                     deflaterOutputStream.close();
150                 } finally {
151                     deflater.end();
152                 }
153             }
154 
155             @Override
156             public void write(final byte[] b) throws IOException {
157                 deflaterOutputStream.write(b);
158             }
159 
160             @Override
161             public void write(final byte[] b, final int off, final int len) throws IOException {
162                 deflaterOutputStream.write(b, off, len);
163             }
164 
165             @Override
166             public void write(final int b) throws IOException {
167                 deflaterOutputStream.write(b);
168             }
169         }
170 
171         private static final byte[] ONE_ZERO_BYTE = new byte[1];
172 
173         DeflateDecoder() {
174             super(Number.class);
175         }
176 
177         @Override
178         InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, final Coder coder, final byte[] password,
179                 final int maxMemoryLimitInKb) throws IOException {
180             final Inflater inflater = new Inflater(true);
181             // Inflater with nowrap=true has this odd contract for a zero padding
182             // byte following the data stream; this used to be zlib's requirement
183             // and has been fixed a long time ago, but the contract persists so
184             // we comply.
185             // https://docs.oracle.com/javase/8/docs/api/java/util/zip/Inflater.html#Inflater(boolean)
186             final InflaterInputStream inflaterInputStream = new InflaterInputStream(new SequenceInputStream(in, new ByteArrayInputStream(ONE_ZERO_BYTE)),
187                     inflater);
188             return new DeflateDecoderInputStream(inflaterInputStream, inflater);
189         }
190 
191         @Override
192         OutputStream encode(final OutputStream out, final Object options) {
193             final int level = toInt(options, 9);
194             final Deflater deflater = new Deflater(level, true);
195             final DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(out, deflater);
196             return new DeflateDecoderOutputStream(deflaterOutputStream, deflater);
197         }
198     }
199 
200     private static final Map<SevenZMethod, AbstractCoder> CODER_MAP = new HashMap<SevenZMethod, AbstractCoder>() {
201 
202         private static final long serialVersionUID = 1664829131806520867L;
203 
204         {
205             put(SevenZMethod.COPY, new CopyDecoder());
206             put(SevenZMethod.LZMA, new LZMADecoder());
207             put(SevenZMethod.LZMA2, new LZMA2Decoder());
208             put(SevenZMethod.DEFLATE, new DeflateDecoder());
209             put(SevenZMethod.DEFLATE64, new Deflate64Decoder());
210             put(SevenZMethod.BZIP2, new BZIP2Decoder());
211             put(SevenZMethod.AES256SHA256, new AES256SHA256Decoder());
212             put(SevenZMethod.BCJ_X86_FILTER, new BCJDecoder(new X86Options()));
213             put(SevenZMethod.BCJ_PPC_FILTER, new BCJDecoder(new PowerPCOptions()));
214             put(SevenZMethod.BCJ_IA64_FILTER, new BCJDecoder(new IA64Options()));
215             put(SevenZMethod.BCJ_ARM_FILTER, new BCJDecoder(new ARMOptions()));
216             put(SevenZMethod.BCJ_ARM_THUMB_FILTER, new BCJDecoder(new ARMThumbOptions()));
217             put(SevenZMethod.BCJ_SPARC_FILTER, new BCJDecoder(new SPARCOptions()));
218             put(SevenZMethod.DELTA_FILTER, new DeltaDecoder());
219         }
220     };
221 
222     static InputStream addDecoder(final String archiveName, final InputStream is, final long uncompressedLength, final Coder coder, final byte[] password,
223             final int maxMemoryLimitInKb) throws IOException {
224         final AbstractCoder cb = findByMethod(SevenZMethod.byId(coder.decompressionMethodId));
225         if (cb == null) {
226             throw new IOException("Unsupported compression method " + Arrays.toString(coder.decompressionMethodId) + " used in " + archiveName);
227         }
228         return cb.decode(archiveName, is, uncompressedLength, coder, password, maxMemoryLimitInKb);
229     }
230 
231     static OutputStream addEncoder(final OutputStream out, final SevenZMethod method, final Object options) throws IOException {
232         final AbstractCoder cb = findByMethod(method);
233         if (cb == null) {
234             throw new IOException("Unsupported compression method " + method);
235         }
236         return cb.encode(out, options);
237     }
238 
239     static AbstractCoder findByMethod(final SevenZMethod method) {
240         return CODER_MAP.get(method);
241     }
242 
243 }