Merge remote-tracking branch 'origin/jetty-9.3.x'

This commit is contained in:
Greg Wilkins 2016-03-30 20:05:59 +11:00
commit 3590abc534
13 changed files with 942 additions and 124 deletions

View File

@ -171,7 +171,7 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent
} }
// PHP does not like multiple Cookie headers, coalesce into one. // PHP does not like multiple Cookie headers, coalesce into one.
List<String> cookies = proxyRequest.getHeaders().getValuesList(HttpHeader.COOKIE.asString()); List<String> cookies = proxyRequest.getHeaders().getValuesList(HttpHeader.COOKIE);
if (cookies.size() > 1) if (cookies.size() > 1)
{ {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();

View File

@ -32,7 +32,6 @@ import java.util.Set;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import org.eclipse.jetty.util.ArrayTernaryTrie; import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.Trie; import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
@ -50,6 +49,7 @@ import org.eclipse.jetty.util.log.Logger;
*/ */
public class HttpFields implements Iterable<HttpField> public class HttpFields implements Iterable<HttpField>
{ {
@Deprecated
public static final String __separators = ", \t"; public static final String __separators = ", \t";
private static final Logger LOG = Log.getLogger(HttpFields.class); private static final Logger LOG = Log.getLogger(HttpFields.class);
@ -244,11 +244,26 @@ public class HttpFields implements Iterable<HttpField>
} }
return null; return null;
} }
/** /**
* Get multi headers * Get multiple header of the same name
* *
* @return List the values * @return List the values
* @param header the header
*/
public List<String> getValuesList(HttpHeader header)
{
final List<String> 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 * @param name the case-insensitive field name
*/ */
public List<String> getValuesList(String name) public List<String> getValuesList(String name)
@ -260,6 +275,72 @@ public class HttpFields implements Iterable<HttpField>
return list; 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<String> 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<String> 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<String> 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<String> 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 * Get multi headers
* *
@ -325,6 +406,7 @@ public class HttpFields implements Iterable<HttpField>
* @param separators String of separators. * @param separators String of separators.
* @return Enumeration of the values, or null if no such header. * @return Enumeration of the values, or null if no such header.
*/ */
@Deprecated
public Enumeration<String> getValues(String name, final String separators) public Enumeration<String> getValues(String name, final String separators)
{ {
final Enumeration<String> e = getValues(name); final Enumeration<String> e = getValues(name);
@ -713,6 +795,28 @@ public class HttpFields implements Iterable<HttpField>
} }
} }
/**
* 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:
*
* <PRE>
*
* FieldName : Value ; param1=val1 ; param2=val2
*
* </PRE>
*
* @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 * 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: * value from the parameters and optionally populates a map with the parameters. For example:
@ -723,7 +827,7 @@ public class HttpFields implements Iterable<HttpField>
* *
* </PRE> * </PRE>
* *
* @param value The Field value, possibly with parameteres. * @param value The Field value, possibly with parameters.
* @param parameters A map to populate with the parameters, or null * @param parameters A map to populate with the parameters, or null
* @return The value. * @return The value.
*/ */
@ -752,8 +856,11 @@ public class HttpFields implements Iterable<HttpField>
return value.substring(0, i).trim(); return value.substring(0, i).trim();
} }
@Deprecated
private static final Float __one = new Float("1.0"); private static final Float __one = new Float("1.0");
@Deprecated
private static final Float __zero = new Float("0.0"); private static final Float __zero = new Float("0.0");
@Deprecated
private static final Trie<Float> __qualities = new ArrayTernaryTrie<>(); private static final Trie<Float> __qualities = new ArrayTernaryTrie<>();
static static
{ {
@ -775,6 +882,7 @@ public class HttpFields implements Iterable<HttpField>
__qualities.put("0.0", __zero); __qualities.put("0.0", __zero);
} }
@Deprecated
public static Float getQuality(String value) public static Float getQuality(String value)
{ {
if (value == null) return __zero; if (value == null) return __zero;
@ -816,53 +924,16 @@ public class HttpFields implements Iterable<HttpField>
* @param e Enumeration of values with quality parameters * @param e Enumeration of values with quality parameters
* @return values in quality order. * @return values in quality order.
*/ */
@Deprecated
public static List<String> qualityList(Enumeration<String> e) public static List<String> qualityList(Enumeration<String> e)
{ {
if (e == null || !e.hasMoreElements()) if (e == null || !e.hasMoreElements())
return Collections.emptyList(); return Collections.emptyList();
Object list = null; QuotedQualityCSV values = new QuotedQualityCSV();
Object qual = null; while(e.hasMoreElements())
values.addValue(e.nextElement());
// Assume list will be well ordered and just add nonzero return values.getValues();
while (e.hasMoreElements())
{
String v = e.nextElement();
Float q = getQuality(v);
if (q >= 0.001)
{
list = LazyList.add(list, v);
qual = LazyList.add(qual, q);
}
}
List<String> vl = LazyList.getList(list, false);
if (vl.size() < 2)
return vl;
List<Float> ql = LazyList.getList(qual, false);
// sort list with swaps
Float last = __zero;
for (int i = vl.size(); i-- > 0;)
{
Float q = ql.get(i);
if (last.compareTo(q) > 0)
{
String tmp = vl.get(i);
vl.set(i, vl.get(i + 1));
vl.set(i + 1, tmp);
ql.set(i, ql.get(i + 1));
ql.set(i + 1, q);
last = __zero;
i = vl.size();
continue;
}
last = q;
}
ql.clear();
return vl;
} }

View File

@ -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<String>
{
private enum State { VALUE, PARAM_NAME, PARAM_VALUE};
private final List<String> _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<String> getValues()
{
return _values;
}
@Override
public Iterator<String> 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<l;i++)
{
char c=s.charAt(i);
if (c=='"')
break;
}
if (i==l)
return s;
boolean quoted=true;
boolean sloshed=false;
StringBuffer buffer = new StringBuffer();
buffer.append(s,0,i);
i++;
for (;i<l;i++)
{
char c=s.charAt(i);
if (quoted)
{
if (sloshed)
{
buffer.append(c);
sloshed=false;
}
else if (c=='"')
quoted=false;
else if (c=='\\')
sloshed=true;
else
buffer.append(c);
}
else if (c=='"')
quoted=true;
else
buffer.append(c);
}
return buffer.toString();
}
}

View File

@ -0,0 +1,252 @@
//
// ========================================================================
// 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 quality values
* in accordance with RFC7230 and RFC7231.
* Values are returned sorted in quality order, with OWS and the
* quality parameters removed.
* @see "https://tools.ietf.org/html/rfc7230#section-3.2.6"
* @see "https://tools.ietf.org/html/rfc7230#section-7"
* @see "https://tools.ietf.org/html/rfc7231#section-5.3.1"
*/
public class QuotedQualityCSV implements Iterable<String>
{
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<String> _values = new ArrayList<>();
private final List<Double> _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<String> getValues()
{
if (!_sorted)
sort();
return _values;
}
@Override
public Iterator<String> 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()<len))
{
_values.set(i, _values.get(i + 1));
_values.set(i + 1, v);
_quality.set(i, _quality.get(i + 1));
_quality.set(i + 1, q);
last = ZERO;
len=0;
i = _values.size();
continue;
}
last=q;
len=v.length();
}
}
}

View File

@ -0,0 +1,103 @@
//
// ========================================================================
// 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 static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
public class QuotedCSVTest
{
@Test
public void testOWS()
{
QuotedCSV values = new QuotedCSV();
values.addValue(" value 0.5 ; p = v ; q =0.5 , value 1.0 ");
Assert.assertThat(values,Matchers.contains(
"value 0.5;p=v;q=0.5",
"value 1.0"));
}
@Test
public void testEmpty()
{
QuotedCSV values = new QuotedCSV();
values.addValue(",aaaa, , bbbb ,,cccc,");
Assert.assertThat(values,Matchers.contains(
"aaaa",
"bbbb",
"cccc"));
}
@Test
public void testQuoted()
{
QuotedCSV values = new QuotedCSV();
values.addValue("A;p=\"v\",B,\"C, D\"");
Assert.assertThat(values,Matchers.contains(
"A;p=\"v\"",
"B",
"\"C, D\""));
}
@Test
public void testOpenQuote()
{
QuotedCSV values = new QuotedCSV();
values.addValue("value;p=\"v");
Assert.assertThat(values,Matchers.contains(
"value;p=\"v"));
}
@Test
public void testQuotedNoQuotes()
{
QuotedCSV values = new QuotedCSV(false);
values.addValue("A;p=\"v\",B,\"C, D\"");
Assert.assertThat(values,Matchers.contains(
"A;p=v",
"B",
"C, D"));
}
@Test
public void testOpenQuoteNoQuotes()
{
QuotedCSV values = new QuotedCSV(false);
values.addValue("value;p=\"v");
assertThat(values,Matchers.contains(
"value;p=v"));
}
@Test
public void testUnQuote()
{
assertThat(QuotedCSV.unquote(""),is(""));
assertThat(QuotedCSV.unquote("\"\""),is(""));
assertThat(QuotedCSV.unquote("foo"),is("foo"));
assertThat(QuotedCSV.unquote("\"foo\""),is("foo"));
assertThat(QuotedCSV.unquote("f\"o\"o"),is("foo"));
assertThat(QuotedCSV.unquote("\"\\\"foo\""),is("\"foo"));
assertThat(QuotedCSV.unquote("\\foo"),is("\\foo"));
}
}

View File

@ -0,0 +1,146 @@
//
// ========================================================================
// 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 org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
public class QuotedQualityCSVTest
{
@Test
public void test7231_5_3_2_example1()
{
QuotedQualityCSV values = new QuotedQualityCSV();
values.addValue(" audio/*; q=0.2, audio/basic");
Assert.assertThat(values,Matchers.contains("audio/basic","audio/*"));
}
@Test
public void test7231_5_3_2_example2()
{
QuotedQualityCSV values = new QuotedQualityCSV();
values.addValue("text/plain; q=0.5, text/html,");
values.addValue("text/x-dvi; q=0.8, text/x-c");
Assert.assertThat(values,Matchers.contains("text/html","text/x-c","text/x-dvi","text/plain"));
}
@Test
public void test7231_5_3_2_example3()
{
QuotedQualityCSV values = new QuotedQualityCSV();
values.addValue("text/*, text/plain, text/plain;format=flowed, */*");
Assert.assertThat(values,Matchers.contains("text/plain;format=flowed","text/plain","text/*","*/*"));
}
@Test
public void test7231_5_3_2_example4()
{
QuotedQualityCSV values = new QuotedQualityCSV();
values.addValue("text/*;q=0.3, text/html;q=0.7, text/html;level=1,");
values.addValue("text/html;level=2;q=0.4, */*;q=0.5");
Assert.assertThat(values,Matchers.contains(
"text/html;level=1",
"text/html",
"*/*",
"text/html;level=2",
"text/*"
));
}
@Test
public void test7231_5_3_4_example1()
{
QuotedQualityCSV values = new QuotedQualityCSV();
values.addValue("compress, gzip");
values.addValue("");
values.addValue("*");
values.addValue("compress;q=0.5, gzip;q=1.0");
values.addValue("gzip;q=1.0, identity; q=0.5, *;q=0");
Assert.assertThat(values,Matchers.contains(
"compress",
"gzip",
"gzip",
"gzip",
"*",
"compress",
"identity"
));
}
@Test
public void testOWS()
{
QuotedQualityCSV values = new QuotedQualityCSV();
values.addValue(" value 0.5 ; p = v ; q =0.5 , value 1.0 ");
Assert.assertThat(values,Matchers.contains(
"value 1.0",
"value 0.5;p=v"));
}
@Test
public void testEmpty()
{
QuotedQualityCSV values = new QuotedQualityCSV();
values.addValue(",aaaa, , bbbb ,,cccc,");
Assert.assertThat(values,Matchers.contains(
"aaaa",
"bbbb",
"cccc"));
}
@Test
public void testQuoted()
{
QuotedQualityCSV values = new QuotedQualityCSV();
values.addValue(" value 0.5 ; p = \"v ; q = \\\"0.5\\\" , value 1.0 \" ");
Assert.assertThat(values,Matchers.contains(
"value 0.5;p=\"v ; q = \\\"0.5\\\" , value 1.0 \""));
}
@Test
public void testOpenQuote()
{
QuotedQualityCSV values = new QuotedQualityCSV();
values.addValue("value;p=\"v");
Assert.assertThat(values,Matchers.contains(
"value;p=\"v"));
}
@Test
public void testQuotedQuality()
{
QuotedQualityCSV values = new QuotedQualityCSV();
values.addValue(" value 0.5 ; p = v ; q = \"0.5\" , value 1.0 ");
Assert.assertThat(values,Matchers.contains(
"value 1.0",
"value 0.5;p=v"));
}
@Test
public void testBadQuality()
{
QuotedQualityCSV values = new QuotedQualityCSV();
values.addValue("value0.5;p=v;q=0.5,value1.0,valueBad;q=X");
Assert.assertThat(values,Matchers.contains(
"value1.0",
"value0.5;p=v"));
}
}

View File

@ -23,7 +23,7 @@ import java.util.Locale;
import javax.servlet.http.Cookie; import javax.servlet.http.Cookie;
import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.http.QuotedCSV;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
@ -35,7 +35,6 @@ import org.eclipse.jetty.util.log.Logger;
* call to {@link #getCookies()}. * call to {@link #getCookies()}.
* If the added fields are identical to those last added (as strings), then the * If the added fields are identical to those last added (as strings), then the
* cookies are not re parsed. * cookies are not re parsed.
*
* *
*/ */
public class CookieCutter public class CookieCutter
@ -274,8 +273,9 @@ public class CookieCutter
// If after processing the current character we have a value and a name, then it is a cookie // If after processing the current character we have a value and a name, then it is a cookie
if (value!=null && name!=null) if (value!=null && name!=null)
{ {
name=QuotedStringTokenizer.unquoteOnly(name);
value=QuotedStringTokenizer.unquoteOnly(value); name=QuotedCSV.unquote(name);
value=QuotedCSV.unquote(value);
try try
{ {

View File

@ -40,6 +40,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
import javax.servlet.AsyncContext; import javax.servlet.AsyncContext;
import javax.servlet.AsyncListener; import javax.servlet.AsyncListener;
@ -702,20 +703,12 @@ public class Request implements HttpServletRequest
} }
_cookiesExtracted = true; _cookiesExtracted = true;
Enumeration<?> enm = _metadata.getFields().getValues(HttpHeader.COOKIE.toString()); for (String c : _metadata.getFields().getValuesList(HttpHeader.COOKIE))
// Handle no cookies
if (enm != null)
{ {
if (_cookies == null) if (_cookies == null)
_cookies = new CookieCutter(); _cookies = new CookieCutter();
_cookies.addCookieField(c);
while (enm.hasMoreElements())
{
String c = (String)enm.nextElement();
_cookies.addCookieField(c);
}
} }
//Javadoc for Request.getCookies() stipulates null for no cookies //Javadoc for Request.getCookies() stipulates null for no cookies
@ -826,34 +819,22 @@ public class Request implements HttpServletRequest
if (_metadata==null) if (_metadata==null)
return Locale.getDefault(); return Locale.getDefault();
Enumeration<String> enm = _metadata.getFields().getValues(HttpHeader.ACCEPT_LANGUAGE.toString(),HttpFields.__separators); List<String> acceptable = _metadata.getFields().getQualityCSV(HttpHeader.ACCEPT_LANGUAGE);
// handle no locale // handle no locale
if (enm == null || !enm.hasMoreElements()) if (acceptable.isEmpty())
return Locale.getDefault(); return Locale.getDefault();
// sort the list in quality order String language = acceptable.get(0);
List<?> acceptLanguage = HttpFields.qualityList(enm); language = HttpFields.stripParameters(language);
if (acceptLanguage.size() == 0) String country = "";
return Locale.getDefault(); int dash = language.indexOf('-');
if (dash > -1)
int size = acceptLanguage.size();
if (size > 0)
{ {
String language = (String)acceptLanguage.get(0); country = language.substring(dash + 1).trim();
language = HttpFields.valueParameters(language,null); language = language.substring(0,dash).trim();
String country = "";
int dash = language.indexOf('-');
if (dash > -1)
{
country = language.substring(dash + 1).trim();
language = language.substring(0,dash).trim();
}
return new Locale(language,country);
} }
return new Locale(language,country);
return Locale.getDefault();
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -866,38 +847,26 @@ public class Request implements HttpServletRequest
if (_metadata==null) if (_metadata==null)
return Collections.enumeration(__defaultLocale); return Collections.enumeration(__defaultLocale);
Enumeration<String> enm = _metadata.getFields().getValues(HttpHeader.ACCEPT_LANGUAGE.toString(),HttpFields.__separators); List<String> acceptable = _metadata.getFields().getQualityCSV(HttpHeader.ACCEPT_LANGUAGE);
// handle no locale // handle no locale
if (enm == null || !enm.hasMoreElements()) if (acceptable.isEmpty())
return Collections.enumeration(__defaultLocale); return Collections.enumeration(__defaultLocale);
// sort the list in quality order List<Locale> locales = acceptable.stream().map(language->
List<String> acceptLanguage = HttpFields.qualityList(enm);
if (acceptLanguage.size() == 0)
return Collections.enumeration(__defaultLocale);
List<Locale> langs = new ArrayList<>();
// convert to locals
for (String language : acceptLanguage)
{ {
language = HttpFields.valueParameters(language, null); language = HttpFields.stripParameters(language);
String country = ""; String country = "";
int dash = language.indexOf('-'); int dash = language.indexOf('-');
if (dash > -1) if (dash > -1)
{ {
country = language.substring(dash + 1).trim(); country = language.substring(dash + 1).trim();
language = language.substring(0, dash).trim(); language = language.substring(0,dash).trim();
} }
langs.add(new Locale(language, country)); return new Locale(language,country);
} }).collect(Collectors.toList());
if (langs.size() == 0) return Collections.enumeration(locales);
return Collections.enumeration(__defaultLocale);
return Collections.enumeration(langs);
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */

View File

@ -42,12 +42,12 @@ import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.PreEncodedHttpField; import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.http.QuotedCSV;
import org.eclipse.jetty.io.WriterOutputStream; import org.eclipse.jetty.io.WriterOutputStream;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.MultiPartOutputStream; import org.eclipse.jetty.util.MultiPartOutputStream;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
@ -430,12 +430,14 @@ public abstract class ResourceService
boolean match=false; boolean match=false;
if (etag!=null) if (etag!=null)
{ {
QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifm,", ",false,true); QuotedCSV quoted = new QuotedCSV(true,ifm);
while (!match && quoted.hasMoreTokens()) for (String tag : quoted)
{ {
String tag = quoted.nextToken();
if (etag.equals(tag) || tag.endsWith(ETAG_GZIP_QUOTE) && etag.equals(removeGzipFromETag(tag))) if (etag.equals(tag) || tag.endsWith(ETAG_GZIP_QUOTE) && etag.equals(removeGzipFromETag(tag)))
{
match=true; match=true;
break;
}
} }
} }
@ -457,10 +459,9 @@ public abstract class ResourceService
} }
// Handle list of tags // Handle list of tags
QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifnm,", ",false,true); QuotedCSV quoted = new QuotedCSV(true,ifnm);
while (quoted.hasMoreTokens()) for (String tag : quoted)
{ {
String tag = quoted.nextToken();
if (etag.equals(tag) || tag.endsWith(ETAG_GZIP_QUOTE) && etag.equals(removeGzipFromETag(tag))) if (etag.equals(tag) || tag.endsWith(ETAG_GZIP_QUOTE) && etag.equals(removeGzipFromETag(tag)))
{ {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.server; package org.eclipse.jetty.server;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@ -39,6 +40,8 @@ import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -130,7 +133,6 @@ public class RequestTest
} }
@Ignore("Empty headers are not 7230 compliant")
@Test @Test
public void testEmptyHeaders() throws Exception public void testEmptyHeaders() throws Exception
{ {
@ -140,15 +142,24 @@ public class RequestTest
public boolean check(HttpServletRequest request,HttpServletResponse response) public boolean check(HttpServletRequest request,HttpServletResponse response)
{ {
assertNotNull(request.getLocale()); assertNotNull(request.getLocale());
assertTrue(request.getLocales().hasMoreElements()); assertTrue(request.getLocales().hasMoreElements()); // Default locale
assertNull(request.getContentType()); assertEquals("",request.getContentType());
assertNull(request.getCharacterEncoding()); assertNull(request.getCharacterEncoding());
assertEquals(0,request.getQueryString().length()); assertEquals(0,request.getQueryString().length());
assertEquals(-1,request.getContentLength()); assertEquals(-1,request.getContentLength());
assertNull(request.getCookies()); assertNull(request.getCookies());
assertNull(request.getHeader("Name")); assertEquals("",request.getHeader("Name"));
assertFalse(request.getHeaders("Name").hasMoreElements()); assertTrue(request.getHeaders("Name").hasMoreElements()); // empty
assertEquals(-1,request.getDateHeader("Name")); try
{
request.getDateHeader("Name");
assertTrue(false);
}
catch(IllegalArgumentException e)
{
}
assertEquals(-1,request.getDateHeader("Other"));
return true; return true;
} }
}; };
@ -215,6 +226,43 @@ public class RequestTest
assertTrue(responses.startsWith("HTTP/1.1 200")); assertTrue(responses.startsWith("HTTP/1.1 200"));
} }
@Test
public void testLocale() throws Exception
{
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException
{
assertThat(request.getLocale().getLanguage(),is("da"));
Enumeration<Locale> locales = request.getLocales();
Locale locale=locales.nextElement();
assertThat(locale.getLanguage(),is("da"));
assertThat(locale.getCountry(),is(""));
locale=locales.nextElement();
assertThat(locale.getLanguage(),is("en"));
assertThat(locale.getCountry(),is("AU"));
locale=locales.nextElement();
assertThat(locale.getLanguage(),is("en"));
assertThat(locale.getCountry(),is("GB"));
locale=locales.nextElement();
assertThat(locale.getLanguage(),is("en"));
assertThat(locale.getCountry(),is(""));
assertFalse(locales.hasMoreElements());
return true;
}
};
String request="GET / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Connection: close\r\n"+
"Accept-Language: da, en-gb;q=0.8, en;q=0.7\r\n"+
"Accept-Language: XX;q=0, en-au;q=0.9\r\n"+
"\r\n";
String response = _connector.getResponses(request);
assertThat(response,Matchers.containsString(" 200 OK"));
}
@Test @Test
public void testMultiPart() throws Exception public void testMultiPart() throws Exception
@ -1089,14 +1137,14 @@ public class RequestTest
response=_connector.getResponses( response=_connector.getResponses(
"GET / HTTP/1.1\n"+ "GET / HTTP/1.1\n"+
"Host: whatever\n"+ "Host: whatever\n"+
"Cookie: name=quoted=\\\"value\\\"\n" + "Cookie: name=quoted=\"\\\"value\\\"\"\n" +
"Connection: close\n"+ "Connection: close\n"+
"\n" "\n"
); );
assertTrue(response.startsWith("HTTP/1.1 200 OK")); assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertEquals(1,cookies.size()); assertEquals(1,cookies.size());
assertEquals("name", cookies.get(0).getName()); assertEquals("name", cookies.get(0).getName());
assertEquals("quoted=\\\"value\\\"", cookies.get(0).getValue()); assertEquals("quoted=\"value\"", cookies.get(0).getValue());
cookies.clear(); cookies.clear();
response=_connector.getResponses( response=_connector.getResponses(

View File

@ -462,7 +462,6 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
resp.setHeader("Allow", "GET,HEAD,POST,OPTIONS"); resp.setHeader("Allow", "GET,HEAD,POST,OPTIONS");
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/* /*
* @see javax.servlet.Servlet#destroy() * @see javax.servlet.Servlet#destroy()

View File

@ -246,16 +246,15 @@ public class StringUtil
if (c<s.length()) if (c<s.length())
buf.append(s.substring(c,s.length())); buf.append(s.substring(c,s.length()));
return buf.toString(); return buf.toString();
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** Remove single or double quotes. /** Remove single or double quotes.
* @param s the input string * @param s the input string
* @return the string with quotes removed * @return the string with quotes removed
*/ */
@Deprecated
public static String unquote(String s) public static String unquote(String s)
{ {
return QuotedStringTokenizer.unquote(s); return QuotedStringTokenizer.unquote(s);

View File

@ -378,6 +378,7 @@ public class UpgradeConnection extends AbstractConnection implements Connection.
{ {
for (String extVal : extValues) for (String extVal : extValues)
{ {
// TODO use QuotedCSV ???
QuotedStringTokenizer tok = new QuotedStringTokenizer(extVal,","); QuotedStringTokenizer tok = new QuotedStringTokenizer(extVal,",");
while (tok.hasMoreTokens()) while (tok.hasMoreTokens())
{ {