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