View Javadoc

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