XZCompressorInputStream.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.  * http://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.compressors.xz;

  20. import java.io.IOException;
  21. import java.io.InputStream;

  22. import org.apache.commons.compress.MemoryLimitException;
  23. import org.apache.commons.compress.compressors.CompressorInputStream;
  24. import org.apache.commons.compress.utils.InputStreamStatistics;
  25. import org.apache.commons.io.input.BoundedInputStream;
  26. import org.tukaani.xz.SingleXZInputStream;
  27. import org.tukaani.xz.XZ;
  28. import org.tukaani.xz.XZInputStream;

  29. /**
  30.  * XZ decompressor.
  31.  *
  32.  * @since 1.4
  33.  */
  34. public class XZCompressorInputStream extends CompressorInputStream implements InputStreamStatistics {

  35.     /**
  36.      * Checks if the signature matches what is expected for a .xz file.
  37.      *
  38.      * @param signature the bytes to check
  39.      * @param length    the number of bytes to check
  40.      * @return true if signature matches the .xz magic bytes, false otherwise
  41.      */
  42.     public static boolean matches(final byte[] signature, final int length) {
  43.         if (length < XZ.HEADER_MAGIC.length) {
  44.             return false;
  45.         }

  46.         for (int i = 0; i < XZ.HEADER_MAGIC.length; ++i) {
  47.             if (signature[i] != XZ.HEADER_MAGIC[i]) {
  48.                 return false;
  49.             }
  50.         }

  51.         return true;
  52.     }

  53.     private final BoundedInputStream countingStream;

  54.     private final InputStream in;

  55.     /**
  56.      * Creates a new input stream that decompresses XZ-compressed data from the specified input stream. This doesn't support concatenated .xz files.
  57.      *
  58.      * @param inputStream where to read the compressed data
  59.      *
  60.      * @throws IOException if the input is not in the .xz format, the input is corrupt or truncated, the .xz headers specify options that are not supported by
  61.      *                     this implementation, or the underlying {@code inputStream} throws an exception
  62.      */
  63.     public XZCompressorInputStream(final InputStream inputStream) throws IOException {
  64.         this(inputStream, false);
  65.     }

  66.     /**
  67.      * Creates a new input stream that decompresses XZ-compressed data from the specified input stream.
  68.      *
  69.      * @param inputStream            where to read the compressed data
  70.      * @param decompressConcatenated if true, decompress until the end of the input; if false, stop after the first .xz stream and leave the input position to
  71.      *                               point to the next byte after the .xz stream
  72.      *
  73.      * @throws IOException if the input is not in the .xz format, the input is corrupt or truncated, the .xz headers specify options that are not supported by
  74.      *                     this implementation, or the underlying {@code inputStream} throws an exception
  75.      */
  76.     public XZCompressorInputStream(final InputStream inputStream, final boolean decompressConcatenated) throws IOException {
  77.         this(inputStream, decompressConcatenated, -1);
  78.     }

  79.     /**
  80.      * Creates a new input stream that decompresses XZ-compressed data from the specified input stream.
  81.      *
  82.      * @param inputStream            where to read the compressed data
  83.      * @param decompressConcatenated if true, decompress until the end of the input; if false, stop after the first .xz stream and leave the input position to
  84.      *                               point to the next byte after the .xz stream
  85.      * @param memoryLimitInKb        memory limit used when reading blocks. If the estimated memory limit is exceeded on {@link #read()}, a
  86.      *                               {@link MemoryLimitException} is thrown.
  87.      *
  88.      * @throws IOException if the input is not in the .xz format, the input is corrupt or truncated, the .xz headers specify options that are not supported by
  89.      *                     this implementation, or the underlying {@code inputStream} throws an exception
  90.      *
  91.      * @since 1.14
  92.      */
  93.     public XZCompressorInputStream(final InputStream inputStream, final boolean decompressConcatenated, final int memoryLimitInKb) throws IOException {
  94.         countingStream = BoundedInputStream.builder().setInputStream(inputStream).get();
  95.         if (decompressConcatenated) {
  96.             in = new XZInputStream(countingStream, memoryLimitInKb);
  97.         } else {
  98.             in = new SingleXZInputStream(countingStream, memoryLimitInKb);
  99.         }
  100.     }

  101.     @Override
  102.     public int available() throws IOException {
  103.         return in.available();
  104.     }

  105.     @Override
  106.     public void close() throws IOException {
  107.         in.close();
  108.     }

  109.     /**
  110.      * @since 1.17
  111.      */
  112.     @Override
  113.     public long getCompressedCount() {
  114.         return countingStream.getCount();
  115.     }

  116.     @Override
  117.     public int read() throws IOException {
  118.         try {
  119.             final int ret = in.read();
  120.             count(ret == -1 ? -1 : 1);
  121.             return ret;
  122.         } catch (final org.tukaani.xz.MemoryLimitException e) {
  123.             throw new MemoryLimitException(e.getMemoryNeeded(), e.getMemoryLimit(), e);
  124.         }
  125.     }

  126.     @Override
  127.     public int read(final byte[] buf, final int off, final int len) throws IOException {
  128.         if (len == 0) {
  129.             return 0;
  130.         }
  131.         try {
  132.             final int ret = in.read(buf, off, len);
  133.             count(ret);
  134.             return ret;
  135.         } catch (final org.tukaani.xz.MemoryLimitException e) {
  136.             // convert to commons-compress MemoryLimtException
  137.             throw new MemoryLimitException(e.getMemoryNeeded(), e.getMemoryLimit(), e);
  138.         }
  139.     }

  140.     @Override
  141.     public long skip(final long n) throws IOException {
  142.         try {
  143.             return org.apache.commons.io.IOUtils.skip(in, n);
  144.         } catch (final org.tukaani.xz.MemoryLimitException e) {
  145.             // convert to commons-compress MemoryLimtException
  146.             throw new MemoryLimitException(e.getMemoryNeeded(), e.getMemoryLimit(), e);
  147.         }
  148.     }
  149. }