// ========================================================================
// $Id: HttpFields.java,v 1.76 2005/08/13 00:01:24 gregwilkins Exp $
// Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at 
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================

package org.mortbay.http;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.TimeZone;

import javax.servlet.http.Cookie;

import org.apache.commons.logging.Log;
import org.mortbay.log.LogFactory;
import org.mortbay.util.DateCache;
import org.mortbay.util.LazyList;
import org.mortbay.util.LineInput;
import org.mortbay.util.LogSupport;
import org.mortbay.util.QuotedStringTokenizer;
import org.mortbay.util.StringMap;
import org.mortbay.util.StringUtil;
import org.mortbay.util.URI;

/* ------------------------------------------------------------ */
/** HTTP Fields.
 * A collection of HTTP header and or Trailer fields.
 * This class is not synchronized and needs to be protected from
 * concurrent access.
 *
 * This class is not synchronized as it is expected that modifications
 * will only be performed by a single thread.
 *
 * @version $Id: HttpFields.java,v 1.76 2005/08/13 00:01:24 gregwilkins Exp $
 * @author Greg Wilkins (gregw)
 */
public class HttpFields
{
    private static Log log = LogFactory.getLog(HttpFields.class);
        
    /* ------------------------------------------------------------ */
    /** General Fields.
     */
    public final static String
        __CacheControl = "Cache-Control",
        __Connection = "Connection",
        __Date = "Date",
        __Pragma = "Pragma",
        __ProxyConnection = "Proxy-Connection",
        __Trailer = "Trailer",
        __TransferEncoding = "Transfer-Encoding",
        __Upgrade = "Upgrade",
        __Via = "Via",
        __Warning = "Warning";
        
    /* ------------------------------------------------------------ */
    /** Entity Fields.
     */
    public final static String
        __Allow = "Allow",
        __ContentEncoding = "Content-Encoding",
        __ContentLanguage = "Content-Language",
        __ContentLength = "Content-Length",
        __ContentLocation = "Content-Location",
        __ContentMD5 = "Content-MD5",
        __ContentRange = "Content-Range",
        __ContentType = "Content-Type",
        __Expires = "Expires",
        __LastModified = "Last-Modified";
    
    /* ------------------------------------------------------------ */
    /** Request Fields.
     */
    public final static String
        __Accept = "Accept",
        __AcceptCharset = "Accept-Charset",
        __AcceptEncoding = "Accept-Encoding",
        __AcceptLanguage = "Accept-Language",
        __Authorization = "Authorization",
        __Expect = "Expect",
        __Forwarded = "Forwarded",
        __From = "From",
        __Host = "Host",
        __IfMatch = "If-Match",
        __IfModifiedSince = "If-Modified-Since",
        __IfNoneMatch = "If-None-Match",
        __IfRange = "If-Range",
        __IfUnmodifiedSince = "If-Unmodified-Since",
        __KeepAlive = "keep-alive",
        __MaxForwards = "Max-Forwards",
        __ProxyAuthorization = "Proxy-Authorization",
        __Range = "Range",
        __RequestRange = "Request-Range",
        __Referer = "Referer",
        __TE = "TE",
        __UserAgent = "User-Agent",
        __XForwardedFor = "X-Forwarded-For";
    

    /* ------------------------------------------------------------ */
    /** Response Fields.
     */
    public final static String
        __AcceptRanges = "Accept-Ranges",
        __Age = "Age",
        __ETag = "ETag",
        __Location = "Location",
        __ProxyAuthenticate = "Proxy-Authenticate",
        __RetryAfter = "Retry-After",
        __Server = "Server",
        __ServletEngine = "Servlet-Engine",
        __Vary = "Vary",
        __WwwAuthenticate = "WWW-Authenticate";
     
    /* ------------------------------------------------------------ */
    /** Other Fields.
     */
    public final static String
        __Cookie = "Cookie",
        __SetCookie = "Set-Cookie",
        __SetCookie2 = "Set-Cookie2",
        __MimeVersion ="MIME-Version",
        __Identity ="identity",
        __SoapAction ="SOAPAction";

    /* ------------------------------------------------------------ */
    /** Private class to hold Field name info
     */
    private static final class FieldInfo
    {
        String _name;
        String _lname;
        boolean _inlineValues;
        int _hashCode;
        static int __hashCode;
        
        FieldInfo(String name, boolean inline)
        {
            synchronized(FieldInfo.class)
            {
                _name=name;
                _lname=StringUtil.asciiToLowerCase(name);
                _inlineValues=inline;
                
                _hashCode=__hashCode++;
                
                if (__hashCode < __maxCacheSize)
                {
                    FieldInfo oldInfo = (FieldInfo)__info.get(name);
                    if (oldInfo == null)
                    {
                        __info.put(name, this);
                        if (!name.equals(_lname))
                            __info.put(_lname, this);
                    }
                    else
                        _hashCode = oldInfo._hashCode;
                }
            }
        }

        public String toString()
        {
            return "["+_name+","+_hashCode+","+_inlineValues+"]";
        }

        public int hashCode()
        {
            return _hashCode;
        }

        public boolean equals(Object o)
        {
            if (o==null || !(o instanceof FieldInfo))
                return false;
            FieldInfo fi = (FieldInfo)o;
            return
                fi==this ||
                fi._hashCode==_hashCode ||
                fi._name.equals(_name);
        }
    }

    /* ------------------------------------------------------------ */
    private static final StringMap __info = new StringMap(true);
    private static final StringMap __values = new StringMap(true);
    private static final int __maxCacheSize=128;
    
    /* ------------------------------------------------------------ */
    static
    {
        // Initialize FieldInfo's with special values.
        // In order of most frequently used.
        new FieldInfo(__Host,false);
        
        new FieldInfo(__KeepAlive,false);
        new FieldInfo(__Connection,false);
        
        new FieldInfo(__Cookie,false);
        
        new FieldInfo(__Accept,false);
        new FieldInfo(__AcceptLanguage,false);
        new FieldInfo(__AcceptEncoding,false);
        new FieldInfo(__AcceptCharset,false);
        new FieldInfo(__CacheControl,false);
        new FieldInfo(__SetCookie,false);
        new FieldInfo(__SetCookie2,false);
        
        new FieldInfo(__Date,false);
        new FieldInfo(__TransferEncoding,true);
        new FieldInfo(__ContentEncoding,true);
        new FieldInfo(__ContentLength,false);
        new FieldInfo(__Expires,false);
        new FieldInfo(__Expect,false);
        
        new FieldInfo(__Referer,false);
        new FieldInfo(__TE,false);
        new FieldInfo(__UserAgent,false);
        
        new FieldInfo(__IfModifiedSince,false);
        new FieldInfo(__IfRange,false);
        new FieldInfo(__IfUnmodifiedSince,false);

        new FieldInfo(__Location,false);
        new FieldInfo(__Server,false);
        new FieldInfo(__ServletEngine,false);
        
        new FieldInfo(__AcceptRanges,false);
        new FieldInfo(__Range,false);
        new FieldInfo(__RequestRange,false);

        new FieldInfo(__SoapAction,false);
        
        new FieldInfo(__ContentLocation,false);
        new FieldInfo(__ContentMD5,false);
        new FieldInfo(__ContentRange,false);
        new FieldInfo(__ContentType,false);
        new FieldInfo(__LastModified,false);
        new FieldInfo(__Authorization,false);
        new FieldInfo(__From,false);
        new FieldInfo(__MaxForwards,false);
        new FieldInfo(__ProxyAuthenticate,false);
        new FieldInfo(__Age,false);
        new FieldInfo(__ETag,false);
        new FieldInfo(__RetryAfter,false);

        
    }
    
    /* ------------------------------------------------------------ */
    private static FieldInfo getFieldInfo(String name)
    {
        FieldInfo info = (FieldInfo)__info.get(name);
        if (info==null)
            info = new FieldInfo(name,false);
        return info;
    }
    
    /* ------------------------------------------------------------ */
    private static FieldInfo getFieldInfo(char[] name,int offset,int length)
    {
        Map.Entry entry = __info.getEntry(name,offset,length);
        if (entry==null)
            return new FieldInfo(new String(name,offset,length),false);

        return (FieldInfo) entry.getValue();
    }
    
    /* ------------------------------------------------------------ */
    /** Fields Values.
     */    
    public final static String __Chunked = "chunked";
    public final static String __Close = "close";
    public final static String __TextHtml = "text/html";
    public final static String __MessageHttp = "message/http";
    public final static String __WwwFormUrlEncode =
        "application/x-www-form-urlencoded";
    public static final String __ExpectContinue="100-continue";

    static
    {
        __values.put(__KeepAlive,__KeepAlive);
        __values.put(__Chunked,__Chunked);
        __values.put(__Close,__Close);
        __values.put(__TextHtml,__TextHtml);
        __values.put(__MessageHttp,__MessageHttp);
        __values.put(__WwwFormUrlEncode,__WwwFormUrlEncode);
        __values.put(__ExpectContinue,__ExpectContinue);
        __values.put("max-age=0","max-age=0");
        __values.put("no-cache","no-cache");
        __values.put("300","300");
        __values.put("ISO-8859-1, utf-8;q=0.66, *;q=0.66","ISO-8859-1, utf-8;q=0.66, *;q=0.66");
    }
    
    /* ------------------------------------------------------------ */
    public final static String __separators = ", \t";    

    /* ------------------------------------------------------------ */
    public final static char[] __CRLF = {'\015','\012'};
    public final static char[] __COLON = {':',' '};

    /* ------------------------------------------------------------ */
    private static String[] DAYS= { "Sat","Sun","Mon","Tue","Wed","Thu","Fri","Sat" };
    private static String[] MONTHS= { "Jan","Feb","Mar","Apr","May","Jun",
                                      "Jul","Aug","Sep","Oct","Nov","Dec","Jan" };


    /* ------------------------------------------------------------ */
    /** Format HTTP date
     * "EEE, dd MMM yyyy HH:mm:ss 'GMT'" or 
     * "EEE, dd-MMM-yy HH:mm:ss 'GMT'"for cookies
     */
    public static String formatDate(long date, boolean cookie)
    {
        StringBuffer buf = new StringBuffer(32);
        HttpCal gc = new HttpCal();
        gc.setTimeInMillis(date);
        formatDate(buf,gc,cookie);
        return buf.toString();
    } 

    /* ------------------------------------------------------------ */
    /** Format HTTP date
     * "EEE, dd MMM yyyy HH:mm:ss 'GMT'" or 
     * "EEE, dd-MMM-yy HH:mm:ss 'GMT'"for cookies
     */
    public static String formatDate(Calendar calendar, boolean cookie)
    {
        StringBuffer buf = new StringBuffer(32);
        formatDate(buf,calendar,cookie);
        return buf.toString();
    }

    /* ------------------------------------------------------------ */
    /** Format HTTP date
     * "EEE, dd MMM yyyy HH:mm:ss 'GMT'" or 
     * "EEE, dd-MMM-yy HH:mm:ss 'GMT'"for cookies
     */
    public static String formatDate(StringBuffer buf, long date, boolean cookie)
    {
        HttpCal gc = new HttpCal();
        gc.setTimeInMillis(date);
        formatDate(buf,gc,cookie);
        return buf.toString();
    } 

    /* ------------------------------------------------------------ */
    /** Format HTTP date
     * "EEE, dd MMM yyyy HH:mm:ss 'GMT'" or 
     * "EEE, dd-MMM-yy HH:mm:ss 'GMT'"for cookies
     */
    public static void formatDate(StringBuffer buf,Calendar calendar, boolean cookie)
    {
        // "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
        // "EEE, dd-MMM-yy HH:mm:ss 'GMT'",     cookie
        
        int day_of_week  = calendar.get(Calendar.DAY_OF_WEEK);
        int day_of_month = calendar.get(Calendar.DAY_OF_MONTH);
        int month = calendar.get(Calendar.MONTH);
        int year = calendar.get(Calendar.YEAR);
        int century = year/100;
        year=year%100;

        long tm = (calendar instanceof HttpCal)?(((HttpCal)calendar).getTimeInMillis()):calendar.getTime().getTime();
        int epoch=(int)((tm/1000) % (60*60*24));
        int seconds=epoch%60;
        epoch=epoch/60;
        int minutes=epoch%60;
        int hours=epoch/60;
        
        buf.append(DAYS[day_of_week]);
        buf.append(',');
        buf.append(' ');
        StringUtil.append2digits(buf,day_of_month);
        
        if (cookie)
        {
            buf.append('-');
            buf.append(MONTHS[month]);
            buf.append('-');
            StringUtil.append2digits(buf,year);
        }
        else
        {
            buf.append(' ');
            buf.append(MONTHS[month]);
            buf.append(' ');
            StringUtil.append2digits(buf,century);
            StringUtil.append2digits(buf,year);
        }
        buf.append(' ');
        StringUtil.append2digits(buf,hours);
        buf.append(':');
        StringUtil.append2digits(buf,minutes);
        buf.append(':');
        StringUtil.append2digits(buf,seconds);
        buf.append(" GMT");
    }    

    /* -------------------------------------------------------------- */
    private static TimeZone __GMT = TimeZone.getTimeZone("GMT");
    public final static DateCache __dateCache = 
        new DateCache("EEE, dd MMM yyyy HH:mm:ss 'GMT'",
                      Locale.US);     
    
    /* ------------------------------------------------------------ */
    private final static String __dateReceiveFmt[] =
    {
        "EEE, dd MMM yyyy HH:mm:ss zzz",
        "EEE, dd-MMM-yy HH:mm:ss zzz",
        "EEE MMM dd HH:mm:ss yyyy",
        "EEE, dd MMM yyyy HH:mm:ss zzz",
        "EEE, dd-MMM-yy HH:mm:ss zzz",
        "dd MMM yyyy HH:mm:ss",
        "dd-MMM-yy HH:mm:ss",
    };
    public static SimpleDateFormat __dateReceiveSource[];
    public static final ThreadLocal __dateReceiveCache=new ThreadLocal();
    static
    {
        __GMT.setID("GMT");
        __dateCache.setTimeZone(__GMT); 
        __dateReceiveSource = new SimpleDateFormat[__dateReceiveFmt.length];
        for(int i=0;i<__dateReceiveSource.length;i++)
        {
            __dateReceiveSource[i] =
                new SimpleDateFormat(__dateReceiveFmt[i],Locale.US);
            __dateReceiveSource[i].setTimeZone(__GMT);
        }
    }                 
    
    public final static String __01Jan1970=HttpFields.formatDate(0,false);


    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    private static final class Field
    {
        FieldInfo _info;
        String _value;
        Field _next;
        Field _prev;
        int _version;

        /* ------------------------------------------------------------ */
        Field(FieldInfo info, String value, int version)
        {
            _info=info;
            _value=value;
            _next=null;
            _prev=null;
            _version=version;
        }
        
        /* ------------------------------------------------------------ */
        Field(FieldInfo info, char[] buf, int offset, int length, int version)
        {
            Map.Entry valueEntry=__values.getEntry(buf,offset,length);
            String value=null;
            if (valueEntry!=null)
                value=(String)valueEntry.getKey();
            else
                value=new String(buf,offset,length);
            
            _info=info;
            _value=value;
            _next=null;
            _prev=null;
            _version=version;
        }
        
        /* ------------------------------------------------------------ */
        public boolean equals(Object o)
        {
            return (o instanceof Field) &&
                o==this &&
                _version==((Field)o)._version;
        }

        /* ------------------------------------------------------------ */
        public int hashCode()
        {
            return _info.hashCode()*_version;
        }
        
        /* ------------------------------------------------------------ */
        void clear()
        {
            _version=-1;
        }
        
        /* ------------------------------------------------------------ */
        void destroy()
        {
            _info=null;
            _value=null;
            _next=null;
            _prev=null;
            _version=-1;
        }
        
        /* ------------------------------------------------------------ */
        void reset(String value,int version)
        {
            _value=value;
            _version=version;
        }
        
        /* ------------------------------------------------------------ */
        /** Reassign a value to this field.
         * Checks if the value is the same as that in the char array, if so
         * then just reuse existing value.
         */
        void reset(char[] buf, int offset, int length, int version)
        {  
            _version=version;
            if (!StringUtil.equals(_value,buf,offset,length))
            {
                Map.Entry valueEntry=__values.getEntry(buf,offset,length);
                String value=null;
                if (valueEntry!=null)
                    value=(String)valueEntry.getKey();
                else
                    value=new String(buf,offset,length);
                _value=value;
            }
        }

        
        /* ------------------------------------------------------------ */
        void write(Writer writer, int version)
            throws IOException
        {
            if (_info==null || _version!=version)
                return;
            if (_info._inlineValues)
            {
                if (_prev!=null)
                    return;
                writer.write(_info._name);
                writer.write(__COLON);
                Field f=this;
                while (true)
                {
                    writer.write(QuotedStringTokenizer.quote(f._value,", \t"));
                    f=f._next;
                    if (f==null)
                        break;
                    writer.write(",");
                }
                writer.write(__CRLF);
            }
            else
            {
                writer.write(_info._name);
                writer.write(__COLON);
                writer.write(_value);
                writer.write(__CRLF);
            }
        }

        /* ------------------------------------------------------------ */
        String getDisplayName()
        {
            return _info._name;
        }
        
        /* ------------------------------------------------------------ */
        public String toString()
        {
            return ("["+
                (_prev==null?"":"<-")+
                getDisplayName()+"="+_value+
                (_next==null?"":"->")+
                "]");
        }
    }
    
    /* ------------------------------------------------------------ */
    private static Float __one = new Float("1.0");
    private static Float __zero = new Float("0.0");
    private static StringMap __qualities=new StringMap();
    static
    {
        __qualities.put(null,__one);
        __qualities.put("1.0",__one);
        __qualities.put("1",__one);
        __qualities.put("0.9",new Float("0.9"));
        __qualities.put("0.8",new Float("0.8"));
        __qualities.put("0.7",new Float("0.7"));
        __qualities.put("0.66",new Float("0.66"));
        __qualities.put("0.6",new Float("0.6"));
        __qualities.put("0.5",new Float("0.5"));
        __qualities.put("0.4",new Float("0.4"));
        __qualities.put("0.33",new Float("0.33"));
        __qualities.put("0.3",new Float("0.3"));
        __qualities.put("0.2",new Float("0.2"));
        __qualities.put("0.1",new Float("0.1"));
        __qualities.put("0",__zero);
        __qualities.put("0.0",__zero);
    }
    
    
    /* -------------------------------------------------------------- */
    private ArrayList _fields=new ArrayList(15);
    private int[] _index=new int[__maxCacheSize];
    private int _version;
    private SimpleDateFormat _dateReceive[]; 
    private StringBuffer _dateBuffer;
    private HttpCal _calendar;

    /* ------------------------------------------------------------ */
    /** Constructor. 
     */
    public HttpFields()
    {
        Arrays.fill(_index,-1);
    }

    
    /* ------------------------------------------------------------ */
    public int size()
    {
        return _fields.size();
    }
    
    /* -------------------------------------------------------------- */
    /** Get enumeration of header _names.
     * Returns an enumeration of strings representing the header _names
     * for this request. 
     */
    public Enumeration getFieldNames()
    {
        return new Enumeration()
            {
                int i=0;
                Field field=null;

                public boolean hasMoreElements()
                {
                    if (field!=null)
                        return true;
                    while (i<_fields.size())
                    {
                        Field f=(Field)_fields.get(i++);
                        if (f!=null && f._version==_version && f._prev==null)
                        {
                            field=f;
                            return true;
                        }
                    }
                    return false;
                }

                public Object nextElement()
                    throws NoSuchElementException
                {
                    if (field!=null || hasMoreElements())
                    {
                        String n=field._info._name;
                        field=null;
                        return n;
                    }
                    throw new NoSuchElementException();
                }
            };
    }
    
    /* ------------------------------------------------------------ */
    Field getField(String name)
    {       
        FieldInfo info=getFieldInfo(name);
        return getField(info,true);
    }
        
    /* ------------------------------------------------------------ */
    Field getField(FieldInfo info, boolean getValid)
    {
        int hi=info.hashCode();
        
        if (hi<_index.length)
        {
            if (_index[hi]>=0)
            {
                Field field=(Field)(_fields.get(_index[hi]));
                
                return (field!=null && (!getValid||field._version==_version))?field:null;
            }
        }
        else
        {    
            for (int i=0;i<_fields.size();i++)
            {
                Field field=(Field)_fields.get(i);
                if (info.equals(field._info) && (!getValid||field._version==_version))
                    return field;
            }
        }
        return null;
    }    
    
    /* ------------------------------------------------------------ */
    public boolean containsKey(String name)
    {
        FieldInfo info=getFieldInfo(name);
        return getField(info,true)!=null;
    }
    
    /* -------------------------------------------------------------- */
    /**
     * @return the value of a field, or null if not found. For
     * multiple fields of the same name, only the first is returned.
     * @param name the case-insensitive field name
     */
    public String get(String name)
    {
        FieldInfo info=getFieldInfo(name);
        Field field=getField(info,true);
        if (field!=null)
            return field._value;
        return null;
    }
    
    /* -------------------------------------------------------------- */
    /** Get multi headers
     * @return Enumeration of the values, or null if no such header.
     * @param name the case-insensitive field name
     */
    public Enumeration getValues(String name)
    {
        FieldInfo info=getFieldInfo(name);
        final Field field=getField(info,true);

        if (field!=null)
        {            
            return new Enumeration()
                {
                    Field f=field;
                    
                    public boolean hasMoreElements()
                    {
                        while (f!=null && f._version!=_version)
                            f=f._next;
                        return f!=null;
                    }
                        
                    public Object nextElement()
                        throws NoSuchElementException
                    {
                        if (f==null)
                            throw new NoSuchElementException();
                        Field n=f;
                        do f=f._next; while (f!=null && f._version!=_version);
                        return n._value;
                    }
                };
        }
        return null;
    }
    
    /* -------------------------------------------------------------- */
    /** Get multi field values with separator.
     * The multiple values can be represented as separate headers of
     * the same name, or by a single header using the separator(s), or
     * a combination of both. Separators may be quoted.
     * @param name the case-insensitive field name
     * @param separators String of separators.
     * @return Enumeration of the values, or null if no such header.
     */
    public Enumeration getValues(String name,final String separators)
    {
        final Enumeration e = getValues(name);
        if (e==null)
            return null;
        return new Enumeration()
            {
                QuotedStringTokenizer tok=null;
                public boolean hasMoreElements()
                {
                    if (tok!=null && tok.hasMoreElements())
                            return true;
                    while (e.hasMoreElements())
                    {
                        String value=(String)e.nextElement();
                        tok=new QuotedStringTokenizer(value,separators,false,false);
                        if (tok.hasMoreElements())
                            return true;
                    }
                    tok=null;
                    return false;
                }
                        
                public Object nextElement()
                    throws NoSuchElementException
                {
                    if (!hasMoreElements())
                        throw new NoSuchElementException();
                    String next=(String) tok.nextElement();
		    if (next!=null)next=next.trim();
		    return next;
                }
            };
    }
    
    /* -------------------------------------------------------------- */
    /** Set a field.
     * @param name the name of the field
     * @param value the value of the field. If null the field is cleared.
     */
    public String put(String name,String value)
    {
        if (value==null)
            return remove(name);
        
        FieldInfo info=getFieldInfo(name);
        Field field=getField(info,false);
        // Look for value to replace.
        if (field!=null)
        {
            String old=(field._version==_version)?field._value:null;
            field.reset(value,_version);

            field=field._next;
            while(field!=null)
            {
                field.clear();
                field=field._next;
            }
            return old;    
        }
        else
        {
            // new value;
            field=new Field(info,value,_version);
            int hi=info.hashCode();
            if (hi<_index.length)
                _index[hi]=_fields.size();
            _fields.add(field);
            return null;
        }
    }
    
        
    /* -------------------------------------------------------------- */
    /** Set a field.
     * @param name the name of the field
     * @param list the List value of the field. If null the field is cleared.
     */
    public void put(String name,List list)
    {
        if (list==null || list.size()==0)
        {
            remove(name);
            return;
        }
        
        Object v=list.get(0);
        if (v!=null)
            put(name,v.toString());
        else
            remove(name);
        
        if (list.size()>1)
        {    
            java.util.Iterator iter = list.iterator();
            iter.next();
            while(iter.hasNext())
            {
                v=iter.next();
                if (v!=null)
                    add(name,v.toString());
            }
        }
    }

    
    /* -------------------------------------------------------------- */
    /** Add to or set a field.
     * If the field is allowed to have multiple values, add will add
     * multiple headers of the same name.
     * @param name the name of the field
     * @param value the value of the field.
     * @exception IllegalArgumentException If the name is a single
     *            valued field and already has a value.
     */
    public void add(String name,String value)
        throws IllegalArgumentException
    {
        if (value==null)
            throw new IllegalArgumentException("null value");
        
        FieldInfo info=getFieldInfo(name);
        Field field=getField(info,false);
        Field last=null;
        if (field!=null)
        {
            while(field!=null && field._version==_version)
            {
                last=field;
                field=field._next;
            }
        }

        if (field!=null)    
            field.reset(value,_version);
        else
        {
            // create the field
            field=new Field(info,value,_version);
            
            // look for chain to add too
            if(last!=null)
            {
                field._prev=last;
                last._next=field;    
            }
            else if (info.hashCode()<_index.length)
                _index[info.hashCode()]=_fields.size();
            
            _fields.add(field);
        }
    }
    
    /* ------------------------------------------------------------ */
    /** Remove a field.
     * @param name 
     */
    public String remove(String name)
    {
        String old=null;
        FieldInfo info=getFieldInfo(name);
        Field field=getField(info,true);

        if (field!=null)
        {
            old=field._value;
            while(field!=null)
            {
                field.clear();
                field=field._next;
            }
        }
        
        return old;
    }
   
    /* -------------------------------------------------------------- */
    /** Get a header as an integer value.
     * Returns the value of an integer field or -1 if not found.
     * The case of the field name is ignored.
     * @param name the case-insensitive field name
     * @exception NumberFormatException If bad integer found
     */
    public int getIntField(String name)
        throws NumberFormatException
    {
        String val = valueParameters(get(name),null);
        if (val!=null)
            return Integer.parseInt(val);
        return -1;
    }
    
    /* -------------------------------------------------------------- */
    /** Get a header as a date value.
     * Returns the value of a date field, or -1 if not found.
     * The case of the field name is ignored.
     * @param name the case-insensitive field name
     */
    public long getDateField(String name)
    {
        String val = valueParameters(get(name),null);
        if (val==null)
            return -1;

        if (_dateReceive==null)
       {
               _dateReceive=(SimpleDateFormat[])__dateReceiveCache.get();
               if (_dateReceive==null)
               {
                    _dateReceive=(SimpleDateFormat[]) new SimpleDateFormat[__dateReceiveSource.length];
                    __dateReceiveCache.set(_dateReceive);
               }
       }
       
        for (int i=0;i<_dateReceive.length;i++)
        {
            // clone formatter for thread safety
            if (_dateReceive[i]==null)
                _dateReceive[i]=(SimpleDateFormat)__dateReceiveSource[i].clone();
            
            try{
                Date date=(Date)_dateReceive[i].parseObject(val);
                return date.getTime();
            }
            catch(java.lang.Exception e)
            {
                LogSupport.ignore(log,e);
            }
        }
        if (val.endsWith(" GMT"))
        {
            val=val.substring(0,val.length()-4);
            for (int i=0;i<_dateReceive.length;i++)
            {
                try{
                    Date date=(Date)_dateReceive[i].parseObject(val);
                    return date.getTime();
                }
                catch(java.lang.Exception e)
                {
                    LogSupport.ignore(log,e);
                }
            }
        }

        throw new IllegalArgumentException(val);
    }
    
    /* -------------------------------------------------------------- */
    /**
     * Sets the value of an integer field.
     * @param name the field name
     * @param value the field integer value
     */
    public void putIntField(String name, int value)
    {
        put(name, Integer.toString(value));
    }

    /* -------------------------------------------------------------- */
    /**
     * Sets the value of a date field.
     * @param name the field name
     * @param date the field date value
     */
    public void putDateField(String name, Date date)
    {
        putDateField(name,date.getTime());
    }
    
    /* -------------------------------------------------------------- */
    /**
     * Adds the value of a date field.
     * @param name the field name
     * @param date the field date value
     */
    public void addDateField(String name, Date date)
    {
        addDateField(name,date.getTime());
    }
    
    /* -------------------------------------------------------------- */
    /**
     * Adds the value of a date field.
     * @param name the field name
     * @param date the field date value
     */
    public void addDateField(String name, long date)
    {
        if (_dateBuffer==null)
        {
            _dateBuffer=new StringBuffer(32);
            _calendar=new HttpCal();
        }
        _dateBuffer.setLength(0);
        _calendar.setTimeInMillis(date);
        formatDate(_dateBuffer, _calendar, false);
        add(name, _dateBuffer.toString());
    }
    
    /* -------------------------------------------------------------- */
    /**
     * Sets the value of a date field.
     * @param name the field name
     * @param date the field date value
     */
    public void putDateField(String name, long date)
    {
        if (_dateBuffer==null)
        {
            _dateBuffer=new StringBuffer(32);
            _calendar=new HttpCal();
        }
        _dateBuffer.setLength(0);
        _calendar.setTimeInMillis(date);
        formatDate(_dateBuffer, _calendar, false);
        put(name, _dateBuffer.toString());
    }

    /* -------------------------------------------------------------- */
    /** Read HttpHeaders from inputStream.
     */
    public void read(LineInput in)
        throws IOException
    {  
        Field last=null;
        char[] buf=null;
        int size=0;
        org.mortbay.util.LineInput.LineBuffer line_buffer;
        synchronized(in)
        {
            line:
            while ((line_buffer=in.readLineBuffer())!=null)
            {
                // check space in the lowercase buffer
                buf=line_buffer.buffer;
                size=line_buffer.size;
                if (size==0)
                    break;
                
                // setup loop state machine
                int i1=-1;
                int i2=-1;
                int name_l=0;
                int i=0;
                char c=buf[0];
                
                // Check for continuity line
                if (c!=' ' && c!='\t')
                {
                    i2=0;
                    // reading name upto :
                    for (i=1;i<size;i++)
                    {
                        c=buf[i];
                        if (c==':')
                        {
                            name_l=i2+1; 
                            break;
                        }
                        
                        if (c!=' '&&c!='\t')
                            i2=i;
                    }
                }   

                // skip whitespace after : or start of continuity line
                for (i++;i<size;i++)
                {
                    c=buf[i];
                    if (c!=' ' && c!='\t')
                    {
                        i1=i;
                        i2=i-1;
                        break;
                    }
                }
                
                // Reverse Parse the "name : value" to last char of value
                for (i=size;i-->i1 && i>=0;)
                {
                    c=buf[i];
                    if (c!=' ' && c!='\t')
                    {
                        i2=i;
                        break;
                    }
                }

                // If no name, it is a continuation line
                if (name_l<=0)
                {
                    if (i1>0 && last!=null)
                        last._value=last._value+' '+new String(buf,i1,i2-i1+1);
                    continue;
                }

                // create the field.
                FieldInfo info = getFieldInfo(buf,0,name_l);
                Field field=getField(info,false);
                last=null;
                if (field!=null)
                {
                    while(field!=null && field._version==_version)
                    {
                        last=field;
                        field=field._next;
                    }
                }
                
                if (field!=null)
                {
                    if (i1>=0)
                        field.reset(buf,i1,i2-i1+1,_version);
                    else
                        field.reset("",_version);
                }
                else
                {
                    // create the field
                    if (i1>=0)
                        field=new Field(info,buf,i1,i2-i1+1,_version);
                    else
                        field=new Field(info,"",_version);
                    
                    // look for chain to add too
                    if(last!=null)
                    {
                        field._prev=last;
                        last._next=field; 
                          
                    }
                    else if (info.hashCode()<_index.length)
                        _index[info.hashCode()]=_fields.size(); 
                    _fields.add(field);
                }
                
                last=field;
            }
        }
    }

    
    /* -------------------------------------------------------------- */
    /* Write Extra HTTP headers.
     */
    public void write(Writer writer)
        throws IOException
    {
        synchronized(writer)
        {
            for (int i=0;i<_fields.size();i++)
            {
                Field field=(Field)_fields.get(i);
                if (field!=null)
                    field.write(writer,_version);
            }
            writer.write(__CRLF);
        }
    }
    
    
    /* -------------------------------------------------------------- */
    public String toString()
    {
        try
        {
            StringWriter writer = new StringWriter();
            write(writer);
            return writer.toString();
        }
        catch(Exception e)
        {}
        return null;
    }

    /* ------------------------------------------------------------ */
    /** Clear the header.
     */
    public void clear()
    {
        _version++;
        if (_version>1000)
        {
            _version=0;
            for (int i=_fields.size();i-->0;)
            {
                Field field=(Field)_fields.get(i);
                if (field!=null)
                    field.clear();
            }
        }
    }
    
    /* ------------------------------------------------------------ */
    /** Destroy the header.
     * Help the garbage collector by null everything that we can.
     */
    public void destroy()
    {   
        for (int i=_fields.size();i-->0;)
        {
            Field field=(Field)_fields.get(i);
            if (field!=null)
                field.destroy();
        }
        _fields=null;
        _index=null;
        _dateBuffer=null;
        _calendar=null;
        _dateReceive=null;
    }
    
    /* ------------------------------------------------------------ */
    /** Get field value parameters.
     * Some field values can have parameters.  This method separates
     * the value from the parameters and optionally populates a
     * map with the paramters. For example:<PRE>
     *   FieldName : Value ; param1=val1 ; param2=val2
     * </PRE>
     * @param value The Field value, possibly with parameteres.
     * @param parameters A map to populate with the parameters, or null
     * @return The value.
     */
    public static String valueParameters(String value, Map parameters)
    {
        if (value==null)
            return null;
        
        int i = value.indexOf(';');
        if (i<0)
            return value;
        if (parameters==null)
            return value.substring(0,i).trim();

        StringTokenizer tok1 =
            new QuotedStringTokenizer(value.substring(i),";",false,true);
        while(tok1.hasMoreTokens())
        {
            String token=tok1.nextToken();
            StringTokenizer tok2 =
                new QuotedStringTokenizer(token,"= ");
            if (tok2.hasMoreTokens())
            {
                String paramName=tok2.nextToken();
                String paramVal=null;
                if (tok2.hasMoreTokens())
                    paramVal=tok2.nextToken();
                parameters.put(paramName,paramVal);
            }
        }
        
        return value.substring(0,i).trim();
    }

    /* ------------------------------------------------------------ */
    public static Float getQuality(String value)
    {
        if (value==null)
            return __zero;
        
        int qe=value.indexOf(";");
        if (qe++<0 || qe==value.length())
            return __one;
        
        if (value.charAt(qe++)=='q')
        {
            qe++;
            Map.Entry entry=__qualities.getEntry(value,qe,value.length()-qe);
            if (entry!=null)
                return (Float)entry.getValue();
        }
        
        HashMap params = new HashMap(3);
        valueParameters(value,params);
        String qs=(String)params.get("q");
        Float q=(Float)__qualities.get(qs);
        if (q==null)
        {
            try{q=new Float(qs);}
            catch(Exception e){q=__one;}
        }
        return q;
    }

    /* ------------------------------------------------------------ */
    /** List values in quality order.
     * @param enm Enumeration of values with quality parameters
     * @return values in quality order.
     */
    public static List qualityList(Enumeration enm)
    {
        if(enm==null || !enm.hasMoreElements())
            return Collections.EMPTY_LIST;

        Object list=null;
        Object qual=null;

        // Assume list will be well ordered and just add nonzero
        while(enm.hasMoreElements())
        {
            String v=enm.nextElement().toString();
            Float q=getQuality(v);

            if (q.floatValue()>=0.001)
            {
                list=LazyList.add(list,v);
                qual=LazyList.add(qual,q);
            }
        }

        List vl=LazyList.getList(list,false);
        if (vl.size()<2)
            return vl;

        List ql=LazyList.getList(qual,false);

        // sort list with swaps
        Float last=__zero;
        for (int i=vl.size();i-->0;)
        {
            Float q = (Float)ql.get(i);
            if (last.compareTo(q)>0)
            {
                Object tmp=vl.get(i);
                vl.set(i,vl.get(i+1));
                vl.set(i+1,tmp);
                ql.set(i,ql.get(i+1));
                ql.set(i+1,q);
                last=__zero;
                i=vl.size();
                continue;
            }
            last=q;
        }
        ql.clear();
        return vl;
    }
    


    /* ------------------------------------------------------------ */
    /** Format a set cookie value
     * @param cookie The cookie.
     */
    public void addSetCookie(Cookie cookie)
    {
        String name=cookie.getName();
        String value=cookie.getValue();
        int version=cookie.getVersion();
        
        // Check arguments
        if (name==null || name.length()==0)
            throw new IllegalArgumentException("Bad cookie name");

        // Format value and params
        StringBuffer buf = new StringBuffer(128);
        String name_value_params=null;
        synchronized(buf)
        {
            buf.append(name);
            buf.append('=');
            if (value!=null && value.length()>0)
            {
                if (version==0)
                    URI.encodeString(buf,value,"\";, ");
                else
                    buf.append(QuotedStringTokenizer.quote(value,"\";, "));
            }

            if (version>0)
            {
                buf.append(";Version=");
                buf.append(version);
                String comment=cookie.getComment();
                if (comment!=null && comment.length()>0)
                {
                    buf.append(";Comment=");
                    QuotedStringTokenizer.quote(buf,comment);
                }
            }
            String path=cookie.getPath();
            if (path!=null && path.length()>0)
            {
                buf.append(";Path=");
                buf.append(path);
            }
            String domain=cookie.getDomain();
            if (domain!=null && domain.length()>0)
            {
                buf.append(";Domain=");
                buf.append(domain.toLowerCase());// lowercase for IE
            }
            long maxAge = cookie.getMaxAge();
            if (maxAge>=0)
            {
                if (version==0)
                {
                    buf.append(";Expires=");
                    if (maxAge==0)
                        buf.append(__01Jan1970);
                    else
                        formatDate(buf,System.currentTimeMillis()+1000L*maxAge,true);
                }
                else
                {
                    buf.append (";Max-Age=");
                    buf.append (cookie.getMaxAge());
                }
            }
            else if (version>0)
            {
                buf.append (";Discard");
            }
            if (cookie.getSecure())
            {
                buf.append(";Secure");
            }
            if (cookie instanceof HttpOnlyCookie)
                buf.append(";HttpOnly");
            
            name_value_params = buf.toString();
        }
        put(__Expires,__01Jan1970);
        add(__SetCookie,name_value_params);
    }

    /* ------------------------------------------------------------ */
    /** Add fields from another HttpFields instance.
     * Single valued fields are replaced, while all others are added.
     * @param fields 
     */
    public void add(HttpFields fields)
    {
        if (fields==null)
            return;

        Enumeration enm = fields.getFieldNames();
        while( enm.hasMoreElements() )
        {
            String name = (String)enm.nextElement();
            Enumeration values = fields.getValues(name);
            while(values.hasMoreElements())
                add(name,(String)values.nextElement());
        }
    }

    /* ------------------------------------------------------------ */
    /** 
     * return an iterator for field name:value pairs
     * @return an HttpFields.Iterator
     */
    public Iterator iterator() {return new EntryIterator();}

    
    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    public class Entry
    {
        protected int _i;
        
        Entry(int i) {_i=i;}
        public String getKey() {return ((Field)_fields.get(_i)).getDisplayName();}
        public String getValue() {return ((Field)_fields.get(_i))._value;}
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    private class EntryIterator implements Iterator
    {
        protected int _i=0;
        public boolean hasNext() {return (_i<_fields.size());}
        public Object next() throws NoSuchElementException {return new Entry(_i++);}
        public void remove() { throw new UnsupportedOperationException();}
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /* handle 1.3 protected methods                        */
    private static class HttpCal extends GregorianCalendar
    {
        HttpCal()
        {
            super(__GMT);
        }

        /* ------------------------------------------------------------------------------- */
        /**
         * @see java.util.Calendar#setTimeInMillis(long)
         */
        public void setTimeInMillis(long arg0)
        {
            super.setTimeInMillis(arg0);
        }
        /* ------------------------------------------------------------------------------- */
        /**
         * @see java.util.Calendar#getTimeInMillis()
         */
        public long getTimeInMillis()
        {
            return super.getTimeInMillis();
        }
    }
}
