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