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

curl -s http:/%rwww.iana.org/assignments/http-status-codes |    ruby -ane 'm = /^(\d{3}) +(\S[^\[(]+)/.match($_) and
             puts "      #{m[1]}  => \x27#{m[2].strip}x27,"'
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

build_nested_query(value, prefix = nil) click to toggle source
# File lib/rack/utils.rb, line 141
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 130
def build_query(params)
  params.map { |k, v|
    if v.class == Array
      build_query(v.map { |x| [k, x] })
    else
      "#{escape(k)}=#{escape(v)}"
    end
  }.join("&")
end
bytesize(string) click to toggle source
# File lib/rack/utils.rb, line 274
def bytesize(string)
  string.bytesize
end
escape(s) click to toggle source

Performs URI escaping so that you can construct proper query strings faster. Use this rather than the cgi.rb version since it’s faster. (Stolen from Camping).

# File lib/rack/utils.rb, line 14
def escape(s)
  s.to_s.gsub(%r([^ a-zA-Z0-9_.-]+)/) {
    '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
  }.tr(' ', '+')
end
escape_html(string) click to toggle source

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

# File lib/rack/utils.rb, line 177
def escape_html(string)
  string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
end
normalize_params(params, name, v = nil) click to toggle source
# File lib/rack/utils.rb, line 98
def normalize_params(params, name, v = nil)
  name =~ %r(\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 =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
    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[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
      normalize_params(params[k].last, child_key, v)
    else
      params[k] << normalize_params({}, child_key, v)
    end
  else
    params[k] ||= {}
    raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
    params[k] = normalize_params(params[k], after, v)
  end

  return params
end
parse_nested_query(qs, d = nil) click to toggle source
# File lib/rack/utils.rb, line 75
def parse_nested_query(qs, d = nil)
  params = {}

  max_key_space = Utils.key_space_limit
  bytes = 0

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

    if k
      bytes += k.size
      if bytes > max_key_space
        raise RangeError, "exceeded available parameter key space"
      end
    end

    normalize_params(params, k, v)
  end

  return params
end
parse_query(qs, d = nil) 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 44
def parse_query(qs, d = nil)
  params = {}

  max_key_space = Utils.key_space_limit
  bytes = 0

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

    if k
      bytes += k.size
      if bytes > max_key_space
        raise RangeError, "exceeded available parameter key space"
      end
    end

    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
end
rfc2822(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 293
def rfc2822(time)
  wday = Time::RFC2822_DAY_NAME[time.wday]
  mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
  time.strftime("#{wday}, %d-#{mon}-%Y %T GMT")
end
select_best_encoding(available_encodings, accept_encoding) click to toggle source
# File lib/rack/utils.rb, line 182
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 466
def status_code(status)
  if status.is_a?(Symbol)
    SYMBOL_TO_STATUS_CODE[status] || 500
  else
    status.to_i
  end
end
unescape(s) click to toggle source

Unescapes a URI escaped string. (Stolen from Camping).

# File lib/rack/utils.rb, line 22
def unescape(s)
  s.tr('+', ' ').gsub(%r((?:%[0-9a-fA-F]{2})+)/){
    [$1.delete('%')].pack('H*')
  }
end