Module: Rack::Acceptable::MIMETypes

Defined in:
lib/rack/acceptable/mimetypes.rb

Constant Summary collapse

MEDIA_RANGE_REGEX =
/^\s*(#{Utils::TOKEN_PATTERN})\/(#{Utils::TOKEN_PATTERN})\s*$/o.freeze
REGISTRY_PATH =
::File.expand_path(::File.join(::File.dirname(__FILE__), 'data', 'mime.types')).freeze
REGISTRY =
{}
EXTENSIONS =
{}

Class Method Summary collapse

Class Method Details

.clearObject

Empties the registry.



263
264
265
266
267
# File 'lib/rack/acceptable/mimetypes.rb', line 263

def clear
  EXTENSIONS.clear
  REGISTRY.clear
  nil
end

.delete(thing) ⇒ Object

Deletes the MIME-Type (and associated extensions) from registry.



249
250
251
252
# File 'lib/rack/acceptable/mimetypes.rb', line 249

def delete(thing)
  REGISTRY.delete_if { |_,v| v == thing }
  EXTENSIONS.delete thing
end

.detect_best_mime_type(provides, accepts, by_qvalue_only = false) ⇒ Object

Parameters

provides<Array>

The Array of available Media-Types (snippets or parsed). Could be empty.

accepts<String>

The Array of acceptable Media-Ranges. Could be empty.

by_qvalue_only<String>

Optional flag, see MIMETypes#weigh_mime_type. Default is false.

Returns

The best one of available Media-Types or nil.

Raises

Same things as Utils#parse_media_range.

Notes

Acceptable Media-Types are supposed to have downcased and well-formed type, subtype, parameter’s keys (according to RFC 2616, enumerated things are case-insensitive too), and sensible qvalues (“real numbers in the range 0 through 1, where 0 is the minimum and 1 the maximum value”).



224
225
226
227
228
229
230
# File 'lib/rack/acceptable/mimetypes.rb', line 224

def detect_best_mime_type(provides, accepts, by_qvalue_only = false)
  return nil if provides.empty?
  return provides.first if accepts.empty?
  i = 1
  candidate = provides.map { |t| weigh_mime_type(t,accepts,by_qvalue_only) << i-=1 }.max
  candidate.at(0) == 0 ? nil : provides.at(-candidate.last)
end

.extension_for(thing, fallback = nil) ⇒ Object



258
259
260
# File 'lib/rack/acceptable/mimetypes.rb', line 258

def extension_for(thing, fallback = nil)
  EXTENSIONS.fetch thing, fallback
end

.load_from(file) ⇒ Object

Loads the set of MIME-Types from the Apache compatible mime.types file. original source: webrick.



278
279
280
281
282
283
284
285
286
287
# File 'lib/rack/acceptable/mimetypes.rb', line 278

def load_from(file)
  open(file) do |io|
    io.each do |line|
      line.strip!
      next if line.empty? || /^#/ === line
      register *line.split(/\s+/)
    end
  end
  true
end

.lookup(ext, fallback = 'application/octet-stream') ⇒ Object



254
255
256
# File 'lib/rack/acceptable/mimetypes.rb', line 254

def lookup(ext, fallback = 'application/octet-stream')
  REGISTRY.fetch(ext[0] == ?. ? ext.downcase : ".#{ext.downcase}", fallback)
end

.parse_media_range(thing) ⇒ Object

Parameters

thing<String>

The Media-Type snippet or the single item from the HTTP_ACCEPT request-header, without ‘q’ parameter, accept-extensions and so on.

Returns

Array[String, String, Hash]

Media-Range: type, subtype and parameter (as a Hash).

Raises

Same things as Utils#split_mime_type. In other words, it checks only type/subtype pair.

Raises:

  • (ArgumentError)


37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/rack/acceptable/mimetypes.rb', line 37

def parse_media_range(thing)
  snippets = thing.split(Utils::SEMICOLON_SPLITTER)
  raise ArgumentError, "Malformed MIME-Type: #{thing}" unless MEDIA_RANGE_REGEX === snippets.shift

  type = $1
  subtype = $2
  type.downcase!
  subtype.downcase!

  raise ArgumentError,
    "Malformed MIME-Type: #{thing}" if type == Const::WILDCARD && subtype != Const::WILDCARD

  params = {}
  snippets.each do |pair|
    pair.strip!
    k,v = pair.split(Utils::PAIR_SPLITTER,2)
    k.downcase!
    params[k] = v
  end

  [type, subtype, params]
end

.parse_mime_type(thing) ⇒ Object

Parameters

thing<String>

The Media-Type snippet or the single item from the HTTP_ACCEPT request-header.

Returns

Array[String, String, Hash, Float, Hash]

Media-Range (type, subtype and parameter, as a Hash), quality factor and accept-extension (as a Hash, if any, or nil) of the MIME-Type.

Raises

ArgumentError

There’s a malformed quality factor, or type/subtype pair is not in a RFC ‘Media-Range’ pattern.

Raises:

  • (ArgumentError)


74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/rack/acceptable/mimetypes.rb', line 74

def parse_mime_type(thing)

  snippets = thing.split(Utils::SEMICOLON_SPLITTER)
  raise ArgumentError, "Malformed MIME-Type: #{thing}" unless MEDIA_RANGE_REGEX === snippets.shift

  type = $1
  subtype = $2
  type.downcase!
  subtype.downcase!

  raise ArgumentError,
    "Malformed MIME-Type: #{thing}" if type == Const::WILDCARD && subtype != Const::WILDCARD

  qvalue = Utils::QVALUE_DEFAULT
  params = {}
  has_qvalue = false
  accept_extension = nil

  for pair in snippets
    pair.strip!
    k,v = pair.split(Utils::PAIR_SPLITTER,2)

    # RFC 2616, sec. 14.1:
    # Each media-range MAY be followed by one or more accept-params,
    # beginning with the "q" parameter for indicating a relative quality
    # factor. The first "q" parameter (if any) separates the media-range
    # parameter(s) from the accept-params. Quality factors allow the user
    # or user agent to indicate the relative degree of preference for that
    # media-range, using the qvalue scale from 0 to 1 (section 3.9). The
    # default value is q=1.

    if has_qvalue
      accept_extension ||= {}
      accept_extension[k] = v || true # token [ "=" ( token | quoted-string ) ] - i.e, "v" is OPTIONAL.
    else
      k.downcase!
      if k == Utils::QVALUE
        raise ArgumentError, "Malformed quality factor: #{v.inspect}" unless Utils::QVALUE_REGEX === v
        qvalue = v.to_f
        has_qvalue = true
      else
        params[k] = v
      end
    end

  end

  [type, subtype, params, qvalue, accept_extension]
end

.qualify_mime_type(thing, types) ⇒ Object

Parameters

thing<String, Array>

The Media-Type snippet or parsed Media-Type.

types<Array>

Parsed HTTP_ACCEPT request-header to check against.

Returns

Float

The quality factor (relative strength of the Media-Type).



131
132
133
# File 'lib/rack/acceptable/mimetypes.rb', line 131

def qualify_mime_type(thing, types)
  weigh_mime_type(thing, types).first
end

.register(thing, *extensions) ⇒ Object

Registers the new MIME-Type and associated extensions. The first one of extensions will be treated as the ‘preferred’ for the MIME-Type.



240
241
242
243
244
245
246
# File 'lib/rack/acceptable/mimetypes.rb', line 240

def register(thing, *extensions)
  return if extensions.empty?
  extensions.map! { |ext| ext[0] == ?. ? ext.downcase : ".#{ext.downcase}" }
  extensions.each { |ext| REGISTRY[ext] = thing }
  EXTENSIONS[thing] = extensions.first
  nil
end

.resetObject

Resets the registry, i.e removes all and loads the default set of the MIME-Types.



271
272
273
274
# File 'lib/rack/acceptable/mimetypes.rb', line 271

def reset
  clear
  load_from(REGISTRY_PATH)
end

.weigh_mime_type(thing, types, qvalue_only = false) ⇒ Object

Parameters

thing<String, Array>

The Media-Type snippet or parsed Media-Type.

types<Array>

Parsed HTTP_ACCEPT request-header to check against.

qvalue_only<Boolean>

Flag to force weighting to return the qvalue only. Optional. Default is false.

Returns

Array[Float, Integer, Integer, Integer] or Array

Quality factor, rate, specificity and negated index of the most relevant Media-Range; i.e full relative weight of the Media-Type. If qvaulue_only option is set to true, returns qvalue only.



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/rack/acceptable/mimetypes.rb', line 147

def weigh_mime_type(thing, types, qvalue_only = false)

  type, subtype, params = thing.is_a?(String) ? parse_media_range(thing) : thing

  rate = 0
  specificity = -1
  quality = 0.00
  index = 0

  # RFC 2616, sec. 14.1:
  # Media ranges can be overridden by more specific media ranges or
  # specific media types. If more than one media range applies to a given
  # type, the most specific reference has precedence.
  # ...
  # The media type quality factor associated with a given type is
  # determined by finding the media range with the highest precedence
  # which matches that type.

  type_is_a_wildcard = type == Const::WILDCARD
  subtype_is_a_wildcard = subtype == Const::WILDCARD

  types.each_with_index do |(t,s,p,q),i|
    next unless (type_is_a_wildcard     || t == type    || no_type_match    = t == Const::WILDCARD) &&
                (subtype_is_a_wildcard  || s == subtype || no_subtype_match = s == Const::WILDCARD)

    # we should skip when:
    # - divergence: 
    #     * "text;html;a=2" against "text/html;a=1,text/*;a=1" etc
    #     * "text/html;b=1" or "text/html" against "text/html;a=1" etc,
    #       i.e, 'a' parameter is NECESSARY, but our MIME-Type does NOT contain it
    # - rate is lesser
    # - rates are equal, but sp(ecificity) is lesser or exactly the same

    r  = no_type_match    ? 0 : 10
    r += no_subtype_match ? 0 : 1

    next if r < rate

    sp = 0
    p.each do |k,v|
      if params.key?(k) && params[k] == v
        sp += 1
      else
        sp = -1
        break
      end
    end

    #next if sp == -1 || (r == rate && (sp < specificity || sp == specificity && quality > q))
    if sp > -1 && (r > rate || (sp > specificity || sp == specificity && quality < q))
      specificity = sp
      rate = r
      quality = q
      index = i
    end
  end

  qvalue_only ? [quality] : [quality, rate, specificity, -index]
end