1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.eclipse.jetty.http2.hpack;
21
22 import java.nio.ByteBuffer;
23
24 import org.eclipse.jetty.http.BadMessageException;
25 import org.eclipse.jetty.http.HttpField;
26 import org.eclipse.jetty.http.HttpHeader;
27 import org.eclipse.jetty.http.HttpStatus;
28 import org.eclipse.jetty.http.MetaData;
29 import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
30 import org.eclipse.jetty.util.TypeUtil;
31 import org.eclipse.jetty.util.log.Log;
32 import org.eclipse.jetty.util.log.Logger;
33
34
35
36
37
38 public class HpackDecoder
39 {
40 public static final Logger LOG = Log.getLogger(HpackDecoder.class);
41 public final static HttpField.LongValueHttpField CONTENT_LENGTH_0 =
42 new HttpField.LongValueHttpField(HttpHeader.CONTENT_LENGTH,0L);
43
44 private final HpackContext _context;
45 private final MetaDataBuilder _builder;
46 private int _localMaxDynamicTableSize;
47
48
49
50
51
52 public HpackDecoder(int localMaxDynamicTableSize, int maxHeaderSize)
53 {
54 _context=new HpackContext(localMaxDynamicTableSize);
55 _localMaxDynamicTableSize=localMaxDynamicTableSize;
56 _builder = new MetaDataBuilder(maxHeaderSize);
57 }
58
59 public HpackContext getHpackContext()
60 {
61 return _context;
62 }
63
64 public void setLocalMaxDynamicTableSize(int localMaxdynamciTableSize)
65 {
66 _localMaxDynamicTableSize=localMaxdynamciTableSize;
67 }
68
69 public MetaData decode(ByteBuffer buffer)
70 {
71 if (LOG.isDebugEnabled())
72 LOG.debug(String.format("CtxTbl[%x] decoding %d octets",_context.hashCode(),buffer.remaining()));
73
74
75 if (buffer.remaining()>_builder.getMaxSize())
76 throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413,"Header frame size "+buffer.remaining()+">"+_builder.getMaxSize());
77
78
79 while(buffer.hasRemaining())
80 {
81 if (LOG.isDebugEnabled())
82 {
83 int l=Math.min(buffer.remaining(),16);
84
85 LOG.debug("decode {}{}",
86 TypeUtil.toHexString(buffer.array(),buffer.arrayOffset()+buffer.position(),l),
87 l<buffer.remaining()?"...":"");
88 }
89
90 byte b = buffer.get();
91 if (b<0)
92 {
93
94 int index = NBitInteger.decode(buffer,7);
95 Entry entry=_context.get(index);
96 if (entry==null)
97 {
98 throw new BadMessageException("Unknown index "+index);
99 }
100 else if (entry.isStatic())
101 {
102 if (LOG.isDebugEnabled())
103 LOG.debug("decode IdxStatic {}",entry);
104
105 _builder.emit(entry.getHttpField());
106
107
108
109 }
110 else
111 {
112 if (LOG.isDebugEnabled())
113 LOG.debug("decode Idx {}",entry);
114
115 _builder.emit(entry.getHttpField());
116 }
117 }
118 else
119 {
120
121 byte f= (byte)((b&0xF0)>>4);
122 String name;
123 HttpHeader header;
124 String value;
125
126 boolean indexed;
127 int name_index;
128
129 switch (f)
130 {
131 case 2:
132 case 3:
133
134 int size = NBitInteger.decode(buffer,5);
135 if (LOG.isDebugEnabled())
136 LOG.debug("decode resize="+size);
137 if (size>_localMaxDynamicTableSize)
138 throw new IllegalArgumentException();
139 _context.resize(size);
140 continue;
141
142 case 0:
143 case 1:
144 indexed=false;
145 name_index=NBitInteger.decode(buffer,4);
146 break;
147
148
149 case 4:
150 case 5:
151 case 6:
152 case 7:
153 indexed=true;
154 name_index=NBitInteger.decode(buffer,6);
155 break;
156
157 default:
158 throw new IllegalStateException();
159 }
160
161
162 boolean huffmanName=false;
163
164
165 if (name_index>0)
166 {
167 Entry name_entry=_context.get(name_index);
168 name=name_entry.getHttpField().getName();
169 header=name_entry.getHttpField().getHeader();
170 }
171 else
172 {
173 huffmanName = (buffer.get()&0x80)==0x80;
174 int length = NBitInteger.decode(buffer,7);
175 _builder.checkSize(length,huffmanName);
176 if (huffmanName)
177 name=Huffman.decode(buffer,length);
178 else
179 name=toASCIIString(buffer,length);
180 for (int i=0;i<name.length();i++)
181 {
182 char c=name.charAt(i);
183 if (c>='A'&&c<='Z')
184 {
185 throw new BadMessageException(400,"Uppercase header name");
186 }
187 }
188 header=HttpHeader.CACHE.get(name);
189 }
190
191
192 boolean huffmanValue = (buffer.get()&0x80)==0x80;
193 int length = NBitInteger.decode(buffer,7);
194 _builder.checkSize(length,huffmanValue);
195 if (huffmanValue)
196 value=Huffman.decode(buffer,length);
197 else
198 value=toASCIIString(buffer,length);
199
200
201 HttpField field;
202 if (header==null)
203 {
204
205 field = new HttpField(null,name,value);
206 }
207 else
208 {
209
210
211 switch(header)
212 {
213 case C_STATUS:
214 if (indexed)
215 field = new HttpField.IntValueHttpField(header,name,value);
216 else
217 field = new HttpField(header,name,value);
218 break;
219
220 case C_AUTHORITY:
221 field = new AuthorityHttpField(value);
222 break;
223
224 case CONTENT_LENGTH:
225 if ("0".equals(value))
226 field = CONTENT_LENGTH_0;
227 else
228 field = new HttpField.LongValueHttpField(header,name,value);
229 break;
230
231 default:
232 field = new HttpField(header,name,value);
233 break;
234 }
235 }
236
237 if (LOG.isDebugEnabled())
238 {
239 LOG.debug("decoded '{}' by {}/{}/{}",
240 field,
241 name_index > 0 ? "IdxName" : (huffmanName ? "HuffName" : "LitName"),
242 huffmanValue ? "HuffVal" : "LitVal",
243 indexed ? "Idx" : "");
244 }
245
246
247 _builder.emit(field);
248
249
250 if (indexed)
251 {
252
253 _context.add(field);
254 }
255
256 }
257 }
258
259 return _builder.build();
260 }
261
262 public static String toASCIIString(ByteBuffer buffer,int length)
263 {
264 StringBuilder builder = new StringBuilder(length);
265 int position=buffer.position();
266 int start=buffer.arrayOffset()+ position;
267 int end=start+length;
268 buffer.position(position+length);
269 byte[] array=buffer.array();
270 for (int i=start;i<end;i++)
271 builder.append((char)(0x7f&array[i]));
272 return builder.toString();
273 }
274
275 @Override
276 public String toString()
277 {
278 return String.format("HpackDecoder@%x{%s}",hashCode(),_context);
279 }
280 }