View Javadoc

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