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.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  
25  import org.apache.commons.compress.MemoryLimitException;
26  import org.tukaani.xz.FinishableWrapperOutputStream;
27  import org.tukaani.xz.LZMA2InputStream;
28  import org.tukaani.xz.LZMA2Options;
29  
30  final class LZMA2Decoder extends AbstractCoder {
31  
32      LZMA2Decoder() {
33          super(LZMA2Options.class, Number.class);
34      }
35  
36      @Override
37      InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, final Coder coder, final byte[] password,
38              final int maxMemoryLimitKiB) throws IOException {
39          try {
40              final int dictionarySize = getDictionarySize(coder);
41              final int memoryUsageInKiB = LZMA2InputStream.getMemoryUsage(dictionarySize);
42              if (memoryUsageInKiB > maxMemoryLimitKiB) {
43                  throw new MemoryLimitException(memoryUsageInKiB, maxMemoryLimitKiB);
44              }
45              return new LZMA2InputStream(in, dictionarySize);
46          } catch (final IllegalArgumentException ex) { // NOSONAR
47              throw new IOException(ex);
48          }
49      }
50  
51      @SuppressWarnings("resource") // Caller closes.
52      @Override
53      OutputStream encode(final OutputStream out, final Object opts) throws IOException {
54          return getOptions(opts).getOutputStream(new FinishableWrapperOutputStream(out));
55      }
56  
57      private int getDictionarySize(final Coder coder) throws IOException {
58          if (coder.properties == null) {
59              throw new IOException("Missing LZMA2 properties");
60          }
61          if (coder.properties.length < 1) {
62              throw new IOException("LZMA2 properties too short");
63          }
64          final int dictionarySizeBits = 0xff & coder.properties[0];
65          if ((dictionarySizeBits & ~0x3f) != 0) {
66              throw new IOException("Unsupported LZMA2 property bits");
67          }
68          if (dictionarySizeBits > 40) {
69              throw new IOException("Dictionary larger than 4GiB maximum size");
70          }
71          if (dictionarySizeBits == 40) {
72              return 0xFFFFffff;
73          }
74          return (2 | dictionarySizeBits & 0x1) << dictionarySizeBits / 2 + 11;
75      }
76  
77      private int getDictSize(final Object opts) {
78          if (opts instanceof LZMA2Options) {
79              return ((LZMA2Options) opts).getDictSize();
80          }
81          return numberOptionOrDefault(opts);
82      }
83  
84      private LZMA2Options getOptions(final Object opts) throws IOException {
85          if (opts instanceof LZMA2Options) {
86              return (LZMA2Options) opts;
87          }
88          final LZMA2Options options = new LZMA2Options();
89          options.setDictSize(numberOptionOrDefault(opts));
90          return options;
91      }
92  
93      @Override
94      byte[] getOptionsAsProperties(final Object opts) {
95          final int dictSize = getDictSize(opts);
96          final int lead = Integer.numberOfLeadingZeros(dictSize);
97          final int secondBit = (dictSize >>> 30 - lead) - 2;
98          return new byte[] { (byte) ((19 - lead) * 2 + secondBit) };
99      }
100 
101     @Override
102     Object getOptionsFromCoder(final Coder coder, final InputStream in) throws IOException {
103         return getDictionarySize(coder);
104     }
105 
106     private int numberOptionOrDefault(final Object opts) {
107         return toInt(opts, LZMA2Options.DICT_SIZE_DEFAULT);
108     }
109 }