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