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