View Javadoc

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