Coders.java

  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. import java.io.ByteArrayInputStream;
  21. import java.io.FilterInputStream;
  22. import java.io.IOException;
  23. import java.io.InputStream;
  24. import java.io.OutputStream;
  25. import java.io.SequenceInputStream;
  26. import java.util.Arrays;
  27. import java.util.HashMap;
  28. import java.util.Map;
  29. import java.util.zip.Deflater;
  30. import java.util.zip.DeflaterOutputStream;
  31. import java.util.zip.Inflater;
  32. import java.util.zip.InflaterInputStream;

  33. import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
  34. import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
  35. import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream;
  36. import org.apache.commons.compress.utils.FlushShieldFilterOutputStream;
  37. import org.tukaani.xz.ARMOptions;
  38. import org.tukaani.xz.ARMThumbOptions;
  39. import org.tukaani.xz.FilterOptions;
  40. import org.tukaani.xz.FinishableWrapperOutputStream;
  41. import org.tukaani.xz.IA64Options;
  42. import org.tukaani.xz.PowerPCOptions;
  43. import org.tukaani.xz.SPARCOptions;
  44. import org.tukaani.xz.X86Options;

  45. final class Coders {

  46.     static final class BCJDecoder extends AbstractCoder {
  47.         private final FilterOptions opts;

  48.         BCJDecoder(final FilterOptions opts) {
  49.             this.opts = opts;
  50.         }

  51.         @Override
  52.         InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, final Coder coder, final byte[] password,
  53.                 final int maxMemoryLimitKiB) throws IOException {
  54.             try {
  55.                 return opts.getInputStream(in);
  56.             } catch (final AssertionError e) {
  57.                 throw new IOException("BCJ filter used in " + archiveName + " needs XZ for Java > 1.4 - see "
  58.                         + "https://commons.apache.org/proper/commons-compress/limitations.html#7Z", e);
  59.             }
  60.         }

  61.         @SuppressWarnings("resource")
  62.         @Override
  63.         OutputStream encode(final OutputStream out, final Object options) {
  64.             return new FlushShieldFilterOutputStream(opts.getOutputStream(new FinishableWrapperOutputStream(out)));
  65.         }
  66.     }

  67.     static final class BZIP2Decoder extends AbstractCoder {
  68.         BZIP2Decoder() {
  69.             super(Number.class);
  70.         }

  71.         @Override
  72.         InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, final Coder coder, final byte[] password,
  73.                 final int maxMemoryLimitKiB) throws IOException {
  74.             return new BZip2CompressorInputStream(in);
  75.         }

  76.         @Override
  77.         OutputStream encode(final OutputStream out, final Object options) throws IOException {
  78.             final int blockSize = toInt(options, BZip2CompressorOutputStream.MAX_BLOCKSIZE);
  79.             return new BZip2CompressorOutputStream(out, blockSize);
  80.         }
  81.     }

  82.     static final class CopyDecoder extends AbstractCoder {
  83.         @Override
  84.         InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, final Coder coder, final byte[] password,
  85.                 final int maxMemoryLimitKiB) throws IOException {
  86.             return in;
  87.         }

  88.         @Override
  89.         OutputStream encode(final OutputStream out, final Object options) {
  90.             return out;
  91.         }
  92.     }

  93.     static final class Deflate64Decoder extends AbstractCoder {
  94.         Deflate64Decoder() {
  95.             super(Number.class);
  96.         }

  97.         @Override
  98.         InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, final Coder coder, final byte[] password,
  99.                 final int maxMemoryLimitKiB) throws IOException {
  100.             return new Deflate64CompressorInputStream(in);
  101.         }
  102.     }

  103.     static final class DeflateDecoder extends AbstractCoder {

  104.         static final class DeflateDecoderInputStream extends FilterInputStream {

  105.             Inflater inflater;

  106.             DeflateDecoderInputStream(final InflaterInputStream inflaterInputStream, final Inflater inflater) {
  107.                 super(inflaterInputStream);
  108.                 this.inflater = inflater;
  109.             }

  110.             @Override
  111.             public void close() throws IOException {
  112.                 try {
  113.                     super.close();
  114.                 } finally {
  115.                     inflater.end();
  116.                 }
  117.             }

  118.         }

  119.         static final class DeflateDecoderOutputStream extends OutputStream {

  120.             final DeflaterOutputStream deflaterOutputStream;
  121.             Deflater deflater;

  122.             DeflateDecoderOutputStream(final DeflaterOutputStream deflaterOutputStream, final Deflater deflater) {
  123.                 this.deflaterOutputStream = deflaterOutputStream;
  124.                 this.deflater = deflater;
  125.             }

  126.             @Override
  127.             public void close() throws IOException {
  128.                 try {
  129.                     deflaterOutputStream.close();
  130.                 } finally {
  131.                     deflater.end();
  132.                 }
  133.             }

  134.             @Override
  135.             public void write(final byte[] b) throws IOException {
  136.                 deflaterOutputStream.write(b);
  137.             }

  138.             @Override
  139.             public void write(final byte[] b, final int off, final int len) throws IOException {
  140.                 deflaterOutputStream.write(b, off, len);
  141.             }

  142.             @Override
  143.             public void write(final int b) throws IOException {
  144.                 deflaterOutputStream.write(b);
  145.             }
  146.         }

  147.         private static final byte[] ONE_ZERO_BYTE = new byte[1];

  148.         DeflateDecoder() {
  149.             super(Number.class);
  150.         }

  151.         @Override
  152.         InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, final Coder coder, final byte[] password,
  153.                 final int maxMemoryLimitKiB) throws IOException {
  154.             final Inflater inflater = new Inflater(true);
  155.             // Inflater with nowrap=true has this odd contract for a zero padding
  156.             // byte following the data stream; this used to be zlib's requirement
  157.             // and has been fixed a long time ago, but the contract persists so
  158.             // we comply.
  159.             // https://docs.oracle.com/javase/8/docs/api/java/util/zip/Inflater.html#Inflater(boolean)
  160.             final InflaterInputStream inflaterInputStream = new InflaterInputStream(new SequenceInputStream(in, new ByteArrayInputStream(ONE_ZERO_BYTE)),
  161.                     inflater);
  162.             return new DeflateDecoderInputStream(inflaterInputStream, inflater);
  163.         }

  164.         @Override
  165.         OutputStream encode(final OutputStream out, final Object options) {
  166.             final int level = toInt(options, 9);
  167.             final Deflater deflater = new Deflater(level, true);
  168.             final DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(out, deflater);
  169.             return new DeflateDecoderOutputStream(deflaterOutputStream, deflater);
  170.         }
  171.     }

  172.     private static final Map<SevenZMethod, AbstractCoder> CODER_MAP = new HashMap<SevenZMethod, AbstractCoder>() {

  173.         private static final long serialVersionUID = 1664829131806520867L;

  174.         {
  175.             put(SevenZMethod.COPY, new CopyDecoder());
  176.             put(SevenZMethod.LZMA, new LZMADecoder());
  177.             put(SevenZMethod.LZMA2, new LZMA2Decoder());
  178.             put(SevenZMethod.DEFLATE, new DeflateDecoder());
  179.             put(SevenZMethod.DEFLATE64, new Deflate64Decoder());
  180.             put(SevenZMethod.BZIP2, new BZIP2Decoder());
  181.             put(SevenZMethod.AES256SHA256, new AES256SHA256Decoder());
  182.             put(SevenZMethod.BCJ_X86_FILTER, new BCJDecoder(new X86Options()));
  183.             put(SevenZMethod.BCJ_PPC_FILTER, new BCJDecoder(new PowerPCOptions()));
  184.             put(SevenZMethod.BCJ_IA64_FILTER, new BCJDecoder(new IA64Options()));
  185.             put(SevenZMethod.BCJ_ARM_FILTER, new BCJDecoder(new ARMOptions()));
  186.             put(SevenZMethod.BCJ_ARM_THUMB_FILTER, new BCJDecoder(new ARMThumbOptions()));
  187.             put(SevenZMethod.BCJ_SPARC_FILTER, new BCJDecoder(new SPARCOptions()));
  188.             put(SevenZMethod.DELTA_FILTER, new DeltaDecoder());
  189.         }
  190.     };

  191.     static InputStream addDecoder(final String archiveName, final InputStream is, final long uncompressedLength, final Coder coder, final byte[] password,
  192.             final int maxMemoryLimitKiB) throws IOException {
  193.         final AbstractCoder cb = findByMethod(SevenZMethod.byId(coder.decompressionMethodId));
  194.         if (cb == null) {
  195.             throw new IOException("Unsupported compression method " + Arrays.toString(coder.decompressionMethodId) + " used in " + archiveName);
  196.         }
  197.         return cb.decode(archiveName, is, uncompressedLength, coder, password, maxMemoryLimitKiB);
  198.     }

  199.     static OutputStream addEncoder(final OutputStream out, final SevenZMethod method, final Object options) throws IOException {
  200.         final AbstractCoder cb = findByMethod(method);
  201.         if (cb == null) {
  202.             throw new IOException("Unsupported compression method " + method);
  203.         }
  204.         return cb.encode(out, options);
  205.     }

  206.     static AbstractCoder findByMethod(final SevenZMethod method) {
  207.         return CODER_MAP.get(method);
  208.     }

  209. }