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