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