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