View Javadoc

1   // ========================================================================
2   // Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at 
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  // ========================================================================
14  
15  package org.mortbay.jetty;
16  
17  import java.io.IOException;
18  import java.text.SimpleDateFormat;
19  import java.util.ArrayList;
20  import java.util.Calendar;
21  import java.util.Collections;
22  import java.util.Date;
23  import java.util.Enumeration;
24  import java.util.GregorianCalendar;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Locale;
29  import java.util.Map;
30  import java.util.NoSuchElementException;
31  import java.util.StringTokenizer;
32  import java.util.TimeZone;
33  
34  import javax.servlet.http.Cookie;
35  
36  import org.mortbay.io.Buffer;
37  import org.mortbay.io.BufferCache;
38  import org.mortbay.io.BufferDateCache;
39  import org.mortbay.io.BufferUtil;
40  import org.mortbay.io.ByteArrayBuffer;
41  import org.mortbay.io.View;
42  import org.mortbay.io.BufferCache.CachedBuffer;
43  import org.mortbay.util.LazyList;
44  import org.mortbay.util.QuotedStringTokenizer;
45  import org.mortbay.util.StringMap;
46  import org.mortbay.util.StringUtil;
47  import org.mortbay.util.URIUtil;
48  
49  /* ------------------------------------------------------------ */
50  /**
51   * HTTP Fields. A collection of HTTP header and or Trailer fields. This class is not synchronized
52   * and needs to be protected from concurrent access.
53   * 
54   * This class is not synchronized as it is expected that modifications will only be performed by a
55   * single thread.
56   * 
57   * @author Greg Wilkins (gregw)
58   */
59  public class HttpFields
60  {
61      /* ------------------------------------------------------------ */
62      public final static String __separators = ", \t";
63  
64      /* ------------------------------------------------------------ */
65      private static String[] DAYS =
66      { "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
67      private static String[] MONTHS =
68      { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"};
69  
70      /* ------------------------------------------------------------ */
71      /**
72       * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" or "EEE, dd-MMM-yy HH:mm:ss 'GMT'"for
73       * cookies
74       */
75      public static String formatDate(long date, boolean cookie)
76      {
77          StringBuffer buf = new StringBuffer(32);
78          GregorianCalendar gc = new GregorianCalendar(__GMT);
79          gc.setTimeInMillis(date);
80          formatDate(buf, gc, cookie);
81          return buf.toString();
82      }
83  
84      /* ------------------------------------------------------------ */
85      /**
86       * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" or "EEE, dd-MMM-yy HH:mm:ss 'GMT'"for
87       * cookies
88       */
89      public static String formatDate(Calendar calendar, boolean cookie)
90      {
91          StringBuffer buf = new StringBuffer(32);
92          formatDate(buf, calendar, cookie);
93          return buf.toString();
94      }
95  
96      /* ------------------------------------------------------------ */
97      /**
98       * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" or "EEE, dd-MMM-yy HH:mm:ss 'GMT'"for
99       * cookies
100      */
101     public static String formatDate(StringBuffer buf, long date, boolean cookie)
102     {
103         GregorianCalendar gc = new GregorianCalendar(__GMT);
104         gc.setTimeInMillis(date);
105         formatDate(buf, gc, cookie);
106         return buf.toString();
107     }
108 
109     /* ------------------------------------------------------------ */
110     /**
111      * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" or "EEE, dd-MMM-yy HH:mm:ss 'GMT'"for
112      * cookies
113      */
114     public static void formatDate(StringBuffer buf, Calendar calendar, boolean cookie)
115     {
116         // "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
117         // "EEE, dd-MMM-yy HH:mm:ss 'GMT'", cookie
118 
119         int day_of_week = calendar.get(Calendar.DAY_OF_WEEK);
120         int day_of_month = calendar.get(Calendar.DAY_OF_MONTH);
121         int month = calendar.get(Calendar.MONTH);
122         int year = calendar.get(Calendar.YEAR);
123         int century = year / 100;
124         year = year % 100;
125 
126         int epoch = (int) ((calendar.getTimeInMillis() / 1000) % (60 * 60 * 24));
127         int seconds = epoch % 60;
128         epoch = epoch / 60;
129         int minutes = epoch % 60;
130         int hours = epoch / 60;
131 
132         buf.append(DAYS[day_of_week]);
133         buf.append(',');
134         buf.append(' ');
135         StringUtil.append2digits(buf, day_of_month);
136 
137         if (cookie)
138         {
139             buf.append('-');
140             buf.append(MONTHS[month]);
141             buf.append('-');
142             StringUtil.append2digits(buf, century);
143             StringUtil.append2digits(buf, year);
144         }
145         else
146         {
147             buf.append(' ');
148             buf.append(MONTHS[month]);
149             buf.append(' ');
150             StringUtil.append2digits(buf, century);
151             StringUtil.append2digits(buf, year);
152         }
153         buf.append(' ');
154         StringUtil.append2digits(buf, hours);
155         buf.append(':');
156         StringUtil.append2digits(buf, minutes);
157         buf.append(':');
158         StringUtil.append2digits(buf, seconds);
159         buf.append(" GMT");
160     }
161 
162     /* -------------------------------------------------------------- */
163     private static TimeZone __GMT = TimeZone.getTimeZone("GMT");
164     public final static BufferDateCache __dateCache = new BufferDateCache("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
165 
166     /* ------------------------------------------------------------ */
167     private final static String __dateReceiveFmt[] =
168     {   "EEE, dd MMM yyyy HH:mm:ss zzz", 
169         "EEE, dd-MMM-yy HH:mm:ss",
170         "EEE MMM dd HH:mm:ss yyyy",
171         
172         "EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz", 
173         "EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss", 
174         "EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz", 
175         "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", 
176         "MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz",  
177         "EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz", 
178         "EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss",
179       };
180     private  static int __dateReceiveInit=3;
181     private  static SimpleDateFormat __dateReceive[];
182     static
183     {
184         __GMT.setID("GMT");
185         __dateCache.setTimeZone(__GMT);
186         __dateReceive = new SimpleDateFormat[__dateReceiveFmt.length];
187         // Initialize only the standard formats here.
188         for (int i = 0; i < __dateReceiveInit; i++)
189         {
190             __dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US);
191             __dateReceive[i].setTimeZone(__GMT);
192         }
193     }
194     public final static String __01Jan1970 = formatDate(0, true).trim();
195     public final static Buffer __01Jan1970_BUFFER = new ByteArrayBuffer(__01Jan1970);
196 
197     /* -------------------------------------------------------------- */
198     protected ArrayList _fields = new ArrayList(20);
199     protected int _revision;
200     protected HashMap _bufferMap = new HashMap(32);
201     protected SimpleDateFormat _dateReceive[] = new SimpleDateFormat[__dateReceive.length];
202     private StringBuffer _dateBuffer;
203     private Calendar _calendar;
204 
205     /* ------------------------------------------------------------ */
206     /**
207      * Constructor.
208      */
209     public HttpFields()
210     {
211     }
212 
213     /* -------------------------------------------------------------- */
214     /**
215      * Get enumeration of header _names. Returns an enumeration of strings representing the header
216      * _names for this request.
217      */
218     public Enumeration getFieldNames()
219     {
220         final int revision=_revision;
221         return new Enumeration()
222         {
223             int i = 0;
224             Field field = null;
225 
226             public boolean hasMoreElements()
227             {
228                 if (field != null) return true;
229                 while (i < _fields.size())
230                 {
231                     Field f = (Field) _fields.get(i++);
232                     if (f != null && f._prev == null && f._revision == revision)
233                     {
234                         field = f;
235                         return true;
236                     }
237                 }
238                 return false;
239             }
240 
241             public Object nextElement() throws NoSuchElementException
242             {
243                 if (field != null || hasMoreElements())
244                 {
245                     String n = BufferUtil.to8859_1_String(field._name);
246                     field = null;
247                     return n;
248                 }
249                 throw new NoSuchElementException();
250             }
251         };
252     }
253 
254     /* -------------------------------------------------------------- */
255     /**
256      * Get enumeration of Fields Returns an enumeration of Fields for this request.
257      */
258     public Iterator getFields()
259     {
260         final int revision=_revision;
261         return new Iterator()
262         {
263             int i = 0;
264             Field field = null;
265 
266             public boolean hasNext()
267             {
268                 if (field != null) return true;
269                 while (i < _fields.size())
270                 {
271                     Field f = (Field) _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                 if (path.startsWith("\""))
987                     buf.append(path);
988                 else
989                     QuotedStringTokenizer.quoteIfNeeded(buf,path);
990             }
991             String domain = cookie.getDomain();
992             if (domain != null && domain.length() > 0)
993             {
994                 buf.append(";Domain=");
995                 QuotedStringTokenizer.quoteIfNeeded(buf,domain.toLowerCase());
996             }
997 
998             long maxAge = cookie.getMaxAge();
999             if (maxAge >= 0)
1000             {
1001                 if (version == 0)
1002                 {
1003                     buf.append(";Expires=");
1004                     if (maxAge == 0)
1005                         buf.append(__01Jan1970);
1006                     else
1007                         formatDate(buf, System.currentTimeMillis() + 1000L * maxAge, true);
1008                 }
1009                 else
1010                 {
1011                     buf.append(";Max-Age=");
1012                     buf.append(maxAge);
1013                 }
1014             }
1015             else if (version > 0)
1016             {
1017                 buf.append(";Discard");
1018             }
1019 
1020             if (cookie.getSecure())
1021             {
1022                 buf.append(";Secure");
1023             }
1024             if (cookie instanceof HttpOnlyCookie)
1025                 buf.append(";HttpOnly");
1026 
1027             // TODO - straight to Buffer?
1028             name_value_params = buf.toString();
1029         }
1030         put(HttpHeaders.EXPIRES_BUFFER, __01Jan1970_BUFFER);
1031         add(HttpHeaders.SET_COOKIE_BUFFER, new ByteArrayBuffer(name_value_params));
1032     }
1033 
1034     /* -------------------------------------------------------------- */
1035     public void put(Buffer buffer) throws IOException
1036     {
1037         for (int i = 0; i < _fields.size(); i++)
1038         {
1039             Field field = (Field) _fields.get(i);
1040             if (field != null && field._revision == _revision) field.put(buffer);
1041         }
1042         BufferUtil.putCRLF(buffer);
1043     }
1044 
1045     /* -------------------------------------------------------------- */
1046     public String toString()
1047     {
1048         try
1049         {
1050             StringBuffer buffer = new StringBuffer();
1051           
1052             for (int i = 0; i < _fields.size(); i++)
1053             {
1054                 Field field = (Field) _fields.get(i);
1055                 if (field != null && field._revision == _revision)
1056                 {
1057                     String tmp = field.getName();
1058                     if (tmp != null) buffer.append(tmp);
1059                     buffer.append(": ");
1060                     tmp = field.getValue();
1061                     if (tmp != null) buffer.append(tmp);
1062                     buffer.append("\r\n");
1063                 }
1064             } 
1065             buffer.append("\r\n");
1066             return buffer.toString();
1067             
1068         }
1069         catch (Exception e)
1070         {
1071             e.printStackTrace();
1072         }
1073 
1074         return null;
1075     }
1076 
1077     /* ------------------------------------------------------------ */
1078     /**
1079      * Clear the header.
1080      */
1081     public void clear()
1082     {
1083         _revision++;
1084         if (_revision > 1000000)
1085         {
1086             _revision = 0;
1087             for (int i = _fields.size(); i-- > 0;)
1088             {
1089                 Field field = (Field) _fields.get(i);
1090                 if (field != null) field.clear();
1091             }
1092         }
1093     }
1094 
1095     /* ------------------------------------------------------------ */
1096     /**
1097      * Destroy the header. Help the garbage collector by null everything that we can.
1098      */
1099     public void destroy()
1100     {
1101         if (_fields != null)
1102         {
1103             for (int i = _fields.size(); i-- > 0;)
1104             {
1105                 Field field = (Field) _fields.get(i);
1106                 if (field != null) {
1107                     _bufferMap.remove(field.getNameBuffer());
1108                     field.destroy();
1109                 }
1110             }
1111         }
1112         _fields = null;
1113         _dateBuffer = null;
1114         _calendar = null;
1115         _dateReceive = null;
1116     }
1117 
1118     /* ------------------------------------------------------------ */
1119     /**
1120      * Add fields from another HttpFields instance. Single valued fields are replaced, while all
1121      * others are added.
1122      * 
1123      * @param fields
1124      */
1125     public void add(HttpFields fields)
1126     {
1127         if (fields == null) return;
1128 
1129         Enumeration e = fields.getFieldNames();
1130         while (e.hasMoreElements())
1131         {
1132             String name = (String) e.nextElement();
1133             Enumeration values = fields.getValues(name);
1134             while (values.hasMoreElements())
1135                 add(name, (String) values.nextElement());
1136         }
1137     }
1138 
1139     /* ------------------------------------------------------------ */
1140     /**
1141      * Get field value parameters. Some field values can have parameters. This method separates the
1142      * value from the parameters and optionally populates a map with the paramters. For example:
1143      * 
1144      * <PRE>
1145      * 
1146      * FieldName : Value ; param1=val1 ; param2=val2
1147      * 
1148      * </PRE>
1149      * 
1150      * @param value The Field value, possibly with parameteres.
1151      * @param parameters A map to populate with the parameters, or null
1152      * @return The value.
1153      */
1154     public static String valueParameters(String value, Map parameters)
1155     {
1156         if (value == null) return null;
1157 
1158         int i = value.indexOf(';');
1159         if (i < 0) return value;
1160         if (parameters == null) return value.substring(0, i).trim();
1161 
1162         StringTokenizer tok1 = new QuotedStringTokenizer(value.substring(i), ";", false, true);
1163         while (tok1.hasMoreTokens())
1164         {
1165             String token = tok1.nextToken();
1166             StringTokenizer tok2 = new QuotedStringTokenizer(token, "= ");
1167             if (tok2.hasMoreTokens())
1168             {
1169                 String paramName = tok2.nextToken();
1170                 String paramVal = null;
1171                 if (tok2.hasMoreTokens()) paramVal = tok2.nextToken();
1172                 parameters.put(paramName, paramVal);
1173             }
1174         }
1175 
1176         return value.substring(0, i).trim();
1177     }
1178 
1179     /* ------------------------------------------------------------ */
1180     private static Float __one = new Float("1.0");
1181     private static Float __zero = new Float("0.0");
1182     private static StringMap __qualities = new StringMap();
1183     static
1184     {
1185         __qualities.put(null, __one);
1186         __qualities.put("1.0", __one);
1187         __qualities.put("1", __one);
1188         __qualities.put("0.9", new Float("0.9"));
1189         __qualities.put("0.8", new Float("0.8"));
1190         __qualities.put("0.7", new Float("0.7"));
1191         __qualities.put("0.66", new Float("0.66"));
1192         __qualities.put("0.6", new Float("0.6"));
1193         __qualities.put("0.5", new Float("0.5"));
1194         __qualities.put("0.4", new Float("0.4"));
1195         __qualities.put("0.33", new Float("0.33"));
1196         __qualities.put("0.3", new Float("0.3"));
1197         __qualities.put("0.2", new Float("0.2"));
1198         __qualities.put("0.1", new Float("0.1"));
1199         __qualities.put("0", __zero);
1200         __qualities.put("0.0", __zero);
1201     }
1202 
1203     /* ------------------------------------------------------------ */
1204     public static Float getQuality(String value)
1205     {
1206         if (value == null) return __zero;
1207 
1208         int qe = value.indexOf(";");
1209         if (qe++ < 0 || qe == value.length()) return __one;
1210 
1211         if (value.charAt(qe++) == 'q')
1212         {
1213             qe++;
1214             Map.Entry entry = __qualities.getEntry(value, qe, value.length() - qe);
1215             if (entry != null) return (Float) entry.getValue();
1216         }
1217 
1218         HashMap params = new HashMap(3);
1219         valueParameters(value, params);
1220         String qs = (String) params.get("q");
1221         Float q = (Float) __qualities.get(qs);
1222         if (q == null)
1223         {
1224             try
1225             {
1226                 q = new Float(qs);
1227             }
1228             catch (Exception e)
1229             {
1230                 q = __one;
1231             }
1232         }
1233         return q;
1234     }
1235 
1236     /* ------------------------------------------------------------ */
1237     /**
1238      * List values in quality order.
1239      * 
1240      * @param enum Enumeration of values with quality parameters
1241      * @return values in quality order.
1242      */
1243     public static List qualityList(Enumeration e)
1244     {
1245         if (e == null || !e.hasMoreElements()) return Collections.EMPTY_LIST;
1246 
1247         Object list = null;
1248         Object qual = null;
1249 
1250         // Assume list will be well ordered and just add nonzero
1251         while (e.hasMoreElements())
1252         {
1253             String v = e.nextElement().toString();
1254             Float q = getQuality(v);
1255 
1256             if (q.floatValue() >= 0.001)
1257             {
1258                 list = LazyList.add(list, v);
1259                 qual = LazyList.add(qual, q);
1260             }
1261         }
1262 
1263         List vl = LazyList.getList(list, false);
1264         if (vl.size() < 2) return vl;
1265 
1266         List ql = LazyList.getList(qual, false);
1267 
1268         // sort list with swaps
1269         Float last = __zero;
1270         for (int i = vl.size(); i-- > 0;)
1271         {
1272             Float q = (Float) ql.get(i);
1273             if (last.compareTo(q) > 0)
1274             {
1275                 Object tmp = vl.get(i);
1276                 vl.set(i, vl.get(i + 1));
1277                 vl.set(i + 1, tmp);
1278                 ql.set(i, ql.get(i + 1));
1279                 ql.set(i + 1, q);
1280                 last = __zero;
1281                 i = vl.size();
1282                 continue;
1283             }
1284             last = q;
1285         }
1286         ql.clear();
1287         return vl;
1288     }
1289 
1290     /* ------------------------------------------------------------ */
1291     /* ------------------------------------------------------------ */
1292     /* ------------------------------------------------------------ */
1293     public static final class Field
1294     {
1295         private Buffer _name;
1296         private Buffer _value;
1297         private String _stringValue;
1298         private long _numValue;
1299         private Field _next;
1300         private Field _prev;
1301         private int _revision;
1302 
1303         /* ------------------------------------------------------------ */
1304         private Field(Buffer name, Buffer value, long numValue, int revision)
1305         {
1306             _name = name.asImmutableBuffer();
1307             _value = value.isImmutable() ? value : new View(value);
1308             _next = null;
1309             _prev = null;
1310             _revision = revision;
1311             _numValue = numValue;
1312             _stringValue=null;
1313         }
1314 
1315         /* ------------------------------------------------------------ */
1316         private void clear()
1317         {
1318             _revision = -1;
1319         }
1320 
1321         /* ------------------------------------------------------------ */
1322         private void destroy()
1323         {
1324             _name = null;
1325             _value = null;
1326             _next = null;
1327             _prev = null;
1328             _stringValue=null;
1329         }
1330 
1331         /* ------------------------------------------------------------ */
1332         /**
1333          * Reassign a value to this field. Checks if the string value is the same as that in the char
1334          * array, if so then just reuse existing value.
1335          */
1336         private void reset(Buffer value, long numValue, int revision)
1337         {
1338             _revision = revision;
1339             if (_value == null)
1340             {
1341                 _value = value.isImmutable() ? value : new View(value);
1342                 _numValue = numValue;
1343                 _stringValue=null;
1344             }
1345             else if (value.isImmutable())
1346             {
1347                 _value = value;
1348                 _numValue = numValue;
1349                 _stringValue=null;
1350             }
1351             else
1352             {
1353                 if (_value instanceof View)
1354                     ((View) _value).update(value);
1355                 else
1356                     _value = new View(value);
1357                 _numValue = numValue;
1358                 
1359                 // check to see if string value is still valid.
1360                 if (_stringValue!=null)
1361                 {
1362                     if (_stringValue.length()!=value.length())
1363                         _stringValue=null;
1364                     else
1365                     {
1366                         for (int i=value.length();i-->0;)
1367                         {
1368                             if (value.peek(value.getIndex()+i)!=_stringValue.charAt(i))
1369                             {
1370                                 _stringValue=null;
1371                                 break;
1372                             }
1373                         }
1374                     }
1375                 }
1376             }
1377         }
1378         
1379         
1380 
1381         /* ------------------------------------------------------------ */
1382         public void put(Buffer buffer) throws IOException
1383         {
1384             int o=(_name instanceof CachedBuffer)?((CachedBuffer)_name).getOrdinal():-1;
1385             if (o>=0)
1386                 buffer.put(_name);
1387             else
1388             {
1389                 int s=_name.getIndex();
1390                 int e=_name.putIndex();
1391                 while (s<e)
1392                 {
1393                     byte b=_name.peek(s++);
1394                     switch(b)
1395                     {
1396                         case '\r':
1397                         case '\n':
1398                         case ':' :
1399                             continue;
1400                         default:
1401                             buffer.put(b);
1402                     }
1403                 }
1404             }
1405             
1406             buffer.put((byte) ':');
1407             buffer.put((byte) ' ');
1408             
1409             o=(_value instanceof CachedBuffer)?((CachedBuffer)_value).getOrdinal():-1;
1410             if (o>=0 || _numValue>=0)
1411                 buffer.put(_value);
1412             else
1413             {
1414                 int s=_value.getIndex();
1415                 int e=_value.putIndex();
1416                 while (s<e)
1417                 {
1418                     byte b=_value.peek(s++);
1419                     switch(b)
1420                     {
1421                         case '\r':
1422                         case '\n':
1423                             continue;
1424                         default:
1425                             buffer.put(b);
1426                     }
1427                 }
1428             }
1429 
1430             BufferUtil.putCRLF(buffer);
1431         }
1432 
1433         /* ------------------------------------------------------------ */
1434         public String getName()
1435         {
1436             return BufferUtil.to8859_1_String(_name);
1437         }
1438 
1439         /* ------------------------------------------------------------ */
1440         Buffer getNameBuffer()
1441         {
1442             return _name;
1443         }
1444 
1445         /* ------------------------------------------------------------ */
1446         public int getNameOrdinal()
1447         {
1448             return HttpHeaders.CACHE.getOrdinal(_name);
1449         }
1450 
1451         /* ------------------------------------------------------------ */
1452         public String getValue()
1453         {
1454             if (_stringValue==null)
1455                 _stringValue=BufferUtil.to8859_1_String(_value);
1456             return _stringValue;
1457         }
1458 
1459         /* ------------------------------------------------------------ */
1460         public Buffer getValueBuffer()
1461         {
1462             return _value;
1463         }
1464 
1465         /* ------------------------------------------------------------ */
1466         public int getValueOrdinal()
1467         {
1468             return HttpHeaderValues.CACHE.getOrdinal(_value);
1469         }
1470 
1471         /* ------------------------------------------------------------ */
1472         public int getIntValue()
1473         {
1474             return (int) getLongValue();
1475         }
1476 
1477         /* ------------------------------------------------------------ */
1478         public long getLongValue()
1479         {
1480             if (_numValue == -1) _numValue = BufferUtil.toLong(_value);
1481             return _numValue;
1482         }
1483 
1484         /* ------------------------------------------------------------ */
1485         public String toString()
1486         {
1487             return ("[" + (_prev == null ? "" : "<-") + getName() + "="+_revision+"=" + _value + (_next == null ? "" : "->") + "]");
1488         }
1489     }
1490 
1491 }