    001/*
    002 *  Licensed to the Apache Software Foundation (ASF) under one or more
    003 *  contributor license agreements.  See the NOTICE file distributed with
    004 *  this work for additional information regarding copyright ownership.
    005 *  The ASF licenses this file to You under the Apache License, Version 2.0
    006 *  (the "License"); you may not use this file except in compliance with
    007 *  the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 *  Unless required by applicable law or agreed to in writing, software
    012 *  distributed under the License is distributed on an "AS IS" BASIS,
    013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 *  See the License for the specific language governing permissions and
    015 *  limitations under the License.
    016 */
    017package org.apache.commons.compress.harmony.unpack200;
    018
    019import java.io.BufferedInputStream;
    020import java.io.File;
    021import java.io.FileInputStream;
    022import java.io.FilterInputStream;
    023import java.io.IOException;
    024import java.io.InputStream;
    025import java.net.URISyntaxException;
    026import java.net.URL;
    027import java.nio.file.Files;
    028import java.nio.file.Path;
    029import java.nio.file.Paths;
    030import java.util.jar.JarOutputStream;
    031
    032import org.apache.commons.compress.harmony.pack200.Pack200Adapter;
    033import org.apache.commons.compress.harmony.pack200.Pack200Exception;
    034import org.apache.commons.compress.java.util.jar.Pack200.Unpacker;
    035import org.apache.commons.io.input.BoundedInputStream;
    036import org.apache.commons.io.input.CloseShieldInputStream;
    037import org.apache.commons.lang3.reflect.FieldUtils;
    038
    039/**
    040 * This class provides the binding between the standard Pack200 interface and the internal interface for (un)packing.
    041 */
    042public class Pack200UnpackerAdapter extends Pack200Adapter implements Unpacker {
    043
    044    /**
    045     * Creates a new BoundedInputStream bound by the size of the given file.
    046     * <p>
    047     * The new BoundedInputStream wraps a new {@link BufferedInputStream}.
    048     * </p>
    049     *
    050     * @param file The file.
    051     * @return a new BoundedInputStream
    052     * @throws IOException if an I/O error occurs
    053     */
    054    static BoundedInputStream newBoundedInputStream(final File file) throws IOException {
    055        return newBoundedInputStream(file.toPath());
    056    }
    057
    058    private static BoundedInputStream newBoundedInputStream(final FileInputStream fileInputStream) throws IOException {
    059        return newBoundedInputStream(readPathString(fileInputStream));
    060    }
    061
    062    @SuppressWarnings("resource") // Caller closes.
    063    static BoundedInputStream newBoundedInputStream(final InputStream inputStream) throws IOException {
    064        if (inputStream instanceof BoundedInputStream) {
    065            // Already bound.
    066            return (BoundedInputStream) inputStream;
    067        }
    068        if (inputStream instanceof CloseShieldInputStream) {
    069            // Don't unwrap to keep close shield.
    070            return newBoundedInputStream(BoundedInputStream.builder().setInputStream(inputStream).get());
    071        }
    072        if (inputStream instanceof FilterInputStream) {
    073            return newBoundedInputStream(unwrap((FilterInputStream) inputStream));
    074        }
    075        if (inputStream instanceof FileInputStream) {
    076            return newBoundedInputStream((FileInputStream) inputStream);
    077        }
    078        // No limit
    079        return newBoundedInputStream(BoundedInputStream.builder().setInputStream(inputStream).get());
    080    }
    081
    082    /**
    083     * Creates a new BoundedInputStream bound by the size of the given path.
    084     * <p>
    085     * The new BoundedInputStream wraps a new {@link BufferedInputStream}.
    086     * </p>
    087     *
    088     * @param path The path.
    089     * @return a new BoundedInputStream
    090     * @throws IOException if an I/O error occurs
    091     */
    092    @SuppressWarnings("resource") // Caller closes.
    093    static BoundedInputStream newBoundedInputStream(final Path path) throws IOException {
    094        // @formatter:off
    095        return BoundedInputStream.builder()
    096                .setInputStream(new BufferedInputStream(Files.newInputStream(path)))
    097                .setMaxCount(Files.size(path))
    098                .setPropagateClose(false)
    099                .get();
    100        // @formatter:on
    101    }
    102
    103    /**
    104     * Creates a new BoundedInputStream bound by the size of the given file.
    105     * <p>
    106     * The new BoundedInputStream wraps a new {@link BufferedInputStream}.
    107     * </p>
    108     *
    109     * @param first the path string or initial part of the path string.
    110     * @param more  additional strings to be joined to form the path string.
    111     * @return a new BoundedInputStream
    112     * @throws IOException if an I/O error occurs
    113     */
    114    static BoundedInputStream newBoundedInputStream(final String first, final String... more) throws IOException {
    115        return newBoundedInputStream(Paths.get(first, more));
    116    }
    117
    118    /**
    119     * Creates a new BoundedInputStream bound by the size of the given URL to a file.
    120     * <p>
    121     * The new BoundedInputStream wraps a new {@link BufferedInputStream}.
    122     * </p>
    123     *
    124     * @param url The URL.
    125     * @return a new BoundedInputStream
    126     * @throws IOException        if an I/O error occurs
    127     * @throws URISyntaxException
    128     */
    129    static BoundedInputStream newBoundedInputStream(final URL url) throws IOException, URISyntaxException {
    130        return newBoundedInputStream(Paths.get(url.toURI()));
    131    }
    132
    133    @SuppressWarnings("unchecked")
    134    private static <T> T readField(final Object object, final String fieldName) {
    135        try {
    136            return (T) FieldUtils.readField(object, fieldName, true);
    137        } catch (final IllegalAccessException e) {
    138            return null;
    139        }
    140    }
    141
    142    static String readPathString(final FileInputStream fis) {
    143        return readField(fis, "path");
    144    }
    145
    146    /**
    147     * Unwraps the given FilterInputStream to return its wrapped InputStream.
    148     *
    149     * @param filterInputStream The FilterInputStream to unwrap.
    150     * @return The wrapped InputStream
    151     */
    152    static InputStream unwrap(final FilterInputStream filterInputStream) {
    153        return readField(filterInputStream, "in");
    154    }
    155
    156    /**
    157     * Unwraps the given InputStream if it is an FilterInputStream to return its wrapped InputStream.
    158     *
    159     * @param filterInputStream The FilterInputStream to unwrap.
    160     * @return The wrapped InputStream
    161     */
    162    @SuppressWarnings("resource")
    163    static InputStream unwrap(final InputStream inputStream) {
    164        return inputStream instanceof FilterInputStream ? unwrap((FilterInputStream) inputStream) : inputStream;
    165    }
    166
    167    @Override
    168    public void unpack(final File file, final JarOutputStream out) throws IOException {
    169        if (file == null || out == null) {
    170            throw new IllegalArgumentException("Must specify both input and output streams");
    171        }
    172        final long size = file.length();
    173        final int bufferSize = size > 0 && size < DEFAULT_BUFFER_SIZE ? (int) size : DEFAULT_BUFFER_SIZE;
    174        try (InputStream in = new BufferedInputStream(Files.newInputStream(file.toPath()), bufferSize)) {
    175            unpack(in, out);
    176        }
    177    }
    178
    179    @Override
    180    public void unpack(final InputStream in, final JarOutputStream out) throws IOException {
    181        if (in == null || out == null) {
    182            throw new IllegalArgumentException("Must specify both input and output streams");
    183        }
    184        completed(0);
    185        try {
    186            new Archive(in, out).unpack();
    187        } catch (final Pack200Exception e) {
    188            throw new IOException("Failed to unpack Jar:" + e);
    189        }
    190        completed(1);
    191    }
    192}