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