View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 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.io.IOException;
22  import java.nio.ByteBuffer;
23  import java.text.SimpleDateFormat;
24  import java.util.ArrayList;
25  import java.util.Calendar;
26  import java.util.Collection;
27  import java.util.Collections;
28  import java.util.Date;
29  import java.util.Enumeration;
30  import java.util.GregorianCalendar;
31  import java.util.HashMap;
32  import java.util.HashSet;
33  import java.util.Iterator;
34  import java.util.List;
35  import java.util.Locale;
36  import java.util.Map;
37  import java.util.NoSuchElementException;
38  import java.util.Set;
39  import java.util.StringTokenizer;
40  import java.util.TimeZone;
41  
42  import org.eclipse.jetty.util.ArrayTernaryTrie;
43  import org.eclipse.jetty.util.BufferUtil;
44  import org.eclipse.jetty.util.DateCache;
45  import org.eclipse.jetty.util.LazyList;
46  import org.eclipse.jetty.util.QuotedStringTokenizer;
47  import org.eclipse.jetty.util.StringMap;
48  import org.eclipse.jetty.util.StringUtil;
49  import org.eclipse.jetty.util.Trie;
50  import org.eclipse.jetty.util.log.Log;
51  import org.eclipse.jetty.util.log.Logger;
52  
53  
54  /**
55   * HTTP Fields. A collection of HTTP header and or Trailer fields.
56   *
57   * <p>This class is not synchronized as it is expected that modifications will only be performed by a
58   * single thread.
59   *
60   */
61  public class HttpFields implements Iterable<HttpField>
62  {
63      private static final Logger LOG = Log.getLogger(HttpFields.class);
64      public static final String __COOKIE_DELIM="\"\\\n\r\t\f\b%+ ;=";
65      public static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
66      public static final DateCache __dateCache = new DateCache("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
67  
68      static
69      {
70          __GMT.setID("GMT");
71          __dateCache.setTimeZone(__GMT);
72      }
73  
74      public final static String __separators = ", \t";
75  
76      private static final String[] DAYS =
77          { "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
78      private static final String[] MONTHS =
79          { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"};
80  
81      public static class DateGenerator
82      {
83          private final StringBuilder buf = new StringBuilder(32);
84          private final GregorianCalendar gc = new GregorianCalendar(__GMT);
85  
86          /**
87           * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
88           */
89          public String formatDate(long date)
90          {
91              buf.setLength(0);
92              gc.setTimeInMillis(date);
93  
94              int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
95              int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
96              int month = gc.get(Calendar.MONTH);
97              int year = gc.get(Calendar.YEAR);
98              int century = year / 100;
99              year = year % 100;
100 
101             int hours = gc.get(Calendar.HOUR_OF_DAY);
102             int minutes = gc.get(Calendar.MINUTE);
103             int seconds = gc.get(Calendar.SECOND);
104 
105             buf.append(DAYS[day_of_week]);
106             buf.append(',');
107             buf.append(' ');
108             StringUtil.append2digits(buf, day_of_month);
109 
110             buf.append(' ');
111             buf.append(MONTHS[month]);
112             buf.append(' ');
113             StringUtil.append2digits(buf, century);
114             StringUtil.append2digits(buf, year);
115 
116             buf.append(' ');
117             StringUtil.append2digits(buf, hours);
118             buf.append(':');
119             StringUtil.append2digits(buf, minutes);
120             buf.append(':');
121             StringUtil.append2digits(buf, seconds);
122             buf.append(" GMT");
123             return buf.toString();
124         }
125 
126         /**
127          * Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies
128          */
129         public void formatCookieDate(StringBuilder buf, long date)
130         {
131             gc.setTimeInMillis(date);
132 
133             int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
134             int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
135             int month = gc.get(Calendar.MONTH);
136             int year = gc.get(Calendar.YEAR);
137             year = year % 10000;
138 
139             int epoch = (int) ((date / 1000) % (60 * 60 * 24));
140             int seconds = epoch % 60;
141             epoch = epoch / 60;
142             int minutes = epoch % 60;
143             int hours = epoch / 60;
144 
145             buf.append(DAYS[day_of_week]);
146             buf.append(',');
147             buf.append(' ');
148             StringUtil.append2digits(buf, day_of_month);
149 
150             buf.append('-');
151             buf.append(MONTHS[month]);
152             buf.append('-');
153             StringUtil.append2digits(buf, year/100);
154             StringUtil.append2digits(buf, year%100);
155 
156             buf.append(' ');
157             StringUtil.append2digits(buf, hours);
158             buf.append(':');
159             StringUtil.append2digits(buf, minutes);
160             buf.append(':');
161             StringUtil.append2digits(buf, seconds);
162             buf.append(" GMT");
163         }
164     }
165 
166     private static final ThreadLocal<DateGenerator> __dateGenerator =new ThreadLocal<DateGenerator>()
167     {
168         @Override
169         protected DateGenerator initialValue()
170         {
171             return new DateGenerator();
172         }
173     };
174 
175     /**
176      * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
177      */
178     public static String formatDate(long date)
179     {
180         return __dateGenerator.get().formatDate(date);
181     }
182 
183     /**
184      * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
185      */
186     public static void formatCookieDate(StringBuilder buf, long date)
187     {
188         __dateGenerator.get().formatCookieDate(buf,date);
189     }
190 
191     /**
192      * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
193      */
194     public static String formatCookieDate(long date)
195     {
196         StringBuilder buf = new StringBuilder(28);
197         formatCookieDate(buf, date);
198         return buf.toString();
199     }
200 
201     private final static String __dateReceiveFmt[] =
202         {
203         "EEE, dd MMM yyyy HH:mm:ss zzz",
204         "EEE, dd-MMM-yy HH:mm:ss",
205         "EEE MMM dd HH:mm:ss yyyy",
206 
207         "EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz",
208         "EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss",
209         "EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz",
210         "dd MMM yyyy HH:mm:ss", "dd-MMM-yy HH:mm:ss zzz", "dd-MMM-yy HH:mm:ss", "MMM dd HH:mm:ss yyyy zzz",
211         "MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz",
212         "EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz",
213         "EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss",
214         };
215 
216     private static class DateParser
217     {
218         final SimpleDateFormat _dateReceive[]= new SimpleDateFormat[__dateReceiveFmt.length];
219 
220         long parse(final String dateVal)
221         {
222             for (int i = 0; i < _dateReceive.length; i++)
223             {
224                 if (_dateReceive[i] == null)
225                 {
226                     _dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US);
227                     _dateReceive[i].setTimeZone(__GMT);
228                 }
229 
230                 try
231                 {
232                     Date date = (Date) _dateReceive[i].parseObject(dateVal);
233                     return date.getTime();
234                 }
235                 catch (java.lang.Exception e)
236                 {
237                     // LOG.ignore(e);
238                 }
239             }
240 
241             if (dateVal.endsWith(" GMT"))
242             {
243                 final String val = dateVal.substring(0, dateVal.length() - 4);
244 
245                 for (SimpleDateFormat element : _dateReceive)
246                 {
247                     try
248                     {
249                         Date date = (Date) element.parseObject(val);
250                         return date.getTime();
251                     }
252                     catch (java.lang.Exception e)
253                     {
254                         // LOG.ignore(e);
255                     }
256                 }
257             }
258             return -1;
259         }
260     }
261 
262     public static long parseDate(String date)
263     {
264         return __dateParser.get().parse(date);
265     }
266 
267     private static final ThreadLocal<DateParser> __dateParser =new ThreadLocal<DateParser>()
268     {
269         @Override
270         protected DateParser initialValue()
271         {
272             return new DateParser();
273         }
274     };
275 
276     public final static String __01Jan1970=formatDate(0);
277     public final static ByteBuffer __01Jan1970_BUFFER=BufferUtil.toBuffer(__01Jan1970);
278     public final static String __01Jan1970_COOKIE = formatCookieDate(0).trim();
279     private final ArrayList<HttpField> _fields = new ArrayList<>(20);
280 
281     /**
282      * Constructor.
283      */
284     public HttpFields()
285     {
286     }
287 
288 
289     /**
290      * Get Collection of header names.
291      */
292     public Collection<String> getFieldNamesCollection()
293     {
294         final Set<String> list = new HashSet<>(_fields.size());
295         for (HttpField f : _fields)
296         {
297             if (f!=null)
298                 list.add(f.getName());
299         }
300         return list;
301     }
302 
303     /**
304      * Get enumeration of header _names. Returns an enumeration of strings representing the header
305      * _names for this request.
306      */
307     public Enumeration<String> getFieldNames()
308     {
309         return Collections.enumeration(getFieldNamesCollection());
310     }
311 
312     public int size()
313     {
314         return _fields.size();
315     }
316 
317     /**
318      * Get a Field by index.
319      * @return A Field value or null if the Field value has not been set
320      *
321      */
322     public HttpField getField(int i)
323     {
324         return _fields.get(i);
325     }
326 
327     @Override
328     public Iterator<HttpField> iterator()
329     {
330         return _fields.iterator();
331     }
332 
333     public HttpField getField(HttpHeader header)
334     {
335         for (int i=0;i<_fields.size();i++)
336         {
337             HttpField f=_fields.get(i);
338             if (f.getHeader()==header)
339                 return f;
340         }
341         return null;
342     }
343 
344     public HttpField getField(String name)
345     {
346         for (int i=0;i<_fields.size();i++)
347         {
348             HttpField f=_fields.get(i);
349             if (f.getName().equalsIgnoreCase(name))
350                 return f;
351         }
352         return null;
353     }
354 
355     public boolean contains(HttpHeader header, String value)
356     {
357         for (int i=0;i<_fields.size();i++)
358         {
359             HttpField f=_fields.get(i);
360             if (f.getHeader()==header && f.contains(value))
361                 return true;
362         }
363         return false;
364     }
365     
366     public boolean contains(String name, String value)
367     {
368         for (int i=0;i<_fields.size();i++)
369         {
370             HttpField f=_fields.get(i);
371             if (f.getName().equalsIgnoreCase(name) && f.contains(value))
372                 return true;
373         }
374         return false;
375     }
376     
377     public boolean containsKey(String name)
378     {
379         for (int i=0;i<_fields.size();i++)
380         {
381             HttpField f=_fields.get(i);
382             if (f.getName().equalsIgnoreCase(name))
383                 return true;
384         }
385         return false;
386     }
387 
388     public String getStringField(HttpHeader header)
389     {
390         return getStringField(header.asString());
391     }
392 
393     public String get(HttpHeader header)
394     {
395         return getStringField(header.asString());
396     }
397 
398     public String get(String header)
399     {
400         return getStringField(header);
401     }
402 
403     /**
404      * @return the value of a field, or null if not found. For multiple fields of the same name,
405      *         only the first is returned.
406      * @param name the case-insensitive field name
407      */
408     public String getStringField(String name)
409     {
410         HttpField field = getField(name);
411         return field==null?null:field.getValue();
412     }
413 
414 
415     /**
416      * Get multi headers
417      *
418      * @return Enumeration of the values, or null if no such header.
419      * @param name the case-insensitive field name
420      */
421     public Collection<String> getValuesCollection(String name)
422     {
423         final List<String> list = new ArrayList<>();
424         for (HttpField f : _fields)
425             if (f.getName().equalsIgnoreCase(name))
426                 list.add(f.getValue());
427         return list;
428     }
429 
430     /**
431      * Get multi headers
432      *
433      * @return Enumeration of the values
434      * @param name the case-insensitive field name
435      */
436     public Enumeration<String> getValues(final String name)
437     {
438         for (int i=0;i<_fields.size();i++)
439         {
440             final HttpField f = _fields.get(i);
441             
442             if (f.getName().equalsIgnoreCase(name))
443             {
444                 final int first=i;
445                 return new Enumeration<String>()
446                 {
447                     HttpField field=f;
448                     int i = first+1;
449 
450                     @Override
451                     public boolean hasMoreElements()
452                     {
453                         if (field==null)
454                         {
455                             while (i<_fields.size()) 
456                             {
457                                 field=_fields.get(i++);
458                                 if (field.getName().equalsIgnoreCase(name))
459                                     return true;
460                             }
461                             field=null;
462                             return false;
463                         }
464                         return true;
465                     }
466 
467                     @Override
468                     public String nextElement() throws NoSuchElementException
469                     {
470                         if (hasMoreElements())
471                         {
472                             String value=field.getValue();
473                             field=null;
474                             return value;
475                         }
476                         throw new NoSuchElementException();
477                     }
478 
479                 };
480             }
481         }
482 
483         List<String> empty=Collections.emptyList();
484         return Collections.enumeration(empty);
485 
486     }
487 
488     /**
489      * Get multi field values with separator. The multiple values can be represented as separate
490      * headers of the same name, or by a single header using the separator(s), or a combination of
491      * both. Separators may be quoted.
492      *
493      * @param name the case-insensitive field name
494      * @param separators String of separators.
495      * @return Enumeration of the values, or null if no such header.
496      */
497     public Enumeration<String> getValues(String name, final String separators)
498     {
499         final Enumeration<String> e = getValues(name);
500         if (e == null)
501             return null;
502         return new Enumeration<String>()
503         {
504             QuotedStringTokenizer tok = null;
505 
506             @Override
507             public boolean hasMoreElements()
508             {
509                 if (tok != null && tok.hasMoreElements()) return true;
510                 while (e.hasMoreElements())
511                 {
512                     String value = e.nextElement();
513                     tok = new QuotedStringTokenizer(value, separators, false, false);
514                     if (tok.hasMoreElements()) return true;
515                 }
516                 tok = null;
517                 return false;
518             }
519 
520             @Override
521             public String nextElement() throws NoSuchElementException
522             {
523                 if (!hasMoreElements()) throw new NoSuchElementException();
524                 String next = (String) tok.nextElement();
525                 if (next != null) next = next.trim();
526                 return next;
527             }
528         };
529     }
530 
531     public void put(HttpField field)
532     {
533         boolean put=false;
534         for (int i=_fields.size();i-->0;)
535         {
536             HttpField f=_fields.get(i);
537             if (f.isSame(field))
538             {
539                 if (put)
540                     _fields.remove(i);
541                 else
542                 {
543                     _fields.set(i,field);
544                     put=true;
545                 }
546             }
547         }
548         if (!put)
549             _fields.add(field);
550     }
551     
552     /**
553      * Set a field.
554      *
555      * @param name the name of the field
556      * @param value the value of the field. If null the field is cleared.
557      */
558     public void put(String name, String value)
559     {
560         if (value == null)
561             remove(name);
562         else
563             put(new HttpField(name, value));
564     }
565 
566     public void put(HttpHeader header, HttpHeaderValue value)
567     {
568         put(header,value.toString());
569     }
570 
571     /**
572      * Set a field.
573      *
574      * @param header the header name of the field
575      * @param value the value of the field. If null the field is cleared.
576      */
577     public void put(HttpHeader header, String value)
578     {
579         if (value == null)
580             remove(header);
581         else
582             put(new HttpField(header, value));
583     }
584 
585     /**
586      * Set a field.
587      *
588      * @param name the name of the field
589      * @param list the List value of the field. If null the field is cleared.
590      */
591     public void put(String name, List<String> list)
592     {
593         remove(name);
594         for (String v : list)
595             if (v!=null)
596                 add(name,v);
597     }
598 
599     /**
600      * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
601      * headers of the same name.
602      *
603      * @param name the name of the field
604      * @param value the value of the field.
605      * @exception IllegalArgumentException If the name is a single valued field and already has a
606      *                value.
607      */
608     public void add(String name, String value) throws IllegalArgumentException
609     {
610         if (value == null)
611             return;
612 
613         HttpField field = new HttpField(name, value);
614         _fields.add(field);
615     }
616 
617     public void add(HttpHeader header, HttpHeaderValue value) throws IllegalArgumentException
618     {
619         add(header,value.toString());
620     }
621 
622     /**
623      * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
624      * headers of the same name.
625      *
626      * @param header the header
627      * @param value the value of the field.
628      * @exception IllegalArgumentException 
629      */
630     public void add(HttpHeader header, String value) throws IllegalArgumentException
631     {
632         if (value == null) throw new IllegalArgumentException("null value");
633 
634         HttpField field = new HttpField(header, value);
635         _fields.add(field);
636     }
637 
638     /**
639      * Remove a field.
640      *
641      * @param name the field to remove
642      */
643     public void remove(HttpHeader name)
644     {
645         for (int i=_fields.size();i-->0;)
646         {
647             HttpField f=_fields.get(i);
648             if (f.getHeader()==name)
649                 _fields.remove(i);
650         }
651     }
652 
653     /**
654      * Remove a field.
655      *
656      * @param name the field to remove
657      */
658     public void remove(String name)
659     {
660         for (int i=_fields.size();i-->0;)
661         {
662             HttpField f=_fields.get(i);
663             if (f.getName().equalsIgnoreCase(name))
664                 _fields.remove(i);
665         }
666     }
667 
668     /**
669      * Get a header as an long value. Returns the value of an integer field or -1 if not found. The
670      * case of the field name is ignored.
671      *
672      * @param name the case-insensitive field name
673      * @exception NumberFormatException If bad long found
674      */
675     public long getLongField(String name) throws NumberFormatException
676     {
677         HttpField field = getField(name);
678         return field==null?-1L:field.getLongValue();
679     }
680 
681     /**
682      * Get a header as a date value. Returns the value of a date field, or -1 if not found. The case
683      * of the field name is ignored.
684      *
685      * @param name the case-insensitive field name
686      */
687     public long getDateField(String name)
688     {
689         HttpField field = getField(name);
690         if (field == null)
691             return -1;
692 
693         String val = valueParameters(field.getValue(), null);
694         if (val == null)
695             return -1;
696 
697         final long date = __dateParser.get().parse(val);
698         if (date==-1)
699             throw new IllegalArgumentException("Cannot convert date: " + val);
700         return date;
701     }
702 
703 
704     /**
705      * Sets the value of an long field.
706      *
707      * @param name the field name
708      * @param value the field long value
709      */
710     public void putLongField(HttpHeader name, long value)
711     {
712         String v = Long.toString(value);
713         put(name, v);
714     }
715 
716     /**
717      * Sets the value of an long field.
718      *
719      * @param name the field name
720      * @param value the field long value
721      */
722     public void putLongField(String name, long value)
723     {
724         String v = Long.toString(value);
725         put(name, v);
726     }
727 
728 
729     /**
730      * Sets the value of a date field.
731      *
732      * @param name the field name
733      * @param date the field date value
734      */
735     public void putDateField(HttpHeader name, long date)
736     {
737         String d=formatDate(date);
738         put(name, d);
739     }
740 
741     /**
742      * Sets the value of a date field.
743      *
744      * @param name the field name
745      * @param date the field date value
746      */
747     public void putDateField(String name, long date)
748     {
749         String d=formatDate(date);
750         put(name, d);
751     }
752 
753     /**
754      * Sets the value of a date field.
755      *
756      * @param name the field name
757      * @param date the field date value
758      */
759     public void addDateField(String name, long date)
760     {
761         String d=formatDate(date);
762         add(name,d);
763     }
764 
765     /**
766      * Format a set cookie value
767      *
768      * @param cookie The cookie.
769      */
770     public void addSetCookie(HttpCookie cookie)
771     {
772         addSetCookie(
773                 cookie.getName(),
774                 cookie.getValue(),
775                 cookie.getDomain(),
776                 cookie.getPath(),
777                 cookie.getMaxAge(),
778                 cookie.getComment(),
779                 cookie.isSecure(),
780                 cookie.isHttpOnly(),
781                 cookie.getVersion());
782     }
783 
784     /**
785      * Format a set cookie value
786      *
787      * @param name the name
788      * @param value the value
789      * @param domain the domain
790      * @param path the path
791      * @param maxAge the maximum age
792      * @param comment the comment (only present on versions > 0)
793      * @param isSecure true if secure cookie
794      * @param isHttpOnly true if for http only
795      * @param version version of cookie logic to use (0 == default behavior)
796      */
797     public void addSetCookie(
798             final String name,
799             final String value,
800             final String domain,
801             final String path,
802             final long maxAge,
803             final String comment,
804             final boolean isSecure,
805             final boolean isHttpOnly,
806             int version)
807     {
808         String delim=__COOKIE_DELIM;
809 
810         // Check arguments
811         if (name == null || name.length() == 0)
812             throw new IllegalArgumentException("Bad cookie name");
813 
814         // Format value and params
815         StringBuilder buf = new StringBuilder(128);
816         String name_value_params;
817         QuotedStringTokenizer.quoteIfNeeded(buf, name, delim);
818         buf.append('=');
819         String start=buf.toString();
820         boolean hasDomain = false;
821         boolean hasPath = false;
822         
823         if (value != null && value.length() > 0)
824             QuotedStringTokenizer.quoteIfNeeded(buf, value, delim);
825 
826 
827         if (path != null && path.length() > 0)
828         {
829             hasPath = true;
830             buf.append(";Path=");
831             if (path.trim().startsWith("\""))
832                 buf.append(path);
833             else
834                 QuotedStringTokenizer.quoteIfNeeded(buf,path,delim);
835         }
836         if (domain != null && domain.length() > 0)
837         {
838             hasDomain = true;
839             buf.append(";Domain=");
840             QuotedStringTokenizer.quoteIfNeeded(buf,domain.toLowerCase(Locale.ENGLISH),delim);
841         }
842 
843         if (maxAge >= 0)
844         {
845             // Always add the expires param as some browsers still don't handle max-age
846             buf.append(";Expires=");
847             if (maxAge == 0)
848                 buf.append(__01Jan1970_COOKIE);
849             else
850                 formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
851 
852             buf.append(";Max-Age=");
853             buf.append(maxAge);
854         }
855 
856         if (isSecure)
857             buf.append(";Secure");
858         if (isHttpOnly)
859             buf.append(";HttpOnly");
860 
861         if (comment != null && comment.length() > 0)
862         {
863             buf.append(";Comment=");
864             QuotedStringTokenizer.quoteIfNeeded(buf, comment, delim);
865         }
866 
867         name_value_params = buf.toString();
868 
869         // remove existing set-cookie of same name
870 
871         Iterator<HttpField> i=_fields.iterator();
872         while (i.hasNext())
873         {
874             HttpField field=i.next();
875             if (field.getHeader()==HttpHeader.SET_COOKIE)
876             {
877                 String val = (field.getValue() == null ? null : field.getValue().toString());
878                 if (val!=null && val.startsWith(start))
879                 {
880                     //existing cookie has same name, does it also match domain and path?
881                     if (((!hasDomain && !val.contains("Domain")) || (hasDomain && val.contains("Domain="+domain))) &&
882                         ((!hasPath && !val.contains("Path")) || (hasPath && val.contains("Path="+path))))
883                     {
884                         i.remove();
885                     }
886                 }
887                 
888             }
889         }
890         
891         add(HttpHeader.SET_COOKIE.toString(), name_value_params);
892 
893         // Expire responses with set-cookie headers so they do not get cached.
894         put(HttpHeader.EXPIRES.toString(), __01Jan1970);
895     }
896 
897     public void putTo(ByteBuffer bufferInFillMode) throws IOException
898     {
899         for (HttpField field : _fields)
900         {
901             if (field != null)
902                 field.putTo(bufferInFillMode);
903         }
904         BufferUtil.putCRLF(bufferInFillMode);
905     }
906 
907     @Override
908     public String
909     toString()
910     {
911         try
912         {
913             StringBuilder buffer = new StringBuilder();
914             for (HttpField field : _fields)
915             {
916                 if (field != null)
917                 {
918                     String tmp = field.getName();
919                     if (tmp != null) buffer.append(tmp);
920                     buffer.append(": ");
921                     tmp = field.getValue();
922                     if (tmp != null) buffer.append(tmp);
923                     buffer.append("\r\n");
924                 }
925             }
926             buffer.append("\r\n");
927             return buffer.toString();
928         }
929         catch (Exception e)
930         {
931             LOG.warn(e);
932             return e.toString();
933         }
934     }
935 
936     /**
937      * Clear the header.
938      */
939     public void clear()
940     {
941         _fields.clear();
942     }
943 
944     public void add(HttpField field)
945     {
946         _fields.add(field);
947     }
948 
949     
950     
951     /**
952      * Add fields from another HttpFields instance. Single valued fields are replaced, while all
953      * others are added.
954      *
955      * @param fields the fields to add
956      */
957     public void add(HttpFields fields)
958     {
959         if (fields == null) return;
960 
961         Enumeration<String> e = fields.getFieldNames();
962         while (e.hasMoreElements())
963         {
964             String name = e.nextElement();
965             Enumeration<String> values = fields.getValues(name);
966             while (values.hasMoreElements())
967                 add(name, values.nextElement());
968         }
969     }
970 
971     /**
972      * Get field value parameters. Some field values can have parameters. This method separates the
973      * value from the parameters and optionally populates a map with the parameters. For example:
974      *
975      * <PRE>
976      *
977      * FieldName : Value ; param1=val1 ; param2=val2
978      *
979      * </PRE>
980      *
981      * @param value The Field value, possibly with parameteres.
982      * @param parameters A map to populate with the parameters, or null
983      * @return The value.
984      */
985     public static String valueParameters(String value, Map<String,String> parameters)
986     {
987         if (value == null) return null;
988 
989         int i = value.indexOf(';');
990         if (i < 0) return value;
991         if (parameters == null) return value.substring(0, i).trim();
992 
993         StringTokenizer tok1 = new QuotedStringTokenizer(value.substring(i), ";", false, true);
994         while (tok1.hasMoreTokens())
995         {
996             String token = tok1.nextToken();
997             StringTokenizer tok2 = new QuotedStringTokenizer(token, "= ");
998             if (tok2.hasMoreTokens())
999             {
1000                 String paramName = tok2.nextToken();
1001                 String paramVal = null;
1002                 if (tok2.hasMoreTokens()) paramVal = tok2.nextToken();
1003                 parameters.put(paramName, paramVal);
1004             }
1005         }
1006 
1007         return value.substring(0, i).trim();
1008     }
1009 
1010     private static final Float __one = new Float("1.0");
1011     private static final Float __zero = new Float("0.0");
1012     private static final Trie<Float> __qualities = new ArrayTernaryTrie<>();
1013     static
1014     {
1015         __qualities.put("*", __one);
1016         __qualities.put("1.0", __one);
1017         __qualities.put("1", __one);
1018         __qualities.put("0.9", new Float("0.9"));
1019         __qualities.put("0.8", new Float("0.8"));
1020         __qualities.put("0.7", new Float("0.7"));
1021         __qualities.put("0.66", new Float("0.66"));
1022         __qualities.put("0.6", new Float("0.6"));
1023         __qualities.put("0.5", new Float("0.5"));
1024         __qualities.put("0.4", new Float("0.4"));
1025         __qualities.put("0.33", new Float("0.33"));
1026         __qualities.put("0.3", new Float("0.3"));
1027         __qualities.put("0.2", new Float("0.2"));
1028         __qualities.put("0.1", new Float("0.1"));
1029         __qualities.put("0", __zero);
1030         __qualities.put("0.0", __zero);
1031     }
1032 
1033     public static Float getQuality(String value)
1034     {
1035         if (value == null) return __zero;
1036 
1037         int qe = value.indexOf(";");
1038         if (qe++ < 0 || qe == value.length()) return __one;
1039 
1040         if (value.charAt(qe++) == 'q')
1041         {
1042             qe++;
1043             Float q = __qualities.get(value, qe, value.length() - qe);
1044             if (q != null)
1045                 return q;
1046         }
1047 
1048         Map<String,String> params = new HashMap<>(4);
1049         valueParameters(value, params);
1050         String qs = params.get("q");
1051         if (qs==null)
1052             qs="*";
1053         Float q = __qualities.get(qs);
1054         if (q == null)
1055         {
1056             try
1057             {
1058                 q = new Float(qs);
1059             }
1060             catch (Exception e)
1061             {
1062                 q = __one;
1063             }
1064         }
1065         return q;
1066     }
1067 
1068     /**
1069      * List values in quality order.
1070      *
1071      * @param e Enumeration of values with quality parameters
1072      * @return values in quality order.
1073      */
1074     public static List<String> qualityList(Enumeration<String> e)
1075     {
1076         if (e == null || !e.hasMoreElements())
1077             return Collections.emptyList();
1078 
1079         Object list = null;
1080         Object qual = null;
1081 
1082         // Assume list will be well ordered and just add nonzero
1083         while (e.hasMoreElements())
1084         {
1085             String v = e.nextElement();
1086             Float q = getQuality(v);
1087 
1088             if (q >= 0.001)
1089             {
1090                 list = LazyList.add(list, v);
1091                 qual = LazyList.add(qual, q);
1092             }
1093         }
1094 
1095         List vl = LazyList.getList(list, false);
1096         if (vl.size() < 2) return vl;
1097 
1098         List ql = LazyList.getList(qual, false);
1099 
1100         // sort list with swaps
1101         Float last = __zero;
1102         for (int i = vl.size(); i-- > 0;)
1103         {
1104             Float q = (Float) ql.get(i);
1105             if (last.compareTo(q) > 0)
1106             {
1107                 Object tmp = vl.get(i);
1108                 vl.set(i, vl.get(i + 1));
1109                 vl.set(i + 1, tmp);
1110                 ql.set(i, ql.get(i + 1));
1111                 ql.set(i + 1, q);
1112                 last = __zero;
1113                 i = vl.size();
1114                 continue;
1115             }
1116             last = q;
1117         }
1118         ql.clear();
1119         return vl;
1120     }
1121 
1122 
1123 }