source: josm/trunk/src/org/tukaani/xz/BlockInputStream.java@ 13660

Last change on this file since 13660 was 13350, checked in by stoecker, 7 years ago

see #15816 - add XZ support

File size: 11.6 KB
Line 
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
10package org.tukaani.xz;
11
12import java.io.InputStream;
13import java.io.DataInputStream;
14import java.io.ByteArrayInputStream;
15import java.io.IOException;
16import java.util.Arrays;
17import org.tukaani.xz.common.DecoderUtil;
18import org.tukaani.xz.check.Check;
19
20class 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}
Note: See TracBrowser for help on using the repository browser.