Rack::Utils

Rack::Utils contains a grab-bag of useful methods for writing web applications adopted from all kinds of Ruby libraries.

Constants

DEFAULT_SEP
ESCAPE_HTML
ESCAPE_HTML_PATTERN

On 1.8, there is a kcode = ‘u’ bug that allows for XSS otherwhise TODO doesn’t apply to jruby, so a better condition above might be preferable?

HTTP_STATUS_CODES

Every standard HTTP code mapped to the appropriate message. Generated with: irb -ropen-uri -rnokogiri > Nokogiri::XML(open(“www.iana.org/assignments/http-status-codes/http-status-codes.xml”)).css(“record”).each{|r|

puts "#{r.css('value').text} => '#{r.css('description').text}'"}
Multipart
STATUS_WITH_NO_ENTITY_BODY

Responses with HTTP status codes that should not have an entity body

SYMBOL_TO_STATUS_CODE

Attributes

key_space_limit[RW]

Public Class Methods

best_q_match(q_value_header, available_mimes) click to toggle source
# File lib/rack/utils.rb, line 181
def best_q_match(q_value_header, available_mimes)
  values = q_values(q_value_header)

  values.map do |req_mime, quality|
    match = available_mimes.first { |am| Rack::Mime.match?(am, req_mime) }
    next unless match
    [match, quality]
  end.compact.sort_by do |match, quality|
    (match.split('/', 2).count('*') * -10) + quality
  end.last.first
end
build_nested_query(value, prefix = nil) click to toggle source
# File lib/rack/utils.rb, line 150
def build_nested_query(value, prefix = nil)
  case value
  when Array
    value.map { |v|
      build_nested_query(v, "#{prefix}[]")
    }.join("&")
  when Hash
    value.map { |k, v|
      build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
    }.join("&")
  when String
    raise ArgumentError, "value must be a Hash" if prefix.nil?
    "#{prefix}=#{escape(value)}"
  else
    prefix
  end
end
build_query(params) click to toggle source
# File lib/rack/utils.rb, line 139
def build_query(params)
  params.map { |k, v|
    if v.class == Array
      build_query(v.map { |x| [k, x] })
    else
      v.nil? ? escape(k) : "#{escape(k)}=#{escape(v)}"
    end
  }.join("&")
end
byte_ranges(env, size) click to toggle source

Parses the “Range:” header, if present, into an array of Range objects. Returns nil if the header is missing or syntactically invalid. Returns an empty array if none of the ranges are satisfiable.

# File lib/rack/utils.rb, line 367
def byte_ranges(env, size)
  # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
  http_range = env['HTTP_RANGE']
  return nil unless http_range && http_range =~ /bytes=([^;]+)/
  ranges = []
  $1.split(/,\s*/).each do |range_spec|
    return nil  unless range_spec =~ /(\d*)-(\d*)/
    r0,r1 = $1, $2
    if r0.empty?
      return nil  if r1.empty?
      # suffix-byte-range-spec, represents trailing suffix of file
      r0 = size - r1.to_i
      r0 = 0  if r0 < 0
      r1 = size - 1
    else
      r0 = r0.to_i
      if r1.empty?
        r1 = size - 1
      else
        r1 = r1.to_i
        return nil  if r1 < r0  # backwards range is syntactically invalid
        r1 = size-1  if r1 >= size
      end
    end
    ranges << (r0..r1)  if r0 <= r1
  end
  ranges
end
bytesize(string) click to toggle source
# File lib/rack/utils.rb, line 333
def bytesize(string)
  string.bytesize
end
escape(s) click to toggle source

URI escapes. (CGI style space to +)

# File lib/rack/utils.rb, line 25
def escape(s)
  URI.encode_www_form_component(s)
end
escape_html(string) click to toggle source

Escape ampersands, brackets and quotes to their HTML/XML entities.

# File lib/rack/utils.rb, line 211
def escape_html(string)
  string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
end
escape_path(s) click to toggle source

Like URI escaping, but with %20 instead of +. Strictly speaking this is true URI escaping.

# File lib/rack/utils.rb, line 32
def escape_path(s)
  escape(s).gsub('+', '%20')
end
normalize_params(params, name, v = nil) click to toggle source
# File lib/rack/utils.rb, line 102
def normalize_params(params, name, v = nil)
  name =~ %(\A[\[\]]*([^\[\]]+)\]*)
  k = $1 || ''
  after = $' || ''

  return if k.empty?

  if after == ""
    params[k] = v
  elsif after == "[]"
    params[k] ||= []
    raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
    params[k] << v
  elsif after =~ %(^\[\]\[([^\[\]]+)\]$) || after =~ %(^\[\](.+)$)
    child_key = $1
    params[k] ||= []
    raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
    if params_hash_type?(params[k].last) && !params[k].last.key?(child_key)
      normalize_params(params[k].last, child_key, v)
    else
      params[k] << normalize_params(params.class.new, child_key, v)
    end
  else
    params[k] ||= params.class.new
    raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
    params[k] = normalize_params(params[k], after, v)
  end

  return params
end
params_hash_type?(obj) click to toggle source
# File lib/rack/utils.rb, line 134
def params_hash_type?(obj)
  obj.kind_of?(KeySpaceConstrainedParams) || obj.kind_of?(Hash)
end
parse_nested_query(qs, d = nil) click to toggle source
# File lib/rack/utils.rb, line 89
def parse_nested_query(qs, d = nil)
  params = KeySpaceConstrainedParams.new

  (qs || '').split(d ? /[#{d}] */ : DEFAULT_SEP).each do |p|
    k, v = p.split('=', 2).map { |s| unescape(s) }

    normalize_params(params, k, v)
  end

  return params.to_params_hash
end
parse_query(qs, d = nil, &unescaper) click to toggle source

Stolen from Mongrel, with some small modifications: Parses a query string by breaking it up at the ‘&’ and ‘;’ characters. You can also use this to parse cookies by changing the characters used in the second parameter (which defaults to ‘&;’).

# File lib/rack/utils.rb, line 65
def parse_query(qs, d = nil, &unescaper)
  unescaper ||= method(:unescape)

  params = KeySpaceConstrainedParams.new

  (qs || '').split(d ? /[#{d}] */ : DEFAULT_SEP).each do |p|
    next if p.empty?
    k, v = p.split('=', 2).map(&unescaper)

    if cur = params[k]
      if cur.class == Array
        params[k] << v
      else
        params[k] = [cur, v]
      end
    else
      params[k] = v
    end
  end

  return params.to_params_hash
end
q_values(q_value_header) click to toggle source
# File lib/rack/utils.rb, line 169
def q_values(q_value_header)
  q_value_header.to_s.split(/\s*,\s*/).map do |part|
    value, parameters = part.split(/\s*;\s*/, 2)
    quality = 1.0
    if md = /\Aq=([\d.]+)/.match(parameters)
      quality = md[1].to_f
    end
    [value, quality]
  end
end
rfc2109(time) click to toggle source

Modified version of stdlib time.rb Time#rfc2822 to use ‘%d-%b-%Y’ instead of ‘% %b %Y’. It assumes that the time is in GMT to comply to the RFC 2109.

NOTE: I’m not sure the RFC says it requires GMT, but is ambigous enough that I’m certain someone implemented only that option. Do not use %a and %b from Time.strptime, it would use localized names for weekday and month.

# File lib/rack/utils.rb, line 357
def rfc2109(time)
  wday = Time::RFC2822_DAY_NAME[time.wday]
  mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
  time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
end
rfc2822(time) click to toggle source
# File lib/rack/utils.rb, line 343
def rfc2822(time)
  time.rfc2822
end
select_best_encoding(available_encodings, accept_encoding) click to toggle source
# File lib/rack/utils.rb, line 216
def select_best_encoding(available_encodings, accept_encoding)
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

  expanded_accept_encoding =
    accept_encoding.map { |m, q|
      if m == "*"
        (available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] }
      else
        [[m, q]]
      end
    }.inject([]) { |mem, list|
      mem + list
    }

  encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m }

  unless encoding_candidates.include?("identity")
    encoding_candidates.push("identity")
  end

  expanded_accept_encoding.find_all { |m, q|
    q == 0.0
  }.each { |m, _|
    encoding_candidates.delete(m)
  }

  return (encoding_candidates & available_encodings)[0]
end
status_code(status) click to toggle source
# File lib/rack/utils.rb, line 603
def status_code(status)
  if status.is_a?(Symbol)
    SYMBOL_TO_STATUS_CODE[status] || 500
  else
    status.to_i
  end
end
unescape(s, encoding = Encoding::UTF_8) click to toggle source
# File lib/rack/utils.rb, line 40
def unescape(s, encoding = Encoding::UTF_8)
  URI.decode_www_form_component(s, encoding)
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.