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