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.apache.commons.compress.utils.ByteUtils;
27  import org.apache.commons.compress.utils.FlushShieldFilterOutputStream;
28  import org.tukaani.xz.LZMA2Options;
29  import org.tukaani.xz.LZMAInputStream;
30  import org.tukaani.xz.LZMAOutputStream;
31  
32  final class LZMADecoder extends AbstractCoder {
33  
34      LZMADecoder() {
35          super(LZMA2Options.class, Number.class);
36      }
37  
38      @Override
39      InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, final Coder coder, final byte[] password,
40              final int maxMemoryLimitKiB) throws IOException {
41          if (coder.properties == null) {
42              throw new IOException("Missing LZMA properties");
43          }
44          if (coder.properties.length < 1) {
45              throw new IOException("LZMA properties too short");
46          }
47          final byte propsByte = coder.properties[0];
48          final int dictSize = getDictionarySize(coder);
49          if (dictSize > LZMAInputStream.DICT_SIZE_MAX) {
50              throw new IOException("Dictionary larger than 4GiB maximum size used in " + archiveName);
51          }
52          final int memoryUsageInKiB = LZMAInputStream.getMemoryUsage(dictSize, propsByte);
53          if (memoryUsageInKiB > maxMemoryLimitKiB) {
54              throw new MemoryLimitException(memoryUsageInKiB, maxMemoryLimitKiB);
55          }
56          final LZMAInputStream lzmaIn = new LZMAInputStream(in, uncompressedLength, propsByte, dictSize);
57          lzmaIn.enableRelaxedEndCondition();
58          return lzmaIn;
59      }
60  
61      @Override
62      OutputStream encode(final OutputStream out, final Object opts) throws IOException {
63          // NOOP as LZMAOutputStream throws an exception in flush
64          return new FlushShieldFilterOutputStream(new LZMAOutputStream(out, getOptions(opts), false));
65      }
66  
67      private int getDictionarySize(final Coder coder) throws IllegalArgumentException {
68          return (int) ByteUtils.fromLittleEndian(coder.properties, 1, 4);
69      }
70  
71      private LZMA2Options getOptions(final Object opts) throws IOException {
72          if (opts instanceof LZMA2Options) {
73              return (LZMA2Options) opts;
74          }
75          final LZMA2Options options = new LZMA2Options();
76          options.setDictSize(numberOptionOrDefault(opts));
77          return options;
78      }
79  
80      @Override
81      byte[] getOptionsAsProperties(final Object opts) throws IOException {
82          final LZMA2Options options = getOptions(opts);
83          final byte props = (byte) ((options.getPb() * 5 + options.getLp()) * 9 + options.getLc());
84          final int dictSize = options.getDictSize();
85          final byte[] o = new byte[5];
86          o[0] = props;
87          ByteUtils.toLittleEndian(o, dictSize, 1, 4);
88          return o;
89      }
90  
91      @Override
92      Object getOptionsFromCoder(final Coder coder, final InputStream in) throws IOException {
93          if (coder.properties == null) {
94              throw new IOException("Missing LZMA properties");
95          }
96          if (coder.properties.length < 1) {
97              throw new IOException("LZMA properties too short");
98          }
99          final byte propsByte = coder.properties[0];
100         int props = propsByte & 0xFF;
101         final int pb = props / (9 * 5);
102         props -= pb * 9 * 5;
103         final int lp = props / 9;
104         final int lc = props - lp * 9;
105         final LZMA2Options opts = new LZMA2Options();
106         opts.setPb(pb);
107         opts.setLcLp(lc, lp);
108         opts.setDictSize(getDictionarySize(coder));
109         return opts;
110     }
111 
112     private int numberOptionOrDefault(final Object opts) {
113         return toInt(opts, LZMA2Options.DICT_SIZE_DEFAULT);
114     }
115 }