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