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