Issue #458 Improve Quality list handling

Added QuotedCSV and QuotedQualityCSV that are up to date with RFC7230
This commit is contained in:
Greg Wilkins 2016-03-30 19:07:11 +11:00
parent 80eefb5257
commit c768828703
12 changed files with 942 additions and 125 deletions

View File

@ -171,7 +171,7 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent
}
// 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)
{
StringBuilder builder = new StringBuilder();

View File

@ -32,7 +32,6 @@ import java.util.Set;
import java.util.StringTokenizer;
import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.log.Log;
@ -50,6 +49,7 @@ import org.eclipse.jetty.util.log.Logger;
*/
public class HttpFields implements Iterable<HttpField>
{
@Deprecated
public static final String __separators = ", \t";
private static final Logger LOG = Log.getLogger(HttpFields.class);
@ -244,11 +244,26 @@ public class HttpFields implements Iterable<HttpField>
}
return null;
}
/**
* Get multi headers
* Get multiple header of the same name
*
* @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
*/
public List<String> getValuesList(String name)
@ -260,6 +275,72 @@ public class HttpFields implements Iterable<HttpField>
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
*
@ -325,6 +406,7 @@ public class HttpFields implements Iterable<HttpField>
* @param separators String of separators.
* @return Enumeration of the values, or null if no such header.
*/
@Deprecated
public Enumeration<String> getValues(String name, final String separators)
{
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
* 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>
*
* @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
* @return The value.
*/
@ -752,8 +856,11 @@ public class HttpFields implements Iterable<HttpField>
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<Float> __qualities = new ArrayTernaryTrie<>();
static
{
@ -775,6 +882,7 @@ public class HttpFields implements Iterable<HttpField>
__qualities.put("0.0", __zero);
}
@Deprecated
public static Float getQuality(String value)
{
if (value == null) return __zero;
@ -816,53 +924,16 @@ public class HttpFields implements Iterable<HttpField>
* @param e Enumeration of values with quality parameters
* @return values in quality order.
*/
@Deprecated
public static List<String> qualityList(Enumeration<String> e)
{
if (e == null || !e.hasMoreElements())
return Collections.emptyList();
Object list = null;
Object qual = null;
// Assume list will be well ordered and just add nonzero
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;
QuotedQualityCSV values = new QuotedQualityCSV();
while(e.hasMoreElements())
values.addValue(e.nextElement());
return values.getValues();
}

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 org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.http.QuotedCSV;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -35,7 +35,6 @@ import org.eclipse.jetty.util.log.Logger;
* call to {@link #getCookies()}.
* If the added fields are identical to those last added (as strings), then the
* cookies are not re parsed.
*
*
*/
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 (value!=null && name!=null)
{
name=QuotedStringTokenizer.unquoteOnly(name);
value=QuotedStringTokenizer.unquoteOnly(value);
name=QuotedCSV.unquote(name);
value=QuotedCSV.unquote(value);
try
{

View File

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

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.server;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@ -39,6 +40,8 @@ import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@ -129,7 +132,6 @@ public class RequestTest
}
@Ignore("Empty headers are not 7230 compliant")
@Test
public void testEmptyHeaders() throws Exception
{
@ -139,15 +141,24 @@ public class RequestTest
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
assertNotNull(request.getLocale());
assertTrue(request.getLocales().hasMoreElements());
assertNull(request.getContentType());
assertTrue(request.getLocales().hasMoreElements()); // Default locale
assertEquals("",request.getContentType());
assertNull(request.getCharacterEncoding());
assertEquals(0,request.getQueryString().length());
assertEquals(-1,request.getContentLength());
assertNull(request.getCookies());
assertNull(request.getHeader("Name"));
assertFalse(request.getHeaders("Name").hasMoreElements());
assertEquals(-1,request.getDateHeader("Name"));
assertEquals("",request.getHeader("Name"));
assertTrue(request.getHeaders("Name").hasMoreElements()); // empty
try
{
request.getDateHeader("Name");
assertTrue(false);
}
catch(IllegalArgumentException e)
{
}
assertEquals(-1,request.getDateHeader("Other"));
return true;
}
};
@ -214,6 +225,43 @@ public class RequestTest
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
public void testMultiPart() throws Exception
@ -1088,14 +1136,14 @@ public class RequestTest
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: whatever\n"+
"Cookie: name=quoted=\\\"value\\\"\n" +
"Cookie: name=quoted=\"\\\"value\\\"\"\n" +
"Connection: close\n"+
"\n"
);
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertEquals(1,cookies.size());
assertEquals("name", cookies.get(0).getName());
assertEquals("quoted=\\\"value\\\"", cookies.get(0).getValue());
assertEquals("quoted=\"value\"", cookies.get(0).getValue());
cookies.clear();
response=_connector.getResponses(

View File

@ -43,7 +43,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.DateParser;
import org.eclipse.jetty.http.GzipHttpContent;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
@ -52,7 +51,7 @@ import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.PathMap.MappedEntry;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.http.ResourceHttpContent;
import org.eclipse.jetty.http.QuotedCSV;
import org.eclipse.jetty.io.WriterOutputStream;
import org.eclipse.jetty.server.HttpOutput;
import org.eclipse.jetty.server.InclusiveByteRange;
@ -65,7 +64,6 @@ import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.MultiPartOutputStream;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -748,12 +746,14 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
boolean match=false;
if (etag!=null)
{
QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifm,", ",false,true);
while (!match && quoted.hasMoreTokens())
QuotedCSV quoted = new QuotedCSV(true,ifm);
for (String tag : quoted)
{
String tag = quoted.nextToken();
if (etag.equals(tag) || tag.endsWith(ETAG_GZIP_QUOTE) && etag.equals(removeGzipFromETag(tag)))
{
match=true;
break;
}
}
}
@ -775,10 +775,9 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
}
// Handle list of tags
QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifnm,", ",false,true);
while (quoted.hasMoreTokens())
QuotedCSV quoted = new QuotedCSV(true,ifnm);
for (String tag : quoted)
{
String tag = quoted.nextToken();
if (etag.equals(tag) || tag.endsWith(ETAG_GZIP_QUOTE) && etag.equals(removeGzipFromETag(tag)))
{
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);

View File

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

View File

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