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 * http://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 20 package org.apache.commons.compress.compressors.pack200; 21 22 import java.io.File; 23 import java.io.FilterInputStream; 24 import java.io.IOException; 25 import java.io.InputStream; 26 import java.util.Map; 27 import java.util.jar.JarOutputStream; 28 import java.util.jar.Pack200; 29 30 import org.apache.commons.compress.compressors.CompressorInputStream; 31 import org.apache.commons.compress.utils.IOUtils; 32 33 /** 34 * An input stream that decompresses from the Pack200 format to be read 35 * as any other stream. 36 * 37 * <p>The {@link CompressorInputStream#getCount getCount} and {@link 38 * CompressorInputStream#getBytesRead getBytesRead} methods always 39 * return 0.</p> 40 * 41 * @NotThreadSafe 42 * @since 1.3 43 */ 44 public class Pack200CompressorInputStream extends CompressorInputStream { 45 private final InputStream originalInput; 46 private final StreamBridge streamBridge; 47 48 /** 49 * Decompresses the given stream, caching the decompressed data in 50 * memory. 51 * 52 * <p>When reading from a file the File-arg constructor may 53 * provide better performance.</p> 54 * 55 * @param in the InputStream from which this object should be created 56 * @throws IOException if reading fails 57 */ Pack200CompressorInputStream(final InputStream in)58 public Pack200CompressorInputStream(final InputStream in) 59 throws IOException { 60 this(in, Pack200Strategy.IN_MEMORY); 61 } 62 63 /** 64 * Decompresses the given stream using the given strategy to cache 65 * the results. 66 * 67 * <p>When reading from a file the File-arg constructor may 68 * provide better performance.</p> 69 * 70 * @param in the InputStream from which this object should be created 71 * @param mode the strategy to use 72 * @throws IOException if reading fails 73 */ Pack200CompressorInputStream(final InputStream in, final Pack200Strategy mode)74 public Pack200CompressorInputStream(final InputStream in, 75 final Pack200Strategy mode) 76 throws IOException { 77 this(in, null, mode, null); 78 } 79 80 /** 81 * Decompresses the given stream, caching the decompressed data in 82 * memory and using the given properties. 83 * 84 * <p>When reading from a file the File-arg constructor may 85 * provide better performance.</p> 86 * 87 * @param in the InputStream from which this object should be created 88 * @param props Pack200 properties to use 89 * @throws IOException if reading fails 90 */ Pack200CompressorInputStream(final InputStream in, final Map<String, String> props)91 public Pack200CompressorInputStream(final InputStream in, 92 final Map<String, String> props) 93 throws IOException { 94 this(in, Pack200Strategy.IN_MEMORY, props); 95 } 96 97 /** 98 * Decompresses the given stream using the given strategy to cache 99 * the results and the given properties. 100 * 101 * <p>When reading from a file the File-arg constructor may 102 * provide better performance.</p> 103 * 104 * @param in the InputStream from which this object should be created 105 * @param mode the strategy to use 106 * @param props Pack200 properties to use 107 * @throws IOException if reading fails 108 */ Pack200CompressorInputStream(final InputStream in, final Pack200Strategy mode, final Map<String, String> props)109 public Pack200CompressorInputStream(final InputStream in, 110 final Pack200Strategy mode, 111 final Map<String, String> props) 112 throws IOException { 113 this(in, null, mode, props); 114 } 115 116 /** 117 * Decompresses the given file, caching the decompressed data in 118 * memory. 119 * 120 * @param f the file to decompress 121 * @throws IOException if reading fails 122 */ Pack200CompressorInputStream(final File f)123 public Pack200CompressorInputStream(final File f) throws IOException { 124 this(f, Pack200Strategy.IN_MEMORY); 125 } 126 127 /** 128 * Decompresses the given file using the given strategy to cache 129 * the results. 130 * 131 * @param f the file to decompress 132 * @param mode the strategy to use 133 * @throws IOException if reading fails 134 */ Pack200CompressorInputStream(final File f, final Pack200Strategy mode)135 public Pack200CompressorInputStream(final File f, final Pack200Strategy mode) 136 throws IOException { 137 this(null, f, mode, null); 138 } 139 140 /** 141 * Decompresses the given file, caching the decompressed data in 142 * memory and using the given properties. 143 * 144 * @param f the file to decompress 145 * @param props Pack200 properties to use 146 * @throws IOException if reading fails 147 */ Pack200CompressorInputStream(final File f, final Map<String, String> props)148 public Pack200CompressorInputStream(final File f, 149 final Map<String, String> props) 150 throws IOException { 151 this(f, Pack200Strategy.IN_MEMORY, props); 152 } 153 154 /** 155 * Decompresses the given file using the given strategy to cache 156 * the results and the given properties. 157 * 158 * @param f the file to decompress 159 * @param mode the strategy to use 160 * @param props Pack200 properties to use 161 * @throws IOException if reading fails 162 */ Pack200CompressorInputStream(final File f, final Pack200Strategy mode, final Map<String, String> props)163 public Pack200CompressorInputStream(final File f, final Pack200Strategy mode, 164 final Map<String, String> props) 165 throws IOException { 166 this(null, f, mode, props); 167 } 168 Pack200CompressorInputStream(final InputStream in, final File f, final Pack200Strategy mode, final Map<String, String> props)169 private Pack200CompressorInputStream(final InputStream in, final File f, 170 final Pack200Strategy mode, 171 final Map<String, String> props) 172 throws IOException { 173 originalInput = in; 174 streamBridge = mode.newStreamBridge(); 175 try (final JarOutputStream jarOut = new JarOutputStream(streamBridge)) { 176 final Pack200.Unpacker u = Pack200.newUnpacker(); 177 if (props != null) { 178 u.properties().putAll(props); 179 } 180 if (f == null) { 181 u.unpack(new FilterInputStream(in) { 182 @Override 183 public void close() { 184 // unpack would close this stream but we 185 // want to give the user code more control 186 } 187 }, jarOut); 188 } else { 189 u.unpack(f, jarOut); 190 } 191 } 192 } 193 194 @Override read()195 public int read() throws IOException { 196 return streamBridge.getInput().read(); 197 } 198 199 @Override read(final byte[] b)200 public int read(final byte[] b) throws IOException { 201 return streamBridge.getInput().read(b); 202 } 203 204 @Override read(final byte[] b, final int off, final int count)205 public int read(final byte[] b, final int off, final int count) throws IOException { 206 return streamBridge.getInput().read(b, off, count); 207 } 208 209 @Override available()210 public int available() throws IOException { 211 return streamBridge.getInput().available(); 212 } 213 214 @Override markSupported()215 public boolean markSupported() { 216 try { 217 return streamBridge.getInput().markSupported(); 218 } catch (final IOException ex) { 219 return false; 220 } 221 } 222 223 @Override mark(final int limit)224 public void mark(final int limit) { 225 try { 226 streamBridge.getInput().mark(limit); 227 } catch (final IOException ex) { 228 throw new RuntimeException(ex); //NOSONAR 229 } 230 } 231 232 @Override reset()233 public void reset() throws IOException { 234 streamBridge.getInput().reset(); 235 } 236 237 @Override skip(final long count)238 public long skip(final long count) throws IOException { 239 return IOUtils.skip(streamBridge.getInput(), count); 240 } 241 242 @Override close()243 public void close() throws IOException { 244 try { 245 streamBridge.stop(); 246 } finally { 247 if (originalInput != null) { 248 originalInput.close(); 249 } 250 } 251 } 252 253 private static final byte[] CAFE_DOOD = new byte[] { 254 (byte) 0xCA, (byte) 0xFE, (byte) 0xD0, (byte) 0x0D 255 }; 256 private static final int SIG_LENGTH = CAFE_DOOD.length; 257 258 /** 259 * Checks if the signature matches what is expected for a pack200 260 * file (0xCAFED00D). 261 * 262 * @param signature 263 * the bytes to check 264 * @param length 265 * the number of bytes to check 266 * @return true, if this stream is a pack200 compressed stream, 267 * false otherwise 268 */ matches(final byte[] signature, final int length)269 public static boolean matches(final byte[] signature, final int length) { 270 if (length < SIG_LENGTH) { 271 return false; 272 } 273 274 for (int i = 0; i < SIG_LENGTH; i++) { 275 if (signature[i] != CAFE_DOOD[i]) { 276 return false; 277 } 278 } 279 280 return true; 281 } 282 } 283