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