View Javadoc

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 }