LZMADecoder.java

  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. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.io.OutputStream;

  21. import org.apache.commons.compress.MemoryLimitException;
  22. import org.apache.commons.compress.utils.ByteUtils;
  23. import org.apache.commons.compress.utils.FlushShieldFilterOutputStream;
  24. import org.tukaani.xz.LZMA2Options;
  25. import org.tukaani.xz.LZMAInputStream;
  26. import org.tukaani.xz.LZMAOutputStream;

  27. final class LZMADecoder extends AbstractCoder {

  28.     LZMADecoder() {
  29.         super(LZMA2Options.class, Number.class);
  30.     }

  31.     @Override
  32.     InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, final Coder coder, final byte[] password,
  33.             final int maxMemoryLimitInKb) throws IOException {
  34.         if (coder.properties == null) {
  35.             throw new IOException("Missing LZMA properties");
  36.         }
  37.         if (coder.properties.length < 1) {
  38.             throw new IOException("LZMA properties too short");
  39.         }
  40.         final byte propsByte = coder.properties[0];
  41.         final int dictSize = getDictionarySize(coder);
  42.         if (dictSize > LZMAInputStream.DICT_SIZE_MAX) {
  43.             throw new IOException("Dictionary larger than 4GiB maximum size used in " + archiveName);
  44.         }
  45.         final int memoryUsageInKb = LZMAInputStream.getMemoryUsage(dictSize, propsByte);
  46.         if (memoryUsageInKb > maxMemoryLimitInKb) {
  47.             throw new MemoryLimitException(memoryUsageInKb, maxMemoryLimitInKb);
  48.         }
  49.         final LZMAInputStream lzmaIn = new LZMAInputStream(in, uncompressedLength, propsByte, dictSize);
  50.         lzmaIn.enableRelaxedEndCondition();
  51.         return lzmaIn;
  52.     }

  53.     @Override
  54.     OutputStream encode(final OutputStream out, final Object opts) throws IOException {
  55.         // NOOP as LZMAOutputStream throws an exception in flush
  56.         return new FlushShieldFilterOutputStream(new LZMAOutputStream(out, getOptions(opts), false));
  57.     }

  58.     private int getDictionarySize(final Coder coder) throws IllegalArgumentException {
  59.         return (int) ByteUtils.fromLittleEndian(coder.properties, 1, 4);
  60.     }

  61.     private LZMA2Options getOptions(final Object opts) throws IOException {
  62.         if (opts instanceof LZMA2Options) {
  63.             return (LZMA2Options) opts;
  64.         }
  65.         final LZMA2Options options = new LZMA2Options();
  66.         options.setDictSize(numberOptionOrDefault(opts));
  67.         return options;
  68.     }

  69.     @Override
  70.     byte[] getOptionsAsProperties(final Object opts) throws IOException {
  71.         final LZMA2Options options = getOptions(opts);
  72.         final byte props = (byte) ((options.getPb() * 5 + options.getLp()) * 9 + options.getLc());
  73.         final int dictSize = options.getDictSize();
  74.         final byte[] o = new byte[5];
  75.         o[0] = props;
  76.         ByteUtils.toLittleEndian(o, dictSize, 1, 4);
  77.         return o;
  78.     }

  79.     @Override
  80.     Object getOptionsFromCoder(final Coder coder, final InputStream in) throws IOException {
  81.         if (coder.properties == null) {
  82.             throw new IOException("Missing LZMA properties");
  83.         }
  84.         if (coder.properties.length < 1) {
  85.             throw new IOException("LZMA properties too short");
  86.         }
  87.         final byte propsByte = coder.properties[0];
  88.         int props = propsByte & 0xFF;
  89.         final int pb = props / (9 * 5);
  90.         props -= pb * 9 * 5;
  91.         final int lp = props / 9;
  92.         final int lc = props - lp * 9;
  93.         final LZMA2Options opts = new LZMA2Options();
  94.         opts.setPb(pb);
  95.         opts.setLcLp(lc, lp);
  96.         opts.setDictSize(getDictionarySize(coder));
  97.         return opts;
  98.     }

  99.     private int numberOptionOrDefault(final Object opts) {
  100.         return toInt(opts, LZMA2Options.DICT_SIZE_DEFAULT);
  101.     }
  102. }