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