=0)
+ {
+ if (param<__zeroquality.length())
+ param=Character.toLowerCase(c)==__zeroquality.charAt(param)?(param+1):-1;
+ else if (c!='0'&&c!='.')
+ param=-1;
+ }
+
+ }
+ break;
+
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ return param!=__zeroquality.length() && match==search.length();
+ }
+
+
+ @Override
+ public String toString()
+ {
+ String v=getValue();
+ return getName() + ": " + (v==null?"":v);
+ }
+
+ public boolean isSameName(HttpField field)
+ {
+ if (field==null)
+ return false;
+ if (field==this)
+ return true;
+ if (_header!=null && _header==field.getHeader())
+ return true;
+ if (_name.equalsIgnoreCase(field.getName()))
+ return true;
+ return false;
+ }
+
+ private int nameHashCode()
+ {
+ int h = this.hash;
+ int len = _name.length();
+ if (h == 0 && len > 0)
+ {
+ for (int i = 0; i < len; i++)
+ {
+ // simple case insensitive hash
+ char c = _name.charAt(i);
+ // assuming us-ascii (per last paragraph on http://tools.ietf.org/html/rfc7230#section-3.2.4)
+ if ((c >= 'a' && c <= 'z'))
+ c -= 0x20;
+ h = 31 * h + c;
+ }
+ this.hash = h;
+ }
+ return h;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ if (_header==null)
+ return _value.hashCode() ^ nameHashCode();
+ return _value.hashCode() ^ _header.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o==this)
+ return true;
+ if (!(o instanceof HttpField))
+ return false;
+ HttpField field=(HttpField)o;
+ if (_header!=field.getHeader())
+ return false;
+ if (!_name.equalsIgnoreCase(field.getName()))
+ return false;
+ if (_value==null && field.getValue()!=null)
+ return false;
+ return Objects.equals(_value,field.getValue());
+ }
+
+ public static class IntValueHttpField extends HttpField
+ {
+ private final int _int;
+
+ public IntValueHttpField(HttpHeader header, String name, String value, int intValue)
+ {
+ super(header,name,value);
+ _int=intValue;
+ }
+
+ public IntValueHttpField(HttpHeader header, String name, String value)
+ {
+ this(header,name,value,Integer.valueOf(value));
+ }
+
+ public IntValueHttpField(HttpHeader header, String name, int intValue)
+ {
+ this(header,name,Integer.toString(intValue),intValue);
+ }
+
+ public IntValueHttpField(HttpHeader header, int value)
+ {
+ this(header,header.asString(),value);
+ }
+
+ @Override
+ public int getIntValue()
+ {
+ return _int;
+ }
+
+ @Override
+ public long getLongValue()
+ {
+ return _int;
+ }
+ }
+
+ public static class LongValueHttpField extends HttpField
+ {
+ private final long _long;
+
+ public LongValueHttpField(HttpHeader header, String name, String value, long longValue)
+ {
+ super(header,name,value);
+ _long=longValue;
+ }
+
+ public LongValueHttpField(HttpHeader header, String name, String value)
+ {
+ this(header,name,value,Long.valueOf(value));
+ }
+
+ public LongValueHttpField(HttpHeader header, String name, long value)
+ {
+ this(header,name,Long.toString(value),value);
+ }
+
+ public LongValueHttpField(HttpHeader header,long value)
+ {
+ this(header,header.asString(),value);
+ }
+
+ @Override
+ public int getIntValue()
+ {
+ return (int)_long;
+ }
+
+ @Override
+ public long getLongValue()
+ {
+ return _long;
+ }
+ }
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpFieldPreEncoder.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpFieldPreEncoder.java
new file mode 100644
index 0000000000..0b1e4d47af
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpFieldPreEncoder.java
@@ -0,0 +1,36 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.http;
+
+
+/* ------------------------------------------------------------ */
+/** Interface to pre-encode HttpFields. Used by {@link PreEncodedHttpField}
+ */
+public interface HttpFieldPreEncoder
+{
+ /* ------------------------------------------------------------ */
+ /** The major version this encoder is for. Both HTTP/1.0 and HTTP/1.1
+ * use the same field encoding, so the {@link HttpVersion#HTTP_1_0} should
+ * be return for all HTTP/1.x encodings.
+ * @return The major version this encoder is for.
+ */
+ HttpVersion getHttpVersion();
+ byte[] getEncodedField(HttpHeader header, String headerString, String value);
+}
\ No newline at end of file
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpFields.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpFields.java
new file mode 100644
index 0000000000..0951a6de51
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpFields.java
@@ -0,0 +1,970 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/**
+ * HTTP Fields. A collection of HTTP header and or Trailer fields.
+ *
+ * This class is not synchronized as it is expected that modifications will only be performed by a
+ * single thread.
+ *
+ *
The cookie handling provided by this class is guided by the Servlet specification and RFC6265.
+ *
+ */
+public class HttpFields implements Iterable
+{
+ @Deprecated
+ public static final String __separators = ", \t";
+
+ private static final Logger LOG = Log.getLogger(HttpFields.class);
+
+ private HttpField[] _fields;
+ private int _size;
+
+ /**
+ * Initialize an empty HttpFields.
+ */
+ public HttpFields()
+ {
+ _fields=new HttpField[20];
+ }
+
+ /**
+ * Initialize an empty HttpFields.
+ *
+ * @param capacity the capacity of the http fields
+ */
+ public HttpFields(int capacity)
+ {
+ _fields=new HttpField[capacity];
+ }
+
+ /**
+ * Initialize HttpFields from copy.
+ *
+ * @param fields the fields to copy data from
+ */
+ public HttpFields(HttpFields fields)
+ {
+ _fields=Arrays.copyOf(fields._fields,fields._fields.length+10);
+ _size=fields._size;
+ }
+
+ public int size()
+ {
+ return _size;
+ }
+
+ @Override
+ public Iterator iterator()
+ {
+ return new Itr();
+ }
+
+ /**
+ * Get Collection of header names.
+ * @return the unique set of field names.
+ */
+ public Set getFieldNamesCollection()
+ {
+ final Set set = new HashSet<>(_size);
+ for (HttpField f : this)
+ {
+ if (f!=null)
+ set.add(f.getName());
+ }
+ return set;
+ }
+
+ /**
+ * Get enumeration of header _names. Returns an enumeration of strings representing the header
+ * _names for this request.
+ * @return an enumeration of field names
+ */
+ public Enumeration getFieldNames()
+ {
+ return Collections.enumeration(getFieldNamesCollection());
+ }
+
+ /**
+ * Get a Field by index.
+ * @param index the field index
+ * @return A Field value or null if the Field value has not been set
+ */
+ public HttpField getField(int index)
+ {
+ if (index>=_size)
+ throw new NoSuchElementException();
+ return _fields[index];
+ }
+
+ public HttpField getField(HttpHeader header)
+ {
+ for (int i=0;i<_size;i++)
+ {
+ HttpField f=_fields[i];
+ if (f.getHeader()==header)
+ return f;
+ }
+ return null;
+ }
+
+ public HttpField getField(String name)
+ {
+ for (int i=0;i<_size;i++)
+ {
+ HttpField f=_fields[i];
+ if (f.getName().equalsIgnoreCase(name))
+ return f;
+ }
+ return null;
+ }
+
+ public boolean contains(HttpField field)
+ {
+ for (int i=_size;i-->0;)
+ {
+ HttpField f=_fields[i];
+ if (f.isSameName(field) && f.contains(field.getValue()))
+ return true;
+ }
+ return false;
+ }
+
+ public boolean contains(HttpHeader header, String value)
+ {
+ for (int i=_size;i-->0;)
+ {
+ HttpField f=_fields[i];
+ if (f.getHeader()==header && f.contains(value))
+ return true;
+ }
+ return false;
+ }
+
+ public boolean contains(String name, String value)
+ {
+ for (int i=_size;i-->0;)
+ {
+ HttpField f=_fields[i];
+ if (f.getName().equalsIgnoreCase(name) && f.contains(value))
+ return true;
+ }
+ return false;
+ }
+
+ public boolean contains(HttpHeader header)
+ {
+ for (int i=_size;i-->0;)
+ {
+ HttpField f=_fields[i];
+ if (f.getHeader()==header)
+ return true;
+ }
+ return false;
+ }
+
+ public boolean containsKey(String name)
+ {
+ for (int i=_size;i-->0;)
+ {
+ HttpField f=_fields[i];
+ if (f.getName().equalsIgnoreCase(name))
+ return true;
+ }
+ return false;
+ }
+
+ @Deprecated
+ public String getStringField(HttpHeader header)
+ {
+ return get(header);
+ }
+
+ public String get(HttpHeader header)
+ {
+ for (int i=0;i<_size;i++)
+ {
+ HttpField f=_fields[i];
+ if (f.getHeader()==header)
+ return f.getValue();
+ }
+ return null;
+ }
+
+ @Deprecated
+ public String getStringField(String name)
+ {
+ return get(name);
+ }
+
+ public String get(String header)
+ {
+ for (int i=0;i<_size;i++)
+ {
+ HttpField f=_fields[i];
+ if (f.getName().equalsIgnoreCase(header))
+ return f.getValue();
+ }
+ return null;
+ }
+
+ /**
+ * Get multiple header of the same name
+ *
+ * @return List the values
+ * @param header the header
+ */
+ public List getValuesList(HttpHeader header)
+ {
+ final List list = new ArrayList<>();
+ for (HttpField f : this)
+ if (f.getHeader()==header)
+ list.add(f.getValue());
+ return list;
+ }
+
+ /**
+ * Get multiple header of the same name
+ *
+ * @return List the header values
+ * @param name the case-insensitive field name
+ */
+ public List getValuesList(String name)
+ {
+ final List list = new ArrayList<>();
+ for (HttpField f : this)
+ if (f.getName().equalsIgnoreCase(name))
+ list.add(f.getValue());
+ return list;
+ }
+
+ /**
+ * Get multiple field values of the same name, split
+ * as a {@link QuotedCSV}
+ *
+ * @return List the values with OWS stripped
+ * @param header The header
+ * @param keepQuotes True if the fields are kept quoted
+ */
+ public List getCSV(HttpHeader header,boolean keepQuotes)
+ {
+ QuotedCSV values = new QuotedCSV(keepQuotes);
+ for (HttpField f : this)
+ if (f.getHeader()==header)
+ values.addValue(f.getValue());
+ return values.getValues();
+ }
+
+ /**
+ * Get multiple field values of the same name
+ * as a {@link QuotedCSV}
+ *
+ * @return List the values with OWS stripped
+ * @param name the case-insensitive field name
+ * @param keepQuotes True if the fields are kept quoted
+ */
+ public List getCSV(String name,boolean keepQuotes)
+ {
+ QuotedCSV values = new QuotedCSV(keepQuotes);
+ for (HttpField f : this)
+ if (f.getName().equalsIgnoreCase(name))
+ values.addValue(f.getValue());
+ return values.getValues();
+ }
+
+ /**
+ * Get multiple field values of the same name, split and
+ * sorted as a {@link QuotedQualityCSV}
+ *
+ * @return List the values in quality order with the q param and OWS stripped
+ * @param header The header
+ */
+ public List getQualityCSV(HttpHeader header)
+ {
+ QuotedQualityCSV values = new QuotedQualityCSV();
+ for (HttpField f : this)
+ if (f.getHeader()==header)
+ values.addValue(f.getValue());
+ return values.getValues();
+ }
+
+ /**
+ * Get multiple field values of the same name, split and
+ * sorted as a {@link QuotedQualityCSV}
+ *
+ * @return List the values in quality order with the q param and OWS stripped
+ * @param name the case-insensitive field name
+ */
+ public List getQualityCSV(String name)
+ {
+ QuotedQualityCSV values = new QuotedQualityCSV();
+ for (HttpField f : this)
+ if (f.getName().equalsIgnoreCase(name))
+ values.addValue(f.getValue());
+ return values.getValues();
+ }
+
+ /**
+ * Get multi headers
+ *
+ * @return Enumeration of the values
+ * @param name the case-insensitive field name
+ */
+ public Enumeration getValues(final String name)
+ {
+ for (int i=0;i<_size;i++)
+ {
+ final HttpField f = _fields[i];
+
+ if (f.getName().equalsIgnoreCase(name) && f.getValue()!=null)
+ {
+ final int first=i;
+ return new Enumeration()
+ {
+ HttpField field=f;
+ int i = first+1;
+
+ @Override
+ public boolean hasMoreElements()
+ {
+ if (field==null)
+ {
+ while (i<_size)
+ {
+ field=_fields[i++];
+ if (field.getName().equalsIgnoreCase(name) && field.getValue()!=null)
+ return true;
+ }
+ field=null;
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String nextElement() throws NoSuchElementException
+ {
+ if (hasMoreElements())
+ {
+ String value=field.getValue();
+ field=null;
+ return value;
+ }
+ throw new NoSuchElementException();
+ }
+ };
+ }
+ }
+
+ List empty=Collections.emptyList();
+ return Collections.enumeration(empty);
+ }
+
+ /**
+ * Get multi field values with separator. The multiple values can be represented as separate
+ * headers of the same name, or by a single header using the separator(s), or a combination of
+ * both. Separators may be quoted.
+ *
+ * @param name the case-insensitive field name
+ * @param separators String of separators.
+ * @return Enumeration of the values, or null if no such header.
+ */
+ @Deprecated
+ public Enumeration getValues(String name, final String separators)
+ {
+ final Enumeration e = getValues(name);
+ if (e == null)
+ return null;
+ return new Enumeration()
+ {
+ QuotedStringTokenizer tok = null;
+
+ @Override
+ public boolean hasMoreElements()
+ {
+ if (tok != null && tok.hasMoreElements()) return true;
+ while (e.hasMoreElements())
+ {
+ String value = e.nextElement();
+ if (value!=null)
+ {
+ tok = new QuotedStringTokenizer(value, separators, false, false);
+ if (tok.hasMoreElements()) return true;
+ }
+ }
+ tok = null;
+ return false;
+ }
+
+ @Override
+ public String nextElement() throws NoSuchElementException
+ {
+ if (!hasMoreElements()) throw new NoSuchElementException();
+ String next = (String) tok.nextElement();
+ if (next != null) next = next.trim();
+ return next;
+ }
+ };
+ }
+
+ public void put(HttpField field)
+ {
+ boolean put=false;
+ for (int i=_size;i-->0;)
+ {
+ HttpField f=_fields[i];
+ if (f.isSameName(field))
+ {
+ if (put)
+ {
+ System.arraycopy(_fields,i+1,_fields,i,--_size-i);
+ }
+ else
+ {
+ _fields[i]=field;
+ put=true;
+ }
+ }
+ }
+ if (!put)
+ add(field);
+ }
+
+ /**
+ * Set a field.
+ *
+ * @param name the name of the field
+ * @param value the value of the field. If null the field is cleared.
+ */
+ public void put(String name, String value)
+ {
+ if (value == null)
+ remove(name);
+ else
+ put(new HttpField(name, value));
+ }
+
+ public void put(HttpHeader header, HttpHeaderValue value)
+ {
+ put(header,value.toString());
+ }
+
+ /**
+ * Set a field.
+ *
+ * @param header the header name of the field
+ * @param value the value of the field. If null the field is cleared.
+ */
+ public void put(HttpHeader header, String value)
+ {
+ if (value == null)
+ remove(header);
+ else
+ put(new HttpField(header, value));
+ }
+
+ /**
+ * Set a field.
+ *
+ * @param name the name of the field
+ * @param list the List value of the field. If null the field is cleared.
+ */
+ public void put(String name, List list)
+ {
+ remove(name);
+ for (String v : list)
+ if (v!=null)
+ add(name,v);
+ }
+
+ /**
+ * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
+ * headers of the same name.
+ *
+ * @param name the name of the field
+ * @param value the value of the field.
+ */
+ public void add(String name, String value)
+ {
+ if (value == null)
+ return;
+
+ HttpField field = new HttpField(name, value);
+ add(field);
+ }
+
+ public void add(HttpHeader header, HttpHeaderValue value)
+ {
+ add(header,value.toString());
+ }
+
+ /**
+ * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
+ * headers of the same name.
+ *
+ * @param header the header
+ * @param value the value of the field.
+ */
+ public void add(HttpHeader header, String value)
+ {
+ if (value == null) throw new IllegalArgumentException("null value");
+
+ HttpField field = new HttpField(header, value);
+ add(field);
+ }
+
+ /**
+ * Remove a field.
+ *
+ * @param name the field to remove
+ * @return the header that was removed
+ */
+ public HttpField remove(HttpHeader name)
+ {
+ HttpField removed=null;
+ for (int i=_size;i-->0;)
+ {
+ HttpField f=_fields[i];
+ if (f.getHeader()==name)
+ {
+ removed=f;
+ System.arraycopy(_fields,i+1,_fields,i,--_size-i);
+ }
+ }
+ return removed;
+ }
+
+ /**
+ * Remove a field.
+ *
+ * @param name the field to remove
+ * @return the header that was removed
+ */
+ public HttpField remove(String name)
+ {
+ HttpField removed=null;
+ for (int i=_size;i-->0;)
+ {
+ HttpField f=_fields[i];
+ if (f.getName().equalsIgnoreCase(name))
+ {
+ removed=f;
+ System.arraycopy(_fields,i+1,_fields,i,--_size-i);
+ }
+ }
+ return removed;
+ }
+
+ /**
+ * Get a header as an long value. Returns the value of an integer field or -1 if not found. The
+ * case of the field name is ignored.
+ *
+ * @param name the case-insensitive field name
+ * @return the value of the field as a long
+ * @exception NumberFormatException If bad long found
+ */
+ public long getLongField(String name) throws NumberFormatException
+ {
+ HttpField field = getField(name);
+ return field==null?-1L:field.getLongValue();
+ }
+
+ /**
+ * Get a header as a date value. Returns the value of a date field, or -1 if not found. The case
+ * of the field name is ignored.
+ *
+ * @param name the case-insensitive field name
+ * @return the value of the field as a number of milliseconds since unix epoch
+ */
+ public long getDateField(String name)
+ {
+ HttpField field = getField(name);
+ if (field == null)
+ return -1;
+
+ String val = valueParameters(field.getValue(), null);
+ if (val == null)
+ return -1;
+
+ final long date = DateParser.parseDate(val);
+ if (date==-1)
+ throw new IllegalArgumentException("Cannot convert date: " + val);
+ return date;
+ }
+
+
+ /**
+ * Sets the value of an long field.
+ *
+ * @param name the field name
+ * @param value the field long value
+ */
+ public void putLongField(HttpHeader name, long value)
+ {
+ String v = Long.toString(value);
+ put(name, v);
+ }
+
+ /**
+ * Sets the value of an long field.
+ *
+ * @param name the field name
+ * @param value the field long value
+ */
+ public void putLongField(String name, long value)
+ {
+ String v = Long.toString(value);
+ put(name, v);
+ }
+
+
+ /**
+ * Sets the value of a date field.
+ *
+ * @param name the field name
+ * @param date the field date value
+ */
+ public void putDateField(HttpHeader name, long date)
+ {
+ String d=DateGenerator.formatDate(date);
+ put(name, d);
+ }
+
+ /**
+ * Sets the value of a date field.
+ *
+ * @param name the field name
+ * @param date the field date value
+ */
+ public void putDateField(String name, long date)
+ {
+ String d=DateGenerator.formatDate(date);
+ put(name, d);
+ }
+
+ /**
+ * Sets the value of a date field.
+ *
+ * @param name the field name
+ * @param date the field date value
+ */
+ public void addDateField(String name, long date)
+ {
+ String d=DateGenerator.formatDate(date);
+ add(name,d);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int hash=0;
+ for (HttpField field:_fields)
+ hash+=field.hashCode();
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (!(o instanceof HttpFields))
+ return false;
+
+ HttpFields that = (HttpFields)o;
+
+ // Order is not important, so we cannot rely on List.equals().
+ if (size() != that.size())
+ return false;
+
+ loop: for (HttpField fi : this)
+ {
+ for (HttpField fa : that)
+ {
+ if (fi.equals(fa))
+ continue loop;
+ }
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString()
+ {
+ try
+ {
+ StringBuilder buffer = new StringBuilder();
+ for (HttpField field : this)
+ {
+ if (field != null)
+ {
+ String tmp = field.getName();
+ if (tmp != null) buffer.append(tmp);
+ buffer.append(": ");
+ tmp = field.getValue();
+ if (tmp != null) buffer.append(tmp);
+ buffer.append("\r\n");
+ }
+ }
+ buffer.append("\r\n");
+ return buffer.toString();
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ return e.toString();
+ }
+ }
+
+ public void clear()
+ {
+ _size=0;
+ }
+
+ public void add(HttpField field)
+ {
+ if (field!=null)
+ {
+ if (_size==_fields.length)
+ _fields=Arrays.copyOf(_fields,_size*2);
+ _fields[_size++]=field;
+ }
+ }
+
+ public void addAll(HttpFields fields)
+ {
+ for (int i=0;i e = fields.getFieldNames();
+ while (e.hasMoreElements())
+ {
+ String name = e.nextElement();
+ Enumeration values = fields.getValues(name);
+ while (values.hasMoreElements())
+ add(name, values.nextElement());
+ }
+ }
+
+ /**
+ * Get field value without parameters. Some field values can have parameters. This method separates the
+ * value from the parameters and optionally populates a map with the parameters. For example:
+ *
+ *
+ *
+ * FieldName : Value ; param1=val1 ; param2=val2
+ *
+ *
+ *
+ * @param value The Field value, possibly with parameters.
+ * @return The value.
+ */
+ public static String stripParameters(String value)
+ {
+ if (value == null) return null;
+
+ int i = value.indexOf(';');
+ if (i < 0) return value;
+ return value.substring(0, i).trim();
+ }
+
+ /**
+ * Get field value parameters. Some field values can have parameters. This method separates the
+ * value from the parameters and optionally populates a map with the parameters. For example:
+ *
+ *
+ *
+ * FieldName : Value ; param1=val1 ; param2=val2
+ *
+ *
+ *
+ * @param value The Field value, possibly with parameters.
+ * @param parameters A map to populate with the parameters, or null
+ * @return The value.
+ */
+ public static String valueParameters(String value, Map parameters)
+ {
+ if (value == null) return null;
+
+ int i = value.indexOf(';');
+ if (i < 0) return value;
+ if (parameters == null) return value.substring(0, i).trim();
+
+ StringTokenizer tok1 = new QuotedStringTokenizer(value.substring(i), ";", false, true);
+ while (tok1.hasMoreTokens())
+ {
+ String token = tok1.nextToken();
+ StringTokenizer tok2 = new QuotedStringTokenizer(token, "= ");
+ if (tok2.hasMoreTokens())
+ {
+ String paramName = tok2.nextToken();
+ String paramVal = null;
+ if (tok2.hasMoreTokens()) paramVal = tok2.nextToken();
+ parameters.put(paramName, paramVal);
+ }
+ }
+
+ return value.substring(0, i).trim();
+ }
+
+ @Deprecated
+ private static final Float __one = new Float("1.0");
+ @Deprecated
+ private static final Float __zero = new Float("0.0");
+ @Deprecated
+ private static final Trie __qualities = new ArrayTernaryTrie<>();
+ static
+ {
+ __qualities.put("*", __one);
+ __qualities.put("1.0", __one);
+ __qualities.put("1", __one);
+ __qualities.put("0.9", new Float("0.9"));
+ __qualities.put("0.8", new Float("0.8"));
+ __qualities.put("0.7", new Float("0.7"));
+ __qualities.put("0.66", new Float("0.66"));
+ __qualities.put("0.6", new Float("0.6"));
+ __qualities.put("0.5", new Float("0.5"));
+ __qualities.put("0.4", new Float("0.4"));
+ __qualities.put("0.33", new Float("0.33"));
+ __qualities.put("0.3", new Float("0.3"));
+ __qualities.put("0.2", new Float("0.2"));
+ __qualities.put("0.1", new Float("0.1"));
+ __qualities.put("0", __zero);
+ __qualities.put("0.0", __zero);
+ }
+
+ @Deprecated
+ public static Float getQuality(String value)
+ {
+ if (value == null) return __zero;
+
+ int qe = value.indexOf(";");
+ if (qe++ < 0 || qe == value.length()) return __one;
+
+ if (value.charAt(qe++) == 'q')
+ {
+ qe++;
+ Float q = __qualities.get(value, qe, value.length() - qe);
+ if (q != null)
+ return q;
+ }
+
+ Map params = new HashMap<>(4);
+ valueParameters(value, params);
+ String qs = params.get("q");
+ if (qs==null)
+ qs="*";
+ Float q = __qualities.get(qs);
+ if (q == null)
+ {
+ try
+ {
+ q = new Float(qs);
+ }
+ catch (Exception e)
+ {
+ q = __one;
+ }
+ }
+ return q;
+ }
+
+ /**
+ * List values in quality order.
+ *
+ * @param e Enumeration of values with quality parameters
+ * @return values in quality order.
+ */
+ @Deprecated
+ public static List qualityList(Enumeration e)
+ {
+ if (e == null || !e.hasMoreElements())
+ return Collections.emptyList();
+
+ QuotedQualityCSV values = new QuotedQualityCSV();
+ while(e.hasMoreElements())
+ values.addValue(e.nextElement());
+ return values.getValues();
+ }
+
+
+ private class Itr implements Iterator
+ {
+ int _cursor; // index of next element to return
+ int _last=-1;
+
+ public boolean hasNext()
+ {
+ return _cursor != _size;
+ }
+
+ public HttpField next()
+ {
+ int i = _cursor;
+ if (i >= _size)
+ throw new NoSuchElementException();
+ _cursor = i + 1;
+ return _fields[_last=i];
+ }
+
+ public void remove()
+ {
+ if (_last<0)
+ throw new IllegalStateException();
+
+ System.arraycopy(_fields,_last+1,_fields,_last,--_size-_last);
+ _cursor=_last;
+ _last=-1;
+ }
+ }
+
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpGenerator.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpGenerator.java
new file mode 100644
index 0000000000..f743379eb4
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpGenerator.java
@@ -0,0 +1,1023 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jetty.http.HttpTokens.EndOfContent;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * HttpGenerator. Builds HTTP Messages.
+ *
+ * If the system property "org.eclipse.jetty.http.HttpGenerator.STRICT" is set to true,
+ * then the generator will strictly pass on the exact strings received from methods and header
+ * fields. Otherwise a fast case insensitive string lookup is used that may alter the
+ * case and white space of some methods/headers
+ */
+public class HttpGenerator
+{
+ private final static Logger LOG = Log.getLogger(HttpGenerator.class);
+
+ public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpGenerator.STRICT");
+
+ private final static byte[] __colon_space = new byte[] {':',' '};
+ private final static HttpHeaderValue[] CLOSE = {HttpHeaderValue.CLOSE};
+ public static final MetaData.Response CONTINUE_100_INFO = new MetaData.Response(HttpVersion.HTTP_1_1,100,null,null,-1);
+ public static final MetaData.Response PROGRESS_102_INFO = new MetaData.Response(HttpVersion.HTTP_1_1,102,null,null,-1);
+ public final static MetaData.Response RESPONSE_500_INFO =
+ new MetaData.Response(HttpVersion.HTTP_1_1,HttpStatus.INTERNAL_SERVER_ERROR_500,null,new HttpFields(){{put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);}},0);
+
+ // states
+ public enum State { START, COMMITTED, COMPLETING, COMPLETING_1XX, END }
+ public enum Result { NEED_CHUNK,NEED_INFO,NEED_HEADER,FLUSH,CONTINUE,SHUTDOWN_OUT,DONE}
+
+ // other statics
+ public static final int CHUNK_SIZE = 12;
+
+ private State _state = State.START;
+ private EndOfContent _endOfContent = EndOfContent.UNKNOWN_CONTENT;
+
+ private long _contentPrepared = 0;
+ private boolean _noContent = false;
+ private Boolean _persistent = null;
+
+ private final int _send;
+ private final static int SEND_SERVER = 0x01;
+ private final static int SEND_XPOWEREDBY = 0x02;
+ private final static Set __assumedContentMethods = new HashSet<>(Arrays.asList(new String[]{HttpMethod.POST.asString(),HttpMethod.PUT.asString()}));
+
+ /* ------------------------------------------------------------------------------- */
+ public static void setJettyVersion(String serverVersion)
+ {
+ SEND[SEND_SERVER] = StringUtil.getBytes("Server: " + serverVersion + "\015\012");
+ SEND[SEND_XPOWEREDBY] = StringUtil.getBytes("X-Powered-By: " + serverVersion + "\015\012");
+ SEND[SEND_SERVER | SEND_XPOWEREDBY] = StringUtil.getBytes("Server: " + serverVersion + "\015\012X-Powered-By: " +
+ serverVersion + "\015\012");
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ // data
+ private boolean _needCRLF = false;
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpGenerator()
+ {
+ this(false,false);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpGenerator(boolean sendServerVersion,boolean sendXPoweredBy)
+ {
+ _send=(sendServerVersion?SEND_SERVER:0) | (sendXPoweredBy?SEND_XPOWEREDBY:0);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public void reset()
+ {
+ _state = State.START;
+ _endOfContent = EndOfContent.UNKNOWN_CONTENT;
+ _noContent=false;
+ _persistent = null;
+ _contentPrepared = 0;
+ _needCRLF = false;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Deprecated
+ public boolean getSendServerVersion ()
+ {
+ return (_send&SEND_SERVER)!=0;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Deprecated
+ public void setSendServerVersion (boolean sendServerVersion)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /* ------------------------------------------------------------ */
+ public State getState()
+ {
+ return _state;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isState(State state)
+ {
+ return _state == state;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isIdle()
+ {
+ return _state == State.START;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isEnd()
+ {
+ return _state == State.END;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isCommitted()
+ {
+ return _state.ordinal() >= State.COMMITTED.ordinal();
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isChunking()
+ {
+ return _endOfContent==EndOfContent.CHUNKED_CONTENT;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isNoContent()
+ {
+ return _noContent;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setPersistent(boolean persistent)
+ {
+ _persistent=persistent;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if known to be persistent
+ */
+ public boolean isPersistent()
+ {
+ return Boolean.TRUE.equals(_persistent);
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isWritten()
+ {
+ return _contentPrepared>0;
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getContentPrepared()
+ {
+ return _contentPrepared;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void abort()
+ {
+ _persistent=false;
+ _state=State.END;
+ _endOfContent=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Result generateRequest(MetaData.Request info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
+ {
+ switch(_state)
+ {
+ case START:
+ {
+ if (info==null)
+ return Result.NEED_INFO;
+
+ if (header==null)
+ return Result.NEED_HEADER;
+
+ // If we have not been told our persistence, set the default
+ if (_persistent==null)
+ {
+ _persistent=info.getVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal();
+ if (!_persistent && HttpMethod.CONNECT.is(info.getMethod()))
+ _persistent=true;
+ }
+
+ // prepare the header
+ int pos=BufferUtil.flipToFill(header);
+ try
+ {
+ // generate ResponseLine
+ generateRequestLine(info,header);
+
+ if (info.getVersion()==HttpVersion.HTTP_0_9)
+ throw new BadMessageException(500,"HTTP/0.9 not supported");
+
+ generateHeaders(info,header,content,last);
+
+ boolean expect100 = info.getFields().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
+
+ if (expect100)
+ {
+ _state = State.COMMITTED;
+ }
+ else
+ {
+ // handle the content.
+ int len = BufferUtil.length(content);
+ if (len>0)
+ {
+ _contentPrepared+=len;
+ if (isChunking())
+ prepareChunk(header,len);
+ }
+ _state = last?State.COMPLETING:State.COMMITTED;
+ }
+
+ return Result.FLUSH;
+ }
+ catch(Exception e)
+ {
+ String message= (e instanceof BufferOverflowException)?"Request header too large":e.getMessage();
+ throw new BadMessageException(500,message,e);
+ }
+ finally
+ {
+ BufferUtil.flipToFlush(header,pos);
+ }
+ }
+
+ case COMMITTED:
+ {
+ int len = BufferUtil.length(content);
+
+ if (len>0)
+ {
+ // Do we need a chunk buffer?
+ if (isChunking())
+ {
+ // Do we need a chunk buffer?
+ if (chunk==null)
+ return Result.NEED_CHUNK;
+ BufferUtil.clearToFill(chunk);
+ prepareChunk(chunk,len);
+ BufferUtil.flipToFlush(chunk,0);
+ }
+ _contentPrepared+=len;
+ }
+
+ if (last)
+ _state=State.COMPLETING;
+
+ return len>0?Result.FLUSH:Result.CONTINUE;
+ }
+
+ case COMPLETING:
+ {
+ if (BufferUtil.hasContent(content))
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("discarding content in COMPLETING");
+ BufferUtil.clear(content);
+ }
+
+ if (isChunking())
+ {
+ // Do we need a chunk buffer?
+ if (chunk==null)
+ return Result.NEED_CHUNK;
+ BufferUtil.clearToFill(chunk);
+ prepareChunk(chunk,0);
+ BufferUtil.flipToFlush(chunk,0);
+ _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+ return Result.FLUSH;
+ }
+
+ _state=State.END;
+ return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
+ }
+
+ case END:
+ if (BufferUtil.hasContent(content))
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("discarding content in COMPLETING");
+ BufferUtil.clear(content);
+ }
+ return Result.DONE;
+
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public Result generateResponse(MetaData.Response info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
+ {
+ return generateResponse(info,false,header,chunk,content,last);
+ }
+
+ /* ------------------------------------------------------------ */
+ public Result generateResponse(MetaData.Response info, boolean head, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
+ {
+ switch(_state)
+ {
+ case START:
+ {
+ if (info==null)
+ return Result.NEED_INFO;
+ HttpVersion version=info.getVersion();
+ if (version==null)
+ throw new BadMessageException(500,"No version");
+ switch(version)
+ {
+ case HTTP_1_0:
+ if (_persistent==null)
+ _persistent=Boolean.FALSE;
+ break;
+
+ case HTTP_1_1:
+ if (_persistent==null)
+ _persistent=Boolean.TRUE;
+ break;
+
+ default:
+ _persistent = false;
+ _endOfContent=EndOfContent.EOF_CONTENT;
+ if (BufferUtil.hasContent(content))
+ _contentPrepared+=content.remaining();
+ _state = last?State.COMPLETING:State.COMMITTED;
+ return Result.FLUSH;
+ }
+
+ // Do we need a response header
+ if (header==null)
+ return Result.NEED_HEADER;
+
+ // prepare the header
+ int pos=BufferUtil.flipToFill(header);
+ try
+ {
+ // generate ResponseLine
+ generateResponseLine(info,header);
+
+ // Handle 1xx and no content responses
+ int status=info.getStatus();
+ if (status>=100 && status<200 )
+ {
+ _noContent=true;
+
+ if (status!=HttpStatus.SWITCHING_PROTOCOLS_101 )
+ {
+ header.put(HttpTokens.CRLF);
+ _state=State.COMPLETING_1XX;
+ return Result.FLUSH;
+ }
+ }
+ else if (status==HttpStatus.NO_CONTENT_204 || status==HttpStatus.NOT_MODIFIED_304)
+ {
+ _noContent=true;
+ }
+
+ generateHeaders(info,header,content,last);
+
+ // handle the content.
+ int len = BufferUtil.length(content);
+ if (len>0)
+ {
+ _contentPrepared+=len;
+ if (isChunking() && !head)
+ prepareChunk(header,len);
+ }
+ _state = last?State.COMPLETING:State.COMMITTED;
+ }
+ catch(Exception e)
+ {
+ String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
+ throw new BadMessageException(500,message,e);
+ }
+ finally
+ {
+ BufferUtil.flipToFlush(header,pos);
+ }
+
+ return Result.FLUSH;
+ }
+
+ case COMMITTED:
+ {
+ int len = BufferUtil.length(content);
+
+ // handle the content.
+ if (len>0)
+ {
+ if (isChunking())
+ {
+ if (chunk==null)
+ return Result.NEED_CHUNK;
+ BufferUtil.clearToFill(chunk);
+ prepareChunk(chunk,len);
+ BufferUtil.flipToFlush(chunk,0);
+ }
+ _contentPrepared+=len;
+ }
+
+ if (last)
+ {
+ _state=State.COMPLETING;
+ return len>0?Result.FLUSH:Result.CONTINUE;
+ }
+ return len>0?Result.FLUSH:Result.DONE;
+
+ }
+
+ case COMPLETING_1XX:
+ {
+ reset();
+ return Result.DONE;
+ }
+
+ case COMPLETING:
+ {
+ if (BufferUtil.hasContent(content))
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("discarding content in COMPLETING");
+ BufferUtil.clear(content);
+ }
+
+ if (isChunking())
+ {
+ // Do we need a chunk buffer?
+ if (chunk==null)
+ return Result.NEED_CHUNK;
+
+ // Write the last chunk
+ BufferUtil.clearToFill(chunk);
+ prepareChunk(chunk,0);
+ BufferUtil.flipToFlush(chunk,0);
+ _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+ return Result.FLUSH;
+ }
+
+ _state=State.END;
+
+ return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
+ }
+
+ case END:
+ if (BufferUtil.hasContent(content))
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("discarding content in COMPLETING");
+ BufferUtil.clear(content);
+ }
+ return Result.DONE;
+
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private void prepareChunk(ByteBuffer chunk, int remaining)
+ {
+ // if we need CRLF add this to header
+ if (_needCRLF)
+ BufferUtil.putCRLF(chunk);
+
+ // Add the chunk size to the header
+ if (remaining>0)
+ {
+ BufferUtil.putHexInt(chunk, remaining);
+ BufferUtil.putCRLF(chunk);
+ _needCRLF=true;
+ }
+ else
+ {
+ chunk.put(LAST_CHUNK);
+ _needCRLF=false;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private void generateRequestLine(MetaData.Request request,ByteBuffer header)
+ {
+ header.put(StringUtil.getBytes(request.getMethod()));
+ header.put((byte)' ');
+ header.put(StringUtil.getBytes(request.getURIString()));
+ header.put((byte)' ');
+ header.put(request.getVersion().toBytes());
+ header.put(HttpTokens.CRLF);
+ }
+
+ /* ------------------------------------------------------------ */
+ private void generateResponseLine(MetaData.Response response, ByteBuffer header)
+ {
+ // Look for prepared response line
+ int status=response.getStatus();
+ PreparedResponse preprepared = status<__preprepared.length?__preprepared[status]:null;
+ String reason=response.getReason();
+ if (preprepared!=null)
+ {
+ if (reason==null)
+ header.put(preprepared._responseLine);
+ else
+ {
+ header.put(preprepared._schemeCode);
+ header.put(getReasonBytes(reason));
+ header.put(HttpTokens.CRLF);
+ }
+ }
+ else // generate response line
+ {
+ header.put(HTTP_1_1_SPACE);
+ header.put((byte) ('0' + status / 100));
+ header.put((byte) ('0' + (status % 100) / 10));
+ header.put((byte) ('0' + (status % 10)));
+ header.put((byte) ' ');
+ if (reason==null)
+ {
+ header.put((byte) ('0' + status / 100));
+ header.put((byte) ('0' + (status % 100) / 10));
+ header.put((byte) ('0' + (status % 10)));
+ }
+ else
+ header.put(getReasonBytes(reason));
+ header.put(HttpTokens.CRLF);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private byte[] getReasonBytes(String reason)
+ {
+ if (reason.length()>1024)
+ reason=reason.substring(0,1024);
+ byte[] _bytes = StringUtil.getBytes(reason);
+
+ for (int i=_bytes.length;i-->0;)
+ if (_bytes[i]=='\r' || _bytes[i]=='\n')
+ _bytes[i]='?';
+ return _bytes;
+ }
+
+ /* ------------------------------------------------------------ */
+ private void generateHeaders(MetaData _info,ByteBuffer header,ByteBuffer content,boolean last)
+ {
+ final MetaData.Request request=(_info instanceof MetaData.Request)?(MetaData.Request)_info:null;
+ final MetaData.Response response=(_info instanceof MetaData.Response)?(MetaData.Response)_info:null;
+
+ // default field values
+ int send=_send;
+ HttpField transfer_encoding=null;
+ boolean keep_alive=false;
+ boolean close=false;
+ boolean content_type=false;
+ StringBuilder connection = null;
+ long content_length = _info.getContentLength();
+
+ // Generate fields
+ HttpFields fields = _info.getFields();
+ if (fields != null)
+ {
+ int n=fields.size();
+ for (int f=0;f0)
+ {
+ values=new HttpHeaderValue[split.length];
+ for (int i=0;i0)
+ {
+ // we have been given a content length
+ _endOfContent=EndOfContent.CONTENT_LENGTH;
+ if ((response!=null || content_length>0 || content_type ) && !_noContent)
+ {
+ // known length but not actually set.
+ header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
+ BufferUtil.putDecLong(header, content_length);
+ header.put(HttpTokens.CRLF);
+ }
+ }
+ else if (last)
+ {
+ // we have seen all the _content there is, so we can be content-length limited.
+ _endOfContent=EndOfContent.CONTENT_LENGTH;
+ long actual_length = _contentPrepared+BufferUtil.length(content);
+
+ if (content_length>=0 && content_length!=actual_length)
+ throw new BadMessageException(500,"Content-Length header("+content_length+") != actual("+actual_length+")");
+
+ // Do we need to tell the headers about it
+ putContentLength(header,actual_length,content_type,request,response);
+ }
+ else
+ {
+ // No idea, so we must assume that a body is coming.
+ _endOfContent = EndOfContent.CHUNKED_CONTENT;
+ // HTTP 1.0 does not understand chunked content, so we must use EOF content.
+ // For a request with HTTP 1.0 & Connection: keep-alive
+ // we *must* close the connection, otherwise the client
+ // has no way to detect the end of the content.
+ if (!isPersistent() || _info.getVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal())
+ _endOfContent = EndOfContent.EOF_CONTENT;
+ }
+ break;
+
+ case CONTENT_LENGTH:
+ {
+ putContentLength(header,content_length,content_type,request,response);
+ break;
+ }
+
+ case NO_CONTENT:
+ throw new BadMessageException(500);
+
+ case EOF_CONTENT:
+ _persistent = request!=null;
+ break;
+
+ case CHUNKED_CONTENT:
+ break;
+
+ default:
+ break;
+ }
+
+ // Add transfer_encoding if needed
+ if (isChunking())
+ {
+ // try to use user supplied encoding as it may have other values.
+ if (transfer_encoding != null && !HttpHeaderValue.CHUNKED.toString().equalsIgnoreCase(transfer_encoding.getValue()))
+ {
+ String c = transfer_encoding.getValue();
+ if (c.endsWith(HttpHeaderValue.CHUNKED.toString()))
+ putTo(transfer_encoding,header);
+ else
+ throw new BadMessageException(500,"BAD TE");
+ }
+ else
+ header.put(TRANSFER_ENCODING_CHUNKED);
+ }
+
+ // Handle connection if need be
+ if (_endOfContent==EndOfContent.EOF_CONTENT)
+ {
+ keep_alive=false;
+ _persistent=false;
+ }
+
+ // If this is a response, work out persistence
+ if (response!=null)
+ {
+ if (!isPersistent() && (close || _info.getVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal()))
+ {
+ if (connection==null)
+ header.put(CONNECTION_CLOSE);
+ else
+ {
+ header.put(CONNECTION_CLOSE,0,CONNECTION_CLOSE.length-2);
+ header.put((byte)',');
+ header.put(StringUtil.getBytes(connection.toString()));
+ header.put(CRLF);
+ }
+ }
+ else if (keep_alive)
+ {
+ if (connection==null)
+ header.put(CONNECTION_KEEP_ALIVE);
+ else
+ {
+ header.put(CONNECTION_KEEP_ALIVE,0,CONNECTION_KEEP_ALIVE.length-2);
+ header.put((byte)',');
+ header.put(StringUtil.getBytes(connection.toString()));
+ header.put(CRLF);
+ }
+ }
+ else if (connection!=null)
+ {
+ header.put(HttpHeader.CONNECTION.getBytesColonSpace());
+ header.put(StringUtil.getBytes(connection.toString()));
+ header.put(CRLF);
+ }
+ }
+
+ if (status>199)
+ header.put(SEND[send]);
+
+ // end the header.
+ header.put(HttpTokens.CRLF);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ private void putContentLength(ByteBuffer header, long contentLength, boolean contentType, MetaData.Request request, MetaData.Response response)
+ {
+ if (contentLength>0)
+ {
+ header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
+ BufferUtil.putDecLong(header, contentLength);
+ header.put(HttpTokens.CRLF);
+ }
+ else if (!_noContent)
+ {
+ if (contentType || response!=null || (request!=null && __assumedContentMethods.contains(request.getMethod())))
+ header.put(CONTENT_LENGTH_0);
+ }
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public static byte[] getReasonBuffer(int code)
+ {
+ PreparedResponse status = code<__preprepared.length?__preprepared[code]:null;
+ if (status!=null)
+ return status._reason;
+ return null;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{s=%s}",
+ getClass().getSimpleName(),
+ hashCode(),
+ _state);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ // common _content
+ private static final byte[] LAST_CHUNK = { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
+ private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
+ private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012");
+ private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
+ private static final byte[] HTTP_1_1_SPACE = StringUtil.getBytes(HttpVersion.HTTP_1_1+" ");
+ private static final byte[] CRLF = StringUtil.getBytes("\015\012");
+ private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
+ private static final byte[][] SEND = new byte[][]{
+ new byte[0],
+ StringUtil.getBytes("Server: Jetty(9.x.x)\015\012"),
+ StringUtil.getBytes("X-Powered-By: Jetty(9.x.x)\015\012"),
+ StringUtil.getBytes("Server: Jetty(9.x.x)\015\012X-Powered-By: Jetty(9.x.x)\015\012")
+ };
+
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ // Build cache of response lines for status
+ private static class PreparedResponse
+ {
+ byte[] _reason;
+ byte[] _schemeCode;
+ byte[] _responseLine;
+ }
+ private static final PreparedResponse[] __preprepared = new PreparedResponse[HttpStatus.MAX_CODE+1];
+ static
+ {
+ int versionLength=HttpVersion.HTTP_1_1.toString().length();
+
+ for (int i=0;i<__preprepared.length;i++)
+ {
+ HttpStatus.Code code = HttpStatus.getCode(i);
+ if (code==null)
+ continue;
+ String reason=code.getMessage();
+ byte[] line=new byte[versionLength+5+reason.length()+2];
+ HttpVersion.HTTP_1_1.toBuffer().get(line,0,versionLength);
+ line[versionLength+0]=' ';
+ line[versionLength+1]=(byte)('0'+i/100);
+ line[versionLength+2]=(byte)('0'+(i%100)/10);
+ line[versionLength+3]=(byte)('0'+(i%10));
+ line[versionLength+4]=' ';
+ for (int j=0;j0xff || c=='\r' || c=='\n'|| c==':')
+ buffer.put((byte)'?');
+ else
+ buffer.put((byte)(0xff&c));
+ }
+ }
+
+ private static void putSanitisedValue(String s,ByteBuffer buffer)
+ {
+ int l=s.length();
+ for (int i=0;i0xff || c=='\r' || c=='\n')
+ buffer.put((byte)' ');
+ else
+ buffer.put((byte)(0xff&c));
+ }
+ }
+
+ public static void putTo(HttpField field, ByteBuffer bufferInFillMode)
+ {
+ if (field instanceof PreEncodedHttpField)
+ {
+ ((PreEncodedHttpField)field).putTo(bufferInFillMode,HttpVersion.HTTP_1_0);
+ }
+ else
+ {
+ HttpHeader header=field.getHeader();
+ if (header!=null)
+ {
+ bufferInFillMode.put(header.getBytesColonSpace());
+ putSanitisedValue(field.getValue(),bufferInFillMode);
+ }
+ else
+ {
+ putSanitisedName(field.getName(),bufferInFillMode);
+ bufferInFillMode.put(__colon_space);
+ putSanitisedValue(field.getValue(),bufferInFillMode);
+ }
+
+ BufferUtil.putCRLF(bufferInFillMode);
+ }
+ }
+
+ public static void putTo(HttpFields fields, ByteBuffer bufferInFillMode)
+ {
+ for (HttpField field : fields)
+ {
+ if (field != null)
+ putTo(field,bufferInFillMode);
+ }
+ BufferUtil.putCRLF(bufferInFillMode);
+ }
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpHeader.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpHeader.java
new file mode 100644
index 0000000000..b57a5fa244
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpHeader.java
@@ -0,0 +1,191 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+public enum HttpHeader
+{
+ /* ------------------------------------------------------------ */
+ /** General Fields.
+ */
+ CONNECTION("Connection"),
+ CACHE_CONTROL("Cache-Control"),
+ DATE("Date"),
+ PRAGMA("Pragma"),
+ PROXY_CONNECTION ("Proxy-Connection"),
+ TRAILER("Trailer"),
+ TRANSFER_ENCODING("Transfer-Encoding"),
+ UPGRADE("Upgrade"),
+ VIA("Via"),
+ WARNING("Warning"),
+ NEGOTIATE("Negotiate"),
+
+ /* ------------------------------------------------------------ */
+ /** Entity Fields.
+ */
+ ALLOW("Allow"),
+ CONTENT_ENCODING("Content-Encoding"),
+ CONTENT_LANGUAGE("Content-Language"),
+ CONTENT_LENGTH("Content-Length"),
+ CONTENT_LOCATION("Content-Location"),
+ CONTENT_MD5("Content-MD5"),
+ CONTENT_RANGE("Content-Range"),
+ CONTENT_TYPE("Content-Type"),
+ EXPIRES("Expires"),
+ LAST_MODIFIED("Last-Modified"),
+
+ /* ------------------------------------------------------------ */
+ /** Request Fields.
+ */
+ ACCEPT("Accept"),
+ ACCEPT_CHARSET("Accept-Charset"),
+ ACCEPT_ENCODING("Accept-Encoding"),
+ ACCEPT_LANGUAGE("Accept-Language"),
+ AUTHORIZATION("Authorization"),
+ EXPECT("Expect"),
+ FORWARDED("Forwarded"),
+ FROM("From"),
+ HOST("Host"),
+ IF_MATCH("If-Match"),
+ IF_MODIFIED_SINCE("If-Modified-Since"),
+ IF_NONE_MATCH("If-None-Match"),
+ IF_RANGE("If-Range"),
+ IF_UNMODIFIED_SINCE("If-Unmodified-Since"),
+ KEEP_ALIVE("Keep-Alive"),
+ MAX_FORWARDS("Max-Forwards"),
+ PROXY_AUTHORIZATION("Proxy-Authorization"),
+ RANGE("Range"),
+ REQUEST_RANGE("Request-Range"),
+ REFERER("Referer"),
+ TE("TE"),
+ USER_AGENT("User-Agent"),
+ X_FORWARDED_FOR("X-Forwarded-For"),
+ X_FORWARDED_PROTO("X-Forwarded-Proto"),
+ X_FORWARDED_SERVER("X-Forwarded-Server"),
+ X_FORWARDED_HOST("X-Forwarded-Host"),
+
+ /* ------------------------------------------------------------ */
+ /** Response Fields.
+ */
+ ACCEPT_RANGES("Accept-Ranges"),
+ AGE("Age"),
+ ETAG("ETag"),
+ LOCATION("Location"),
+ PROXY_AUTHENTICATE("Proxy-Authenticate"),
+ RETRY_AFTER("Retry-After"),
+ SERVER("Server"),
+ SERVLET_ENGINE("Servlet-Engine"),
+ VARY("Vary"),
+ WWW_AUTHENTICATE("WWW-Authenticate"),
+
+ /* ------------------------------------------------------------ */
+ /** Other Fields.
+ */
+ COOKIE("Cookie"),
+ SET_COOKIE("Set-Cookie"),
+ SET_COOKIE2("Set-Cookie2"),
+ MIME_VERSION("MIME-Version"),
+ IDENTITY("identity"),
+
+ X_POWERED_BY("X-Powered-By"),
+ HTTP2_SETTINGS("HTTP2-Settings"),
+
+ STRICT_TRANSPORT_SECURITY("Strict-Transport-Security"),
+
+ /* ------------------------------------------------------------ */
+ /** HTTP2 Fields.
+ */
+ C_METHOD(":method"),
+ C_SCHEME(":scheme"),
+ C_AUTHORITY(":authority"),
+ C_PATH(":path"),
+ C_STATUS(":status"),
+
+ UNKNOWN("::UNKNOWN::");
+
+
+ /* ------------------------------------------------------------ */
+ public final static Trie CACHE= new ArrayTrie<>(560);
+ static
+ {
+ for (HttpHeader header : HttpHeader.values())
+ if (header!=UNKNOWN)
+ if (!CACHE.put(header.toString(),header))
+ throw new IllegalStateException();
+ }
+
+ private final String _string;
+ private final byte[] _bytes;
+ private final byte[] _bytesColonSpace;
+ private final ByteBuffer _buffer;
+
+ /* ------------------------------------------------------------ */
+ HttpHeader(String s)
+ {
+ _string=s;
+ _bytes=StringUtil.getBytes(s);
+ _bytesColonSpace=StringUtil.getBytes(s+": ");
+ _buffer=ByteBuffer.wrap(_bytes);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteBuffer toBuffer()
+ {
+ return _buffer.asReadOnlyBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte[] getBytes()
+ {
+ return _bytes;
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte[] getBytesColonSpace()
+ {
+ return _bytesColonSpace;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean is(String s)
+ {
+ return _string.equalsIgnoreCase(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String asString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return _string;
+ }
+
+}
+
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpHeaderValue.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpHeaderValue.java
new file mode 100644
index 0000000000..71301003ee
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpHeaderValue.java
@@ -0,0 +1,104 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+/**
+ *
+ */
+public enum HttpHeaderValue
+{
+ CLOSE("close"),
+ CHUNKED("chunked"),
+ GZIP("gzip"),
+ IDENTITY("identity"),
+ KEEP_ALIVE("keep-alive"),
+ CONTINUE("100-continue"),
+ PROCESSING("102-processing"),
+ TE("TE"),
+ BYTES("bytes"),
+ NO_CACHE("no-cache"),
+ UPGRADE("Upgrade"),
+ UNKNOWN("::UNKNOWN::");
+
+ /* ------------------------------------------------------------ */
+ public final static Trie CACHE= new ArrayTrie();
+ static
+ {
+ for (HttpHeaderValue value : HttpHeaderValue.values())
+ if (value!=UNKNOWN)
+ CACHE.put(value.toString(),value);
+ }
+
+ private final String _string;
+ private final ByteBuffer _buffer;
+
+ /* ------------------------------------------------------------ */
+ HttpHeaderValue(String s)
+ {
+ _string=s;
+ _buffer=BufferUtil.toBuffer(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteBuffer toBuffer()
+ {
+ return _buffer.asReadOnlyBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean is(String s)
+ {
+ return _string.equalsIgnoreCase(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String asString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ private static EnumSet __known =
+ EnumSet.of(HttpHeader.CONNECTION,
+ HttpHeader.TRANSFER_ENCODING,
+ HttpHeader.CONTENT_ENCODING);
+
+ /* ------------------------------------------------------------ */
+ public static boolean hasKnownValues(HttpHeader header)
+ {
+ if (header==null)
+ return false;
+ return __known.contains(header);
+ }
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpMethod.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpMethod.java
new file mode 100644
index 0000000000..1d2eeda0e5
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpMethod.java
@@ -0,0 +1,187 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+/* ------------------------------------------------------------------------------- */
+/**
+ */
+public enum HttpMethod
+{
+ GET,
+ POST,
+ HEAD,
+ PUT,
+ OPTIONS,
+ DELETE,
+ TRACE,
+ CONNECT,
+ MOVE,
+ PROXY,
+ PRI;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Optimized lookup to find a method name and trailing space in a byte array.
+ * @param bytes Array containing ISO-8859-1 characters
+ * @param position The first valid index
+ * @param limit The first non valid index
+ * @return A HttpMethod if a match or null if no easy match.
+ */
+ public static HttpMethod lookAheadGet(byte[] bytes, final int position, int limit)
+ {
+ int length=limit-position;
+ if (length<4)
+ return null;
+ switch(bytes[position])
+ {
+ case 'G':
+ if (bytes[position+1]=='E' && bytes[position+2]=='T' && bytes[position+3]==' ')
+ return GET;
+ break;
+ case 'P':
+ if (bytes[position+1]=='O' && bytes[position+2]=='S' && bytes[position+3]=='T' && length>=5 && bytes[position+4]==' ')
+ return POST;
+ if (bytes[position+1]=='R' && bytes[position+2]=='O' && bytes[position+3]=='X' && length>=6 && bytes[position+4]=='Y' && bytes[position+5]==' ')
+ return PROXY;
+ if (bytes[position+1]=='U' && bytes[position+2]=='T' && bytes[position+3]==' ')
+ return PUT;
+ if (bytes[position+1]=='R' && bytes[position+2]=='I' && bytes[position+3]==' ')
+ return PRI;
+ break;
+ case 'H':
+ if (bytes[position+1]=='E' && bytes[position+2]=='A' && bytes[position+3]=='D' && length>=5 && bytes[position+4]==' ')
+ return HEAD;
+ break;
+ case 'O':
+ if (bytes[position+1]=='P' && bytes[position+2]=='T' && bytes[position+3]=='I' && length>=8 &&
+ bytes[position+4]=='O' && bytes[position+5]=='N' && bytes[position+6]=='S' && bytes[position+7]==' ' )
+ return OPTIONS;
+ break;
+ case 'D':
+ if (bytes[position+1]=='E' && bytes[position+2]=='L' && bytes[position+3]=='E' && length>=7 &&
+ bytes[position+4]=='T' && bytes[position+5]=='E' && bytes[position+6]==' ' )
+ return DELETE;
+ break;
+ case 'T':
+ if (bytes[position+1]=='R' && bytes[position+2]=='A' && bytes[position+3]=='C' && length>=6 &&
+ bytes[position+4]=='E' && bytes[position+5]==' ' )
+ return TRACE;
+ break;
+ case 'C':
+ if (bytes[position+1]=='O' && bytes[position+2]=='N' && bytes[position+3]=='N' && length>=8 &&
+ bytes[position+4]=='E' && bytes[position+5]=='C' && bytes[position+6]=='T' && bytes[position+7]==' ' )
+ return CONNECT;
+ break;
+ case 'M':
+ if (bytes[position+1]=='O' && bytes[position+2]=='V' && bytes[position+3]=='E' && length>=5 && bytes[position+4]==' ')
+ return MOVE;
+ break;
+
+ default:
+ break;
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Optimized lookup to find a method name and trailing space in a byte array.
+ * @param buffer buffer containing ISO-8859-1 characters, it is not modified.
+ * @return A HttpMethod if a match or null if no easy match.
+ */
+ public static HttpMethod lookAheadGet(ByteBuffer buffer)
+ {
+ if (buffer.hasArray())
+ return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit());
+
+ int l = buffer.remaining();
+ if (l>=4)
+ {
+ HttpMethod m = CACHE.getBest(buffer,0,l);
+ if (m!=null)
+ {
+ int ml = m.asString().length();
+ if (l>ml && buffer.get(buffer.position()+ml)==' ')
+ return m;
+ }
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public final static Trie CACHE= new ArrayTrie<>();
+ static
+ {
+ for (HttpMethod method : HttpMethod.values())
+ CACHE.put(method.toString(),method);
+ }
+
+ /* ------------------------------------------------------------ */
+ private final ByteBuffer _buffer;
+ private final byte[] _bytes;
+
+ /* ------------------------------------------------------------ */
+ HttpMethod()
+ {
+ _bytes=StringUtil.getBytes(toString());
+ _buffer=ByteBuffer.wrap(_bytes);
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte[] getBytes()
+ {
+ return _bytes;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean is(String s)
+ {
+ return toString().equalsIgnoreCase(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteBuffer asBuffer()
+ {
+ return _buffer.asReadOnlyBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ public String asString()
+ {
+ return toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Converts the given String parameter to an HttpMethod
+ * @param method the String to get the equivalent HttpMethod from
+ * @return the HttpMethod or null if the parameter method is unknown
+ */
+ public static HttpMethod fromString(String method)
+ {
+ return CACHE.get(method);
+ }
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpParser.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpParser.java
new file mode 100644
index 0000000000..12f74a50bb
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpParser.java
@@ -0,0 +1,1802 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.Locale;
+
+import org.eclipse.jetty.http.HttpTokens.EndOfContent;
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.Utf8StringBuilder;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+import static org.eclipse.jetty.http.HttpCompliance.LEGACY;
+import static org.eclipse.jetty.http.HttpCompliance.RFC2616;
+import static org.eclipse.jetty.http.HttpCompliance.RFC7230;
+import static org.eclipse.jetty.http.HttpTokens.CARRIAGE_RETURN;
+import static org.eclipse.jetty.http.HttpTokens.LINE_FEED;
+import static org.eclipse.jetty.http.HttpTokens.SPACE;
+import static org.eclipse.jetty.http.HttpTokens.TAB;
+
+
+/* ------------------------------------------------------------ */
+/** A Parser for 1.0 and 1.1 as defined by RFC7230
+ *
+ * This parser parses HTTP client and server messages from buffers
+ * passed in the {@link #parseNext(ByteBuffer)} method. The parsed
+ * elements of the HTTP message are passed as event calls to the
+ * {@link HttpHandler} instance the parser is constructed with.
+ * If the passed handler is a {@link RequestHandler} then server side
+ * parsing is performed and if it is a {@link ResponseHandler}, then
+ * client side parsing is done.
+ *
+ *
+ * The contract of the {@link HttpHandler} API is that if a call returns
+ * true then the call to {@link #parseNext(ByteBuffer)} will return as
+ * soon as possible also with a true response. Typically this indicates
+ * that the parsing has reached a stage where the caller should process
+ * the events accumulated by the handler. It is the preferred calling
+ * style that handling such as calling a servlet to process a request,
+ * should be done after a true return from {@link #parseNext(ByteBuffer)}
+ * rather than from within the scope of a call like
+ * {@link RequestHandler#messageComplete()}
+ *
+ *
+ * For performance, the parse is heavily dependent on the
+ * {@link Trie#getBest(ByteBuffer, int, int)} method to look ahead in a
+ * single pass for both the structure ( : and CRLF ) and semantic (which
+ * header and value) of a header. Specifically the static {@link HttpHeader#CACHE}
+ * is used to lookup common combinations of headers and values
+ * (eg. "Connection: close"), or just header names (eg. "Connection:" ).
+ * For headers who's value is not known statically (eg. Host, COOKIE) then a
+ * per parser dynamic Trie of {@link HttpFields} from previous parsed messages
+ * is used to help the parsing of subsequent messages.
+ *
+ *
+ * The parser can work in varying compliance modes:
+ *
+ * - RFC7230
- (default) Compliance with RFC7230
+ * - RFC2616
- Wrapped headers and HTTP/0.9 supported
+ * - LEGACY
- (aka STRICT) Adherence to Servlet Specification requirement for
+ * exact case of header names, bypassing the header caches, which are case insensitive,
+ * otherwise equivalent to RFC2616
+ *
+ * @see RFC 7230
+ */
+public class HttpParser
+{
+ public static final Logger LOG = Log.getLogger(HttpParser.class);
+ @Deprecated
+ public final static String __STRICT="org.eclipse.jetty.http.HttpParser.STRICT";
+ public final static int INITIAL_URI_LENGTH=256;
+
+ /**
+ * Cache of common {@link HttpField}s including:
+ * - Common static combinations such as:
+ * - Connection: close
+ *
- Accept-Encoding: gzip
+ *
- Content-Length: 0
+ *
+ * - Combinations of Content-Type header for common mime types by common charsets
+ *
- Most common headers with null values so that a lookup will at least
+ * determine the header name even if the name:value combination is not cached
+ *
+ */
+ public final static Trie CACHE = new ArrayTrie<>(2048);
+
+ // States
+ public enum State
+ {
+ START,
+ METHOD,
+ RESPONSE_VERSION,
+ SPACE1,
+ STATUS,
+ URI,
+ SPACE2,
+ REQUEST_VERSION,
+ REASON,
+ PROXY,
+ HEADER,
+ HEADER_IN_NAME,
+ HEADER_VALUE,
+ HEADER_IN_VALUE,
+ CONTENT,
+ EOF_CONTENT,
+ CHUNKED_CONTENT,
+ CHUNK_SIZE,
+ CHUNK_PARAMS,
+ CHUNK,
+ CHUNK_END,
+ END,
+ CLOSE, // The associated stream/endpoint should be closed
+ CLOSED // The associated stream/endpoint is at EOF
+ }
+
+ private final static EnumSet __idleStates = EnumSet.of(State.START,State.END,State.CLOSE,State.CLOSED);
+ private final static EnumSet __completeStates = EnumSet.of(State.END,State.CLOSE,State.CLOSED);
+
+ private final boolean DEBUG=LOG.isDebugEnabled(); // Cache debug to help branch prediction
+ private final HttpHandler _handler;
+ private final RequestHandler _requestHandler;
+ private final ResponseHandler _responseHandler;
+ private final ComplianceHandler _complianceHandler;
+ private final int _maxHeaderBytes;
+ private final HttpCompliance _compliance;
+ private HttpField _field;
+ private HttpHeader _header;
+ private String _headerString;
+ private HttpHeaderValue _value;
+ private String _valueString;
+ private int _responseStatus;
+ private int _headerBytes;
+ private boolean _host;
+
+ /* ------------------------------------------------------------------------------- */
+ private volatile State _state=State.START;
+ private volatile boolean _eof;
+ private HttpMethod _method;
+ private String _methodString;
+ private HttpVersion _version;
+ private Utf8StringBuilder _uri=new Utf8StringBuilder(INITIAL_URI_LENGTH); // Tune?
+ private EndOfContent _endOfContent;
+ private long _contentLength;
+ private long _contentPosition;
+ private int _chunkLength;
+ private int _chunkPosition;
+ private boolean _headResponse;
+ private boolean _cr;
+ private ByteBuffer _contentChunk;
+ private Trie _connectionFields;
+
+ private int _length;
+ private final StringBuilder _string=new StringBuilder();
+
+ static
+ {
+ CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE));
+ CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.KEEP_ALIVE));
+ CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.UPGRADE));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip, deflate"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip,deflate,sdch"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-US,en;q=0.5"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-GB,en-US;q=0.8,en;q=0.6"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_CHARSET,"ISO-8859-1,utf-8;q=0.7,*;q=0.3"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT,"*/*"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT,"image/png,image/*;q=0.8,*/*;q=0.5"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT,"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"));
+ CACHE.put(new HttpField(HttpHeader.PRAGMA,"no-cache"));
+ CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"private, no-cache, no-cache=Set-Cookie, proxy-revalidate"));
+ CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"no-cache"));
+ CACHE.put(new HttpField(HttpHeader.CONTENT_LENGTH,"0"));
+ CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"gzip"));
+ CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"deflate"));
+ CACHE.put(new HttpField(HttpHeader.TRANSFER_ENCODING,"chunked"));
+ CACHE.put(new HttpField(HttpHeader.EXPIRES,"Fri, 01 Jan 1990 00:00:00 GMT"));
+
+ // Add common Content types as fields
+ for (String type : new String[]{"text/plain","text/html","text/xml","text/json","application/json","application/x-www-form-urlencoded"})
+ {
+ HttpField field=new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,type);
+ CACHE.put(field);
+
+ for (String charset : new String[]{"utf-8","iso-8859-1"})
+ {
+ CACHE.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset));
+ CACHE.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,type+"; charset="+charset));
+ CACHE.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset.toUpperCase(Locale.ENGLISH)));
+ CACHE.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,type+"; charset="+charset.toUpperCase(Locale.ENGLISH)));
+ }
+ }
+
+ // Add headers with null values so HttpParser can avoid looking up name again for unknown values
+ for (HttpHeader h:HttpHeader.values())
+ if (!CACHE.put(new HttpField(h,(String)null)))
+ throw new IllegalStateException("CACHE FULL");
+ // Add some more common headers
+ CACHE.put(new HttpField(HttpHeader.REFERER,(String)null));
+ CACHE.put(new HttpField(HttpHeader.IF_MODIFIED_SINCE,(String)null));
+ CACHE.put(new HttpField(HttpHeader.IF_NONE_MATCH,(String)null));
+ CACHE.put(new HttpField(HttpHeader.AUTHORIZATION,(String)null));
+ CACHE.put(new HttpField(HttpHeader.COOKIE,(String)null));
+ }
+
+ private static HttpCompliance compliance()
+ {
+ Boolean strict = Boolean.getBoolean(__STRICT);
+ return strict?HttpCompliance.LEGACY:HttpCompliance.RFC7230;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(RequestHandler handler)
+ {
+ this(handler,-1,compliance());
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(ResponseHandler handler)
+ {
+ this(handler,-1,compliance());
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(RequestHandler handler,int maxHeaderBytes)
+ {
+ this(handler,maxHeaderBytes,compliance());
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(ResponseHandler handler,int maxHeaderBytes)
+ {
+ this(handler,maxHeaderBytes,compliance());
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ @Deprecated
+ public HttpParser(RequestHandler handler,int maxHeaderBytes,boolean strict)
+ {
+ this(handler,maxHeaderBytes,strict?HttpCompliance.LEGACY:compliance());
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ @Deprecated
+ public HttpParser(ResponseHandler handler,int maxHeaderBytes,boolean strict)
+ {
+ this(handler,maxHeaderBytes,strict?HttpCompliance.LEGACY:compliance());
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(RequestHandler handler,HttpCompliance compliance)
+ {
+ this(handler,-1,compliance);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(RequestHandler handler,int maxHeaderBytes,HttpCompliance compliance)
+ {
+ _handler=handler;
+ _requestHandler=handler;
+ _responseHandler=null;
+ _maxHeaderBytes=maxHeaderBytes;
+ _compliance=compliance==null?compliance():compliance;
+ _complianceHandler=(ComplianceHandler)(handler instanceof ComplianceHandler?handler:null);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(ResponseHandler handler,int maxHeaderBytes,HttpCompliance compliance)
+ {
+ _handler=handler;
+ _requestHandler=null;
+ _responseHandler=handler;
+ _maxHeaderBytes=maxHeaderBytes;
+ _compliance=compliance==null?compliance():compliance;
+ _complianceHandler=(ComplianceHandler)(handler instanceof ComplianceHandler?handler:null);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpHandler getHandler()
+ {
+ return _handler;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /** Check RFC compliance violation
+ * @param compliance The compliance level violated
+ * @param reason The reason for the violation
+ * @return True if the current compliance level is set so as to Not allow this violation
+ */
+ protected boolean complianceViolation(HttpCompliance compliance,String reason)
+ {
+ if (_complianceHandler==null)
+ return _compliance.ordinal()>=compliance.ordinal();
+ if (_compliance.ordinal()=State.CONTENT.ordinal() && _state.ordinal()0 && _state.ordinal() SPACE)
+ {
+ _string.setLength(0);
+ _string.append((char)ch);
+ setState(_requestHandler!=null?State.METHOD:State.RESPONSE_VERSION);
+ return false;
+ }
+ else if (ch==0)
+ break;
+ else if (ch<0)
+ throw new BadMessageException();
+
+ // count this white space as a header byte to avoid DOS
+ if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+ {
+ LOG.warn("padding is too large >"+_maxHeaderBytes);
+ throw new BadMessageException(HttpStatus.BAD_REQUEST_400);
+ }
+ }
+ return false;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ private void setString(String s)
+ {
+ _string.setLength(0);
+ _string.append(s);
+ _length=s.length();
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ private String takeString()
+ {
+ _string.setLength(_length);
+ String s =_string.toString();
+ _string.setLength(0);
+ _length=-1;
+ return s;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /* Parse a request or response line
+ */
+ private boolean parseLine(ByteBuffer buffer)
+ {
+ boolean handle=false;
+
+ // Process headers
+ while (_state.ordinal()0 && ++_headerBytes>_maxHeaderBytes)
+ {
+ if (_state==State.URI)
+ {
+ LOG.warn("URI is too large >"+_maxHeaderBytes);
+ throw new BadMessageException(HttpStatus.REQUEST_URI_TOO_LONG_414);
+ }
+ else
+ {
+ if (_requestHandler!=null)
+ LOG.warn("request is too large >"+_maxHeaderBytes);
+ else
+ LOG.warn("response is too large >"+_maxHeaderBytes);
+ throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
+ }
+ }
+
+ switch (_state)
+ {
+ case METHOD:
+ if (ch == SPACE)
+ {
+ _length=_string.length();
+ _methodString=takeString();
+ HttpMethod method=HttpMethod.CACHE.get(_methodString);
+ if (method!=null)
+ _methodString=legacyString(_methodString,method.asString());
+ setState(State.SPACE1);
+ }
+ else if (ch < SPACE)
+ {
+ if (ch==LINE_FEED)
+ throw new BadMessageException("No URI");
+ else
+ throw new IllegalCharacterException(_state,ch,buffer);
+ }
+ else
+ _string.append((char)ch);
+ break;
+
+ case RESPONSE_VERSION:
+ if (ch == HttpTokens.SPACE)
+ {
+ _length=_string.length();
+ String version=takeString();
+ _version=HttpVersion.CACHE.get(version);
+ if (_version==null)
+ throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Unknown Version");
+ setState(State.SPACE1);
+ }
+ else if (ch < HttpTokens.SPACE)
+ throw new IllegalCharacterException(_state,ch,buffer);
+ else
+ _string.append((char)ch);
+ break;
+
+ case SPACE1:
+ if (ch > HttpTokens.SPACE || ch<0)
+ {
+ if (_responseHandler!=null)
+ {
+ setState(State.STATUS);
+ setResponseStatus(ch-'0');
+ }
+ else
+ {
+ _uri.reset();
+ setState(State.URI);
+ // quick scan for space or EoBuffer
+ if (buffer.hasArray())
+ {
+ byte[] array=buffer.array();
+ int p=buffer.arrayOffset()+buffer.position();
+ int l=buffer.arrayOffset()+buffer.limit();
+ int i=p;
+ while (iHttpTokens.SPACE)
+ i++;
+
+ int len=i-p;
+ _headerBytes+=len;
+
+ if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+ {
+ LOG.warn("URI is too large >"+_maxHeaderBytes);
+ throw new BadMessageException(HttpStatus.REQUEST_URI_TOO_LONG_414);
+ }
+ _uri.append(array,p-1,len+1);
+ buffer.position(i-buffer.arrayOffset());
+ }
+ else
+ _uri.append(ch);
+ }
+ }
+ else if (ch < HttpTokens.SPACE)
+ {
+ throw new BadMessageException(HttpStatus.BAD_REQUEST_400,_requestHandler!=null?"No URI":"No Status");
+ }
+ break;
+
+ case STATUS:
+ if (ch == HttpTokens.SPACE)
+ {
+ setState(State.SPACE2);
+ }
+ else if (ch>='0' && ch<='9')
+ {
+ _responseStatus=_responseStatus*10+(ch-'0');
+ }
+ else if (ch < HttpTokens.SPACE && ch>=0)
+ {
+ setState(State.HEADER);
+ handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle;
+ }
+ else
+ {
+ throw new BadMessageException();
+ }
+ break;
+
+ case URI:
+ if (ch == HttpTokens.SPACE)
+ {
+ setState(State.SPACE2);
+ }
+ else if (ch < HttpTokens.SPACE && ch>=0)
+ {
+ // HTTP/0.9
+ if (complianceViolation(RFC7230,"HTTP/0.9"))
+ throw new BadMessageException("HTTP/0.9 not supported");
+ handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9);
+ setState(State.END);
+ BufferUtil.clear(buffer);
+ handle=_handler.headerComplete()||handle;
+ handle=_handler.messageComplete()||handle;
+ return handle;
+ }
+ else
+ {
+ _uri.append(ch);
+ }
+ break;
+
+ case SPACE2:
+ if (ch > HttpTokens.SPACE)
+ {
+ _string.setLength(0);
+ _string.append((char)ch);
+ if (_responseHandler!=null)
+ {
+ _length=1;
+ setState(State.REASON);
+ }
+ else
+ {
+ setState(State.REQUEST_VERSION);
+
+ // try quick look ahead for HTTP Version
+ HttpVersion version;
+ if (buffer.position()>0 && buffer.hasArray())
+ version=HttpVersion.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit());
+ else
+ version=HttpVersion.CACHE.getBest(buffer,0,buffer.remaining());
+
+ if (version!=null)
+ {
+ int pos = buffer.position()+version.asString().length()-1;
+ if (pos=HttpVersion.HTTP_1_1.getVersion() && _handler.getHeaderCacheSize()>0)
+ {
+ int header_cache = _handler.getHeaderCacheSize();
+ _connectionFields=new ArrayTernaryTrie<>(header_cache);
+ }
+
+ setState(State.HEADER);
+
+ handle=_requestHandler.startRequest(_methodString,_uri.toString(), _version)||handle;
+ continue;
+ }
+ else if (ch>=HttpTokens.SPACE)
+ _string.append((char)ch);
+ else
+ throw new BadMessageException();
+
+ break;
+
+ case REASON:
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ String reason=takeString();
+ setState(State.HEADER);
+ handle=_responseHandler.startResponse(_version, _responseStatus, reason)||handle;
+ continue;
+ }
+ else if (ch>=HttpTokens.SPACE)
+ {
+ _string.append((char)ch);
+ if (ch!=' '&&ch!='\t')
+ _length=_string.length();
+ }
+ else
+ throw new BadMessageException();
+ break;
+
+ default:
+ throw new IllegalStateException(_state.toString());
+
+ }
+ }
+
+ return handle;
+ }
+
+ private void parsedHeader()
+ {
+ // handler last header if any. Delayed to here just in case there was a continuation line (above)
+ if (_headerString!=null || _valueString!=null)
+ {
+ // Handle known headers
+ if (_header!=null)
+ {
+ boolean add_to_connection_trie=false;
+ switch (_header)
+ {
+ case CONTENT_LENGTH:
+ if (_endOfContent == EndOfContent.CONTENT_LENGTH)
+ {
+ throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Duplicate Content-Length");
+ }
+ else if (_endOfContent != EndOfContent.CHUNKED_CONTENT)
+ {
+ _contentLength=convertContentLength(_valueString);
+ if (_contentLength <= 0)
+ _endOfContent=EndOfContent.NO_CONTENT;
+ else
+ _endOfContent=EndOfContent.CONTENT_LENGTH;
+ }
+ break;
+
+ case TRANSFER_ENCODING:
+ if (_value==HttpHeaderValue.CHUNKED)
+ {
+ _endOfContent=EndOfContent.CHUNKED_CONTENT;
+ _contentLength=-1;
+ }
+ else
+ {
+ if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString()))
+ _endOfContent=EndOfContent.CHUNKED_CONTENT;
+ else if (_valueString.contains(HttpHeaderValue.CHUNKED.toString()))
+ throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad chunking");
+ }
+ break;
+
+ case HOST:
+ _host=true;
+ if (!(_field instanceof HostPortHttpField))
+ {
+ _field=new HostPortHttpField(_header,legacyString(_headerString,_header.asString()),_valueString);
+ add_to_connection_trie=_connectionFields!=null;
+ }
+ break;
+
+ case CONNECTION:
+ // Don't cache if not persistent
+ if (_valueString!=null && _valueString.contains("close"))
+ _connectionFields=null;
+
+ break;
+
+ case AUTHORIZATION:
+ case ACCEPT:
+ case ACCEPT_CHARSET:
+ case ACCEPT_ENCODING:
+ case ACCEPT_LANGUAGE:
+ case COOKIE:
+ case CACHE_CONTROL:
+ case USER_AGENT:
+ add_to_connection_trie=_connectionFields!=null && _field==null;
+ break;
+
+ default: break;
+ }
+
+ if (add_to_connection_trie && !_connectionFields.isFull() && _header!=null && _valueString!=null)
+ {
+ if (_field==null)
+ _field=new HttpField(_header,legacyString(_headerString,_header.asString()),_valueString);
+ _connectionFields.put(_field);
+ }
+ }
+ _handler.parsedHeader(_field!=null?_field:new HttpField(_header,_headerString,_valueString));
+ }
+
+ _headerString=_valueString=null;
+ _header=null;
+ _value=null;
+ _field=null;
+ }
+
+ private long convertContentLength(String valueString)
+ {
+ try
+ {
+ return Long.parseLong(valueString);
+ }
+ catch(NumberFormatException e)
+ {
+ LOG.ignore(e);
+ throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Invalid Content-Length Value");
+ }
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /*
+ * Parse the message headers and return true if the handler has signaled for a return
+ */
+ protected boolean parseHeaders(ByteBuffer buffer)
+ {
+ boolean handle=false;
+
+ // Process headers
+ while (_state.ordinal()0 && ++_headerBytes>_maxHeaderBytes)
+ {
+ LOG.warn("Header is too large >"+_maxHeaderBytes);
+ throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
+ }
+
+ switch (_state)
+ {
+ case HEADER:
+ switch(ch)
+ {
+ case HttpTokens.COLON:
+ case HttpTokens.SPACE:
+ case HttpTokens.TAB:
+ {
+ if (complianceViolation(RFC7230,"header folding"))
+ throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Header Folding");
+
+ // header value without name - continuation?
+ if (_valueString==null)
+ {
+ _string.setLength(0);
+ _length=0;
+ }
+ else
+ {
+ setString(_valueString);
+ _string.append(' ');
+ _length++;
+ _valueString=null;
+ }
+ setState(State.HEADER_VALUE);
+ break;
+ }
+
+ case HttpTokens.LINE_FEED:
+ {
+ // process previous header
+ parsedHeader();
+
+ _contentPosition=0;
+
+ // End of headers!
+
+ // Was there a required host header?
+ if (!_host && _version==HttpVersion.HTTP_1_1 && _requestHandler!=null)
+ {
+ throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"No Host");
+ }
+
+ // is it a response that cannot have a body?
+ if (_responseHandler !=null && // response
+ (_responseStatus == 304 || // not-modified response
+ _responseStatus == 204 || // no-content response
+ _responseStatus < 200)) // 1xx response
+ _endOfContent=EndOfContent.NO_CONTENT; // ignore any other headers set
+
+ // else if we don't know framing
+ else if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
+ {
+ if (_responseStatus == 0 // request
+ || _responseStatus == 304 // not-modified response
+ || _responseStatus == 204 // no-content response
+ || _responseStatus < 200) // 1xx response
+ _endOfContent=EndOfContent.NO_CONTENT;
+ else
+ _endOfContent=EndOfContent.EOF_CONTENT;
+ }
+
+ // How is the message ended?
+ switch (_endOfContent)
+ {
+ case EOF_CONTENT:
+ setState(State.EOF_CONTENT);
+ handle=_handler.headerComplete()||handle;
+ return handle;
+
+ case CHUNKED_CONTENT:
+ setState(State.CHUNKED_CONTENT);
+ handle=_handler.headerComplete()||handle;
+ return handle;
+
+ case NO_CONTENT:
+ setState(State.END);
+ handle=_handler.headerComplete()||handle;
+ handle=_handler.messageComplete()||handle;
+ return handle;
+
+ default:
+ setState(State.CONTENT);
+ handle=_handler.headerComplete()||handle;
+ return handle;
+ }
+ }
+
+ default:
+ {
+ // now handle the ch
+ if (chHttpTokens.SPACE)
+ {
+ if (_header!=null)
+ {
+ setString(_header.asString());
+ _header=null;
+ _headerString=null;
+ }
+
+ _string.append((char)ch);
+ if (ch>HttpTokens.SPACE)
+ _length=_string.length();
+ break;
+ }
+
+ if (ch==HttpTokens.LINE_FEED && !complianceViolation(RFC7230,"name only header"))
+ {
+ if (_headerString==null)
+ {
+ _headerString=takeString();
+ _header=HttpHeader.CACHE.get(_headerString);
+ }
+ _value=null;
+ _string.setLength(0);
+ _valueString="";
+ _length=-1;
+
+ setState(State.HEADER);
+ break;
+ }
+
+ throw new IllegalCharacterException(_state,ch,buffer);
+
+ case HEADER_VALUE:
+ if (ch>HttpTokens.SPACE || ch<0)
+ {
+ _string.append((char)(0xff&ch));
+ _length=_string.length();
+ setState(State.HEADER_IN_VALUE);
+ break;
+ }
+
+ if (ch==HttpTokens.SPACE || ch==HttpTokens.TAB)
+ break;
+
+ if (ch==HttpTokens.LINE_FEED)
+ {
+ _value=null;
+ _string.setLength(0);
+ _valueString="";
+ _length=-1;
+
+ setState(State.HEADER);
+ break;
+ }
+ throw new IllegalCharacterException(_state,ch,buffer);
+
+ case HEADER_IN_VALUE:
+ if (ch>=HttpTokens.SPACE || ch<0 || ch==HttpTokens.TAB)
+ {
+ if (_valueString!=null)
+ {
+ setString(_valueString);
+ _valueString=null;
+ _field=null;
+ }
+ _string.append((char)(0xff&ch));
+ if (ch>HttpTokens.SPACE || ch<0)
+ _length=_string.length();
+ break;
+ }
+
+ if (ch==HttpTokens.LINE_FEED)
+ {
+ if (_length > 0)
+ {
+ _value=null;
+ _valueString=takeString();
+ _length=-1;
+ }
+ setState(State.HEADER);
+ break;
+ }
+
+ throw new IllegalCharacterException(_state,ch,buffer);
+
+ default:
+ throw new IllegalStateException(_state.toString());
+
+ }
+ }
+
+ return handle;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /**
+ * Parse until next Event.
+ * @param buffer the buffer to parse
+ * @return True if an {@link RequestHandler} method was called and it returned true;
+ */
+ public boolean parseNext(ByteBuffer buffer)
+ {
+ if (DEBUG)
+ LOG.debug("parseNext s={} {}",_state,BufferUtil.toDetailString(buffer));
+ try
+ {
+ // Start a request/response
+ if (_state==State.START)
+ {
+ _version=null;
+ _method=null;
+ _methodString=null;
+ _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+ _header=null;
+ if (quickStart(buffer))
+ return true;
+ }
+
+ // Request/response line
+ if (_state.ordinal()>= State.START.ordinal() && _state.ordinal()= State.HEADER.ordinal() && _state.ordinal()= State.CONTENT.ordinal() && _state.ordinal()0 && _headResponse)
+ {
+ setState(State.END);
+ return _handler.messageComplete();
+ }
+ else
+ {
+ if (parseContent(buffer))
+ return true;
+ }
+ }
+
+ // handle end states
+ if (_state==State.END)
+ {
+ // eat white space
+ while (buffer.remaining()>0 && buffer.get(buffer.position())<=HttpTokens.SPACE)
+ buffer.get();
+ }
+ else if (_state==State.CLOSE)
+ {
+ // Seeking EOF
+ if (BufferUtil.hasContent(buffer))
+ {
+ // Just ignore data when closed
+ _headerBytes+=buffer.remaining();
+ BufferUtil.clear(buffer);
+ if (_maxHeaderBytes>0 && _headerBytes>_maxHeaderBytes)
+ {
+ // Don't want to waste time reading data of a closed request
+ throw new IllegalStateException("too much data seeking EOF");
+ }
+ }
+ }
+ else if (_state==State.CLOSED)
+ {
+ BufferUtil.clear(buffer);
+ }
+
+ // Handle EOF
+ if (_eof && !buffer.hasRemaining())
+ {
+ switch(_state)
+ {
+ case CLOSED:
+ break;
+
+ case START:
+ setState(State.CLOSED);
+ _handler.earlyEOF();
+ break;
+
+ case END:
+ case CLOSE:
+ setState(State.CLOSED);
+ break;
+
+ case EOF_CONTENT:
+ setState(State.CLOSED);
+ return _handler.messageComplete();
+
+ case CONTENT:
+ case CHUNKED_CONTENT:
+ case CHUNK_SIZE:
+ case CHUNK_PARAMS:
+ case CHUNK:
+ setState(State.CLOSED);
+ _handler.earlyEOF();
+ break;
+
+ default:
+ if (DEBUG)
+ LOG.debug("{} EOF in {}",this,_state);
+ setState(State.CLOSED);
+ _handler.badMessage(400,null);
+ break;
+ }
+ }
+ }
+ catch(BadMessageException e)
+ {
+ BufferUtil.clear(buffer);
+
+ Throwable cause = e.getCause();
+ boolean stack = LOG.isDebugEnabled() ||
+ (!(cause instanceof NumberFormatException ) && (cause instanceof RuntimeException || cause instanceof Error));
+
+ if (stack)
+ LOG.warn("bad HTTP parsed: "+e._code+(e.getReason()!=null?" "+e.getReason():"")+" for "+_handler,e);
+ else
+ LOG.warn("bad HTTP parsed: "+e._code+(e.getReason()!=null?" "+e.getReason():"")+" for "+_handler);
+ setState(State.CLOSE);
+ _handler.badMessage(e.getCode(), e.getReason());
+ }
+ catch(NumberFormatException|IllegalStateException e)
+ {
+ BufferUtil.clear(buffer);
+ LOG.warn("parse exception: {} in {} for {}",e.toString(),_state,_handler);
+ if (DEBUG)
+ LOG.debug(e);
+
+ switch(_state)
+ {
+ case CLOSED:
+ break;
+ case CLOSE:
+ _handler.earlyEOF();
+ break;
+ default:
+ setState(State.CLOSE);
+ _handler.badMessage(400,"Bad Message "+e.toString());
+ }
+ }
+ catch(Exception|Error e)
+ {
+ BufferUtil.clear(buffer);
+
+ LOG.warn("parse exception: "+e.toString()+" for "+_handler,e);
+
+ switch(_state)
+ {
+ case CLOSED:
+ break;
+ case CLOSE:
+ _handler.earlyEOF();
+ break;
+ default:
+ setState(State.CLOSE);
+ _handler.badMessage(400,null);
+ }
+ }
+ return false;
+ }
+
+ protected boolean parseContent(ByteBuffer buffer)
+ {
+ int remaining=buffer.remaining();
+ if (remaining==0 && _state==State.CONTENT)
+ {
+ long content=_contentLength - _contentPosition;
+ if (content == 0)
+ {
+ setState(State.END);
+ return _handler.messageComplete();
+ }
+ }
+
+ // Handle _content
+ byte ch;
+ while (_state.ordinal() < State.END.ordinal() && remaining>0)
+ {
+ switch (_state)
+ {
+ case EOF_CONTENT:
+ _contentChunk=buffer.asReadOnlyBuffer();
+ _contentPosition += remaining;
+ buffer.position(buffer.position()+remaining);
+ if (_handler.content(_contentChunk))
+ return true;
+ break;
+
+ case CONTENT:
+ {
+ long content=_contentLength - _contentPosition;
+ if (content == 0)
+ {
+ setState(State.END);
+ return _handler.messageComplete();
+ }
+ else
+ {
+ _contentChunk=buffer.asReadOnlyBuffer();
+
+ // limit content by expected size
+ if (remaining > content)
+ {
+ // We can cast remaining to an int as we know that it is smaller than
+ // or equal to length which is already an int.
+ _contentChunk.limit(_contentChunk.position()+(int)content);
+ }
+
+ _contentPosition += _contentChunk.remaining();
+ buffer.position(buffer.position()+_contentChunk.remaining());
+
+ if (_handler.content(_contentChunk))
+ return true;
+
+ if(_contentPosition == _contentLength)
+ {
+ setState(State.END);
+ return _handler.messageComplete();
+ }
+ }
+ break;
+ }
+
+ case CHUNKED_CONTENT:
+ {
+ ch=next(buffer);
+ if (ch>HttpTokens.SPACE)
+ {
+ _chunkLength=TypeUtil.convertHexDigit(ch);
+ _chunkPosition=0;
+ setState(State.CHUNK_SIZE);
+ }
+
+ break;
+ }
+
+ case CHUNK_SIZE:
+ {
+ ch=next(buffer);
+ if (ch==0)
+ break;
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ if (_chunkLength == 0)
+ setState(State.CHUNK_END);
+ else
+ setState(State.CHUNK);
+ }
+ else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON)
+ setState(State.CHUNK_PARAMS);
+ else
+ _chunkLength=_chunkLength * 16 + TypeUtil.convertHexDigit(ch);
+ break;
+ }
+
+ case CHUNK_PARAMS:
+ {
+ ch=next(buffer);
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ if (_chunkLength == 0)
+ setState(State.CHUNK_END);
+ else
+ setState(State.CHUNK);
+ }
+ break;
+ }
+
+ case CHUNK:
+ {
+ int chunk=_chunkLength - _chunkPosition;
+ if (chunk == 0)
+ {
+ setState(State.CHUNKED_CONTENT);
+ }
+ else
+ {
+ _contentChunk=buffer.asReadOnlyBuffer();
+
+ if (remaining > chunk)
+ _contentChunk.limit(_contentChunk.position()+chunk);
+ chunk=_contentChunk.remaining();
+
+ _contentPosition += chunk;
+ _chunkPosition += chunk;
+ buffer.position(buffer.position()+chunk);
+ if (_handler.content(_contentChunk))
+ return true;
+ }
+ break;
+ }
+
+ case CHUNK_END:
+ {
+ // TODO handle chunk trailer
+ ch=next(buffer);
+ if (ch==0)
+ break;
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ setState(State.END);
+ return _handler.messageComplete();
+ }
+ throw new IllegalCharacterException(_state,ch,buffer);
+ }
+
+ case CLOSED:
+ {
+ BufferUtil.clear(buffer);
+ return false;
+ }
+
+ default:
+ break;
+
+ }
+
+ remaining=buffer.remaining();
+ }
+ return false;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public boolean isAtEOF()
+
+ {
+ return _eof;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /** Signal that the associated data source is at EOF
+ */
+ public void atEOF()
+ {
+ if (DEBUG)
+ LOG.debug("atEOF {}", this);
+ _eof=true;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /** Request that the associated data source be closed
+ */
+ public void close()
+ {
+ if (DEBUG)
+ LOG.debug("close {}", this);
+ setState(State.CLOSE);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public void reset()
+ {
+ if (DEBUG)
+ LOG.debug("reset {}", this);
+
+ // reset state
+ if (_state==State.CLOSE || _state==State.CLOSED)
+ return;
+
+ setState(State.START);
+ _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+ _contentLength=-1;
+ _contentPosition=0;
+ _responseStatus=0;
+ _contentChunk=null;
+ _headerBytes=0;
+ _host=false;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ protected void setState(State state)
+ {
+ if (DEBUG)
+ LOG.debug("{} --> {}",_state,state);
+ _state=state;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public Trie getFieldCache()
+ {
+ return _connectionFields;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ private String getProxyField(ByteBuffer buffer)
+ {
+ _string.setLength(0);
+ _length=0;
+
+ while (buffer.hasRemaining())
+ {
+ // process each character
+ byte ch=next(buffer);
+ if (ch<=' ')
+ return _string.toString();
+ _string.append((char)ch);
+ }
+ throw new BadMessageException();
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ @Override
+ public String toString()
+ {
+ return String.format("%s{s=%s,%d of %d}",
+ getClass().getSimpleName(),
+ _state,
+ _contentPosition,
+ _contentLength);
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* Event Handler interface
+ * These methods return true if the caller should process the events
+ * so far received (eg return from parseNext and call HttpChannel.handle).
+ * If multiple callbacks are called in sequence (eg
+ * headerComplete then messageComplete) from the same point in the parsing
+ * then it is sufficient for the caller to process the events only once.
+ */
+ public interface HttpHandler
+ {
+ public boolean content(ByteBuffer item);
+
+ public boolean headerComplete();
+
+ public boolean messageComplete();
+
+ /**
+ * This is the method called by parser when a HTTP Header name and value is found
+ * @param field The field parsed
+ */
+ public void parsedHeader(HttpField field);
+
+ /* ------------------------------------------------------------ */
+ /** Called to signal that an EOF was received unexpectedly
+ * during the parsing of a HTTP message
+ */
+ public void earlyEOF();
+
+ /* ------------------------------------------------------------ */
+ /** Called to signal that a bad HTTP message has been received.
+ * @param status The bad status to send
+ * @param reason The textual reason for badness
+ */
+ public void badMessage(int status, String reason);
+
+ /* ------------------------------------------------------------ */
+ /** @return the size in bytes of the per parser header cache
+ */
+ public int getHeaderCacheSize();
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ public interface RequestHandler extends HttpHandler
+ {
+ /**
+ * This is the method called by parser when the HTTP request line is parsed
+ * @param method The method
+ * @param uri The raw bytes of the URI. These are copied into a ByteBuffer that will not be changed until this parser is reset and reused.
+ * @param version the http version in use
+ * @return true if handling parsing should return.
+ */
+ public boolean startRequest(String method, String uri, HttpVersion version);
+
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ public interface ResponseHandler extends HttpHandler
+ {
+ /**
+ * This is the method called by parser when the HTTP request line is parsed
+ * @param version the http version in use
+ * @param status the response status
+ * @param reason the response reason phrase
+ * @return true if handling parsing should return
+ */
+ public boolean startResponse(HttpVersion version, int status, String reason);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ public interface ComplianceHandler extends HttpHandler
+ {
+ public void onComplianceViolation(HttpCompliance compliance,HttpCompliance required,String reason);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ @SuppressWarnings("serial")
+ private static class IllegalCharacterException extends BadMessageException
+ {
+ private IllegalCharacterException(State state,byte ch,ByteBuffer buffer)
+ {
+ super(400,String.format("Illegal character 0x%X",ch));
+ // Bug #460642 - don't reveal buffers to end user
+ LOG.warn(String.format("Illegal character 0x%X in state=%s for buffer %s",ch,state,BufferUtil.toDetailString(buffer)));
+ }
+ }
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpScheme.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpScheme.java
new file mode 100644
index 0000000000..44ac0c6bc7
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpScheme.java
@@ -0,0 +1,79 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Trie;
+
+/* ------------------------------------------------------------------------------- */
+/**
+ */
+public enum HttpScheme
+{
+ HTTP("http"),
+ HTTPS("https"),
+ WS("ws"),
+ WSS("wss");
+
+ /* ------------------------------------------------------------ */
+ public final static Trie CACHE= new ArrayTrie();
+ static
+ {
+ for (HttpScheme version : HttpScheme.values())
+ CACHE.put(version.asString(),version);
+ }
+
+ private final String _string;
+ private final ByteBuffer _buffer;
+
+ /* ------------------------------------------------------------ */
+ HttpScheme(String s)
+ {
+ _string=s;
+ _buffer=BufferUtil.toBuffer(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteBuffer asByteBuffer()
+ {
+ return _buffer.asReadOnlyBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean is(String s)
+ {
+ return s!=null && _string.equalsIgnoreCase(s);
+ }
+
+ public String asString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return _string;
+ }
+
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpStatus.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpStatus.java
new file mode 100644
index 0000000000..5682889537
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpStatus.java
@@ -0,0 +1,1076 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+/**
+ *
+ * HttpStatusCode enum class, for status codes based on various HTTP RFCs. (see
+ * table below)
+ *
+ *
+ *
+ * HTTP Status Codes Table
+ *
+ * Enum |
+ * Code |
+ * Message |
+ *
+ * RFC 1945 - HTTP/1.0 |
+ *
+ * RFC 7231 - HTTP/1.1 Semantics and Content |
+ *
+ * RFC 7238 - HTTP/1.1 Permanent Redirect |
+ *
+ * RFC 2518 - WEBDAV |
+ *
+ *
+ *
+ * Informational - 1xx |
+ * {@link #isInformational(int)} |
+ *
+ *
+ *
+ * {@link #CONTINUE_100} |
+ * 100 |
+ * Continue |
+ * |
+ *
+ * Sec. 6.2.1 |
+ * |
+ *
+ *
+ * {@link #SWITCHING_PROTOCOLS_101} |
+ * 101 |
+ * Switching Protocols |
+ * |
+ *
+ * Sec. 6.2.2 |
+ * |
+ *
+ *
+ * {@link #PROCESSING_102} |
+ * 102 |
+ * Processing |
+ * |
+ * |
+ *
+ * Sec. 10.1 |
+ *
+ *
+ *
+ * Success - 2xx |
+ * {@link #isSuccess(int)} |
+ *
+ *
+ *
+ * {@link #OK_200} |
+ * 200 |
+ * OK |
+ *
+ * Sec. 9.2 |
+ *
+ * Sec. 6.3.1 |
+ * |
+ *
+ *
+ * {@link #CREATED_201} |
+ * 201 |
+ * Created |
+ *
+ * Sec. 9.2 |
+ *
+ * Sec. 6.3.2 |
+ * |
+ *
+ *
+ * {@link #ACCEPTED_202} |
+ * 202 |
+ * Accepted |
+ *
+ * Sec. 9.2 |
+ *
+ * Sec. 6.3.3 |
+ * |
+ *
+ *
+ * {@link #NON_AUTHORITATIVE_INFORMATION_203} |
+ * 203 |
+ * Non Authoritative Information |
+ * |
+ *
+ * Sec. 6.3.4 |
+ * |
+ *
+ *
+ * {@link #NO_CONTENT_204} |
+ * 204 |
+ * No Content |
+ *
+ * Sec. 9.2 |
+ *
+ * Sec. 6.3.5 |
+ * |
+ *
+ *
+ * {@link #RESET_CONTENT_205} |
+ * 205 |
+ * Reset Content |
+ * |
+ *
+ * Sec. 6.3.6 |
+ * |
+ *
+ *
+ * {@link #PARTIAL_CONTENT_206} |
+ * 206 |
+ * Partial Content |
+ * |
+ *
+ * Sec. 6.3.7 |
+ * |
+ *
+ *
+ * {@link #MULTI_STATUS_207} |
+ * 207 |
+ * Multi-Status |
+ * |
+ * |
+ *
+ * Sec. 10.2 |
+ *
+ *
+ * |
+ * 207 |
+ * Partial Update OK |
+ * |
+ *
+ * draft/01 |
+ * |
+ *
+ *
+ *
+ * Redirection - 3xx |
+ * {@link #isRedirection(int)} |
+ *
+ *
+ *
+ * {@link #MULTIPLE_CHOICES_300} |
+ * 300 |
+ * Multiple Choices |
+ *
+ * Sec. 9.3 |
+ *
+ * Sec. 6.4.1 |
+ * |
+ *
+ *
+ * {@link #MOVED_PERMANENTLY_301} |
+ * 301 |
+ * Moved Permanently |
+ *
+ * Sec. 9.3 |
+ *
+ * Sec. 6.4.2 |
+ * |
+ *
+ *
+ * {@link #MOVED_TEMPORARILY_302} |
+ * 302 |
+ * Moved Temporarily |
+ *
+ * Sec. 9.3 |
+ * (now "302 Found ") |
+ * |
+ *
+ *
+ * {@link #FOUND_302} |
+ * 302 |
+ * Found |
+ * (was "302 Moved Temporarily ") |
+ *
+ * Sec. 6.4.3 |
+ * |
+ *
+ *
+ * {@link #SEE_OTHER_303} |
+ * 303 |
+ * See Other |
+ * |
+ *
+ * Sec. 6.4.4 |
+ * |
+ *
+ *
+ * {@link #NOT_MODIFIED_304} |
+ * 304 |
+ * Not Modified |
+ *
+ * Sec. 9.3 |
+ *
+ * Sec. 6.4.5 |
+ * |
+ *
+ *
+ * {@link #USE_PROXY_305} |
+ * 305 |
+ * Use Proxy |
+ * |
+ *
+ * Sec. 6.4.6 |
+ * |
+ *
+ *
+ * |
+ * 306 |
+ * (Unused) |
+ * |
+ *
+ * Sec. 6.4.7 |
+ * |
+ *
+ *
+ * {@link #TEMPORARY_REDIRECT_307} |
+ * 307 |
+ * Temporary Redirect |
+ * |
+ *
+ * Sec. 6.4.8 |
+ * |
+ *
+ *
+ *
+ * {@link #PERMANENT_REDIRECT_308} |
+ * 307 |
+ * Permanent Redirect |
+ * |
+ *
+ * RFC7238 |
+ * |
+ *
+ *
+ *
+ * Client Error - 4xx |
+ * {@link #isClientError(int)} |
+ *
+ *
+ *
+ * {@link #BAD_REQUEST_400} |
+ * 400 |
+ * Bad Request |
+ *
+ * Sec. 9.4 |
+ *
+ * Sec. 6.5.1 |
+ * |
+ *
+ *
+ * {@link #UNAUTHORIZED_401} |
+ * 401 |
+ * Unauthorized |
+ *
+ * Sec. 9.4 |
+ *
+ * Sec. 6.5.2 |
+ * |
+ *
+ *
+ * {@link #PAYMENT_REQUIRED_402} |
+ * 402 |
+ * Payment Required |
+ *
+ * Sec. 9.4 |
+ *
+ * Sec. 6.5.3 |
+ * |
+ *
+ *
+ * {@link #FORBIDDEN_403} |
+ * 403 |
+ * Forbidden |
+ *
+ * Sec. 9.4 |
+ *
+ * Sec. 6.5.4 |
+ * |
+ *
+ *
+ * {@link #NOT_FOUND_404} |
+ * 404 |
+ * Not Found |
+ *
+ * Sec. 9.4 |
+ *
+ * Sec. 6.5.5 |
+ * |
+ *
+ *
+ * {@link #METHOD_NOT_ALLOWED_405} |
+ * 405 |
+ * Method Not Allowed |
+ * |
+ *
+ * Sec. 6.5.6 |
+ * |
+ *
+ *
+ * {@link #NOT_ACCEPTABLE_406} |
+ * 406 |
+ * Not Acceptable |
+ * |
+ *
+ * Sec. 6.5.7 |
+ * |
+ *
+ *
+ * {@link #PROXY_AUTHENTICATION_REQUIRED_407} |
+ * 407 |
+ * Proxy Authentication Required |
+ * |
+ *
+ * Sec. 6.5.8 |
+ * |
+ *
+ *
+ * {@link #REQUEST_TIMEOUT_408} |
+ * 408 |
+ * Request Timeout |
+ * |
+ *
+ * Sec. 6.5.9 |
+ * |
+ *
+ *
+ * {@link #CONFLICT_409} |
+ * 409 |
+ * Conflict |
+ * |
+ *
+ * Sec. 10.4.10
+ * |
+ * |
+ *
+ *
+ * {@link #GONE_410} |
+ * 410 |
+ * Gone |
+ * |
+ *
+ * Sec. 10.4.11
+ * |
+ * |
+ *
+ *
+ * {@link #LENGTH_REQUIRED_411} |
+ * 411 |
+ * Length Required |
+ * |
+ *
+ * Sec. 10.4.12
+ * |
+ * |
+ *
+ *
+ * {@link #PRECONDITION_FAILED_412} |
+ * 412 |
+ * Precondition Failed |
+ * |
+ *
+ * Sec. 10.4.13
+ * |
+ * |
+ *
+ *
+ * {@link #REQUEST_ENTITY_TOO_LARGE_413} |
+ * 413 |
+ * Request Entity Too Large |
+ * |
+ *
+ * Sec. 10.4.14
+ * |
+ * |
+ *
+ *
+ * {@link #REQUEST_URI_TOO_LONG_414} |
+ * 414 |
+ * Request-URI Too Long |
+ * |
+ *
+ * Sec. 10.4.15
+ * |
+ * |
+ *
+ *
+ * {@link #UNSUPPORTED_MEDIA_TYPE_415} |
+ * 415 |
+ * Unsupported Media Type |
+ * |
+ *
+ * Sec. 10.4.16
+ * |
+ * |
+ *
+ *
+ * {@link #REQUESTED_RANGE_NOT_SATISFIABLE_416} |
+ * 416 |
+ * Requested Range Not Satisfiable |
+ * |
+ *
+ * Sec. 10.4.17
+ * |
+ * |
+ *
+ *
+ * {@link #EXPECTATION_FAILED_417} |
+ * 417 |
+ * Expectation Failed |
+ * |
+ *
+ * Sec. 10.4.18
+ * |
+ * |
+ *
+ *
+ * |
+ * 418 |
+ * Reauthentication Required |
+ * |
+ *
+ * draft/01 |
+ * |
+ *
+ *
+ * |
+ * 418 |
+ * Unprocessable Entity |
+ * |
+ * |
+ *
+ * draft/05 |
+ *
+ *
+ * |
+ * 419 |
+ * Proxy Reauthentication Required |
+ * |
+ *
+ * draft/01 |
+ * |
+ *
+ *
+ * |
+ * 419 |
+ * Insufficient Space on Resource |
+ * |
+ * |
+ *
+ * draft/05 |
+ *
+ *
+ * |
+ * 420 |
+ * Method Failure |
+ * |
+ * |
+ *
+ * draft/05 |
+ *
+ *
+ * |
+ * 421 |
+ * (Unused) |
+ * |
+ * |
+ * |
+ *
+ *
+ * {@link #UNPROCESSABLE_ENTITY_422} |
+ * 422 |
+ * Unprocessable Entity |
+ * |
+ * |
+ *
+ * Sec. 10.3 |
+ *
+ *
+ * {@link #LOCKED_423} |
+ * 423 |
+ * Locked |
+ * |
+ * |
+ *
+ * Sec. 10.4 |
+ *
+ *
+ * {@link #FAILED_DEPENDENCY_424} |
+ * 424 |
+ * Failed Dependency |
+ * |
+ * |
+ *
+ * Sec. 10.5 |
+ *
+ *
+ *
+ * Server Error - 5xx |
+ * {@link #isServerError(int)} |
+ *
+ *
+ *
+ * {@link #INTERNAL_SERVER_ERROR_500} |
+ * 500 |
+ * Internal Server Error |
+ *
+ * Sec. 9.5 |
+ *
+ * Sec. 6.6.1 |
+ * |
+ *
+ *
+ * {@link #NOT_IMPLEMENTED_501} |
+ * 501 |
+ * Not Implemented |
+ *
+ * Sec. 9.5 |
+ *
+ * Sec. 6.6.2 |
+ * |
+ *
+ *
+ * {@link #BAD_GATEWAY_502} |
+ * 502 |
+ * Bad Gateway |
+ *
+ * Sec. 9.5 |
+ *
+ * Sec. 6.6.3 |
+ * |
+ *
+ *
+ * {@link #SERVICE_UNAVAILABLE_503} |
+ * 503 |
+ * Service Unavailable |
+ *
+ * Sec. 9.5 |
+ *
+ * Sec. 6.6.4 |
+ * |
+ *
+ *
+ * {@link #GATEWAY_TIMEOUT_504} |
+ * 504 |
+ * Gateway Timeout |
+ * |
+ *
+ * Sec. 6.6.5 |
+ * |
+ *
+ *
+ * {@link #HTTP_VERSION_NOT_SUPPORTED_505} |
+ * 505 |
+ * HTTP Version Not Supported |
+ * |
+ *
+ * Sec. 6.6.6 |
+ * |
+ *
+ *
+ * |
+ * 506 |
+ * (Unused) |
+ * |
+ * |
+ * |
+ *
+ *
+ * {@link #INSUFFICIENT_STORAGE_507} |
+ * 507 |
+ * Insufficient Storage |
+ * |
+ * |
+ *
+ * Sec. 10.6 |
+ *
+ *
+ *
+ *
+ * @version $Id$
+ */
+public class HttpStatus
+{
+ public final static int CONTINUE_100 = 100;
+ public final static int SWITCHING_PROTOCOLS_101 = 101;
+ public final static int PROCESSING_102 = 102;
+
+ public final static int OK_200 = 200;
+ public final static int CREATED_201 = 201;
+ public final static int ACCEPTED_202 = 202;
+ public final static int NON_AUTHORITATIVE_INFORMATION_203 = 203;
+ public final static int NO_CONTENT_204 = 204;
+ public final static int RESET_CONTENT_205 = 205;
+ public final static int PARTIAL_CONTENT_206 = 206;
+ public final static int MULTI_STATUS_207 = 207;
+
+ public final static int MULTIPLE_CHOICES_300 = 300;
+ public final static int MOVED_PERMANENTLY_301 = 301;
+ public final static int MOVED_TEMPORARILY_302 = 302;
+ public final static int FOUND_302 = 302;
+ public final static int SEE_OTHER_303 = 303;
+ public final static int NOT_MODIFIED_304 = 304;
+ public final static int USE_PROXY_305 = 305;
+ public final static int TEMPORARY_REDIRECT_307 = 307;
+ public final static int PERMANENT_REDIRECT_308 = 308;
+
+ public final static int BAD_REQUEST_400 = 400;
+ public final static int UNAUTHORIZED_401 = 401;
+ public final static int PAYMENT_REQUIRED_402 = 402;
+ public final static int FORBIDDEN_403 = 403;
+ public final static int NOT_FOUND_404 = 404;
+ public final static int METHOD_NOT_ALLOWED_405 = 405;
+ public final static int NOT_ACCEPTABLE_406 = 406;
+ public final static int PROXY_AUTHENTICATION_REQUIRED_407 = 407;
+ public final static int REQUEST_TIMEOUT_408 = 408;
+ public final static int CONFLICT_409 = 409;
+ public final static int GONE_410 = 410;
+ public final static int LENGTH_REQUIRED_411 = 411;
+ public final static int PRECONDITION_FAILED_412 = 412;
+ public final static int REQUEST_ENTITY_TOO_LARGE_413 = 413;
+ public final static int REQUEST_URI_TOO_LONG_414 = 414;
+ public final static int UNSUPPORTED_MEDIA_TYPE_415 = 415;
+ public final static int REQUESTED_RANGE_NOT_SATISFIABLE_416 = 416;
+ public final static int EXPECTATION_FAILED_417 = 417;
+ public final static int MISDIRECTED_REQUEST_421 = 421;
+ public final static int UNPROCESSABLE_ENTITY_422 = 422;
+ public final static int LOCKED_423 = 423;
+ public final static int FAILED_DEPENDENCY_424 = 424;
+ public final static int UPGRADE_REQUIRED_426 = 426;
+
+ public final static int INTERNAL_SERVER_ERROR_500 = 500;
+ public final static int NOT_IMPLEMENTED_501 = 501;
+ public final static int BAD_GATEWAY_502 = 502;
+ public final static int SERVICE_UNAVAILABLE_503 = 503;
+ public final static int GATEWAY_TIMEOUT_504 = 504;
+ public final static int HTTP_VERSION_NOT_SUPPORTED_505 = 505;
+ public final static int INSUFFICIENT_STORAGE_507 = 507;
+
+ // RFC 6585
+ public final static int PRECONDITION_REQUIRED_428 = 428;
+ public final static int TOO_MANY_REQUESTS_429 = 429;
+ public final static int REQUEST_HEADER_FIELDS_TOO_LARGE_431 = 431;
+ public final static int NETWORK_AUTHENTICATION_REQUIRED_511 = 511;
+
+ public static final int MAX_CODE = 511;
+
+ private static final Code[] codeMap = new Code[MAX_CODE+1];
+
+ static
+ {
+ for (Code code : Code.values())
+ {
+ codeMap[code._code] = code;
+ }
+ }
+
+
+ public enum Code
+ {
+ /*
+ * --------------------------------------------------------------------
+ * Informational messages in 1xx series. As defined by ... RFC 1945 -
+ * HTTP/1.0 RFC 7231 - HTTP/1.1 RFC 2518 - WebDAV
+ */
+
+ /** 100 Continue
*/
+ CONTINUE(CONTINUE_100, "Continue"),
+ /** 101 Switching Protocols
*/
+ SWITCHING_PROTOCOLS(SWITCHING_PROTOCOLS_101, "Switching Protocols"),
+ /** 102 Processing
*/
+ PROCESSING(PROCESSING_102, "Processing"),
+
+ /*
+ * --------------------------------------------------------------------
+ * Success messages in 2xx series. As defined by ... RFC 1945 - HTTP/1.0
+ * RFC 7231 - HTTP/1.1 RFC 2518 - WebDAV
+ */
+
+ /** 200 OK
*/
+ OK(OK_200, "OK"),
+ /** 201 Created
*/
+ CREATED(CREATED_201, "Created"),
+ /** 202 Accepted
*/
+ ACCEPTED(ACCEPTED_202, "Accepted"),
+ /** 203 Non Authoritative Information
*/
+ NON_AUTHORITATIVE_INFORMATION(NON_AUTHORITATIVE_INFORMATION_203, "Non Authoritative Information"),
+ /** 204 No Content
*/
+ NO_CONTENT(NO_CONTENT_204, "No Content"),
+ /** 205 Reset Content
*/
+ RESET_CONTENT(RESET_CONTENT_205, "Reset Content"),
+ /** 206 Partial Content
*/
+ PARTIAL_CONTENT(PARTIAL_CONTENT_206, "Partial Content"),
+ /** 207 Multi-Status
*/
+ MULTI_STATUS(MULTI_STATUS_207, "Multi-Status"),
+
+ /*
+ * --------------------------------------------------------------------
+ * Redirection messages in 3xx series. As defined by ... RFC 1945 -
+ * HTTP/1.0 RFC 7231 - HTTP/1.1
+ */
+
+ /** 300 Mutliple Choices
*/
+ MULTIPLE_CHOICES(MULTIPLE_CHOICES_300, "Multiple Choices"),
+ /** 301 Moved Permanently
*/
+ MOVED_PERMANENTLY(MOVED_PERMANENTLY_301, "Moved Permanently"),
+ /** 302 Moved Temporarily
*/
+ MOVED_TEMPORARILY(MOVED_TEMPORARILY_302, "Moved Temporarily"),
+ /** 302 Found
*/
+ FOUND(FOUND_302, "Found"),
+ /** 303 See Other
*/
+ SEE_OTHER(SEE_OTHER_303, "See Other"),
+ /** 304 Not Modified
*/
+ NOT_MODIFIED(NOT_MODIFIED_304, "Not Modified"),
+ /** 305 Use Proxy
*/
+ USE_PROXY(USE_PROXY_305, "Use Proxy"),
+ /** 307 Temporary Redirect
*/
+ TEMPORARY_REDIRECT(TEMPORARY_REDIRECT_307, "Temporary Redirect"),
+ /** 308 Permanent Redirect
*/
+ PERMANET_REDIRECT(PERMANENT_REDIRECT_308, "Permanent Redirect"),
+
+ /*
+ * --------------------------------------------------------------------
+ * Client Error messages in 4xx series. As defined by ... RFC 1945 -
+ * HTTP/1.0 RFC 7231 - HTTP/1.1 RFC 2518 - WebDAV
+ */
+
+ /** 400 Bad Request
*/
+ BAD_REQUEST(BAD_REQUEST_400, "Bad Request"),
+ /** 401 Unauthorized
*/
+ UNAUTHORIZED(UNAUTHORIZED_401, "Unauthorized"),
+ /** 402 Payment Required
*/
+ PAYMENT_REQUIRED(PAYMENT_REQUIRED_402, "Payment Required"),
+ /** 403 Forbidden
*/
+ FORBIDDEN(FORBIDDEN_403, "Forbidden"),
+ /** 404 Not Found
*/
+ NOT_FOUND(NOT_FOUND_404, "Not Found"),
+ /** 405 Method Not Allowed
*/
+ METHOD_NOT_ALLOWED(METHOD_NOT_ALLOWED_405, "Method Not Allowed"),
+ /** 406 Not Acceptable
*/
+ NOT_ACCEPTABLE(NOT_ACCEPTABLE_406, "Not Acceptable"),
+ /** 407 Proxy Authentication Required
*/
+ PROXY_AUTHENTICATION_REQUIRED(PROXY_AUTHENTICATION_REQUIRED_407, "Proxy Authentication Required"),
+ /** 408 Request Timeout
*/
+ REQUEST_TIMEOUT(REQUEST_TIMEOUT_408, "Request Timeout"),
+ /** 409 Conflict
*/
+ CONFLICT(CONFLICT_409, "Conflict"),
+ /** 410 Gone
*/
+ GONE(GONE_410, "Gone"),
+ /** 411 Length Required
*/
+ LENGTH_REQUIRED(LENGTH_REQUIRED_411, "Length Required"),
+ /** 412 Precondition Failed
*/
+ PRECONDITION_FAILED(PRECONDITION_FAILED_412, "Precondition Failed"),
+ /** 413 Request Entity Too Large
*/
+ REQUEST_ENTITY_TOO_LARGE(REQUEST_ENTITY_TOO_LARGE_413, "Request Entity Too Large"),
+ /** 414 Request-URI Too Long
*/
+ REQUEST_URI_TOO_LONG(REQUEST_URI_TOO_LONG_414, "Request-URI Too Long"),
+ /** 415 Unsupported Media Type
*/
+ UNSUPPORTED_MEDIA_TYPE(UNSUPPORTED_MEDIA_TYPE_415, "Unsupported Media Type"),
+ /** 416 Requested Range Not Satisfiable
*/
+ REQUESTED_RANGE_NOT_SATISFIABLE(REQUESTED_RANGE_NOT_SATISFIABLE_416, "Requested Range Not Satisfiable"),
+ /** 417 Expectation Failed
*/
+ EXPECTATION_FAILED(EXPECTATION_FAILED_417, "Expectation Failed"),
+ /** 421 Misdirected Request(RFC7234)y
*/
+ MISDIRECTED_REQUEST(MISDIRECTED_REQUEST_421, "Misdirected Request"),
+ /** 422 Unprocessable Entity
*/
+ UNPROCESSABLE_ENTITY(UNPROCESSABLE_ENTITY_422, "Unprocessable Entity"),
+ /** 423 Locked
*/
+ LOCKED(LOCKED_423, "Locked"),
+ /** 424 Failed Dependency
*/
+ FAILED_DEPENDENCY(FAILED_DEPENDENCY_424, "Failed Dependency"),
+
+ /** 426 Upgrade Required (RFC7231)
*/
+ UPGRADE_REQUIRED(UPGRADE_REQUIRED_426, "Upgrade Required"),
+
+ /** 428 Precondition Required (RFC6585)
*/
+ PRECONDITION_REQUIRED(PRECONDITION_REQUIRED_428, "Precondition Required"),
+ /** 429 Too Many Requests (RFC6585)
*/
+ TOO_MANY_REQUESTS(TOO_MANY_REQUESTS_429, "Too Many Requests"),
+ /** 431 Request Header Fields Too Large (RFC6585)
*/
+ REQUEST_HEADER_FIELDS_TOO_LARGE(REQUEST_HEADER_FIELDS_TOO_LARGE_431, "Request Header Fields Too Large"),
+
+ /*
+ * --------------------------------------------------------------------
+ * Server Error messages in 5xx series. As defined by ... RFC 1945 -
+ * HTTP/1.0 RFC 7231 - HTTP/1.1 RFC 2518 - WebDAV
+ */
+
+ /** 500 Server Error
*/
+ INTERNAL_SERVER_ERROR(INTERNAL_SERVER_ERROR_500, "Server Error"),
+ /** 501 Not Implemented
*/
+ NOT_IMPLEMENTED(NOT_IMPLEMENTED_501, "Not Implemented"),
+ /** 502 Bad Gateway
*/
+ BAD_GATEWAY(BAD_GATEWAY_502, "Bad Gateway"),
+ /** 503 Service Unavailable
*/
+ SERVICE_UNAVAILABLE(SERVICE_UNAVAILABLE_503, "Service Unavailable"),
+ /** 504 Gateway Timeout
*/
+ GATEWAY_TIMEOUT(GATEWAY_TIMEOUT_504, "Gateway Timeout"),
+ /** 505 HTTP Version Not Supported
*/
+ HTTP_VERSION_NOT_SUPPORTED(HTTP_VERSION_NOT_SUPPORTED_505, "HTTP Version Not Supported"),
+ /** 507 Insufficient Storage
*/
+ INSUFFICIENT_STORAGE(INSUFFICIENT_STORAGE_507, "Insufficient Storage"),
+
+ /** 511 Network Authentication Required (RFC6585)
*/
+ NETWORK_AUTHENTICATION_REQUIRED(NETWORK_AUTHENTICATION_REQUIRED_511, "Network Authentication Required"),
+
+ ;
+
+ private final int _code;
+ private final String _message;
+
+ private Code(int code, String message)
+ {
+ this._code = code;
+ _message=message;
+ }
+
+ public int getCode()
+ {
+ return _code;
+ }
+
+ public String getMessage()
+ {
+ return _message;
+ }
+
+
+ public boolean equals(int code)
+ {
+ return (this._code == code);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("[%03d %s]",this._code,this.getMessage());
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Informational
message category as defined in the RFC 1945 - HTTP/1.0,
+ * and RFC 7231 -
+ * HTTP/1.1.
+ *
+ * @return true if within range of codes that belongs to
+ * Informational
messages.
+ */
+ public boolean isInformational()
+ {
+ return HttpStatus.isInformational(this._code);
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Success
message category as defined in the RFC 1945 - HTTP/1.0,
+ * and RFC 7231 -
+ * HTTP/1.1.
+ *
+ * @return true if within range of codes that belongs to
+ * Success
messages.
+ */
+ public boolean isSuccess()
+ {
+ return HttpStatus.isSuccess(this._code);
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Redirection
message category as defined in the RFC 1945 - HTTP/1.0,
+ * and RFC 7231 -
+ * HTTP/1.1.
+ *
+ * @return true if within range of codes that belongs to
+ * Redirection
messages.
+ */
+ public boolean isRedirection()
+ {
+ return HttpStatus.isRedirection(this._code);
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Client Error
message category as defined in the RFC 1945 - HTTP/1.0,
+ * and RFC 7231 -
+ * HTTP/1.1.
+ *
+ * @return true if within range of codes that belongs to
+ * Client Error
messages.
+ */
+ public boolean isClientError()
+ {
+ return HttpStatus.isClientError(this._code);
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Server Error
message category as defined in the RFC 1945 - HTTP/1.0,
+ * and RFC 7231 -
+ * HTTP/1.1.
+ *
+ * @return true if within range of codes that belongs to
+ * Server Error
messages.
+ */
+ public boolean isServerError()
+ {
+ return HttpStatus.isServerError(this._code);
+ }
+ }
+
+
+ /**
+ * Get the HttpStatusCode for a specific code
+ *
+ * @param code
+ * the code to lookup.
+ * @return the {@link HttpStatus} if found, or null if not found.
+ */
+ public static Code getCode(int code)
+ {
+ if (code <= MAX_CODE)
+ {
+ return codeMap[code];
+ }
+ return null;
+ }
+
+ /**
+ * Get the status message for a specific code.
+ *
+ * @param code
+ * the code to look up
+ * @return the specific message, or the code number itself if code
+ * does not match known list.
+ */
+ public static String getMessage(int code)
+ {
+ Code codeEnum = getCode(code);
+ if (codeEnum != null)
+ {
+ return codeEnum.getMessage();
+ }
+ else
+ {
+ return Integer.toString(code);
+ }
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Informational
message category as defined in the RFC 1945 - HTTP/1.0, and RFC 7231 - HTTP/1.1.
+ *
+ * @param code
+ * the code to test.
+ * @return true if within range of codes that belongs to
+ * Informational
messages.
+ */
+ public static boolean isInformational(int code)
+ {
+ return ((100 <= code) && (code <= 199));
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Success
message category as defined in the RFC 1945 - HTTP/1.0, and RFC 7231 - HTTP/1.1.
+ *
+ * @param code
+ * the code to test.
+ * @return true if within range of codes that belongs to
+ * Success
messages.
+ */
+ public static boolean isSuccess(int code)
+ {
+ return ((200 <= code) && (code <= 299));
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Redirection
message category as defined in the RFC 1945 - HTTP/1.0, and RFC 7231 - HTTP/1.1.
+ *
+ * @param code
+ * the code to test.
+ * @return true if within range of codes that belongs to
+ * Redirection
messages.
+ */
+ public static boolean isRedirection(int code)
+ {
+ return ((300 <= code) && (code <= 399));
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Client Error
message category as defined in the RFC 1945 - HTTP/1.0, and RFC 7231 - HTTP/1.1.
+ *
+ * @param code
+ * the code to test.
+ * @return true if within range of codes that belongs to
+ * Client Error
messages.
+ */
+ public static boolean isClientError(int code)
+ {
+ return ((400 <= code) && (code <= 499));
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Server Error
message category as defined in the RFC 1945 - HTTP/1.0, and RFC 7231 - HTTP/1.1.
+ *
+ * @param code
+ * the code to test.
+ * @return true if within range of codes that belongs to
+ * Server Error
messages.
+ */
+ public static boolean isServerError(int code)
+ {
+ return ((500 <= code) && (code <= 599));
+ }
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpTokens.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpTokens.java
new file mode 100644
index 0000000000..66aabdaf72
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpTokens.java
@@ -0,0 +1,38 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+/**
+ * HTTP constants
+ */
+public interface HttpTokens
+{
+ // Terminal symbols.
+ static final byte COLON= (byte)':';
+ static final byte TAB= 0x09;
+ static final byte LINE_FEED= 0x0A;
+ static final byte CARRIAGE_RETURN= 0x0D;
+ static final byte SPACE= 0x20;
+ static final byte[] CRLF = {CARRIAGE_RETURN,LINE_FEED};
+ static final byte SEMI_COLON= (byte)';';
+
+ public enum EndOfContent { UNKNOWN_CONTENT,NO_CONTENT,EOF_CONTENT,CONTENT_LENGTH,CHUNKED_CONTENT }
+
+}
+
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpURI.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpURI.java
new file mode 100644
index 0000000000..87ce90cf42
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpURI.java
@@ -0,0 +1,783 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.UrlEncoded;
+
+
+/* ------------------------------------------------------------ */
+/** Http URI.
+ * Parse a HTTP URI from a string or byte array. Given a URI
+ * http://user@host:port/path/info;param?query#fragment
+ * this class will split it into the following undecoded optional elements:
+ * - {@link #getScheme()} - http:
+ * - {@link #getAuthority()} - //name@host:port
+ * - {@link #getHost()} - host
+ * - {@link #getPort()} - port
+ * - {@link #getPath()} - /path/info
+ * - {@link #getParam()} - param
+ * - {@link #getQuery()} - query
+ * - {@link #getFragment()} - fragment
+ *
+ *
+ * Any parameters will be returned from {@link #getPath()}, but are excluded from the
+ * return value of {@link #getDecodedPath()}. If there are multiple parameters, the
+ * {@link #getParam()} method returns only the last one.
+ */
+public class HttpURI
+{
+ private enum State {
+ START,
+ HOST_OR_PATH,
+ SCHEME_OR_PATH,
+ HOST,
+ IPV6,
+ PORT,
+ PATH,
+ PARAM,
+ QUERY,
+ FRAGMENT,
+ ASTERISK};
+
+ private String _scheme;
+ private String _user;
+ private String _host;
+ private int _port;
+ private String _path;
+ private String _param;
+ private String _query;
+ private String _fragment;
+
+ String _uri;
+ String _decodedPath;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Construct a normalized URI.
+ * Port is not set if it is the default port.
+ * @param scheme the URI scheme
+ * @param host the URI hose
+ * @param port the URI port
+ * @param path the URI path
+ * @param param the URI param
+ * @param query the URI query
+ * @param fragment the URI fragment
+ * @return the normalized URI
+ */
+ public static HttpURI createHttpURI(String scheme, String host, int port, String path, String param, String query, String fragment)
+ {
+ if (port==80 && HttpScheme.HTTP.is(scheme))
+ port=0;
+ if (port==443 && HttpScheme.HTTPS.is(scheme))
+ port=0;
+ return new HttpURI(scheme,host,port,path,param,query,fragment);
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpURI()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpURI(String scheme, String host, int port, String path, String param, String query, String fragment)
+ {
+ _scheme = scheme;
+ _host = host;
+ _port = port;
+ _path = path;
+ _param = param;
+ _query = query;
+ _fragment = fragment;
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpURI(HttpURI uri)
+ {
+ this(uri._scheme,uri._host,uri._port,uri._path,uri._param,uri._query,uri._fragment);
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpURI(String uri)
+ {
+ _port=-1;
+ parse(State.START,uri,0,uri.length());
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpURI(URI uri)
+ {
+ _uri=null;
+
+ _scheme=uri.getScheme();
+ _host=uri.getHost();
+ if (_host==null && uri.getRawSchemeSpecificPart().startsWith("//"))
+ _host="";
+ _port=uri.getPort();
+ _user = uri.getUserInfo();
+ _path=uri.getRawPath();
+
+ _decodedPath = uri.getPath();
+ if (_decodedPath != null)
+ {
+ int p = _decodedPath.lastIndexOf(';');
+ if (p >= 0)
+ _param = _decodedPath.substring(p + 1);
+ }
+ _query=uri.getRawQuery();
+ _fragment=uri.getFragment();
+
+ _decodedPath=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpURI(String scheme, String host, int port, String pathQuery)
+ {
+ _uri=null;
+
+ _scheme=scheme;
+ _host=host;
+ _port=port;
+
+ parse(State.PATH,pathQuery,0,pathQuery.length());
+
+ }
+
+ /* ------------------------------------------------------------ */
+ public void parse(String uri)
+ {
+ clear();
+ _uri=uri;
+ parse(State.START,uri,0,uri.length());
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Parse according to https://tools.ietf.org/html/rfc7230#section-5.3
+ * @param method
+ * @param uri
+ */
+ public void parseRequestTarget(String method,String uri)
+ {
+ clear();
+ _uri=uri;
+
+ if (HttpMethod.CONNECT.is(method))
+ _path=uri;
+ else
+ parse(uri.startsWith("/")?State.PATH:State.START,uri,0,uri.length());
+ }
+
+ /* ------------------------------------------------------------ */
+ @Deprecated
+ public void parseConnect(String uri)
+ {
+ clear();
+ _uri=uri;
+ _path=uri;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void parse(String uri, int offset, int length)
+ {
+ clear();
+ int end=offset+length;
+ _uri=uri.substring(offset,end);
+ parse(State.START,uri,offset,end);
+ }
+
+ /* ------------------------------------------------------------ */
+ private void parse(State state, final String uri, final int offset, final int end)
+ {
+ boolean encoded=false;
+ int mark=offset;
+ int path_mark=0;
+
+ for (int i=offset; i mark)
+ _host=uri.substring(mark,i);
+ mark=i+1;
+ state=State.PORT;
+ break;
+ case '@':
+ if (_user!=null)
+ throw new IllegalArgumentException("Bad authority");
+ _user=uri.substring(mark,i);
+ mark=i+1;
+ break;
+
+ case '[':
+ state=State.IPV6;
+ break;
+ }
+ continue;
+ }
+
+ case IPV6:
+ {
+ switch (c)
+ {
+ case '/':
+ throw new IllegalArgumentException("No closing ']' for ipv6 in " + uri);
+ case ']':
+ c = uri.charAt(++i);
+ _host=uri.substring(mark,i);
+ if (c == ':')
+ {
+ mark=i+1;
+ state=State.PORT;
+ }
+ else
+ {
+ path_mark=mark=i;
+ state=State.PATH;
+ }
+ break;
+ }
+
+ continue;
+ }
+
+ case PORT:
+ {
+ if (c=='@')
+ {
+ if (_user!=null)
+ throw new IllegalArgumentException("Bad authority");
+ // It wasn't a port, but a password!
+ _user=_host+":"+uri.substring(mark,i);
+ mark=i+1;
+ state=State.HOST;
+ }
+ else if (c=='/')
+ {
+ _port=TypeUtil.parseInt(uri,mark,i-mark,10);
+ path_mark=mark=i;
+ state=State.PATH;
+ }
+ continue;
+ }
+
+ case PATH:
+ {
+ switch (c)
+ {
+ case ';':
+ mark=i+1;
+ state=State.PARAM;
+ break;
+ case '?':
+ _path=uri.substring(path_mark,i);
+ mark=i+1;
+ state=State.QUERY;
+ break;
+ case '#':
+ _path=uri.substring(path_mark,i);
+ mark=i+1;
+ state=State.FRAGMENT;
+ break;
+ case '%':
+ encoded=true;
+ break;
+ }
+ continue;
+ }
+
+ case PARAM:
+ {
+ switch (c)
+ {
+ case '?':
+ _path=uri.substring(path_mark,i);
+ _param=uri.substring(mark,i);
+ mark=i+1;
+ state=State.QUERY;
+ break;
+ case '#':
+ _path=uri.substring(path_mark,i);
+ _param=uri.substring(mark,i);
+ mark=i+1;
+ state=State.FRAGMENT;
+ break;
+ case '/':
+ encoded=true;
+ // ignore internal params
+ state=State.PATH;
+ break;
+ case ';':
+ // multiple parameters
+ mark=i+1;
+ break;
+ }
+ continue;
+ }
+
+ case QUERY:
+ {
+ if (c=='#')
+ {
+ _query=uri.substring(mark,i);
+ mark=i+1;
+ state=State.FRAGMENT;
+ }
+ continue;
+ }
+
+ case ASTERISK:
+ {
+ throw new IllegalArgumentException("only '*'");
+ }
+
+ case FRAGMENT:
+ {
+ _fragment=uri.substring(mark,end);
+ i=end;
+ }
+ }
+ }
+
+
+ switch(state)
+ {
+ case START:
+ break;
+ case SCHEME_OR_PATH:
+ _path=uri.substring(mark,end);
+ break;
+
+ case HOST_OR_PATH:
+ _path=uri.substring(mark,end);
+ break;
+
+ case HOST:
+ if(end>mark)
+ _host=uri.substring(mark,end);
+ break;
+
+ case IPV6:
+ throw new IllegalArgumentException("No closing ']' for ipv6 in " + uri);
+
+ case PORT:
+ _port=TypeUtil.parseInt(uri,mark,end-mark,10);
+ break;
+
+ case ASTERISK:
+ break;
+
+ case FRAGMENT:
+ _fragment=uri.substring(mark,end);
+ break;
+
+ case PARAM:
+ _path=uri.substring(path_mark,end);
+ _param=uri.substring(mark,end);
+ break;
+
+ case PATH:
+ _path=uri.substring(path_mark,end);
+ break;
+
+ case QUERY:
+ _query=uri.substring(mark,end);
+ break;
+ }
+
+ if (!encoded)
+ {
+ if (_param==null)
+ _decodedPath=_path;
+ else
+ _decodedPath=_path.substring(0,_path.length()-_param.length()-1);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getScheme()
+ {
+ return _scheme;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getHost()
+ {
+ // Return null for empty host to retain compatibility with java.net.URI
+ if (_host!=null && _host.length()==0)
+ return null;
+ return _host;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getPort()
+ {
+ return _port;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * The parsed Path.
+ *
+ * @return the path as parsed on valid URI. null for invalid URI.
+ */
+ public String getPath()
+ {
+ return _path;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getDecodedPath()
+ {
+ if (_decodedPath==null && _path!=null)
+ _decodedPath=URIUtil.decodePath(_path);
+ return _decodedPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getParam()
+ {
+ return _param;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getQuery()
+ {
+ return _query;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean hasQuery()
+ {
+ return _query!=null && _query.length()>0;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getFragment()
+ {
+ return _fragment;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void decodeQueryTo(MultiMap parameters)
+ {
+ if (_query==_fragment)
+ return;
+ UrlEncoded.decodeUtf8To(_query,parameters);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void decodeQueryTo(MultiMap parameters, String encoding) throws UnsupportedEncodingException
+ {
+ decodeQueryTo(parameters,Charset.forName(encoding));
+ }
+
+ /* ------------------------------------------------------------ */
+ public void decodeQueryTo(MultiMap parameters, Charset encoding) throws UnsupportedEncodingException
+ {
+ if (_query==_fragment)
+ return;
+
+ if (encoding==null || StandardCharsets.UTF_8.equals(encoding))
+ UrlEncoded.decodeUtf8To(_query,parameters);
+ else
+ UrlEncoded.decodeTo(_query,parameters,encoding);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void clear()
+ {
+ _uri=null;
+
+ _scheme=null;
+ _host=null;
+ _port=-1;
+ _path=null;
+ _param=null;
+ _query=null;
+ _fragment=null;
+
+ _decodedPath=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isAbsolute()
+ {
+ return _scheme!=null && _scheme.length()>0;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ if (_uri==null)
+ {
+ StringBuilder out = new StringBuilder();
+
+ if (_scheme!=null)
+ out.append(_scheme).append(':');
+
+ if (_host != null)
+ {
+ out.append("//");
+ if (_user != null)
+ out.append(_user).append('@');
+ out.append(_host);
+ }
+
+ if (_port>0)
+ out.append(':').append(_port);
+
+ if (_path!=null)
+ out.append(_path);
+
+ if (_query!=null)
+ out.append('?').append(_query);
+
+ if (_fragment!=null)
+ out.append('#').append(_fragment);
+
+ if (out.length()>0)
+ _uri=out.toString();
+ else
+ _uri="";
+ }
+ return _uri;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean equals(Object o)
+ {
+ if (o==this)
+ return true;
+ if (!(o instanceof HttpURI))
+ return false;
+ return toString().equals(o.toString());
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setScheme(String scheme)
+ {
+ _scheme=scheme;
+ _uri=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param host the host
+ * @param port the port
+ */
+ public void setAuthority(String host, int port)
+ {
+ _host=host;
+ _port=port;
+ _uri=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param path the path
+ */
+ public void setPath(String path)
+ {
+ _uri=null;
+ _path=path;
+ _decodedPath=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setPathQuery(String path)
+ {
+ _uri=null;
+ _path=null;
+ _decodedPath=null;
+ _param=null;
+ _fragment=null;
+ if (path!=null)
+ parse(State.PATH,path,0,path.length());
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setQuery(String query)
+ {
+ _query=query;
+ _uri=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public URI toURI() throws URISyntaxException
+ {
+ return new URI(_scheme,null,_host,_port,_path,_query==null?null:UrlEncoded.decodeString(_query),_fragment);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getPathQuery()
+ {
+ if (_query==null)
+ return _path;
+ return _path+"?"+_query;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getAuthority()
+ {
+ if (_port>0)
+ return _host+":"+_port;
+ return _host;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getUser()
+ {
+ return _user;
+ }
+
+
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpVersion.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpVersion.java
new file mode 100644
index 0000000000..ddedc27471
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/HttpVersion.java
@@ -0,0 +1,173 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+/* ------------------------------------------------------------------------------- */
+public enum HttpVersion
+{
+ HTTP_0_9("HTTP/0.9",9),
+ HTTP_1_0("HTTP/1.0",10),
+ HTTP_1_1("HTTP/1.1",11),
+ HTTP_2("HTTP/2.0",20);
+
+ /* ------------------------------------------------------------ */
+ public final static Trie CACHE= new ArrayTrie();
+ static
+ {
+ for (HttpVersion version : HttpVersion.values())
+ CACHE.put(version.toString(),version);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Optimised lookup to find a Http Version and whitespace in a byte array.
+ * @param bytes Array containing ISO-8859-1 characters
+ * @param position The first valid index
+ * @param limit The first non valid index
+ * @return A HttpMethod if a match or null if no easy match.
+ */
+ public static HttpVersion lookAheadGet(byte[] bytes, int position, int limit)
+ {
+ int length=limit-position;
+ if (length<9)
+ return null;
+
+ if (bytes[position+4]=='/' && bytes[position+6]=='.' && Character.isWhitespace((char)bytes[position+8]) &&
+ ((bytes[position]=='H' && bytes[position+1]=='T' && bytes[position+2]=='T' && bytes[position+3]=='P') ||
+ (bytes[position]=='h' && bytes[position+1]=='t' && bytes[position+2]=='t' && bytes[position+3]=='p')))
+ {
+ switch(bytes[position+5])
+ {
+ case '1':
+ switch(bytes[position+7])
+ {
+ case '0':
+ return HTTP_1_0;
+ case '1':
+ return HTTP_1_1;
+ }
+ break;
+ case '2':
+ switch(bytes[position+7])
+ {
+ case '0':
+ return HTTP_2;
+ }
+ break;
+ }
+ }
+
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Optimised lookup to find a HTTP Version and trailing white space in a byte array.
+ * @param buffer buffer containing ISO-8859-1 characters
+ * @return A HttpVersion if a match or null if no easy match.
+ */
+ public static HttpVersion lookAheadGet(ByteBuffer buffer)
+ {
+ if (buffer.hasArray())
+ return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit());
+ return null;
+ }
+
+
+ private final String _string;
+ private final byte[] _bytes;
+ private final ByteBuffer _buffer;
+ private final int _version;
+
+ /* ------------------------------------------------------------ */
+ HttpVersion(String s,int version)
+ {
+ _string=s;
+ _bytes=StringUtil.getBytes(s);
+ _buffer=ByteBuffer.wrap(_bytes);
+ _version=version;
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte[] toBytes()
+ {
+ return _bytes;
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteBuffer toBuffer()
+ {
+ return _buffer.asReadOnlyBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getVersion()
+ {
+ return _version;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean is(String s)
+ {
+ return _string.equalsIgnoreCase(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String asString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return _string;
+ }
+
+ /**
+ * Case insensitive fromString() conversion
+ * @param version the String to convert to enum constant
+ * @return the enum constant or null if version unknown
+ */
+ public static HttpVersion fromString(String version)
+ {
+ return CACHE.get(version);
+ }
+
+ /* ------------------------------------------------------------ */
+ public static HttpVersion fromVersion(int version)
+ {
+ switch(version)
+ {
+ case 9: return HttpVersion.HTTP_0_9;
+ case 10: return HttpVersion.HTTP_1_0;
+ case 11: return HttpVersion.HTTP_1_1;
+ case 20: return HttpVersion.HTTP_2;
+ default: throw new IllegalArgumentException();
+ }
+ }
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/MetaData.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/MetaData.java
new file mode 100644
index 0000000000..725870db92
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/MetaData.java
@@ -0,0 +1,298 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.util.Collections;
+import java.util.Iterator;
+
+public class MetaData implements Iterable
+{
+ private HttpVersion _httpVersion;
+ private HttpFields _fields;
+ private long _contentLength;
+
+ public MetaData(HttpVersion version, HttpFields fields)
+ {
+ this(version, fields, Long.MIN_VALUE);
+ }
+
+ public MetaData(HttpVersion version, HttpFields fields, long contentLength)
+ {
+ _httpVersion = version;
+ _fields = fields;
+ _contentLength = contentLength;
+ }
+
+ protected void recycle()
+ {
+ _httpVersion = null;
+ if (_fields != null)
+ _fields.clear();
+ _contentLength = Long.MIN_VALUE;
+ }
+
+ public boolean isRequest()
+ {
+ return false;
+ }
+
+ public boolean isResponse()
+ {
+ return false;
+ }
+
+ /**
+ * @return the HTTP version of this MetaData object
+ */
+ public HttpVersion getVersion()
+ {
+ return _httpVersion;
+ }
+
+ /**
+ * @param httpVersion the HTTP version to set
+ */
+ public void setHttpVersion(HttpVersion httpVersion)
+ {
+ _httpVersion = httpVersion;
+ }
+
+ /**
+ * @return the HTTP fields of this MetaData object
+ */
+ public HttpFields getFields()
+ {
+ return _fields;
+ }
+
+ /**
+ * @return the content length if available, otherwise {@link Long#MIN_VALUE}
+ */
+ public long getContentLength()
+ {
+ if (_contentLength == Long.MIN_VALUE)
+ {
+ if (_fields != null)
+ {
+ HttpField field = _fields.getField(HttpHeader.CONTENT_LENGTH);
+ _contentLength = field == null ? -1 : field.getLongValue();
+ }
+ }
+ return _contentLength;
+ }
+
+ /**
+ * @return an iterator over the HTTP fields
+ * @see #getFields()
+ */
+ public Iterator iterator()
+ {
+ HttpFields fields = getFields();
+ return fields == null ? Collections.emptyIterator() : fields.iterator();
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder out = new StringBuilder();
+ for (HttpField field : this)
+ out.append(field).append(System.lineSeparator());
+ return out.toString();
+ }
+
+ public static class Request extends MetaData
+ {
+ private String _method;
+ private HttpURI _uri;
+
+ public Request(HttpFields fields)
+ {
+ this(null, null, null, fields);
+ }
+
+ public Request(String method, HttpURI uri, HttpVersion version, HttpFields fields)
+ {
+ this(method, uri, version, fields, Long.MIN_VALUE);
+ }
+
+ public Request(String method, HttpURI uri, HttpVersion version, HttpFields fields, long contentLength)
+ {
+ super(version, fields, contentLength);
+ _method = method;
+ _uri = uri;
+ }
+
+ public Request(String method, HttpScheme scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields)
+ {
+ this(method, new HttpURI(scheme == null ? null : scheme.asString(), hostPort.getHost(), hostPort.getPort(), uri), version, fields);
+ }
+
+ public Request(String method, HttpScheme scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields, long contentLength)
+ {
+ this(method, new HttpURI(scheme == null ? null : scheme.asString(), hostPort.getHost(), hostPort.getPort(), uri), version, fields, contentLength);
+ }
+
+ public Request(String method, String scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields, long contentLength)
+ {
+ this(method, new HttpURI(scheme, hostPort.getHost(), hostPort.getPort(), uri), version, fields, contentLength);
+ }
+
+ public Request(Request request)
+ {
+ this(request.getMethod(),new HttpURI(request.getURI()), request.getVersion(), new HttpFields(request.getFields()), request.getContentLength());
+ }
+
+ // TODO MetaData should be immuttable!!!
+ public void recycle()
+ {
+ super.recycle();
+ _method = null;
+ if (_uri != null)
+ _uri.clear();
+ }
+
+ @Override
+ public boolean isRequest()
+ {
+ return true;
+ }
+
+ /**
+ * @return the HTTP method
+ */
+ public String getMethod()
+ {
+ return _method;
+ }
+
+ /**
+ * @param method the HTTP method to set
+ */
+ public void setMethod(String method)
+ {
+ _method = method;
+ }
+
+ /**
+ * @return the HTTP URI
+ */
+ public HttpURI getURI()
+ {
+ return _uri;
+ }
+
+ /**
+ * @return the HTTP URI in string form
+ */
+ public String getURIString()
+ {
+ return _uri == null ? null : _uri.toString();
+ }
+
+ /**
+ * @param uri the HTTP URI to set
+ */
+ public void setURI(HttpURI uri)
+ {
+ _uri = uri;
+ }
+
+ @Override
+ public String toString()
+ {
+ HttpFields fields = getFields();
+ return String.format("%s{u=%s,%s,h=%d}",
+ getMethod(), getURI(), getVersion(), fields == null ? -1 : fields.size());
+ }
+ }
+
+ public static class Response extends MetaData
+ {
+ private int _status;
+ private String _reason;
+
+ public Response()
+ {
+ this(null, 0, null);
+ }
+
+ public Response(HttpVersion version, int status, HttpFields fields)
+ {
+ this(version, status, fields, Long.MIN_VALUE);
+ }
+
+ public Response(HttpVersion version, int status, HttpFields fields, long contentLength)
+ {
+ super(version, fields, contentLength);
+ _status = status;
+ }
+
+ public Response(HttpVersion version, int status, String reason, HttpFields fields, long contentLength)
+ {
+ super(version, fields, contentLength);
+ _reason = reason;
+ _status = status;
+ }
+
+ @Override
+ public boolean isResponse()
+ {
+ return true;
+ }
+
+ /**
+ * @return the HTTP status
+ */
+ public int getStatus()
+ {
+ return _status;
+ }
+
+ /**
+ * @return the HTTP reason
+ */
+ public String getReason()
+ {
+ return _reason;
+ }
+
+ /**
+ * @param status the HTTP status to set
+ */
+ public void setStatus(int status)
+ {
+ _status = status;
+ }
+
+ /**
+ * @param reason the HTTP reason to set
+ */
+ public void setReason(String reason)
+ {
+ _reason = reason;
+ }
+
+ @Override
+ public String toString()
+ {
+ HttpFields fields = getFields();
+ return String.format("%s{s=%d,h=%d}", getVersion(), getStatus(), fields == null ? -1 : fields.size());
+ }
+ }
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/MimeTypes.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/MimeTypes.java
new file mode 100644
index 0000000000..dbdd4b83c6
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/MimeTypes.java
@@ -0,0 +1,497 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.Set;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ *
+ */
+public class MimeTypes
+{
+ public enum Type
+ {
+ FORM_ENCODED("application/x-www-form-urlencoded"),
+ MESSAGE_HTTP("message/http"),
+ MULTIPART_BYTERANGES("multipart/byteranges"),
+
+ TEXT_HTML("text/html"),
+ TEXT_PLAIN("text/plain"),
+ TEXT_XML("text/xml"),
+ TEXT_JSON("text/json",StandardCharsets.UTF_8),
+ APPLICATION_JSON("application/json",StandardCharsets.UTF_8),
+
+ TEXT_HTML_8859_1("text/html;charset=iso-8859-1",TEXT_HTML),
+ TEXT_HTML_UTF_8("text/html;charset=utf-8",TEXT_HTML),
+
+ TEXT_PLAIN_8859_1("text/plain;charset=iso-8859-1",TEXT_PLAIN),
+ TEXT_PLAIN_UTF_8("text/plain;charset=utf-8",TEXT_PLAIN),
+
+ TEXT_XML_8859_1("text/xml;charset=iso-8859-1",TEXT_XML),
+ TEXT_XML_UTF_8("text/xml;charset=utf-8",TEXT_XML),
+
+ TEXT_JSON_8859_1("text/json;charset=iso-8859-1",TEXT_JSON),
+ TEXT_JSON_UTF_8("text/json;charset=utf-8",TEXT_JSON),
+
+ APPLICATION_JSON_8859_1("text/json;charset=iso-8859-1",APPLICATION_JSON),
+ APPLICATION_JSON_UTF_8("text/json;charset=utf-8",APPLICATION_JSON);
+
+
+ /* ------------------------------------------------------------ */
+ private final String _string;
+ private final Type _base;
+ private final ByteBuffer _buffer;
+ private final Charset _charset;
+ private final String _charsetString;
+ private final boolean _assumedCharset;
+ private final HttpField _field;
+
+ /* ------------------------------------------------------------ */
+ Type(String s)
+ {
+ _string=s;
+ _buffer=BufferUtil.toBuffer(s);
+ _base=this;
+ _charset=null;
+ _charsetString=null;
+ _assumedCharset=false;
+ _field=new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,_string);
+ }
+
+ /* ------------------------------------------------------------ */
+ Type(String s,Type base)
+ {
+ _string=s;
+ _buffer=BufferUtil.toBuffer(s);
+ _base=base;
+ int i=s.indexOf(";charset=");
+ _charset=Charset.forName(s.substring(i+9));
+ _charsetString=_charset.toString().toLowerCase(Locale.ENGLISH);
+ _assumedCharset=false;
+ _field=new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,_string);
+ }
+
+ /* ------------------------------------------------------------ */
+ Type(String s,Charset cs)
+ {
+ _string=s;
+ _base=this;
+ _buffer=BufferUtil.toBuffer(s);
+ _charset=cs;
+ _charsetString=_charset==null?null:_charset.toString().toLowerCase(Locale.ENGLISH);
+ _assumedCharset=true;
+ _field=new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,_string);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteBuffer asBuffer()
+ {
+ return _buffer.asReadOnlyBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ public Charset getCharset()
+ {
+ return _charset;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getCharsetString()
+ {
+ return _charsetString;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean is(String s)
+ {
+ return _string.equalsIgnoreCase(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String asString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isCharsetAssumed()
+ {
+ return _assumedCharset;
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpField getContentTypeField()
+ {
+ return _field;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Type getBaseType()
+ {
+ return _base;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private static final Logger LOG = Log.getLogger(MimeTypes.class);
+ public final static Trie CACHE= new ArrayTrie<>(512);
+ private final static Trie TYPES= new ArrayTrie(512);
+ private final static Map __dftMimeMap = new HashMap();
+ private final static Map __encodings = new HashMap();
+
+ static
+ {
+ for (MimeTypes.Type type : MimeTypes.Type.values())
+ {
+ CACHE.put(type.toString(),type);
+ TYPES.put(type.toString(),type.asBuffer());
+
+ int charset=type.toString().indexOf(";charset=");
+ if (charset>0)
+ {
+ String alt=type.toString().replace(";charset=","; charset=");
+ CACHE.put(alt,type);
+ TYPES.put(alt,type.asBuffer());
+ }
+ }
+
+ try
+ {
+ ResourceBundle mime = ResourceBundle.getBundle("org/eclipse/jetty/http/mime");
+ Enumeration i = mime.getKeys();
+ while(i.hasMoreElements())
+ {
+ String ext = i.nextElement();
+ String m = mime.getString(ext);
+ __dftMimeMap.put(StringUtil.asciiToLowerCase(ext),normalizeMimeType(m));
+ }
+ }
+ catch(MissingResourceException e)
+ {
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ }
+
+ try
+ {
+ ResourceBundle encoding = ResourceBundle.getBundle("org/eclipse/jetty/http/encoding");
+ Enumeration i = encoding.getKeys();
+ while(i.hasMoreElements())
+ {
+ String type = i.nextElement();
+ __encodings.put(type,encoding.getString(type));
+ }
+ }
+ catch(MissingResourceException e)
+ {
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ private final Map _mimeMap=new HashMap();
+
+ /* ------------------------------------------------------------ */
+ /** Constructor.
+ */
+ public MimeTypes()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public synchronized Map getMimeMap()
+ {
+ return _mimeMap;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param mimeMap A Map of file extension to mime-type.
+ */
+ public void setMimeMap(Map mimeMap)
+ {
+ _mimeMap.clear();
+ if (mimeMap!=null)
+ {
+ for (Entry ext : mimeMap.entrySet())
+ _mimeMap.put(StringUtil.asciiToLowerCase(ext.getKey()),normalizeMimeType(ext.getValue()));
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the MIME type by filename extension.
+ * @param filename A file name
+ * @return MIME type matching the longest dot extension of the
+ * file name.
+ */
+ public String getMimeByExtension(String filename)
+ {
+ String type=null;
+
+ if (filename!=null)
+ {
+ int i=-1;
+ while(type==null)
+ {
+ i=filename.indexOf(".",i+1);
+
+ if (i<0 || i>=filename.length())
+ break;
+
+ String ext=StringUtil.asciiToLowerCase(filename.substring(i+1));
+ if (_mimeMap!=null)
+ type=_mimeMap.get(ext);
+ if (type==null)
+ type=__dftMimeMap.get(ext);
+ }
+ }
+
+ if (type==null)
+ {
+ if (_mimeMap!=null)
+ type=_mimeMap.get("*");
+ if (type==null)
+ type=__dftMimeMap.get("*");
+ }
+
+ return type;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set a mime mapping
+ * @param extension the extension
+ * @param type the mime type
+ */
+ public void addMimeMapping(String extension,String type)
+ {
+ _mimeMap.put(StringUtil.asciiToLowerCase(extension),normalizeMimeType(type));
+ }
+
+ /* ------------------------------------------------------------ */
+ public static Set getKnownMimeTypes()
+ {
+ return new HashSet<>(__dftMimeMap.values());
+ }
+
+ /* ------------------------------------------------------------ */
+ private static String normalizeMimeType(String type)
+ {
+ MimeTypes.Type t =CACHE.get(type);
+ if (t!=null)
+ return t.asString();
+
+ return StringUtil.asciiToLowerCase(type);
+ }
+
+ /* ------------------------------------------------------------ */
+ public static String getCharsetFromContentType(String value)
+ {
+ if (value==null)
+ return null;
+ int end=value.length();
+ int state=0;
+ int start=0;
+ boolean quote=false;
+ int i=0;
+ for (;i
+ * This mapping implements the path specification recommended
+ * in the 2.2 Servlet API.
+ *
+ *
+ *
+ * Path specifications can be of the following forms:
+ *
+ *
+ * /foo/bar - an exact path specification.
+ * /foo/* - a prefix path specification (must end '/*').
+ * *.ext - a suffix path specification.
+ * / - the default path specification.
+ * "" - the / path specification
+ *
+ *
+ * Matching is performed in the following order
+ *
+ * - Exact match.
+ * - Longest prefix match.
+ * - Longest suffix match.
+ * - default.
+ *
+ *
+ *
+ * Multiple path specifications can be mapped by providing a list of
+ * specifications. By default this class uses characters ":," as path
+ * separators, unless configured differently by calling the static
+ * method @see PathMap#setPathSpecSeparators(String)
+ *
+ * Special characters within paths such as '?� and ';' are not treated specially
+ * as it is assumed they would have been either encoded in the original URL or
+ * stripped from the path.
+ *
+ * This class is not synchronized. If concurrent modifications are
+ * possible then it should be synchronized at a higher level.
+ *
+ * @param the Map.Entry value type
+ * @deprecated replaced with {@link org.eclipse.jetty.http.pathmap.PathMappings} (this class will be removed in Jetty 10)
+ */
+@Deprecated
+public class PathMap extends HashMap
+{
+ /* ------------------------------------------------------------ */
+ private static String __pathSpecSeparators = ":,";
+
+ /* ------------------------------------------------------------ */
+ /** Set the path spec separator.
+ * Multiple path specification may be included in a single string
+ * if they are separated by the characters set in this string.
+ * By default this class uses ":," characters as path separators.
+ * @param s separators
+ */
+ public static void setPathSpecSeparators(String s)
+ {
+ __pathSpecSeparators=s;
+ }
+
+ /* --------------------------------------------------------------- */
+ Trie> _prefixMap=new ArrayTernaryTrie<>(false);
+ Trie> _suffixMap=new ArrayTernaryTrie<>(false);
+ final Map> _exactMap=new HashMap<>();
+
+ List> _defaultSingletonList=null;
+ MappedEntry _prefixDefault=null;
+ MappedEntry _default=null;
+ boolean _nodefault=false;
+
+ /* --------------------------------------------------------------- */
+ public PathMap()
+ {
+ this(11);
+ }
+
+ /* --------------------------------------------------------------- */
+ public PathMap(boolean noDefault)
+ {
+ this(11, noDefault);
+ }
+
+ /* --------------------------------------------------------------- */
+ public PathMap(int capacity)
+ {
+ this(capacity, false);
+ }
+
+ /* --------------------------------------------------------------- */
+ private PathMap(int capacity, boolean noDefault)
+ {
+ super(capacity);
+ _nodefault=noDefault;
+ }
+
+ /* --------------------------------------------------------------- */
+ /**
+ * Construct from dictionary PathMap.
+ * @param dictMap the map representing the dictionary to build this PathMap from
+ */
+ public PathMap(Map dictMap)
+ {
+ putAll(dictMap);
+ }
+
+ /* --------------------------------------------------------------- */
+ /** Add a single path match to the PathMap.
+ * @param pathSpec The path specification, or comma separated list of
+ * path specifications.
+ * @param object The object the path maps to
+ */
+ @Override
+ public O put(String pathSpec, O object)
+ {
+ if ("".equals(pathSpec.trim()))
+ {
+ MappedEntry entry = new MappedEntry<>("",object);
+ entry.setMapped("");
+ _exactMap.put("", entry);
+ return super.put("", object);
+ }
+
+ StringTokenizer tok = new StringTokenizer(pathSpec,__pathSpecSeparators);
+ O old =null;
+
+ while (tok.hasMoreTokens())
+ {
+ String spec=tok.nextToken();
+
+ if (!spec.startsWith("/") && !spec.startsWith("*."))
+ throw new IllegalArgumentException("PathSpec "+spec+". must start with '/' or '*.'");
+
+ old = super.put(spec,object);
+
+ // Make entry that was just created.
+ MappedEntry entry = new MappedEntry<>(spec,object);
+
+ if (entry.getKey().equals(spec))
+ {
+ if (spec.equals("/*"))
+ _prefixDefault=entry;
+ else if (spec.endsWith("/*"))
+ {
+ String mapped=spec.substring(0,spec.length()-2);
+ entry.setMapped(mapped);
+ while (!_prefixMap.put(mapped,entry))
+ _prefixMap=new ArrayTernaryTrie<>((ArrayTernaryTrie>)_prefixMap,1.5);
+ }
+ else if (spec.startsWith("*."))
+ {
+ String suffix=spec.substring(2);
+ while(!_suffixMap.put(suffix,entry))
+ _suffixMap=new ArrayTernaryTrie<>((ArrayTernaryTrie>)_suffixMap,1.5);
+ }
+ else if (spec.equals(URIUtil.SLASH))
+ {
+ if (_nodefault)
+ _exactMap.put(spec,entry);
+ else
+ {
+ _default=entry;
+ _defaultSingletonList=Collections.singletonList(_default);
+ }
+ }
+ else
+ {
+ entry.setMapped(spec);
+ _exactMap.put(spec,entry);
+ }
+ }
+ }
+
+ return old;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get object matched by the path.
+ * @param path the path.
+ * @return Best matched object or null.
+ */
+ public O match(String path)
+ {
+ MappedEntry entry = getMatch(path);
+ if (entry!=null)
+ return entry.getValue();
+ return null;
+ }
+
+
+ /* --------------------------------------------------------------- */
+ /** Get the entry mapped by the best specification.
+ * @param path the path.
+ * @return Map.Entry of the best matched or null.
+ */
+ public MappedEntry getMatch(String path)
+ {
+ if (path==null)
+ return null;
+
+ int l=path.length();
+
+ MappedEntry entry=null;
+
+ //special case
+ if (l == 1 && path.charAt(0)=='/')
+ {
+ entry = _exactMap.get("");
+ if (entry != null)
+ return entry;
+ }
+
+ // try exact match
+ entry=_exactMap.get(path);
+ if (entry!=null)
+ return entry;
+
+ // prefix search
+ int i=l;
+ final Trie> prefix_map=_prefixMap;
+ while(i>=0)
+ {
+ entry=prefix_map.getBest(path,0,i);
+ if (entry==null)
+ break;
+ String key = entry.getKey();
+ if (key.length()-2>=path.length() || path.charAt(key.length()-2)=='/')
+ return entry;
+ i=key.length()-3;
+ }
+
+ // Prefix Default
+ if (_prefixDefault!=null)
+ return _prefixDefault;
+
+ // Extension search
+ i=0;
+ final Trie> suffix_map=_suffixMap;
+ while ((i=path.indexOf('.',i+1))>0)
+ {
+ entry=suffix_map.get(path,i+1,l-i-1);
+ if (entry!=null)
+ return entry;
+ }
+
+ // Default
+ return _default;
+ }
+
+ /* --------------------------------------------------------------- */
+ /** Get all entries matched by the path.
+ * Best match first.
+ * @param path Path to match
+ * @return List of Map.Entry instances key=pathSpec
+ */
+ public List extends Map.Entry> getMatches(String path)
+ {
+ MappedEntry entry;
+ List> entries=new ArrayList<>();
+
+ if (path==null)
+ return entries;
+ if (path.length()==0)
+ return _defaultSingletonList;
+
+ // try exact match
+ entry=_exactMap.get(path);
+ if (entry!=null)
+ entries.add(entry);
+
+ // prefix search
+ int l=path.length();
+ int i=l;
+ final Trie> prefix_map=_prefixMap;
+ while(i>=0)
+ {
+ entry=prefix_map.getBest(path,0,i);
+ if (entry==null)
+ break;
+ String key = entry.getKey();
+ if (key.length()-2>=path.length() || path.charAt(key.length()-2)=='/')
+ entries.add(entry);
+
+ i=key.length()-3;
+ }
+
+ // Prefix Default
+ if (_prefixDefault!=null)
+ entries.add(_prefixDefault);
+
+ // Extension search
+ i=0;
+ final Trie> suffix_map=_suffixMap;
+ while ((i=path.indexOf('.',i+1))>0)
+ {
+ entry=suffix_map.get(path,i+1,l-i-1);
+ if (entry!=null)
+ entries.add(entry);
+ }
+
+ // root match
+ if ("/".equals(path))
+ {
+ entry=_exactMap.get("");
+ if (entry!=null)
+ entries.add(entry);
+ }
+
+ // Default
+ if (_default!=null)
+ entries.add(_default);
+
+ return entries;
+ }
+
+
+ /* --------------------------------------------------------------- */
+ /** Return whether the path matches any entries in the PathMap,
+ * excluding the default entry
+ * @param path Path to match
+ * @return Whether the PathMap contains any entries that match this
+ */
+ public boolean containsMatch(String path)
+ {
+ MappedEntry> match = getMatch(path);
+ return match!=null && !match.equals(_default);
+ }
+
+ /* --------------------------------------------------------------- */
+ @Override
+ public O remove(Object pathSpec)
+ {
+ if (pathSpec!=null)
+ {
+ String spec=(String) pathSpec;
+ if (spec.equals("/*"))
+ _prefixDefault=null;
+ else if (spec.endsWith("/*"))
+ _prefixMap.remove(spec.substring(0,spec.length()-2));
+ else if (spec.startsWith("*."))
+ _suffixMap.remove(spec.substring(2));
+ else if (spec.equals(URIUtil.SLASH))
+ {
+ _default=null;
+ _defaultSingletonList=null;
+ }
+ else
+ _exactMap.remove(spec);
+ }
+ return super.remove(pathSpec);
+ }
+
+ /* --------------------------------------------------------------- */
+ @Override
+ public void clear()
+ {
+ _exactMap.clear();
+ _prefixMap=new ArrayTernaryTrie<>(false);
+ _suffixMap=new ArrayTernaryTrie<>(false);
+ _default=null;
+ _defaultSingletonList=null;
+ _prefixDefault=null;
+ super.clear();
+ }
+
+ /* --------------------------------------------------------------- */
+ /**
+ * @param pathSpec the path spec
+ * @param path the path
+ * @return true if match.
+ */
+ public static boolean match(String pathSpec, String path)
+ {
+ return match(pathSpec, path, false);
+ }
+
+ /* --------------------------------------------------------------- */
+ /**
+ * @param pathSpec the path spec
+ * @param path the path
+ * @param noDefault true to not handle the default path "/" special, false to allow matcher rules to run
+ * @return true if match.
+ */
+ public static boolean match(String pathSpec, String path, boolean noDefault)
+ {
+ if (pathSpec.length()==0)
+ return "/".equals(path);
+
+ char c = pathSpec.charAt(0);
+ if (c=='/')
+ {
+ if (!noDefault && pathSpec.length()==1 || pathSpec.equals(path))
+ return true;
+
+ if(isPathWildcardMatch(pathSpec, path))
+ return true;
+ }
+ else if (c=='*')
+ return path.regionMatches(path.length()-pathSpec.length()+1,
+ pathSpec,1,pathSpec.length()-1);
+ return false;
+ }
+
+ /* --------------------------------------------------------------- */
+ private static boolean isPathWildcardMatch(String pathSpec, String path)
+ {
+ // For a spec of "/foo/*" match "/foo" , "/foo/..." but not "/foobar"
+ int cpl=pathSpec.length()-2;
+ if (pathSpec.endsWith("/*") && path.regionMatches(0,pathSpec,0,cpl))
+ {
+ if (path.length()==cpl || '/'==path.charAt(cpl))
+ return true;
+ }
+ return false;
+ }
+
+
+ /* --------------------------------------------------------------- */
+ /** Return the portion of a path that matches a path spec.
+ * @param pathSpec the path spec
+ * @param path the path
+ * @return null if no match at all.
+ */
+ public static String pathMatch(String pathSpec, String path)
+ {
+ char c = pathSpec.charAt(0);
+
+ if (c=='/')
+ {
+ if (pathSpec.length()==1)
+ return path;
+
+ if (pathSpec.equals(path))
+ return path;
+
+ if (isPathWildcardMatch(pathSpec, path))
+ return path.substring(0,pathSpec.length()-2);
+ }
+ else if (c=='*')
+ {
+ if (path.regionMatches(path.length()-(pathSpec.length()-1),
+ pathSpec,1,pathSpec.length()-1))
+ return path;
+ }
+ return null;
+ }
+
+ /* --------------------------------------------------------------- */
+ /** Return the portion of a path that is after a path spec.
+ * @param pathSpec the path spec
+ * @param path the path
+ * @return The path info string
+ */
+ public static String pathInfo(String pathSpec, String path)
+ {
+ if ("".equals(pathSpec))
+ return path; //servlet 3 spec sec 12.2 will be '/'
+
+ char c = pathSpec.charAt(0);
+
+ if (c=='/')
+ {
+ if (pathSpec.length()==1)
+ return null;
+
+ boolean wildcard = isPathWildcardMatch(pathSpec, path);
+
+ // handle the case where pathSpec uses a wildcard and path info is "/*"
+ if (pathSpec.equals(path) && !wildcard)
+ return null;
+
+ if (wildcard)
+ {
+ if (path.length()==pathSpec.length()-2)
+ return null;
+ return path.substring(pathSpec.length()-2);
+ }
+ }
+ return null;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Relative path.
+ * @param base The base the path is relative to.
+ * @param pathSpec The spec of the path segment to ignore.
+ * @param path the additional path
+ * @return base plus path with pathspec removed
+ */
+ public static String relativePath(String base,
+ String pathSpec,
+ String path )
+ {
+ String info=pathInfo(pathSpec,path);
+ if (info==null)
+ info=path;
+
+ if( info.startsWith( "./"))
+ info = info.substring( 2);
+ if( base.endsWith( URIUtil.SLASH))
+ if( info.startsWith( URIUtil.SLASH))
+ path = base + info.substring(1);
+ else
+ path = base + info;
+ else
+ if( info.startsWith( URIUtil.SLASH))
+ path = base + info;
+ else
+ path = base + URIUtil.SLASH + info;
+ return path;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public static class MappedEntry implements Map.Entry
+ {
+ private final String key;
+ private final O value;
+ private String mapped;
+
+ MappedEntry(String key, O value)
+ {
+ this.key=key;
+ this.value=value;
+ }
+
+ @Override
+ public String getKey()
+ {
+ return key;
+ }
+
+ @Override
+ public O getValue()
+ {
+ return value;
+ }
+
+ @Override
+ public O setValue(O o)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String toString()
+ {
+ return key+"="+value;
+ }
+
+ public String getMapped()
+ {
+ return mapped;
+ }
+
+ void setMapped(String mapped)
+ {
+ this.mapped = mapped;
+ }
+ }
+
+ public static class PathSet extends AbstractSet implements Predicate
+ {
+ private final PathMap _map = new PathMap<>();
+
+ @Override
+ public Iterator iterator()
+ {
+ return _map.keySet().iterator();
+ }
+
+ @Override
+ public int size()
+ {
+ return _map.size();
+ }
+
+ @Override
+ public boolean add(String item)
+ {
+ return _map.put(item,Boolean.TRUE)==null;
+ }
+
+ @Override
+ public boolean remove(Object item)
+ {
+ return _map.remove(item)!=null;
+ }
+
+ @Override
+ public boolean contains(Object o)
+ {
+ return _map.containsKey(o);
+ }
+
+ @Override
+ public boolean test(String s)
+ {
+ return _map.containsMatch(s);
+ }
+
+ public boolean containsMatch(String s)
+ {
+ return _map.containsMatch(s);
+ }
+ }
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/PreEncodedHttpField.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/PreEncodedHttpField.java
new file mode 100644
index 0000000000..6c81a91469
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/PreEncodedHttpField.java
@@ -0,0 +1,94 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ServiceLoader;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** Pre encoded HttpField.
+ * A HttpField that will be cached and used many times can be created as
+ * a {@link PreEncodedHttpField}, which will use the {@link HttpFieldPreEncoder}
+ * instances discovered by the {@link ServiceLoader} to pre-encode the header
+ * for each version of HTTP in use. This will save garbage
+ * and CPU each time the field is encoded into a response.
+ *
+ */
+public class PreEncodedHttpField extends HttpField
+{
+ private final static Logger LOG = Log.getLogger(PreEncodedHttpField.class);
+ private final static HttpFieldPreEncoder[] __encoders;
+
+ static
+ {
+ List encoders = new ArrayList<>();
+ Iterator iter = ServiceLoader.load(HttpFieldPreEncoder.class,PreEncodedHttpField.class.getClassLoader()).iterator();
+ while (iter.hasNext())
+ {
+ try
+ {
+ encoders.add(iter.next());
+ }
+ catch(Error|RuntimeException e)
+ {
+ LOG.debug(e);
+ }
+ }
+ // TODO avoid needing this catch all
+ if (encoders.size()==0)
+ encoders.add(new Http1FieldPreEncoder());
+ LOG.debug("HttpField encoders loaded: {}",encoders);
+ __encoders = encoders.toArray(new HttpFieldPreEncoder[encoders.size()]);
+ }
+
+ private final byte[][] _encodedField=new byte[2][];
+
+ public PreEncodedHttpField(HttpHeader header,String name,String value)
+ {
+ super(header,name, value);
+
+ for (HttpFieldPreEncoder e:__encoders)
+ {
+ _encodedField[e.getHttpVersion()==HttpVersion.HTTP_2?1:0]=e.getEncodedField(header,header.asString(),value);
+ }
+ }
+
+ public PreEncodedHttpField(HttpHeader header,String value)
+ {
+ this(header,header.asString(),value);
+ }
+
+ public PreEncodedHttpField(String name,String value)
+ {
+ this(null,name,value);
+ }
+
+ public void putTo(ByteBuffer bufferInFillMode, HttpVersion version)
+ {
+ bufferInFillMode.put(_encodedField[version==HttpVersion.HTTP_2?1:0]);
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/QuotedCSV.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/QuotedCSV.java
new file mode 100644
index 0000000000..8c9a779c41
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/QuotedCSV.java
@@ -0,0 +1,229 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/* ------------------------------------------------------------ */
+/**
+ * Implements a quoted comma separated list of values
+ * in accordance with RFC7230.
+ * OWS is removed and quoted characters ignored for parsing.
+ * @see "https://tools.ietf.org/html/rfc7230#section-3.2.6"
+ * @see "https://tools.ietf.org/html/rfc7230#section-7"
+ */
+public class QuotedCSV implements Iterable
+{
+ private enum State { VALUE, PARAM_NAME, PARAM_VALUE};
+
+ private final List _values = new ArrayList<>();
+ private final boolean _keepQuotes;
+
+ /* ------------------------------------------------------------ */
+ public QuotedCSV(String... values)
+ {
+ this(true,values);
+ }
+
+ /* ------------------------------------------------------------ */
+ public QuotedCSV(boolean keepQuotes,String... values)
+ {
+ _keepQuotes=keepQuotes;
+ for (String v:values)
+ addValue(v);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void addValue(String value)
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ int l=value.length();
+ State state=State.VALUE;
+ boolean quoted=false;
+ boolean sloshed=false;
+ int nws_length=0;
+ int last_length=0;
+ for (int i=0;i<=l;i++)
+ {
+ char c=i==l?0:value.charAt(i);
+
+ // Handle quoting https://tools.ietf.org/html/rfc7230#section-3.2.6
+ if (quoted && c!=0)
+ {
+ if (sloshed)
+ sloshed=false;
+ else
+ {
+ switch(c)
+ {
+ case '\\':
+ sloshed=true;
+ if (!_keepQuotes)
+ continue;
+ break;
+ case '"':
+ quoted=false;
+ if (!_keepQuotes)
+ continue;
+ break;
+ }
+ }
+
+ buffer.append(c);
+ nws_length=buffer.length();
+ continue;
+ }
+
+ // Handle common cases
+ switch(c)
+ {
+ case ' ':
+ case '\t':
+ if (buffer.length()>last_length) // not leading OWS
+ buffer.append(c);
+ continue;
+
+ case '"':
+ quoted=true;
+ if (_keepQuotes)
+ buffer.append(c);
+ nws_length=buffer.length();
+ continue;
+
+ case ';':
+ buffer.setLength(nws_length); // trim following OWS
+ buffer.append(c);
+ last_length=++nws_length;
+ state=State.PARAM_NAME;
+ continue;
+
+ case ',':
+ case 0:
+ if (nws_length>0)
+ {
+ buffer.setLength(nws_length); // trim following OWS
+ _values.add(buffer.toString());
+ }
+ buffer.setLength(0);
+ last_length=0;
+ nws_length=0;
+ state=State.VALUE;
+ continue;
+
+ default:
+ {
+ switch (state)
+ {
+ case VALUE:
+ {
+ buffer.append(c);
+ nws_length=buffer.length();
+ continue;
+ }
+
+ case PARAM_NAME:
+ {
+ if (c=='=')
+ {
+ buffer.setLength(nws_length); // trim following OWS
+ buffer.append(c);
+ last_length=++nws_length;
+ state=State.PARAM_VALUE;
+ continue;
+ }
+ buffer.append(c);
+ nws_length=buffer.length();
+
+ continue;
+ }
+
+ case PARAM_VALUE:
+ {
+ buffer.append(c);
+ nws_length=buffer.length();
+ continue;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public List getValues()
+ {
+ return _values;
+ }
+
+ @Override
+ public Iterator iterator()
+ {
+ return _values.iterator();
+ }
+
+ public static String unquote(String s)
+ {
+ // handle trivial cases
+ int l=s.length();
+ if (s==null || l==0)
+ return s;
+
+ // Look for any quotes
+ int i=0;
+ for (;i
+{
+ private final static Double ZERO=new Double(0.0);
+ private final static Double ONE=new Double(1.0);
+ private enum State { VALUE, PARAM_NAME, PARAM_VALUE, Q_VALUE};
+
+ private final List _values = new ArrayList<>();
+ private final List _quality = new ArrayList<>();
+ private boolean _sorted = false;
+
+ /* ------------------------------------------------------------ */
+ public QuotedQualityCSV(String... values)
+ {
+ for (String v:values)
+ addValue(v);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public void addValue(String value)
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ int l=value.length();
+ State state=State.VALUE;
+ boolean quoted=false;
+ boolean sloshed=false;
+ int nws_length=0;
+ int last_length=0;
+ Double q=ONE;
+ for (int i=0;i<=l;i++)
+ {
+ char c=i==l?0:value.charAt(i);
+
+ // Handle quoting https://tools.ietf.org/html/rfc7230#section-3.2.6
+ if (quoted && c!=0)
+ {
+ if (sloshed)
+ sloshed=false;
+ else
+ {
+ switch(c)
+ {
+ case '\\':
+ sloshed=true;
+ break;
+ case '"':
+ quoted=false;
+ if (state==State.Q_VALUE)
+ continue;
+ break;
+ }
+ }
+
+ buffer.append(c);
+ nws_length=buffer.length();
+ continue;
+ }
+
+ // Handle common cases
+ switch(c)
+ {
+ case ' ':
+ case '\t':
+ if (buffer.length()>last_length) // not leading OWS
+ buffer.append(c);
+ continue;
+
+ case '"':
+ quoted=true;
+ if (state==State.Q_VALUE)
+ continue;
+
+ buffer.append(c);
+ nws_length=buffer.length();
+ continue;
+
+ case ';':
+ if (state==State.Q_VALUE)
+ {
+ try
+ {
+ q=new Double(buffer.substring(last_length));
+ }
+ catch(Exception e)
+ {
+ q=ZERO;
+ }
+ nws_length=last_length;
+ }
+
+ buffer.setLength(nws_length); // trim following OWS
+ buffer.append(c);
+ last_length=++nws_length;
+ state=State.PARAM_NAME;
+ continue;
+
+ case ',':
+ case 0:
+ if (state==State.Q_VALUE)
+ {
+ try
+ {
+ q=new Double(buffer.substring(last_length));
+ }
+ catch(Exception e)
+ {
+ q=ZERO;
+ }
+ nws_length=last_length;
+ }
+ buffer.setLength(nws_length); // trim following OWS
+ if (q>0.0 && nws_length>0)
+ {
+ _values.add(buffer.toString());
+ _quality.add(q);
+ _sorted=false;
+ }
+ buffer.setLength(0);
+ last_length=0;
+ nws_length=0;
+ q=ONE;
+ state=State.VALUE;
+ continue;
+
+ default:
+ {
+ switch (state)
+ {
+ case VALUE:
+ {
+ buffer.append(c);
+ nws_length=buffer.length();
+ continue;
+ }
+
+ case PARAM_NAME:
+ {
+ if (c=='=')
+ {
+ buffer.setLength(nws_length); // trim following OWS
+ if (nws_length-last_length==1 && Character.toLowerCase(buffer.charAt(last_length))=='q')
+ {
+ buffer.setLength(last_length-1);
+ nws_length=buffer.length();
+ last_length=nws_length;
+ state=State.Q_VALUE;
+ continue;
+ }
+ buffer.append(c);
+ last_length=++nws_length;
+ state=State.PARAM_VALUE;
+ continue;
+ }
+ buffer.append(c);
+ nws_length=buffer.length();
+ continue;
+ }
+
+ case PARAM_VALUE:
+ case Q_VALUE:
+ {
+ buffer.append(c);
+ nws_length=buffer.length();
+ continue;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public List getValues()
+ {
+ if (!_sorted)
+ sort();
+ return _values;
+ }
+
+ @Override
+ public Iterator iterator()
+ {
+ if (!_sorted)
+ sort();
+ return _values.iterator();
+ }
+
+ protected void sort()
+ {
+ _sorted=true;
+
+ Double last = ZERO;
+ int len = Integer.MIN_VALUE;
+
+ for (int i = _values.size(); i-- > 0;)
+ {
+ String v = _values.get(i);
+ Double q = _quality.get(i);
+
+ int compare=last.compareTo(q);
+ if (compare > 0 || (compare==0 && v.length()The HttpContent is used to server static content that is not
+ * cached. So fields and values are only generated as need be an not
+ * kept for reuse
+ */
+public class ResourceHttpContent implements HttpContent
+{
+ final Resource _resource;
+ final String _contentType;
+ final int _maxBuffer;
+ HttpContent _gzip;
+ String _etag;
+
+ /* ------------------------------------------------------------ */
+ public ResourceHttpContent(final Resource resource, final String contentType)
+ {
+ this(resource,contentType,-1,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ResourceHttpContent(final Resource resource, final String contentType, int maxBuffer)
+ {
+ this(resource,contentType,maxBuffer,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ResourceHttpContent(final Resource resource, final String contentType, int maxBuffer, HttpContent gzip)
+ {
+ _resource=resource;
+ _contentType=contentType;
+ _maxBuffer=maxBuffer;
+ _gzip=gzip;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getContentTypeValue()
+ {
+ return _contentType;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public HttpField getContentType()
+ {
+ return _contentType==null?null:new HttpField(HttpHeader.CONTENT_TYPE,_contentType);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public HttpField getContentEncoding()
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getContentEncodingValue()
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getCharacterEncoding()
+ {
+ return _contentType==null?null:MimeTypes.getCharsetFromContentType(_contentType);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Type getMimeType()
+ {
+ return _contentType==null?null:MimeTypes.CACHE.get(MimeTypes.getContentTypeWithoutCharset(_contentType));
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public HttpField getLastModified()
+ {
+ long lm = _resource.lastModified();
+ return lm>=0?new HttpField(HttpHeader.LAST_MODIFIED,DateGenerator.formatDate(lm)):null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getLastModifiedValue()
+ {
+ long lm = _resource.lastModified();
+ return lm>=0?DateGenerator.formatDate(lm):null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public ByteBuffer getDirectBuffer()
+ {
+ if (_resource.length()<=0 || _maxBuffer>0 && _maxBuffer<_resource.length())
+ return null;
+ try
+ {
+ return BufferUtil.toBuffer(_resource,true);
+ }
+ catch(IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public HttpField getETag()
+ {
+ return new HttpField(HttpHeader.ETAG,getETagValue());
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getETagValue()
+ {
+ return _resource.getWeakETag();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public ByteBuffer getIndirectBuffer()
+ {
+ if (_resource.length()<=0 || _maxBuffer>0 && _maxBuffer<_resource.length())
+ return null;
+ try
+ {
+ return BufferUtil.toBuffer(_resource,false);
+ }
+ catch(IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public HttpField getContentLength()
+ {
+ long l=_resource.length();
+ return l==-1?null:new HttpField.LongValueHttpField(HttpHeader.CONTENT_LENGTH,_resource.length());
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public long getContentLengthValue()
+ {
+ return _resource.length();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public InputStream getInputStream() throws IOException
+ {
+ return _resource.getInputStream();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public ReadableByteChannel getReadableByteChannel() throws IOException
+ {
+ return _resource.getReadableByteChannel();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Resource getResource()
+ {
+ return _resource;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void release()
+ {
+ _resource.close();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{r=%s,gz=%b}",this.getClass().getSimpleName(),hashCode(),_resource,_gzip!=null);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public HttpContent getGzipContent()
+ {
+ return _gzip==null?null:new GzipHttpContent(this,_gzip);
+ }
+
+}
\ No newline at end of file
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/encoding.properties b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/encoding.properties
new file mode 100644
index 0000000000..04fe87e751
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/encoding.properties
@@ -0,0 +1,5 @@
+text/html=utf-8
+text/plain=iso-8859-1
+text/xml=utf-8
+text/json=utf-8
+application/xhtml+xml=utf-8
\ No newline at end of file
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/mime.properties b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/mime.properties
new file mode 100644
index 0000000000..0112b88a25
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/mime.properties
@@ -0,0 +1,189 @@
+ai=application/postscript
+aif=audio/x-aiff
+aifc=audio/x-aiff
+aiff=audio/x-aiff
+apk=application/vnd.android.package-archive
+asc=text/plain
+asf=video/x.ms.asf
+asx=video/x.ms.asx
+au=audio/basic
+avi=video/x-msvideo
+bcpio=application/x-bcpio
+bin=application/octet-stream
+bmp=image/bmp
+cab=application/x-cabinet
+cdf=application/x-netcdf
+chm=application/vnd.ms-htmlhelp
+class=application/java-vm
+cpio=application/x-cpio
+cpt=application/mac-compactpro
+crt=application/x-x509-ca-cert
+csh=application/x-csh
+css=text/css
+csv=text/csv
+dcr=application/x-director
+dir=application/x-director
+dll=application/x-msdownload
+dms=application/octet-stream
+doc=application/msword
+dtd=application/xml-dtd
+dvi=application/x-dvi
+dxr=application/x-director
+eps=application/postscript
+etx=text/x-setext
+exe=application/octet-stream
+ez=application/andrew-inset
+gif=image/gif
+gtar=application/x-gtar
+gz=application/gzip
+gzip=application/gzip
+hdf=application/x-hdf
+hqx=application/mac-binhex40
+htc=text/x-component
+htm=text/html
+html=text/html
+ice=x-conference/x-cooltalk
+ico=image/x-icon
+ief=image/ief
+iges=model/iges
+igs=model/iges
+jad=text/vnd.sun.j2me.app-descriptor
+jar=application/java-archive
+java=text/plain
+jnlp=application/x-java-jnlp-file
+jpe=image/jpeg
+jp2=image/jpeg2000
+jpeg=image/jpeg
+jpg=image/jpeg
+js=application/javascript
+json=application/json
+jsp=text/html
+kar=audio/midi
+latex=application/x-latex
+lha=application/octet-stream
+lzh=application/octet-stream
+man=application/x-troff-man
+mathml=application/mathml+xml
+me=application/x-troff-me
+mesh=model/mesh
+mid=audio/midi
+midi=audio/midi
+mif=application/vnd.mif
+mol=chemical/x-mdl-molfile
+mov=video/quicktime
+movie=video/x-sgi-movie
+mp2=audio/mpeg
+mp3=audio/mpeg
+mpe=video/mpeg
+mpeg=video/mpeg
+mpg=video/mpeg
+mpga=audio/mpeg
+ms=application/x-troff-ms
+msh=model/mesh
+msi=application/octet-stream
+nc=application/x-netcdf
+oda=application/oda
+odb=application/vnd.oasis.opendocument.database
+odc=application/vnd.oasis.opendocument.chart
+odf=application/vnd.oasis.opendocument.formula
+odg=application/vnd.oasis.opendocument.graphics
+odi=application/vnd.oasis.opendocument.image
+odm=application/vnd.oasis.opendocument.text-master
+odp=application/vnd.oasis.opendocument.presentation
+ods=application/vnd.oasis.opendocument.spreadsheet
+odt=application/vnd.oasis.opendocument.text
+ogg=application/ogg
+otc=application/vnd.oasis.opendocument.chart-template
+otf=application/vnd.oasis.opendocument.formula-template
+otg=application/vnd.oasis.opendocument.graphics-template
+oth=application/vnd.oasis.opendocument.text-web
+oti=application/vnd.oasis.opendocument.image-template
+otp=application/vnd.oasis.opendocument.presentation-template
+ots=application/vnd.oasis.opendocument.spreadsheet-template
+ott=application/vnd.oasis.opendocument.text-template
+pbm=image/x-portable-bitmap
+pdb=chemical/x-pdb
+pdf=application/pdf
+pgm=image/x-portable-graymap
+pgn=application/x-chess-pgn
+png=image/png
+pnm=image/x-portable-anymap
+ppm=image/x-portable-pixmap
+pps=application/vnd.ms-powerpoint
+ppt=application/vnd.ms-powerpoint
+ps=application/postscript
+qml=text/x-qml
+qt=video/quicktime
+ra=audio/x-pn-realaudio
+rar=application/x-rar-compressed
+ram=audio/x-pn-realaudio
+ras=image/x-cmu-raster
+rdf=application/rdf+xml
+rgb=image/x-rgb
+rm=audio/x-pn-realaudio
+roff=application/x-troff
+rpm=application/x-rpm
+rtf=application/rtf
+rtx=text/richtext
+rv=video/vnd.rn-realvideo
+ser=application/java-serialized-object
+sgm=text/sgml
+sgml=text/sgml
+sh=application/x-sh
+shar=application/x-shar
+silo=model/mesh
+sit=application/x-stuffit
+skd=application/x-koan
+skm=application/x-koan
+skp=application/x-koan
+skt=application/x-koan
+smi=application/smil
+smil=application/smil
+snd=audio/basic
+spl=application/x-futuresplash
+src=application/x-wais-source
+sv4cpio=application/x-sv4cpio
+sv4crc=application/x-sv4crc
+svg=image/svg+xml
+svgz=image/svg+xml
+swf=application/x-shockwave-flash
+t=application/x-troff
+tar=application/x-tar
+tar.gz=application/x-gtar
+tcl=application/x-tcl
+tex=application/x-tex
+texi=application/x-texinfo
+texinfo=application/x-texinfo
+tgz=application/x-gtar
+tif=image/tiff
+tiff=image/tiff
+tr=application/x-troff
+tsv=text/tab-separated-values
+txt=text/plain
+ustar=application/x-ustar
+vcd=application/x-cdlink
+vrml=model/vrml
+vxml=application/voicexml+xml
+wav=audio/x-wav
+wbmp=image/vnd.wap.wbmp
+wml=text/vnd.wap.wml
+wmlc=application/vnd.wap.wmlc
+wmls=text/vnd.wap.wmlscript
+wmlsc=application/vnd.wap.wmlscriptc
+wrl=model/vrml
+wtls-ca-certificate=application/vnd.wap.wtls-ca-certificate
+xbm=image/x-xbitmap
+xcf=image/xcf
+xht=application/xhtml+xml
+xhtml=application/xhtml+xml
+xls=application/vnd.ms-excel
+xml=application/xml
+xpm=image/x-xpixmap
+xsd=application/xml
+xsl=application/xml
+xslt=application/xslt+xml
+xul=application/vnd.mozilla.xul+xml
+xwd=image/x-xwindowdump
+xyz=chemical/x-xyz
+z=application/compress
+zip=application/zip
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/package-info.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/package-info.java
new file mode 100644
index 0000000000..aeca4a3f2a
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/package-info.java
@@ -0,0 +1,23 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+/**
+ * Jetty Http : Tools for Http processing
+ */
+package org.eclipse.jetty.http;
+
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/MappedResource.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/MappedResource.java
new file mode 100644
index 0000000000..155dce8b8b
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/MappedResource.java
@@ -0,0 +1,101 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http.pathmap;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+@ManagedObject("Mapped Resource")
+public class MappedResource implements Comparable>
+{
+ private final PathSpec pathSpec;
+ private final E resource;
+
+ public MappedResource(PathSpec pathSpec, E resource)
+ {
+ this.pathSpec = pathSpec;
+ this.resource = resource;
+ }
+
+ /**
+ * Comparison is based solely on the pathSpec
+ */
+ @Override
+ public int compareTo(MappedResource other)
+ {
+ return this.pathSpec.compareTo(other.pathSpec);
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ if (obj == null)
+ {
+ return false;
+ }
+ if (getClass() != obj.getClass())
+ {
+ return false;
+ }
+ MappedResource> other = (MappedResource>)obj;
+ if (pathSpec == null)
+ {
+ if (other.pathSpec != null)
+ {
+ return false;
+ }
+ }
+ else if (!pathSpec.equals(other.pathSpec))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ @ManagedAttribute(value = "path spec", readonly = true)
+ public PathSpec getPathSpec()
+ {
+ return pathSpec;
+ }
+
+ @ManagedAttribute(value = "resource", readonly = true)
+ public E getResource()
+ {
+ return resource;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = (prime * result) + ((pathSpec == null) ? 0 : pathSpec.hashCode());
+ return result;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("MappedResource[pathSpec=%s,resource=%s]",pathSpec,resource);
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/PathMappings.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/PathMappings.java
new file mode 100644
index 0000000000..d1c36cfbd6
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/PathMappings.java
@@ -0,0 +1,159 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http.pathmap;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Path Mappings of PathSpec to Resource.
+ *
+ * Sorted into search order upon entry into the Set
+ *
+ * @param the type of mapping endpoint
+ */
+@ManagedObject("Path Mappings")
+public class PathMappings implements Iterable>, Dumpable
+{
+ private static final Logger LOG = Log.getLogger(PathMappings.class);
+ private List> mappings = new ArrayList>();
+ private MappedResource defaultResource = null;
+ private MappedResource rootResource = null;
+
+ @Override
+ public String dump()
+ {
+ return ContainerLifeCycle.dump(this);
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ ContainerLifeCycle.dump(out,indent,mappings);
+ }
+
+ @ManagedAttribute(value = "mappings", readonly = true)
+ public List> getMappings()
+ {
+ return mappings;
+ }
+
+ public void reset()
+ {
+ mappings.clear();
+ }
+
+ /**
+ * Return a list of MappedResource matches for the specified path.
+ *
+ * @param path the path to return matches on
+ * @return the list of mapped resource the path matches on
+ */
+ public List> getMatches(String path)
+ {
+ boolean matchRoot = "/".equals(path);
+
+ List> ret = new ArrayList<>();
+ int len = mappings.size();
+ for (int i = 0; i < len; i++)
+ {
+ MappedResource mr = mappings.get(i);
+
+ switch (mr.getPathSpec().group)
+ {
+ case ROOT:
+ if (matchRoot)
+ ret.add(mr);
+ break;
+ case DEFAULT:
+ if (matchRoot || mr.getPathSpec().matches(path))
+ ret.add(mr);
+ break;
+ default:
+ if (mr.getPathSpec().matches(path))
+ ret.add(mr);
+ break;
+ }
+ }
+ return ret;
+ }
+
+ public MappedResource getMatch(String path)
+ {
+ if (path.equals("/") && rootResource != null)
+ {
+ return rootResource;
+ }
+
+ int len = mappings.size();
+ for (int i = 0; i < len; i++)
+ {
+ MappedResource mr = mappings.get(i);
+ if (mr.getPathSpec().matches(path))
+ {
+ return mr;
+ }
+ }
+ return defaultResource;
+ }
+
+ @Override
+ public Iterator> iterator()
+ {
+ return mappings.iterator();
+ }
+
+ @SuppressWarnings("incomplete-switch")
+ public void put(PathSpec pathSpec, E resource)
+ {
+ MappedResource entry = new MappedResource<>(pathSpec,resource);
+ switch (pathSpec.group)
+ {
+ case DEFAULT:
+ defaultResource = entry;
+ break;
+ case ROOT:
+ rootResource = entry;
+ break;
+ }
+
+ // TODO: add warning when replacing an existing pathspec?
+
+ mappings.add(entry);
+ if (LOG.isDebugEnabled())
+ LOG.debug("Added {} to {}",entry,this);
+ Collections.sort(mappings);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[size=%d]",this.getClass().getSimpleName(),mappings.size());
+ }
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/PathSpec.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/PathSpec.java
new file mode 100644
index 0000000000..a2b8ea56cf
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/PathSpec.java
@@ -0,0 +1,167 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http.pathmap;
+
+/**
+ * The base PathSpec, what all other path specs are based on
+ */
+public abstract class PathSpec implements Comparable
+{
+ protected String pathSpec;
+ protected PathSpecGroup group;
+ protected int pathDepth;
+ protected int specLength;
+
+ @Override
+ public int compareTo(PathSpec other)
+ {
+ // Grouping (increasing)
+ int diff = this.group.ordinal() - other.group.ordinal();
+ if (diff != 0)
+ {
+ return diff;
+ }
+
+ // Spec Length (decreasing)
+ diff = other.specLength - this.specLength;
+ if (diff != 0)
+ {
+ return diff;
+ }
+
+ // Path Spec Name (alphabetical)
+ return this.pathSpec.compareTo(other.pathSpec);
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ if (obj == null)
+ {
+ return false;
+ }
+ if (getClass() != obj.getClass())
+ {
+ return false;
+ }
+ PathSpec other = (PathSpec)obj;
+ if (pathSpec == null)
+ {
+ if (other.pathSpec != null)
+ {
+ return false;
+ }
+ }
+ else if (!pathSpec.equals(other.pathSpec))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public PathSpecGroup getGroup()
+ {
+ return group;
+ }
+
+ /**
+ * Get the number of path elements that this path spec declares.
+ *
+ * This is used to determine longest match logic.
+ *
+ * @return the depth of the path segments that this spec declares
+ */
+ public int getPathDepth()
+ {
+ return pathDepth;
+ }
+
+ /**
+ * Return the portion of the path that is after the path spec.
+ *
+ * @param path
+ * the path to match against
+ * @return the path info portion of the string
+ */
+ public abstract String getPathInfo(String path);
+
+ /**
+ * Return the portion of the path that matches a path spec.
+ *
+ * @param path
+ * the path to match against
+ * @return the match, or null if no match at all
+ */
+ public abstract String getPathMatch(String path);
+
+ /**
+ * The as-provided path spec.
+ *
+ * @return the as-provided path spec
+ */
+ public String getDeclaration()
+ {
+ return pathSpec;
+ }
+
+ /**
+ * Get the relative path.
+ *
+ * @param base
+ * the base the path is relative to
+ * @param path
+ * the additional path
+ * @return the base plus path with pathSpec portion removed
+ */
+ public abstract String getRelativePath(String base, String path);
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = (prime * result) + ((pathSpec == null)?0:pathSpec.hashCode());
+ return result;
+ }
+
+ /**
+ * Test to see if the provided path matches this path spec
+ *
+ * @param path
+ * the path to test
+ * @return true if the path matches this path spec, false otherwise
+ */
+ public abstract boolean matches(String path);
+
+ @Override
+ public String toString()
+ {
+ StringBuilder str = new StringBuilder();
+ str.append(this.getClass().getSimpleName()).append("[\"");
+ str.append(pathSpec);
+ str.append("\",pathDepth=").append(pathDepth);
+ str.append(",group=").append(group);
+ str.append("]");
+ return str.toString();
+ }
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/PathSpecGroup.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/PathSpecGroup.java
new file mode 100644
index 0000000000..f9d96ced22
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/PathSpecGroup.java
@@ -0,0 +1,101 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http.pathmap;
+
+/**
+ * Types of path spec groups.
+ *
+ * This is used to facilitate proper pathspec search order.
+ *
+ * Search Order:
+ *
+ * - {@link PathSpecGroup#ordinal()} [increasing]
+ * - {@link PathSpec#specLength} [decreasing]
+ * - {@link PathSpec#pathSpec} [natural sort order]
+ *
+ */
+public enum PathSpecGroup
+{
+ // NOTE: Order of enums determines order of Groups.
+
+ /**
+ * For exactly defined path specs, no glob.
+ */
+ EXACT,
+ /**
+ * For path specs that have a hardcoded prefix and suffix with wildcard glob in the middle.
+ *
+ *
+ * "^/downloads/[^/]*.zip$" - regex spec
+ * "/a/{var}/c" - uri-template spec
+ *
+ *
+ * Note: there is no known servlet spec variant of this kind of path spec
+ */
+ MIDDLE_GLOB,
+ /**
+ * For path specs that have a hardcoded prefix and a trailing wildcard glob.
+ *
+ *
+ *
+ * "/downloads/*" - servlet spec
+ * "/api/*" - servlet spec
+ * "^/rest/.*$" - regex spec
+ * "/bookings/{guest-id}" - uri-template spec
+ * "/rewards/{vip-level}" - uri-template spec
+ *
+ */
+ PREFIX_GLOB,
+ /**
+ * For path specs that have a wildcard glob with a hardcoded suffix
+ *
+ *
+ * "*.do" - servlet spec
+ * "*.css" - servlet spec
+ * "^.*\.zip$" - regex spec
+ *
+ *
+ * Note: there is no known uri-template spec variant of this kind of path spec
+ */
+ SUFFIX_GLOB,
+ /**
+ * The root spec for accessing the Root behavior.
+ *
+ *
+ * "" - servlet spec (Root Servlet)
+ * null - servlet spec (Root Servlet)
+ *
+ *
+ * Note: there is no known uri-template spec variant of this kind of path spec
+ */
+ ROOT,
+ /**
+ * The default spec for accessing the Default path behavior.
+ *
+ *
+ * "/" - servlet spec (Default Servlet)
+ * "/" - uri-template spec (Root Context)
+ * "^/$" - regex spec (Root Context)
+ *
+ *
+ * Per Servlet Spec, pathInfo is always null for these specs.
+ * If nothing above matches, then default will match.
+ */
+ DEFAULT,
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/PathSpecSet.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/PathSpecSet.java
new file mode 100644
index 0000000000..c1a3235472
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/PathSpecSet.java
@@ -0,0 +1,222 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http.pathmap;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.function.Predicate;
+
+/**
+ * A Set of PathSpec strings.
+ *
+ * Used by {@link org.eclipse.jetty.util.IncludeExclude} logic
+ */
+public class PathSpecSet implements Set, Predicate
+{
+ private final Set specs = new TreeSet<>();
+
+ @Override
+ public boolean test(String s)
+ {
+ for (PathSpec spec : specs)
+ {
+ if (spec.matches(s))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isEmpty()
+ {
+ return specs.isEmpty();
+ }
+
+ @Override
+ public Iterator iterator()
+ {
+ return new Iterator()
+ {
+ private Iterator iter = specs.iterator();
+
+ @Override
+ public boolean hasNext()
+ {
+ return iter.hasNext();
+ }
+
+ @Override
+ public String next()
+ {
+ PathSpec spec = iter.next();
+ if (spec == null)
+ {
+ return null;
+ }
+ return spec.getDeclaration();
+ }
+
+ @Override
+ public void remove()
+ {
+ throw new UnsupportedOperationException("Remove not supported by this Iterator");
+ }
+ };
+ }
+
+ @Override
+ public int size()
+ {
+ return specs.size();
+ }
+
+ @Override
+ public boolean contains(Object o)
+ {
+ if (o instanceof PathSpec)
+ {
+ return specs.contains(o);
+ }
+ if (o instanceof String)
+ {
+ return specs.contains(toPathSpec((String)o));
+ }
+ return false;
+ }
+
+ private PathSpec asPathSpec(Object o)
+ {
+ if (o == null)
+ {
+ return null;
+ }
+ if (o instanceof PathSpec)
+ {
+ return (PathSpec)o;
+ }
+ if (o instanceof String)
+ {
+ return toPathSpec((String)o);
+ }
+ return toPathSpec(o.toString());
+ }
+
+ private PathSpec toPathSpec(String rawSpec)
+ {
+ if ((rawSpec == null) || (rawSpec.length() < 1))
+ {
+ throw new RuntimeException("Path Spec String must start with '^', '/', or '*.': got [" + rawSpec + "]");
+ }
+ if (rawSpec.charAt(0) == '^')
+ {
+ return new RegexPathSpec(rawSpec);
+ }
+ else
+ {
+ return new ServletPathSpec(rawSpec);
+ }
+ }
+
+ @Override
+ public Object[] toArray()
+ {
+ return toArray(new String[specs.size()]);
+ }
+
+ @Override
+ public T[] toArray(T[] a)
+ {
+ int i = 0;
+ for (PathSpec spec : specs)
+ {
+ a[i++] = (T)spec.getDeclaration();
+ }
+ return a;
+ }
+
+ @Override
+ public boolean add(String e)
+ {
+ return specs.add(toPathSpec(e));
+ }
+
+ @Override
+ public boolean remove(Object o)
+ {
+ return specs.remove(asPathSpec(o));
+ }
+
+ @Override
+ public boolean containsAll(Collection> coll)
+ {
+ for (Object o : coll)
+ {
+ if (!specs.contains(asPathSpec(o)))
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean addAll(Collection extends String> coll)
+ {
+ boolean ret = false;
+
+ for (String s : coll)
+ {
+ ret |= add(s);
+ }
+
+ return ret;
+ }
+
+ @Override
+ public boolean retainAll(Collection> coll)
+ {
+ List collSpecs = new ArrayList<>();
+ for (Object o : coll)
+ {
+ collSpecs.add(asPathSpec(o));
+ }
+ return specs.retainAll(collSpecs);
+ }
+
+ @Override
+ public boolean removeAll(Collection> coll)
+ {
+ List collSpecs = new ArrayList<>();
+ for (Object o : coll)
+ {
+ collSpecs.add(asPathSpec(o));
+ }
+ return specs.removeAll(collSpecs);
+ }
+
+ @Override
+ public void clear()
+ {
+ specs.clear();
+ }
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/RegexPathSpec.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/RegexPathSpec.java
new file mode 100644
index 0000000000..0d83f3b000
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/RegexPathSpec.java
@@ -0,0 +1,176 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http.pathmap;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class RegexPathSpec extends PathSpec
+{
+ protected Pattern pattern;
+
+ protected RegexPathSpec()
+ {
+ super();
+ }
+
+ public RegexPathSpec(String regex)
+ {
+ super.pathSpec = regex;
+ boolean inGrouping = false;
+ this.pathDepth = 0;
+ this.specLength = pathSpec.length();
+ // build up a simple signature we can use to identify the grouping
+ StringBuilder signature = new StringBuilder();
+ for (char c : pathSpec.toCharArray())
+ {
+ switch (c)
+ {
+ case '[':
+ inGrouping = true;
+ break;
+ case ']':
+ inGrouping = false;
+ signature.append('g'); // glob
+ break;
+ case '*':
+ signature.append('g'); // glob
+ break;
+ case '/':
+ if (!inGrouping)
+ {
+ this.pathDepth++;
+ }
+ break;
+ default:
+ if (!inGrouping)
+ {
+ if (Character.isLetterOrDigit(c))
+ {
+ signature.append('l'); // literal (exact)
+ }
+ }
+ break;
+ }
+ }
+ this.pattern = Pattern.compile(pathSpec);
+
+ // Figure out the grouping based on the signature
+ String sig = signature.toString();
+
+ if (Pattern.matches("^l*$",sig))
+ {
+ this.group = PathSpecGroup.EXACT;
+ }
+ else if (Pattern.matches("^l*g+",sig))
+ {
+ this.group = PathSpecGroup.PREFIX_GLOB;
+ }
+ else if (Pattern.matches("^g+l+$",sig))
+ {
+ this.group = PathSpecGroup.SUFFIX_GLOB;
+ }
+ else
+ {
+ this.group = PathSpecGroup.MIDDLE_GLOB;
+ }
+ }
+
+ public Matcher getMatcher(String path)
+ {
+ return this.pattern.matcher(path);
+ }
+
+ @Override
+ public String getPathInfo(String path)
+ {
+ // Path Info only valid for PREFIX_GLOB types
+ if (group == PathSpecGroup.PREFIX_GLOB)
+ {
+ Matcher matcher = getMatcher(path);
+ if (matcher.matches())
+ {
+ if (matcher.groupCount() >= 1)
+ {
+ String pathInfo = matcher.group(1);
+ if ("".equals(pathInfo))
+ {
+ return "/";
+ }
+ else
+ {
+ return pathInfo;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String getPathMatch(String path)
+ {
+ Matcher matcher = getMatcher(path);
+ if (matcher.matches())
+ {
+ if (matcher.groupCount() >= 1)
+ {
+ int idx = matcher.start(1);
+ if (idx > 0)
+ {
+ if (path.charAt(idx - 1) == '/')
+ {
+ idx--;
+ }
+ return path.substring(0,idx);
+ }
+ }
+ return path;
+ }
+ return null;
+ }
+
+ public Pattern getPattern()
+ {
+ return this.pattern;
+ }
+
+ @Override
+ public String getRelativePath(String base, String path)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public boolean matches(final String path)
+ {
+ int idx = path.indexOf('?');
+ if (idx >= 0)
+ {
+ // match only non-query part
+ return getMatcher(path.substring(0,idx)).matches();
+ }
+ else
+ {
+ // match entire path
+ return getMatcher(path).matches();
+ }
+ }
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/ServletPathSpec.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/ServletPathSpec.java
new file mode 100644
index 0000000000..4563305659
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/ServletPathSpec.java
@@ -0,0 +1,261 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http.pathmap;
+
+import org.eclipse.jetty.util.URIUtil;
+
+public class ServletPathSpec extends PathSpec
+{
+ public ServletPathSpec(String servletPathSpec)
+ {
+ super();
+ assertValidServletPathSpec(servletPathSpec);
+
+ // The Root Path Spec
+ if ((servletPathSpec == null) || (servletPathSpec.length() == 0))
+ {
+ super.pathSpec = "";
+ super.pathDepth = -1; // force this to be at the end of the sort order
+ this.specLength = 1;
+ this.group = PathSpecGroup.ROOT;
+ return;
+ }
+
+ // The Default Path Spec
+ if("/".equals(servletPathSpec))
+ {
+ super.pathSpec = "/";
+ super.pathDepth = -1; // force this to be at the end of the sort order
+ this.specLength = 1;
+ this.group = PathSpecGroup.DEFAULT;
+ return;
+ }
+
+ this.specLength = servletPathSpec.length();
+ super.pathDepth = 0;
+ char lastChar = servletPathSpec.charAt(specLength - 1);
+ // prefix based
+ if ((servletPathSpec.charAt(0) == '/') && (specLength > 1) && (lastChar == '*'))
+ {
+ this.group = PathSpecGroup.PREFIX_GLOB;
+ }
+ // suffix based
+ else if (servletPathSpec.charAt(0) == '*')
+ {
+ this.group = PathSpecGroup.SUFFIX_GLOB;
+ }
+ else
+ {
+ this.group = PathSpecGroup.EXACT;
+ }
+
+ for (int i = 0; i < specLength; i++)
+ {
+ int cp = servletPathSpec.codePointAt(i);
+ if (cp < 128)
+ {
+ char c = (char)cp;
+ switch (c)
+ {
+ case '/':
+ super.pathDepth++;
+ break;
+ }
+ }
+ }
+
+ super.pathSpec = servletPathSpec;
+ }
+
+ private void assertValidServletPathSpec(String servletPathSpec)
+ {
+ if ((servletPathSpec == null) || servletPathSpec.equals(""))
+ {
+ return; // empty path spec
+ }
+
+ int len = servletPathSpec.length();
+ // path spec must either start with '/' or '*.'
+ if (servletPathSpec.charAt(0) == '/')
+ {
+ // Prefix Based
+ if (len == 1)
+ {
+ return; // simple '/' path spec
+ }
+ int idx = servletPathSpec.indexOf('*');
+ if (idx < 0)
+ {
+ return; // no hit on glob '*'
+ }
+ // only allowed to have '*' at the end of the path spec
+ if (idx != (len - 1))
+ {
+ throw new IllegalArgumentException("Servlet Spec 12.2 violation: glob '*' can only exist at end of prefix based matches: bad spec \""+ servletPathSpec +"\"");
+ }
+ }
+ else if (servletPathSpec.startsWith("*."))
+ {
+ // Suffix Based
+ int idx = servletPathSpec.indexOf('/');
+ // cannot have path separator
+ if (idx >= 0)
+ {
+ throw new IllegalArgumentException("Servlet Spec 12.2 violation: suffix based path spec cannot have path separators: bad spec \""+ servletPathSpec +"\"");
+ }
+
+ idx = servletPathSpec.indexOf('*',2);
+ // only allowed to have 1 glob '*', at the start of the path spec
+ if (idx >= 1)
+ {
+ throw new IllegalArgumentException("Servlet Spec 12.2 violation: suffix based path spec cannot have multiple glob '*': bad spec \""+ servletPathSpec +"\"");
+ }
+ }
+ else
+ {
+ throw new IllegalArgumentException("Servlet Spec 12.2 violation: path spec must start with \"/\" or \"*.\": bad spec \""+ servletPathSpec +"\"");
+ }
+ }
+
+ @Override
+ public String getPathInfo(String path)
+ {
+ // Path Info only valid for PREFIX_GLOB types
+ if (group == PathSpecGroup.PREFIX_GLOB)
+ {
+ if (path.length() == (specLength - 2))
+ {
+ return null;
+ }
+ return path.substring(specLength - 2);
+ }
+
+ return null;
+ }
+
+ @Override
+ public String getPathMatch(String path)
+ {
+ switch (group)
+ {
+ case EXACT:
+ if (pathSpec.equals(path))
+ {
+ return path;
+ }
+ else
+ {
+ return null;
+ }
+ case PREFIX_GLOB:
+ if (isWildcardMatch(path))
+ {
+ return path.substring(0,specLength - 2);
+ }
+ else
+ {
+ return null;
+ }
+ case SUFFIX_GLOB:
+ if (path.regionMatches(path.length() - (specLength - 1),pathSpec,1,specLength - 1))
+ {
+ return path;
+ }
+ else
+ {
+ return null;
+ }
+ case DEFAULT:
+ return path;
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public String getRelativePath(String base, String path)
+ {
+ String info = getPathInfo(path);
+ if (info == null)
+ {
+ info = path;
+ }
+
+ if (info.startsWith("./"))
+ {
+ info = info.substring(2);
+ }
+ if (base.endsWith(URIUtil.SLASH))
+ {
+ if (info.startsWith(URIUtil.SLASH))
+ {
+ path = base + info.substring(1);
+ }
+ else
+ {
+ path = base + info;
+ }
+ }
+ else if (info.startsWith(URIUtil.SLASH))
+ {
+ path = base + info;
+ }
+ else
+ {
+ path = base + URIUtil.SLASH + info;
+ }
+ return path;
+ }
+
+ private boolean isWildcardMatch(String path)
+ {
+ // For a spec of "/foo/*" match "/foo" , "/foo/..." but not "/foobar"
+ int cpl = specLength - 2;
+ if ((group == PathSpecGroup.PREFIX_GLOB) && (path.regionMatches(0,pathSpec,0,cpl)))
+ {
+ if ((path.length() == cpl) || ('/' == path.charAt(cpl)))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean matches(String path)
+ {
+ switch (group)
+ {
+ case EXACT:
+ return pathSpec.equals(path);
+ case PREFIX_GLOB:
+ return isWildcardMatch(path);
+ case SUFFIX_GLOB:
+ return path.regionMatches((path.length() - specLength) + 1,pathSpec,1,specLength - 1);
+ case ROOT:
+ // Only "/" matches
+ return ("/".equals(path));
+ case DEFAULT:
+ // If we reached this point, then everything matches
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/UriTemplatePathSpec.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/UriTemplatePathSpec.java
new file mode 100644
index 0000000000..208820b6e3
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http/pathmap/UriTemplatePathSpec.java
@@ -0,0 +1,341 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http.pathmap;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * PathSpec for URI Template based declarations
+ *
+ * @see URI Templates (Level 1)
+ */
+public class UriTemplatePathSpec extends RegexPathSpec
+{
+ private static final Logger LOG = Log.getLogger(UriTemplatePathSpec.class);
+
+ private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{(.*)\\}");
+ /** Reserved Symbols in URI Template variable */
+ private static final String VARIABLE_RESERVED = ":/?#[]@" + // gen-delims
+ "!$&'()*+,;="; // sub-delims
+ /** Allowed Symbols in a URI Template variable */
+ private static final String VARIABLE_SYMBOLS="-._";
+ private static final Set FORBIDDEN_SEGMENTS;
+
+ static
+ {
+ FORBIDDEN_SEGMENTS = new HashSet<>();
+ FORBIDDEN_SEGMENTS.add("/./");
+ FORBIDDEN_SEGMENTS.add("/../");
+ FORBIDDEN_SEGMENTS.add("//");
+ }
+
+ private String variables[];
+
+ public UriTemplatePathSpec(String rawSpec)
+ {
+ super();
+ Objects.requireNonNull(rawSpec,"Path Param Spec cannot be null");
+
+ if ("".equals(rawSpec) || "/".equals(rawSpec))
+ {
+ super.pathSpec = "/";
+ super.pattern = Pattern.compile("^/$");
+ super.pathDepth = 1;
+ this.specLength = 1;
+ this.variables = new String[0];
+ this.group = PathSpecGroup.EXACT;
+ return;
+ }
+
+ if (rawSpec.charAt(0) != '/')
+ {
+ // path specs must start with '/'
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: path spec \"");
+ err.append(rawSpec);
+ err.append("\" must start with '/'");
+ throw new IllegalArgumentException(err.toString());
+ }
+
+ for (String forbidden : FORBIDDEN_SEGMENTS)
+ {
+ if (rawSpec.contains(forbidden))
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: segment ");
+ err.append(forbidden);
+ err.append(" is forbidden in path spec: ");
+ err.append(rawSpec);
+ throw new IllegalArgumentException(err.toString());
+ }
+ }
+
+ this.pathSpec = rawSpec;
+
+ StringBuilder regex = new StringBuilder();
+ regex.append('^');
+
+ List varNames = new ArrayList<>();
+ // split up into path segments (ignoring the first slash that will always be empty)
+ String segments[] = rawSpec.substring(1).split("/");
+ char segmentSignature[] = new char[segments.length];
+ this.pathDepth = segments.length;
+ for (int i = 0; i < segments.length; i++)
+ {
+ String segment = segments[i];
+ Matcher mat = VARIABLE_PATTERN.matcher(segment);
+
+ if (mat.matches())
+ {
+ // entire path segment is a variable.
+ String variable = mat.group(1);
+ if (varNames.contains(variable))
+ {
+ // duplicate variable names
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: variable ");
+ err.append(variable);
+ err.append(" is duplicated in path spec: ");
+ err.append(rawSpec);
+ throw new IllegalArgumentException(err.toString());
+ }
+
+ assertIsValidVariableLiteral(variable);
+
+ segmentSignature[i] = 'v'; // variable
+ // valid variable name
+ varNames.add(variable);
+ // build regex
+ regex.append("/([^/]+)");
+ }
+ else if (mat.find(0))
+ {
+ // variable exists as partial segment
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: variable ");
+ err.append(mat.group());
+ err.append(" must exist as entire path segment: ");
+ err.append(rawSpec);
+ throw new IllegalArgumentException(err.toString());
+ }
+ else if ((segment.indexOf('{') >= 0) || (segment.indexOf('}') >= 0))
+ {
+ // variable is split with a path separator
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: invalid path segment /");
+ err.append(segment);
+ err.append("/ variable declaration incomplete: ");
+ err.append(rawSpec);
+ throw new IllegalArgumentException(err.toString());
+ }
+ else if (segment.indexOf('*') >= 0)
+ {
+ // glob segment
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: path segment /");
+ err.append(segment);
+ err.append("/ contains a wildcard symbol (not supported by this uri-template implementation): ");
+ err.append(rawSpec);
+ throw new IllegalArgumentException(err.toString());
+ }
+ else
+ {
+ // valid path segment
+ segmentSignature[i] = 'e'; // exact
+ // build regex
+ regex.append('/');
+ // escape regex special characters
+ for (char c : segment.toCharArray())
+ {
+ if ((c == '.') || (c == '[') || (c == ']') || (c == '\\'))
+ {
+ regex.append('\\');
+ }
+ regex.append(c);
+ }
+ }
+ }
+
+ // Handle trailing slash (which is not picked up during split)
+ if(rawSpec.charAt(rawSpec.length()-1) == '/')
+ {
+ regex.append('/');
+ }
+
+ regex.append('$');
+
+ this.pattern = Pattern.compile(regex.toString());
+
+ int varcount = varNames.size();
+ this.variables = varNames.toArray(new String[varcount]);
+
+ // Convert signature to group
+ String sig = String.valueOf(segmentSignature);
+
+ if (Pattern.matches("^e*$",sig))
+ {
+ this.group = PathSpecGroup.EXACT;
+ }
+ else if (Pattern.matches("^e*v+",sig))
+ {
+ this.group = PathSpecGroup.PREFIX_GLOB;
+ }
+ else if (Pattern.matches("^v+e+",sig))
+ {
+ this.group = PathSpecGroup.SUFFIX_GLOB;
+ }
+ else
+ {
+ this.group = PathSpecGroup.MIDDLE_GLOB;
+ }
+ }
+
+ /**
+ * Validate variable literal name, per RFC6570, Section 2.1 Literals
+ * @param variable
+ * @param pathParamSpec
+ */
+ private void assertIsValidVariableLiteral(String variable)
+ {
+ int len = variable.length();
+
+ int i = 0;
+ int codepoint;
+ boolean valid = (len > 0); // must not be zero length
+
+ while (valid && i < len)
+ {
+ codepoint = variable.codePointAt(i);
+ i += Character.charCount(codepoint);
+
+ // basic letters, digits, or symbols
+ if (isValidBasicLiteralCodepoint(codepoint))
+ {
+ continue;
+ }
+
+ // The ucschar and iprivate pieces
+ if (Character.isSupplementaryCodePoint(codepoint))
+ {
+ continue;
+ }
+
+ // pct-encoded
+ if (codepoint == '%')
+ {
+ if (i + 2 > len)
+ {
+ // invalid percent encoding, missing extra 2 chars
+ valid = false;
+ continue;
+ }
+ codepoint = TypeUtil.convertHexDigit(variable.codePointAt(i++)) << 4;
+ codepoint |= TypeUtil.convertHexDigit(variable.codePointAt(i++));
+
+ // validate basic literal
+ if (isValidBasicLiteralCodepoint(codepoint))
+ {
+ continue;
+ }
+ }
+
+ valid = false;
+ }
+
+ if (!valid)
+ {
+ // invalid variable name
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: variable {");
+ err.append(variable);
+ err.append("} an invalid variable name: ");
+ err.append(pathSpec);
+ throw new IllegalArgumentException(err.toString());
+ }
+ }
+
+ private boolean isValidBasicLiteralCodepoint(int codepoint)
+ {
+ // basic letters or digits
+ if((codepoint >= 'a' && codepoint <= 'z') ||
+ (codepoint >= 'A' && codepoint <= 'Z') ||
+ (codepoint >= '0' && codepoint <= '9'))
+ {
+ return true;
+ }
+
+ // basic allowed symbols
+ if(VARIABLE_SYMBOLS.indexOf(codepoint) >= 0)
+ {
+ return true; // valid simple value
+ }
+
+ // basic reserved symbols
+ if(VARIABLE_RESERVED.indexOf(codepoint) >= 0)
+ {
+ LOG.warn("Detected URI Template reserved symbol [{}] in path spec \"{}\"",(char)codepoint,pathSpec);
+ return false; // valid simple value
+ }
+
+ return false;
+ }
+
+ public Map getPathParams(String path)
+ {
+ Matcher matcher = getMatcher(path);
+ if (matcher.matches())
+ {
+ if (group == PathSpecGroup.EXACT)
+ {
+ return Collections.emptyMap();
+ }
+ Map ret = new HashMap<>();
+ int groupCount = matcher.groupCount();
+ for (int i = 1; i <= groupCount; i++)
+ {
+ ret.put(this.variables[i - 1],matcher.group(i));
+ }
+ return ret;
+ }
+ return null;
+ }
+
+ public int getVariableCount()
+ {
+ return variables.length;
+ }
+
+ public String[] getVariables()
+ {
+ return this.variables;
+ }
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/AuthorityHttpField.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/AuthorityHttpField.java
new file mode 100644
index 0000000000..155b3f7520
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/AuthorityHttpField.java
@@ -0,0 +1,43 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.http2.hpack;
+
+import org.eclipse.jetty.http.HostPortHttpField;
+import org.eclipse.jetty.http.HttpHeader;
+
+
+/* ------------------------------------------------------------ */
+/**
+ */
+public class AuthorityHttpField extends HostPortHttpField
+{
+ public final static String AUTHORITY = HpackContext.STATIC_TABLE[1][0];
+
+ public AuthorityHttpField(String authority)
+ {
+ super(HttpHeader.C_AUTHORITY,AUTHORITY,authority);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s(preparsed h=%s p=%d)",super.toString(),getHost(),getPort());
+ }
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/HpackContext.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/HpackContext.java
new file mode 100644
index 0000000000..89fbf00e4a
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/HpackContext.java
@@ -0,0 +1,515 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http2.hpack;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.util.ArrayQueue;
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** HPACK - Header Compression for HTTP/2
+ * This class maintains the compression context for a single HTTP/2
+ * connection. Specifically it holds the static and dynamic Header Field Tables
+ * and the associated sizes and limits.
+ *
+ * It is compliant with draft 11 of the specification
+ */
+public class HpackContext
+{
+ public static final Logger LOG = Log.getLogger(HpackContext.class);
+
+ public static final String[][] STATIC_TABLE =
+ {
+ {null,null},
+ /* 1 */ {":authority",null},
+ /* 2 */ {":method","GET"},
+ /* 3 */ {":method","POST"},
+ /* 4 */ {":path","/"},
+ /* 5 */ {":path","/index.html"},
+ /* 6 */ {":scheme","http"},
+ /* 7 */ {":scheme","https"},
+ /* 8 */ {":status","200"},
+ /* 9 */ {":status","204"},
+ /* 10 */ {":status","206"},
+ /* 11 */ {":status","304"},
+ /* 12 */ {":status","400"},
+ /* 13 */ {":status","404"},
+ /* 14 */ {":status","500"},
+ /* 15 */ {"accept-charset",null},
+ /* 16 */ {"accept-encoding","gzip, deflate"},
+ /* 17 */ {"accept-language",null},
+ /* 18 */ {"accept-ranges",null},
+ /* 19 */ {"accept",null},
+ /* 20 */ {"access-control-allow-origin",null},
+ /* 21 */ {"age",null},
+ /* 22 */ {"allow",null},
+ /* 23 */ {"authorization",null},
+ /* 24 */ {"cache-control",null},
+ /* 25 */ {"content-disposition",null},
+ /* 26 */ {"content-encoding",null},
+ /* 27 */ {"content-language",null},
+ /* 28 */ {"content-length",null},
+ /* 29 */ {"content-location",null},
+ /* 30 */ {"content-range",null},
+ /* 31 */ {"content-type",null},
+ /* 32 */ {"cookie",null},
+ /* 33 */ {"date",null},
+ /* 34 */ {"etag",null},
+ /* 35 */ {"expect",null},
+ /* 36 */ {"expires",null},
+ /* 37 */ {"from",null},
+ /* 38 */ {"host",null},
+ /* 39 */ {"if-match",null},
+ /* 40 */ {"if-modified-since",null},
+ /* 41 */ {"if-none-match",null},
+ /* 42 */ {"if-range",null},
+ /* 43 */ {"if-unmodified-since",null},
+ /* 44 */ {"last-modified",null},
+ /* 45 */ {"link",null},
+ /* 46 */ {"location",null},
+ /* 47 */ {"max-forwards",null},
+ /* 48 */ {"proxy-authenticate",null},
+ /* 49 */ {"proxy-authorization",null},
+ /* 50 */ {"range",null},
+ /* 51 */ {"referer",null},
+ /* 52 */ {"refresh",null},
+ /* 53 */ {"retry-after",null},
+ /* 54 */ {"server",null},
+ /* 55 */ {"set-cookie",null},
+ /* 56 */ {"strict-transport-security",null},
+ /* 57 */ {"transfer-encoding",null},
+ /* 58 */ {"user-agent",null},
+ /* 59 */ {"vary",null},
+ /* 60 */ {"via",null},
+ /* 61 */ {"www-authenticate",null},
+ };
+
+ private static final Map __staticFieldMap = new HashMap<>();
+ private static final Trie __staticNameMap = new ArrayTernaryTrie<>(true,512);
+ private static final StaticEntry[] __staticTableByHeader = new StaticEntry[HttpHeader.UNKNOWN.ordinal()];
+ private static final StaticEntry[] __staticTable=new StaticEntry[STATIC_TABLE.length];
+ static
+ {
+ Set added = new HashSet<>();
+ for (int i=1;i _fieldMap = new HashMap<>();
+ private final Map _nameMap = new HashMap<>();
+
+ HpackContext(int maxDynamicTableSize)
+ {
+ _maxDynamicTableSizeInBytes=maxDynamicTableSize;
+ int guesstimateEntries = 10+maxDynamicTableSize/(32+10+10);
+ _dynamicTable=new DynamicTable(guesstimateEntries,guesstimateEntries+10);
+ if (LOG.isDebugEnabled())
+ LOG.debug(String.format("HdrTbl[%x] created max=%d",hashCode(),maxDynamicTableSize));
+ }
+
+ public void resize(int newMaxDynamicTableSize)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug(String.format("HdrTbl[%x] resized max=%d->%d",hashCode(),_maxDynamicTableSizeInBytes,newMaxDynamicTableSize));
+ _maxDynamicTableSizeInBytes=newMaxDynamicTableSize;
+ int guesstimateEntries = 10+newMaxDynamicTableSize/(32+10+10);
+ evict();
+ _dynamicTable.resizeUnsafe(guesstimateEntries);
+ }
+
+ public Entry get(HttpField field)
+ {
+ Entry entry = _fieldMap.get(field);
+ if (entry==null)
+ entry=__staticFieldMap.get(field);
+ return entry;
+ }
+
+ public Entry get(String name)
+ {
+ Entry entry = __staticNameMap.get(name);
+ if (entry!=null)
+ return entry;
+ return _nameMap.get(StringUtil.asciiToLowerCase(name));
+ }
+
+ public Entry get(int index)
+ {
+ if (index<__staticTable.length)
+ return __staticTable[index];
+
+ int d=_dynamicTable.size()-index+__staticTable.length-1;
+
+ if (d>=0)
+ return _dynamicTable.getUnsafe(d);
+ return null;
+ }
+
+ public Entry get(HttpHeader header)
+ {
+ Entry e = __staticTableByHeader[header.ordinal()];
+ if (e==null)
+ return get(header.asString());
+ return e;
+ }
+
+ public static Entry getStatic(HttpHeader header)
+ {
+ return __staticTableByHeader[header.ordinal()];
+ }
+
+ public Entry add(HttpField field)
+ {
+ int slot=_dynamicTable.getNextSlotUnsafe();
+ Entry entry=new Entry(slot,field);
+ int size = entry.getSize();
+ if (size>_maxDynamicTableSizeInBytes)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug(String.format("HdrTbl[%x] !added size %d>%d",hashCode(),size,_maxDynamicTableSizeInBytes));
+ return null;
+ }
+ _dynamicTableSizeInBytes+=size;
+ _dynamicTable.addUnsafe(entry);
+ _fieldMap.put(field,entry);
+ _nameMap.put(StringUtil.asciiToLowerCase(field.getName()),entry);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug(String.format("HdrTbl[%x] added %s",hashCode(),entry));
+ evict();
+ return entry;
+ }
+
+ /**
+ * @return Current dynamic table size in entries
+ */
+ public int size()
+ {
+ return _dynamicTable.size();
+ }
+
+ /**
+ * @return Current Dynamic table size in Octets
+ */
+ public int getDynamicTableSize()
+ {
+ return _dynamicTableSizeInBytes;
+ }
+
+ /**
+ * @return Max Dynamic table size in Octets
+ */
+ public int getMaxDynamicTableSize()
+ {
+ return _maxDynamicTableSizeInBytes;
+ }
+
+ public int index(Entry entry)
+ {
+ if (entry._slot<0)
+ return 0;
+ if (entry.isStatic())
+ return entry._slot;
+
+ return _dynamicTable.index(entry)+__staticTable.length-1;
+ }
+
+ public static int staticIndex(HttpHeader header)
+ {
+ if (header==null)
+ return 0;
+ Entry entry=__staticNameMap.get(header.asString());
+ if (entry==null)
+ return 0;
+ return entry.getSlot();
+ }
+
+ private void evict()
+ {
+ while (_dynamicTableSizeInBytes>_maxDynamicTableSizeInBytes)
+ {
+ Entry entry = _dynamicTable.pollUnsafe();
+ if (LOG.isDebugEnabled())
+ LOG.debug(String.format("HdrTbl[%x] evict %s",hashCode(),entry));
+ _dynamicTableSizeInBytes-=entry.getSize();
+ entry._slot=-1;
+ _fieldMap.remove(entry.getHttpField());
+ String lc=StringUtil.asciiToLowerCase(entry.getHttpField().getName());
+ if (entry==_nameMap.get(lc))
+ _nameMap.remove(lc);
+ }
+ if (LOG.isDebugEnabled())
+ LOG.debug(String.format("HdrTbl[%x] entries=%d, size=%d, max=%d",hashCode(),_dynamicTable.size(),_dynamicTableSizeInBytes,_maxDynamicTableSizeInBytes));
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("HpackContext@%x{entries=%d,size=%d,max=%d}",hashCode(),_dynamicTable.size(),_dynamicTableSizeInBytes,_maxDynamicTableSizeInBytes);
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ */
+ private class DynamicTable extends ArrayQueue
+ {
+ /* ------------------------------------------------------------ */
+ /**
+ * @param initCapacity
+ * @param growBy
+ */
+ private DynamicTable(int initCapacity, int growBy)
+ {
+ super(initCapacity,growBy);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.ArrayQueue#growUnsafe()
+ */
+ @Override
+ protected void resizeUnsafe(int newCapacity)
+ {
+ // Relay on super.growUnsafe to pack all entries 0 to _nextSlot
+ super.resizeUnsafe(newCapacity);
+ for (int s=0;s<_nextSlot;s++)
+ ((Entry)_elements[s])._slot=s;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.ArrayQueue#enqueue(java.lang.Object)
+ */
+ @Override
+ public boolean enqueue(Entry e)
+ {
+ return super.enqueue(e);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.ArrayQueue#dequeue()
+ */
+ @Override
+ public Entry dequeue()
+ {
+ return super.dequeue();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param entry
+ * @return
+ */
+ private int index(Entry entry)
+ {
+ return entry._slot>=_nextE?_size-entry._slot+_nextE:_nextSlot-entry._slot;
+ }
+
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public static class Entry
+ {
+ final HttpField _field;
+ int _slot;
+
+ Entry()
+ {
+ _slot=0;
+ _field=null;
+ }
+
+ Entry(int index,String name, String value)
+ {
+ _slot=index;
+ _field=new HttpField(name,value);
+ }
+
+ Entry(int slot, HttpField field)
+ {
+ _slot=slot;
+ _field=field;
+ }
+
+ public int getSize()
+ {
+ return 32+_field.getName().length()+_field.getValue().length();
+ }
+
+ public HttpField getHttpField()
+ {
+ return _field;
+ }
+
+ public boolean isStatic()
+ {
+ return false;
+ }
+
+ public byte[] getStaticHuffmanValue()
+ {
+ return null;
+ }
+
+ public int getSlot()
+ {
+ return _slot;
+ }
+
+ public String toString()
+ {
+ return String.format("{%s,%d,%s,%x}",isStatic()?"S":"D",_slot,_field,hashCode());
+ }
+ }
+
+ public static class StaticEntry extends Entry
+ {
+ private final byte[] _huffmanValue;
+ private final byte _encodedField;
+
+ StaticEntry(int index,HttpField field)
+ {
+ super(index,field);
+ String value = field.getValue();
+ if (value!=null && value.length()>0)
+ {
+ int huffmanLen = Huffman.octetsNeeded(value);
+ int lenLen = NBitInteger.octectsNeeded(7,huffmanLen);
+ _huffmanValue = new byte[1+lenLen+huffmanLen];
+ ByteBuffer buffer = ByteBuffer.wrap(_huffmanValue);
+
+ // Indicate Huffman
+ buffer.put((byte)0x80);
+ // Add huffman length
+ NBitInteger.encode(buffer,7,huffmanLen);
+ // Encode value
+ Huffman.encode(buffer,value);
+ }
+ else
+ _huffmanValue=null;
+
+ _encodedField=(byte)(0x80|index);
+ }
+
+ @Override
+ public boolean isStatic()
+ {
+ return true;
+ }
+
+ @Override
+ public byte[] getStaticHuffmanValue()
+ {
+ return _huffmanValue;
+ }
+
+ public byte getEncodedField()
+ {
+ return _encodedField;
+ }
+ }
+
+
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/HpackDecoder.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/HpackDecoder.java
new file mode 100644
index 0000000000..38dc8567f1
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/HpackDecoder.java
@@ -0,0 +1,280 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.http2.hpack;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.http.BadMessageException;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.MetaData;
+import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Hpack Decoder
+ * This is not thread safe and may only be called by 1 thread at a time.
+ */
+public class HpackDecoder
+{
+ public static final Logger LOG = Log.getLogger(HpackDecoder.class);
+ public final static HttpField.LongValueHttpField CONTENT_LENGTH_0 =
+ new HttpField.LongValueHttpField(HttpHeader.CONTENT_LENGTH,0L);
+
+ private final HpackContext _context;
+ private final MetaDataBuilder _builder;
+ private int _localMaxDynamicTableSize;
+
+ /**
+ * @param localMaxDynamicTableSize The maximum allowed size of the local dynamic header field table.
+ * @param maxHeaderSize The maximum allowed size of a headers block, expressed as total of all name and value characters.
+ */
+ public HpackDecoder(int localMaxDynamicTableSize, int maxHeaderSize)
+ {
+ _context=new HpackContext(localMaxDynamicTableSize);
+ _localMaxDynamicTableSize=localMaxDynamicTableSize;
+ _builder = new MetaDataBuilder(maxHeaderSize);
+ }
+
+ public HpackContext getHpackContext()
+ {
+ return _context;
+ }
+
+ public void setLocalMaxDynamicTableSize(int localMaxdynamciTableSize)
+ {
+ _localMaxDynamicTableSize=localMaxdynamciTableSize;
+ }
+
+ public MetaData decode(ByteBuffer buffer)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug(String.format("CtxTbl[%x] decoding %d octets",_context.hashCode(),buffer.remaining()));
+
+ // If the buffer is big, don't even think about decoding it
+ if (buffer.remaining()>_builder.getMaxSize())
+ throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413,"Header frame size "+buffer.remaining()+">"+_builder.getMaxSize());
+
+
+ while(buffer.hasRemaining())
+ {
+ if (LOG.isDebugEnabled())
+ {
+ int l=Math.min(buffer.remaining(),16);
+ // TODO: not guaranteed the buffer has a backing array !
+ LOG.debug("decode {}{}",
+ TypeUtil.toHexString(buffer.array(),buffer.arrayOffset()+buffer.position(),l),
+ l>4);
+ String name;
+ HttpHeader header;
+ String value;
+
+ boolean indexed;
+ int name_index;
+
+ switch (f)
+ {
+ case 2: // 7.3
+ case 3: // 7.3
+ // change table size
+ int size = NBitInteger.decode(buffer,5);
+ if (LOG.isDebugEnabled())
+ LOG.debug("decode resize="+size);
+ if (size>_localMaxDynamicTableSize)
+ throw new IllegalArgumentException();
+ _context.resize(size);
+ continue;
+
+ case 0: // 7.2.2
+ case 1: // 7.2.3
+ indexed=false;
+ name_index=NBitInteger.decode(buffer,4);
+ break;
+
+
+ case 4: // 7.2.1
+ case 5: // 7.2.1
+ case 6: // 7.2.1
+ case 7: // 7.2.1
+ indexed=true;
+ name_index=NBitInteger.decode(buffer,6);
+ break;
+
+ default:
+ throw new IllegalStateException();
+ }
+
+
+ boolean huffmanName=false;
+
+ // decode the name
+ if (name_index>0)
+ {
+ Entry name_entry=_context.get(name_index);
+ name=name_entry.getHttpField().getName();
+ header=name_entry.getHttpField().getHeader();
+ }
+ else
+ {
+ huffmanName = (buffer.get()&0x80)==0x80;
+ int length = NBitInteger.decode(buffer,7);
+ _builder.checkSize(length,huffmanName);
+ if (huffmanName)
+ name=Huffman.decode(buffer,length);
+ else
+ name=toASCIIString(buffer,length);
+ for (int i=0;i='A'&&c<='Z')
+ {
+ throw new BadMessageException(400,"Uppercase header name");
+ }
+ }
+ header=HttpHeader.CACHE.get(name);
+ }
+
+ // decode the value
+ boolean huffmanValue = (buffer.get()&0x80)==0x80;
+ int length = NBitInteger.decode(buffer,7);
+ _builder.checkSize(length,huffmanValue);
+ if (huffmanValue)
+ value=Huffman.decode(buffer,length);
+ else
+ value=toASCIIString(buffer,length);
+
+ // Make the new field
+ HttpField field;
+ if (header==null)
+ {
+ // just make a normal field and bypass header name lookup
+ field = new HttpField(null,name,value);
+ }
+ else
+ {
+ // might be worthwhile to create a value HttpField if it is indexed
+ // and/or of a type that may be looked up multiple times.
+ switch(header)
+ {
+ case C_STATUS:
+ if (indexed)
+ field = new HttpField.IntValueHttpField(header,name,value);
+ else
+ field = new HttpField(header,name,value);
+ break;
+
+ case C_AUTHORITY:
+ field = new AuthorityHttpField(value);
+ break;
+
+ case CONTENT_LENGTH:
+ if ("0".equals(value))
+ field = CONTENT_LENGTH_0;
+ else
+ field = new HttpField.LongValueHttpField(header,name,value);
+ break;
+
+ default:
+ field = new HttpField(header,name,value);
+ break;
+ }
+ }
+
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("decoded '{}' by {}/{}/{}",
+ field,
+ name_index > 0 ? "IdxName" : (huffmanName ? "HuffName" : "LitName"),
+ huffmanValue ? "HuffVal" : "LitVal",
+ indexed ? "Idx" : "");
+ }
+
+ // emit the field
+ _builder.emit(field);
+
+ // if indexed
+ if (indexed)
+ {
+ // add to dynamic table
+ _context.add(field);
+ }
+
+ }
+ }
+
+ return _builder.build();
+ }
+
+ public static String toASCIIString(ByteBuffer buffer,int length)
+ {
+ StringBuilder builder = new StringBuilder(length);
+ int position=buffer.position();
+ int start=buffer.arrayOffset()+ position;
+ int end=start+length;
+ buffer.position(position+length);
+ byte[] array=buffer.array();
+ for (int i=start;i __DO_NOT_HUFFMAN =
+ EnumSet.of(
+ HttpHeader.AUTHORIZATION,
+ HttpHeader.CONTENT_MD5,
+ HttpHeader.PROXY_AUTHENTICATE,
+ HttpHeader.PROXY_AUTHORIZATION);
+
+ final static EnumSet __DO_NOT_INDEX =
+ EnumSet.of(
+ // HttpHeader.C_PATH, // TODO more data needed
+ // HttpHeader.DATE, // TODO more data needed
+ HttpHeader.AUTHORIZATION,
+ HttpHeader.CONTENT_MD5,
+ HttpHeader.CONTENT_RANGE,
+ HttpHeader.ETAG,
+ HttpHeader.IF_MODIFIED_SINCE,
+ HttpHeader.IF_UNMODIFIED_SINCE,
+ HttpHeader.IF_NONE_MATCH,
+ HttpHeader.IF_RANGE,
+ HttpHeader.IF_MATCH,
+ HttpHeader.LOCATION,
+ HttpHeader.RANGE,
+ HttpHeader.RETRY_AFTER,
+ // HttpHeader.EXPIRES,
+ HttpHeader.LAST_MODIFIED,
+ HttpHeader.SET_COOKIE,
+ HttpHeader.SET_COOKIE2);
+
+
+ final static EnumSet __NEVER_INDEX =
+ EnumSet.of(
+ HttpHeader.AUTHORIZATION,
+ HttpHeader.SET_COOKIE,
+ HttpHeader.SET_COOKIE2);
+
+ static
+ {
+ for (HttpStatus.Code code : HttpStatus.Code.values())
+ __status[code.getCode()]=new PreEncodedHttpField(HttpHeader.C_STATUS,Integer.toString(code.getCode()));
+ }
+
+ private final HpackContext _context;
+ private final boolean _debug;
+ private int _remoteMaxDynamicTableSize;
+ private int _localMaxDynamicTableSize;
+
+ public HpackEncoder()
+ {
+ this(4096,4096);
+ }
+
+ public HpackEncoder(int localMaxDynamicTableSize)
+ {
+ this(localMaxDynamicTableSize,4096);
+ }
+
+ public HpackEncoder(int localMaxDynamicTableSize,int remoteMaxDynamicTableSize)
+ {
+ _context=new HpackContext(remoteMaxDynamicTableSize);
+ _remoteMaxDynamicTableSize=remoteMaxDynamicTableSize;
+ _localMaxDynamicTableSize=localMaxDynamicTableSize;
+ _debug=LOG.isDebugEnabled();
+ }
+
+ public HpackContext getHpackContext()
+ {
+ return _context;
+ }
+
+ public void setRemoteMaxDynamicTableSize(int remoteMaxDynamicTableSize)
+ {
+ _remoteMaxDynamicTableSize=remoteMaxDynamicTableSize;
+ }
+
+ public void setLocalMaxDynamicTableSize(int localMaxDynamicTableSize)
+ {
+ _localMaxDynamicTableSize=localMaxDynamicTableSize;
+ }
+
+ public void encode(ByteBuffer buffer, MetaData metadata)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug(String.format("CtxTbl[%x] encoding",_context.hashCode()));
+
+ int pos = buffer.position();
+
+ // Check the dynamic table sizes!
+ int maxDynamicTableSize=Math.min(_remoteMaxDynamicTableSize,_localMaxDynamicTableSize);
+ if (maxDynamicTableSize!=_context.getMaxDynamicTableSize())
+ encodeMaxDynamicTableSize(buffer,maxDynamicTableSize);
+
+ // Add Request/response meta fields
+ if (metadata.isRequest())
+ {
+ MetaData.Request request = (MetaData.Request)metadata;
+
+ // TODO optimise these to avoid HttpField creation
+ String scheme=request.getURI().getScheme();
+ encode(buffer,new HttpField(HttpHeader.C_SCHEME,scheme==null?HttpScheme.HTTP.asString():scheme));
+ encode(buffer,new HttpField(HttpHeader.C_METHOD,request.getMethod()));
+ encode(buffer,new HttpField(HttpHeader.C_AUTHORITY,request.getURI().getAuthority()));
+ encode(buffer,new HttpField(HttpHeader.C_PATH,request.getURI().getPathQuery()));
+
+ }
+ else if (metadata.isResponse())
+ {
+ MetaData.Response response = (MetaData.Response)metadata;
+ int code=response.getStatus();
+ HttpField status = code<__status.length?__status[code]:null;
+ if (status==null)
+ status=new HttpField.IntValueHttpField(HttpHeader.C_STATUS,code);
+ encode(buffer,status);
+ }
+
+ // Add all the other fields
+ for (HttpField field : metadata)
+ encode(buffer,field);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug(String.format("CtxTbl[%x] encoded %d octets",_context.hashCode(), buffer.position() - pos));
+ }
+
+ public void encodeMaxDynamicTableSize(ByteBuffer buffer, int maxDynamicTableSize)
+ {
+ if (maxDynamicTableSize>_remoteMaxDynamicTableSize)
+ throw new IllegalArgumentException();
+ buffer.put((byte)0x20);
+ NBitInteger.encode(buffer,5,maxDynamicTableSize);
+ _context.resize(maxDynamicTableSize);
+ }
+
+ public void encode(ByteBuffer buffer, HttpField field)
+ {
+ final int p=_debug?buffer.position():-1;
+
+ String encoding=null;
+
+ // Is there an entry for the field?
+ Entry entry = _context.get(field);
+ if (entry!=null)
+ {
+ // Known field entry, so encode it as indexed
+ if (entry.isStatic())
+ {
+ buffer.put(((StaticEntry)entry).getEncodedField());
+ if (_debug)
+ encoding="IdxFieldS1";
+ }
+ else
+ {
+ int index=_context.index(entry);
+ buffer.put((byte)0x80);
+ NBitInteger.encode(buffer,7,index);
+ if (_debug)
+ encoding="IdxField"+(entry.isStatic()?"S":"")+(1+NBitInteger.octectsNeeded(7,index));
+ }
+ }
+ else
+ {
+ // Unknown field entry, so we will have to send literally.
+ final boolean indexed;
+
+ // But do we know it's name?
+ HttpHeader header = field.getHeader();
+
+ // Select encoding strategy
+ if (header==null)
+ {
+ // Select encoding strategy for unknown header names
+ Entry name = _context.get(field.getName());
+
+ if (field instanceof PreEncodedHttpField)
+ {
+ int i=buffer.position();
+ ((PreEncodedHttpField)field).putTo(buffer,HttpVersion.HTTP_2);
+ byte b=buffer.get(i);
+ indexed=b<0||b>=0x40;
+ if (_debug)
+ encoding=indexed?"PreEncodedIdx":"PreEncoded";
+ }
+ // has the custom header name been seen before?
+ else if (name==null)
+ {
+ // unknown name and value, so let's index this just in case it is
+ // the first time we have seen a custom name or a custom field.
+ // unless the name is changing, this is worthwhile
+ indexed=true;
+ encodeName(buffer,(byte)0x40,6,field.getName(),null);
+ encodeValue(buffer,true,field.getValue());
+ if (_debug)
+ encoding="LitHuffNHuffVIdx";
+ }
+ else
+ {
+ // known custom name, but unknown value.
+ // This is probably a custom field with changing value, so don't index.
+ indexed=false;
+ encodeName(buffer,(byte)0x00,4,field.getName(),null);
+ encodeValue(buffer,true,field.getValue());
+ if (_debug)
+ encoding="LitHuffNHuffV!Idx";
+ }
+ }
+ else
+ {
+ // Select encoding strategy for known header names
+ Entry name = _context.get(header);
+
+ if (field instanceof PreEncodedHttpField)
+ {
+ // Preencoded field
+ int i=buffer.position();
+ ((PreEncodedHttpField)field).putTo(buffer,HttpVersion.HTTP_2);
+ byte b=buffer.get(i);
+ indexed=b<0||b>=0x40;
+ if (_debug)
+ encoding=indexed?"PreEncodedIdx":"PreEncoded";
+ }
+ else if (__DO_NOT_INDEX.contains(header))
+ {
+ // Non indexed field
+ indexed=false;
+ boolean never_index=__NEVER_INDEX.contains(header);
+ boolean huffman=!__DO_NOT_HUFFMAN.contains(header);
+ encodeName(buffer,never_index?(byte)0x10:(byte)0x00,4,header.asString(),name);
+ encodeValue(buffer,huffman,field.getValue());
+
+ if (_debug)
+ encoding="Lit"+
+ ((name==null)?"HuffN":("IdxN"+(name.isStatic()?"S":"")+(1+NBitInteger.octectsNeeded(4,_context.index(name)))))+
+ (huffman?"HuffV":"LitV")+
+ (indexed?"Idx":(never_index?"!!Idx":"!Idx"));
+ }
+ else if (header==HttpHeader.CONTENT_LENGTH && field.getValue().length()>1)
+ {
+ // Non indexed content length for 2 digits or more
+ indexed=false;
+ encodeName(buffer,(byte)0x00,4,header.asString(),name);
+ encodeValue(buffer,true,field.getValue());
+ if (_debug)
+ encoding="LitIdxNS"+(1+NBitInteger.octectsNeeded(4,_context.index(name)))+"HuffV!Idx";
+ }
+ else
+ {
+ // indexed
+ indexed=true;
+ boolean huffman=!__DO_NOT_HUFFMAN.contains(header);
+ encodeName(buffer,(byte)0x40,6,header.asString(),name);
+ encodeValue(buffer,huffman,field.getValue());
+ if (_debug)
+ encoding=((name==null)?"LitHuffN":("LitIdxN"+(name.isStatic()?"S":"")+(1+NBitInteger.octectsNeeded(6,_context.index(name)))))+
+ (huffman?"HuffVIdx":"LitVIdx");
+ }
+ }
+
+ // If we want the field referenced, then we add it to our
+ // table and reference set.
+ if (indexed)
+ _context.add(field);
+ }
+
+ if (_debug)
+ {
+ int e=buffer.position();
+ if (LOG.isDebugEnabled())
+ LOG.debug("encode {}:'{}' to '{}'",encoding,field,TypeUtil.toHexString(buffer.array(),buffer.arrayOffset()+p,e-p));
+ }
+ }
+
+ private void encodeName(ByteBuffer buffer, byte mask, int bits, String name, Entry entry)
+ {
+ buffer.put(mask);
+ if (entry==null)
+ {
+ // leave name index bits as 0
+ // Encode the name always with lowercase huffman
+ buffer.put((byte)0x80);
+ NBitInteger.encode(buffer,7,Huffman.octetsNeededLC(name));
+ Huffman.encodeLC(buffer,name);
+ }
+ else
+ {
+ NBitInteger.encode(buffer,bits,_context.index(entry));
+ }
+ }
+
+ static void encodeValue(ByteBuffer buffer, boolean huffman, String value)
+ {
+ if (huffman)
+ {
+ // huffman literal value
+ buffer.put((byte)0x80);
+ NBitInteger.encode(buffer,7,Huffman.octetsNeeded(value));
+ Huffman.encode(buffer,value);
+ }
+ else
+ {
+ // add literal assuming iso_8859_1
+ buffer.put((byte)0x00);
+ NBitInteger.encode(buffer,7,value.length());
+ for (int i=0;i127)
+ throw new IllegalArgumentException();
+ buffer.put((byte)c);
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/HpackFieldPreEncoder.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/HpackFieldPreEncoder.java
new file mode 100644
index 0000000000..f73b34728e
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/HpackFieldPreEncoder.java
@@ -0,0 +1,97 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.http2.hpack;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.http.HttpFieldPreEncoder;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.util.BufferUtil;
+
+
+/* ------------------------------------------------------------ */
+/**
+ */
+public class HpackFieldPreEncoder implements HttpFieldPreEncoder
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.http.HttpFieldPreEncoder#getHttpVersion()
+ */
+ @Override
+ public HttpVersion getHttpVersion()
+ {
+ return HttpVersion.HTTP_2;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.http.HttpFieldPreEncoder#getEncodedField(org.eclipse.jetty.http.HttpHeader, java.lang.String, java.lang.String)
+ */
+ @Override
+ public byte[] getEncodedField(HttpHeader header, String name, String value)
+ {
+ boolean not_indexed=HpackEncoder.__DO_NOT_INDEX.contains(header);
+
+ ByteBuffer buffer = BufferUtil.allocate(name.length()+value.length()+10);
+ BufferUtil.clearToFill(buffer);
+ boolean huffman;
+ int bits;
+
+ if (not_indexed)
+ {
+ // Non indexed field
+ boolean never_index=HpackEncoder.__NEVER_INDEX.contains(header);
+ huffman=!HpackEncoder.__DO_NOT_HUFFMAN.contains(header);
+ buffer.put(never_index?(byte)0x10:(byte)0x00);
+ bits=4;
+ }
+ else if (header==HttpHeader.CONTENT_LENGTH && value.length()>1)
+ {
+ // Non indexed content length for 2 digits or more
+ buffer.put((byte)0x00);
+ huffman=true;
+ bits=4;
+ }
+ else
+ {
+ // indexed
+ buffer.put((byte)0x40);
+ huffman=!HpackEncoder.__DO_NOT_HUFFMAN.contains(header);
+ bits=6;
+ }
+
+ int name_idx=HpackContext.staticIndex(header);
+ if (name_idx>0)
+ NBitInteger.encode(buffer,bits,name_idx);
+ else
+ {
+ buffer.put((byte)0x80);
+ NBitInteger.encode(buffer,7,Huffman.octetsNeededLC(name));
+ Huffman.encodeLC(buffer,name);
+ }
+
+ HpackEncoder.encodeValue(buffer,huffman,value);
+
+ BufferUtil.flipToFlush(buffer,0);
+ return BufferUtil.toArray(buffer);
+ }
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/Huffman.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/Huffman.java
new file mode 100644
index 0000000000..da28684c4a
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/Huffman.java
@@ -0,0 +1,480 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http2.hpack;
+
+import java.nio.ByteBuffer;
+
+public class Huffman
+{
+
+ // Appendix C: Huffman Codes
+ // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C
+ static final int[][] CODES =
+ {
+ /* ( 0) |11111111|11000 */ {0x1ff8,13},
+ /* ( 1) |11111111|11111111|1011000 */ {0x7fffd8,23},
+ /* ( 2) |11111111|11111111|11111110|0010 */ {0xfffffe2,28},
+ /* ( 3) |11111111|11111111|11111110|0011 */ {0xfffffe3,28},
+ /* ( 4) |11111111|11111111|11111110|0100 */ {0xfffffe4,28},
+ /* ( 5) |11111111|11111111|11111110|0101 */ {0xfffffe5,28},
+ /* ( 6) |11111111|11111111|11111110|0110 */ {0xfffffe6,28},
+ /* ( 7) |11111111|11111111|11111110|0111 */ {0xfffffe7,28},
+ /* ( 8) |11111111|11111111|11111110|1000 */ {0xfffffe8,28},
+ /* ( 9) |11111111|11111111|11101010 */ {0xffffea,24},
+ /* ( 10) |11111111|11111111|11111111|111100 */ {0x3ffffffc,30},
+ /* ( 11) |11111111|11111111|11111110|1001 */ {0xfffffe9,28},
+ /* ( 12) |11111111|11111111|11111110|1010 */ {0xfffffea,28},
+ /* ( 13) |11111111|11111111|11111111|111101 */ {0x3ffffffd,30},
+ /* ( 14) |11111111|11111111|11111110|1011 */ {0xfffffeb,28},
+ /* ( 15) |11111111|11111111|11111110|1100 */ {0xfffffec,28},
+ /* ( 16) |11111111|11111111|11111110|1101 */ {0xfffffed,28},
+ /* ( 17) |11111111|11111111|11111110|1110 */ {0xfffffee,28},
+ /* ( 18) |11111111|11111111|11111110|1111 */ {0xfffffef,28},
+ /* ( 19) |11111111|11111111|11111111|0000 */ {0xffffff0,28},
+ /* ( 20) |11111111|11111111|11111111|0001 */ {0xffffff1,28},
+ /* ( 21) |11111111|11111111|11111111|0010 */ {0xffffff2,28},
+ /* ( 22) |11111111|11111111|11111111|111110 */ {0x3ffffffe,30},
+ /* ( 23) |11111111|11111111|11111111|0011 */ {0xffffff3,28},
+ /* ( 24) |11111111|11111111|11111111|0100 */ {0xffffff4,28},
+ /* ( 25) |11111111|11111111|11111111|0101 */ {0xffffff5,28},
+ /* ( 26) |11111111|11111111|11111111|0110 */ {0xffffff6,28},
+ /* ( 27) |11111111|11111111|11111111|0111 */ {0xffffff7,28},
+ /* ( 28) |11111111|11111111|11111111|1000 */ {0xffffff8,28},
+ /* ( 29) |11111111|11111111|11111111|1001 */ {0xffffff9,28},
+ /* ( 30) |11111111|11111111|11111111|1010 */ {0xffffffa,28},
+ /* ( 31) |11111111|11111111|11111111|1011 */ {0xffffffb,28},
+ /*' ' ( 32) |010100 */ {0x14, 6},
+ /*'!' ( 33) |11111110|00 */ {0x3f8,10},
+ /*'"' ( 34) |11111110|01 */ {0x3f9,10},
+ /*'#' ( 35) |11111111|1010 */ {0xffa,12},
+ /*'$' ( 36) |11111111|11001 */ {0x1ff9,13},
+ /*'%' ( 37) |010101 */ {0x15, 6},
+ /*'&' ( 38) |11111000 */ {0xf8, 8},
+ /*''' ( 39) |11111111|010 */ {0x7fa,11},
+ /*'(' ( 40) |11111110|10 */ {0x3fa,10},
+ /*')' ( 41) |11111110|11 */ {0x3fb,10},
+ /*'*' ( 42) |11111001 */ {0xf9, 8},
+ /*'+' ( 43) |11111111|011 */ {0x7fb,11},
+ /*',' ( 44) |11111010 */ {0xfa, 8},
+ /*'-' ( 45) |010110 */ {0x16, 6},
+ /*'.' ( 46) |010111 */ {0x17, 6},
+ /*'/' ( 47) |011000 */ {0x18, 6},
+ /*'0' ( 48) |00000 */ {0x0, 5},
+ /*'1' ( 49) |00001 */ {0x1, 5},
+ /*'2' ( 50) |00010 */ {0x2, 5},
+ /*'3' ( 51) |011001 */ {0x19, 6},
+ /*'4' ( 52) |011010 */ {0x1a, 6},
+ /*'5' ( 53) |011011 */ {0x1b, 6},
+ /*'6' ( 54) |011100 */ {0x1c, 6},
+ /*'7' ( 55) |011101 */ {0x1d, 6},
+ /*'8' ( 56) |011110 */ {0x1e, 6},
+ /*'9' ( 57) |011111 */ {0x1f, 6},
+ /*':' ( 58) |1011100 */ {0x5c, 7},
+ /*';' ( 59) |11111011 */ {0xfb, 8},
+ /*'<' ( 60) |11111111|1111100 */ {0x7ffc,15},
+ /*'=' ( 61) |100000 */ {0x20, 6},
+ /*'>' ( 62) |11111111|1011 */ {0xffb,12},
+ /*'?' ( 63) |11111111|00 */ {0x3fc,10},
+ /*'@' ( 64) |11111111|11010 */ {0x1ffa,13},
+ /*'A' ( 65) |100001 */ {0x21, 6},
+ /*'B' ( 66) |1011101 */ {0x5d, 7},
+ /*'C' ( 67) |1011110 */ {0x5e, 7},
+ /*'D' ( 68) |1011111 */ {0x5f, 7},
+ /*'E' ( 69) |1100000 */ {0x60, 7},
+ /*'F' ( 70) |1100001 */ {0x61, 7},
+ /*'G' ( 71) |1100010 */ {0x62, 7},
+ /*'H' ( 72) |1100011 */ {0x63, 7},
+ /*'I' ( 73) |1100100 */ {0x64, 7},
+ /*'J' ( 74) |1100101 */ {0x65, 7},
+ /*'K' ( 75) |1100110 */ {0x66, 7},
+ /*'L' ( 76) |1100111 */ {0x67, 7},
+ /*'M' ( 77) |1101000 */ {0x68, 7},
+ /*'N' ( 78) |1101001 */ {0x69, 7},
+ /*'O' ( 79) |1101010 */ {0x6a, 7},
+ /*'P' ( 80) |1101011 */ {0x6b, 7},
+ /*'Q' ( 81) |1101100 */ {0x6c, 7},
+ /*'R' ( 82) |1101101 */ {0x6d, 7},
+ /*'S' ( 83) |1101110 */ {0x6e, 7},
+ /*'T' ( 84) |1101111 */ {0x6f, 7},
+ /*'U' ( 85) |1110000 */ {0x70, 7},
+ /*'V' ( 86) |1110001 */ {0x71, 7},
+ /*'W' ( 87) |1110010 */ {0x72, 7},
+ /*'X' ( 88) |11111100 */ {0xfc, 8},
+ /*'Y' ( 89) |1110011 */ {0x73, 7},
+ /*'Z' ( 90) |11111101 */ {0xfd, 8},
+ /*'[' ( 91) |11111111|11011 */ {0x1ffb,13},
+ /*'\' ( 92) |11111111|11111110|000 */ {0x7fff0,19},
+ /*']' ( 93) |11111111|11100 */ {0x1ffc,13},
+ /*'^' ( 94) |11111111|111100 */ {0x3ffc,14},
+ /*'_' ( 95) |100010 */ {0x22, 6},
+ /*'`' ( 96) |11111111|1111101 */ {0x7ffd,15},
+ /*'a' ( 97) |00011 */ {0x3, 5},
+ /*'b' ( 98) |100011 */ {0x23, 6},
+ /*'c' ( 99) |00100 */ {0x4, 5},
+ /*'d' (100) |100100 */ {0x24, 6},
+ /*'e' (101) |00101 */ {0x5, 5},
+ /*'f' (102) |100101 */ {0x25, 6},
+ /*'g' (103) |100110 */ {0x26, 6},
+ /*'h' (104) |100111 */ {0x27, 6},
+ /*'i' (105) |00110 */ {0x6, 5},
+ /*'j' (106) |1110100 */ {0x74, 7},
+ /*'k' (107) |1110101 */ {0x75, 7},
+ /*'l' (108) |101000 */ {0x28, 6},
+ /*'m' (109) |101001 */ {0x29, 6},
+ /*'n' (110) |101010 */ {0x2a, 6},
+ /*'o' (111) |00111 */ {0x7, 5},
+ /*'p' (112) |101011 */ {0x2b, 6},
+ /*'q' (113) |1110110 */ {0x76, 7},
+ /*'r' (114) |101100 */ {0x2c, 6},
+ /*'s' (115) |01000 */ {0x8, 5},
+ /*'t' (116) |01001 */ {0x9, 5},
+ /*'u' (117) |101101 */ {0x2d, 6},
+ /*'v' (118) |1110111 */ {0x77, 7},
+ /*'w' (119) |1111000 */ {0x78, 7},
+ /*'x' (120) |1111001 */ {0x79, 7},
+ /*'y' (121) |1111010 */ {0x7a, 7},
+ /*'z' (122) |1111011 */ {0x7b, 7},
+ /*'{' (123) |11111111|1111110 */ {0x7ffe,15},
+ /*'|' (124) |11111111|100 */ {0x7fc,11},
+ /*'}' (125) |11111111|111101 */ {0x3ffd,14},
+ /*'~' (126) |11111111|11101 */ {0x1ffd,13},
+ /* (127) |11111111|11111111|11111111|1100 */ {0xffffffc,28},
+ /* (128) |11111111|11111110|0110 */ {0xfffe6,20},
+ /* (129) |11111111|11111111|010010 */ {0x3fffd2,22},
+ /* (130) |11111111|11111110|0111 */ {0xfffe7,20},
+ /* (131) |11111111|11111110|1000 */ {0xfffe8,20},
+ /* (132) |11111111|11111111|010011 */ {0x3fffd3,22},
+ /* (133) |11111111|11111111|010100 */ {0x3fffd4,22},
+ /* (134) |11111111|11111111|010101 */ {0x3fffd5,22},
+ /* (135) |11111111|11111111|1011001 */ {0x7fffd9,23},
+ /* (136) |11111111|11111111|010110 */ {0x3fffd6,22},
+ /* (137) |11111111|11111111|1011010 */ {0x7fffda,23},
+ /* (138) |11111111|11111111|1011011 */ {0x7fffdb,23},
+ /* (139) |11111111|11111111|1011100 */ {0x7fffdc,23},
+ /* (140) |11111111|11111111|1011101 */ {0x7fffdd,23},
+ /* (141) |11111111|11111111|1011110 */ {0x7fffde,23},
+ /* (142) |11111111|11111111|11101011 */ {0xffffeb,24},
+ /* (143) |11111111|11111111|1011111 */ {0x7fffdf,23},
+ /* (144) |11111111|11111111|11101100 */ {0xffffec,24},
+ /* (145) |11111111|11111111|11101101 */ {0xffffed,24},
+ /* (146) |11111111|11111111|010111 */ {0x3fffd7,22},
+ /* (147) |11111111|11111111|1100000 */ {0x7fffe0,23},
+ /* (148) |11111111|11111111|11101110 */ {0xffffee,24},
+ /* (149) |11111111|11111111|1100001 */ {0x7fffe1,23},
+ /* (150) |11111111|11111111|1100010 */ {0x7fffe2,23},
+ /* (151) |11111111|11111111|1100011 */ {0x7fffe3,23},
+ /* (152) |11111111|11111111|1100100 */ {0x7fffe4,23},
+ /* (153) |11111111|11111110|11100 */ {0x1fffdc,21},
+ /* (154) |11111111|11111111|011000 */ {0x3fffd8,22},
+ /* (155) |11111111|11111111|1100101 */ {0x7fffe5,23},
+ /* (156) |11111111|11111111|011001 */ {0x3fffd9,22},
+ /* (157) |11111111|11111111|1100110 */ {0x7fffe6,23},
+ /* (158) |11111111|11111111|1100111 */ {0x7fffe7,23},
+ /* (159) |11111111|11111111|11101111 */ {0xffffef,24},
+ /* (160) |11111111|11111111|011010 */ {0x3fffda,22},
+ /* (161) |11111111|11111110|11101 */ {0x1fffdd,21},
+ /* (162) |11111111|11111110|1001 */ {0xfffe9,20},
+ /* (163) |11111111|11111111|011011 */ {0x3fffdb,22},
+ /* (164) |11111111|11111111|011100 */ {0x3fffdc,22},
+ /* (165) |11111111|11111111|1101000 */ {0x7fffe8,23},
+ /* (166) |11111111|11111111|1101001 */ {0x7fffe9,23},
+ /* (167) |11111111|11111110|11110 */ {0x1fffde,21},
+ /* (168) |11111111|11111111|1101010 */ {0x7fffea,23},
+ /* (169) |11111111|11111111|011101 */ {0x3fffdd,22},
+ /* (170) |11111111|11111111|011110 */ {0x3fffde,22},
+ /* (171) |11111111|11111111|11110000 */ {0xfffff0,24},
+ /* (172) |11111111|11111110|11111 */ {0x1fffdf,21},
+ /* (173) |11111111|11111111|011111 */ {0x3fffdf,22},
+ /* (174) |11111111|11111111|1101011 */ {0x7fffeb,23},
+ /* (175) |11111111|11111111|1101100 */ {0x7fffec,23},
+ /* (176) |11111111|11111111|00000 */ {0x1fffe0,21},
+ /* (177) |11111111|11111111|00001 */ {0x1fffe1,21},
+ /* (178) |11111111|11111111|100000 */ {0x3fffe0,22},
+ /* (179) |11111111|11111111|00010 */ {0x1fffe2,21},
+ /* (180) |11111111|11111111|1101101 */ {0x7fffed,23},
+ /* (181) |11111111|11111111|100001 */ {0x3fffe1,22},
+ /* (182) |11111111|11111111|1101110 */ {0x7fffee,23},
+ /* (183) |11111111|11111111|1101111 */ {0x7fffef,23},
+ /* (184) |11111111|11111110|1010 */ {0xfffea,20},
+ /* (185) |11111111|11111111|100010 */ {0x3fffe2,22},
+ /* (186) |11111111|11111111|100011 */ {0x3fffe3,22},
+ /* (187) |11111111|11111111|100100 */ {0x3fffe4,22},
+ /* (188) |11111111|11111111|1110000 */ {0x7ffff0,23},
+ /* (189) |11111111|11111111|100101 */ {0x3fffe5,22},
+ /* (190) |11111111|11111111|100110 */ {0x3fffe6,22},
+ /* (191) |11111111|11111111|1110001 */ {0x7ffff1,23},
+ /* (192) |11111111|11111111|11111000|00 */ {0x3ffffe0,26},
+ /* (193) |11111111|11111111|11111000|01 */ {0x3ffffe1,26},
+ /* (194) |11111111|11111110|1011 */ {0xfffeb,20},
+ /* (195) |11111111|11111110|001 */ {0x7fff1,19},
+ /* (196) |11111111|11111111|100111 */ {0x3fffe7,22},
+ /* (197) |11111111|11111111|1110010 */ {0x7ffff2,23},
+ /* (198) |11111111|11111111|101000 */ {0x3fffe8,22},
+ /* (199) |11111111|11111111|11110110|0 */ {0x1ffffec,25},
+ /* (200) |11111111|11111111|11111000|10 */ {0x3ffffe2,26},
+ /* (201) |11111111|11111111|11111000|11 */ {0x3ffffe3,26},
+ /* (202) |11111111|11111111|11111001|00 */ {0x3ffffe4,26},
+ /* (203) |11111111|11111111|11111011|110 */ {0x7ffffde,27},
+ /* (204) |11111111|11111111|11111011|111 */ {0x7ffffdf,27},
+ /* (205) |11111111|11111111|11111001|01 */ {0x3ffffe5,26},
+ /* (206) |11111111|11111111|11110001 */ {0xfffff1,24},
+ /* (207) |11111111|11111111|11110110|1 */ {0x1ffffed,25},
+ /* (208) |11111111|11111110|010 */ {0x7fff2,19},
+ /* (209) |11111111|11111111|00011 */ {0x1fffe3,21},
+ /* (210) |11111111|11111111|11111001|10 */ {0x3ffffe6,26},
+ /* (211) |11111111|11111111|11111100|000 */ {0x7ffffe0,27},
+ /* (212) |11111111|11111111|11111100|001 */ {0x7ffffe1,27},
+ /* (213) |11111111|11111111|11111001|11 */ {0x3ffffe7,26},
+ /* (214) |11111111|11111111|11111100|010 */ {0x7ffffe2,27},
+ /* (215) |11111111|11111111|11110010 */ {0xfffff2,24},
+ /* (216) |11111111|11111111|00100 */ {0x1fffe4,21},
+ /* (217) |11111111|11111111|00101 */ {0x1fffe5,21},
+ /* (218) |11111111|11111111|11111010|00 */ {0x3ffffe8,26},
+ /* (219) |11111111|11111111|11111010|01 */ {0x3ffffe9,26},
+ /* (220) |11111111|11111111|11111111|1101 */ {0xffffffd,28},
+ /* (221) |11111111|11111111|11111100|011 */ {0x7ffffe3,27},
+ /* (222) |11111111|11111111|11111100|100 */ {0x7ffffe4,27},
+ /* (223) |11111111|11111111|11111100|101 */ {0x7ffffe5,27},
+ /* (224) |11111111|11111110|1100 */ {0xfffec,20},
+ /* (225) |11111111|11111111|11110011 */ {0xfffff3,24},
+ /* (226) |11111111|11111110|1101 */ {0xfffed,20},
+ /* (227) |11111111|11111111|00110 */ {0x1fffe6,21},
+ /* (228) |11111111|11111111|101001 */ {0x3fffe9,22},
+ /* (229) |11111111|11111111|00111 */ {0x1fffe7,21},
+ /* (230) |11111111|11111111|01000 */ {0x1fffe8,21},
+ /* (231) |11111111|11111111|1110011 */ {0x7ffff3,23},
+ /* (232) |11111111|11111111|101010 */ {0x3fffea,22},
+ /* (233) |11111111|11111111|101011 */ {0x3fffeb,22},
+ /* (234) |11111111|11111111|11110111|0 */ {0x1ffffee,25},
+ /* (235) |11111111|11111111|11110111|1 */ {0x1ffffef,25},
+ /* (236) |11111111|11111111|11110100 */ {0xfffff4,24},
+ /* (237) |11111111|11111111|11110101 */ {0xfffff5,24},
+ /* (238) |11111111|11111111|11111010|10 */ {0x3ffffea,26},
+ /* (239) |11111111|11111111|1110100 */ {0x7ffff4,23},
+ /* (240) |11111111|11111111|11111010|11 */ {0x3ffffeb,26},
+ /* (241) |11111111|11111111|11111100|110 */ {0x7ffffe6,27},
+ /* (242) |11111111|11111111|11111011|00 */ {0x3ffffec,26},
+ /* (243) |11111111|11111111|11111011|01 */ {0x3ffffed,26},
+ /* (244) |11111111|11111111|11111100|111 */ {0x7ffffe7,27},
+ /* (245) |11111111|11111111|11111101|000 */ {0x7ffffe8,27},
+ /* (246) |11111111|11111111|11111101|001 */ {0x7ffffe9,27},
+ /* (247) |11111111|11111111|11111101|010 */ {0x7ffffea,27},
+ /* (248) |11111111|11111111|11111101|011 */ {0x7ffffeb,27},
+ /* (249) |11111111|11111111|11111111|1110 */ {0xffffffe,28},
+ /* (250) |11111111|11111111|11111101|100 */ {0x7ffffec,27},
+ /* (251) |11111111|11111111|11111101|101 */ {0x7ffffed,27},
+ /* (252) |11111111|11111111|11111101|110 */ {0x7ffffee,27},
+ /* (253) |11111111|11111111|11111101|111 */ {0x7ffffef,27},
+ /* (254) |11111111|11111111|11111110|000 */ {0x7fffff0,27},
+ /* (255) |11111111|11111111|11111011|10 */ {0x3ffffee,26},
+ /*EOS (256) |11111111|11111111|11111111|111111 */ {0x3fffffff,30},
+ };
+
+ static final int[][] LCCODES = new int[CODES.length][];
+
+ // Huffman decode tree stored in a flattened char array for good
+ // locality of reference.
+ static final char[] tree;
+ static final char[] rowsym;
+ static final byte[] rowbits;
+
+ // Build the Huffman lookup tree and LC TABLE
+ static
+ {
+ System.arraycopy(CODES,0,LCCODES,0,CODES.length);
+ for (int i='A';i<='Z';i++)
+ LCCODES[i]=LCCODES['a'+i-'A'];
+
+ int r=0;
+ for (int i=0;i 8)
+ {
+ len -= 8;
+ int i = ((code >>> len) & 0xFF);
+
+ int t=current*256+i;
+ current = tree[t];
+ if (current == 0)
+ {
+ tree[t] = (char)++r;
+ current=r;
+ }
+ }
+
+ int terminal = ++r;
+ rowsym[r]=(char)sym;
+ int b = len & 0x07;
+ int terminalBits = b == 0?8:b;
+
+ rowbits[r]=(byte)terminalBits;
+ int shift = 8 - len;
+ int start = current*256 + ((code << shift) & 0xFF);
+ int end = start + (1<= 8)
+ {
+ int c = (current >>> (bits - 8)) & 0xFF;
+ node = tree[node*256+c];
+ if (rowbits[node]!=0)
+ {
+ // terminal node
+ out.append(rowsym[node]);
+ bits -= rowbits[node];
+ node = 0;
+ }
+ else
+ {
+ // non-terminal node
+ bits -= 8;
+ }
+ }
+ }
+
+ while (bits > 0)
+ {
+ int c = (current << (8 - bits)) & 0xFF;
+ node = tree[node*256+c];
+ if (rowbits[node]==0 || rowbits[node] > bits)
+ break;
+
+ if (rowbits[node]==0)
+ throw new IllegalStateException();
+
+ out.append(rowsym[node]);
+ bits -= rowbits[node];
+ node = 0;
+ }
+
+ return out.toString();
+ }
+
+ public static int octetsNeeded(String s)
+ {
+ return octetsNeeded(CODES,s);
+ }
+
+ public static void encode(ByteBuffer buffer,String s)
+ {
+ encode(CODES,buffer,s);
+ }
+
+ public static int octetsNeededLC(String s)
+ {
+ return octetsNeeded(LCCODES,s);
+ }
+
+ public static void encodeLC(ByteBuffer buffer, String s)
+ {
+ encode(LCCODES,buffer,s);
+ }
+
+ private static int octetsNeeded(final int[][] table,String s)
+ {
+ int needed=0;
+ int len = s.length();
+ for (int i=0;i=128 || c<' ')
+ throw new IllegalArgumentException();
+ needed += table[c][1];
+ }
+
+ return (needed+7) / 8;
+ }
+
+ private static void encode(final int[][] table,ByteBuffer buffer,String s)
+ {
+ long current = 0;
+ int n = 0;
+
+ byte[] array = buffer.array();
+ int p=buffer.arrayOffset()+buffer.position();
+
+ int len = s.length();
+ for (int i=0;i=128 || c<' ')
+ throw new IllegalArgumentException();
+ int code = table[c][0];
+ int bits = table[c][1];
+
+ current <<= bits;
+ current |= code;
+ n += bits;
+
+ while (n >= 8)
+ {
+ n -= 8;
+ array[p++]=(byte)(current >> n);
+ }
+ }
+
+ if (n > 0)
+ {
+ current <<= (8 - n);
+ current |= (0xFF >>> n);
+ array[p++]=(byte)current;
+ }
+
+ buffer.position(p-buffer.arrayOffset());
+ }
+
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java
new file mode 100644
index 0000000000..1db32e3a1e
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java
@@ -0,0 +1,184 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.http2.hpack;
+
+
+import org.eclipse.jetty.http.BadMessageException;
+import org.eclipse.jetty.http.HostPortHttpField;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MetaData;
+
+public class MetaDataBuilder
+{
+ private final int _maxSize;
+ private int _size;
+ private int _status;
+ private String _method;
+ private HttpScheme _scheme;
+ private HostPortHttpField _authority;
+ private String _path;
+ private long _contentLength=Long.MIN_VALUE;
+
+ private HttpFields _fields = new HttpFields(10);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param maxHeadersSize The maximum size of the headers, expressed as total name and value characters.
+ */
+ MetaDataBuilder(int maxHeadersSize)
+ {
+ _maxSize=maxHeadersSize;
+ }
+
+ /** Get the maxSize.
+ * @return the maxSize
+ */
+ public int getMaxSize()
+ {
+ return _maxSize;
+ }
+
+ /** Get the size.
+ * @return the current size in bytes
+ */
+ public int getSize()
+ {
+ return _size;
+ }
+
+ public void emit(HttpField field)
+ {
+ int field_size = field.getName().length()+field.getValue().length();
+ _size+=field_size;
+ if (_size>_maxSize)
+ throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413,"Header size "+_size+">"+_maxSize);
+
+ if (field instanceof StaticTableHttpField)
+ {
+ StaticTableHttpField value = (StaticTableHttpField)field;
+ switch(field.getHeader())
+ {
+ case C_STATUS:
+ _status=(Integer)value.getStaticValue();
+ break;
+
+ case C_METHOD:
+ _method=field.getValue();
+ break;
+
+ case C_SCHEME:
+ _scheme = (HttpScheme)value.getStaticValue();
+ break;
+
+ default:
+ throw new IllegalArgumentException(field.getName());
+ }
+ }
+ else if (field.getHeader()!=null)
+ {
+ switch(field.getHeader())
+ {
+ case C_STATUS:
+ _status=field.getIntValue();
+ break;
+
+ case C_METHOD:
+ _method=field.getValue();
+ break;
+
+ case C_SCHEME:
+ _scheme = HttpScheme.CACHE.get(field.getValue());
+ break;
+
+ case C_AUTHORITY:
+ _authority=(field instanceof HostPortHttpField)?((HostPortHttpField)field):new AuthorityHttpField(field.getValue());
+ break;
+
+ case HOST:
+ // :authority fields must come first. If we have one, ignore the host header as far as authority goes.
+ if (_authority==null)
+ _authority=(field instanceof HostPortHttpField)?((HostPortHttpField)field):new AuthorityHttpField(field.getValue());
+ _fields.add(field);
+ break;
+
+ case C_PATH:
+ _path = field.getValue();
+ break;
+
+ case CONTENT_LENGTH:
+ _contentLength = field.getLongValue();
+ _fields.add(field);
+ break;
+
+ default:
+ if (field.getName().charAt(0)!=':')
+ _fields.add(field);
+ }
+ }
+ else
+ {
+ if (field.getName().charAt(0)!=':')
+ _fields.add(field);
+ }
+ }
+
+ public MetaData build()
+ {
+ try
+ {
+ HttpFields fields = _fields;
+ _fields = new HttpFields(Math.max(10,fields.size()+5));
+
+ if (_method!=null)
+ return new MetaData.Request(_method,_scheme,_authority,_path,HttpVersion.HTTP_2,fields,_contentLength);
+ if (_status!=0)
+ return new MetaData.Response(HttpVersion.HTTP_2,_status,fields,_contentLength);
+ return new MetaData(HttpVersion.HTTP_2,fields,_contentLength);
+ }
+ finally
+ {
+ _status=0;
+ _method=null;
+ _scheme=null;
+ _authority=null;
+ _path=null;
+ _size=0;
+ _contentLength=Long.MIN_VALUE;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Check that the max size will not be exceeded.
+ * @param length the length
+ * @param huffman the huffman name
+ */
+ public void checkSize(int length, boolean huffman)
+ {
+ // Apply a huffman fudge factor
+ if (huffman)
+ length=(length*4)/3;
+ if ((_size+length)>_maxSize)
+ throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413,"Header size "+(_size+length)+">"+_maxSize);
+ }
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/NBitInteger.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/NBitInteger.java
new file mode 100644
index 0000000000..5e991173dc
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/NBitInteger.java
@@ -0,0 +1,151 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http2.hpack;
+
+import java.nio.ByteBuffer;
+
+public class NBitInteger
+{
+ public static int octectsNeeded(int n,int i)
+ {
+ if (n==8)
+ {
+ int nbits = 0xFF;
+ i=i-nbits;
+ if (i<0)
+ return 1;
+ if (i==0)
+ return 2;
+ int lz=Integer.numberOfLeadingZeros(i);
+ int log=32-lz;
+ return 1+(log+6)/7;
+ }
+
+ int nbits = 0xFF >>> (8 - n);
+ i=i-nbits;
+ if (i<0)
+ return 0;
+ if (i==0)
+ return 1;
+ int lz=Integer.numberOfLeadingZeros(i);
+ int log=32-lz;
+ return (log+6)/7;
+ }
+
+ public static void encode(ByteBuffer buf, int n, int i)
+ {
+ if (n==8)
+ {
+ if (i < 0xFF)
+ {
+ buf.put((byte)i);
+ }
+ else
+ {
+ buf.put((byte)0xFF);
+
+ int length = i - 0xFF;
+ while (true)
+ {
+ if ((length & ~0x7F) == 0)
+ {
+ buf.put((byte)length);
+ return;
+ }
+ else
+ {
+ buf.put((byte)((length & 0x7F) | 0x80));
+ length >>>= 7;
+ }
+ }
+ }
+ }
+ else
+ {
+ int p=buf.position()-1;
+ int bits = 0xFF >>> (8 - n);
+
+ if (i < bits)
+ {
+ buf.put(p,(byte)((buf.get(p)&~bits)|i));
+ }
+ else
+ {
+ buf.put(p,(byte)(buf.get(p)|bits));
+
+ int length = i - bits;
+ while (true)
+ {
+ if ((length & ~0x7F) == 0)
+ {
+ buf.put((byte)length);
+ return;
+ }
+ else
+ {
+ buf.put((byte)((length & 0x7F) | 0x80));
+ length >>>= 7;
+ }
+ }
+ }
+ }
+ }
+
+ public static int decode(ByteBuffer buffer, int n)
+ {
+ if (n==8)
+ {
+ int nbits = 0xFF;
+
+ int i=buffer.get()&0xff;
+
+ if (i == nbits)
+ {
+ int m=1;
+ int b;
+ do
+ {
+ b = 0xff&buffer.get();
+ i = i + (b&127) * m;
+ m = m*128;
+ }
+ while ((b&128) == 128);
+ }
+ return i;
+ }
+
+ int nbits = 0xFF >>> (8 - n);
+
+ int i=buffer.get(buffer.position()-1)&nbits;
+
+ if (i == nbits)
+ {
+ int m=1;
+ int b;
+ do
+ {
+ b = 0xff&buffer.get();
+ i = i + (b&127) * m;
+ m = m*128;
+ }
+ while ((b&128) == 128);
+ }
+ return i;
+ }
+}
diff --git a/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/StaticTableHttpField.java b/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/StaticTableHttpField.java
new file mode 100644
index 0000000000..275ab53629
--- /dev/null
+++ b/org.eclipse.jetty.http/src/org/eclipse/jetty/http2/hpack/StaticTableHttpField.java
@@ -0,0 +1,61 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.http2.hpack;
+
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpHeader;
+
+/* ------------------------------------------------------------ */
+public class StaticTableHttpField extends HttpField
+{
+ private final Object _value;
+
+ public StaticTableHttpField(HttpHeader header, String name, String valueString, Object value)
+ {
+ super(header,name,valueString);
+ if (value==null)
+ throw new IllegalArgumentException();
+ _value=value;
+ }
+
+ public StaticTableHttpField(HttpHeader header,String valueString, Object value)
+ {
+ this (header,header.asString(),valueString, value);
+ }
+
+ public StaticTableHttpField(String name, String valueString, Object value)
+ {
+ super(name,valueString);
+ if (value==null)
+ throw new IllegalArgumentException();
+ _value=value;
+ }
+
+ public Object getStaticValue()
+ {
+ return _value;
+ }
+
+ @Override
+ public String toString()
+ {
+ return super.toString()+"(evaluated)";
+ }
+}
\ No newline at end of file
diff --git a/org.idempiere.customize-feature/.project b/org.idempiere.customize-feature/.project
new file mode 100644
index 0000000000..14d94e9777
--- /dev/null
+++ b/org.idempiere.customize-feature/.project
@@ -0,0 +1,17 @@
+
+
+ org.idempiere.customize-feature
+
+
+
+
+
+ org.eclipse.pde.FeatureBuilder
+
+
+
+
+
+ org.eclipse.pde.FeatureNature
+
+
diff --git a/org.idempiere.customize-feature/build.properties b/org.idempiere.customize-feature/build.properties
new file mode 100644
index 0000000000..64f93a9f0b
--- /dev/null
+++ b/org.idempiere.customize-feature/build.properties
@@ -0,0 +1 @@
+bin.includes = feature.xml
diff --git a/org.idempiere.customize-feature/feature.xml b/org.idempiere.customize-feature/feature.xml
new file mode 100644
index 0000000000..c2a52ec265
--- /dev/null
+++ b/org.idempiere.customize-feature/feature.xml
@@ -0,0 +1,19 @@
+
+
+
+
+ [Enter Feature Description here.]
+
+
+
+ [Enter Copyright Description here.]
+
+
+
+ [Enter License Description here.]
+
+
+
diff --git a/org.idempiere.eclipse.platform-feature/feature.xml b/org.idempiere.eclipse.platform-feature/feature.xml
index f0b018d856..6a9b7ed801 100644
--- a/org.idempiere.eclipse.platform-feature/feature.xml
+++ b/org.idempiere.eclipse.platform-feature/feature.xml
@@ -138,14 +138,14 @@
unpack="false"/>
diff --git a/org.idempiere.fitnesse.fixture/.settings/org.eclipse.jdt.core.prefs b/org.idempiere.fitnesse.fixture/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index f42de363af..0000000000
--- a/org.idempiere.fitnesse.fixture/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,7 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
-org.eclipse.jdt.core.compiler.compliance=1.7
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.7
diff --git a/org.idempiere.fitnesse.fixture/META-INF/MANIFEST.MF b/org.idempiere.fitnesse.fixture/META-INF/MANIFEST.MF
index e9aa6ef0d2..3b2fc660c9 100644
--- a/org.idempiere.fitnesse.fixture/META-INF/MANIFEST.MF
+++ b/org.idempiere.fitnesse.fixture/META-INF/MANIFEST.MF
@@ -5,7 +5,7 @@ Bundle-SymbolicName: org.idempiere.fitnesse.fixture
Bundle-Version: 3.1.0.qualifier
Bundle-Activator: org.idempiere.fitnesse.fixture.Activator
Bundle-ActivationPolicy: lazy
-Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: fit,
fit.decorator,
fit.decorator.exceptions,
diff --git a/org.idempiere.fitnesse.server/.classpath b/org.idempiere.fitnesse.server/.classpath
index 62b0413eb0..455f2a5f1e 100644
--- a/org.idempiere.fitnesse.server/.classpath
+++ b/org.idempiere.fitnesse.server/.classpath
@@ -1,5 +1,6 @@
+
@@ -7,7 +8,6 @@
-
diff --git a/org.idempiere.fitnesse.server/.settings/org.eclipse.jdt.core.prefs b/org.idempiere.fitnesse.server/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index f42de363af..0000000000
--- a/org.idempiere.fitnesse.server/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,7 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
-org.eclipse.jdt.core.compiler.compliance=1.7
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.7
diff --git a/org.idempiere.fitnesse.server/META-INF/MANIFEST.MF b/org.idempiere.fitnesse.server/META-INF/MANIFEST.MF
index 75413dce0b..3011a52d4b 100644
--- a/org.idempiere.fitnesse.server/META-INF/MANIFEST.MF
+++ b/org.idempiere.fitnesse.server/META-INF/MANIFEST.MF
@@ -5,7 +5,7 @@ Bundle-SymbolicName: org.idempiere.fitnesse.server
Bundle-Version: 3.1.0.qualifier
Bundle-Activator: org.idempiere.fitnesse.server.Activator
Bundle-ActivationPolicy: lazy
-Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: javax.servlet;version="3.0.0",
javax.servlet.http;version="3.0.0",
org.adempiere.base,
@@ -18,8 +18,8 @@ Bundle-ClassPath: .,
lib/fitnesse.jar,
lib/log4j-1.2.16.jar,
lib/fitlibraryweb-2.0.jar,
- lib/selenium-server-standalone-2.35.0.jar,
- lib/phantomjsdriver-1.0.3.jar
+ lib/phantomjsdriver-1.0.3.jar,
+ lib/selenium-server-standalone-2.39.0.jar
Web-ContextPath: fitnesse
Export-Package: fit,
fit.decorator,
diff --git a/org.idempiere.fitnesse.server/build.properties b/org.idempiere.fitnesse.server/build.properties
index 2aa4dbabef..03b5d0fbbf 100644
--- a/org.idempiere.fitnesse.server/build.properties
+++ b/org.idempiere.fitnesse.server/build.properties
@@ -8,4 +8,4 @@ bin.includes = META-INF/,\
lib/fitlibraryweb-2.0.jar,\
WEB-INF/,\
lib/phantomjsdriver-1.0.3.jar,\
- lib/selenium-server-standalone-2.35.0.jar
+ lib/selenium-server-standalone-2.39.0.jar
diff --git a/org.idempiere.fitnesse.server/copyjars.xml b/org.idempiere.fitnesse.server/copyjars.xml
index f7c22ea584..a1b71cfe20 100644
--- a/org.idempiere.fitnesse.server/copyjars.xml
+++ b/org.idempiere.fitnesse.server/copyjars.xml
@@ -1,16 +1,16 @@
-
-
-
+
+
+
-
-
-
+ in case use lib version, we can download at maven, it's small ${url.maven2.lib}/maven2/org/seleniumhq/selenium/selenium-server/2.35.0/selenium-server-2.35.0.jar-->
+
+
+
diff --git a/org.idempiere.fitrecorder/.settings/org.eclipse.jdt.core.prefs b/org.idempiere.fitrecorder/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index f42de363af..0000000000
--- a/org.idempiere.fitrecorder/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,7 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
-org.eclipse.jdt.core.compiler.compliance=1.7
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.7
diff --git a/org.idempiere.fitrecorder/META-INF/MANIFEST.MF b/org.idempiere.fitrecorder/META-INF/MANIFEST.MF
index cf8d4c8cbe..ebff57bc5c 100644
--- a/org.idempiere.fitrecorder/META-INF/MANIFEST.MF
+++ b/org.idempiere.fitrecorder/META-INF/MANIFEST.MF
@@ -8,6 +8,6 @@ Import-Package: org.adempiere.base,
org.adempiere.plugin.utils,
org.compiere.model,
org.osgi.framework;version="1.3.0"
-Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Service-Component: plugin.xml
-Require-Bundle: org.adempiere.base;bundle-version="3.1.0"
+Require-Bundle: org.adempiere.base;bundle-version="0.0.0"
diff --git a/org.idempiere.hazelcast.service-feature/feature.xml b/org.idempiere.hazelcast.service-feature/feature.xml
index 911794f6e4..3d65c1b985 100644
--- a/org.idempiere.hazelcast.service-feature/feature.xml
+++ b/org.idempiere.hazelcast.service-feature/feature.xml
@@ -20,7 +20,7 @@
id="org.idempiere.hazelcast.service"
download-size="0"
install-size="0"
- version="3.1.0.qualifier"
+ version="0.0.0"
unpack="false"/>
-
-
+
+
diff --git a/org.idempiere.ui.zk.selenium/.settings/org.eclipse.jdt.core.prefs b/org.idempiere.ui.zk.selenium/.settings/org.eclipse.jdt.core.prefs
index 7341ab1683..0c68a61dca 100644
--- a/org.idempiere.ui.zk.selenium/.settings/org.eclipse.jdt.core.prefs
+++ b/org.idempiere.ui.zk.selenium/.settings/org.eclipse.jdt.core.prefs
@@ -1,11 +1,7 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.7
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.7
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/org.idempiere.ui.zk.selenium/.settings/org.eclipse.wst.common.project.facet.core.xml b/org.idempiere.ui.zk.selenium/.settings/org.eclipse.wst.common.project.facet.core.xml
index bc0009a455..f4ef8aa0a5 100644
--- a/org.idempiere.ui.zk.selenium/.settings/org.eclipse.wst.common.project.facet.core.xml
+++ b/org.idempiere.ui.zk.selenium/.settings/org.eclipse.wst.common.project.facet.core.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/org.idempiere.ui.zk.selenium/META-INF/MANIFEST.MF b/org.idempiere.ui.zk.selenium/META-INF/MANIFEST.MF
index 2dca3f845e..b5efaafb88 100644
--- a/org.idempiere.ui.zk.selenium/META-INF/MANIFEST.MF
+++ b/org.idempiere.ui.zk.selenium/META-INF/MANIFEST.MF
@@ -5,7 +5,7 @@ Bundle-SymbolicName: org.idempiere.ui.zk.selenium
Bundle-Version: 3.1.0.qualifier
Bundle-Activator: org.idempiere.ui.zk.selenium.fitnesse.Activator
Bundle-ActivationPolicy: lazy
-Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: fit,
fit.decorator,
fit.decorator.exceptions,
diff --git a/org.idempiere.webservices/.settings/org.eclipse.jdt.core.prefs b/org.idempiere.webservices/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 11f6e462df..0000000000
--- a/org.idempiere.webservices/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,7 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
-org.eclipse.jdt.core.compiler.compliance=1.7
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.7
diff --git a/org.idempiere.webservices/META-INF/MANIFEST.MF b/org.idempiere.webservices/META-INF/MANIFEST.MF
index 3936337876..0c12f71f6c 100644
--- a/org.idempiere.webservices/META-INF/MANIFEST.MF
+++ b/org.idempiere.webservices/META-INF/MANIFEST.MF
@@ -4,7 +4,7 @@ Bundle-Name: iDempiere Webservices
Bundle-SymbolicName: org.idempiere.webservices;singleton:=true
Bundle-Version: 3.1.0.qualifier
Bundle-Activator: org.idempiere.webservices.Activator
-Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Service-Component: OSGI-INF/ws_modelfactory.xml
Import-Package: javax.activation,
javax.mail.internet;version="1.5",
@@ -59,9 +59,9 @@ Import-Package: javax.activation,
org.apache.xerces.xs.datatypes;version="2.9.0",
org.apache.xml.serialize;version="2.9.0",
org.osgi.framework;version="1.6.0"
-Require-Bundle: org.adempiere.base;bundle-version="3.1.0",
- org.adempiere.ui;bundle-version="3.1.0",
- org.apache.ecs;bundle-version="3.1.0"
+Require-Bundle: org.adempiere.base;bundle-version="0.0.0",
+ org.adempiere.ui;bundle-version="0.0.0",
+ org.apache.ecs;bundle-version="0.0.0"
Bundle-ClassPath: .,
WEB-INF/lib/spring-osgi-extender-1.2.1.jar,
WEB-INF/lib/spring-aop-3.0.7.RELEASE.jar,
diff --git a/org.idempiere.webservices/copyjars.xml b/org.idempiere.webservices/copyjars.xml
index 9e6feb544f..07c9c9d47b 100644
--- a/org.idempiere.webservices/copyjars.xml
+++ b/org.idempiere.webservices/copyjars.xml
@@ -1,26 +1,26 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.idempiere.zk.extra/META-INF/MANIFEST.MF b/org.idempiere.zk.extra/META-INF/MANIFEST.MF
index 7e0191d751..d887767ad4 100644
--- a/org.idempiere.zk.extra/META-INF/MANIFEST.MF
+++ b/org.idempiere.zk.extra/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@ Bundle-ManifestVersion: 2
Bundle-Name: extra zk library
Bundle-SymbolicName: org.idempiere.zk.extra
Bundle-Version: 1.0.0.qualifier
-Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ClassPath: lib/calendar.jar,
lib/ckez.jar,
lib/gmapsz.jar,
diff --git a/org.idempiere.zk.extra/copyjars.xml b/org.idempiere.zk.extra/copyjars.xml
index 20e59329f0..2848fb09fb 100644
--- a/org.idempiere.zk.extra/copyjars.xml
+++ b/org.idempiere.zk.extra/copyjars.xml
@@ -1,10 +1,10 @@
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/org.slf4j.jdk14/META-INF/MANIFEST.MF b/org.slf4j.jdk14/META-INF/MANIFEST.MF
index 0f115b1331..f7b870fdda 100644
--- a/org.slf4j.jdk14/META-INF/MANIFEST.MF
+++ b/org.slf4j.jdk14/META-INF/MANIFEST.MF
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Name: SLF4J with JDK logger
Bundle-SymbolicName: org.slf4j.jdk14
Bundle-Version: 1.7.2.qualifier
-Fragment-Host: org.slf4j.api;bundle-version="1.7.2"
-Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Fragment-Host: org.slf4j.api;bundle-version="0.0.0"
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Export-Package: org.slf4j.impl;version=1.7.2
Import-Package: org.slf4j;version=1.7.2,
org.slf4j.spi;version=1.7.2,org.slf4j.helpers;version=1.7.2
diff --git a/org.slf4j.jdk14/copyjars.xml b/org.slf4j.jdk14/copyjars.xml
index ecc90a359f..2bceb023f2 100644
--- a/org.slf4j.jdk14/copyjars.xml
+++ b/org.slf4j.jdk14/copyjars.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/org.w3c.dom.fragment/.settings/org.eclipse.jdt.core.prefs b/org.w3c.dom.fragment/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index f42de363af..0000000000
--- a/org.w3c.dom.fragment/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,7 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
-org.eclipse.jdt.core.compiler.compliance=1.7
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.7
diff --git a/org.w3c.dom.fragment/META-INF/MANIFEST.MF b/org.w3c.dom.fragment/META-INF/MANIFEST.MF
index d3fb8ee3e9..7a8c4bdef3 100644
--- a/org.w3c.dom.fragment/META-INF/MANIFEST.MF
+++ b/org.w3c.dom.fragment/META-INF/MANIFEST.MF
@@ -4,7 +4,7 @@ Bundle-Name: W3c DOM Fragment
Bundle-SymbolicName: org.w3c.dom.fragment
Bundle-Version: 3.0.0.qualifier
Fragment-Host: system.bundle; extension:=framework
-Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ClassPath: batik-ext.jar,
.
Export-Package: org.w3c.dom,
diff --git a/org.w3c.dom.fragment/copyjars.xml b/org.w3c.dom.fragment/copyjars.xml
index 81fd55a561..5f3ec9b3ea 100644
--- a/org.w3c.dom.fragment/copyjars.xml
+++ b/org.w3c.dom.fragment/copyjars.xml
@@ -1,5 +1,5 @@
-
+