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