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 int getTmpBufSize() {
162 return tmpBuf == null ? 0 : tmpBuf.capacity();
163 }
164
165 private void clearState() {
166
167 if (this.tmpBuf != null) {
168 this.tmpBuf.clear();
169 }
170 if (this.charsetDecoder != null) {
171 this.charsetDecoder.reset();
172 }
173 this.contentBuf.clear();
174 }
175
176 private void expandCapacity(final int capacity) {
177
178 final CharBuffer previous = this.tmpBuf;
179 this.tmpBuf = CharBuffer.allocate(capacity);
180 previous.flip();
181 this.tmpBuf.put(previous);
182 }
183
184 private void ensureCapacity(final int extra) {
185
186 if (this.tmpBuf == null) {
187 this.tmpBuf = CharBuffer.allocate(Math.max(256, extra));
188 }
189 final int requiredCapacity = this.tmpBuf.position() + extra;
190 if (requiredCapacity > this.tmpBuf.capacity()) {
191 expandCapacity(requiredCapacity);
192 }
193 }
194
195 int decodeString(final ByteBuffer src, final StringBuilder buf) throws HPackException, CharacterCodingException {
196
197 clearState();
198 decodeString(this.contentBuf, src);
199 final int binaryLen = this.contentBuf.length();
200 if (binaryLen == 0) {
201 return 0;
202 }
203 if (this.charsetDecoder == null) {
204 buf.ensureCapacity(binaryLen);
205 for (int i = 0; i < binaryLen; i++) {
206 buf.append((char) (this.contentBuf.byteAt(i) & 0xff));
207 }
208 } else {
209 final ByteBuffer in = ByteBuffer.wrap(this.contentBuf.array(), 0, binaryLen);
210 while (in.hasRemaining()) {
211 ensureCapacity(in.remaining());
212 final CoderResult result = this.charsetDecoder.decode(in, this.tmpBuf, true);
213 if (result.isError()) {
214 result.throwException();
215 }
216 }
217 ensureCapacity(8);
218 final CoderResult result = this.charsetDecoder.flush(this.tmpBuf);
219 if (result.isError()) {
220 result.throwException();
221 }
222 this.tmpBuf.flip();
223 buf.append(this.tmpBuf);
224 }
225 return binaryLen;
226 }
227
228 HPackHeader decodeLiteralHeader(
229 final ByteBuffer src,
230 final HPackRepresentation representation) throws HPackException, CharacterCodingException {
231
232 final int n = representation == HPackRepresentation.WITH_INDEXING ? 6 : 4;
233 final int index = decodeInt(src, n);
234 final String name;
235 final int nameLen;
236 if (index == 0) {
237 final StringBuilder buf = new StringBuilder();
238 nameLen = decodeString(src, buf);
239 name = buf.toString();
240 } else {
241 final HPackHeader existing = this.dynamicTable.getHeader(index);
242 if (existing == null) {
243 throw new HPackException("Invalid header index");
244 }
245 name = existing.getName();
246 nameLen = existing.getNameLen();
247 }
248 final StringBuilder buf = new StringBuilder();
249 final int valueLen = decodeString(src, buf);
250 final String value = buf.toString();
251 final HPackHeader header = new HPackHeader(name, nameLen, value, valueLen, representation == HPackRepresentation.NEVER_INDEXED);
252 if (representation == HPackRepresentation.WITH_INDEXING) {
253 this.dynamicTable.add(header);
254 }
255 return header;
256 }
257
258 HPackHeader decodeIndexedHeader(final ByteBuffer src) throws HPackException {
259
260 final int index = decodeInt(src, 7);
261 final HPackHeader existing = this.dynamicTable.getHeader(index);
262 if (existing == null) {
263 throw new HPackException("Invalid header index");
264 }
265 return existing;
266 }
267
268 public Header decodeHeader(final ByteBuffer src) throws HPackException {
269 final HPackHeader header = decodeHPackHeader(src);
270 return header != null ? new BasicHeader(header.getName(), header.getValue(), header.isSensitive()) : null;
271 }
272
273 HPackHeader decodeHPackHeader(final ByteBuffer src) throws HPackException {
274 try {
275 while (src.hasRemaining()) {
276 final int b = peekByte(src);
277 if ((b & 0x80) == 0x80) {
278 return decodeIndexedHeader(src);
279 } else if ((b & 0xc0) == 0x40) {
280 return decodeLiteralHeader(src, HPackRepresentation.WITH_INDEXING);
281 } else if ((b & 0xf0) == 0x00) {
282 return decodeLiteralHeader(src, HPackRepresentation.WITHOUT_INDEXING);
283 } else if ((b & 0xf0) == 0x10) {
284 return decodeLiteralHeader(src, HPackRepresentation.NEVER_INDEXED);
285 } else if ((b & 0xe0) == 0x20) {
286 final int maxSize = decodeInt(src, 5);
287 this.dynamicTable.setMaxSize(Math.min(this.maxTableSize, maxSize));
288 } else {
289 throw new HPackException("Unexpected header first byte: 0x" + Integer.toHexString(b));
290 }
291 }
292 return null;
293 } catch (final CharacterCodingException ex) {
294 throw new HPackException(ex.getMessage(), ex);
295 }
296 }
297
298 public List<Header> decodeHeaders(final ByteBuffer src) throws HPackException {
299 final boolean enforceSizeLimit = maxListSize < Integer.MAX_VALUE;
300 int listSize = 0;
301
302 final List<Header> list = new ArrayList<>();
303 while (src.hasRemaining()) {
304 final HPackHeader header = decodeHPackHeader(src);
305 if (header == null) {
306 break;
307 }
308 if (enforceSizeLimit) {
309 listSize += header.getTotalSize();
310 if (listSize >= maxListSize) {
311 throw new HeaderListConstraintException("Maximum header list size exceeded");
312 }
313 }
314 list.add(new BasicHeader(header.getName(), header.getValue(), header.isSensitive()));
315 }
316 return list;
317 }
318
319 public int getMaxTableSize() {
320 return this.maxTableSize;
321 }
322
323 public void setMaxTableSize(final int maxTableSize) {
324 Args.notNegative(maxTableSize, "Max table size");
325 this.maxTableSize = maxTableSize;
326 this.dynamicTable.setMaxSize(maxTableSize);
327 }
328
329 public int getMaxListSize() {
330 return maxListSize;
331 }
332
333 public void setMaxListSize(final int maxListSize) {
334 Args.notNegative(maxListSize, "Max list size");
335 this.maxListSize = maxListSize;
336 }
337
338 }