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