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