[13350] | 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 |
|
---|
| 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 |
|
---|
| 203 | public int read() throws IOException {
|
---|
| 204 | return read(tempBuf, 0, 1) == -1 ? -1 : (tempBuf[0] & 0xFF);
|
---|
| 205 | }
|
---|
| 206 |
|
---|
| 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 |
|
---|
| 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 |
|
---|
| 274 | public int available() throws IOException {
|
---|
| 275 | return filterChain.available();
|
---|
| 276 | }
|
---|
| 277 |
|
---|
| 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 |
|
---|
| 298 | public long getUnpaddedSize() {
|
---|
| 299 | return headerSize + inCounted.getSize() + check.getSize();
|
---|
| 300 | }
|
---|
| 301 |
|
---|
| 302 | public long getUncompressedSize() {
|
---|
| 303 | return uncompressedSize;
|
---|
| 304 | }
|
---|
| 305 | }
|
---|