/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.undertow.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
Utility class for parsing headers that accept q values
Author: Stuart Douglas
/**
* Utility class for parsing headers that accept q values
*
* @author Stuart Douglas
*/
public class QValueParser {
private QValueParser() {
}
Parses a set of headers that take q values to determine the most preferred one.
It returns the result in the form of a sorted list of list, with every element in
the list having the same q value. This means the highest priority items are at the
front of the list. The container should use its own internal preferred ordering
to determinately pick the correct item to use
Params: - headers – The headers
Returns: The q value results
/**
* Parses a set of headers that take q values to determine the most preferred one.
*
* It returns the result in the form of a sorted list of list, with every element in
* the list having the same q value. This means the highest priority items are at the
* front of the list. The container should use its own internal preferred ordering
* to determinately pick the correct item to use
*
* @param headers The headers
* @return The q value results
*/
public static List<List<QValueResult>> parse(List<String> headers) {
final List<QValueResult> found = new ArrayList<>();
QValueResult current = null;
for (final String header : headers) {
final int l = header.length();
//we do not use a string builder
//we just keep track of where the current string starts and call substring()
int stringStart = 0;
for (int i = 0; i < l; ++i) {
char c = header.charAt(i);
switch (c) {
case ',': {
if (current != null &&
(i - stringStart > 2 && header.charAt(stringStart) == 'q' &&
header.charAt(stringStart + 1) == '=')) {
//if this is a valid qvalue
current.qvalue = header.substring(stringStart + 2, i);
current = null;
} else if (stringStart != i) {
current = handleNewEncoding(found, header, stringStart, i);
}
stringStart = i + 1;
break;
}
case ';': {
if (stringStart != i) {
current = handleNewEncoding(found, header, stringStart, i);
stringStart = i + 1;
}
break;
}
case ' ': {
if (stringStart != i) {
if (current != null &&
(i - stringStart > 2 && header.charAt(stringStart) == 'q' &&
header.charAt(stringStart + 1) == '=')) {
//if this is a valid qvalue
current.qvalue = header.substring(stringStart + 2, i);
} else {
current = handleNewEncoding(found, header, stringStart, i);
}
}
stringStart = i + 1;
}
}
}
if (stringStart != l) {
if (current != null &&
(l - stringStart > 2 && header.charAt(stringStart) == 'q' &&
header.charAt(stringStart + 1) == '=')) {
//if this is a valid qvalue
current.qvalue = header.substring(stringStart + 2, l);
} else {
current = handleNewEncoding(found, header, stringStart, l);
}
}
}
Collections.sort(found, Collections.reverseOrder());
String currentQValue = null;
List<List<QValueResult>> values = new ArrayList<>();
List<QValueResult> currentSet = null;
for(QValueResult val : found) {
if(!val.qvalue.equals(currentQValue)) {
currentQValue = val.qvalue;
currentSet = new ArrayList<>();
values.add(currentSet);
}
currentSet.add(val);
}
return values;
}
private static QValueResult handleNewEncoding(final List<QValueResult> found, final String header, final int stringStart, final int i) {
final QValueResult current = new QValueResult();
current.value = header.substring(stringStart, i);
found.add(current);
return current;
}
public static class QValueResult implements Comparable<QValueResult> {
The string value of the result
/**
* The string value of the result
*/
private String value;
we keep the qvalue as a string to avoid parsing the double.
This should give both performance and also possible security improvements
/**
* we keep the qvalue as a string to avoid parsing the double.
* <p/>
* This should give both performance and also possible security improvements
*/
private String qvalue = "1";
public String getValue() {
return value;
}
public String getQvalue() {
return qvalue;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof QValueResult)) return false;
QValueResult that = (QValueResult) o;
if (getValue() != null ? !getValue().equals(that.getValue()) : that.getValue() != null) return false;
return getQvalue() != null ? getQvalue().equals(that.getQvalue()) : that.getQvalue() == null;
}
@Override
public int hashCode() {
int result = getValue() != null ? getValue().hashCode() : 0;
result = 31 * result + (getQvalue() != null ? getQvalue().hashCode() : 0);
return result;
}
@Override
public int compareTo(final QValueResult other) {
//we compare the strings as if they were decimal values.
//we know they can only be
final String t = qvalue;
final String o = other.qvalue;
if (t == null && o == null) {
//neither of them has a q value
//we compare them via the server specified default precedence
//note that encoding is never null here, a * without a q value is meaningless
//and will be discarded before this
return 0;
}
if (o == null) {
return 1;
} else if (t == null) {
return -1;
}
final int tl = t.length();
final int ol = o.length();
//we only compare the first 5 characters as per spec
for (int i = 0; i < 5; ++i) {
if (tl == i || ol == i) {
return ol - tl; //longer one is higher
}
if (i == 1) continue; // this is just the decimal point
final int tc = t.charAt(i);
final int oc = o.charAt(i);
int res = tc - oc;
if (res != 0) {
return res;
}
}
return 0;
}
public boolean isQValueZero() {
//we ignore * without a qvalue
if (qvalue != null) {
int length = Math.min(5, qvalue.length());
//we need to find out if this is prohibiting identity
//encoding (q=0). Otherwise we just treat it as the identity encoding
boolean zero = true;
for (int j = 0; j < length; ++j) {
if (j == 1) continue;//decimal point
if (qvalue.charAt(j) != '0') {
zero = false;
break;
}
}
return zero;
}
return false;
}
}
}