1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.commons.compress.archivers.zip;
21
22 import java.io.IOException;
23 import java.io.InputStream;
24
25 import org.apache.commons.compress.utils.ExactMath;
26 import org.apache.commons.compress.utils.InputStreamStatistics;
27 import org.apache.commons.io.input.BoundedInputStream;
28 import org.apache.commons.io.input.CloseShieldInputStream;
29
30
31
32
33
34
35
36
37
38
39 final class ExplodingInputStream extends InputStream implements InputStreamStatistics {
40
41
42 private final InputStream in;
43
44
45 private BitStream bits;
46
47
48 private final int dictionarySize;
49
50
51 private final int numberOfTrees;
52
53 private final int minimumMatchLength;
54
55
56 private BinaryTree literalTree;
57
58
59 private BinaryTree lengthTree;
60
61
62 private BinaryTree distanceTree;
63
64
65 private final CircularBuffer buffer = new CircularBuffer(32 * 1024);
66
67 private long uncompressedCount;
68
69 private long treeSizes;
70
71
72
73
74
75
76
77
78 ExplodingInputStream(final int dictionarySize, final int numberOfTrees, final InputStream in) {
79 if (dictionarySize != 4096 && dictionarySize != 8192) {
80 throw new IllegalArgumentException("The dictionary size must be 4096 or 8192");
81 }
82 if (numberOfTrees != 2 && numberOfTrees != 3) {
83 throw new IllegalArgumentException("The number of trees must be 2 or 3");
84 }
85 this.dictionarySize = dictionarySize;
86 this.numberOfTrees = numberOfTrees;
87 this.minimumMatchLength = numberOfTrees;
88 this.in = in;
89 }
90
91
92
93
94 @Override
95 public void close() throws IOException {
96 in.close();
97 }
98
99
100
101
102
103
104 private void fillBuffer() throws IOException {
105 init();
106
107 final int bit = bits.nextBit();
108 if (bit == -1) {
109
110 return;
111 }
112 if (bit == 1) {
113
114 final int literal;
115 if (literalTree != null) {
116 literal = literalTree.read(bits);
117 } else {
118 literal = bits.nextByte();
119 }
120
121 if (literal == -1) {
122
123 return;
124 }
125
126 buffer.put(literal);
127
128 } else {
129
130 final int distanceLowSize = dictionarySize == 4096 ? 6 : 7;
131 final int distanceLow = (int) bits.nextBits(distanceLowSize);
132 final int distanceHigh = distanceTree.read(bits);
133 if (distanceHigh == -1 && distanceLow <= 0) {
134
135 return;
136 }
137 final int distance = distanceHigh << distanceLowSize | distanceLow;
138
139 int length = lengthTree.read(bits);
140 if (length == 63) {
141 final long nextByte = bits.nextBits(8);
142 if (nextByte == -1) {
143
144 return;
145 }
146 length = ExactMath.add(length, nextByte);
147 }
148 length += minimumMatchLength;
149
150 buffer.copy(distance + 1, length);
151 }
152 }
153
154
155
156
157 @Override
158 public long getCompressedCount() {
159 return bits.getBytesRead() + treeSizes;
160 }
161
162
163
164
165 @Override
166 public long getUncompressedCount() {
167 return uncompressedCount;
168 }
169
170
171
172
173
174
175 private void init() throws IOException {
176 if (bits == null) {
177
178 try (BoundedInputStream cis = BoundedInputStream.builder().setInputStream(CloseShieldInputStream.wrap(in)).get()) {
179 if (numberOfTrees == 3) {
180 literalTree = BinaryTree.decode(cis, 256);
181 }
182
183 lengthTree = BinaryTree.decode(cis, 64);
184 distanceTree = BinaryTree.decode(cis, 64);
185 treeSizes += cis.getCount();
186 }
187
188 bits = new BitStream(in);
189 }
190 }
191
192 @Override
193 public int read() throws IOException {
194 if (!buffer.available()) {
195 try {
196 fillBuffer();
197 } catch (final IllegalArgumentException ex) {
198 throw new IOException("bad IMPLODE stream", ex);
199 }
200 }
201
202 final int ret = buffer.get();
203 if (ret > -1) {
204 uncompressedCount++;
205 }
206 return ret;
207 }
208
209 }