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}