View Javadoc

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