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