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