View Javadoc
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   *   https://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.harmony.unpack200;
20  
21  import java.io.BufferedInputStream;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FilterInputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.net.URISyntaxException;
28  import java.net.URL;
29  import java.nio.file.Files;
30  import java.nio.file.Path;
31  import java.nio.file.Paths;
32  import java.util.jar.JarOutputStream;
33  
34  import org.apache.commons.compress.harmony.pack200.Pack200Adapter;
35  import org.apache.commons.compress.harmony.pack200.Pack200Exception;
36  import org.apache.commons.compress.java.util.jar.Pack200.Unpacker;
37  import org.apache.commons.io.input.BoundedInputStream;
38  import org.apache.commons.io.input.CloseShieldInputStream;
39  import org.apache.commons.lang3.reflect.FieldUtils;
40  
41  /**
42   * This class provides the binding between the standard Pack200 interface and the internal interface for (un)packing.
43   */
44  public class Pack200UnpackerAdapter extends Pack200Adapter implements Unpacker {
45  
46      /**
47       * Creates a new BoundedInputStream bound by the size of the given file.
48       * <p>
49       * The new BoundedInputStream wraps a new {@link BufferedInputStream}.
50       * </p>
51       *
52       * @param file The file.
53       * @return a new BoundedInputStream
54       * @throws IOException if an I/O error occurs
55       */
56      static BoundedInputStream newBoundedInputStream(final File file) throws IOException {
57          return newBoundedInputStream(file.toPath());
58      }
59  
60      private static BoundedInputStream newBoundedInputStream(final FileInputStream fileInputStream) throws IOException {
61          return newBoundedInputStream(readPathString(fileInputStream));
62      }
63  
64      @SuppressWarnings("resource") // Caller closes.
65      static BoundedInputStream newBoundedInputStream(final InputStream inputStream) throws IOException {
66          if (inputStream instanceof BoundedInputStream) {
67              // Already bound.
68              return (BoundedInputStream) inputStream;
69          }
70          if (inputStream instanceof CloseShieldInputStream) {
71              // Don't unwrap to keep close shield.
72              return newBoundedInputStream(BoundedInputStream.builder().setInputStream(inputStream).get());
73          }
74          if (inputStream instanceof FilterInputStream) {
75              return newBoundedInputStream(unwrap((FilterInputStream) inputStream));
76          }
77          if (inputStream instanceof FileInputStream) {
78              return newBoundedInputStream((FileInputStream) inputStream);
79          }
80          // No limit
81          return newBoundedInputStream(BoundedInputStream.builder().setInputStream(inputStream).get());
82      }
83  
84      /**
85       * Creates a new BoundedInputStream bound by the size of the given path.
86       * <p>
87       * The new BoundedInputStream wraps a new {@link BufferedInputStream}.
88       * </p>
89       *
90       * @param path The path.
91       * @return a new BoundedInputStream
92       * @throws IOException if an I/O error occurs
93       */
94      @SuppressWarnings("resource") // Caller closes.
95      static BoundedInputStream newBoundedInputStream(final Path path) throws IOException {
96          // @formatter:off
97          return BoundedInputStream.builder()
98                  .setInputStream(new BufferedInputStream(Files.newInputStream(path)))
99                  .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 }