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")
093    static BoundedInputStream newBoundedInputStream(final Path path) throws IOException {
094        return new BoundedInputStream(new BufferedInputStream(Files.newInputStream(path)), Files.size(path));
095    }
096
097    /**
098     * Creates a new BoundedInputStream bound by the size of the given file.
099     * <p>
100     * The new BoundedInputStream wraps a new {@link BufferedInputStream}.
101     * </p>
102     *
103     * @param first the path string or initial part of the path string.
104     * @param more  additional strings to be joined to form the path string.
105     * @return a new BoundedInputStream
106     * @throws IOException if an I/O error occurs
107     */
108    static BoundedInputStream newBoundedInputStream(final String first, final String... more) throws IOException {
109        return newBoundedInputStream(Paths.get(first, more));
110    }
111
112    /**
113     * Creates a new BoundedInputStream bound by the size of the given URL to a file.
114     * <p>
115     * The new BoundedInputStream wraps a new {@link BufferedInputStream}.
116     * </p>
117     *
118     * @param path The URL.
119     * @return a new BoundedInputStream
120     * @throws IOException        if an I/O error occurs
121     * @throws URISyntaxException
122     */
123    static BoundedInputStream newBoundedInputStream(final URL url) throws IOException, URISyntaxException {
124        return newBoundedInputStream(Paths.get(url.toURI()));
125    }
126
127    @SuppressWarnings("unchecked")
128    private static <T> T readField(final Object object, final String fieldName) {
129        try {
130            return (T) FieldUtils.readField(object, fieldName, true);
131        } catch (final IllegalAccessException e) {
132            return null;
133        }
134    }
135
136    static String readPathString(final FileInputStream fis) {
137        return readField(fis, "path");
138    }
139
140    /**
141     * Unwraps the given FilterInputStream to return its wrapped InputStream.
142     *
143     * @param filterInputStream The FilterInputStream to unwrap.
144     * @return The wrapped InputStream
145     */
146    static InputStream unwrap(final FilterInputStream filterInputStream) {
147        return readField(filterInputStream, "in");
148    }
149
150    /**
151     * Unwraps the given InputStream if it is an FilterInputStream to return its wrapped InputStream.
152     *
153     * @param filterInputStream The FilterInputStream to unwrap.
154     * @return The wrapped InputStream
155     */
156    @SuppressWarnings("resource")
157    static InputStream unwrap(final InputStream inputStream) {
158        return inputStream instanceof FilterInputStream ? unwrap((FilterInputStream) inputStream) : inputStream;
159    }
160
161    @Override
162    public void unpack(final File file, final JarOutputStream out) throws IOException {
163        if (file == null || out == null) {
164            throw new IllegalArgumentException("Must specify both input and output streams");
165        }
166        final long size = file.length();
167        final int bufferSize = size > 0 && size < DEFAULT_BUFFER_SIZE ? (int) size : DEFAULT_BUFFER_SIZE;
168        try (InputStream in = new BufferedInputStream(Files.newInputStream(file.toPath()), bufferSize)) {
169            unpack(in, out);
170        }
171    }
172
173    @Override
174    public void unpack(final InputStream in, final JarOutputStream out) throws IOException {
175        if (in == null || out == null) {
176            throw new IllegalArgumentException("Must specify both input and output streams");
177        }
178        completed(0);
179        try {
180            new Archive(in, out).unpack();
181        } catch (final Pack200Exception e) {
182            throw new IOException("Failed to unpack Jar:" + e);
183        }
184        completed(1);
185    }
186}