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 import java.util.EnumSet;
24
25 import org.eclipse.jetty.http.HttpField;
26 import org.eclipse.jetty.http.HttpHeader;
27 import org.eclipse.jetty.http.HttpScheme;
28 import org.eclipse.jetty.http.HttpStatus;
29 import org.eclipse.jetty.http.HttpVersion;
30 import org.eclipse.jetty.http.MetaData;
31 import org.eclipse.jetty.http.PreEncodedHttpField;
32 import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
33 import org.eclipse.jetty.http2.hpack.HpackContext.StaticEntry;
34 import org.eclipse.jetty.util.TypeUtil;
35 import org.eclipse.jetty.util.log.Log;
36 import org.eclipse.jetty.util.log.Logger;
37
38 public class HpackEncoder
39 {
40 public static final Logger LOG = Log.getLogger(HpackEncoder.class);
41
42 private final static HttpField[] __status= new HttpField[599];
43
44
45 final static EnumSet<HttpHeader> __DO_NOT_HUFFMAN =
46 EnumSet.of(
47 HttpHeader.AUTHORIZATION,
48 HttpHeader.CONTENT_MD5,
49 HttpHeader.PROXY_AUTHENTICATE,
50 HttpHeader.PROXY_AUTHORIZATION);
51
52 final static EnumSet<HttpHeader> __DO_NOT_INDEX =
53 EnumSet.of(
54
55
56 HttpHeader.AUTHORIZATION,
57 HttpHeader.CONTENT_MD5,
58 HttpHeader.CONTENT_RANGE,
59 HttpHeader.ETAG,
60 HttpHeader.IF_MODIFIED_SINCE,
61 HttpHeader.IF_UNMODIFIED_SINCE,
62 HttpHeader.IF_NONE_MATCH,
63 HttpHeader.IF_RANGE,
64 HttpHeader.IF_MATCH,
65 HttpHeader.LOCATION,
66 HttpHeader.RANGE,
67 HttpHeader.RETRY_AFTER,
68
69 HttpHeader.LAST_MODIFIED,
70 HttpHeader.SET_COOKIE,
71 HttpHeader.SET_COOKIE2);
72
73
74 final static EnumSet<HttpHeader> __NEVER_INDEX =
75 EnumSet.of(
76 HttpHeader.AUTHORIZATION,
77 HttpHeader.SET_COOKIE,
78 HttpHeader.SET_COOKIE2);
79
80 static
81 {
82 for (HttpStatus.Code code : HttpStatus.Code.values())
83 __status[code.getCode()]=new PreEncodedHttpField(HttpHeader.C_STATUS,Integer.toString(code.getCode()));
84 }
85
86 private final HpackContext _context;
87 private final boolean _debug;
88 private int _remoteMaxDynamicTableSize;
89 private int _localMaxDynamicTableSize;
90 private int _maxHeaderListSize;
91 private int _size;
92
93 public HpackEncoder()
94 {
95 this(4096,4096,-1);
96 }
97
98 public HpackEncoder(int localMaxDynamicTableSize)
99 {
100 this(localMaxDynamicTableSize,4096,-1);
101 }
102
103 public HpackEncoder(int localMaxDynamicTableSize,int remoteMaxDynamicTableSize)
104 {
105 this(localMaxDynamicTableSize,remoteMaxDynamicTableSize,-1);
106 }
107
108 public HpackEncoder(int localMaxDynamicTableSize,int remoteMaxDynamicTableSize, int maxHeaderListSize)
109 {
110 _context=new HpackContext(remoteMaxDynamicTableSize);
111 _remoteMaxDynamicTableSize=remoteMaxDynamicTableSize;
112 _localMaxDynamicTableSize=localMaxDynamicTableSize;
113 _maxHeaderListSize=maxHeaderListSize;
114 _debug=LOG.isDebugEnabled();
115 }
116
117 public int getMaxHeaderListSize()
118 {
119 return _maxHeaderListSize;
120 }
121
122 public void setMaxHeaderListSize(int maxHeaderListSize)
123 {
124 _maxHeaderListSize = maxHeaderListSize;
125 }
126
127 public HpackContext getHpackContext()
128 {
129 return _context;
130 }
131
132 public void setRemoteMaxDynamicTableSize(int remoteMaxDynamicTableSize)
133 {
134 _remoteMaxDynamicTableSize=remoteMaxDynamicTableSize;
135 }
136
137 public void setLocalMaxDynamicTableSize(int localMaxDynamicTableSize)
138 {
139 _localMaxDynamicTableSize=localMaxDynamicTableSize;
140 }
141
142 public void encode(ByteBuffer buffer, MetaData metadata)
143 {
144 if (LOG.isDebugEnabled())
145 LOG.debug(String.format("CtxTbl[%x] encoding",_context.hashCode()));
146
147 _size=0;
148 int pos = buffer.position();
149
150
151 int maxDynamicTableSize=Math.min(_remoteMaxDynamicTableSize,_localMaxDynamicTableSize);
152 if (maxDynamicTableSize!=_context.getMaxDynamicTableSize())
153 encodeMaxDynamicTableSize(buffer,maxDynamicTableSize);
154
155
156 if (metadata.isRequest())
157 {
158 MetaData.Request request = (MetaData.Request)metadata;
159
160
161 String scheme=request.getURI().getScheme();
162 encode(buffer,new HttpField(HttpHeader.C_SCHEME,scheme==null?HttpScheme.HTTP.asString():scheme));
163 encode(buffer,new HttpField(HttpHeader.C_METHOD,request.getMethod()));
164 encode(buffer,new HttpField(HttpHeader.C_AUTHORITY,request.getURI().getAuthority()));
165 encode(buffer,new HttpField(HttpHeader.C_PATH,request.getURI().getPathQuery()));
166 }
167 else if (metadata.isResponse())
168 {
169 MetaData.Response response = (MetaData.Response)metadata;
170 int code=response.getStatus();
171 HttpField status = code<__status.length?__status[code]:null;
172 if (status==null)
173 status=new HttpField.IntValueHttpField(HttpHeader.C_STATUS,code);
174 encode(buffer,status);
175 }
176
177
178 for (HttpField field : metadata)
179 encode(buffer,field);
180
181
182 if (_maxHeaderListSize>0 && _size>_maxHeaderListSize)
183 {
184 LOG.warn("Header list size too large {} > {} for {}",_size,_maxHeaderListSize);
185 if (LOG.isDebugEnabled())
186 LOG.debug("metadata={}",metadata);
187 }
188
189 if (LOG.isDebugEnabled())
190 LOG.debug(String.format("CtxTbl[%x] encoded %d octets",_context.hashCode(), buffer.position() - pos));
191 }
192
193 public void encodeMaxDynamicTableSize(ByteBuffer buffer, int maxDynamicTableSize)
194 {
195 if (maxDynamicTableSize>_remoteMaxDynamicTableSize)
196 throw new IllegalArgumentException();
197 buffer.put((byte)0x20);
198 NBitInteger.encode(buffer,5,maxDynamicTableSize);
199 _context.resize(maxDynamicTableSize);
200 }
201
202 public void encode(ByteBuffer buffer, HttpField field)
203 {
204 if (field.getValue()==null)
205 field = new HttpField(field.getHeader(),field.getName(),"");
206
207 int field_size = field.getName().length() + field.getValue().length();
208 _size+=field_size+32;
209
210 final int p=_debug?buffer.position():-1;
211
212 String encoding=null;
213
214
215 Entry entry = _context.get(field);
216 if (entry!=null)
217 {
218
219 if (entry.isStatic())
220 {
221 buffer.put(((StaticEntry)entry).getEncodedField());
222 if (_debug)
223 encoding="IdxFieldS1";
224 }
225 else
226 {
227 int index=_context.index(entry);
228 buffer.put((byte)0x80);
229 NBitInteger.encode(buffer,7,index);
230 if (_debug)
231 encoding="IdxField"+(entry.isStatic()?"S":"")+(1+NBitInteger.octectsNeeded(7,index));
232 }
233 }
234 else
235 {
236
237 final boolean indexed;
238
239
240 HttpHeader header = field.getHeader();
241
242
243 if (header==null)
244 {
245
246 Entry name = _context.get(field.getName());
247
248 if (field instanceof PreEncodedHttpField)
249 {
250 int i=buffer.position();
251 ((PreEncodedHttpField)field).putTo(buffer,HttpVersion.HTTP_2);
252 byte b=buffer.get(i);
253 indexed=b<0||b>=0x40;
254 if (_debug)
255 encoding=indexed?"PreEncodedIdx":"PreEncoded";
256 }
257
258 else if (name==null)
259 {
260
261
262
263 indexed=true;
264 encodeName(buffer,(byte)0x40,6,field.getName(),null);
265 encodeValue(buffer,true,field.getValue());
266 if (_debug)
267 encoding="LitHuffNHuffVIdx";
268 }
269 else
270 {
271
272
273 indexed=false;
274 encodeName(buffer,(byte)0x00,4,field.getName(),null);
275 encodeValue(buffer,true,field.getValue());
276 if (_debug)
277 encoding="LitHuffNHuffV!Idx";
278 }
279 }
280 else
281 {
282
283 Entry name = _context.get(header);
284
285 if (field instanceof PreEncodedHttpField)
286 {
287
288 int i=buffer.position();
289 ((PreEncodedHttpField)field).putTo(buffer,HttpVersion.HTTP_2);
290 byte b=buffer.get(i);
291 indexed=b<0||b>=0x40;
292 if (_debug)
293 encoding=indexed?"PreEncodedIdx":"PreEncoded";
294 }
295 else if (__DO_NOT_INDEX.contains(header))
296 {
297
298 indexed=false;
299 boolean never_index=__NEVER_INDEX.contains(header);
300 boolean huffman=!__DO_NOT_HUFFMAN.contains(header);
301 encodeName(buffer,never_index?(byte)0x10:(byte)0x00,4,header.asString(),name);
302 encodeValue(buffer,huffman,field.getValue());
303
304 if (_debug)
305 encoding="Lit"+
306 ((name==null)?"HuffN":("IdxN"+(name.isStatic()?"S":"")+(1+NBitInteger.octectsNeeded(4,_context.index(name)))))+
307 (huffman?"HuffV":"LitV")+
308 (indexed?"Idx":(never_index?"!!Idx":"!Idx"));
309 }
310 else if (header==HttpHeader.CONTENT_LENGTH && field.getValue().length()>1)
311 {
312
313 indexed=false;
314 encodeName(buffer,(byte)0x00,4,header.asString(),name);
315 encodeValue(buffer,true,field.getValue());
316 if (_debug)
317 encoding="LitIdxNS"+(1+NBitInteger.octectsNeeded(4,_context.index(name)))+"HuffV!Idx";
318 }
319 else
320 {
321
322 indexed=true;
323 boolean huffman=!__DO_NOT_HUFFMAN.contains(header);
324 encodeName(buffer,(byte)0x40,6,header.asString(),name);
325 encodeValue(buffer,huffman,field.getValue());
326 if (_debug)
327 encoding=((name==null)?"LitHuffN":("LitIdxN"+(name.isStatic()?"S":"")+(1+NBitInteger.octectsNeeded(6,_context.index(name)))))+
328 (huffman?"HuffVIdx":"LitVIdx");
329 }
330 }
331
332
333
334 if (indexed)
335 _context.add(field);
336 }
337
338 if (_debug)
339 {
340 int e=buffer.position();
341 if (LOG.isDebugEnabled())
342 LOG.debug("encode {}:'{}' to '{}'",encoding,field,TypeUtil.toHexString(buffer.array(),buffer.arrayOffset()+p,e-p));
343 }
344 }
345
346 private void encodeName(ByteBuffer buffer, byte mask, int bits, String name, Entry entry)
347 {
348 buffer.put(mask);
349 if (entry==null)
350 {
351
352
353 buffer.put((byte)0x80);
354 NBitInteger.encode(buffer,7,Huffman.octetsNeededLC(name));
355 Huffman.encodeLC(buffer,name);
356 }
357 else
358 {
359 NBitInteger.encode(buffer,bits,_context.index(entry));
360 }
361 }
362
363 static void encodeValue(ByteBuffer buffer, boolean huffman, String value)
364 {
365 if (huffman)
366 {
367
368 buffer.put((byte)0x80);
369 NBitInteger.encode(buffer,7,Huffman.octetsNeeded(value));
370 Huffman.encode(buffer,value);
371 }
372 else
373 {
374
375 buffer.put((byte)0x00);
376 NBitInteger.encode(buffer,7,value.length());
377 for (int i=0;i<value.length();i++)
378 {
379 char c=value.charAt(i);
380 if (c<' '|| c>127)
381 throw new IllegalArgumentException();
382 buffer.put((byte)c);
383 }
384 }
385 }
386 }