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