ratpack.http.internal.MimeParse Maven / Gradle / Ivy
/**
* MIME-Type Parser
*
* This class provides basic functions for handling mime-types. It can handle
* matching mime-types against a list of media-ranges. See section 14.1 of the
* HTTP specification [RFC 2616] for a complete explanation.
*
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
*
* A port to Java of Joe Gregorio's MIME-Type Parser:
*
* http://code.google.com/p/mimeparse/
*
* Ported by Tom Zellman .
*/
package ratpack.http.internal;
import java.util.*;
public final class MimeParse {
protected static class ParseResults {
String type;
String subType;
// !a dictionary of all the parameters for the media range
Map params;
@Override
public String toString() {
StringBuilder s = new StringBuilder("('" + type + "', '" + subType
+ "', {");
for (String k : params.keySet()) {
s.append("'").append(k).append("':'").append(params.get(k)).append("',");
}
return s.append("})").toString();
}
}
protected static ParseResults parseMimeType(CharSequence mimeType) {
String[] parts = mimeType.toString().split(";");
ParseResults results = new ParseResults();
results.params = new HashMap<>();
String fullType = parts[0].trim();
// Java URLConnection class sends an Accept header that includes a
// single "*" - Turn it into a legal wildcard.
if (fullType.equals("*")) {
fullType = "*/*";
}
String[] types = fullType.split("/", 2);
if (types.length != 2) {
results.type = types[0].trim();
return results;
}
results.type = types[0].trim();
results.subType = types[1].trim();
for (int i = 1; i < parts.length; ++i) {
String p = parts[i];
String[] subParts = p.split("=", 2);
if (subParts.length == 2) {
results.params.put(subParts[0].trim(), subParts[1].trim());
}
}
return results;
}
protected static ParseResults parseMediaRange(CharSequence range) {
ParseResults results = parseMimeType(range);
String q = results.params.get("q");
float f = toFloat(q, -1);
if (q == null || q.trim().isEmpty() || f < 0 || f > 1) {
results.params.put("q", "1");
}
return results;
}
/**
* Structure for holding a fitness/quality combo
*/
protected static class FitnessAndQuality implements
Comparable {
int fitness;
float quality;
String mimeType; // optionally used
public FitnessAndQuality(int fitness, float quality) {
this.fitness = fitness;
this.quality = quality;
}
public int compareTo(FitnessAndQuality o) {
if (fitness == o.fitness) {
if (quality == o.quality) {
return 0;
} else {
return quality < o.quality ? -1 : 1;
}
} else {
return fitness < o.fitness ? -1 : 1;
}
}
}
protected static FitnessAndQuality fitnessAndQualityParsed(CharSequence mimeType,
Collection parsedRanges) {
int bestFitness = -1;
float bestFitQ = 0;
ParseResults target = parseMediaRange(mimeType);
for (ParseResults range : parsedRanges) {
boolean typeMatch = target.type.equals(range.type) || range.type.equals("*") || target.type.equals("*");
boolean subTypeMatch;
if (target.subType == null || range.subType == null) {
subTypeMatch = Objects.equals(target.subType, range.subType);
} else {
subTypeMatch = target.subType.equals(range.subType) || range.subType.equals("*") || target.subType.equals("*");
}
if (typeMatch && subTypeMatch) {
for (String k : target.params.keySet()) {
int paramMatches = 0;
if (!k.equals("q") && range.params.containsKey(k)
&& target.params.get(k).equals(range.params.get(k))) {
paramMatches++;
}
int fitness = (range.type.equals(target.type)) ? 100 : 0;
fitness += (Objects.equals(range.subType, target.subType)) ? 10 : 0;
fitness += paramMatches;
if (fitness > bestFitness) {
bestFitness = fitness;
bestFitQ = toFloat(range.params.get("q"), 0);
}
}
}
}
return new FitnessAndQuality(bestFitness, bestFitQ);
}
public static String bestMatch(Iterable extends CharSequence> supported, String header) {
List parseResults = new ArrayList<>();
List weightedMatches = new ArrayList<>();
for (String r : header.split(",")) {
ParseResults parseResult = parseMediaRange(r);
if (parseResult != null) {
parseResults.add(parseResult);
}
}
for (CharSequence s : supported) {
FitnessAndQuality fitnessAndQuality = fitnessAndQualityParsed(s, parseResults);
fitnessAndQuality.mimeType = s.toString();
weightedMatches.add(fitnessAndQuality);
}
Collections.sort(weightedMatches);
FitnessAndQuality lastOne = weightedMatches.get(weightedMatches.size() - 1);
return lastOne.quality != 0f ? lastOne.mimeType : "";
}
private static float toFloat(String f, float ifNullOrNotANumber) {
try {
return f == null ? ifNullOrNotANumber : Float.valueOf(f);
} catch (NumberFormatException e) {
return ifNullOrNotANumber;
}
}
// hidden
private MimeParse() {
}
}