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