View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.commons.compress.harmony.unpack200;
18  
19  import java.io.BufferedInputStream;
20  import java.io.File;
21  import java.io.FileInputStream;
22  import java.io.FilterInputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.net.URISyntaxException;
26  import java.net.URL;
27  import java.nio.file.Files;
28  import java.nio.file.Path;
29  import java.nio.file.Paths;
30  import java.util.jar.JarOutputStream;
31  
32  import org.apache.commons.compress.harmony.pack200.Pack200Adapter;
33  import org.apache.commons.compress.harmony.pack200.Pack200Exception;
34  import org.apache.commons.compress.java.util.jar.Pack200.Unpacker;
35  import org.apache.commons.io.input.BoundedInputStream;
36  import org.apache.commons.lang3.reflect.FieldUtils;
37  
38  /**
39   * This class provides the binding between the standard Pack200 interface and the internal interface for (un)packing.
40   */
41  public class Pack200UnpackerAdapter extends Pack200Adapter implements Unpacker {
42  
43      /**
44       * Creates a new BoundedInputStream bound by the size of the given file.
45       * <p>
46       * The new BoundedInputStream wraps a new {@link BufferedInputStream}.
47       * </p>
48       *
49       * @param file The file.
50       * @return a new BoundedInputStream
51       * @throws IOException if an I/O error occurs
52       */
53      static BoundedInputStream newBoundedInputStream(final File file) throws IOException {
54          return newBoundedInputStream(file.toPath());
55      }
56  
57      private static BoundedInputStream newBoundedInputStream(final FileInputStream fileInputStream) throws IOException {
58          return newBoundedInputStream(readPath(fileInputStream));
59      }
60  
61      static BoundedInputStream newBoundedInputStream(final InputStream inputStream) throws IOException {
62          if (inputStream instanceof BoundedInputStream) {
63              return (BoundedInputStream) inputStream;
64          }
65          if (inputStream instanceof FilterInputStream) {
66              return newBoundedInputStream(unwrap((FilterInputStream) inputStream));
67          }
68          if (inputStream instanceof FileInputStream) {
69              return newBoundedInputStream((FileInputStream) inputStream);
70          }
71          // No limit
72          return new BoundedInputStream(inputStream);
73      }
74  
75      /**
76       * Creates a new BoundedInputStream bound by the size of the given path.
77       * <p>
78       * The new BoundedInputStream wraps a new {@link BufferedInputStream}.
79       * </p>
80       *
81       * @param path The path.
82       * @return a new BoundedInputStream
83       * @throws IOException if an I/O error occurs
84       */
85      @SuppressWarnings("resource")
86      static BoundedInputStream newBoundedInputStream(final Path path) throws IOException {
87          return new BoundedInputStream(new BufferedInputStream(Files.newInputStream(path)), Files.size(path));
88      }
89  
90      /**
91       * Creates a new BoundedInputStream bound by the size of the given file.
92       * <p>
93       * The new BoundedInputStream wraps a new {@link BufferedInputStream}.
94       * </p>
95       *
96       * @param first the path string or initial part of the path string.
97       * @param more  additional strings to be joined to form the path string.
98       * @return a new BoundedInputStream
99       * @throws IOException if an I/O error occurs
100      */
101     static BoundedInputStream newBoundedInputStream(final String first, final String... more) throws IOException {
102         return newBoundedInputStream(Paths.get(first, more));
103     }
104 
105     /**
106      * Creates a new BoundedInputStream bound by the size of the given URL to a file.
107      * <p>
108      * The new BoundedInputStream wraps a new {@link BufferedInputStream}.
109      * </p>
110      *
111      * @param path The URL.
112      * @return a new BoundedInputStream
113      * @throws IOException        if an I/O error occurs
114      * @throws URISyntaxException
115      */
116     static BoundedInputStream newBoundedInputStream(final URL url) throws IOException, URISyntaxException {
117         return newBoundedInputStream(Paths.get(url.toURI()));
118     }
119 
120     @SuppressWarnings("unchecked")
121     private static <T> T readField(final Object object, final String fieldName) {
122         try {
123             return (T) FieldUtils.readField(object, fieldName, true);
124         } catch (final IllegalAccessException e) {
125             return null;
126         }
127     }
128 
129     static String readPath(final FileInputStream fis) {
130         return readField(fis, "path");
131     }
132 
133     /**
134      * Unwraps the given FilterInputStream to return its wrapped InputStream.
135      *
136      * @param filterInputStream The FilterInputStream to unwrap.
137      * @return The wrapped InputStream
138      */
139     static InputStream unwrap(final FilterInputStream filterInputStream) {
140         return readField(filterInputStream, "in");
141     }
142 
143     /**
144      * Unwraps the given InputStream if it is an FilterInputStream to return its wrapped InputStream.
145      *
146      * @param filterInputStream The FilterInputStream to unwrap.
147      * @return The wrapped InputStream
148      */
149     @SuppressWarnings("resource")
150     static InputStream unwrap(final InputStream inputStream) {
151         return inputStream instanceof FilterInputStream ? unwrap((FilterInputStream) inputStream) : inputStream;
152     }
153 
154     @Override
155     public void unpack(final File file, final JarOutputStream out) throws IOException {
156         if (file == null || out == null) {
157             throw new IllegalArgumentException("Must specify both input and output streams");
158         }
159         final long size = file.length();
160         final int bufferSize = size > 0 && size < DEFAULT_BUFFER_SIZE ? (int) size : DEFAULT_BUFFER_SIZE;
161         try (InputStream in = new BufferedInputStream(Files.newInputStream(file.toPath()), bufferSize)) {
162             unpack(in, out);
163         }
164     }
165 
166     @Override
167     public void unpack(final InputStream in, final JarOutputStream out) throws IOException {
168         if (in == null || out == null) {
169             throw new IllegalArgumentException("Must specify both input and output streams");
170         }
171         completed(0);
172         try {
173             new Archive(in, out).unpack();
174         } catch (final Pack200Exception e) {
175             throw new IOException("Failed to unpack Jar:" + e);
176         }
177         completed(1);
178     }
179 }