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