    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one
    003 * or more contributor license agreements.  See the NOTICE file
    004 * distributed with this work for additional information
    005 * regarding copyright ownership.  The ASF licenses this file
    006 * to you under the Apache License, Version 2.0 (the
    007 * "License"); you may not use this file except in compliance
    008 * with the License.  You may obtain a copy of the License at
    009 *
    010 * http://www.apache.org/licenses/LICENSE-2.0
    011 *
    012 * Unless required by applicable law or agreed to in writing,
    013 * software distributed under the License is distributed on an
    014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015 * KIND, either express or implied.  See the License for the
    016 * specific language governing permissions and limitations
    017 * under the License.
    018 */
    019
    020package org.apache.commons.compress.compressors.pack200;
    021
    022import java.io.File;
    023import java.io.IOException;
    024import java.io.InputStream;
    025import java.util.Map;
    026import java.util.jar.JarOutputStream;
    027import org.apache.commons.compress.java.util.jar.Pack200;
    028
    029import org.apache.commons.compress.compressors.CompressorInputStream;
    030import org.apache.commons.compress.utils.CloseShieldFilterInputStream;
    031import org.apache.commons.compress.utils.IOUtils;
    032
    033/**
    034 * An input stream that decompresses from the Pack200 format to be read
    035 * as any other stream.
    036 *
    037 * <p>The {@link CompressorInputStream#getCount getCount} and {@link
    038 * CompressorInputStream#getBytesRead getBytesRead} methods always
    039 * return 0.</p>
    040 *
    041 * @NotThreadSafe
    042 * @since 1.3
    043 */
    044public class Pack200CompressorInputStream extends CompressorInputStream {
    045    private final InputStream originalInput;
    046    private final StreamBridge streamBridge;
    047
    048    /**
    049     * Decompresses the given stream, caching the decompressed data in
    050     * memory.
    051     *
    052     * <p>When reading from a file the File-arg constructor may
    053     * provide better performance.</p>
    054     *
    055     * @param in the InputStream from which this object should be created
    056     * @throws IOException if reading fails
    057     */
    058    public Pack200CompressorInputStream(final InputStream in)
    059        throws IOException {
    060        this(in, Pack200Strategy.IN_MEMORY);
    061    }
    062
    063    /**
    064     * Decompresses the given stream using the given strategy to cache
    065     * the results.
    066     *
    067     * <p>When reading from a file the File-arg constructor may
    068     * provide better performance.</p>
    069     *
    070     * @param in the InputStream from which this object should be created
    071     * @param mode the strategy to use
    072     * @throws IOException if reading fails
    073     */
    074    public Pack200CompressorInputStream(final InputStream in,
    075                                        final Pack200Strategy mode)
    076        throws IOException {
    077        this(in, null, mode, null);
    078    }
    079
    080    /**
    081     * Decompresses the given stream, caching the decompressed data in
    082     * memory and using the given properties.
    083     *
    084     * <p>When reading from a file the File-arg constructor may
    085     * provide better performance.</p>
    086     *
    087     * @param in the InputStream from which this object should be created
    088     * @param props Pack200 properties to use
    089     * @throws IOException if reading fails
    090     */
    091    public Pack200CompressorInputStream(final InputStream in,
    092                                        final Map<String, String> props)
    093        throws IOException {
    094        this(in, Pack200Strategy.IN_MEMORY, props);
    095    }
    096
    097    /**
    098     * Decompresses the given stream using the given strategy to cache
    099     * the results and the given properties.
    100     *
    101     * <p>When reading from a file the File-arg constructor may
    102     * provide better performance.</p>
    103     *
    104     * @param in the InputStream from which this object should be created
    105     * @param mode the strategy to use
    106     * @param props Pack200 properties to use
    107     * @throws IOException if reading fails
    108     */
    109    public Pack200CompressorInputStream(final InputStream in,
    110                                        final Pack200Strategy mode,
    111                                        final Map<String, String> props)
    112        throws IOException {
    113        this(in, null, mode, props);
    114    }
    115
    116    /**
    117     * Decompresses the given file, caching the decompressed data in
    118     * memory.
    119     *
    120     * @param f the file to decompress
    121     * @throws IOException if reading fails
    122     */
    123    public Pack200CompressorInputStream(final File f) throws IOException {
    124        this(f, Pack200Strategy.IN_MEMORY);
    125    }
    126
    127    /**
    128     * Decompresses the given file using the given strategy to cache
    129     * the results.
    130     *
    131     * @param f the file to decompress
    132     * @param mode the strategy to use
    133     * @throws IOException if reading fails
    134     */
    135    public Pack200CompressorInputStream(final File f, final Pack200Strategy mode)
    136        throws IOException {
    137        this(null, f, mode, null);
    138    }
    139
    140    /**
    141     * Decompresses the given file, caching the decompressed data in
    142     * memory and using the given properties.
    143     *
    144     * @param f the file to decompress
    145     * @param props Pack200 properties to use
    146     * @throws IOException if reading fails
    147     */
    148    public Pack200CompressorInputStream(final File f,
    149                                        final Map<String, String> props)
    150        throws IOException {
    151        this(f, Pack200Strategy.IN_MEMORY, props);
    152    }
    153
    154    /**
    155     * Decompresses the given file using the given strategy to cache
    156     * the results and the given properties.
    157     *
    158     * @param f the file to decompress
    159     * @param mode the strategy to use
    160     * @param props Pack200 properties to use
    161     * @throws IOException if reading fails
    162     */
    163    public Pack200CompressorInputStream(final File f, final Pack200Strategy mode,
    164                                        final Map<String, String> props)
    165        throws IOException {
    166        this(null, f, mode, props);
    167    }
    168
    169    private Pack200CompressorInputStream(final InputStream in, final File f,
    170                                         final Pack200Strategy mode,
    171                                         final Map<String, String> props)
    172            throws IOException {
    173        originalInput = in;
    174        streamBridge = mode.newStreamBridge();
    175        try (final JarOutputStream jarOut = new JarOutputStream(streamBridge)) {
    176            final Pack200.Unpacker u = Pack200.newUnpacker();
    177            if (props != null) {
    178                u.properties().putAll(props);
    179            }
    180            if (f == null) {
    181                // unpack would close this stream but we
    182                // want to give the user code more control
    183                u.unpack(new CloseShieldFilterInputStream(in), jarOut);
    184            } else {
    185                u.unpack(f, jarOut);
    186            }
    187        }
    188    }
    189
    190    @Override
    191    public int read() throws IOException {
    192        return streamBridge.getInput().read();
    193    }
    194
    195    @Override
    196    public int read(final byte[] b) throws IOException {
    197        return streamBridge.getInput().read(b);
    198    }
    199
    200    @Override
    201    public int read(final byte[] b, final int off, final int count) throws IOException {
    202        return streamBridge.getInput().read(b, off, count);
    203    }
    204
    205    @Override
    206    public int available() throws IOException {
    207        return streamBridge.getInput().available();
    208    }
    209
    210    @Override
    211    public boolean markSupported() {
    212        try {
    213            return streamBridge.getInput().markSupported();
    214        } catch (final IOException ex) { // NOSONAR
    215            return false;
    216        }
    217    }
    218
    219    @Override
    220    public synchronized void mark(final int limit) {
    221        try {
    222            streamBridge.getInput().mark(limit);
    223        } catch (final IOException ex) {
    224            throw new RuntimeException(ex); //NOSONAR
    225        }
    226    }
    227
    228    @Override
    229    public synchronized void reset() throws IOException {
    230        streamBridge.getInput().reset();
    231    }
    232
    233    @Override
    234    public long skip(final long count) throws IOException {
    235        return IOUtils.skip(streamBridge.getInput(), count);
    236    }
    237
    238    @Override
    239    public void close() throws IOException {
    240        try {
    241            streamBridge.stop();
    242        } finally {
    243            if (originalInput != null) {
    244                originalInput.close();
    245            }
    246        }
    247    }
    248
    249    private static final byte[] CAFE_DOOD = new byte[] {
    250        (byte) 0xCA, (byte) 0xFE, (byte) 0xD0, (byte) 0x0D
    251    };
    252    private static final int SIG_LENGTH = CAFE_DOOD.length;
    253
    254    /**
    255     * Checks if the signature matches what is expected for a pack200
    256     * file (0xCAFED00D).
    257     *
    258     * @param signature
    259     *            the bytes to check
    260     * @param length
    261     *            the number of bytes to check
    262     * @return true, if this stream is a pack200 compressed stream,
    263     * false otherwise
    264     */
    265    public static boolean matches(final byte[] signature, final int length) {
    266        if (length < SIG_LENGTH) {
    267            return false;
    268        }
    269
    270        for (int i = 0; i < SIG_LENGTH; i++) {
    271            if (signature[i] != CAFE_DOOD[i]) {
    272                return false;
    273            }
    274        }
    275
    276        return true;
    277    }
    278}