Merge remote-tracking branch 'origin/jetty-9.4.x' into jetty-10.0.x

Signed-off-by: Greg Wilkins <gregw@webtide.com>
This commit is contained in:
Greg Wilkins 2019-03-05 10:40:52 +11:00
commit aa2064f02f
4 changed files with 87 additions and 57 deletions

View File

@ -30,6 +30,7 @@ import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Set; import java.util.Set;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.function.ToIntFunction;
import java.util.stream.Stream; import java.util.stream.Stream;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
@ -419,6 +420,19 @@ public class HttpFields implements Iterable<HttpField>
* @param header The header * @param header The header
*/ */
public List<String> getQualityCSV(HttpHeader header) public List<String> getQualityCSV(HttpHeader header)
{
return getQualityCSV(header,null);
}
/**
* Get multiple field values of the same name, split and
* sorted as a {@link QuotedQualityCSV}
*
* @param header The header
* @param secondaryOrdering Function to apply an ordering other than specified by quality
* @return List the values in quality order with the q param and OWS stripped
*/
public List<String> getQualityCSV(HttpHeader header, ToIntFunction<String> secondaryOrdering)
{ {
QuotedQualityCSV values = null; QuotedQualityCSV values = null;
for (HttpField f : this) for (HttpField f : this)
@ -426,7 +440,7 @@ public class HttpFields implements Iterable<HttpField>
if (f.getHeader()==header) if (f.getHeader()==header)
{ {
if (values==null) if (values==null)
values = new QuotedQualityCSV(); values = new QuotedQualityCSV(secondaryOrdering);
values.addValue(f.getValue()); values.addValue(f.getValue());
} }
} }

View File

@ -21,63 +21,72 @@ package org.eclipse.jetty.http;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.ToIntFunction;
import org.eclipse.jetty.util.log.Log;
import static java.lang.Integer.MIN_VALUE;
import static java.lang.Integer.MIN_VALUE; import static java.lang.Integer.MIN_VALUE;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* Implements a quoted comma separated list of quality values * Implements a quoted comma separated list of quality values
* in accordance with RFC7230 and RFC7231. * in accordance with RFC7230 and RFC7231.
* Values are returned sorted in quality order, with OWS and the * Values are returned sorted in quality order, with OWS and the
* quality parameters removed. * quality parameters removed.
*
* @see "https://tools.ietf.org/html/rfc7230#section-3.2.6" * @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/rfc7230#section-7"
* @see "https://tools.ietf.org/html/rfc7231#section-5.3.1" * @see "https://tools.ietf.org/html/rfc7231#section-5.3.1"
*/ */
public class QuotedQualityCSV extends QuotedCSV implements Iterable<String> public class QuotedQualityCSV extends QuotedCSV implements Iterable<String>
{ {
private final static Double ZERO = 0.0D;
private final static Double ONE = 1.0D;
/** /**
* Function to apply a most specific MIME encoding secondary ordering * Lambda to apply a most specific MIME encoding secondary ordering.
*
* @see "https://tools.ietf.org/html/rfc7231#section-5.3.2"
*/ */
public static Function<String, Integer> MOST_SPECIFIC = new Function<String, Integer>() public static ToIntFunction<String> MOST_SPECIFIC_MIME_ORDERING = s ->
{ {
@Override if ("*/*".equals(s))
public Integer apply(String s) return 0;
{ if (s.endsWith("/*"))
String[] elements = s.split("/"); return 1;
return 1000000*elements.length+1000*elements[0].length()+elements[elements.length-1].length(); if (s.indexOf(';') < 0)
} return 2;
return 3;
}; };
private final List<Double> _quality = new ArrayList<>(); private final List<Double> _quality = new ArrayList<>();
private boolean _sorted = false; private boolean _sorted = false;
private final Function<String, Integer> _secondaryOrdering; private final ToIntFunction<String> _secondaryOrdering;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* Sorts values with equal quality according to the length of the value String. * Sorts values with equal quality according to the length of the value String.
*/ */
public QuotedQualityCSV() public QuotedQualityCSV()
{ {
this((s) -> 0); this((ToIntFunction)null);
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* Sorts values with equal quality according to given order. * Sorts values with equal quality according to given order.
*
* @param preferredOrder Array indicating the preferred order of known values * @param preferredOrder Array indicating the preferred order of known values
*/ */
public QuotedQualityCSV(String[] preferredOrder) public QuotedQualityCSV(String[] preferredOrder)
{ {
this((s) -> { this((s) ->
for (int i=0;i<preferredOrder.length;++i) {
for (int i = 0; i < preferredOrder.length; ++i)
if (preferredOrder[i].equals(s)) if (preferredOrder[i].equals(s))
return preferredOrder.length-i; return preferredOrder.length - i;
if ("*".equals(s)) if ("*".equals(s))
return preferredOrder.length; return preferredOrder.length;
@ -87,13 +96,15 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable<String>
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* Orders values with equal quality with the given function. * Orders values with equal quality with the given function.
*
* @param secondaryOrdering Function to apply an ordering other than specified by quality * @param secondaryOrdering Function to apply an ordering other than specified by quality
*/ */
public QuotedQualityCSV(Function<String, Integer> secondaryOrdering) public QuotedQualityCSV(ToIntFunction<String> secondaryOrdering)
{ {
this._secondaryOrdering = secondaryOrdering; this._secondaryOrdering = secondaryOrdering == null ? s -> 0 : secondaryOrdering;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -101,37 +112,41 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable<String>
protected void parsedValue(StringBuffer buffer) protected void parsedValue(StringBuffer buffer)
{ {
super.parsedValue(buffer); super.parsedValue(buffer);
_quality.add(ONE);
// Assume a quality of ONE
_quality.add(1.0D);
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@Override @Override
protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue) protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue)
{ {
if (paramName<0) if (paramName < 0)
{ {
if (buffer.charAt(buffer.length()-1)==';') if (buffer.charAt(buffer.length() - 1) == ';')
buffer.setLength(buffer.length()-1); buffer.setLength(buffer.length() - 1);
} }
else if (paramValue>=0 && else if (paramValue >= 0 &&
buffer.charAt(paramName)=='q' && paramValue>paramName && buffer.charAt(paramName) == 'q' && paramValue > paramName &&
buffer.length()>=paramName && buffer.charAt(paramName+1)=='=') buffer.length() >= paramName && buffer.charAt(paramName + 1) == '=')
{ {
Double q; Double q;
try try
{ {
q=(_keepQuotes && buffer.charAt(paramValue)=='"') q = (_keepQuotes && buffer.charAt(paramValue) == '"')
? (Double) Double.parseDouble(buffer.substring(paramValue + 1, buffer.length() - 1)) ? Double.valueOf(buffer.substring(paramValue + 1, buffer.length() - 1))
: (Double) Double.parseDouble(buffer.substring(paramValue)); : Double.valueOf(buffer.substring(paramValue));
} }
catch(Exception e) catch (Exception e)
{ {
q=ZERO; Log.getLogger(QuotedQualityCSV.class).ignore(e);
q = 0.0D;
} }
buffer.setLength(Math.max(0,paramName-1)); buffer.setLength(Math.max(0, paramName - 1));
if (!ONE.equals(q)) if (q != 1.0D)
_quality.set(_quality.size()-1,q); // replace assumed quality
_quality.set(_quality.size() - 1, q);
} }
} }
@ -153,35 +168,35 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable<String>
protected void sort() protected void sort()
{ {
_sorted=true; _sorted = true;
Double last = ZERO; Double last = 0.0D;
int lastSecondaryOrder = Integer.MIN_VALUE; int lastSecondaryOrder = Integer.MIN_VALUE;
for (int i = _values.size(); i-- > 0;) for (int i = _values.size(); i-- > 0; )
{ {
String v = _values.get(i); String v = _values.get(i);
Double q = _quality.get(i); Double q = _quality.get(i);
int compare=last.compareTo(q); int compare = last.compareTo(q);
if (compare>0 || (compare==0 && _secondaryOrdering.apply(v)<lastSecondaryOrder)) if (compare > 0 || (compare == 0 && _secondaryOrdering.applyAsInt(v) < lastSecondaryOrder))
{ {
_values.set(i, _values.get(i + 1)); _values.set(i, _values.get(i + 1));
_values.set(i + 1, v); _values.set(i + 1, v);
_quality.set(i, _quality.get(i + 1)); _quality.set(i, _quality.get(i + 1));
_quality.set(i + 1, q); _quality.set(i + 1, q);
last = ZERO; last = 0.0D;
lastSecondaryOrder=0; lastSecondaryOrder = 0;
i = _values.size(); i = _values.size();
continue; continue;
} }
last=q; last = q;
lastSecondaryOrder=_secondaryOrdering.apply(v); lastSecondaryOrder = _secondaryOrdering.applyAsInt(v);
} }
int last_element=_quality.size(); int last_element = _quality.size();
while(last_element>0 && _quality.get(--last_element).equals(ZERO)) while (last_element > 0 && _quality.get(--last_element).equals(0.0D))
{ {
_quality.remove(last_element); _quality.remove(last_element);
_values.remove(last_element); _values.remove(last_element);

View File

@ -61,7 +61,7 @@ public class QuotedQualityCSVTest
@Test @Test
public void test7231_5_3_2_example3_most_specific() public void test7231_5_3_2_example3_most_specific()
{ {
QuotedQualityCSV values = new QuotedQualityCSV(QuotedQualityCSV.MOST_SPECIFIC); QuotedQualityCSV values = new QuotedQualityCSV(QuotedQualityCSV.MOST_SPECIFIC_MIME_ORDERING);
values.addValue("text/*, text/plain, text/plain;format=flowed, */*"); values.addValue("text/*, text/plain, text/plain;format=flowed, */*");
assertThat(values,Matchers.contains("text/plain;format=flowed","text/plain","text/*","*/*")); assertThat(values,Matchers.contains("text/plain;format=flowed","text/plain","text/*","*/*"));

View File

@ -38,6 +38,7 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.QuotedQualityCSV;
import org.eclipse.jetty.server.Dispatcher; import org.eclipse.jetty.server.Dispatcher;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
@ -166,7 +167,7 @@ public class ErrorHandler extends AbstractHandler
protected void generateAcceptableResponse(Request baseRequest, HttpServletRequest request, HttpServletResponse response, int code, String message) protected void generateAcceptableResponse(Request baseRequest, HttpServletRequest request, HttpServletResponse response, int code, String message)
throws IOException throws IOException
{ {
List<String> acceptable=baseRequest.getHttpFields().getQualityCSV(HttpHeader.ACCEPT); List<String> acceptable=baseRequest.getHttpFields().getQualityCSV(HttpHeader.ACCEPT, QuotedQualityCSV.MOST_SPECIFIC_MIME_ORDERING);
if (acceptable.isEmpty() && !baseRequest.getHttpFields().contains(HttpHeader.ACCEPT)) if (acceptable.isEmpty() && !baseRequest.getHttpFields().contains(HttpHeader.ACCEPT))
{ {