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 final int p=_debug?buffer.position():-1;
179
180 String encoding=null;
181
182
183 Entry entry = _context.get(field);
184 if (entry!=null)
185 {
186
187 if (entry.isStatic())
188 {
189 buffer.put(((StaticEntry)entry).getEncodedField());
190 if (_debug)
191 encoding="IdxFieldS1";
192 }
193 else
194 {
195 int index=_context.index(entry);
196 buffer.put((byte)0x80);
197 NBitInteger.encode(buffer,7,index);
198 if (_debug)
199 encoding="IdxField"+(entry.isStatic()?"S":"")+(1+NBitInteger.octectsNeeded(7,index));
200 }
201 }
202 else
203 {
204
205 final boolean indexed;
206
207
208 HttpHeader header = field.getHeader();
209
210
211 if (header==null)
212 {
213
214 Entry name = _context.get(field.getName());
215
216 if (field instanceof PreEncodedHttpField)
217 {
218 int i=buffer.position();
219 ((PreEncodedHttpField)field).putTo(buffer,HttpVersion.HTTP_2);
220 byte b=buffer.get(i);
221 indexed=b<0||b>=0x40;
222 if (_debug)
223 encoding=indexed?"PreEncodedIdx":"PreEncoded";
224 }
225
226 else if (name==null)
227 {
228
229
230
231 indexed=true;
232 encodeName(buffer,(byte)0x40,6,field.getName(),null);
233 encodeValue(buffer,true,field.getValue());
234 if (_debug)
235 encoding="LitHuffNHuffVIdx";
236 }
237 else
238 {
239
240
241 indexed=false;
242 encodeName(buffer,(byte)0x00,4,field.getName(),null);
243 encodeValue(buffer,true,field.getValue());
244 if (_debug)
245 encoding="LitHuffNHuffV!Idx";
246 }
247 }
248 else
249 {
250
251 Entry name = _context.get(header);
252
253 if (field instanceof PreEncodedHttpField)
254 {
255
256 int i=buffer.position();
257 ((PreEncodedHttpField)field).putTo(buffer,HttpVersion.HTTP_2);
258 byte b=buffer.get(i);
259 indexed=b<0||b>=0x40;
260 if (_debug)
261 encoding=indexed?"PreEncodedIdx":"PreEncoded";
262 }
263 else if (__DO_NOT_INDEX.contains(header))
264 {
265
266 indexed=false;
267 boolean never_index=__NEVER_INDEX.contains(header);
268 boolean huffman=!__DO_NOT_HUFFMAN.contains(header);
269 encodeName(buffer,never_index?(byte)0x10:(byte)0x00,4,header.asString(),name);
270 encodeValue(buffer,huffman,field.getValue());
271
272 if (_debug)
273 encoding="Lit"+
274 ((name==null)?"HuffN":("IdxN"+(name.isStatic()?"S":"")+(1+NBitInteger.octectsNeeded(4,_context.index(name)))))+
275 (huffman?"HuffV":"LitV")+
276 (indexed?"Idx":(never_index?"!!Idx":"!Idx"));
277 }
278 else if (header==HttpHeader.CONTENT_LENGTH && field.getValue().length()>1)
279 {
280
281 indexed=false;
282 encodeName(buffer,(byte)0x00,4,header.asString(),name);
283 encodeValue(buffer,true,field.getValue());
284 if (_debug)
285 encoding="LitIdxNS"+(1+NBitInteger.octectsNeeded(4,_context.index(name)))+"HuffV!Idx";
286 }
287 else
288 {
289
290 indexed=true;
291 boolean huffman=!__DO_NOT_HUFFMAN.contains(header);
292 encodeName(buffer,(byte)0x40,6,header.asString(),name);
293 encodeValue(buffer,huffman,field.getValue());
294 if (_debug)
295 encoding=((name==null)?"LitHuffN":("LitIdxN"+(name.isStatic()?"S":"")+(1+NBitInteger.octectsNeeded(6,_context.index(name)))))+
296 (huffman?"HuffVIdx":"LitVIdx");
297 }
298 }
299
300
301
302 if (indexed)
303 _context.add(field);
304 }
305
306 if (_debug)
307 {
308 int e=buffer.position();
309 if (LOG.isDebugEnabled())
310 LOG.debug("encode {}:'{}' to '{}'",encoding,field,TypeUtil.toHexString(buffer.array(),buffer.arrayOffset()+p,e-p));
311 }
312 }
313
314 private void encodeName(ByteBuffer buffer, byte mask, int bits, String name, Entry entry)
315 {
316 buffer.put(mask);
317 if (entry==null)
318 {
319
320
321 buffer.put((byte)0x80);
322 NBitInteger.encode(buffer,7,Huffman.octetsNeededLC(name));
323 Huffman.encodeLC(buffer,name);
324 }
325 else
326 {
327 NBitInteger.encode(buffer,bits,_context.index(entry));
328 }
329 }
330
331 static void encodeValue(ByteBuffer buffer, boolean huffman, String value)
332 {
333 if (huffman)
334 {
335
336 buffer.put((byte)0x80);
337 NBitInteger.encode(buffer,7,Huffman.octetsNeeded(value));
338 Huffman.encode(buffer,value);
339 }
340 else
341 {
342
343 buffer.put((byte)0x00);
344 NBitInteger.encode(buffer,7,value.length());
345 for (int i=0;i<value.length();i++)
346 {
347 char c=value.charAt(i);
348 if (c<' '|| c>127)
349 throw new IllegalArgumentException();
350 buffer.put((byte)c);
351 }
352 }
353 }
354 }