Module: Wants::MIMEParse

Included in:
TestMimeParsing
Defined in:
lib/wants/mimeparse.rb

Overview

From code.google.com/p/mimeparse

This module 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

This is a port of Joe Gregario’s mimeparse.py, which can be found at

<http://code.google.com/p/mimeparse/>.

ported from version 0.1.2

Comments are mostly excerpted from the original.

Class Method Summary collapse

Class Method Details

.best_match(supported, header) ⇒ Object

Takes a list of supported mime-types and finds the best match

for all the media-ranges listed in header. The value of header
must be a string that conforms to the format of the HTTP Accept:
header. The value of supported is an Enumerable of mime-types

   irb> best_match(["application/xbel+xml", "text/xml"], "text/*;q=0.5,*/*; q=0.1")
   => "text/xml"


127
128
129
130
131
132
133
134
135
136
137
# File 'lib/wants/mimeparse.rb', line 127

def best_match(supported, header)
  parsed_header = header.split(",").map { |r| parse_media_range(r) }

  weighted_matches = supported.map do |mime_type|
    [fitness_and_quality_parsed(mime_type, parsed_header), mime_type]
  end

  weighted_matches.sort!

  weighted_matches.last[0][1].zero? ? nil : weighted_matches.last[1]
end

.fitness_and_quality_parsed(mime_type, parsed_ranges) ⇒ Object

Find the best match for a given mime-type against a list of

media_ranges that have already been parsed by #parse_media_range

Returns the fitness and the “q” quality parameter of the best match,

or [-1, 0] if no match was found. Just as for #quality_parsed,
"parsed_ranges" must be an Enumerable of parsed media ranges.


76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/wants/mimeparse.rb', line 76

def fitness_and_quality_parsed(mime_type, parsed_ranges)
  best_fitness = -1
  best_fit_q = 0
  target_type, target_subtype, target_params = parse_media_range(mime_type)

  parsed_ranges.each do |type,subtype,params|
    if (type == target_type or type == "*" or target_type == "*") and
        (subtype == target_subtype or subtype == "*" or target_subtype == "*")
      param_matches = target_params.find_all { |k,v| k != "q" and params.has_key?(k) and v == params[k] }.length

      fitness = (type == target_type) ? 100 : 0
      fitness += (subtype == target_subtype) ? 10 : 0
      fitness += param_matches

      if fitness > best_fitness
        best_fitness = fitness
        best_fit_q = params["q"]
      end
    end
  end

  [best_fitness, best_fit_q.to_f]
end

.parse_media_range(range) ⇒ Object

Carves up a media range and returns an Array of the

[type, subtype, params] where "params" is a Hash of all
the parameters for the media range.

For example, the media range “application/*;q=0.5” would

get parsed into:
“application”, “*”, { “q”, “0.5” }

In addition this function also guarantees that there

is a value for "q" in the params dictionary, filling it
in with a proper default if necessary.


61
62
63
64
65
66
67
68
# File 'lib/wants/mimeparse.rb', line 61

def parse_media_range(range)
  type, subtype, params = parse_mime_type(range)
  unless params.has_key?("q") and params["q"] and params["q"].to_f and params["q"].to_f <= 1 and params["q"].to_f >= 0
    params["q"] = "1"
  end

  [type, subtype, params]
end

.parse_mime_type(mime_type) ⇒ Object

Carves up a mime-type and returns an Array of the

[type, subtype, params] where "params" is a Hash of all
the parameters for the media range.

For example, the media range “application/xhtml;q=0.5” would

get parsed into:
“application”, “xhtml”, { “q” => “0.5” }


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/wants/mimeparse.rb', line 29

def parse_mime_type(mime_type)
  parts = mime_type.split(";")

  params = {}

  parts[1..-1].map do |param|
    k,v = param.split("=").map { |s| s.strip }
    params[k] = v
  end

  full_type = parts[0].strip
  # Java URLConnection class sends an Accept header that includes a single "*"
  # Turn it into a legal wildcard.
  full_type = "*/*" if full_type == "*"
  type, subtype = full_type.split("/")
  raise "malformed mime type" unless subtype

  [type.strip, subtype.strip, params]
end

.quality(mime_type, ranges) ⇒ Object

Returns the quality “q” of a mime_type when compared against

the media-ranges in ranges. For example:

   irb> quality("text/html", "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5")
   => 0.7


115
116
117
118
# File 'lib/wants/mimeparse.rb', line 115

def quality(mime_type, ranges)
  parsed_ranges = ranges.split(",").map { |r| parse_media_range(r) }
  quality_parsed(mime_type, parsed_ranges)
end

.quality_parsed(mime_type, parsed_ranges) ⇒ Object

Find the best match for a given mime-type against a list of

media_ranges that have already been parsed by #parse_media_range

Returns the “q” quality parameter of the best match, 0 if no match

was found. This function behaves the same as #quality except that
"parsed_ranges" must be an Enumerable of parsed media ranges.


106
107
108
# File 'lib/wants/mimeparse.rb', line 106

def quality_parsed(mime_type, parsed_ranges)
  fitness_and_quality_parsed(mime_type, parsed_ranges)[1]
end