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