1 //
2 // ========================================================================
3 // Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
8 //
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
11 //
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
14 //
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
17 //
18
19 package org.eclipse.jetty.http;
20
21 import java.util.ArrayList;
22 import java.util.Objects;
23
24 /** A HTTP Field
25 */
26 public class HttpField
27 {
28 private final static String __zeroquality="q=0";
29 private final HttpHeader _header;
30 private final String _name;
31 private final String _value;
32 // cached hashcode for case insensitive name
33 private int hash = 0;
34
35 public HttpField(HttpHeader header, String name, String value)
36 {
37 _header = header;
38 _name = name;
39 _value = value;
40 }
41
42 public HttpField(HttpHeader header, String value)
43 {
44 this(header,header.asString(),value);
45 }
46
47 public HttpField(HttpHeader header, HttpHeaderValue value)
48 {
49 this(header,header.asString(),value.asString());
50 }
51
52 public HttpField(String name, String value)
53 {
54 this(HttpHeader.CACHE.get(name),name,value);
55 }
56
57 public HttpHeader getHeader()
58 {
59 return _header;
60 }
61
62 public String getName()
63 {
64 return _name;
65 }
66
67 public String getValue()
68 {
69 return _value;
70 }
71
72 public int getIntValue()
73 {
74 return Integer.valueOf(_value);
75 }
76
77 public long getLongValue()
78 {
79 return Long.valueOf(_value);
80 }
81
82 public String[] getValues()
83 {
84 ArrayList<String> list = new ArrayList<>();
85 int state = 0;
86 int start=0;
87 int end=0;
88 StringBuilder builder = new StringBuilder();
89
90 for (int i=0;i<_value.length();i++)
91 {
92 char c = _value.charAt(i);
93 switch(state)
94 {
95 case 0: // initial white space
96 switch(c)
97 {
98 case '"': // open quote
99 state=2;
100 break;
101
102 case ',': // ignore leading empty field
103 break;
104
105 case ' ': // more white space
106 case '\t':
107 break;
108
109 default: // character
110 start=i;
111 end=i;
112 state=1;
113 }
114 break;
115
116 case 1: // In token
117 switch(c)
118 {
119 case ',': // next field
120 list.add(_value.substring(start,end+1));
121 state=0;
122 break;
123
124 case ' ': // more white space
125 case '\t':
126 break;
127
128 default:
129 end=i;
130 }
131 break;
132
133 case 2: // In Quoted
134 switch(c)
135 {
136 case '\\': // next field
137 state=3;
138 break;
139
140 case '"': // end quote
141 list.add(builder.toString());
142 builder.setLength(0);
143 state=4;
144 break;
145
146 default:
147 builder.append(c);
148 }
149 break;
150
151 case 3: // In Quoted Quoted
152 builder.append(c);
153 state=2;
154 break;
155
156 case 4: // WS after end quote
157 switch(c)
158 {
159 case ' ': // white space
160 case '\t': // white space
161 break;
162
163 case ',': // white space
164 state=0;
165 break;
166
167 default:
168 throw new IllegalArgumentException("c="+(int)c);
169
170 }
171 break;
172 }
173 }
174
175 switch(state)
176 {
177 case 0:
178 break;
179 case 1:
180 list.add(_value.substring(start,end+1));
181 break;
182 case 4:
183 break;
184
185 default:
186 throw new IllegalArgumentException("state="+state);
187 }
188
189 return list.toArray(new String[list.size()]);
190 }
191
192 /* ------------------------------------------------------------ */
193 /** Look for a value in a possible multi valued field
194 * @param search Values to search for
195 * @return True iff the value is contained in the field value entirely or
196 * as an element of a quoted comma separated list. List element parameters (eg qualities) are ignored,
197 * except if they are q=0, in which case the item itself is ignored.
198 */
199 public boolean contains(String search)
200 {
201 if (search==null)
202 return _value==null;
203 if (search.length()==0)
204 return false;
205 if (_value==null)
206 return false;
207
208 int state=0;
209 int match=0;
210 int param=0;
211
212 for (int i=0;i<_value.length();i++)
213 {
214 char c = _value.charAt(i);
215 switch(state)
216 {
217 case 0: // initial white space
218 switch(c)
219 {
220 case '"': // open quote
221 match=0;
222 state=2;
223 break;
224
225 case ',': // ignore leading empty field
226 break;
227
228 case ';': // ignore leading empty field parameter
229 param=-1;
230 match=-1;
231 state=5;
232 break;
233
234 case ' ': // more white space
235 case '\t':
236 break;
237
238 default: // character
239 match = c==search.charAt(0)?1:-1;
240 state=1;
241 break;
242 }
243 break;
244
245 case 1: // In token
246 switch(c)
247 {
248 case ',': // next field
249 // Have we matched the token?
250 if (match==search.length())
251 return true;
252 state=0;
253 break;
254
255 case ';':
256 param=match>=0?0:-1;
257 state=5; // parameter
258 break;
259
260 default:
261 if (match>0)
262 {
263 if (match<search.length())
264 match=c==search.charAt(match)?(match+1):-1;
265 else if (c!=' ' && c!= '\t')
266 match=-1;
267 }
268 break;
269
270 }
271 break;
272
273 case 2: // In Quoted token
274 switch(c)
275 {
276 case '\\': // quoted character
277 state=3;
278 break;
279
280 case '"': // end quote
281 state=4;
282 break;
283
284 default:
285 if (match>=0)
286 {
287 if (match<search.length())
288 match=c==search.charAt(match)?(match+1):-1;
289 else
290 match=-1;
291 }
292 }
293 break;
294
295 case 3: // In Quoted character in quoted token
296 if (match>=0)
297 {
298 if (match<search.length())
299 match=c==search.charAt(match)?(match+1):-1;
300 else
301 match=-1;
302 }
303 state=2;
304 break;
305
306 case 4: // WS after end quote
307 switch(c)
308 {
309 case ' ': // white space
310 case '\t': // white space
311 break;
312
313 case ';':
314 state=5; // parameter
315 break;
316
317 case ',': // end token
318 // Have we matched the token?
319 if (match==search.length())
320 return true;
321 state=0;
322 break;
323
324 default:
325 // This is an illegal token, just ignore
326 match=-1;
327 }
328 break;
329
330 case 5: // parameter
331 switch(c)
332 {
333 case ',': // end token
334 // Have we matched the token and not q=0?
335 if (param!=__zeroquality.length() && match==search.length())
336 return true;
337 param=0;
338 state=0;
339 break;
340
341 case ' ': // white space
342 case '\t': // white space
343 break;
344
345 default:
346 if (param>=0)
347 {
348 if (param<__zeroquality.length())
349 param=c==__zeroquality.charAt(param)?(param+1):-1;
350 else if (c!='0'&&c!='.')
351 param=-1;
352 }
353
354 }
355 break;
356
357 default:
358 throw new IllegalStateException();
359 }
360 }
361
362 return param!=__zeroquality.length() && match==search.length();
363 }
364
365
366 @Override
367 public String toString()
368 {
369 String v=getValue();
370 return getName() + ": " + (v==null?"":v);
371 }
372
373 public boolean isSameName(HttpField field)
374 {
375 if (field==null)
376 return false;
377 if (field==this)
378 return true;
379 if (_header!=null && _header==field.getHeader())
380 return true;
381 if (_name.equalsIgnoreCase(field.getName()))
382 return true;
383 return false;
384 }
385
386 private int nameHashCode()
387 {
388 int h = this.hash;
389 int len = _name.length();
390 if (h == 0 && len > 0)
391 {
392 for (int i = 0; i < len; i++)
393 {
394 // simple case insensitive hash
395 char c = _name.charAt(i);
396 // assuming us-ascii (per last paragraph on http://tools.ietf.org/html/rfc7230#section-3.2.4)
397 if ((c >= 'a' && c <= 'z'))
398 c -= 0x20;
399 h = 31 * h + c;
400 }
401 this.hash = h;
402 }
403 return h;
404 }
405
406 @Override
407 public int hashCode()
408 {
409 if (_header==null)
410 return _value.hashCode() ^ nameHashCode();
411 return _value.hashCode() ^ _header.hashCode();
412 }
413
414 @Override
415 public boolean equals(Object o)
416 {
417 if (o==this)
418 return true;
419 if (!(o instanceof HttpField))
420 return false;
421 HttpField field=(HttpField)o;
422 if (_header!=field.getHeader())
423 return false;
424 if (!_name.equalsIgnoreCase(field.getName()))
425 return false;
426 if (_value==null && field.getValue()!=null)
427 return false;
428 return Objects.equals(_value,field.getValue());
429 }
430
431 public static class IntValueHttpField extends HttpField
432 {
433 private final int _int;
434
435 public IntValueHttpField(HttpHeader header, String name, String value, int intValue)
436 {
437 super(header,name,value);
438 _int=intValue;
439 }
440
441 public IntValueHttpField(HttpHeader header, String name, String value)
442 {
443 this(header,name,value,Integer.valueOf(value));
444 }
445
446 public IntValueHttpField(HttpHeader header, String name, int intValue)
447 {
448 this(header,name,Integer.toString(intValue),intValue);
449 }
450
451 public IntValueHttpField(HttpHeader header, int value)
452 {
453 this(header,header.asString(),value);
454 }
455
456 @Override
457 public int getIntValue()
458 {
459 return _int;
460 }
461
462 @Override
463 public long getLongValue()
464 {
465 return _int;
466 }
467 }
468
469 public static class LongValueHttpField extends HttpField
470 {
471 private final long _long;
472
473 public LongValueHttpField(HttpHeader header, String name, String value, long longValue)
474 {
475 super(header,name,value);
476 _long=longValue;
477 }
478
479 public LongValueHttpField(HttpHeader header, String name, String value)
480 {
481 this(header,name,value,Long.valueOf(value));
482 }
483
484 public LongValueHttpField(HttpHeader header, String name, long value)
485 {
486 this(header,name,Long.toString(value),value);
487 }
488
489 public LongValueHttpField(HttpHeader header,long value)
490 {
491 this(header,header.asString(),value);
492 }
493
494 @Override
495 public int getIntValue()
496 {
497 return (int)_long;
498 }
499
500 @Override
501 public long getLongValue()
502 {
503 return _long;
504 }
505 }
506 }