1 /* 2 * BlockInputStream 3 * 4 * Author: Lasse Collin <lasse.collin@tukaani.org> 5 * 6 * This file has been put into the public domain. 7 * You can do whatever you want with this file. 8 */ 9 10 package org.tukaani.xz; 11 12 import java.io.InputStream; 13 import java.io.DataInputStream; 14 import java.io.ByteArrayInputStream; 15 import java.io.IOException; 16 import java.util.Arrays; 17 import org.tukaani.xz.common.DecoderUtil; 18 import org.tukaani.xz.check.Check; 19 20 class BlockInputStream extends InputStream { 21 private final DataInputStream inData; 22 private final CountingInputStream inCounted; 23 private InputStream filterChain; 24 private final Check check; 25 private final boolean verifyCheck; 26 27 private long uncompressedSizeInHeader = -1; 28 private long compressedSizeInHeader = -1; 29 private long compressedSizeLimit; 30 private final int headerSize; 31 private long uncompressedSize = 0; 32 private boolean endReached = false; 33 34 private final byte[] tempBuf = new byte[1]; 35 BlockInputStream(InputStream in, Check check, boolean verifyCheck, int memoryLimit, long unpaddedSizeInIndex, long uncompressedSizeInIndex, ArrayCache arrayCache)36 public BlockInputStream(InputStream in, 37 Check check, boolean verifyCheck, 38 int memoryLimit, 39 long unpaddedSizeInIndex, 40 long uncompressedSizeInIndex, 41 ArrayCache arrayCache) 42 throws IOException, IndexIndicatorException { 43 this.check = check; 44 this.verifyCheck = verifyCheck; 45 inData = new DataInputStream(in); 46 47 // Block Header Size or Index Indicator 48 int b = inData.readUnsignedByte(); 49 50 // See if this begins the Index field. 51 if (b == 0x00) 52 throw new IndexIndicatorException(); 53 54 // Read the rest of the Block Header. 55 headerSize = 4 * (b + 1); 56 57 final byte[] buf = new byte[headerSize]; 58 buf[0] = (byte)b; 59 inData.readFully(buf, 1, headerSize - 1); 60 61 // Validate the CRC32. 62 if (!DecoderUtil.isCRC32Valid(buf, 0, headerSize - 4, headerSize - 4)) 63 throw new CorruptedInputException("XZ Block Header is corrupt"); 64 65 // Check for reserved bits in Block Flags. 66 if ((buf[1] & 0x3C) != 0) 67 throw new UnsupportedOptionsException( 68 "Unsupported options in XZ Block Header"); 69 70 // Memory for the Filter Flags field 71 int filterCount = (buf[1] & 0x03) + 1; 72 long[] filterIDs = new long[filterCount]; 73 byte[][] filterProps = new byte[filterCount][]; 74 75 // Use a stream to parse the fields after the Block Flags field. 76 // Exclude the CRC32 field at the end. 77 ByteArrayInputStream bufStream = new ByteArrayInputStream( 78 buf, 2, headerSize - 6); 79 80 try { 81 // Set the maximum valid compressed size. This is overriden 82 // by the value from the Compressed Size field if it is present. 83 compressedSizeLimit = (DecoderUtil.VLI_MAX & ~3) 84 - headerSize - check.getSize(); 85 86 // Decode and validate Compressed Size if the relevant flag 87 // is set in Block Flags. 88 if ((buf[1] & 0x40) != 0x00) { 89 compressedSizeInHeader = DecoderUtil.decodeVLI(bufStream); 90 91 if (compressedSizeInHeader == 0 92 || compressedSizeInHeader > compressedSizeLimit) 93 throw new CorruptedInputException(); 94 95 compressedSizeLimit = compressedSizeInHeader; 96 } 97 98 // Decode Uncompressed Size if the relevant flag is set 99 // in Block Flags. 100 if ((buf[1] & 0x80) != 0x00) 101 uncompressedSizeInHeader = DecoderUtil.decodeVLI(bufStream); 102 103 // Decode Filter Flags. 104 for (int i = 0; i < filterCount; ++i) { 105 filterIDs[i] = DecoderUtil.decodeVLI(bufStream); 106 107 long filterPropsSize = DecoderUtil.decodeVLI(bufStream); 108 if (filterPropsSize > bufStream.available()) 109 throw new CorruptedInputException(); 110 111 filterProps[i] = new byte[(int)filterPropsSize]; 112 bufStream.read(filterProps[i]); 113 } 114 115 } catch (IOException e) { 116 throw new CorruptedInputException("XZ Block Header is corrupt"); 117 } 118 119 // Check that the remaining bytes are zero. 120 for (int i = bufStream.available(); i > 0; --i) 121 if (bufStream.read() != 0x00) 122 throw new UnsupportedOptionsException( 123 "Unsupported options in XZ Block Header"); 124 125 // Validate the Blcok Header against the Index when doing 126 // random access reading. 127 if (unpaddedSizeInIndex != -1) { 128 // Compressed Data must be at least one byte, so if Block Header 129 // and Check alone take as much or more space than the size 130 // stored in the Index, the file is corrupt. 131 int headerAndCheckSize = headerSize + check.getSize(); 132 if (headerAndCheckSize >= unpaddedSizeInIndex) 133 throw new CorruptedInputException( 134 "XZ Index does not match a Block Header"); 135 136 // The compressed size calculated from Unpadded Size must 137 // match the value stored in the Compressed Size field in 138 // the Block Header. 139 long compressedSizeFromIndex 140 = unpaddedSizeInIndex - headerAndCheckSize; 141 if (compressedSizeFromIndex > compressedSizeLimit 142 || (compressedSizeInHeader != -1 143 && compressedSizeInHeader != compressedSizeFromIndex)) 144 throw new CorruptedInputException( 145 "XZ Index does not match a Block Header"); 146 147 // The uncompressed size stored in the Index must match 148 // the value stored in the Uncompressed Size field in 149 // the Block Header. 150 if (uncompressedSizeInHeader != -1 151 && uncompressedSizeInHeader != uncompressedSizeInIndex) 152 throw new CorruptedInputException( 153 "XZ Index does not match a Block Header"); 154 155 // For further validation, pretend that the values from the Index 156 // were stored in the Block Header. 157 compressedSizeLimit = compressedSizeFromIndex; 158 compressedSizeInHeader = compressedSizeFromIndex; 159 uncompressedSizeInHeader = uncompressedSizeInIndex; 160 } 161 162 // Check if the Filter IDs are supported, decode 163 // the Filter Properties, and check that they are 164 // supported by this decoder implementation. 165 FilterDecoder[] filters = new FilterDecoder[filterIDs.length]; 166 167 for (int i = 0; i < filters.length; ++i) { 168 if (filterIDs[i] == LZMA2Coder.FILTER_ID) 169 filters[i] = new LZMA2Decoder(filterProps[i]); 170 171 else if (filterIDs[i] == DeltaCoder.FILTER_ID) 172 filters[i] = new DeltaDecoder(filterProps[i]); 173 174 else if (BCJDecoder.isBCJFilterID(filterIDs[i])) 175 filters[i] = new BCJDecoder(filterIDs[i], filterProps[i]); 176 177 else 178 throw new UnsupportedOptionsException( 179 "Unknown Filter ID " + filterIDs[i]); 180 } 181 182 RawCoder.validate(filters); 183 184 // Check the memory usage limit. 185 if (memoryLimit >= 0) { 186 int memoryNeeded = 0; 187 for (int i = 0; i < filters.length; ++i) 188 memoryNeeded += filters[i].getMemoryUsage(); 189 190 if (memoryNeeded > memoryLimit) 191 throw new MemoryLimitException(memoryNeeded, memoryLimit); 192 } 193 194 // Use an input size counter to calculate 195 // the size of the Compressed Data field. 196 inCounted = new CountingInputStream(in); 197 198 // Initialize the filter chain. 199 filterChain = inCounted; 200 for (int i = filters.length - 1; i >= 0; --i) 201 filterChain = filters[i].getInputStream(filterChain, arrayCache); 202 } 203 read()204 public int read() throws IOException { 205 return read(tempBuf, 0, 1) == -1 ? -1 : (tempBuf[0] & 0xFF); 206 } 207 read(byte[] buf, int off, int len)208 public int read(byte[] buf, int off, int len) throws IOException { 209 if (endReached) 210 return -1; 211 212 int ret = filterChain.read(buf, off, len); 213 214 if (ret > 0) { 215 if (verifyCheck) 216 check.update(buf, off, ret); 217 218 uncompressedSize += ret; 219 220 // Catch invalid values. 221 long compressedSize = inCounted.getSize(); 222 if (compressedSize < 0 223 || compressedSize > compressedSizeLimit 224 || uncompressedSize < 0 225 || (uncompressedSizeInHeader != -1 226 && uncompressedSize > uncompressedSizeInHeader)) 227 throw new CorruptedInputException(); 228 229 // Check the Block integrity as soon as possible: 230 // - The filter chain shouldn't return less than requested 231 // unless it hit the end of the input. 232 // - If the uncompressed size is known, we know when there 233 // shouldn't be more data coming. We still need to read 234 // one byte to let the filter chain catch errors and to 235 // let it read end of payload marker(s). 236 if (ret < len || uncompressedSize == uncompressedSizeInHeader) { 237 if (filterChain.read() != -1) 238 throw new CorruptedInputException(); 239 240 validate(); 241 endReached = true; 242 } 243 } else if (ret == -1) { 244 validate(); 245 endReached = true; 246 } 247 248 return ret; 249 } 250 validate()251 private void validate() throws IOException { 252 long compressedSize = inCounted.getSize(); 253 254 // Validate Compressed Size and Uncompressed Size if they were 255 // present in Block Header. 256 if ((compressedSizeInHeader != -1 257 && compressedSizeInHeader != compressedSize) 258 || (uncompressedSizeInHeader != -1 259 && uncompressedSizeInHeader != uncompressedSize)) 260 throw new CorruptedInputException(); 261 262 // Block Padding bytes must be zeros. 263 while ((compressedSize++ & 3) != 0) 264 if (inData.readUnsignedByte() != 0x00) 265 throw new CorruptedInputException(); 266 267 // Validate the integrity check if verifyCheck is true. 268 byte[] storedCheck = new byte[check.getSize()]; 269 inData.readFully(storedCheck); 270 if (verifyCheck && !Arrays.equals(check.finish(), storedCheck)) 271 throw new CorruptedInputException("Integrity check (" 272 + check.getName() + ") does not match"); 273 } 274 available()275 public int available() throws IOException { 276 return filterChain.available(); 277 } 278 close()279 public void close() { 280 // This puts all arrays, that were allocated from ArrayCache, 281 // back to the ArrayCache. The last filter in the chain will 282 // call inCounted.close() which, being an instance of 283 // CloseIgnoringInputStream, won't close() the InputStream that 284 // was provided by the application. 285 try { 286 filterChain.close(); 287 } catch (IOException e) { 288 // It's a bug if we get here. The InputStreams that we are closing 289 // are all from this package and they are known to not throw 290 // IOException. (They could throw an IOException if we were 291 // closing the application-supplied InputStream, but 292 // inCounted.close() doesn't do that.) 293 assert false; 294 } 295 296 filterChain = null; 297 } 298 getUnpaddedSize()299 public long getUnpaddedSize() { 300 return headerSize + inCounted.getSize() + check.getSize(); 301 } 302 getUncompressedSize()303 public long getUncompressedSize() { 304 return uncompressedSize; 305 } 306 } 307