Rack::Utils contains a grab-bag of useful methods for writing web applications adopted from all kinds of Ruby libraries.
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?
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,"'
Responses with HTTP status codes that should not have an entity body
# 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
# 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
# File lib/rack/utils.rb, line 274 def bytesize(string) string.bytesize end
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 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
# 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
# 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
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
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
# 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
# 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
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