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