Module: Rack::Acceptable::Utils

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

Constant Summary collapse

QUALITY_PATTERN =
'\s*(?:;\s*q=(0(?:\.\d{0,3})?|1(?:\.0{0,3})?))?'.freeze
QVALUE_REGEX =
/\A(?:0(?:\.\d{0,3})?|1(?:\.0{0,3})?)\z/.freeze
QUALITY_REGEX =
/\s*;\s*q\s*=([^;\s]*)/i.freeze
QVALUE_DEFAULT =
1.00
QVALUE =
'q'.freeze
PAIR_SPLITTER =
'='.freeze
HYPHEN_SPLITTER =
Const::HYPHEN
COMMA_SPLITTER =
Const::COMMA
SEMICOLON_SPLITTER =
Const::SEMICOLON
COMMA_WS_SPLITTER =
/,\s*/.freeze
TOKEN_PATTERN =
"[A-Za-z0-9#{Regexp.escape('!#$&%\'*+-.^_`|~')}]+".freeze
HTTP_ACCEPT_TOKEN_REGEX =

:startdoc:

/^\s*(#{Utils::TOKEN_PATTERN})#{Utils::QUALITY_PATTERN}\s*$/o.freeze
HTTP_ACCEPT_LANGUAGE_REGEX =

TODO: move to LanguageTag or Languages

/^\s*(\*|[a-z]{1,8}(?:-[a-z\d]{1,8}|-\*)*)#{Utils::QUALITY_PATTERN}\s*$/io.freeze

Class Method Summary collapse

Class Method Details

.blank?(s) ⇒ Boolean

Returns:

  • (Boolean)


224
225
226
# File 'lib/rack/acceptable/utils.rb', line 224

def blank?(s)
  s.empty? || s.strip.empty?
end

.detect_best_charset(provides, accepts) ⇒ Object

Parameters

provides<Array>

The Array of available Charsets. Could be empty.

accepts<Array>

The Array of acceptable Charsets. Could be empty.

Returns

The best one of available Charsets (as a String) or nil.

Notes

Available and acceptable Charsets are supposed to be downcased. According to section 3.4 of RFC 2616, Charsets are case-insensitive.



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
# File 'lib/rack/acceptable/utils.rb', line 161

def detect_best_charset(provides, accepts)
  return nil if provides.empty?

  # RFC 2616, sec 14.2:
  # If no Accept-Charset header is present, the default is that any
  # character set is acceptable. If an Accept-Charset header is present,
  # and if the server cannot send a response which is acceptable
  # according to the Accept-Charset header, then the server SHOULD send
  # an error response with the 406 (not acceptable) status code, though
  # the sending of an unacceptable response is also allowed.

  return provides.first if accepts.empty?

  expansion = nil
  candidates = []
  i = 0

  accepts << [Const::ISO_8859_1, 1.0] unless accepts.assoc(Const::ISO_8859_1) || accepts.assoc(Const::WILDCARD)

  accepts.sort_by { |_,q| [-q,i+=1] }.each do |c,q|

    next if q == 0

    if c == Const::WILDCARD

      # RFC 2616, sec 14.2:
      # The special value "*", if present in the Accept-Charset field,
      # matches every character set (including ISO-8859-1) which is not
      # mentioned elsewhere in the Accept-Charset field. If no "*" is present
      # in an Accept-Charset field, then all character sets not explicitly
      # mentioned get a quality value of 0, except for ISO-8859-1, which gets
      # a quality value of 1 if not explicitly mentioned.

      expansion ||= provides - accepts.map { |c,_| c }
      candidates.concat expansion
    else
      candidates << c
    end
  end

  (candidates & provides).first
end

.detect_best_encoding(provides, accepts) ⇒ Object

Parameters

provides<Array>

The Array of available Content-Codings. Could be empty.

accepts<Array>

The Array of acceptable Content-Codings. Could be empty.

Returns

The best one of available Content-Codings (as a String) or nil.

Notes

Available and acceptable Content-Codings are supposed to be downcased According to section 3.5 of RFC 2616, Content-Codings are case-insensitive.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/rack/acceptable/utils.rb', line 105

def detect_best_encoding(provides, accepts)
  return nil if provides.empty?

  identity = provides.include?(Const::IDENTITY) # presence of 'identity' in available content-codings
  identity_or_wildcard = true # absence of explicit 'identity;q=0' or '*;q=0'

  # RFC 2616, sec. 14.3:
  # If no Accept-Encoding field is present in a request, the server
  # MAY assume that the client will accept any content coding. In this
  # case, if "identity" is one of the available content-codings, then
  # the server SHOULD use the "identity" content-coding, unless it has
  # additional information that a different content-coding is meaningful
  # to the client.

  return Const::IDENTITY if identity && accepts.empty?
  #return (identity ? Const::IDENTITY : provides.first) if accepts.empty?

  # RFC 2616, sec. 14.3:
  # The "identity" content-coding is always acceptable, unless
  # specifically refused because the Accept-Encoding field includes
  # "identity;q=0", or because the field includes "*;q=0" and does
  # not explicitly include the "identity" content-coding. If the
  # Accept-Encoding field-value is empty, then only the "identity"
  # encoding is acceptable.

  candidates = []
  expansion = nil
  i = 0

  accepts.sort_by { |_,q| [-q,i+=1] }.each do |c,q|
    if q == 0
      identity_or_wildcard = false if c == Const::IDENTITY || c == Const::WILDCARD
    elsif c == Const::WILDCARD
      expansion ||= provides - accepts.map { |c| c.first }
      candidates.concat expansion
    else
      candidates << c
    end
  end

  (candidates & provides).first ||
  (Const::IDENTITY if identity && identity_or_wildcard) ||
  nil
end

.extract_qvalues(header) ⇒ Object

Parameters

header<String>

The ‘Accept’ request-header, one of:

  • Accept

  • Accept-Charset

  • Accept-Encoding

  • Accept-Language

Returns

Result of parsing. An Array with entries (as a Strings) and associated quality factors (qvalues). Default qvalue is 1.0.

Raises

ArgumentError

There’s a malformed qvalue in header.

Notes

  • It checks only quality factors (full syntactical inspection of the HTTP header is not a task of simple qvalues extractor).

  • It does not perform additional operations (downcase etc), thereto a bunch of more specific parsers is provided.



65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/rack/acceptable/utils.rb', line 65

def extract_qvalues(header)
  header.split(COMMA_WS_SPLITTER).map! { |entry|
    QUALITY_REGEX === entry
    thing = $` || entry
    if !(qvalue = $1)
      [thing, QVALUE_DEFAULT]
    elsif QVALUE_REGEX === qvalue
      [thing, qvalue.to_f]
    else
      raise ArgumentError, "Malformed quality factor: #{qvalue.inspect}"
    end
  }
end

.normalize_header(header) ⇒ Object



217
218
219
220
221
222
# File 'lib/rack/acceptable/utils.rb', line 217

def normalize_header(header)
  ret = header.strip
  ret.gsub!(/\s*(?:,\s*)+/, Const::COMMA)
  ret.gsub!(/^,|,$/, Const::EMPTY_STRING)
  ret
end

.parse_header(header, regex) ⇒ Object

:stopdoc:



81
82
83
84
85
86
# File 'lib/rack/acceptable/utils.rb', line 81

def parse_header(header, regex)
  header.split(COMMA_SPLITTER).map! do |entry|
    raise unless regex === entry
    [$1, ($2 || QVALUE_DEFAULT).to_f]
  end
end