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