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 *   https://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 */
019package org.apache.commons.compress.harmony.unpack200;
020
021import java.io.BufferedInputStream;
022import java.io.File;
023import java.io.FileInputStream;
024import java.io.FilterInputStream;
025import java.io.IOException;
026import java.io.InputStream;
027import java.net.URISyntaxException;
028import java.net.URL;
029import java.nio.file.Files;
030import java.nio.file.Path;
031import java.nio.file.Paths;
032import java.util.jar.JarOutputStream;
033
034import org.apache.commons.compress.harmony.pack200.Pack200Adapter;
035import org.apache.commons.compress.harmony.pack200.Pack200Exception;
036import org.apache.commons.compress.java.util.jar.Pack200.Unpacker;
037import org.apache.commons.io.input.BoundedInputStream;
038import org.apache.commons.io.input.CloseShieldInputStream;
039import org.apache.commons.lang3.reflect.FieldUtils;
040
041/**
042 * This class provides the binding between the standard Pack200 interface and the internal interface for (un)packing.
043 */
044public class Pack200UnpackerAdapter extends Pack200Adapter implements Unpacker {
045
046    /**
047     * Creates a new BoundedInputStream bound by the size of the given file.
048     * <p>
049     * The new BoundedInputStream wraps a new {@link BufferedInputStream}.
050     * </p>
051     *
052     * @param file The file.
053     * @return a new BoundedInputStream
054     * @throws IOException if an I/O error occurs
055     */
056    static BoundedInputStream newBoundedInputStream(final File file) throws IOException {
057        return newBoundedInputStream(file.toPath());
058    }
059
060    private static BoundedInputStream newBoundedInputStream(final FileInputStream fileInputStream) throws IOException {
061        return newBoundedInputStream(readPathString(fileInputStream));
062    }
063
064    @SuppressWarnings("resource") // Caller closes.
065    static BoundedInputStream newBoundedInputStream(final InputStream inputStream) throws IOException {
066        if (inputStream instanceof BoundedInputStream) {
067            // Already bound.
068            return (BoundedInputStream) inputStream;
069        }
070        if (inputStream instanceof CloseShieldInputStream) {
071            // Don't unwrap to keep close shield.
072            return newBoundedInputStream(BoundedInputStream.builder().setInputStream(inputStream).get());
073        }
074        if (inputStream instanceof FilterInputStream) {
075            return newBoundedInputStream(unwrap((FilterInputStream) inputStream));
076        }
077        if (inputStream instanceof FileInputStream) {
078            return newBoundedInputStream((FileInputStream) inputStream);
079        }
080        // No limit
081        return newBoundedInputStream(BoundedInputStream.builder().setInputStream(inputStream).get());
082    }
083
084    /**
085     * Creates a new BoundedInputStream bound by the size of the given path.
086     * <p>
087     * The new BoundedInputStream wraps a new {@link BufferedInputStream}.
088     * </p>
089     *
090     * @param path The path.
091     * @return a new BoundedInputStream
092     * @throws IOException if an I/O error occurs
093     */
094    @SuppressWarnings("resource") // Caller closes.
095    static BoundedInputStream newBoundedInputStream(final Path path) throws IOException {
096        // @formatter:off
097        return BoundedInputStream.builder()
098                .setInputStream(new BufferedInputStream(Files.newInputStream(path)))
099                .setMaxCount(Files.size(path))
100                .setPropagateClose(false)
101                .get();
102        // @formatter:on
103    }
104
105    /**
106     * Creates a new BoundedInputStream bound by the size of the given file.
107     * <p>
108     * The new BoundedInputStream wraps a new {@link BufferedInputStream}.
109     * </p>
110     *
111     * @param first the path string or initial part of the path string.
112     * @param more  additional strings to be joined to form the path string.
113     * @return a new BoundedInputStream
114     * @throws IOException if an I/O error occurs
115     */
116    static BoundedInputStream newBoundedInputStream(final String first, final String... more) throws IOException {
117        return newBoundedInputStream(Paths.get(first, more));
118    }
119
120    /**
121     * Creates a new BoundedInputStream bound by the size of the given URL to a file.
122     * <p>
123     * The new BoundedInputStream wraps a new {@link BufferedInputStream}.
124     * </p>
125     *
126     * @param url The URL.
127     * @return a new BoundedInputStream
128     * @throws IOException        if an I/O error occurs.
129     * @throws URISyntaxException if the URL is not formatted strictly according to to RFC2396 and cannot be converted to a URI.
130     */
131    static BoundedInputStream newBoundedInputStream(final URL url) throws IOException, URISyntaxException {
132        return newBoundedInputStream(Paths.get(url.toURI()));
133    }
134
135    @SuppressWarnings("unchecked")
136    private static <T> T readField(final Object object, final String fieldName) {
137        try {
138            return (T) FieldUtils.readField(object, fieldName, true);
139        } catch (final IllegalAccessException e) {
140            return null;
141        }
142    }
143
144    static String readPathString(final FileInputStream fis) {
145        return readField(fis, "path");
146    }
147
148    /**
149     * Unwraps the given FilterInputStream to return its wrapped InputStream.
150     *
151     * @param filterInputStream The FilterInputStream to unwrap.
152     * @return The wrapped InputStream
153     */
154    static InputStream unwrap(final FilterInputStream filterInputStream) {
155        return readField(filterInputStream, "in");
156    }
157
158    /**
159     * Unwraps the given InputStream if it is an FilterInputStream to return its wrapped InputStream.
160     *
161     * @param inputStream The FilterInputStream to unwrap.
162     * @return The wrapped InputStream
163     */
164    static InputStream unwrap(final InputStream inputStream) {
165        return inputStream instanceof FilterInputStream ? unwrap((FilterInputStream) inputStream) : inputStream;
166    }
167
168    @Override
169    public void unpack(final File file, final JarOutputStream out) throws IOException {
170        if (file == null) {
171            throw new IllegalArgumentException("Must specify input file.");
172        }
173        if (out == null) {
174            throw new IllegalArgumentException("Must specify output stream.");
175        }
176        final long size = file.length();
177        final int bufferSize = size > 0 && size < DEFAULT_BUFFER_SIZE ? (int) size : DEFAULT_BUFFER_SIZE;
178        try (InputStream in = new BufferedInputStream(Files.newInputStream(file.toPath()), bufferSize)) {
179            unpack(in, out);
180        }
181    }
182
183    @Override
184    public void unpack(final InputStream in, final JarOutputStream out) throws IOException {
185        if (in == null) {
186            throw new IllegalArgumentException("Must specify input stream.");
187        }
188        if (out == null) {
189            throw new IllegalArgumentException("Must specify output stream.");
190        }
191        completed(0);
192        try {
193            new Archive(in, out).unpack();
194        } catch (final Pack200Exception e) {
195            throw new IOException("Failed to unpack Jar:" + e);
196        }
197        completed(1);
198    }
199}