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