1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.core5.http2.hpack;
29
30 import java.nio.ByteBuffer;
31 import java.nio.CharBuffer;
32 import java.nio.charset.CharacterCodingException;
33 import java.nio.charset.Charset;
34 import java.nio.charset.CharsetDecoder;
35 import java.nio.charset.CoderResult;
36 import java.nio.charset.StandardCharsets;
37 import java.util.ArrayList;
38 import java.util.List;
39
40 import org.apache.hc.core5.annotation.Internal;
41 import org.apache.hc.core5.http.Header;
42 import org.apache.hc.core5.http.message.BasicHeader;
43 import org.apache.hc.core5.util.Args;
44 import org.apache.hc.core5.util.ByteArrayBuffer;
45
46
47
48
49
50
51 @Internal
52 public final class HPackDecoder {
53
54 private static final String UNEXPECTED_EOS = "Unexpected end of HPACK data";
55 private static final String MAX_LIMIT_EXCEEDED = "Max integer exceeded";
56
57 private final InboundDynamicTable dynamicTable;
58 private final ByteArrayBuffer contentBuf;
59 private final CharsetDecoder charsetDecoder;
60 private CharBuffer tmpBuf;
61 private int maxTableSize;
62 private int maxListSize;
63
64 HPackDecoder(final InboundDynamicTable dynamicTable, final CharsetDecoder charsetDecoder) {
65 this.dynamicTable = dynamicTable != null ? dynamicTable : new InboundDynamicTable();
66 this.contentBuf = new ByteArrayBuffer(256);
67 this.charsetDecoder = charsetDecoder;
68 this.maxTableSize = dynamicTable != null ? dynamicTable.getMaxSize() : Integer.MAX_VALUE;
69 this.maxListSize = Integer.MAX_VALUE;
70 }
71
72 HPackDecoder(final InboundDynamicTable dynamicTable, final Charset charset) {
73 this(dynamicTable, charset != null && !StandardCharsets.US_ASCII.equals(charset) ? charset.newDecoder() : null);
74 }
75
76 public HPackDecoder(final Charset charset) {
77 this(new InboundDynamicTable(), charset);
78 }
79
80 public HPackDecoder(final CharsetDecoder charsetDecoder) {
81 this(new InboundDynamicTable(), charsetDecoder);
82 }
83
84 static int readByte(final ByteBuffer src) throws HPackException {
85
86 if (!src.hasRemaining()) {
87 throw new HPackException(UNEXPECTED_EOS);
88 }
89 return src.get() & 0xff;
90 }
91
92 static int peekByte(final ByteBuffer src) throws HPackException {
93
94 if (!src.hasRemaining()) {
95 throw new HPackException(UNEXPECTED_EOS);
96 }
97 final int pos = src.position();
98 final int b = src.get() & 0xff;
99 src.position(pos);
100 return b;
101 }
102
103 static int decodeInt(final ByteBuffer src, final int n) throws HPackException {
104
105 final int nbits = 0xff >>> (8 - n);
106 int value = readByte(src) & nbits;
107 if (value < nbits) {
108 return value;
109 }
110 int m = 0;
111 while (m < 32) {
112 final int b = readByte(src);
113 if ((b & 0x80) != 0) {
114 value += (b & 0x7f) << m;
115 m += 7;
116 } else {
117 if (m == 28 && (b & 0xf8) != 0) {
118 break;
119 }
120 value += b << m;
121 return value;
122 }
123 }
124 throw new HPackException(MAX_LIMIT_EXCEEDED);
125 }
126
127 static void decodePlainString(final ByteArrayBuffer buffer, final ByteBuffer src) throws HPackException {
128 final int strLen = decodeInt(src, 7);
129 final int remaining = src.remaining();
130 if (strLen > remaining) {
131 throw new HPackException(UNEXPECTED_EOS);
132 }
133 final int originalLimit = src.limit();
134 src.limit(originalLimit - (remaining - strLen));
135 buffer.append(src);
136 src.limit(originalLimit);
137 }
138
139 static void decodeHuffman(final ByteArrayBuffer buffer, final ByteBuffer src) throws HPackException {
140
141 final int strLen = decodeInt(src, 7);
142 if (strLen > src.remaining()) {
143 throw new HPackException(UNEXPECTED_EOS);
144 }
145 final int limit = src.limit();
146 src.limit(src.position() + strLen);
147 Huffman.DECODER.decode(buffer, src);
148 src.limit(limit);
149 }
150
151 void decodeString(final ByteArrayBuffer buffer, final ByteBuffer src) throws HPackException {
152
153 final int firstByte = peekByte(src);
154 if ((firstByte & 0x80) == 0x80) {
155 decodeHuffman(buffer, src);
156 } else {
157 decodePlainString(buffer, src);
158 }
159 }
160
161 private void clearState() {
162
163 if (this.tmpBuf != null) {
164 this.tmpBuf.clear();
165 }
166 if (this.charsetDecoder != null) {
167 this.charsetDecoder.reset();
168 }
169 this.contentBuf.clear();
170 }
171
172 private void expandCapacity(final int capacity) {
173
174 final CharBuffer previous = this.tmpBuf;
175 this.tmpBuf = CharBuffer.allocate(capacity);
176 previous.flip();
177 this.tmpBuf.put(previous);
178 }
179
180 private void ensureCapacity(final int extra) {
181
182 if (this.tmpBuf == null) {
183 this.tmpBuf = CharBuffer.allocate(Math.max(256, extra));
184 }
185 final int requiredCapacity = this.tmpBuf.remaining() + extra;
186 if (requiredCapacity > this.tmpBuf.capacity()) {
187 expandCapacity(requiredCapacity);
188 }
189 }
190
191 int decodeString(final ByteBuffer src, final StringBuilder buf) throws HPackException, CharacterCodingException {
192
193 clearState();
194 decodeString(this.contentBuf, src);
195 final int binaryLen = this.contentBuf.length();
196 if (binaryLen == 0) {
197 return 0;
198 }
199 if (this.charsetDecoder == null) {
200 buf.ensureCapacity(binaryLen);
201 for (int i = 0; i < binaryLen; i++) {
202 buf.append((char) (this.contentBuf.byteAt(i) & 0xff));
203 }
204 } else {
205 final ByteBuffer in = ByteBuffer.wrap(this.contentBuf.array(), 0, binaryLen);
206 while (in.hasRemaining()) {
207 ensureCapacity(in.remaining());
208 final CoderResult result = this.charsetDecoder.decode(in, this.tmpBuf, true);
209 if (result.isError()) {
210 result.throwException();
211 }
212 }
213 ensureCapacity(8);
214 final CoderResult result = this.charsetDecoder.flush(this.tmpBuf);
215 if (result.isError()) {
216 result.throwException();
217 }
218 this.tmpBuf.flip();
219 buf.append(this.tmpBuf);
220 }
221 return binaryLen;
222 }
223
224 HPackHeader decodeLiteralHeader(
225 final ByteBuffer src,
226 final HPackRepresentation representation) throws HPackException, CharacterCodingException {
227
228 final int n = representation == HPackRepresentation.WITH_INDEXING ? 6 : 4;
229 final int index = decodeInt(src, n);
230 final String name;
231 final int nameLen;
232 if (index == 0) {
233 final StringBuilder buf = new StringBuilder();
234 nameLen = decodeString(src, buf);
235 name = buf.toString();
236 } else {
237 final HPackHeader existing = this.dynamicTable.getHeader(index);
238 if (existing == null) {
239 throw new HPackException("Invalid header index");
240 }
241 name = existing.getName();
242 nameLen = existing.getNameLen();
243 }
244 final StringBuilder buf = new StringBuilder();
245 final int valueLen = decodeString(src, buf);
246 final String value = buf.toString();
247 final HPackHeaderpack/HPackHeader.html#HPackHeader">HPackHeader header = new HPackHeader(name, nameLen, value, valueLen, representation == HPackRepresentation.NEVER_INDEXED);
248 if (representation == HPackRepresentation.WITH_INDEXING) {
249 this.dynamicTable.add(header);
250 }
251 return header;
252 }
253
254 HPackHeader decodeIndexedHeader(final ByteBuffer src) throws HPackException {
255
256 final int index = decodeInt(src, 7);
257 final HPackHeader existing = this.dynamicTable.getHeader(index);
258 if (existing == null) {
259 throw new HPackException("Invalid header index");
260 }
261 return existing;
262 }
263
264 public Header decodeHeader(final ByteBuffer src) throws HPackException {
265 final HPackHeader header = decodeHPackHeader(src);
266 return header != null ? new BasicHeader(header.getName(), header.getValue(), header.isSensitive()) : null;
267 }
268
269 HPackHeader decodeHPackHeader(final ByteBuffer src) throws HPackException {
270 try {
271 while (src.hasRemaining()) {
272 final int b = peekByte(src);
273 if ((b & 0x80) == 0x80) {
274 return decodeIndexedHeader(src);
275 } else if ((b & 0xc0) == 0x40) {
276 return decodeLiteralHeader(src, HPackRepresentation.WITH_INDEXING);
277 } else if ((b & 0xf0) == 0x00) {
278 return decodeLiteralHeader(src, HPackRepresentation.WITHOUT_INDEXING);
279 } else if ((b & 0xf0) == 0x10) {
280 return decodeLiteralHeader(src, HPackRepresentation.NEVER_INDEXED);
281 } else if ((b & 0xe0) == 0x20) {
282 final int maxSize = decodeInt(src, 5);
283 this.dynamicTable.setMaxSize(Math.min(this.maxTableSize, maxSize));
284 } else {
285 throw new HPackException("Unexpected header first byte: 0x" + Integer.toHexString(b));
286 }
287 }
288 return null;
289 } catch (final CharacterCodingException ex) {
290 throw new HPackException(ex.getMessage(), ex);
291 }
292 }
293
294 public List<Header> decodeHeaders(final ByteBuffer src) throws HPackException {
295 final boolean enforceSizeLimit = maxListSize < Integer.MAX_VALUE;
296 int listSize = 0;
297
298 final List<Header> list = new ArrayList<>();
299 while (src.hasRemaining()) {
300 final HPackHeader header = decodeHPackHeader(src);
301 if (header == null) {
302 break;
303 }
304 if (enforceSizeLimit) {
305 listSize += header.getTotalSize();
306 if (listSize >= maxListSize) {
307 throw new HeaderListConstraintException("Maximum header list size exceeded");
308 }
309 }
310 list.add(new BasicHeader(header.getName(), header.getValue(), header.isSensitive()));
311 }
312 return list;
313 }
314
315 public int getMaxTableSize() {
316 return this.maxTableSize;
317 }
318
319 public void setMaxTableSize(final int maxTableSize) {
320 Args.notNegative(maxTableSize, "Max table size");
321 this.maxTableSize = maxTableSize;
322 this.dynamicTable.setMaxSize(maxTableSize);
323 }
324
325 public int getMaxListSize() {
326 return maxListSize;
327 }
328
329 public void setMaxListSize(final int maxListSize) {
330 Args.notNegative(maxListSize, "Max list size");
331 this.maxListSize = maxListSize;
332 }
333
334 }