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