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