View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 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.Collection;
23  import java.util.Collections;
24  import java.util.Enumeration;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.NoSuchElementException;
31  import java.util.Set;
32  import java.util.StringTokenizer;
33  import java.util.regex.Pattern;
34  
35  import org.eclipse.jetty.util.ArrayTernaryTrie;
36  import org.eclipse.jetty.util.LazyList;
37  import org.eclipse.jetty.util.QuotedStringTokenizer;
38  import org.eclipse.jetty.util.StringUtil;
39  import org.eclipse.jetty.util.Trie;
40  import org.eclipse.jetty.util.log.Log;
41  import org.eclipse.jetty.util.log.Logger;
42  
43  
44  /**
45   * HTTP Fields. A collection of HTTP header and or Trailer fields.
46   *
47   * <p>This class is not synchronized as it is expected that modifications will only be performed by a
48   * single thread.
49   * 
50   * <p>The cookie handling provided by this class is guided by the Servlet specification and RFC6265.
51   *
52   */
53  public class HttpFields implements Iterable<HttpField>
54  {
55      private static final Logger LOG = Log.getLogger(HttpFields.class);
56      private final static Pattern __splitter = Pattern.compile("\\s*,\\s*");     
57      public final static String __separators = ", \t";
58  
59      private final ArrayList<HttpField> _fields = new ArrayList<>(20);
60  
61      /**
62       * Constructor.
63       */
64      public HttpFields()
65      {
66      }
67  
68      /**
69       * Get Collection of header names.
70       */
71      public Collection<String> getFieldNamesCollection()
72      {
73          final Set<String> list = new HashSet<>(_fields.size());
74          for (HttpField f : _fields)
75          {
76              if (f!=null)
77                  list.add(f.getName());
78          }
79          return list;
80      }
81  
82      /**
83       * Get enumeration of header _names. Returns an enumeration of strings representing the header
84       * _names for this request.
85       */
86      public Enumeration<String> getFieldNames()
87      {
88          return Collections.enumeration(getFieldNamesCollection());
89      }
90  
91      public int size()
92      {
93          return _fields.size();
94      }
95  
96      /**
97       * Get a Field by index.
98       * @return A Field value or null if the Field value has not been set
99       *
100      */
101     public HttpField getField(int i)
102     {
103         return _fields.get(i);
104     }
105 
106     @Override
107     public Iterator<HttpField> iterator()
108     {
109         return _fields.iterator();
110     }
111 
112     public HttpField getField(HttpHeader header)
113     {
114         for (int i=0;i<_fields.size();i++)
115         {
116             HttpField f=_fields.get(i);
117             if (f.getHeader()==header)
118                 return f;
119         }
120         return null;
121     }
122 
123     public HttpField getField(String name)
124     {
125         for (int i=0;i<_fields.size();i++)
126         {
127             HttpField f=_fields.get(i);
128             if (f.getName().equalsIgnoreCase(name))
129                 return f;
130         }
131         return null;
132     }
133     
134     public boolean contains(HttpHeader header, String value)
135     {
136         for (int i=0;i<_fields.size();i++)
137         {
138             HttpField f=_fields.get(i);
139             if (f.getHeader()==header && contains(f,value))
140                 return true;
141         }
142         return false;
143     }
144     
145     public boolean contains(String name, String value)
146     {
147         for (int i=0;i<_fields.size();i++)
148         {
149             HttpField f=_fields.get(i);
150             if (f.getName().equalsIgnoreCase(name) && contains(f,value))
151                 return true;
152         }
153         return false;
154     }
155     
156     private boolean contains(HttpField field,String value)
157     {
158         String v = field.getValue();
159         if (v==null)
160             return false;
161 
162         if (value.equalsIgnoreCase(v))
163             return true;
164 
165         String[] split = __splitter.split(v);
166         for (int i = 0; split!=null && i < split.length; i++) 
167         {
168             if (value.equals(split[i]))
169                 return true;
170         }
171 
172         return false;
173     }
174 
175     public boolean contains(HttpHeader header)
176     {
177         for (int i=0;i<_fields.size();i++)
178         {
179             HttpField f=_fields.get(i);
180             if (f.getHeader()==header)
181                 return true;
182         }
183         return false;
184     }
185     
186     public boolean containsKey(String name)
187     {
188         for (int i=0;i<_fields.size();i++)
189         {
190             HttpField f=_fields.get(i);
191             if (f.getName().equalsIgnoreCase(name))
192                 return true;
193         }
194         return false;
195     }
196     
197     
198     public String getStringField(HttpHeader header)
199     {
200         return getStringField(header.asString());
201     }
202 
203     public String get(HttpHeader header)
204     {
205         return getStringField(header.asString());
206     }
207 
208     public String get(String header)
209     {
210         return getStringField(header);
211     }
212 
213     /**
214      * @return the value of a field, or null if not found. For multiple fields of the same name,
215      *         only the first is returned.
216      * @param name the case-insensitive field name
217      */
218     public String getStringField(String name)
219     {
220         HttpField field = getField(name);
221         return field==null?null:field.getValue();
222     }
223 
224     /**
225      * Get multi headers
226      *
227      * @return List the values
228      * @param name the case-insensitive field name
229      */
230     public List<String> getValuesList(String name)
231     {
232         final List<String> list = new ArrayList<>();
233         for (HttpField f : _fields)
234             if (f.getName().equalsIgnoreCase(name))
235                 list.add(f.getValue());
236         return list;
237     }
238 
239     /**
240      * Get multi headers
241      *
242      * @return Enumeration of the values
243      * @param name the case-insensitive field name
244      */
245     public Enumeration<String> getValues(final String name)
246     {
247         for (int i=0;i<_fields.size();i++)
248         {
249             final HttpField f = _fields.get(i);
250             
251             if (f.getName().equalsIgnoreCase(name) && f.getValue()!=null)
252             {
253                 final int first=i;
254                 return new Enumeration<String>()
255                 {
256                     HttpField field=f;
257                     int i = first+1;
258 
259                     @Override
260                     public boolean hasMoreElements()
261                     {
262                         if (field==null)
263                         {
264                             while (i<_fields.size()) 
265                             {
266                                 field=_fields.get(i++);
267                                 if (field.getName().equalsIgnoreCase(name) && field.getValue()!=null)
268                                     return true;
269                             }
270                             field=null;
271                             return false;
272                         }
273                         return true;
274                     }
275 
276                     @Override
277                     public String nextElement() throws NoSuchElementException
278                     {
279                         if (hasMoreElements())
280                         {
281                             String value=field.getValue();
282                             field=null;
283                             return value;
284                         }
285                         throw new NoSuchElementException();
286                     }
287 
288                 };
289             }
290         }
291 
292         List<String> empty=Collections.emptyList();
293         return Collections.enumeration(empty);
294     }
295 
296     /**
297      * Get multi field values with separator. The multiple values can be represented as separate
298      * headers of the same name, or by a single header using the separator(s), or a combination of
299      * both. Separators may be quoted.
300      *
301      * @param name the case-insensitive field name
302      * @param separators String of separators.
303      * @return Enumeration of the values, or null if no such header.
304      */
305     public Enumeration<String> getValues(String name, final String separators)
306     {
307         final Enumeration<String> e = getValues(name);
308         if (e == null)
309             return null;
310         return new Enumeration<String>()
311         {
312             QuotedStringTokenizer tok = null;
313 
314             @Override
315             public boolean hasMoreElements()
316             {
317                 if (tok != null && tok.hasMoreElements()) return true;
318                 while (e.hasMoreElements())
319                 {
320                     String value = e.nextElement();
321                     if (value!=null)
322                     {
323                         tok = new QuotedStringTokenizer(value, separators, false, false);
324                         if (tok.hasMoreElements()) return true;
325                     }
326                 }
327                 tok = null;
328                 return false;
329             }
330 
331             @Override
332             public String nextElement() throws NoSuchElementException
333             {
334                 if (!hasMoreElements()) throw new NoSuchElementException();
335                 String next = (String) tok.nextElement();
336                 if (next != null) next = next.trim();
337                 return next;
338             }
339         };
340     }
341 
342     public void put(HttpField field)
343     {
344         boolean put=false;
345         for (int i=_fields.size();i-->0;)
346         {
347             HttpField f=_fields.get(i);
348             if (f.isSame(field))
349             {
350                 if (put)
351                     _fields.remove(i);
352                 else
353                 {
354                     _fields.set(i,field);
355                     put=true;
356                 }
357             }
358         }
359         if (!put)
360             _fields.add(field);
361     }
362     
363     /**
364      * Set a field.
365      *
366      * @param name the name of the field
367      * @param value the value of the field. If null the field is cleared.
368      */
369     public void put(String name, String value)
370     {
371         if (value == null)
372             remove(name);
373         else
374             put(new HttpField(name, value));
375     }
376 
377     public void put(HttpHeader header, HttpHeaderValue value)
378     {
379         put(header,value.toString());
380     }
381 
382     /**
383      * Set a field.
384      *
385      * @param header the header name of the field
386      * @param value the value of the field. If null the field is cleared.
387      */
388     public void put(HttpHeader header, String value)
389     {
390         if (value == null)
391             remove(header);
392         else
393             put(new HttpField(header, value));
394     }
395 
396     /**
397      * Set a field.
398      *
399      * @param name the name of the field
400      * @param list the List value of the field. If null the field is cleared.
401      */
402     public void put(String name, List<String> list)
403     {
404         remove(name);
405         for (String v : list)
406             if (v!=null)
407                 add(name,v);
408     }
409 
410     /**
411      * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
412      * headers of the same name.
413      *
414      * @param name the name of the field
415      * @param value the value of the field.
416      * @exception IllegalArgumentException If the name is a single valued field and already has a
417      *                value.
418      */
419     public void add(String name, String value) throws IllegalArgumentException
420     {
421         if (value == null)
422             return;
423 
424         HttpField field = new HttpField(name, value);
425         _fields.add(field);
426     }
427 
428     public void add(HttpHeader header, HttpHeaderValue value) throws IllegalArgumentException
429     {
430         add(header,value.toString());
431     }
432 
433     /**
434      * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
435      * headers of the same name.
436      *
437      * @param header the header
438      * @param value the value of the field.
439      * @exception IllegalArgumentException 
440      */
441     public void add(HttpHeader header, String value) throws IllegalArgumentException
442     {
443         if (value == null) throw new IllegalArgumentException("null value");
444 
445         HttpField field = new HttpField(header, value);
446         _fields.add(field);
447     }
448 
449     /**
450      * Remove a field.
451      *
452      * @param name the field to remove
453      */
454     public HttpField remove(HttpHeader name)
455     {
456         for (int i=_fields.size();i-->0;)
457         {
458             HttpField f=_fields.get(i);
459             if (f.getHeader()==name)
460                 return _fields.remove(i);
461         }
462         return null;
463     }
464 
465     /**
466      * Remove a field.
467      *
468      * @param name the field to remove
469      */
470     public HttpField remove(String name)
471     {
472         for (int i=_fields.size();i-->0;)
473         {
474             HttpField f=_fields.get(i);
475             if (f.getName().equalsIgnoreCase(name))
476                 return _fields.remove(i);
477         }
478         return null;
479     }
480 
481     /**
482      * Get a header as an long value. Returns the value of an integer field or -1 if not found. The
483      * case of the field name is ignored.
484      *
485      * @param name the case-insensitive field name
486      * @exception NumberFormatException If bad long found
487      */
488     public long getLongField(String name) throws NumberFormatException
489     {
490         HttpField field = getField(name);
491         return field==null?-1L:StringUtil.toLong(field.getValue());
492     }
493 
494     /**
495      * Get a header as a date value. Returns the value of a date field, or -1 if not found. The case
496      * of the field name is ignored.
497      *
498      * @param name the case-insensitive field name
499      */
500     public long getDateField(String name)
501     {
502         HttpField field = getField(name);
503         if (field == null)
504             return -1;
505 
506         String val = valueParameters(field.getValue(), null);
507         if (val == null)
508             return -1;
509 
510         final long date = DateParser.parseDate(val);
511         if (date==-1)
512             throw new IllegalArgumentException("Cannot convert date: " + val);
513         return date;
514     }
515 
516 
517     /**
518      * Sets the value of an long field.
519      *
520      * @param name the field name
521      * @param value the field long value
522      */
523     public void putLongField(HttpHeader name, long value)
524     {
525         String v = Long.toString(value);
526         put(name, v);
527     }
528 
529     /**
530      * Sets the value of an long field.
531      *
532      * @param name the field name
533      * @param value the field long value
534      */
535     public void putLongField(String name, long value)
536     {
537         String v = Long.toString(value);
538         put(name, v);
539     }
540 
541 
542     /**
543      * Sets the value of a date field.
544      *
545      * @param name the field name
546      * @param date the field date value
547      */
548     public void putDateField(HttpHeader name, long date)
549     {
550         String d=DateGenerator.formatDate(date);
551         put(name, d);
552     }
553 
554     /**
555      * Sets the value of a date field.
556      *
557      * @param name the field name
558      * @param date the field date value
559      */
560     public void putDateField(String name, long date)
561     {
562         String d=DateGenerator.formatDate(date);
563         put(name, d);
564     }
565 
566     /**
567      * Sets the value of a date field.
568      *
569      * @param name the field name
570      * @param date the field date value
571      */
572     public void addDateField(String name, long date)
573     {
574         String d=DateGenerator.formatDate(date);
575         add(name,d);
576     }
577 
578     @Override
579     public String
580     toString()
581     {
582         try
583         {
584             StringBuilder buffer = new StringBuilder();
585             for (HttpField field : _fields)
586             {
587                 if (field != null)
588                 {
589                     String tmp = field.getName();
590                     if (tmp != null) buffer.append(tmp);
591                     buffer.append(": ");
592                     tmp = field.getValue();
593                     if (tmp != null) buffer.append(tmp);
594                     buffer.append("\r\n");
595                 }
596             }
597             buffer.append("\r\n");
598             return buffer.toString();
599         }
600         catch (Exception e)
601         {
602             LOG.warn(e);
603             return e.toString();
604         }
605     }
606 
607     /**
608      * Clear the header.
609      */
610     public void clear()
611     {
612         _fields.clear();
613     }
614 
615     public void add(HttpField field)
616     {
617         _fields.add(field);
618     }
619 
620     
621     
622     /**
623      * Add fields from another HttpFields instance. Single valued fields are replaced, while all
624      * others are added.
625      *
626      * @param fields the fields to add
627      */
628     public void add(HttpFields fields)
629     {
630         if (fields == null) return;
631 
632         Enumeration<String> e = fields.getFieldNames();
633         while (e.hasMoreElements())
634         {
635             String name = e.nextElement();
636             Enumeration<String> values = fields.getValues(name);
637             while (values.hasMoreElements())
638                 add(name, values.nextElement());
639         }
640     }
641 
642     /**
643      * Get field value parameters. Some field values can have parameters. This method separates the
644      * value from the parameters and optionally populates a map with the parameters. For example:
645      *
646      * <PRE>
647      *
648      * FieldName : Value ; param1=val1 ; param2=val2
649      *
650      * </PRE>
651      *
652      * @param value The Field value, possibly with parameteres.
653      * @param parameters A map to populate with the parameters, or null
654      * @return The value.
655      */
656     public static String valueParameters(String value, Map<String,String> parameters)
657     {
658         if (value == null) return null;
659 
660         int i = value.indexOf(';');
661         if (i < 0) return value;
662         if (parameters == null) return value.substring(0, i).trim();
663 
664         StringTokenizer tok1 = new QuotedStringTokenizer(value.substring(i), ";", false, true);
665         while (tok1.hasMoreTokens())
666         {
667             String token = tok1.nextToken();
668             StringTokenizer tok2 = new QuotedStringTokenizer(token, "= ");
669             if (tok2.hasMoreTokens())
670             {
671                 String paramName = tok2.nextToken();
672                 String paramVal = null;
673                 if (tok2.hasMoreTokens()) paramVal = tok2.nextToken();
674                 parameters.put(paramName, paramVal);
675             }
676         }
677 
678         return value.substring(0, i).trim();
679     }
680 
681     private static final Float __one = new Float("1.0");
682     private static final Float __zero = new Float("0.0");
683     private static final Trie<Float> __qualities = new ArrayTernaryTrie<>();
684     static
685     {
686         __qualities.put("*", __one);
687         __qualities.put("1.0", __one);
688         __qualities.put("1", __one);
689         __qualities.put("0.9", new Float("0.9"));
690         __qualities.put("0.8", new Float("0.8"));
691         __qualities.put("0.7", new Float("0.7"));
692         __qualities.put("0.66", new Float("0.66"));
693         __qualities.put("0.6", new Float("0.6"));
694         __qualities.put("0.5", new Float("0.5"));
695         __qualities.put("0.4", new Float("0.4"));
696         __qualities.put("0.33", new Float("0.33"));
697         __qualities.put("0.3", new Float("0.3"));
698         __qualities.put("0.2", new Float("0.2"));
699         __qualities.put("0.1", new Float("0.1"));
700         __qualities.put("0", __zero);
701         __qualities.put("0.0", __zero);
702     }
703 
704     public static Float getQuality(String value)
705     {
706         if (value == null) return __zero;
707 
708         int qe = value.indexOf(";");
709         if (qe++ < 0 || qe == value.length()) return __one;
710 
711         if (value.charAt(qe++) == 'q')
712         {
713             qe++;
714             Float q = __qualities.get(value, qe, value.length() - qe);
715             if (q != null)
716                 return q;
717         }
718 
719         Map<String,String> params = new HashMap<>(4);
720         valueParameters(value, params);
721         String qs = params.get("q");
722         if (qs==null)
723             qs="*";
724         Float q = __qualities.get(qs);
725         if (q == null)
726         {
727             try
728             {
729                 q = new Float(qs);
730             }
731             catch (Exception e)
732             {
733                 q = __one;
734             }
735         }
736         return q;
737     }
738 
739     /**
740      * List values in quality order.
741      *
742      * @param e Enumeration of values with quality parameters
743      * @return values in quality order.
744      */
745     public static List<String> qualityList(Enumeration<String> e)
746     {
747         if (e == null || !e.hasMoreElements())
748             return Collections.emptyList();
749 
750         Object list = null;
751         Object qual = null;
752 
753         // Assume list will be well ordered and just add nonzero
754         while (e.hasMoreElements())
755         {
756             String v = e.nextElement();
757             Float q = getQuality(v);
758 
759             if (q >= 0.001)
760             {
761                 list = LazyList.add(list, v);
762                 qual = LazyList.add(qual, q);
763             }
764         }
765 
766         List<String> vl = LazyList.getList(list, false);
767         if (vl.size() < 2) 
768             return vl;
769 
770         List<Float> ql = LazyList.getList(qual, false);
771 
772         // sort list with swaps
773         Float last = __zero;
774         for (int i = vl.size(); i-- > 0;)
775         {
776             Float q = ql.get(i);
777             if (last.compareTo(q) > 0)
778             {
779                 String tmp = vl.get(i);
780                 vl.set(i, vl.get(i + 1));
781                 vl.set(i + 1, tmp);
782                 ql.set(i, ql.get(i + 1));
783                 ql.set(i + 1, q);
784                 last = __zero;
785                 i = vl.size();
786                 continue;
787             }
788             last = q;
789         }
790         ql.clear();
791         return vl;
792     }
793 
794 
795 
796 }