All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ratpack.http.internal.MimeParse Maven / Gradle / Ivy

There is a newer version: 2.0.0-rc-1
Show newest version
/**
 * 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 supported, String header) {
    List parseResults = new LinkedList<>();
    List weightedMatches = new LinkedList<>();
    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() {
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy