View Javadoc

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