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      * @param cookie2 If true, use the alternate cookie 2 header
940      */
941     public void addSetCookie(HttpCookie cookie)
942     {
943         addSetCookie(
944                 cookie.getName(),
945                 cookie.getValue(),
946                 cookie.getDomain(),
947                 cookie.getPath(),
948                 cookie.getMaxAge(),
949                 cookie.getComment(),
950                 cookie.isSecure(),
951                 cookie.isHttpOnly(),
952                 cookie.getVersion());
953     }
954     
955     /* ------------------------------------------------------------ */
956     /**
957      * Format a set cookie value
958      * @param cookie The cookie.
959      * @param cookie2 If true, use the alternate cookie 2 header
960      */
961     public void addSetCookie(
962             final String name, 
963             final String value, 
964             final String domain,
965             final String path, 
966             final long maxAge,
967             final String comment, 
968             final boolean isSecure,
969             final boolean isHttpOnly, 
970             final int version)
971     {
972         // Check arguments
973         if (name == null || name.length() == 0) throw new IllegalArgumentException("Bad cookie name");
974 
975         // Format value and params
976         StringBuilder buf = new StringBuilder(128);
977         String name_value_params;
978         QuotedStringTokenizer.quoteIfNeeded(buf, name);
979         buf.append('=');
980         if (value != null && value.length() > 0)
981             QuotedStringTokenizer.quoteIfNeeded(buf, value);
982 
983         if (version > 0)
984         {
985             buf.append(";Version=");
986             buf.append(version);
987             if (comment != null && comment.length() > 0)
988             {
989                 buf.append(";Comment=");
990                 QuotedStringTokenizer.quoteIfNeeded(buf, comment);
991             }
992         }
993         if (path != null && path.length() > 0)
994         {
995             buf.append(";Path=");
996             if (path.trim().startsWith("\""))
997                 buf.append(path);
998             else
999                 QuotedStringTokenizer.quoteIfNeeded(buf,path);
1000         }
1001         if (domain != null && domain.length() > 0)
1002         {
1003             buf.append(";Domain=");
1004             QuotedStringTokenizer.quoteIfNeeded(buf,domain.toLowerCase());
1005         }
1006 
1007         if (maxAge >= 0)
1008         {
1009             if (version == 0)
1010             {
1011                 buf.append(";Expires=");
1012                 if (maxAge == 0)
1013                     buf.append(__01Jan1970);
1014                 else
1015                     formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
1016             }
1017             else
1018             {
1019                 buf.append(";Max-Age=");
1020                 buf.append(maxAge);
1021             }
1022         }
1023         else if (version > 0)
1024         {
1025             buf.append(";Discard");
1026         }
1027 
1028         if (isSecure)
1029             buf.append(";Secure");
1030         if (isHttpOnly) 
1031             buf.append(";HttpOnly");
1032 
1033         // TODO - straight to Buffer?
1034         name_value_params = buf.toString();
1035         put(HttpHeaders.EXPIRES_BUFFER, __01Jan1970_BUFFER);
1036         add(HttpHeaders.SET_COOKIE_BUFFER, new ByteArrayBuffer(name_value_params));
1037     }
1038 
1039     /* -------------------------------------------------------------- */
1040     public void put(Buffer buffer) throws IOException
1041     {
1042         for (int i = 0; i < _fields.size(); i++)
1043         {
1044             Field field = _fields.get(i);
1045             if (field != null && field._revision == _revision) field.put(buffer);
1046         }
1047         BufferUtil.putCRLF(buffer);
1048     }
1049 
1050     /* -------------------------------------------------------------- */
1051     public String toString()
1052     {
1053         try
1054         {
1055             StringBuffer buffer = new StringBuffer();
1056             for (int i = 0; i < _fields.size(); i++)
1057             {
1058                 Field field = (Field) _fields.get(i);
1059                 if (field != null && field._revision == _revision)
1060                 {
1061                     String tmp = field.getName();
1062                     if (tmp != null) buffer.append(tmp);
1063                     buffer.append(": ");
1064                     tmp = field.getValue();
1065                     if (tmp != null) buffer.append(tmp);
1066                     buffer.append("\r\n");
1067                 }
1068             }
1069             buffer.append("\r\n");
1070             return buffer.toString();
1071         }
1072         catch (Exception e)
1073         {
1074             Log.warn(e);
1075             return e.toString();
1076         }
1077     }
1078 
1079     /* ------------------------------------------------------------ */
1080     /**
1081      * Clear the header.
1082      */
1083     public void clear()
1084     {
1085         _revision++;
1086         if (_revision > 1000000)
1087         {
1088             _revision = 0;
1089             for (int i = _fields.size(); i-- > 0;)
1090             {
1091                 Field field = _fields.get(i);
1092                 if (field != null) field.clear();
1093             }
1094         }
1095     }
1096 
1097     /* ------------------------------------------------------------ */
1098     /**
1099      * Destroy the header. Help the garbage collector by null everything that we can.
1100      */
1101     public void destroy()
1102     {
1103         if (_fields != null)
1104         {
1105             for (int i = _fields.size(); i-- > 0;)
1106             {
1107                 Field field = _fields.get(i);
1108                 if (field != null) {
1109                     _bufferMap.remove(field.getNameBuffer());
1110                     field.destroy();
1111                 }
1112             }
1113             _fields.clear();
1114         }
1115     }
1116 
1117     /* ------------------------------------------------------------ */
1118     /**
1119      * Add fields from another HttpFields instance. Single valued fields are replaced, while all
1120      * others are added.
1121      * 
1122      * @param fields
1123      */
1124     public void add(HttpFields fields)
1125     {
1126         if (fields == null) return;
1127 
1128         Enumeration e = fields.getFieldNames();
1129         while (e.hasMoreElements())
1130         {
1131             String name = (String) e.nextElement();
1132             Enumeration values = fields.getValues(name);
1133             while (values.hasMoreElements())
1134                 add(name, (String) values.nextElement());
1135         }
1136     }
1137 
1138     /* ------------------------------------------------------------ */
1139     /**
1140      * Get field value parameters. Some field values can have parameters. This method separates the
1141      * value from the parameters and optionally populates a map with the parameters. For example:
1142      * 
1143      * <PRE>
1144      * 
1145      * FieldName : Value ; param1=val1 ; param2=val2
1146      * 
1147      * </PRE>
1148      * 
1149      * @param value The Field value, possibly with parameteres.
1150      * @param parameters A map to populate with the parameters, or null
1151      * @return The value.
1152      */
1153     public static String valueParameters(String value, Map<String,String> parameters)
1154     {
1155         if (value == null) return null;
1156 
1157         int i = value.indexOf(';');
1158         if (i < 0) return value;
1159         if (parameters == null) return value.substring(0, i).trim();
1160 
1161         StringTokenizer tok1 = new QuotedStringTokenizer(value.substring(i), ";", false, true);
1162         while (tok1.hasMoreTokens())
1163         {
1164             String token = tok1.nextToken();
1165             StringTokenizer tok2 = new QuotedStringTokenizer(token, "= ");
1166             if (tok2.hasMoreTokens())
1167             {
1168                 String paramName = tok2.nextToken();
1169                 String paramVal = null;
1170                 if (tok2.hasMoreTokens()) paramVal = tok2.nextToken();
1171                 parameters.put(paramName, paramVal);
1172             }
1173         }
1174 
1175         return value.substring(0, i).trim();
1176     }
1177 
1178     /* ------------------------------------------------------------ */
1179     private static final Float __one = new Float("1.0");
1180     private static final Float __zero = new Float("0.0");
1181     private static final StringMap __qualities = new StringMap();
1182     static
1183     {
1184         __qualities.put(null, __one);
1185         __qualities.put("1.0", __one);
1186         __qualities.put("1", __one);
1187         __qualities.put("0.9", new Float("0.9"));
1188         __qualities.put("0.8", new Float("0.8"));
1189         __qualities.put("0.7", new Float("0.7"));
1190         __qualities.put("0.66", new Float("0.66"));
1191         __qualities.put("0.6", new Float("0.6"));
1192         __qualities.put("0.5", new Float("0.5"));
1193         __qualities.put("0.4", new Float("0.4"));
1194         __qualities.put("0.33", new Float("0.33"));
1195         __qualities.put("0.3", new Float("0.3"));
1196         __qualities.put("0.2", new Float("0.2"));
1197         __qualities.put("0.1", new Float("0.1"));
1198         __qualities.put("0", __zero);
1199         __qualities.put("0.0", __zero);
1200     }
1201 
1202     /* ------------------------------------------------------------ */
1203     public static Float getQuality(String value)
1204     {
1205         if (value == null) return __zero;
1206 
1207         int qe = value.indexOf(";");
1208         if (qe++ < 0 || qe == value.length()) return __one;
1209 
1210         if (value.charAt(qe++) == 'q')
1211         {
1212             qe++;
1213             Map.Entry entry = __qualities.getEntry(value, qe, value.length() - qe);
1214             if (entry != null) return (Float) entry.getValue();
1215         }
1216 
1217         HashMap params = new HashMap(3);
1218         valueParameters(value, params);
1219         String qs = (String) params.get("q");
1220         Float q = (Float) __qualities.get(qs);
1221         if (q == null)
1222         {
1223             try
1224             {
1225                 q = new Float(qs);
1226             }
1227             catch (Exception e)
1228             {
1229                 q = __one;
1230             }
1231         }
1232         return q;
1233     }
1234 
1235     /* ------------------------------------------------------------ */
1236     /**
1237      * List values in quality order.
1238      * 
1239      * @param enum Enumeration of values with quality parameters
1240      * @return values in quality order.
1241      */
1242     public static List qualityList(Enumeration e)
1243     {
1244         if (e == null || !e.hasMoreElements()) return Collections.EMPTY_LIST;
1245 
1246         Object list = null;
1247         Object qual = null;
1248 
1249         // Assume list will be well ordered and just add nonzero
1250         while (e.hasMoreElements())
1251         {
1252             String v = e.nextElement().toString();
1253             Float q = getQuality(v);
1254 
1255             if (q.floatValue() >= 0.001)
1256             {
1257                 list = LazyList.add(list, v);
1258                 qual = LazyList.add(qual, q);
1259             }
1260         }
1261 
1262         List vl = LazyList.getList(list, false);
1263         if (vl.size() < 2) return vl;
1264 
1265         List ql = LazyList.getList(qual, false);
1266 
1267         // sort list with swaps
1268         Float last = __zero;
1269         for (int i = vl.size(); i-- > 0;)
1270         {
1271             Float q = (Float) ql.get(i);
1272             if (last.compareTo(q) > 0)
1273             {
1274                 Object tmp = vl.get(i);
1275                 vl.set(i, vl.get(i + 1));
1276                 vl.set(i + 1, tmp);
1277                 ql.set(i, ql.get(i + 1));
1278                 ql.set(i + 1, q);
1279                 last = __zero;
1280                 i = vl.size();
1281                 continue;
1282             }
1283             last = q;
1284         }
1285         ql.clear();
1286         return vl;
1287     }
1288 
1289     /* ------------------------------------------------------------ */
1290     /* ------------------------------------------------------------ */
1291     /* ------------------------------------------------------------ */
1292     public static final class Field
1293     {
1294         private Buffer _name;
1295         private Buffer _value;
1296         private String _stringValue;
1297         private long _numValue;
1298         private Field _next;
1299         private Field _prev;
1300         private int _revision;
1301 
1302         /* ------------------------------------------------------------ */
1303         private Field(Buffer name, Buffer value, long numValue, int revision)
1304         {
1305             _name = name.asImmutableBuffer();
1306             _value = value.isImmutable() ? value : new View(value);
1307             _next = null;
1308             _prev = null;
1309             _revision = revision;
1310             _numValue = numValue;
1311             _stringValue=null;
1312         }
1313 
1314         /* ------------------------------------------------------------ */
1315         private void clear()
1316         {
1317             _revision = -1;
1318         }
1319 
1320         /* ------------------------------------------------------------ */
1321         private void destroy()
1322         {
1323             _name = null;
1324             _value = null;
1325             _next = null;
1326             _prev = null;
1327             _stringValue=null;
1328         }
1329 
1330         /* ------------------------------------------------------------ */
1331         /**
1332          * Reassign a value to this field. Checks if the string value is the same as that in the char
1333          * array, if so then just reuse existing value.
1334          */
1335         private void reset(Buffer value, long numValue, int revision)
1336         {
1337             _revision = revision;
1338             if (_value == null)
1339             {
1340                 _value = value.isImmutable() ? value : new View(value);
1341                 _numValue = numValue;
1342                 _stringValue=null;
1343             }
1344             else if (value.isImmutable())
1345             {
1346                 _value = value;
1347                 _numValue = numValue;
1348                 _stringValue=null;
1349             }
1350             else
1351             {
1352                 if (_value instanceof View)
1353                     ((View) _value).update(value);
1354                 else
1355                     _value = new View(value);
1356                 _numValue = numValue;
1357                 
1358                 // check to see if string value is still valid.
1359                 if (_stringValue!=null)
1360                 {
1361                     if (_stringValue.length()!=value.length())
1362                         _stringValue=null;
1363                     else
1364                     {
1365                         for (int i=value.length();i-->0;)
1366                         {
1367                             if (value.peek(value.getIndex()+i)!=_stringValue.charAt(i))
1368                             {
1369                                 _stringValue=null;
1370                                 break;
1371                             }
1372                         }
1373                     }
1374                 }
1375             }
1376         }
1377         
1378         
1379 
1380         /* ------------------------------------------------------------ */
1381         public void put(Buffer buffer) throws IOException
1382         {
1383             int o=(_name instanceof CachedBuffer)?((CachedBuffer)_name).getOrdinal():-1;
1384             if (o>=0)
1385                 buffer.put(_name);
1386             else
1387             {
1388                 int s=_name.getIndex();
1389                 int e=_name.putIndex();
1390                 while (s<e)
1391                 {
1392                     byte b=_name.peek(s++);
1393                     switch(b)
1394                     {
1395                         case '\r':
1396                         case '\n':
1397                         case ':' :
1398                             continue;
1399                         default:
1400                             buffer.put(b);
1401                     }
1402                 }
1403             }
1404             
1405             buffer.put((byte) ':');
1406             buffer.put((byte) ' ');
1407             
1408             o=(_value instanceof CachedBuffer)?((CachedBuffer)_value).getOrdinal():-1;
1409             if (o>=0 || _numValue>=0)
1410                 buffer.put(_value);
1411             else
1412             {
1413                 int s=_value.getIndex();
1414                 int e=_value.putIndex();
1415                 while (s<e)
1416                 {
1417                     byte b=_value.peek(s++);
1418                     switch(b)
1419                     {
1420                         case '\r':
1421                         case '\n':
1422                             continue;
1423                         default:
1424                             buffer.put(b);
1425                     }
1426                 }
1427             }
1428 
1429             BufferUtil.putCRLF(buffer);
1430         }
1431 
1432         /* ------------------------------------------------------------ */
1433         public String getName()
1434         {
1435             return BufferUtil.to8859_1_String(_name);
1436         }
1437 
1438         /* ------------------------------------------------------------ */
1439         Buffer getNameBuffer()
1440         {
1441             return _name;
1442         }
1443 
1444         /* ------------------------------------------------------------ */
1445         public int getNameOrdinal()
1446         {
1447             return HttpHeaders.CACHE.getOrdinal(_name);
1448         }
1449 
1450         /* ------------------------------------------------------------ */
1451         public String getValue()
1452         {
1453             if (_stringValue==null)
1454             {
1455                 _stringValue=(_value instanceof CachedBuffer)
1456                     ?_value.toString()
1457                     :BufferUtil.to8859_1_String(_value);
1458             }
1459             return _stringValue;
1460         }
1461 
1462         /* ------------------------------------------------------------ */
1463         public Buffer getValueBuffer()
1464         {
1465             return _value;
1466         }
1467 
1468         /* ------------------------------------------------------------ */
1469         public int getValueOrdinal()
1470         {
1471             return HttpHeaderValues.CACHE.getOrdinal(_value);
1472         }
1473 
1474         /* ------------------------------------------------------------ */
1475         public int getIntValue()
1476         {
1477             return (int) getLongValue();
1478         }
1479 
1480         /* ------------------------------------------------------------ */
1481         public long getLongValue()
1482         {
1483             if (_numValue == -1) _numValue = BufferUtil.toLong(_value);
1484             return _numValue;
1485         }
1486 
1487         /* ------------------------------------------------------------ */
1488         public String toString()
1489         {
1490             return ("[" + (_prev == null ? "" : "<-") + getName() + "="+_revision+"=" + _value + (_next == null ? "" : "->") + "]");
1491         }
1492     }
1493 
1494 }