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  import org.eclipse.jetty.util.log.Log;
45  
46  /* ------------------------------------------------------------ */
47  /**
48   * HTTP Fields. A collection of HTTP header and or Trailer fields. 
49   * 
50   * <p>This class is not synchronized as it is expected that modifications will only be performed by a
51   * single thread.
52   * 
53   * 
54   */
55  public class HttpFields
56  {    
57      /* ------------------------------------------------------------ */
58      public static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
59      public final static BufferDateCache __dateCache = new BufferDateCache("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
60  
61      /* -------------------------------------------------------------- */
62      static
63      {
64          __GMT.setID("GMT");
65          __dateCache.setTimeZone(__GMT);
66      }
67      
68      /* ------------------------------------------------------------ */
69      public final static String __separators = ", \t";
70  
71      /* ------------------------------------------------------------ */
72      private static final String[] DAYS =
73      { "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
74      private static final String[] MONTHS =
75      { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"};
76  
77      
78      /* ------------------------------------------------------------ */
79      private static class DateGenerator
80      {
81          private final StringBuilder buf = new StringBuilder(32);
82          private final GregorianCalendar gc = new GregorianCalendar(__GMT);
83          
84          public String formatDate(long date)
85          {
86              buf.setLength(0);
87              gc.setTimeInMillis(date);
88              
89              int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
90              int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
91              int month = gc.get(Calendar.MONTH);
92              int year = gc.get(Calendar.YEAR);
93              int century = year / 100;
94              year = year % 100;
95  
96              int epoch = (int) ((date / 1000) % (60 * 60 * 24));
97              int seconds = epoch % 60;
98              epoch = epoch / 60;
99              int minutes = epoch % 60;
100             int hours = epoch / 60;
101 
102             buf.append(DAYS[day_of_week]);
103             buf.append(',');
104             buf.append(' ');
105             StringUtil.append2digits(buf, day_of_month);
106 
107             buf.append(' ');
108             buf.append(MONTHS[month]);
109             buf.append(' ');
110             StringUtil.append2digits(buf, century);
111             StringUtil.append2digits(buf, year);
112             
113             buf.append(' ');
114             StringUtil.append2digits(buf, hours);
115             buf.append(':');
116             StringUtil.append2digits(buf, minutes);
117             buf.append(':');
118             StringUtil.append2digits(buf, seconds);
119             buf.append(" GMT");
120             return buf.toString();
121         }
122 
123         /* ------------------------------------------------------------ */
124         /**
125          * Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies
126          */
127         public void formatCookieDate(StringBuilder buf, long date)
128         {
129             gc.setTimeInMillis(date);
130             
131             int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
132             int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
133             int month = gc.get(Calendar.MONTH);
134             int year = gc.get(Calendar.YEAR);
135             year = year % 100;
136 
137             int epoch = (int) ((date / 1000) % (60 * 60 * 24));
138             int seconds = epoch % 60;
139             epoch = epoch / 60;
140             int minutes = epoch % 60;
141             int hours = epoch / 60;
142 
143             buf.append(DAYS[day_of_week]);
144             buf.append(',');
145             buf.append(' ');
146             StringUtil.append2digits(buf, day_of_month);
147 
148             buf.append('-');
149             buf.append(MONTHS[month]);
150             buf.append('-');
151             StringUtil.append2digits(buf, year);
152 
153             buf.append(' ');
154             StringUtil.append2digits(buf, hours);
155             buf.append(':');
156             StringUtil.append2digits(buf, minutes);
157             buf.append(':');
158             StringUtil.append2digits(buf, seconds);
159             buf.append(" GMT");
160         }
161 
162 
163     }
164 
165     /* ------------------------------------------------------------ */
166     private static final ThreadLocal<DateGenerator> __dateGenerator =new ThreadLocal<DateGenerator>()
167     {
168         @Override
169         protected DateGenerator initialValue()
170         {
171             return new DateGenerator();
172         }
173     };
174     
175     /* ------------------------------------------------------------ */
176     /**
177      * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" 
178      * cookies
179      */
180     public static String formatDate(long date)
181     {
182         return __dateGenerator.get().formatDate(date);
183     }
184 
185     /* ------------------------------------------------------------ */
186     /**
187      * Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies
188      */
189     public static void formatCookieDate(StringBuilder buf, long date)
190     {
191         __dateGenerator.get().formatCookieDate(buf,date);
192     }
193 
194 
195     /* ------------------------------------------------------------ */
196     private final static String __dateReceiveFmt[] =
197     {   
198         "EEE, dd MMM yyyy HH:mm:ss zzz", 
199         "EEE, dd-MMM-yy HH:mm:ss",
200         "EEE MMM dd HH:mm:ss yyyy",
201 
202         "EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz", 
203         "EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss", 
204         "EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz", 
205         "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", 
206         "MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz",  
207         "EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz", 
208         "EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss",
209     };
210     
211     private static class DateParser
212     {
213         final SimpleDateFormat _dateReceive[]= new SimpleDateFormat[__dateReceiveFmt.length];
214  
215         long parse(final String dateVal)
216         {
217             for (int i = 0; i < _dateReceive.length; i++)
218             {
219                 if (_dateReceive[i] == null)
220                 {
221                     _dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US);
222                     _dateReceive[i].setTimeZone(__GMT);
223                 }
224 
225                 try
226                 {
227                     Date date = (Date) _dateReceive[i].parseObject(dateVal);
228                     return date.getTime();
229                 }
230                 catch (java.lang.Exception e)
231                 {
232                     // Log.ignore(e);
233                 }
234             }
235             
236             if (dateVal.endsWith(" GMT"))
237             {
238                 final String val = dateVal.substring(0, dateVal.length() - 4);
239 
240                 for (int i = 0; i < _dateReceive.length; i++)
241                 {
242                     try
243                     {
244                         Date date = (Date) _dateReceive[i].parseObject(val);
245                         return date.getTime();
246                     }
247                     catch (java.lang.Exception e)
248                     {
249                         // Log.ignore(e);
250                     }
251                 }
252             }    
253             return -1;
254         }
255     }
256 
257     /* ------------------------------------------------------------ */
258     public static long parseDate(String date)
259     {
260         return __dateParser.get().parse(date);
261     }
262 
263     /* ------------------------------------------------------------ */
264     private static final ThreadLocal<DateParser> __dateParser =new ThreadLocal<DateParser>()
265     {
266         @Override
267         protected DateParser initialValue()
268         {
269             return new DateParser();
270         }
271     };
272     
273     
274    
275     
276     
277     
278     public final static String __01Jan1970 = formatDate(0).trim();
279     public final static Buffer __01Jan1970_BUFFER = new ByteArrayBuffer(__01Jan1970);
280 
281     /* -------------------------------------------------------------- */
282     protected final ArrayList<Field> _fields = new ArrayList<Field>(20);
283     protected final HashMap<Buffer,Field> _bufferMap = new HashMap<Buffer,Field>(32);
284     protected int _revision;
285 
286     /* ------------------------------------------------------------ */
287     /**
288      * Constructor.
289      */
290     public HttpFields()
291     {
292     }
293 
294     /* -------------------------------------------------------------- */
295     /**
296      * Get enumeration of header _names. Returns an enumeration of strings representing the header
297      * _names for this request.
298      */
299     public Enumeration<String> getFieldNames()
300     {
301         final int revision=_revision;
302         return new Enumeration<String>()
303         {
304             int i = 0;
305             Field field = null;
306 
307             public boolean hasMoreElements()
308             {
309                 if (field != null) return true;
310                 while (i < _fields.size())
311                 {
312                     Field f = _fields.get(i++);
313                     if (f != null && f._prev == null && f._revision == revision)
314                     {
315                         field = f;
316                         return true;
317                     }
318                 }
319                 return false;
320             }
321 
322             public String nextElement() throws NoSuchElementException
323             {
324                 if (field != null || hasMoreElements())
325                 {
326                     String n = BufferUtil.to8859_1_String(field._name);
327                     field = null;
328                     return n;
329                 }
330                 throw new NoSuchElementException();
331             }
332         };
333     }
334 
335     /* ------------------------------------------------------------ */
336     public int size()
337     {
338         return _fields.size();
339     }
340     
341     /* ------------------------------------------------------------ */
342     /**
343      * Get a Field by index.
344      * @return A Field value or null if the Field value has not been set
345      * for this revision of the fields.
346      */
347     public Field getField(int i)
348     {
349         final Field field = _fields.get(i);
350         if (field._revision!=_revision)
351             return null;
352         return field;
353     }
354 
355     /* ------------------------------------------------------------ */
356     private Field getField(String name)
357     {
358         return _bufferMap.get(HttpHeaders.CACHE.lookup(name));
359     }
360 
361     /* ------------------------------------------------------------ */
362     private Field getField(Buffer name)
363     {
364         return _bufferMap.get(name);
365     }
366 
367     /* ------------------------------------------------------------ */
368     public boolean containsKey(Buffer name)
369     {
370         Field f = getField(name);
371         return (f != null && f._revision == _revision); 
372     }
373 
374     /* ------------------------------------------------------------ */
375     public boolean containsKey(String name)
376     {
377         Field f = getField(name);
378         return (f != null && f._revision == _revision); 
379     }
380 
381     /* -------------------------------------------------------------- */
382     /**
383      * @return the value of a field, or null if not found. For multiple fields of the same name,
384      *         only the first is returned.
385      * @param name the case-insensitive field name
386      */
387     public String getStringField(String name)
388     {
389         // TODO - really reuse strings from previous requests!
390         Field field = getField(name);
391         if (field != null && field._revision == _revision) 
392             return field.getValue();
393         return null;
394     }
395 
396     /* -------------------------------------------------------------- */
397     /**
398      * @return the value of a field, or null if not found. For multiple fields of the same name,
399      *         only the first is returned.
400      * @param name the case-insensitive field name
401      */
402     public String getStringField(Buffer name)
403     {
404         // TODO - really reuse strings from previous requests!
405         Field field = getField(name);
406         if (field != null && field._revision == _revision) 
407             return BufferUtil.to8859_1_String(field._value);
408         return null;
409     }
410 
411     /* -------------------------------------------------------------- */
412     /**
413      * @return the value of a field, or null if not found. For multiple fields of the same name,
414      *         only the first is returned.
415      * @param name the case-insensitive field name
416      */
417     public Buffer get(Buffer name)
418     {
419         Field field = getField(name);
420         if (field != null && field._revision == _revision) 
421             return field._value;
422         return null;
423     }
424 
425     /* -------------------------------------------------------------- */
426     /**
427      * Get multi headers
428      * 
429      * @return Enumeration of the values, or null if no such header.
430      * @param name the case-insensitive field name
431      */
432     public Enumeration<String> getValues(String name)
433     {
434         final Field field = getField(name);
435         if (field == null) 
436             return null;
437         final int revision=_revision;
438 
439         return new Enumeration<String>()
440         {
441             Field f = field;
442 
443             public boolean hasMoreElements()
444             {
445                 while (f != null && f._revision != revision)
446                     f = f._next;
447                 return f != null;
448             }
449 
450             public String nextElement() throws NoSuchElementException
451             {
452                 if (f == null) throw new NoSuchElementException();
453                 Field n = f;
454                 do
455                     f = f._next;
456                 while (f != null && f._revision != revision);
457                 return n.getValue();
458             }
459         };
460     }
461 
462     /* -------------------------------------------------------------- */
463     /**
464      * Get multi headers
465      * 
466      * @return Enumeration of the value Strings, or null if no such header.
467      * @param name the case-insensitive field name
468      */
469     public Enumeration<String> getValues(Buffer name)
470     {
471         final Field field = getField(name);
472         if (field == null) 
473             return null;
474         final int revision=_revision;
475 
476         return new Enumeration<String>()
477         {
478             Field f = field;
479 
480             public boolean hasMoreElements()
481             {
482                 while (f != null && f._revision != revision)
483                     f = f._next;
484                 return f != null;
485             }
486 
487             public String nextElement() throws NoSuchElementException
488             {
489                 if (f == null) throw new NoSuchElementException();
490                 Field n = f;
491                 f = f._next;
492                 while (f != null && f._revision != revision)
493                     f = f._next;
494                 return n.getValue();
495             }
496         };
497     }
498 
499     /* -------------------------------------------------------------- */
500     /**
501      * Get multi field values with separator. The multiple values can be represented as separate
502      * headers of the same name, or by a single header using the separator(s), or a combination of
503      * both. Separators may be quoted.
504      * 
505      * @param name the case-insensitive field name
506      * @param separators String of separators.
507      * @return Enumeration of the values, or null if no such header.
508      */
509     public Enumeration<String> getValues(String name, final String separators)
510     {
511         final Enumeration<String> e = getValues(name);
512         if (e == null) 
513             return null;
514         return new Enumeration<String>()
515         {
516             QuotedStringTokenizer tok = null;
517 
518             public boolean hasMoreElements()
519             {
520                 if (tok != null && tok.hasMoreElements()) return true;
521                 while (e.hasMoreElements())
522                 {
523                     String value = e.nextElement();
524                     tok = new QuotedStringTokenizer(value, separators, false, false);
525                     if (tok.hasMoreElements()) return true;
526                 }
527                 tok = null;
528                 return false;
529             }
530 
531             public String nextElement() throws NoSuchElementException
532             {
533                 if (!hasMoreElements()) throw new NoSuchElementException();
534                 String next = (String) tok.nextElement();
535                 if (next != null) next = next.trim();
536                 return next;
537             }
538         };
539     }
540 
541     /* -------------------------------------------------------------- */
542     /**
543      * Set a field.
544      * 
545      * @param name the name of the field
546      * @param value the value of the field. If null the field is cleared.
547      */
548     public void put(String name, String value)
549     {
550         Buffer n = HttpHeaders.CACHE.lookup(name);
551         Buffer v = null;
552         if (value != null)
553             v = HttpHeaderValues.CACHE.lookup(value);
554         put(n, v, -1);
555     }
556 
557     /* -------------------------------------------------------------- */
558     /**
559      * Set a field.
560      * 
561      * @param name the name of the field
562      * @param value the value of the field. If null the field is cleared.
563      */
564     public void put(Buffer name, String value)
565     {
566         Buffer v = HttpHeaderValues.CACHE.lookup(value);
567         put(name, v, -1);
568     }
569 
570     /* -------------------------------------------------------------- */
571     /**
572      * Set a field.
573      * 
574      * @param name the name of the field
575      * @param value the value of the field. If null the field is cleared.
576      */
577     public void put(Buffer name, Buffer value)
578     {
579         put(name, value, -1);
580     }
581 
582     /* -------------------------------------------------------------- */
583     /**
584      * Set a field.
585      * 
586      * @param name the name of the field
587      * @param value the value of the field. If null the field is cleared.
588      * @param numValue the numeric value of the field (must match value) or -1
589      */
590     public void put(Buffer name, Buffer value, long numValue)
591     {
592         if (value == null)
593         {
594             remove(name);
595             return;
596         }
597 
598         if (!(name instanceof BufferCache.CachedBuffer)) name = HttpHeaders.CACHE.lookup(name);
599 
600         Field field = _bufferMap.get(name);
601 
602         // Look for value to replace.
603         if (field != null)
604         {
605             field.reset(value, numValue, _revision);
606             field = field._next;
607             while (field != null)
608             {
609                 field.clear();
610                 field = field._next;
611             }
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 = _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 = _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;
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             if (path.trim().startsWith("\""))
983                 buf.append(path);
984             else
985                 QuotedStringTokenizer.quoteIfNeeded(buf,path);
986         }
987         if (domain != null && domain.length() > 0)
988         {
989             buf.append(";Domain=");
990             QuotedStringTokenizer.quoteIfNeeded(buf,domain.toLowerCase());
991         }
992 
993         if (maxAge >= 0)
994         {
995             if (version == 0)
996             {
997                 buf.append(";Expires=");
998                 if (maxAge == 0)
999                     buf.append(__01Jan1970);
1000                 else
1001                     formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
1002             }
1003             else
1004             {
1005                 buf.append(";Max-Age=");
1006                 buf.append(maxAge);
1007             }
1008         }
1009         else if (version > 0)
1010         {
1011             buf.append(";Discard");
1012         }
1013 
1014         if (isSecure)
1015             buf.append(";Secure");
1016         if (isHttpOnly) 
1017             buf.append(";HttpOnly");
1018 
1019         // TODO - straight to Buffer?
1020         name_value_params = buf.toString();
1021         put(HttpHeaders.EXPIRES_BUFFER, __01Jan1970_BUFFER);
1022         add(HttpHeaders.SET_COOKIE_BUFFER, new ByteArrayBuffer(name_value_params));
1023     }
1024 
1025     /* -------------------------------------------------------------- */
1026     public void put(Buffer buffer) throws IOException
1027     {
1028         for (int i = 0; i < _fields.size(); i++)
1029         {
1030             Field field = _fields.get(i);
1031             if (field != null && field._revision == _revision) field.put(buffer);
1032         }
1033         BufferUtil.putCRLF(buffer);
1034     }
1035 
1036     /* -------------------------------------------------------------- */
1037     public String toString()
1038     {
1039         try
1040         {
1041             ByteArrayBuffer buffer = new ByteArrayBuffer(4096);
1042             put(buffer);
1043             return BufferUtil.to8859_1_String(buffer);
1044         }
1045         catch (Exception e)
1046         {
1047             Log.warn(e);
1048             return e.toString();
1049         }
1050 
1051     }
1052 
1053     /* ------------------------------------------------------------ */
1054     /**
1055      * Clear the header.
1056      */
1057     public void clear()
1058     {
1059         _revision++;
1060         if (_revision > 1000000)
1061         {
1062             _revision = 0;
1063             for (int i = _fields.size(); i-- > 0;)
1064             {
1065                 Field field = _fields.get(i);
1066                 if (field != null) field.clear();
1067             }
1068         }
1069     }
1070 
1071     /* ------------------------------------------------------------ */
1072     /**
1073      * Destroy the header. Help the garbage collector by null everything that we can.
1074      */
1075     public void destroy()
1076     {
1077         if (_fields != null)
1078         {
1079             for (int i = _fields.size(); i-- > 0;)
1080             {
1081                 Field field = _fields.get(i);
1082                 if (field != null) field.destroy();
1083             }
1084             _fields.clear();
1085         }
1086     }
1087 
1088     /* ------------------------------------------------------------ */
1089     /**
1090      * Add fields from another HttpFields instance. Single valued fields are replaced, while all
1091      * others are added.
1092      * 
1093      * @param fields
1094      */
1095     public void add(HttpFields fields)
1096     {
1097         if (fields == null) return;
1098 
1099         Enumeration e = fields.getFieldNames();
1100         while (e.hasMoreElements())
1101         {
1102             String name = (String) e.nextElement();
1103             Enumeration values = fields.getValues(name);
1104             while (values.hasMoreElements())
1105                 add(name, (String) values.nextElement());
1106         }
1107     }
1108 
1109     /* ------------------------------------------------------------ */
1110     /**
1111      * Get field value parameters. Some field values can have parameters. This method separates the
1112      * value from the parameters and optionally populates a map with the parameters. For example:
1113      * 
1114      * <PRE>
1115      * 
1116      * FieldName : Value ; param1=val1 ; param2=val2
1117      * 
1118      * </PRE>
1119      * 
1120      * @param value The Field value, possibly with parameteres.
1121      * @param parameters A map to populate with the parameters, or null
1122      * @return The value.
1123      */
1124     public static String valueParameters(String value, Map<String,String> parameters)
1125     {
1126         if (value == null) return null;
1127 
1128         int i = value.indexOf(';');
1129         if (i < 0) return value;
1130         if (parameters == null) return value.substring(0, i).trim();
1131 
1132         StringTokenizer tok1 = new QuotedStringTokenizer(value.substring(i), ";", false, true);
1133         while (tok1.hasMoreTokens())
1134         {
1135             String token = tok1.nextToken();
1136             StringTokenizer tok2 = new QuotedStringTokenizer(token, "= ");
1137             if (tok2.hasMoreTokens())
1138             {
1139                 String paramName = tok2.nextToken();
1140                 String paramVal = null;
1141                 if (tok2.hasMoreTokens()) paramVal = tok2.nextToken();
1142                 parameters.put(paramName, paramVal);
1143             }
1144         }
1145 
1146         return value.substring(0, i).trim();
1147     }
1148 
1149     /* ------------------------------------------------------------ */
1150     private static final Float __one = new Float("1.0");
1151     private static final Float __zero = new Float("0.0");
1152     private static final StringMap __qualities = new StringMap();
1153     static
1154     {
1155         __qualities.put(null, __one);
1156         __qualities.put("1.0", __one);
1157         __qualities.put("1", __one);
1158         __qualities.put("0.9", new Float("0.9"));
1159         __qualities.put("0.8", new Float("0.8"));
1160         __qualities.put("0.7", new Float("0.7"));
1161         __qualities.put("0.66", new Float("0.66"));
1162         __qualities.put("0.6", new Float("0.6"));
1163         __qualities.put("0.5", new Float("0.5"));
1164         __qualities.put("0.4", new Float("0.4"));
1165         __qualities.put("0.33", new Float("0.33"));
1166         __qualities.put("0.3", new Float("0.3"));
1167         __qualities.put("0.2", new Float("0.2"));
1168         __qualities.put("0.1", new Float("0.1"));
1169         __qualities.put("0", __zero);
1170         __qualities.put("0.0", __zero);
1171     }
1172 
1173     /* ------------------------------------------------------------ */
1174     public static Float getQuality(String value)
1175     {
1176         if (value == null) return __zero;
1177 
1178         int qe = value.indexOf(";");
1179         if (qe++ < 0 || qe == value.length()) return __one;
1180 
1181         if (value.charAt(qe++) == 'q')
1182         {
1183             qe++;
1184             Map.Entry entry = __qualities.getEntry(value, qe, value.length() - qe);
1185             if (entry != null) return (Float) entry.getValue();
1186         }
1187 
1188         HashMap params = new HashMap(3);
1189         valueParameters(value, params);
1190         String qs = (String) params.get("q");
1191         Float q = (Float) __qualities.get(qs);
1192         if (q == null)
1193         {
1194             try
1195             {
1196                 q = new Float(qs);
1197             }
1198             catch (Exception e)
1199             {
1200                 q = __one;
1201             }
1202         }
1203         return q;
1204     }
1205 
1206     /* ------------------------------------------------------------ */
1207     /**
1208      * List values in quality order.
1209      * 
1210      * @param enum Enumeration of values with quality parameters
1211      * @return values in quality order.
1212      */
1213     public static List qualityList(Enumeration e)
1214     {
1215         if (e == null || !e.hasMoreElements()) return Collections.EMPTY_LIST;
1216 
1217         Object list = null;
1218         Object qual = null;
1219 
1220         // Assume list will be well ordered and just add nonzero
1221         while (e.hasMoreElements())
1222         {
1223             String v = e.nextElement().toString();
1224             Float q = getQuality(v);
1225 
1226             if (q.floatValue() >= 0.001)
1227             {
1228                 list = LazyList.add(list, v);
1229                 qual = LazyList.add(qual, q);
1230             }
1231         }
1232 
1233         List vl = LazyList.getList(list, false);
1234         if (vl.size() < 2) return vl;
1235 
1236         List ql = LazyList.getList(qual, false);
1237 
1238         // sort list with swaps
1239         Float last = __zero;
1240         for (int i = vl.size(); i-- > 0;)
1241         {
1242             Float q = (Float) ql.get(i);
1243             if (last.compareTo(q) > 0)
1244             {
1245                 Object tmp = vl.get(i);
1246                 vl.set(i, vl.get(i + 1));
1247                 vl.set(i + 1, tmp);
1248                 ql.set(i, ql.get(i + 1));
1249                 ql.set(i + 1, q);
1250                 last = __zero;
1251                 i = vl.size();
1252                 continue;
1253             }
1254             last = q;
1255         }
1256         ql.clear();
1257         return vl;
1258     }
1259 
1260     /* ------------------------------------------------------------ */
1261     /* ------------------------------------------------------------ */
1262     /* ------------------------------------------------------------ */
1263     public static final class Field
1264     {
1265         private Buffer _name;
1266         private Buffer _value;
1267         private String _stringValue;
1268         private long _numValue;
1269         private Field _next;
1270         private Field _prev;
1271         private int _revision;
1272 
1273         /* ------------------------------------------------------------ */
1274         private Field(Buffer name, Buffer value, long numValue, int revision)
1275         {
1276             _name = name.asImmutableBuffer();
1277             _value = value.isImmutable() ? value : new View(value);
1278             _next = null;
1279             _prev = null;
1280             _revision = revision;
1281             _numValue = numValue;
1282             _stringValue=null;
1283         }
1284 
1285         /* ------------------------------------------------------------ */
1286         private void clear()
1287         {
1288             _revision = -1;
1289         }
1290 
1291         /* ------------------------------------------------------------ */
1292         private void destroy()
1293         {
1294             _name = null;
1295             _value = null;
1296             _next = null;
1297             _prev = null;
1298             _stringValue=null;
1299         }
1300 
1301         /* ------------------------------------------------------------ */
1302         /**
1303          * Reassign a value to this field. Checks if the string value is the same as that in the char
1304          * array, if so then just reuse existing value.
1305          */
1306         private void reset(Buffer value, long numValue, int revision)
1307         {
1308             _revision = revision;
1309             if (_value == null)
1310             {
1311                 _value = value.isImmutable() ? value : new View(value);
1312                 _numValue = numValue;
1313                 _stringValue=null;
1314             }
1315             else if (value.isImmutable())
1316             {
1317                 _value = value;
1318                 _numValue = numValue;
1319                 _stringValue=null;
1320             }
1321             else
1322             {
1323                 if (_value instanceof View)
1324                     ((View) _value).update(value);
1325                 else
1326                     _value = new View(value);
1327                 _numValue = numValue;
1328                 
1329                 // check to see if string value is still valid.
1330                 if (_stringValue!=null)
1331                 {
1332                     if (_stringValue.length()!=value.length())
1333                         _stringValue=null;
1334                     else
1335                     {
1336                         for (int i=value.length();i-->0;)
1337                         {
1338                             if (value.peek(value.getIndex()+i)!=_stringValue.charAt(i))
1339                             {
1340                                 _stringValue=null;
1341                                 break;
1342                             }
1343                         }
1344                     }
1345                 }
1346             }
1347         }
1348         
1349         
1350 
1351         /* ------------------------------------------------------------ */
1352         public void put(Buffer buffer) throws IOException
1353         {
1354             int o=(_name instanceof CachedBuffer)?((CachedBuffer)_name).getOrdinal():-1;
1355             if (o>=0)
1356                 buffer.put(_name);
1357             else
1358             {
1359                 int s=_name.getIndex();
1360                 int e=_name.putIndex();
1361                 while (s<e)
1362                 {
1363                     byte b=_name.peek(s++);
1364                     switch(b)
1365                     {
1366                         case '\r':
1367                         case '\n':
1368                         case ':' :
1369                             continue;
1370                         default:
1371                             buffer.put(b);
1372                     }
1373                 }
1374             }
1375             
1376             buffer.put((byte) ':');
1377             buffer.put((byte) ' ');
1378             
1379             o=(_value instanceof CachedBuffer)?((CachedBuffer)_value).getOrdinal():-1;
1380             if (o>=0 || _numValue>=0)
1381                 buffer.put(_value);
1382             else
1383             {
1384                 int s=_value.getIndex();
1385                 int e=_value.putIndex();
1386                 while (s<e)
1387                 {
1388                     byte b=_value.peek(s++);
1389                     switch(b)
1390                     {
1391                         case '\r':
1392                         case '\n':
1393                             continue;
1394                         default:
1395                             buffer.put(b);
1396                     }
1397                 }
1398             }
1399 
1400             BufferUtil.putCRLF(buffer);
1401         }
1402 
1403         /* ------------------------------------------------------------ */
1404         public String getName()
1405         {
1406             return BufferUtil.to8859_1_String(_name);
1407         }
1408 
1409         /* ------------------------------------------------------------ */
1410         Buffer getNameBuffer()
1411         {
1412             return _name;
1413         }
1414 
1415         /* ------------------------------------------------------------ */
1416         public int getNameOrdinal()
1417         {
1418             return HttpHeaders.CACHE.getOrdinal(_name);
1419         }
1420 
1421         /* ------------------------------------------------------------ */
1422         public String getValue()
1423         {
1424             if (_stringValue==null)
1425             {
1426                 _stringValue=(_value instanceof CachedBuffer)
1427                     ?_value.toString()
1428                     :BufferUtil.to8859_1_String(_value);
1429             }
1430             return _stringValue;
1431         }
1432 
1433         /* ------------------------------------------------------------ */
1434         public Buffer getValueBuffer()
1435         {
1436             return _value;
1437         }
1438 
1439         /* ------------------------------------------------------------ */
1440         public int getValueOrdinal()
1441         {
1442             return HttpHeaderValues.CACHE.getOrdinal(_value);
1443         }
1444 
1445         /* ------------------------------------------------------------ */
1446         public int getIntValue()
1447         {
1448             return (int) getLongValue();
1449         }
1450 
1451         /* ------------------------------------------------------------ */
1452         public long getLongValue()
1453         {
1454             if (_numValue == -1) _numValue = BufferUtil.toLong(_value);
1455             return _numValue;
1456         }
1457 
1458         /* ------------------------------------------------------------ */
1459         public String toString()
1460         {
1461             return ("[" + (_prev == null ? "" : "<-") + getName() + "="+_revision+"=" + _value + (_next == null ? "" : "->") + "]");
1462         }
1463     }
1464 
1465 }