2 // ========================================================================
3 // Copyright (c) 1995-2016 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.
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
19 package org.eclipse.jetty.http;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.Enumeration;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.List;
30 import java.util.NoSuchElementException;
32 import java.util.StringTokenizer;
33 import java.util.regex.Pattern;
35 import org.eclipse.jetty.util.ArrayTernaryTrie;
36 import org.eclipse.jetty.util.LazyList;
37 import org.eclipse.jetty.util.QuotedStringTokenizer;
38 import org.eclipse.jetty.util.StringUtil;
39 import org.eclipse.jetty.util.Trie;
40 import org.eclipse.jetty.util.log.Log;
41 import org.eclipse.jetty.util.log.Logger;
45 * HTTP Fields. A collection of HTTP header and or Trailer fields.
47 * <p>This class is not synchronized as it is expected that modifications will only be performed by a
50 * <p>The cookie handling provided by this class is guided by the Servlet specification and RFC6265.
53 public class HttpFields implements Iterable<HttpField>
55 private static final Logger LOG = Log.getLogger(HttpFields.class);
56 private final static Pattern __splitter = Pattern.compile("\\s*,\\s*");
57 public final static String __separators = ", \t";
59 private final ArrayList<HttpField> _fields = new ArrayList<>(20);
69 * Get Collection of header names.
71 public Collection<String> getFieldNamesCollection()
73 final Set<String> list = new HashSet<>(_fields.size());
74 for (HttpField f : _fields)
77 list.add(f.getName());
83 * Get enumeration of header _names. Returns an enumeration of strings representing the header
84 * _names for this request.
86 public Enumeration<String> getFieldNames()
88 return Collections.enumeration(getFieldNamesCollection());
93 return _fields.size();
97 * Get a Field by index.
98 * @return A Field value or null if the Field value has not been set
101 public HttpField getField(int i)
103 return _fields.get(i);
107 public Iterator<HttpField> iterator()
109 return _fields.iterator();
112 public HttpField getField(HttpHeader header)
114 for (int i=0;i<_fields.size();i++)
116 HttpField f=_fields.get(i);
117 if (f.getHeader()==header)
123 public HttpField getField(String name)
125 for (int i=0;i<_fields.size();i++)
127 HttpField f=_fields.get(i);
128 if (f.getName().equalsIgnoreCase(name))
134 public boolean contains(HttpHeader header, String value)
136 for (int i=0;i<_fields.size();i++)
138 HttpField f=_fields.get(i);
139 if (f.getHeader()==header && contains(f,value))
145 public boolean contains(String name, String value)
147 for (int i=0;i<_fields.size();i++)
149 HttpField f=_fields.get(i);
150 if (f.getName().equalsIgnoreCase(name) && contains(f,value))
156 private boolean contains(HttpField field,String value)
158 String v = field.getValue();
162 if (value.equalsIgnoreCase(v))
165 String[] split = __splitter.split(v);
166 for (int i = 0; split!=null && i < split.length; i++)
168 if (value.equals(split[i]))
175 public boolean contains(HttpHeader header)
177 for (int i=0;i<_fields.size();i++)
179 HttpField f=_fields.get(i);
180 if (f.getHeader()==header)
186 public boolean containsKey(String name)
188 for (int i=0;i<_fields.size();i++)
190 HttpField f=_fields.get(i);
191 if (f.getName().equalsIgnoreCase(name))
198 public String getStringField(HttpHeader header)
200 return getStringField(header.asString());
203 public String get(HttpHeader header)
205 return getStringField(header.asString());
208 public String get(String header)
210 return getStringField(header);
214 * @return the value of a field, or null if not found. For multiple fields of the same name,
215 * only the first is returned.
216 * @param name the case-insensitive field name
218 public String getStringField(String name)
220 HttpField field = getField(name);
221 return field==null?null:field.getValue();
227 * @return List the values
228 * @param name the case-insensitive field name
230 public List<String> getValuesList(String name)
232 final List<String> list = new ArrayList<>();
233 for (HttpField f : _fields)
234 if (f.getName().equalsIgnoreCase(name))
235 list.add(f.getValue());
242 * @return Enumeration of the values
243 * @param name the case-insensitive field name
245 public Enumeration<String> getValues(final String name)
247 for (int i=0;i<_fields.size();i++)
249 final HttpField f = _fields.get(i);
251 if (f.getName().equalsIgnoreCase(name) && f.getValue()!=null)
254 return new Enumeration<String>()
260 public boolean hasMoreElements()
264 while (i<_fields.size())
266 field=_fields.get(i++);
267 if (field.getName().equalsIgnoreCase(name) && field.getValue()!=null)
277 public String nextElement() throws NoSuchElementException
279 if (hasMoreElements())
281 String value=field.getValue();
285 throw new NoSuchElementException();
292 List<String> empty=Collections.emptyList();
293 return Collections.enumeration(empty);
297 * Get multi field values with separator. The multiple values can be represented as separate
298 * headers of the same name, or by a single header using the separator(s), or a combination of
299 * both. Separators may be quoted.
301 * @param name the case-insensitive field name
302 * @param separators String of separators.
303 * @return Enumeration of the values, or null if no such header.
305 public Enumeration<String> getValues(String name, final String separators)
307 final Enumeration<String> e = getValues(name);
310 return new Enumeration<String>()
312 QuotedStringTokenizer tok = null;
315 public boolean hasMoreElements()
317 if (tok != null && tok.hasMoreElements()) return true;
318 while (e.hasMoreElements())
320 String value = e.nextElement();
323 tok = new QuotedStringTokenizer(value, separators, false, false);
324 if (tok.hasMoreElements()) return true;
332 public String nextElement() throws NoSuchElementException
334 if (!hasMoreElements()) throw new NoSuchElementException();
335 String next = (String) tok.nextElement();
336 if (next != null) next = next.trim();
342 public void put(HttpField field)
345 for (int i=_fields.size();i-->0;)
347 HttpField f=_fields.get(i);
354 _fields.set(i,field);
366 * @param name the name of the field
367 * @param value the value of the field. If null the field is cleared.
369 public void put(String name, String value)
374 put(new HttpField(name, value));
377 public void put(HttpHeader header, HttpHeaderValue value)
379 put(header,value.toString());
385 * @param header the header name of the field
386 * @param value the value of the field. If null the field is cleared.
388 public void put(HttpHeader header, String value)
393 put(new HttpField(header, value));
399 * @param name the name of the field
400 * @param list the List value of the field. If null the field is cleared.
402 public void put(String name, List<String> list)
405 for (String v : list)
411 * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
412 * headers of the same name.
414 * @param name the name of the field
415 * @param value the value of the field.
416 * @exception IllegalArgumentException If the name is a single valued field and already has a
419 public void add(String name, String value) throws IllegalArgumentException
424 HttpField field = new HttpField(name, value);
428 public void add(HttpHeader header, HttpHeaderValue value) throws IllegalArgumentException
430 add(header,value.toString());
434 * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
435 * headers of the same name.
437 * @param header the header
438 * @param value the value of the field.
439 * @exception IllegalArgumentException
441 public void add(HttpHeader header, String value) throws IllegalArgumentException
443 if (value == null) throw new IllegalArgumentException("null value");
445 HttpField field = new HttpField(header, value);
452 * @param name the field to remove
454 public HttpField remove(HttpHeader name)
456 for (int i=_fields.size();i-->0;)
458 HttpField f=_fields.get(i);
459 if (f.getHeader()==name)
460 return _fields.remove(i);
468 * @param name the field to remove
470 public HttpField remove(String name)
472 for (int i=_fields.size();i-->0;)
474 HttpField f=_fields.get(i);
475 if (f.getName().equalsIgnoreCase(name))
476 return _fields.remove(i);
482 * Get a header as an long value. Returns the value of an integer field or -1 if not found. The
483 * case of the field name is ignored.
485 * @param name the case-insensitive field name
486 * @exception NumberFormatException If bad long found
488 public long getLongField(String name) throws NumberFormatException
490 HttpField field = getField(name);
491 return field==null?-1L:StringUtil.toLong(field.getValue());
495 * Get a header as a date value. Returns the value of a date field, or -1 if not found. The case
496 * of the field name is ignored.
498 * @param name the case-insensitive field name
500 public long getDateField(String name)
502 HttpField field = getField(name);
506 String val = valueParameters(field.getValue(), null);
510 final long date = DateParser.parseDate(val);
512 throw new IllegalArgumentException("Cannot convert date: " + val);
518 * Sets the value of an long field.
520 * @param name the field name
521 * @param value the field long value
523 public void putLongField(HttpHeader name, long value)
525 String v = Long.toString(value);
530 * Sets the value of an long field.
532 * @param name the field name
533 * @param value the field long value
535 public void putLongField(String name, long value)
537 String v = Long.toString(value);
543 * Sets the value of a date field.
545 * @param name the field name
546 * @param date the field date value
548 public void putDateField(HttpHeader name, long date)
550 String d=DateGenerator.formatDate(date);
555 * Sets the value of a date field.
557 * @param name the field name
558 * @param date the field date value
560 public void putDateField(String name, long date)
562 String d=DateGenerator.formatDate(date);
567 * Sets the value of a date field.
569 * @param name the field name
570 * @param date the field date value
572 public void addDateField(String name, long date)
574 String d=DateGenerator.formatDate(date);
584 StringBuilder buffer = new StringBuilder();
585 for (HttpField field : _fields)
589 String tmp = field.getName();
590 if (tmp != null) buffer.append(tmp);
592 tmp = field.getValue();
593 if (tmp != null) buffer.append(tmp);
594 buffer.append("\r\n");
597 buffer.append("\r\n");
598 return buffer.toString();
615 public void add(HttpField field)
623 * Add fields from another HttpFields instance. Single valued fields are replaced, while all
626 * @param fields the fields to add
628 public void add(HttpFields fields)
630 if (fields == null) return;
632 Enumeration<String> e = fields.getFieldNames();
633 while (e.hasMoreElements())
635 String name = e.nextElement();
636 Enumeration<String> values = fields.getValues(name);
637 while (values.hasMoreElements())
638 add(name, values.nextElement());
643 * Get field value parameters. Some field values can have parameters. This method separates the
644 * value from the parameters and optionally populates a map with the parameters. For example:
648 * FieldName : Value ; param1=val1 ; param2=val2
652 * @param value The Field value, possibly with parameteres.
653 * @param parameters A map to populate with the parameters, or null
656 public static String valueParameters(String value, Map<String,String> parameters)
658 if (value == null) return null;
660 int i = value.indexOf(';');
661 if (i < 0) return value;
662 if (parameters == null) return value.substring(0, i).trim();
664 StringTokenizer tok1 = new QuotedStringTokenizer(value.substring(i), ";", false, true);
665 while (tok1.hasMoreTokens())
667 String token = tok1.nextToken();
668 StringTokenizer tok2 = new QuotedStringTokenizer(token, "= ");
669 if (tok2.hasMoreTokens())
671 String paramName = tok2.nextToken();
672 String paramVal = null;
673 if (tok2.hasMoreTokens()) paramVal = tok2.nextToken();
674 parameters.put(paramName, paramVal);
678 return value.substring(0, i).trim();
681 private static final Float __one = new Float("1.0");
682 private static final Float __zero = new Float("0.0");
683 private static final Trie<Float> __qualities = new ArrayTernaryTrie<>();
686 __qualities.put("*", __one);
687 __qualities.put("1.0", __one);
688 __qualities.put("1", __one);
689 __qualities.put("0.9", new Float("0.9"));
690 __qualities.put("0.8", new Float("0.8"));
691 __qualities.put("0.7", new Float("0.7"));
692 __qualities.put("0.66", new Float("0.66"));
693 __qualities.put("0.6", new Float("0.6"));
694 __qualities.put("0.5", new Float("0.5"));
695 __qualities.put("0.4", new Float("0.4"));
696 __qualities.put("0.33", new Float("0.33"));
697 __qualities.put("0.3", new Float("0.3"));
698 __qualities.put("0.2", new Float("0.2"));
699 __qualities.put("0.1", new Float("0.1"));
700 __qualities.put("0", __zero);
701 __qualities.put("0.0", __zero);
704 public static Float getQuality(String value)
706 if (value == null) return __zero;
708 int qe = value.indexOf(";");
709 if (qe++ < 0 || qe == value.length()) return __one;
711 if (value.charAt(qe++) == 'q')
714 Float q = __qualities.get(value, qe, value.length() - qe);
719 Map<String,String> params = new HashMap<>(4);
720 valueParameters(value, params);
721 String qs = params.get("q");
724 Float q = __qualities.get(qs);
740 * List values in quality order.
742 * @param e Enumeration of values with quality parameters
743 * @return values in quality order.
745 public static List<String> qualityList(Enumeration<String> e)
747 if (e == null || !e.hasMoreElements())
748 return Collections.emptyList();
753 // Assume list will be well ordered and just add nonzero
754 while (e.hasMoreElements())
756 String v = e.nextElement();
757 Float q = getQuality(v);
761 list = LazyList.add(list, v);
762 qual = LazyList.add(qual, q);
766 List<String> vl = LazyList.getList(list, false);
770 List<Float> ql = LazyList.getList(qual, false);
772 // sort list with swaps
774 for (int i = vl.size(); i-- > 0;)
777 if (last.compareTo(q) > 0)
779 String tmp = vl.get(i);
780 vl.set(i, vl.get(i + 1));
782 ql.set(i, ql.get(i + 1));