/*
* 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
*/
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
*
* @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
*/
private
String value;
/**
* 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;
}
}
}