view src/org/eclipse/jetty/http/HttpFields.java @ 825:7fb7c1915788

remove jetty.util.B64Code
author Franklin Schmidt <fschmidt@gmail.com>
date Wed, 14 Sep 2016 16:38:33 -0600
parents 8e9db0bbf4f9
children 86338c0029a9
line wrap: on
line source

//
//  ========================================================================
//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.http;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Collection;
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 java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.BufferCache;
import org.eclipse.jetty.io.BufferCache.CachedBuffer;
import org.eclipse.jetty.io.BufferDateCache;
import org.eclipse.jetty.io.BufferUtil;
import org.eclipse.jetty.io.ByteArrayBuffer;
import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringMap;
import org.eclipse.jetty.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/* ------------------------------------------------------------ */
/**
 * HTTP Fields. A collection of HTTP header and or Trailer fields. 
 * 
 * <p>This class is not synchronized as it is expected that modifications will only be performed by a
 * single thread.
 * 
 * 
 */
public class HttpFields
{
    private static final Logger LOG = LoggerFactory.getLogger(HttpFields.class);
    
    /* ------------------------------------------------------------ */
    public static final String __COOKIE_DELIM="\"\\\n\r\t\f\b%+ ;=";
    public static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
    public static final BufferDateCache __dateCache = new BufferDateCache("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);

    /* -------------------------------------------------------------- */
    static
    {
        __GMT.setID("GMT");
        __dateCache.setTimeZone(__GMT);
    }
    
    /* ------------------------------------------------------------ */
    public final static String __separators = ", \t";

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

    
    /* ------------------------------------------------------------ */
    private static class DateGenerator
    {
        private final StringBuilder buf = new StringBuilder(32);
        private final GregorianCalendar gc = new GregorianCalendar(__GMT);
        
        /**
         * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" 
         */
        public String formatDate(long date)
        {
            buf.setLength(0);
            gc.setTimeInMillis(date);
            
            int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
            int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
            int month = gc.get(Calendar.MONTH);
            int year = gc.get(Calendar.YEAR);
            int century = year / 100;
            year = year % 100;
            
            int hours = gc.get(Calendar.HOUR_OF_DAY);
            int minutes = gc.get(Calendar.MINUTE);
            int seconds = gc.get(Calendar.SECOND);

            buf.append(DAYS[day_of_week]);
            buf.append(',');
            buf.append(' ');
            StringUtil.append2digits(buf, day_of_month);

            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");
            return buf.toString();
        }

        /* ------------------------------------------------------------ */
        /**
         * Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies
         */
        public void formatCookieDate(StringBuilder buf, long date)
        {
            gc.setTimeInMillis(date);
            
            int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
            int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
            int month = gc.get(Calendar.MONTH);
            int year = gc.get(Calendar.YEAR);
            year = year % 10000;

            int epoch = (int) ((date / 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);

            buf.append('-');
            buf.append(MONTHS[month]);
            buf.append('-');
            StringUtil.append2digits(buf, year/100);
            StringUtil.append2digits(buf, year%100);
            
            buf.append(' ');
            StringUtil.append2digits(buf, hours);
            buf.append(':');
            StringUtil.append2digits(buf, minutes);
            buf.append(':');
            StringUtil.append2digits(buf, seconds);
            buf.append(" GMT");
        }
    }

    /* ------------------------------------------------------------ */
    private static final ThreadLocal<DateGenerator> __dateGenerator =new ThreadLocal<DateGenerator>()
    {
        @Override
        protected DateGenerator initialValue()
        {
            return new DateGenerator();
        }
    };
    
    /* ------------------------------------------------------------ */
    /**
     * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" 
     */
    public static String formatDate(long date)
    {
        return __dateGenerator.get().formatDate(date);
    }

    /* ------------------------------------------------------------ */
    /**
     * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
     */
    public static void formatCookieDate(StringBuilder buf, long date)
    {
        __dateGenerator.get().formatCookieDate(buf,date);
    }
    
    /* ------------------------------------------------------------ */
    /**
     * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
     */
    public static String formatCookieDate(long date)
    {
        StringBuilder buf = new StringBuilder(28);
        formatCookieDate(buf, date);
        return buf.toString();
    }

    /* ------------------------------------------------------------ */
    private final static String __dateReceiveFmt[] =
    {   
        "EEE, dd MMM yyyy HH:mm:ss zzz", 
        "EEE, dd-MMM-yy HH:mm:ss",
        "EEE MMM dd HH:mm:ss yyyy",

        "EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz", 
        "EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss", 
        "EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz", 
        "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", 
        "MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz",  
        "EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz", 
        "EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss",
    };

    /* ------------------------------------------------------------ */
    private static class DateParser
    {
        final SimpleDateFormat _dateReceive[]= new SimpleDateFormat[__dateReceiveFmt.length];
 
        long parse(final String dateVal)
        {
            for (int i = 0; i < _dateReceive.length; i++)
            {
                if (_dateReceive[i] == null)
                {
                    _dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US);
                    _dateReceive[i].setTimeZone(__GMT);
                }

                try
                {
                    Date date = (Date) _dateReceive[i].parseObject(dateVal);
                    return date.getTime();
                }
                catch (java.lang.Exception e)
                {
                    // LOG.ignore(e);
                }
            }
            
            if (dateVal.endsWith(" GMT"))
            {
                final String val = dateVal.substring(0, dateVal.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)
                    {
                        // LOG.ignore(e);
                    }
                }
            }    
            return -1;
        }
    }

    /* ------------------------------------------------------------ */
    public static long parseDate(String date)
    {
        return __dateParser.get().parse(date);
    }

    /* ------------------------------------------------------------ */
    private static final ThreadLocal<DateParser> __dateParser =new ThreadLocal<DateParser>()
    {
        @Override
        protected DateParser initialValue()
        {
            return new DateParser();
        }
    };

    /* -------------------------------------------------------------- */
    public final static String __01Jan1970=formatDate(0);
    public final static Buffer __01Jan1970_BUFFER=new ByteArrayBuffer(__01Jan1970);
    public final static String __01Jan1970_COOKIE = formatCookieDate(0).trim();

    /* -------------------------------------------------------------- */
    private final ArrayList<Field> _fields = new ArrayList<Field>(20);
    private final HashMap<Buffer,Field> _names = new HashMap<Buffer,Field>(32);
    
    /* ------------------------------------------------------------ */
    /**
     * Constructor.
     */
    public HttpFields()
    {
    }

    // TODO externalize this cache so it can be configurable
    private static ConcurrentMap<String, Buffer> __cache = new ConcurrentHashMap<String, Buffer>();
    private static int __cacheSize = Integer.getInteger("org.eclipse.jetty.http.HttpFields.CACHE",2000);
    
    /* -------------------------------------------------------------- */
    private Buffer convertValue(String value)
    {
        Buffer buffer = __cache.get(value);
        if (buffer!=null)
            return buffer;
        
        try
        {   
            buffer = new ByteArrayBuffer(value,StringUtil.__ISO_8859_1);
            
            if (__cacheSize>0)
            {
                if (__cache.size()>__cacheSize)
                    __cache.clear();
                Buffer b=__cache.putIfAbsent(value,buffer);
                if (b!=null)
                    buffer=b;
            }
            
            return buffer;
        }
        catch (UnsupportedEncodingException e)
        {
            throw new RuntimeException(e);
        }
    }
    
    /* -------------------------------------------------------------- */
    /**
     * Get Collection of header names. 
     */
    public Collection<String> getFieldNamesCollection()
    {
        final List<String> list = new ArrayList<String>(_fields.size());

	for (Field f : _fields)
	{
	    if (f!=null)
	        list.add(BufferUtil.to8859_1_String(f._name));
	}
	return list;
    }
    
    /* -------------------------------------------------------------- */
    /**
     * Get enumeration of header _names. Returns an enumeration of strings representing the header
     * _names for this request.
     */
    public Enumeration<String> getFieldNames()
    {
        final Enumeration<?> buffers = Collections.enumeration(_names.keySet());
        return new Enumeration<String>()
        {
            public String nextElement()
            {
                return buffers.nextElement().toString();
            }
            
            public boolean hasMoreElements()
            {
                return buffers.hasMoreElements();
            }
        }; 
    }
    
    /* ------------------------------------------------------------ */
    public int size()
    {
        return _fields.size();
    }
    
    /* ------------------------------------------------------------ */
    /**
     * Get a Field by index.
     * @return A Field value or null if the Field value has not been set
     * 
     */
    public Field getField(int i)
    {
        return _fields.get(i);
    }

    /* ------------------------------------------------------------ */
    private Field getField(String name)
    {
        return _names.get(HttpHeaders.CACHE.lookup(name));
    }

    /* ------------------------------------------------------------ */
    private Field getField(Buffer name)
    {
        return _names.get(HttpHeaders.CACHE.lookup(name));
    }

    /* ------------------------------------------------------------ */
    public boolean containsKey(Buffer name)
    {
        return _names.containsKey(HttpHeaders.CACHE.lookup(name));
    }

    /* ------------------------------------------------------------ */
    public boolean containsKey(String name)
    {
        return _names.containsKey(HttpHeaders.CACHE.lookup(name));
    }

    /* -------------------------------------------------------------- */
    /**
     * @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 getStringField(String name)
    {
        Field field = getField(name);
        return field==null?null:field.getValue();
    }

    /* -------------------------------------------------------------- */
    /**
     * @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 getStringField(Buffer name)
    {
        Field field = getField(name);
        return field==null?null:field.getValue();
    }

    /* -------------------------------------------------------------- */
    /**
     * @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 Buffer get(Buffer name)
    {
        Field field = getField(name);
        return field==null?null:field._value;
    }


    /* -------------------------------------------------------------- */
    /**
     * Get multi headers
     * 
     * @return Enumeration of the values, or null if no such header.
     * @param name the case-insensitive field name
     */
    public Collection<String> getValuesCollection(String name)
    {
        Field field = getField(name);
	if (field==null)
	    return null;

        final List<String> list = new ArrayList<String>();

	while(field!=null)
	{
	    list.add(field.getValue());
	    field=field._next;
	}
	return list;
    }

    /* -------------------------------------------------------------- */
    /**
     * Get multi headers
     * 
     * @return Enumeration of the values
     * @param name the case-insensitive field name
     */
    public Enumeration<String> getValues(String name)
    {
        final Field field = getField(name);
        if (field == null) 
        {
            List<String> empty=Collections.emptyList();
            return Collections.enumeration(empty);
        }

        return new Enumeration<String>()
        {
            Field f = field;

            public boolean hasMoreElements()
            {
                return f != null;
            }

            public String nextElement() throws NoSuchElementException
            {
                if (f == null) throw new NoSuchElementException();
                Field n = f;
                f = f._next;
                return n.getValue();
            }
        };
    }

    /* -------------------------------------------------------------- */
    /**
     * Get multi headers
     * 
     * @return Enumeration of the value Strings
     * @param name the case-insensitive field name
     */
    public Enumeration<String> getValues(Buffer name)
    {
        final Field field = getField(name);
        if (field == null) 
        {
            List<String> empty=Collections.emptyList();
            return Collections.enumeration(empty);
        }

        return new Enumeration<String>()
        {
            Field f = field;

            public boolean hasMoreElements()
            {
                return f != null;
            }

            public String nextElement() throws NoSuchElementException
            {
                if (f == null) throw new NoSuchElementException();
                Field n = f;
                f = f._next;
                return n.getValue();
            }
        };
    }

    /* -------------------------------------------------------------- */
    /**
     * 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<String> getValues(String name, final String separators)
    {
        final Enumeration<String> e = getValues(name);
        if (e == null) 
            return null;
        return new Enumeration<String>()
        {
            QuotedStringTokenizer tok = null;

            public boolean hasMoreElements()
            {
                if (tok != null && tok.hasMoreElements()) return true;
                while (e.hasMoreElements())
                {
                    String value = e.nextElement();
                    tok = new QuotedStringTokenizer(value, separators, false, false);
                    if (tok.hasMoreElements()) return true;
                }
                tok = null;
                return false;
            }

            public String 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 void put(String name, String value)
    {
        if (value==null)
            remove(name);
        else
        {
            Buffer n = HttpHeaders.CACHE.lookup(name);
            Buffer v = convertValue(value);
            put(n, v);
        }
    }

    /* -------------------------------------------------------------- */
    /**
     * Set a field.
     * 
     * @param name the name of the field
     * @param value the value of the field. If null the field is cleared.
     */
    public void put(Buffer name, String value)
    {
        Buffer n = HttpHeaders.CACHE.lookup(name);
        Buffer v = convertValue(value);
        put(n, v);
    }

    /* -------------------------------------------------------------- */
    /**
     * Set a field.
     * 
     * @param name the name of the field
     * @param value the value of the field. If null the field is cleared.
     */
    public void put(Buffer name, Buffer value)
    {
        remove(name);
        if (value == null)
            return;

        if (!(name instanceof BufferCache.CachedBuffer)) 
            name = HttpHeaders.CACHE.lookup(name);
        if (!(value instanceof CachedBuffer))
            value= HttpHeaderValues.CACHE.lookup(value).asImmutableBuffer();
        
        // new value;
        Field field = new Field(name, value);
        _fields.add(field);
        _names.put(name, field);
    }

    /* -------------------------------------------------------------- */
    /**
     * 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;
        }
        Buffer n = HttpHeaders.CACHE.lookup(name);

        Object v = list.get(0);
        if (v != null)
            put(n, HttpHeaderValues.CACHE.lookup(v.toString()));
        else
            remove(n);

        if (list.size() > 1)
        {
            java.util.Iterator<?> iter = list.iterator();
            iter.next();
            while (iter.hasNext())
            {
                v = iter.next();
                if (v != null) put(n, HttpHeaderValues.CACHE.lookup(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)
            return;
        Buffer n = HttpHeaders.CACHE.lookup(name);
        Buffer v = convertValue(value);
        add(n, v);
    }

    /* -------------------------------------------------------------- */
    /**
     * 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(Buffer name, Buffer value) throws IllegalArgumentException
    {   
        if (value == null) throw new IllegalArgumentException("null value");

        if (!(name instanceof CachedBuffer))
            name = HttpHeaders.CACHE.lookup(name);
        name=name.asImmutableBuffer();
        
        if (!(value instanceof CachedBuffer) && HttpHeaderValues.hasKnownValues(HttpHeaders.CACHE.getOrdinal(name)))
            value= HttpHeaderValues.CACHE.lookup(value);
        value=value.asImmutableBuffer();
        
        Field field = _names.get(name);
        Field last = null;
        while (field != null)
        {
            last = field;
            field = field._next;
        }

        // create the field
        field = new Field(name, value);
        _fields.add(field);

        // look for chain to add too
        if (last != null)
            last._next = field;
        else
            _names.put(name, field);
    }

    /* ------------------------------------------------------------ */
    /**
     * Remove a field.
     * 
     * @param name
     */
    public void remove(String name)
    {
        remove(HttpHeaders.CACHE.lookup(name));
    }

    /* ------------------------------------------------------------ */
    /**
     * Remove a field.
     * 
     * @param name
     */
    public void remove(Buffer name)
    {
        if (!(name instanceof BufferCache.CachedBuffer)) 
            name = HttpHeaders.CACHE.lookup(name);
        Field field = _names.remove(name);
        while (field != null)
        {
            _fields.remove(field);
            field = field._next;
        }
    }

    /* -------------------------------------------------------------- */
    /**
     * Get a header as an long 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 long found
     */
    public long getLongField(String name) throws NumberFormatException
    {
        Field field = getField(name);
        return field==null?-1L:field.getLongValue();
    }

    /* -------------------------------------------------------------- */
    /**
     * Get a header as an long 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 long found
     */
    public long getLongField(Buffer name) throws NumberFormatException
    {
        Field field = getField(name);
        return field==null?-1L:field.getLongValue();
    }

    /* -------------------------------------------------------------- */
    /**
     * 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)
    {
        Field field = getField(name);
        if (field == null) 
            return -1;

        String val = valueParameters(BufferUtil.to8859_1_String(field._value), null);
        if (val == null) 
            return -1;

        final long date = __dateParser.get().parse(val);
        if (date==-1)
            throw new IllegalArgumentException("Cannot convert date: " + val);
        return date;
    }

    /* -------------------------------------------------------------- */
    /**
     * Sets the value of an long field.
     * 
     * @param name the field name
     * @param value the field long value
     */
    public void putLongField(Buffer name, long value)
    {
        Buffer v = BufferUtil.toBuffer(value);
        put(name, v);
    }

    /* -------------------------------------------------------------- */
    /**
     * Sets the value of an long field.
     * 
     * @param name the field name
     * @param value the field long value
     */
    public void putLongField(String name, long value)
    {
        Buffer n = HttpHeaders.CACHE.lookup(name);
        Buffer v = BufferUtil.toBuffer(value);
        put(n, v);
    }

    /* -------------------------------------------------------------- */
    /**
     * Sets the value of an long field.
     * 
     * @param name the field name
     * @param value the field long value
     */
    public void addLongField(String name, long value)
    {
        Buffer n = HttpHeaders.CACHE.lookup(name);
        Buffer v = BufferUtil.toBuffer(value);
        add(n, v);
    }

    /* -------------------------------------------------------------- */
    /**
     * Sets the value of an long field.
     * 
     * @param name the field name
     * @param value the field long value
     */
    public void addLongField(Buffer name, long value)
    {
        Buffer v = BufferUtil.toBuffer(value);
        add(name, v);
    }

    /* -------------------------------------------------------------- */
    /**
     * Sets the value of a date field.
     * 
     * @param name the field name
     * @param date the field date value
     */
    public void putDateField(Buffer name, long date)
    {
        String d=formatDate(date);
        Buffer v = new ByteArrayBuffer(d);
        put(name, v);
    }

    /* -------------------------------------------------------------- */
    /**
     * 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)
    {
        Buffer n = HttpHeaders.CACHE.lookup(name);
        putDateField(n,date);
    }

    /* -------------------------------------------------------------- */
    /**
     * Sets the value of a date field.
     * 
     * @param name the field name
     * @param date the field date value
     */
    public void addDateField(String name, long date)
    {
        String d=formatDate(date);
        Buffer n = HttpHeaders.CACHE.lookup(name);
        Buffer v = new ByteArrayBuffer(d);
        add(n, v);
    }

    /* ------------------------------------------------------------ */
    /**
     * Format a set cookie value
     * 
     * @param cookie The cookie.
     */
    public void addSetCookie(HttpCookie cookie)
    {
        addSetCookie(
                cookie.getName(),
                cookie.getValue(),
                cookie.getDomain(),
                cookie.getPath(),
                cookie.getMaxAge(),
                cookie.getComment(),
                cookie.isSecure(),
                cookie.isHttpOnly(),
                cookie.getVersion());
    }

    /**
     * Format a set cookie value
     * 
     * @param name the name
     * @param value the value
     * @param domain the domain
     * @param path the path
     * @param maxAge the maximum age
     * @param comment the comment (only present on versions > 0)
     * @param isSecure true if secure cookie
     * @param isHttpOnly true if for http only
     * @param version version of cookie logic to use (0 == default behavior)
     */
    public void addSetCookie(
            final String name, 
            final String value, 
            final String domain,
            final String path, 
            final long maxAge,
            final String comment, 
            final boolean isSecure,
            final boolean isHttpOnly, 
            int version)
    {
    	String delim=__COOKIE_DELIM;
    	
        // Check arguments
        if (name == null || name.length() == 0) 
            throw new IllegalArgumentException("Bad cookie name");

        // Format value and params
        StringBuilder buf = new StringBuilder(128);
        String name_value_params;
        QuotedStringTokenizer.quoteIfNeeded(buf, name, delim);
        buf.append('=');
        String start=buf.toString();
        boolean hasDomain = false;
        boolean hasPath = false;
        
        if (value != null && value.length() > 0)
            QuotedStringTokenizer.quoteIfNeeded(buf, value, delim);        

        if (comment != null && comment.length() > 0)
        {
            buf.append(";Comment=");
            QuotedStringTokenizer.quoteIfNeeded(buf, comment, delim);
        }

        if (path != null && path.length() > 0)
        {
            hasPath = true;
            buf.append(";Path=");
            if (path.trim().startsWith("\""))
                buf.append(path);
            else
                QuotedStringTokenizer.quoteIfNeeded(buf,path,delim);
        }
        if (domain != null && domain.length() > 0)
        {
            hasDomain = true;
            buf.append(";Domain=");
            QuotedStringTokenizer.quoteIfNeeded(buf,domain.toLowerCase(Locale.ENGLISH),delim);
        }

        if (maxAge >= 0)
        {
            // Always add the expires param as some browsers still don't handle max-age
            buf.append(";Expires=");
            if (maxAge == 0)
                buf.append(__01Jan1970_COOKIE);
            else
                formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);

            if (version >0)
            {
                buf.append(";Max-Age=");
                buf.append(maxAge);
            }
        }

        if (isSecure)
            buf.append(";Secure");
        if (isHttpOnly) 
            buf.append(";HttpOnly");

        name_value_params = buf.toString();
        
        // remove existing set-cookie of same name
        Field field = getField(HttpHeaders.SET_COOKIE);
        Field last=null;
        while (field!=null)
        {
            String val = (field._value == null ? null : field._value.toString());
            if (val!=null && val.startsWith(start))
            {
                //existing cookie has same name, does it also match domain and path?
                if (((!hasDomain && !val.contains("Domain")) || (hasDomain && val.contains("Domain="+domain))) &&
                    ((!hasPath && !val.contains("Path")) || (hasPath && val.contains("Path="+path))))
                {
                    _fields.remove(field);
                    if (last==null)
                        _names.put(HttpHeaders.SET_COOKIE_BUFFER,field._next);
                    else
                        last._next=field._next;
                    break;
                }
            }
            last=field;
            field=field._next;
        }

        add(HttpHeaders.SET_COOKIE_BUFFER, new ByteArrayBuffer(name_value_params));
        
        // Expire responses with set-cookie headers so they do not get cached.
        put(HttpHeaders.EXPIRES_BUFFER, __01Jan1970_BUFFER);
    }

    /* -------------------------------------------------------------- */
    public void putTo(Buffer buffer) throws IOException
    {
        for (int i = 0; i < _fields.size(); i++)
        {
            Field field = _fields.get(i);
            if (field != null) 
                field.putTo(buffer);
        }
        BufferUtil.putCRLF(buffer);
    }

    /* -------------------------------------------------------------- */
    public String toString()
    {
        try
        {
            StringBuffer buffer = new StringBuffer();
            for (int i = 0; i < _fields.size(); i++)
            {
                Field field = (Field) _fields.get(i);
                if (field != null)
                {
                    String tmp = field.getName();
                    if (tmp != null) buffer.append(tmp);
                    buffer.append(": ");
                    tmp = field.getValue();
                    if (tmp != null) buffer.append(tmp);
                    buffer.append("\r\n");
                }
            }
            buffer.append("\r\n");
            return buffer.toString();
        }
        catch (Exception e)
        {
            LOG.warn("",e);
            return e.toString();
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * Clear the header.
     */
    public void clear()
    {
        _fields.clear();
        _names.clear();
    }

    /* ------------------------------------------------------------ */
    /**
     * 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 e = fields.getFieldNames();
        while (e.hasMoreElements())
        {
            String name = (String) e.nextElement();
            Enumeration values = fields.getValues(name);
            while (values.hasMoreElements())
                add(name, (String) values.nextElement());
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * 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 parameters. 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<String,String> 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();
    }

    /* ------------------------------------------------------------ */
    private static final Float __one = new Float("1.0");
    private static final Float __zero = new Float("0.0");
    private static final 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);
    }

    /* ------------------------------------------------------------ */
    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 e Enumeration of values with quality parameters
     * @return values in quality order.
     */
    public static List qualityList(Enumeration e)
    {
        if (e == null || !e.hasMoreElements()) return Collections.EMPTY_LIST;

        Object list = null;
        Object qual = null;

        // Assume list will be well ordered and just add nonzero
        while (e.hasMoreElements())
        {
            String v = e.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;
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    public static final class Field
    {
        private Buffer _name;
        private Buffer _value;
        private Field _next;

        /* ------------------------------------------------------------ */
        private Field(Buffer name, Buffer value)
        {
            _name = name;
            _value = value;
            _next = null;
        }
        
        /* ------------------------------------------------------------ */
        public void putTo(Buffer buffer) throws IOException
        {
            int o=(_name instanceof CachedBuffer)?((CachedBuffer)_name).getOrdinal():-1;
            if (o>=0)
                buffer.put(_name);
            else
            {
                int s=_name.getIndex();
                int e=_name.putIndex();
                while (s<e)
                {
                    byte b=_name.peek(s++);
                    switch(b)
                    {
                        case '\r':
                        case '\n':
                        case ':' :
                            continue;
                        default:
                            buffer.put(b);
                    }
                }
            }
            
            buffer.put((byte) ':');
            buffer.put((byte) ' ');
            
            o=(_value instanceof CachedBuffer)?((CachedBuffer)_value).getOrdinal():-1;
            if (o>=0)
                buffer.put(_value);
            else
            {
                int s=_value.getIndex();
                int e=_value.putIndex();
                while (s<e)
                {
                    byte b=_value.peek(s++);
                    switch(b)
                    {
                        case '\r':
                        case '\n':
                            continue;
                        default:
                            buffer.put(b);
                    }
                }
            }

            BufferUtil.putCRLF(buffer);
        }

        /* ------------------------------------------------------------ */
        public String getName()
        {
            return BufferUtil.to8859_1_String(_name);
        }

        /* ------------------------------------------------------------ */
        Buffer getNameBuffer()
        {
            return _name;
        }

        /* ------------------------------------------------------------ */
        public int getNameOrdinal()
        {
            return HttpHeaders.CACHE.getOrdinal(_name);
        }

        /* ------------------------------------------------------------ */
        public String getValue()
        {
            return BufferUtil.to8859_1_String(_value);
        }

        /* ------------------------------------------------------------ */
        public Buffer getValueBuffer()
        {
            return _value;
        }

        /* ------------------------------------------------------------ */
        public int getValueOrdinal()
        {
            return HttpHeaderValues.CACHE.getOrdinal(_value);
        }

        /* ------------------------------------------------------------ */
        public int getIntValue()
        {
            return (int) getLongValue();
        }

        /* ------------------------------------------------------------ */
        public long getLongValue()
        {
            return BufferUtil.toLong(_value);
        }

        /* ------------------------------------------------------------ */
        public String toString()
        {
            return ("[" + getName() + "=" + _value + (_next == null ? "" : "->") + "]");
        }
    }
}