View Javadoc

    1   // ========================================================================
    2   // Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
    3   // ------------------------------------------------------------------------
    4   // Licensed under the Apache License, Version 2.0 (the "License");
    5   // you may not use this file except in compliance with the License.
    6   // You may obtain a copy of the License at
    7   // http://www.apache.org/licenses/LICENSE-2.0
    8   // Unless required by applicable law or agreed to in writing, software
    9   // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  // ========================================================================
    14
    15  package org.mortbay.jetty;
    16
    17  import java.io.IOException;
    18  import java.text.SimpleDateFormat;
    19  import java.util.ArrayList;
    20  import java.util.Calendar;
    21  import java.util.Collections;
    22  import java.util.Date;
    23  import java.util.Enumeration;
    24  import java.util.GregorianCalendar;
    25  import java.util.HashMap;
    26  import java.util.Iterator;
    27  import java.util.List;
    28  import java.util.Locale;
    29  import java.util.Map;
    30  import java.util.NoSuchElementException;
    31  import java.util.StringTokenizer;
    32  import java.util.TimeZone;
    33
    34  import javax.servlet.http.Cookie;
    35
    36  import org.mortbay.io.Buffer;
    37  import org.mortbay.io.BufferCache;
    38  import org.mortbay.io.BufferDateCache;
    39  import org.mortbay.io.BufferUtil;
    40  import org.mortbay.io.ByteArrayBuffer;
    41  import org.mortbay.io.View;
    42  import org.mortbay.io.BufferCache.CachedBuffer;
    43  import org.mortbay.util.LazyList;
    44  import org.mortbay.util.QuotedStringTokenizer;
    45  import org.mortbay.util.StringMap;
    46  import org.mortbay.util.StringUtil;
    47  import org.mortbay.util.URIUtil;
    48
    49  /* ------------------------------------------------------------ */
    50  /**
    51   * HTTP Fields. A collection of HTTP header and or Trailer fields. This class is not synchronized
    52   * and needs to be protected from concurrent access.
    53   *
    54   * This class is not synchronized as it is expected that modifications will only be performed by a
    55   * single thread.
    56   *
    57   * @author Greg Wilkins (gregw)
    58   */
    59  public class HttpFields
    60  {
    61      /* ------------------------------------------------------------ */
    62      public final static String __separators = ", \t";
    63
    64      /* ------------------------------------------------------------ */
    65      private static String[] DAYS =
    66      { "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
    67      private static String[] MONTHS =
    68      { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"};
    69
    70      /* ------------------------------------------------------------ */
    71      /**
    72       * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" or "EEE, dd-MMM-yy HH:mm:ss 'GMT'"for
    73       * cookies
    74       */
    75      public static String formatDate(long date, boolean cookie)
    76      {
    77          StringBuffer buf = new StringBuffer(32);
    78          GregorianCalendar gc = new GregorianCalendar(__GMT);
    79          gc.setTimeInMillis(date);
    80          formatDate(buf, gc, cookie);
    81          return buf.toString();
    82      }
    83
    84      /* ------------------------------------------------------------ */
    85      /**
    86       * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" or "EEE, dd-MMM-yy HH:mm:ss 'GMT'"for
    87       * cookies
    88       */
    89      public static String formatDate(Calendar calendar, boolean cookie)
    90      {
    91          StringBuffer buf = new StringBuffer(32);
    92          formatDate(buf, calendar, cookie);
    93          return buf.toString();
    94      }
    95
    96      /* ------------------------------------------------------------ */
    97      /**
    98       * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" or "EEE, dd-MMM-yy HH:mm:ss 'GMT'"for
    99       * cookies
    100      */
    101     public static String formatDate(StringBuffer buf, long date, boolean cookie)
    102     {
    103         GregorianCalendar gc = new GregorianCalendar(__GMT);
    104         gc.setTimeInMillis(date);
    105         formatDate(buf, gc, cookie);
    106         return buf.toString();
    107     }
    108
    109     /* ------------------------------------------------------------ */
    110     /**
    111      * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" or "EEE, dd-MMM-yy HH:mm:ss 'GMT'"for
    112      * cookies
    113      */
    114     public static void formatDate(StringBuffer buf, Calendar calendar, boolean cookie)
    115     {
    116         // "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
    117         // "EEE, dd-MMM-yy HH:mm:ss 'GMT'", cookie
    118
    119         int day_of_week = calendar.get(Calendar.DAY_OF_WEEK);
    120         int day_of_month = calendar.get(Calendar.DAY_OF_MONTH);
    121         int month = calendar.get(Calendar.MONTH);
    122         int year = calendar.get(Calendar.YEAR);
    123         int century = year / 100;
    124         year = year % 100;
    125
    126         int epoch = (int) ((calendar.getTimeInMillis() / 1000) % (60 * 60 * 24));
    127         int seconds = epoch % 60;
    128         epoch = epoch / 60;
    129         int minutes = epoch % 60;
    130         int hours = epoch / 60;
    131
    132         buf.append(DAYS[day_of_week]);
    133         buf.append(',');
    134         buf.append(' ');
    135         StringUtil.append2digits(buf, day_of_month);
    136
    137         if (cookie)
    138         {
    139             buf.append('-');
    140             buf.append(MONTHS[month]);
    141             buf.append('-');
    142             StringUtil.append2digits(buf, century);
    143             StringUtil.append2digits(buf, year);
    144         }
    145         else
    146         {
    147             buf.append(' ');
    148             buf.append(MONTHS[month]);
    149             buf.append(' ');
    150             StringUtil.append2digits(buf, century);
    151             StringUtil.append2digits(buf, year);
    152         }
    153         buf.append(' ');
    154         StringUtil.append2digits(buf, hours);
    155         buf.append(':');
    156         StringUtil.append2digits(buf, minutes);
    157         buf.append(':');
    158         StringUtil.append2digits(buf, seconds);
    159         buf.append(" GMT");
    160     }
    161
    162     /* -------------------------------------------------------------- */
    163     private static TimeZone __GMT = TimeZone.getTimeZone("GMT");
    164     public final static BufferDateCache __dateCache = new BufferDateCache("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
    165
    166     /* ------------------------------------------------------------ */
    167     private final static String __dateReceiveFmt[] =
    168     {   "EEE, dd MMM yyyy HH:mm:ss zzz",
    169         "EEE, dd-MMM-yy HH:mm:ss",
    170         "EEE MMM dd HH:mm:ss yyyy",
    171
    172         "EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz",
    173         "EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss",
    174         "EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz",
    175         "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",
    176         "MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz",
    177         "EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz",
    178         "EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss",
    179       };
    180     private  static int __dateReceiveInit=3;
    181     private  static SimpleDateFormat __dateReceive[];
    182     static
    183     {
    184         __GMT.setID("GMT");
    185         __dateCache.setTimeZone(__GMT);
    186         __dateReceive = new SimpleDateFormat[__dateReceiveFmt.length];
    187         // Initialize only the standard formats here.
    188         for (int i = 0; i < __dateReceiveInit; i++)
    189         {
    190             __dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US);
    191             __dateReceive[i].setTimeZone(__GMT);
    192         }
    193     }
    194     public final static String __01Jan1970 = formatDate(0, true).trim();
    195     public final static Buffer __01Jan1970_BUFFER = new ByteArrayBuffer(__01Jan1970);
    196
    197     /* -------------------------------------------------------------- */
    198     protected ArrayList _fields = new ArrayList(20);
    199     protected int _revision;
    200     protected HashMap _bufferMap = new HashMap(32);
    201     protected SimpleDateFormat _dateReceive[] = new SimpleDateFormat[__dateReceive.length];
    202     private StringBuffer _dateBuffer;
    203     private Calendar _calendar;
    204
    205     /* ------------------------------------------------------------ */
    206     /**
    207      * Constructor.
    208      */
    209     public HttpFields()
    210     {
    211     }
    212
    213     /* -------------------------------------------------------------- */
    214     /**
    215      * Get enumeration of header _names. Returns an enumeration of strings representing the header
    216      * _names for this request.
    217      */
    218     public Enumeration getFieldNames()
    219     {
    220         final int revision=_revision;
    221         return new Enumeration()
    222         {
    223             int i = 0;
    224             Field field = null;
    225
    226             public boolean hasMoreElements()
    227             {
    228                 if (field != null) return true;
    229                 while (i < _fields.size())
    230                 {
    231                     Field f = (Field) _fields.get(i++);
    232                     if (f != null && f._prev == null && f._revision == revision)
    233                     {
    234                         field = f;
    235                         return true;
    236                     }
    237                 }
    238                 return false;
    239             }
    240
    241             public Object nextElement() throws NoSuchElementException
    242             {
    243                 if (field != null || hasMoreElements())
    244                 {
    245                     String n = BufferUtil.to8859_1_String(field._name);
    246                     field = null;
    247                     return n;
    248                 }
    249                 throw new NoSuchElementException();
    250             }
    251         };
    252     }
    253
    254     /* -------------------------------------------------------------- */
    255     /**
    256      * Get enumeration of Fields Returns an enumeration of Fields for this request.
    257      */
    258     public Iterator getFields()
    259     {
    260         final int revision=_revision;
    261         return new Iterator()
    262         {
    263             int i = 0;
    264             Field field = null;
    265
    266             public boolean hasNext()
    267             {
    268                 if (field != null) return true;
    269                 while (i < _fields.size())
    270                 {
    271                     Field f = (Field) _fields.get(i++);
    272                     if (f != null && f._revision == revision)
    273                     {
    274                         field = f;
    275                         return true;
    276                     }
    277                 }
    278                 return false;
    279             }
    280
    281             public Object next()
    282             {
    283                 if (field != null || hasNext())
    284                 {
    285                     final Field f = field;
    286                     field = null;
    287                     return f;
    288                 }
    289                 throw new NoSuchElementException();
    290             }
    291
    292             public void remove()
    293             {
    294                 throw new UnsupportedOperationException();
    295             }
    296         };
    297     }
    298
    299     /* ------------------------------------------------------------ */
    300     private Field getField(String name)
    301     {
    302         return (Field) _bufferMap.get(HttpHeaders.CACHE.lookup(name));
    303     }
    304
    305     /* ------------------------------------------------------------ */
    306     private Field getField(Buffer name)
    307     {
    308         return (Field) _bufferMap.get(name);
    309     }
    310
    311     /* ------------------------------------------------------------ */
    312     public boolean containsKey(Buffer name)
    313     {
    314         Field f = getField(name);
    315         return (f != null && f._revision == _revision);
    316     }
    317
    318     /* ------------------------------------------------------------ */
    319     public boolean containsKey(String name)
    320     {
    321         Field f = getField(name);
    322         return (f != null && f._revision == _revision);
    323     }
    324
    325     /* -------------------------------------------------------------- */
    326     /**
    327      * @return the value of a field, or null if not found. For multiple fields of the same name,
    328      *         only the first is returned.
    329      * @param name the case-insensitive field name
    330      */
    331     public String getStringField(String name)
    332     {
    333         // TODO - really reuse strings from previous requests!
    334         Field field = getField(name);
    335         if (field != null && field._revision == _revision) return field.getValue();
    336         return null;
    337     }
    338
    339     /* -------------------------------------------------------------- */
    340     /**
    341      * @return the value of a field, or null if not found. For multiple fields of the same name,
    342      *         only the first is returned.
    343      * @param name the case-insensitive field name
    344      */
    345     public String getStringField(Buffer name)
    346     {
    347         // TODO - really reuse strings from previous requests!
    348         Field field = getField(name);
    349         if (field != null && field._revision == _revision)
    350             return BufferUtil.to8859_1_String(field._value);
    351         return null;
    352     }
    353
    354     /* -------------------------------------------------------------- */
    355     /**
    356      * @return the value of a field, or null if not found. For multiple fields of the same name,
    357      *         only the first is returned.
    358      * @param name the case-insensitive field name
    359      */
    360     public Buffer get(Buffer name)
    361     {
    362         Field field = getField(name);
    363         if (field != null && field._revision == _revision)
    364             return field._value;
    365         return null;
    366     }
    367
    368     /* -------------------------------------------------------------- */
    369     /**
    370      * Get multi headers
    371      *
    372      * @return Enumeration of the values, or null if no such header.
    373      * @param name the case-insensitive field name
    374      */
    375     public Enumeration getValues(String name)
    376     {
    377         final Field field = getField(name);
    378         if (field == null)
    379             return null;
    380         final int revision=_revision;
    381
    382         return new Enumeration()
    383         {
    384             Field f = field;
    385
    386             public boolean hasMoreElements()
    387             {
    388                 while (f != null && f._revision != revision)
    389                     f = f._next;
    390                 return f != null;
    391             }
    392
    393             public Object nextElement() throws NoSuchElementException
    394             {
    395                 if (f == null) throw new NoSuchElementException();
    396                 Field n = f;
    397                 do
    398                     f = f._next;
    399                 while (f != null && f._revision != revision);
    400                 return n.getValue();
    401             }
    402         };
    403     }
    404
    405     /* -------------------------------------------------------------- */
    406     /**
    407      * Get multi headers
    408      *
    409      * @return Enumeration of the value Strings, or null if no such header.
    410      * @param name the case-insensitive field name
    411      */
    412     public Enumeration getValues(Buffer name)
    413     {
    414         final Field field = getField(name);
    415         if (field == null)
    416             return null;
    417         final int revision=_revision;
    418
    419         return new Enumeration()
    420         {
    421             Field f = field;
    422
    423             public boolean hasMoreElements()
    424             {
    425                 while (f != null && f._revision != revision)
    426                     f = f._next;
    427                 return f != null;
    428             }
    429
    430             public Object nextElement() throws NoSuchElementException
    431             {
    432                 if (f == null) throw new NoSuchElementException();
    433                 Field n = f;
    434                 f = f._next;
    435                 while (f != null && f._revision != revision)
    436                     f = f._next;
    437                 return n.getValue();
    438             }
    439         };
    440     }
    441
    442     /* -------------------------------------------------------------- */
    443     /**
    444      * Get multi field values with separator. The multiple values can be represented as separate
    445      * headers of the same name, or by a single header using the separator(s), or a combination of
    446      * both. Separators may be quoted.
    447      *
    448      * @param name the case-insensitive field name
    449      * @param separators String of separators.
    450      * @return Enumeration of the values, or null if no such header.
    451      */
    452     public Enumeration getValues(String name, final String separators)
    453     {
    454         final Enumeration e = getValues(name);
    455         if (e == null)
    456             return null;
    457         return new Enumeration()
    458         {
    459             QuotedStringTokenizer tok = null;
    460
    461             public boolean hasMoreElements()
    462             {
    463                 if (tok != null && tok.hasMoreElements()) return true;
    464                 while (e.hasMoreElements())
    465                 {
    466                     String value = (String) e.nextElement();
    467                     tok = new QuotedStringTokenizer(value, separators, false, false);
    468                     if (tok.hasMoreElements()) return true;
    469                 }
    470                 tok = null;
    471                 return false;
    472             }
    473
    474             public Object nextElement() throws NoSuchElementException
    475             {
    476                 if (!hasMoreElements()) throw new NoSuchElementException();
    477                 String next = (String) tok.nextElement();
    478                 if (next != null) next = next.trim();
    479                 return next;
    480             }
    481         };
    482     }
    483
    484     /* -------------------------------------------------------------- */
    485     /**
    486      * Set a field.
    487      *
    488      * @param name the name of the field
    489      * @param value the value of the field. If null the field is cleared.
    490      */
    491     public void put(String name, String value)
    492     {
    493         Buffer n = HttpHeaders.CACHE.lookup(name);
    494         Buffer v = null;
    495         if (value != null)
    496             v = HttpHeaderValues.CACHE.lookup(value);
    497         put(n, v, -1);
    498     }
    499
    500     /* -------------------------------------------------------------- */
    501     /**
    502      * Set a field.
    503      *
    504      * @param name the name of the field
    505      * @param value the value of the field. If null the field is cleared.
    506      */
    507     public void put(Buffer name, String value)
    508     {
    509         Buffer v = HttpHeaderValues.CACHE.lookup(value);
    510         put(name, v, -1);
    511     }
    512
    513     /* -------------------------------------------------------------- */
    514     /**
    515      * Set a field.
    516      *
    517      * @param name the name of the field
    518      * @param value the value of the field. If null the field is cleared.
    519      */
    520     public void put(Buffer name, Buffer value)
    521     {
    522         put(name, value, -1);
    523     }
    524
    525     /* -------------------------------------------------------------- */
    526     /**
    527      * Set a field.
    528      *
    529      * @param name the name of the field
    530      * @param value the value of the field. If null the field is cleared.
    531      * @param numValue the numeric value of the field (must match value) or -1
    532      */
    533     public void put(Buffer name, Buffer value, long numValue)
    534     {
    535         if (value == null)
    536         {
    537             remove(name);
    538             return;
    539         }
    540
    541         if (!(name instanceof BufferCache.CachedBuffer)) name = HttpHeaders.CACHE.lookup(name);
    542
    543         Field field = (Field) _bufferMap.get(name);
    544
    545         // Look for value to replace.
    546         if (field != null)
    547         {
    548             field.reset(value, numValue, _revision);
    549             field = field._next;
    550             while (field != null)
    551             {
    552                 field.clear();
    553                 field = field._next;
    554             }
    555             return;
    556         }
    557         else
    558         {
    559             // new value;
    560             field = new Field(name, value, numValue, _revision);
    561             _fields.add(field);
    562             _bufferMap.put(field.getNameBuffer(), field);
    563         }
    564     }
    565
    566     /* -------------------------------------------------------------- */
    567     /**
    568      * Set a field.
    569      *
    570      * @param name the name of the field
    571      * @param list the List value of the field. If null the field is cleared.
    572      */
    573     public void put(String name, List list)
    574     {
    575         if (list == null || list.size() == 0)
    576         {
    577             remove(name);
    578             return;
    579         }
    580         Buffer n = HttpHeaders.CACHE.lookup(name);
    581
    582         Object v = list.get(0);
    583         if (v != null)
    584             put(n, HttpHeaderValues.CACHE.lookup(v.toString()));
    585         else
    586             remove(n);
    587
    588         if (list.size() > 1)
    589         {
    590             java.util.Iterator iter = list.iterator();
    591             iter.next();
    592             while (iter.hasNext())
    593             {
    594                 v = iter.next();
    595                 if (v != null) put(n, HttpHeaderValues.CACHE.lookup(v.toString()));
    596             }
    597         }
    598     }
    599
    600     /* -------------------------------------------------------------- */
    601     /**
    602      * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
    603      * headers of the same name.
    604      *
    605      * @param name the name of the field
    606      * @param value the value of the field.
    607      * @exception IllegalArgumentException If the name is a single valued field and already has a
    608      *                value.
    609      */
    610     public void add(String name, String value) throws IllegalArgumentException
    611     {
    612         Buffer n = HttpHeaders.CACHE.lookup(name);
    613         Buffer v = HttpHeaderValues.CACHE.lookup(value);
    614         add(n, v, -1);
    615     }
    616
    617     /* -------------------------------------------------------------- */
    618     /**
    619      * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
    620      * headers of the same name.
    621      *
    622      * @param name the name of the field
    623      * @param value the value of the field.
    624      * @exception IllegalArgumentException If the name is a single valued field and already has a
    625      *                value.
    626      */
    627     public void add(Buffer name, Buffer value) throws IllegalArgumentException
    628     {
    629         add(name, value, -1);
    630     }
    631
    632     /* -------------------------------------------------------------- */
    633     /**
    634      * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
    635      * headers of the same name.
    636      *
    637      * @param name the name of the field
    638      * @param value the value of the field.
    639      * @exception IllegalArgumentException If the name is a single valued field and already has a
    640      *                value.
    641      */
    642     private void add(Buffer name, Buffer value, long numValue) throws IllegalArgumentException
    643     {
    644         if (value == null) throw new IllegalArgumentException("null value");
    645
    646         if (!(name instanceof BufferCache.CachedBuffer)) name = HttpHeaders.CACHE.lookup(name);
    647
    648         Field field = (Field) _bufferMap.get(name);
    649         Field last = null;
    650         if (field != null)
    651         {
    652             while (field != null && field._revision == _revision)
    653             {
    654                 last = field;
    655                 field = field._next;
    656             }
    657         }
    658
    659         if (field != null)
    660             field.reset(value, numValue, _revision);
    661         else
    662         {
    663             // create the field
    664             field = new Field(name, value, numValue, _revision);
    665
    666             // look for chain to add too
    667             if (last != null)
    668             {
    669                 field._prev = last;
    670                 last._next = field;
    671             }
    672             else
    673                 _bufferMap.put(field.getNameBuffer(), field);
    674
    675             _fields.add(field);
    676         }
    677     }
    678
    679     /* ------------------------------------------------------------ */
    680     /**
    681      * Remove a field.
    682      *
    683      * @param name
    684      */
    685     public void remove(String name)
    686     {
    687         remove(HttpHeaders.CACHE.lookup(name));
    688     }
    689
    690     /* ------------------------------------------------------------ */
    691     /**
    692      * Remove a field.
    693      *
    694      * @param name
    695      */
    696     public void remove(Buffer name)
    697     {
    698         Field field = (Field) _bufferMap.get(name);
    699
    700         if (field != null)
    701         {
    702             while (field != null)
    703             {
    704                 field.clear();
    705                 field = field._next;
    706             }
    707         }
    708     }
    709
    710     /* -------------------------------------------------------------- */
    711     /**
    712      * Get a header as an long value. Returns the value of an integer field or -1 if not found. The
    713      * case of the field name is ignored.
    714      *
    715      * @param name the case-insensitive field name
    716      * @exception NumberFormatException If bad long found
    717      */
    718     public long getLongField(String name) throws NumberFormatException
    719     {
    720         Field field = getField(name);
    721         if (field != null && field._revision == _revision) return field.getLongValue();
    722
    723         return -1L;
    724     }
    725
    726     /* -------------------------------------------------------------- */
    727     /**
    728      * Get a header as an long value. Returns the value of an integer field or -1 if not found. The
    729      * case of the field name is ignored.
    730      *
    731      * @param name the case-insensitive field name
    732      * @exception NumberFormatException If bad long found
    733      */
    734     public long getLongField(Buffer name) throws NumberFormatException
    735     {
    736         Field field = getField(name);
    737         if (field != null && field._revision == _revision) return field.getLongValue();
    738         return -1L;
    739     }
    740
    741     /* -------------------------------------------------------------- */
    742     /**
    743      * Get a header as a date value. Returns the value of a date field, or -1 if not found. The case
    744      * of the field name is ignored.
    745      *
    746      * @param name the case-insensitive field name
    747      */
    748     public long getDateField(String name)
    749     {
    750         Field field = getField(name);
    751         if (field == null || field._revision != _revision) return -1;
    752
    753         if (field._numValue != -1) return field._numValue;
    754
    755         String val = valueParameters(BufferUtil.to8859_1_String(field._value), null);
    756         if (val == null) return -1;
    757
    758
    759
    760         for (int i = 0; i < __dateReceiveInit; i++)
    761         {
    762             if (_dateReceive[i] == null) _dateReceive[i] = (SimpleDateFormat) __dateReceive[i].clone();
    763
    764             try
    765             {
    766                 Date date = (Date) _dateReceive[i].parseObject(val);
    767                 return field._numValue = date.getTime();
    768             }
    769             catch (java.lang.Exception e)
    770             {
    771             }
    772         }
    773         if (val.endsWith(" GMT"))
    774         {
    775             val = val.substring(0, val.length() - 4);
    776             for (int i = 0; i < __dateReceiveInit; i++)
    777             {
    778                 try
    779                 {
    780                     Date date = (Date) _dateReceive[i].parseObject(val);
    781                     return field._numValue = date.getTime();
    782                 }
    783                 catch (java.lang.Exception e)
    784                 {
    785                 }
    786             }
    787         }
    788
    789         // The standard formats did not work.  So we will lock the common format array
    790         // and look at lazily creating the non-standard formats
    791         synchronized (__dateReceive)
    792         {
    793             for (int i = __dateReceiveInit; i < _dateReceive.length; i++)
    794             {
    795                 if (_dateReceive[i] == null)
    796                 {
    797                     if (__dateReceive[i]==null)
    798                     {
    799                         __dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US);
    800                         __dateReceive[i].setTimeZone(__GMT);
    801                     }
    802                     _dateReceive[i] = (SimpleDateFormat) __dateReceive[i].clone();
    803                 }
    804
    805                 try
    806                 {
    807                     Date date = (Date) _dateReceive[i].parseObject(val);
    808                     return field._numValue = date.getTime();
    809                 }
    810                 catch (java.lang.Exception e)
    811                 {
    812                 }
    813             }
    814             if (val.endsWith(" GMT"))
    815             {
    816                 val = val.substring(0, val.length() - 4);
    817                 for (int i = 0; i < _dateReceive.length; i++)
    818                 {
    819                     try
    820                     {
    821                         Date date = (Date) _dateReceive[i].parseObject(val);
    822                         return field._numValue = date.getTime();
    823                     }
    824                     catch (java.lang.Exception e)
    825                     {
    826                     }
    827                 }
    828             }
    829         }
    830
    831
    832         throw new IllegalArgumentException("Cannot convert date: " + val);
    833     }
    834
    835     /* -------------------------------------------------------------- */
    836     /**
    837      * Sets the value of an long field.
    838      *
    839      * @param name the field name
    840      * @param value the field long value
    841      */
    842     public void putLongField(Buffer name, long value)
    843     {
    844         Buffer v = BufferUtil.toBuffer(value);
    845         put(name, v, value);
    846     }
    847
    848     /* -------------------------------------------------------------- */
    849     /**
    850      * Sets the value of an long field.
    851      *
    852      * @param name the field name
    853      * @param value the field long value
    854      */
    855     public void putLongField(String name, long value)
    856     {
    857         Buffer n = HttpHeaders.CACHE.lookup(name);
    858         Buffer v = BufferUtil.toBuffer(value);
    859         put(n, v, value);
    860     }
    861
    862     /* -------------------------------------------------------------- */
    863     /**
    864      * Sets the value of an long field.
    865      *
    866      * @param name the field name
    867      * @param value the field long value
    868      */
    869     public void addLongField(String name, long value)
    870     {
    871         Buffer n = HttpHeaders.CACHE.lookup(name);
    872         Buffer v = BufferUtil.toBuffer(value);
    873         add(n, v, value);
    874     }
    875
    876     /* -------------------------------------------------------------- */
    877     /**
    878      * Sets the value of an long field.
    879      *
    880      * @param name the field name
    881      * @param value the field long value
    882      */
    883     public void addLongField(Buffer name, long value)
    884     {
    885         Buffer v = BufferUtil.toBuffer(value);
    886         add(name, v, value);
    887     }
    888
    889     /* -------------------------------------------------------------- */
    890     /**
    891      * Sets the value of a date field.
    892      *
    893      * @param name the field name
    894      * @param date the field date value
    895      */
    896     public void putDateField(Buffer name, long date)
    897     {
    898         if (_dateBuffer == null)
    899         {
    900             _dateBuffer = new StringBuffer(32);
    901             _calendar = new GregorianCalendar(__GMT);
    902         }
    903         _dateBuffer.setLength(0);
    904         _calendar.setTimeInMillis(date);
    905         formatDate(_dateBuffer, _calendar, false);
    906         Buffer v = new ByteArrayBuffer(_dateBuffer.toString());
    907         put(name, v, date);
    908     }
    909
    910     /* -------------------------------------------------------------- */
    911     /**
    912      * Sets the value of a date field.
    913      *
    914      * @param name the field name
    915      * @param date the field date value
    916      */
    917     public void putDateField(String name, long date)
    918     {
    919         Buffer n = HttpHeaders.CACHE.lookup(name);
    920         putDateField(n,date);
    921     }
    922
    923     /* -------------------------------------------------------------- */
    924     /**
    925      * Sets the value of a date field.
    926      *
    927      * @param name the field name
    928      * @param date the field date value
    929      */
    930     public void addDateField(String name, long date)
    931     {
    932         if (_dateBuffer == null)
    933         {
    934             _dateBuffer = new StringBuffer(32);
    935             _calendar = new GregorianCalendar(__GMT);
    936         }
    937         _dateBuffer.setLength(0);
    938         _calendar.setTimeInMillis(date);
    939         formatDate(_dateBuffer, _calendar, false);
    940         Buffer n = HttpHeaders.CACHE.lookup(name);
    941         Buffer v = new ByteArrayBuffer(_dateBuffer.toString());
    942         add(n, v, date);
    943     }
    944
    945     /* ------------------------------------------------------------ */
    946     /**
    947      * Format a set cookie value
    948      *
    949      * @param cookie The cookie.
    950      * @param cookie2 If true, use the alternate cookie 2 header
    951      */
    952     public void addSetCookie(Cookie cookie)
    953     {
    954         String name = cookie.getName();
    955         String value = cookie.getValue();
    956         int version = cookie.getVersion();
    957
    958         // Check arguments
    959         if (name == null || name.length() == 0) throw new IllegalArgumentException("Bad cookie name");
    960
    961         // Format value and params
    962         StringBuffer buf = new StringBuffer(128);
    963         String name_value_params = null;
    964         synchronized (buf)
    965         {
    966             QuotedStringTokenizer.quoteIfNeeded(buf, name);
    967             buf.append('=');
    968             if (value != null && value.length() > 0)
    969                 QuotedStringTokenizer.quoteIfNeeded(buf, value);
    970
    971             if (version > 0)
    972             {
    973                 buf.append(";Version=");
    974                 buf.append(version);
    975                 String comment = cookie.getComment();
    976                 if (comment != null && comment.length() > 0)
    977                 {
    978                     buf.append(";Comment=");
    979                     QuotedStringTokenizer.quoteIfNeeded(buf, comment);
    980                 }
    981             }
    982             String path = cookie.getPath();
    983             if (path != null && path.length() > 0)
    984             {
    985                 buf.append(";Path=");
    986                 if (path.startsWith("\""))
    987                     buf.append(path);
    988                 else
    989                     QuotedStringTokenizer.quoteIfNeeded(buf,path);
    990             }
    991             String domain = cookie.getDomain();
    992             if (domain != null && domain.length() > 0)
    993             {
    994                 buf.append(";Domain=");
    995                 QuotedStringTokenizer.quoteIfNeeded(buf,domain.toLowerCase());
    996             }
    997
    998             long maxAge = cookie.getMaxAge();
    999             if (maxAge >= 0)
    1000             {
    1001                 if (version == 0)
    1002                 {
    1003                     buf.append(";Expires=");
    1004                     if (maxAge == 0)
    1005                         buf.append(__01Jan1970);
    1006                     else
    1007                         formatDate(buf, System.currentTimeMillis() + 1000L * maxAge, true);
    1008                 }
    1009                 else
    1010                 {
    1011                     buf.append(";Max-Age=");
    1012                     buf.append(maxAge);
    1013                 }
    1014             }
    1015             else if (version > 0)
    1016             {
    1017                 buf.append(";Discard");
    1018             }
    1019
    1020             if (cookie.getSecure())
    1021             {
    1022                 buf.append(";Secure");
    1023             }
    1024             if (cookie instanceof HttpOnlyCookie)
    1025                 buf.append(";HttpOnly");
    1026
    1027             // TODO - straight to Buffer?
    1028             name_value_params = buf.toString();
    1029         }
    1030         put(HttpHeaders.EXPIRES_BUFFER, __01Jan1970_BUFFER);
    1031         add(HttpHeaders.SET_COOKIE_BUFFER, new ByteArrayBuffer(name_value_params));
    1032     }
    1033
    1034     /* -------------------------------------------------------------- */
    1035     public void put(Buffer buffer) throws IOException
    1036     {
    1037         for (int i = 0; i < _fields.size(); i++)
    1038         {
    1039             Field field = (Field) _fields.get(i);
    1040             if (field != null && field._revision == _revision) field.put(buffer);
    1041         }
    1042         BufferUtil.putCRLF(buffer);
    1043     }
    1044
    1045     /* -------------------------------------------------------------- */
    1046     public String toString()
    1047     {
    1048         try
    1049         {
    1050             StringBuffer buffer = new StringBuffer();
    1051
    1052             for (int i = 0; i < _fields.size(); i++)
    1053             {
    1054                 Field field = (Field) _fields.get(i);
    1055                 if (field != null && field._revision == _revision)
    1056                 {
    1057                     String tmp = field.getName();
    1058                     if (tmp != null) buffer.append(tmp);
    1059                     buffer.append(": ");
    1060                     tmp = field.getValue();
    1061                     if (tmp != null) buffer.append(tmp);
    1062                     buffer.append("\r\n");
    1063                 }
    1064             }
    1065             buffer.append("\r\n");
    1066             return buffer.toString();
    1067
    1068         }
    1069         catch (Exception e)
    1070         {
    1071             e.printStackTrace();
    1072         }
    1073
    1074         return null;
    1075     }
    1076
    1077     /* ------------------------------------------------------------ */
    1078     /**
    1079      * Clear the header.
    1080      */
    1081     public void clear()
    1082     {
    1083         _revision++;
    1084         if (_revision > 1000000)
    1085         {
    1086             _revision = 0;
    1087             for (int i = _fields.size(); i-- > 0;)
    1088             {
    1089                 Field field = (Field) _fields.get(i);
    1090                 if (field != null) field.clear();
    1091             }
    1092         }
    1093     }
    1094
    1095     /* ------------------------------------------------------------ */
    1096     /**
    1097      * Destroy the header. Help the garbage collector by null everything that we can.
    1098      */
    1099     public void destroy()
    1100     {
    1101         if (_fields != null)
    1102         {
    1103             for (int i = _fields.size(); i-- > 0;)
    1104             {
    1105                 Field field = (Field) _fields.get(i);
    1106                 if (field != null) {
    1107                     _bufferMap.remove(field.getNameBuffer());
    1108                     field.destroy();
    1109                 }
    1110             }
    1111         }
    1112         _fields = null;
    1113         _dateBuffer = null;
    1114         _calendar = null;
    1115         _dateReceive = null;
    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 paramters. 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 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 Float __one = new Float("1.0");
    1181     private static Float __zero = new Float("0.0");
    1182     private static 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 enum 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 String _stringValue;
    1298         private long _numValue;
    1299         private Field _next;
    1300         private Field _prev;
    1301         private int _revision;
    1302
    1303         /* ------------------------------------------------------------ */
    1304         private Field(Buffer name, Buffer value, long numValue, int revision)
    1305         {
    1306             _name = name.asImmutableBuffer();
    1307             _value = value.isImmutable() ? value : new View(value);
    1308             _next = null;
    1309             _prev = null;
    1310             _revision = revision;
    1311             _numValue = numValue;
    1312             _stringValue=null;
    1313         }
    1314
    1315         /* ------------------------------------------------------------ */
    1316         private void clear()
    1317         {
    1318             _revision = -1;
    1319         }
    1320
    1321         /* ------------------------------------------------------------ */
    1322         private void destroy()
    1323         {
    1324             _name = null;
    1325             _value = null;
    1326             _next = null;
    1327             _prev = null;
    1328             _stringValue=null;
    1329         }
    1330
    1331         /* ------------------------------------------------------------ */
    1332         /**
    1333          * Reassign a value to this field. Checks if the string value is the same as that in the char
    1334          * array, if so then just reuse existing value.
    1335          */
    1336         private void reset(Buffer value, long numValue, int revision)
    1337         {
    1338             _revision = revision;
    1339             if (_value == null)
    1340             {
    1341                 _value = value.isImmutable() ? value : new View(value);
    1342                 _numValue = numValue;
    1343                 _stringValue=null;
    1344             }
    1345             else if (value.isImmutable())
    1346             {
    1347                 _value = value;
    1348                 _numValue = numValue;
    1349                 _stringValue=null;
    1350             }
    1351             else
    1352             {
    1353                 if (_value instanceof View)
    1354                     ((View) _value).update(value);
    1355                 else
    1356                     _value = new View(value);
    1357                 _numValue = numValue;
    1358
    1359                 // check to see if string value is still valid.
    1360                 if (_stringValue!=null)
    1361                 {
    1362                     if (_stringValue.length()!=value.length())
    1363                         _stringValue=null;
    1364                     else
    1365                     {
    1366                         for (int i=value.length();i-->0;)
    1367                         {
    1368                             if (value.peek(value.getIndex()+i)!=_stringValue.charAt(i))
    1369                             {
    1370                                 _stringValue=null;
    1371                                 break;
    1372                             }
    1373                         }
    1374                     }
    1375                 }
    1376             }
    1377         }
    1378
    1379
    1380
    1381         /* ------------------------------------------------------------ */
    1382         public void put(Buffer buffer) throws IOException
    1383         {
    1384             int o=(_name instanceof CachedBuffer)?((CachedBuffer)_name).getOrdinal():-1;
    1385             if (o>=0)
    1386                 buffer.put(_name);
    1387             else
    1388             {
    1389                 int s=_name.getIndex();
    1390                 int e=_name.putIndex();
    1391                 while (s<e)
    1392                 {
    1393                     byte b=_name.peek(s++);
    1394                     switch(b)
    1395                     {
    1396                         case '\r':
    1397                         case '\n':
    1398                         case ':' :
    1399                             continue;
    1400                         default:
    1401                             buffer.put(b);
    1402                     }
    1403                 }
    1404             }
    1405
    1406             buffer.put((byte) ':');
    1407             buffer.put((byte) ' ');
    1408
    1409             o=(_value instanceof CachedBuffer)?((CachedBuffer)_value).getOrdinal():-1;
    1410             if (o>=0 || _numValue>=0)
    1411                 buffer.put(_value);
    1412             else
    1413             {
    1414                 int s=_value.getIndex();
    1415                 int e=_value.putIndex();
    1416                 while (s<e)
    1417                 {
    1418                     byte b=_value.peek(s++);
    1419                     switch(b)
    1420                     {
    1421                         case '\r':
    1422                         case '\n':
    1423                             continue;
    1424                         default:
    1425                             buffer.put(b);
    1426                     }
    1427                 }
    1428             }
    1429
    1430             BufferUtil.putCRLF(buffer);
    1431         }
    1432
    1433         /* ------------------------------------------------------------ */
    1434         public String getName()
    1435         {
    1436             return BufferUtil.to8859_1_String(_name);
    1437         }
    1438
    1439         /* ------------------------------------------------------------ */
    1440         Buffer getNameBuffer()
    1441         {
    1442             return _name;
    1443         }
    1444
    1445         /* ------------------------------------------------------------ */
    1446         public int getNameOrdinal()
    1447         {
    1448             return HttpHeaders.CACHE.getOrdinal(_name);
    1449         }
    1450
    1451         /* ------------------------------------------------------------ */
    1452         public String getValue()
    1453         {
    1454             if (_stringValue==null)
    1455                 _stringValue=BufferUtil.to8859_1_String(_value);
    1456             return _stringValue;
    1457         }
    1458
    1459         /* ------------------------------------------------------------ */
    1460         public Buffer getValueBuffer()
    1461         {
    1462             return _value;
    1463         }
    1464
    1465         /* ------------------------------------------------------------ */
    1466         public int getValueOrdinal()
    1467         {
    1468             return HttpHeaderValues.CACHE.getOrdinal(_value);
    1469         }
    1470
    1471         /* ------------------------------------------------------------ */
    1472         public int getIntValue()
    1473         {
    1474             return (int) getLongValue();
    1475         }
    1476
    1477         /* ------------------------------------------------------------ */
    1478         public long getLongValue()
    1479         {
    1480             if (_numValue == -1) _numValue = BufferUtil.toLong(_value);
    1481             return _numValue;
    1482         }
    1483
    1484         /* ------------------------------------------------------------ */
    1485         public String toString()
    1486         {
    1487             return ("[" + (_prev == null ? "" : "<-") + getName() + "="+_revision+"=" + _value + (_next == null ? "" : "->") + "]");
    1488         }
    1489     }
    1490
    1491 }