View Javadoc

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