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 */ 18 package org.apache.commons.compress.archivers.sevenz; 19 20 import java.io.ByteArrayInputStream; 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.io.OutputStream; 24 import java.io.SequenceInputStream; 25 import java.util.Arrays; 26 import java.util.HashMap; 27 import java.util.Map; 28 import java.util.zip.Deflater; 29 import java.util.zip.DeflaterOutputStream; 30 import java.util.zip.Inflater; 31 import java.util.zip.InflaterInputStream; 32 33 import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; 34 import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream; 35 import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream; 36 import org.apache.commons.compress.utils.FlushShieldFilterOutputStream; 37 import org.tukaani.xz.ARMOptions; 38 import org.tukaani.xz.ARMThumbOptions; 39 import org.tukaani.xz.FilterOptions; 40 import org.tukaani.xz.FinishableWrapperOutputStream; 41 import org.tukaani.xz.IA64Options; 42 import org.tukaani.xz.PowerPCOptions; 43 import org.tukaani.xz.SPARCOptions; 44 import org.tukaani.xz.X86Options; 45 46 class Coders { 47 private static final Map<SevenZMethod, CoderBase> CODER_MAP = new HashMap<SevenZMethod, CoderBase>() { 48 49 private static final long serialVersionUID = 1664829131806520867L; 50 { 51 put(SevenZMethod.COPY, new CopyDecoder()); 52 put(SevenZMethod.LZMA, new LZMADecoder()); 53 put(SevenZMethod.LZMA2, new LZMA2Decoder()); 54 put(SevenZMethod.DEFLATE, new DeflateDecoder()); 55 put(SevenZMethod.DEFLATE64, new Deflate64Decoder()); 56 put(SevenZMethod.BZIP2, new BZIP2Decoder()); 57 put(SevenZMethod.AES256SHA256, new AES256SHA256Decoder()); 58 put(SevenZMethod.BCJ_X86_FILTER, new BCJDecoder(new X86Options())); 59 put(SevenZMethod.BCJ_PPC_FILTER, new BCJDecoder(new PowerPCOptions())); 60 put(SevenZMethod.BCJ_IA64_FILTER, new BCJDecoder(new IA64Options())); 61 put(SevenZMethod.BCJ_ARM_FILTER, new BCJDecoder(new ARMOptions())); 62 put(SevenZMethod.BCJ_ARM_THUMB_FILTER, new BCJDecoder(new ARMThumbOptions())); 63 put(SevenZMethod.BCJ_SPARC_FILTER, new BCJDecoder(new SPARCOptions())); 64 put(SevenZMethod.DELTA_FILTER, new DeltaDecoder()); 65 }}; 66 67 static CoderBase findByMethod(final SevenZMethod method) { 68 return CODER_MAP.get(method); 69 } 70 71 static InputStream addDecoder(final String archiveName, final InputStream is, final long uncompressedLength, 72 final Coder coder, final byte[] password) throws IOException { 73 final CoderBase cb = findByMethod(SevenZMethod.byId(coder.decompressionMethodId)); 74 if (cb == null) { 75 throw new IOException("Unsupported compression method " + 76 Arrays.toString(coder.decompressionMethodId) 77 + " used in " + archiveName); 78 } 79 return cb.decode(archiveName, is, uncompressedLength, coder, password); 80 } 81 82 static OutputStream addEncoder(final OutputStream out, final SevenZMethod method, 83 final Object options) throws IOException { 84 final CoderBase cb = findByMethod(method); 85 if (cb == null) { 86 throw new IOException("Unsupported compression method " + method); 87 } 88 return cb.encode(out, options); 89 } 90 91 static class CopyDecoder extends CoderBase { 92 @Override 93 InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, 94 final Coder coder, final byte[] password) throws IOException { 95 return in; 96 } 97 @Override 98 OutputStream encode(final OutputStream out, final Object options) { 99 return out; 100 } 101 } 102 103 static class BCJDecoder extends CoderBase { 104 private final FilterOptions opts; 105 BCJDecoder(final FilterOptions opts) { 106 this.opts = opts; 107 } 108 109 @Override 110 InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, 111 final Coder coder, final byte[] password) throws IOException { 112 try { 113 return opts.getInputStream(in); 114 } catch (final AssertionError e) { 115 throw new IOException("BCJ filter used in " + archiveName 116 + " needs XZ for Java > 1.4 - see " 117 + "https://commons.apache.org/proper/commons-compress/limitations.html#7Z", 118 e); 119 } 120 } 121 122 @SuppressWarnings("resource") 123 @Override 124 OutputStream encode(final OutputStream out, final Object options) { 125 return new FlushShieldFilterOutputStream(opts.getOutputStream(new FinishableWrapperOutputStream(out))); 126 } 127 } 128 129 static class DeflateDecoder extends CoderBase { 130 private static final byte[] ONE_ZERO_BYTE = new byte[1]; 131 DeflateDecoder() { 132 super(Number.class); 133 } 134 135 @SuppressWarnings("resource") // caller must close the InputStream 136 @Override 137 InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, 138 final Coder coder, final byte[] password) 139 throws IOException { 140 final Inflater inflater = new Inflater(true); 141 // Inflater with nowrap=true has this odd contract for a zero padding 142 // byte following the data stream; this used to be zlib's requirement 143 // and has been fixed a long time ago, but the contract persists so 144 // we comply. 145 // https://docs.oracle.com/javase/7/docs/api/java/util/zip/Inflater.html#Inflater(boolean) 146 final InflaterInputStream inflaterInputStream = new InflaterInputStream(new SequenceInputStream(in, 147 new ByteArrayInputStream(ONE_ZERO_BYTE)), inflater); 148 return new DeflateDecoderInputStream(inflaterInputStream, inflater); 149 } 150 @Override 151 OutputStream encode(final OutputStream out, final Object options) { 152 final int level = numberOptionOrDefault(options, 9); 153 final Deflater deflater = new Deflater(level, true); 154 final DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(out, deflater); 155 return new DeflateDecoderOutputStream(deflaterOutputStream, deflater); 156 } 157 158 static class DeflateDecoderInputStream extends InputStream { 159 160 InflaterInputStream inflaterInputStream; 161 Inflater inflater; 162 163 public DeflateDecoderInputStream(InflaterInputStream inflaterInputStream, 164 Inflater inflater) { 165 this.inflaterInputStream = inflaterInputStream; 166 this.inflater = inflater; 167 } 168 169 @Override 170 public int read() throws IOException { 171 return inflaterInputStream.read(); 172 } 173 174 @Override 175 public int read(final byte[] b, final int off, final int len) throws IOException { 176 return inflaterInputStream.read(b, off, len); 177 } 178 179 @Override 180 public int read(final byte[] b) throws IOException { 181 return inflaterInputStream.read(b); 182 } 183 184 @Override 185 public void close() throws IOException { 186 try { 187 inflaterInputStream.close(); 188 } finally { 189 inflater.end(); 190 } 191 } 192 } 193 194 static class DeflateDecoderOutputStream extends OutputStream { 195 196 DeflaterOutputStream deflaterOutputStream; 197 Deflater deflater; 198 199 public DeflateDecoderOutputStream(DeflaterOutputStream deflaterOutputStream, 200 Deflater deflater) { 201 this.deflaterOutputStream = deflaterOutputStream; 202 this.deflater = deflater; 203 } 204 205 @Override 206 public void write(final int b) throws IOException { 207 deflaterOutputStream.write(b); 208 } 209 210 @Override 211 public void write(final byte[] b) throws IOException { 212 deflaterOutputStream.write(b); 213 } 214 215 @Override 216 public void write(final byte[] b, final int off, final int len) throws IOException { 217 deflaterOutputStream.write(b, off, len); 218 } 219 220 @Override 221 public void close() throws IOException { 222 try { 223 deflaterOutputStream.close(); 224 } finally { 225 deflater.end(); 226 } 227 } 228 } 229 } 230 231 static class Deflate64Decoder extends CoderBase { 232 Deflate64Decoder() { 233 super(Number.class); 234 } 235 236 @SuppressWarnings("resource") // caller must close the InputStream 237 @Override 238 InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, 239 final Coder coder, final byte[] password) 240 throws IOException { 241 return new Deflate64CompressorInputStream(in); 242 } 243 } 244 245 static class BZIP2Decoder extends CoderBase { 246 BZIP2Decoder() { 247 super(Number.class); 248 } 249 250 @Override 251 InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, 252 final Coder coder, final byte[] password) 253 throws IOException { 254 return new BZip2CompressorInputStream(in); 255 } 256 @Override 257 OutputStream encode(final OutputStream out, final Object options) 258 throws IOException { 259 final int blockSize = numberOptionOrDefault(options, BZip2CompressorOutputStream.MAX_BLOCKSIZE); 260 return new BZip2CompressorOutputStream(out, blockSize); 261 } 262 } 263 264 } 265