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==-1)
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             StringBuffer buffer = new StringBuffer();
1042             for (int i = 0; i < _fields.size(); i++)
1043             {
1044                 Field field = (Field) _fields.get(i);
1045                 if (field != null && field._revision == _revision)
1046                 {
1047                     String tmp = field.getName();
1048                     if (tmp != null) buffer.append(tmp);
1049                     buffer.append(": ");
1050                     tmp = field.getValue();
1051                     if (tmp != null) buffer.append(tmp);
1052                     buffer.append("\r\n");
1053                 }
1054             }
1055             buffer.append("\r\n");
1056             return buffer.toString();
1057         }
1058         catch (Exception e)
1059         {
1060             Log.warn(e);
1061             return e.toString();
1062         }
1063     }
1064 
1065     /* ------------------------------------------------------------ */
1066     /**
1067      * Clear the header.
1068      */
1069     public void clear()
1070     {
1071         _revision++;
1072         if (_revision > 1000000)
1073         {
1074             _revision = 0;
1075             for (int i = _fields.size(); i-- > 0;)
1076             {
1077                 Field field = _fields.get(i);
1078                 if (field != null) field.clear();
1079             }
1080         }
1081     }
1082 
1083     /* ------------------------------------------------------------ */
1084     /**
1085      * Destroy the header. Help the garbage collector by null everything that we can.
1086      */
1087     public void destroy()
1088     {
1089         if (_fields != null)
1090         {
1091             for (int i = _fields.size(); i-- > 0;)
1092             {
1093                 Field field = _fields.get(i);
1094                 if (field != null) {
1095                     _bufferMap.remove(field.getNameBuffer());
1096                     field.destroy();
1097                 }
1098             }
1099             _fields.clear();
1100         }
1101     }
1102 
1103     /* ------------------------------------------------------------ */
1104     /**
1105      * Add fields from another HttpFields instance. Single valued fields are replaced, while all
1106      * others are added.
1107      * 
1108      * @param fields
1109      */
1110     public void add(HttpFields fields)
1111     {
1112         if (fields == null) return;
1113 
1114         Enumeration e = fields.getFieldNames();
1115         while (e.hasMoreElements())
1116         {
1117             String name = (String) e.nextElement();
1118             Enumeration values = fields.getValues(name);
1119             while (values.hasMoreElements())
1120                 add(name, (String) values.nextElement());
1121         }
1122     }
1123 
1124     /* ------------------------------------------------------------ */
1125     /**
1126      * Get field value parameters. Some field values can have parameters. This method separates the
1127      * value from the parameters and optionally populates a map with the parameters. For example:
1128      * 
1129      * <PRE>
1130      * 
1131      * FieldName : Value ; param1=val1 ; param2=val2
1132      * 
1133      * </PRE>
1134      * 
1135      * @param value The Field value, possibly with parameteres.
1136      * @param parameters A map to populate with the parameters, or null
1137      * @return The value.
1138      */
1139     public static String valueParameters(String value, Map<String,String> parameters)
1140     {
1141         if (value == null) return null;
1142 
1143         int i = value.indexOf(';');
1144         if (i < 0) return value;
1145         if (parameters == null) return value.substring(0, i).trim();
1146 
1147         StringTokenizer tok1 = new QuotedStringTokenizer(value.substring(i), ";", false, true);
1148         while (tok1.hasMoreTokens())
1149         {
1150             String token = tok1.nextToken();
1151             StringTokenizer tok2 = new QuotedStringTokenizer(token, "= ");
1152             if (tok2.hasMoreTokens())
1153             {
1154                 String paramName = tok2.nextToken();
1155                 String paramVal = null;
1156                 if (tok2.hasMoreTokens()) paramVal = tok2.nextToken();
1157                 parameters.put(paramName, paramVal);
1158             }
1159         }
1160 
1161         return value.substring(0, i).trim();
1162     }
1163 
1164     /* ------------------------------------------------------------ */
1165     private static final Float __one = new Float("1.0");
1166     private static final Float __zero = new Float("0.0");
1167     private static final StringMap __qualities = new StringMap();
1168     static
1169     {
1170         __qualities.put(null, __one);
1171         __qualities.put("1.0", __one);
1172         __qualities.put("1", __one);
1173         __qualities.put("0.9", new Float("0.9"));
1174         __qualities.put("0.8", new Float("0.8"));
1175         __qualities.put("0.7", new Float("0.7"));
1176         __qualities.put("0.66", new Float("0.66"));
1177         __qualities.put("0.6", new Float("0.6"));
1178         __qualities.put("0.5", new Float("0.5"));
1179         __qualities.put("0.4", new Float("0.4"));
1180         __qualities.put("0.33", new Float("0.33"));
1181         __qualities.put("0.3", new Float("0.3"));
1182         __qualities.put("0.2", new Float("0.2"));
1183         __qualities.put("0.1", new Float("0.1"));
1184         __qualities.put("0", __zero);
1185         __qualities.put("0.0", __zero);
1186     }
1187 
1188     /* ------------------------------------------------------------ */
1189     public static Float getQuality(String value)
1190     {
1191         if (value == null) return __zero;
1192 
1193         int qe = value.indexOf(";");
1194         if (qe++ < 0 || qe == value.length()) return __one;
1195 
1196         if (value.charAt(qe++) == 'q')
1197         {
1198             qe++;
1199             Map.Entry entry = __qualities.getEntry(value, qe, value.length() - qe);
1200             if (entry != null) return (Float) entry.getValue();
1201         }
1202 
1203         HashMap params = new HashMap(3);
1204         valueParameters(value, params);
1205         String qs = (String) params.get("q");
1206         Float q = (Float) __qualities.get(qs);
1207         if (q == null)
1208         {
1209             try
1210             {
1211                 q = new Float(qs);
1212             }
1213             catch (Exception e)
1214             {
1215                 q = __one;
1216             }
1217         }
1218         return q;
1219     }
1220 
1221     /* ------------------------------------------------------------ */
1222     /**
1223      * List values in quality order.
1224      * 
1225      * @param enum Enumeration of values with quality parameters
1226      * @return values in quality order.
1227      */
1228     public static List qualityList(Enumeration e)
1229     {
1230         if (e == null || !e.hasMoreElements()) return Collections.EMPTY_LIST;
1231 
1232         Object list = null;
1233         Object qual = null;
1234 
1235         // Assume list will be well ordered and just add nonzero
1236         while (e.hasMoreElements())
1237         {
1238             String v = e.nextElement().toString();
1239             Float q = getQuality(v);
1240 
1241             if (q.floatValue() >= 0.001)
1242             {
1243                 list = LazyList.add(list, v);
1244                 qual = LazyList.add(qual, q);
1245             }
1246         }
1247 
1248         List vl = LazyList.getList(list, false);
1249         if (vl.size() < 2) return vl;
1250 
1251         List ql = LazyList.getList(qual, false);
1252 
1253         // sort list with swaps
1254         Float last = __zero;
1255         for (int i = vl.size(); i-- > 0;)
1256         {
1257             Float q = (Float) ql.get(i);
1258             if (last.compareTo(q) > 0)
1259             {
1260                 Object tmp = vl.get(i);
1261                 vl.set(i, vl.get(i + 1));
1262                 vl.set(i + 1, tmp);
1263                 ql.set(i, ql.get(i + 1));
1264                 ql.set(i + 1, q);
1265                 last = __zero;
1266                 i = vl.size();
1267                 continue;
1268             }
1269             last = q;
1270         }
1271         ql.clear();
1272         return vl;
1273     }
1274 
1275     /* ------------------------------------------------------------ */
1276     /* ------------------------------------------------------------ */
1277     /* ------------------------------------------------------------ */
1278     public static final class Field
1279     {
1280         private Buffer _name;
1281         private Buffer _value;
1282         private String _stringValue;
1283         private long _numValue;
1284         private Field _next;
1285         private Field _prev;
1286         private int _revision;
1287 
1288         /* ------------------------------------------------------------ */
1289         private Field(Buffer name, Buffer value, long numValue, int revision)
1290         {
1291             _name = name.asImmutableBuffer();
1292             _value = value.isImmutable() ? value : new View(value);
1293             _next = null;
1294             _prev = null;
1295             _revision = revision;
1296             _numValue = numValue;
1297             _stringValue=null;
1298         }
1299 
1300         /* ------------------------------------------------------------ */
1301         private void clear()
1302         {
1303             _revision = -1;
1304         }
1305 
1306         /* ------------------------------------------------------------ */
1307         private void destroy()
1308         {
1309             _name = null;
1310             _value = null;
1311             _next = null;
1312             _prev = null;
1313             _stringValue=null;
1314         }
1315 
1316         /* ------------------------------------------------------------ */
1317         /**
1318          * Reassign a value to this field. Checks if the string value is the same as that in the char
1319          * array, if so then just reuse existing value.
1320          */
1321         private void reset(Buffer value, long numValue, int revision)
1322         {
1323             _revision = revision;
1324             if (_value == null)
1325             {
1326                 _value = value.isImmutable() ? value : new View(value);
1327                 _numValue = numValue;
1328                 _stringValue=null;
1329             }
1330             else if (value.isImmutable())
1331             {
1332                 _value = value;
1333                 _numValue = numValue;
1334                 _stringValue=null;
1335             }
1336             else
1337             {
1338                 if (_value instanceof View)
1339                     ((View) _value).update(value);
1340                 else
1341                     _value = new View(value);
1342                 _numValue = numValue;
1343                 
1344                 // check to see if string value is still valid.
1345                 if (_stringValue!=null)
1346                 {
1347                     if (_stringValue.length()!=value.length())
1348                         _stringValue=null;
1349                     else
1350                     {
1351                         for (int i=value.length();i-->0;)
1352                         {
1353                             if (value.peek(value.getIndex()+i)!=_stringValue.charAt(i))
1354                             {
1355                                 _stringValue=null;
1356                                 break;
1357                             }
1358                         }
1359                     }
1360                 }
1361             }
1362         }
1363         
1364         
1365 
1366         /* ------------------------------------------------------------ */
1367         public void put(Buffer buffer) throws IOException
1368         {
1369             int o=(_name instanceof CachedBuffer)?((CachedBuffer)_name).getOrdinal():-1;
1370             if (o>=0)
1371                 buffer.put(_name);
1372             else
1373             {
1374                 int s=_name.getIndex();
1375                 int e=_name.putIndex();
1376                 while (s<e)
1377                 {
1378                     byte b=_name.peek(s++);
1379                     switch(b)
1380                     {
1381                         case '\r':
1382                         case '\n':
1383                         case ':' :
1384                             continue;
1385                         default:
1386                             buffer.put(b);
1387                     }
1388                 }
1389             }
1390             
1391             buffer.put((byte) ':');
1392             buffer.put((byte) ' ');
1393             
1394             o=(_value instanceof CachedBuffer)?((CachedBuffer)_value).getOrdinal():-1;
1395             if (o>=0 || _numValue>=0)
1396                 buffer.put(_value);
1397             else
1398             {
1399                 int s=_value.getIndex();
1400                 int e=_value.putIndex();
1401                 while (s<e)
1402                 {
1403                     byte b=_value.peek(s++);
1404                     switch(b)
1405                     {
1406                         case '\r':
1407                         case '\n':
1408                             continue;
1409                         default:
1410                             buffer.put(b);
1411                     }
1412                 }
1413             }
1414 
1415             BufferUtil.putCRLF(buffer);
1416         }
1417 
1418         /* ------------------------------------------------------------ */
1419         public String getName()
1420         {
1421             return BufferUtil.to8859_1_String(_name);
1422         }
1423 
1424         /* ------------------------------------------------------------ */
1425         Buffer getNameBuffer()
1426         {
1427             return _name;
1428         }
1429 
1430         /* ------------------------------------------------------------ */
1431         public int getNameOrdinal()
1432         {
1433             return HttpHeaders.CACHE.getOrdinal(_name);
1434         }
1435 
1436         /* ------------------------------------------------------------ */
1437         public String getValue()
1438         {
1439             if (_stringValue==null)
1440             {
1441                 _stringValue=(_value instanceof CachedBuffer)
1442                     ?_value.toString()
1443                     :BufferUtil.to8859_1_String(_value);
1444             }
1445             return _stringValue;
1446         }
1447 
1448         /* ------------------------------------------------------------ */
1449         public Buffer getValueBuffer()
1450         {
1451             return _value;
1452         }
1453 
1454         /* ------------------------------------------------------------ */
1455         public int getValueOrdinal()
1456         {
1457             return HttpHeaderValues.CACHE.getOrdinal(_value);
1458         }
1459 
1460         /* ------------------------------------------------------------ */
1461         public int getIntValue()
1462         {
1463             return (int) getLongValue();
1464         }
1465 
1466         /* ------------------------------------------------------------ */
1467         public long getLongValue()
1468         {
1469             if (_numValue == -1) _numValue = BufferUtil.toLong(_value);
1470             return _numValue;
1471         }
1472 
1473         /* ------------------------------------------------------------ */
1474         public String toString()
1475         {
1476             return ("[" + (_prev == null ? "" : "<-") + getName() + "="+_revision+"=" + _value + (_next == null ? "" : "->") + "]");
1477         }
1478     }
1479 
1480 }