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