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://www.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 145 145: def build_nested_query(value, prefix = nil) 146: case value 147: when Array 148: value.map { |v| 149: build_nested_query(v, "#{prefix}[]") 150: }.join("&") 151: when Hash 152: value.map { |k, v| 153: build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k)) 154: }.join("&") 155: when String 156: raise ArgumentError, "value must be a Hash" if prefix.nil? 157: "#{prefix}=#{escape(value)}" 158: else 159: prefix 160: end 161: end
# File lib/rack/utils.rb, line 134 134: def build_query(params) 135: params.map { |k, v| 136: if v.class == Array 137: build_query(v.map { |x| [k, x] }) 138: else 139: v.nil? ? escape(k) : "#{escape(k)}=#{escape(v)}" 140: end 141: }.join("&") 142: end
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 309 309: def byte_ranges(env, size) 310: # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35> 311: http_range = env['HTTP_RANGE'] 312: return nil unless http_range 313: ranges = [] 314: http_range.split(/,\s*/).each do |range_spec| 315: matches = range_spec.match(/bytes=(\d*)-(\d*)/) 316: return nil unless matches 317: r0,r1 = matches[1], matches[2] 318: if r0.empty? 319: return nil if r1.empty? 320: # suffix-byte-range-spec, represents trailing suffix of file 321: r0 = [size - r1.to_i, 0].max 322: r1 = size - 1 323: else 324: r0 = r0.to_i 325: if r1.empty? 326: r1 = size - 1 327: else 328: r1 = r1.to_i 329: return nil if r1 < r0 # backwards range is syntactically invalid 330: r1 = size-1 if r1 >= size 331: end 332: end 333: ranges << (r0..r1) if r0 <= r1 334: end 335: ranges 336: end
# File lib/rack/utils.rb, line 280 280: def bytesize(string) 281: string.bytesize 282: end
# File lib/rack/utils.rb, line 284 284: def bytesize(string) 285: string.size 286: end
URI escapes. (CGI style space to +)
# File lib/rack/utils.rb, line 23 23: def escape(s) 24: URI.encode_www_form_component(s) 25: end
Escape ampersands, brackets and quotes to their HTML/XML entities.
# File lib/rack/utils.rb, line 181 181: def escape_html(string) 182: string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] } 183: end
Like URI escaping, but with %20 instead of +. Strictly speaking this is true URI escaping.
# File lib/rack/utils.rb, line 30 30: def escape_path(s) 31: escape(s).gsub('+', '%20') 32: end
# File lib/rack/utils.rb, line 97 97: def normalize_params(params, name, v = nil) 98: name =~ %(\A[\[\]]*([^\[\]]+)\]*) 99: k = $1 || '' 100: after = $' || '' 101: 102: return if k.empty? 103: 104: if after == "" 105: params[k] = v 106: elsif after == "[]" 107: params[k] ||= [] 108: raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array) 109: params[k] << v 110: elsif after =~ %(^\[\]\[([^\[\]]+)\]$) || after =~ %(^\[\](.+)$) 111: child_key = $1 112: params[k] ||= [] 113: raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array) 114: if params_hash_type?(params[k].last) && !params[k].last.key?(child_key) 115: normalize_params(params[k].last, child_key, v) 116: else 117: params[k] << normalize_params(params.class.new, child_key, v) 118: end 119: else 120: params[k] ||= params.class.new 121: raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k]) 122: params[k] = normalize_params(params[k], after, v) 123: end 124: 125: return params 126: end
# File lib/rack/utils.rb, line 129 129: def params_hash_type?(obj) 130: obj.kind_of?(KeySpaceConstrainedParams) || obj.kind_of?(Hash) 131: end
# File lib/rack/utils.rb, line 84 84: def parse_nested_query(qs, d = nil) 85: params = KeySpaceConstrainedParams.new 86: 87: (qs || '').split(d ? /[#{d}] */ : DEFAULT_SEP).each do |p| 88: k, v = p.split('=', 2).map { |s| unescape(s) } 89: 90: normalize_params(params, k, v) 91: end 92: 93: return params.to_params_hash 94: 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 63 63: def parse_query(qs, d = nil) 64: params = KeySpaceConstrainedParams.new 65: 66: (qs || '').split(d ? /[#{d}] */ : DEFAULT_SEP).each do |p| 67: k, v = p.split('=', 2).map { |x| unescape(x) } 68: 69: if cur = params[k] 70: if cur.class == Array 71: params[k] << v 72: else 73: params[k] = [cur, v] 74: end 75: else 76: params[k] = v 77: end 78: end 79: 80: return params.to_params_hash 81: 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 299 299: def rfc2822(time) 300: wday = Time::RFC2822_DAY_NAME[time.wday] 301: mon = Time::RFC2822_MONTH_NAME[time.mon - 1] 302: time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT") 303: end
# File lib/rack/utils.rb, line 186 186: def select_best_encoding(available_encodings, accept_encoding) 187: # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html 188: 189: expanded_accept_encoding = 190: accept_encoding.map { |m, q| 191: if m == "*" 192: (available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] } 193: else 194: [[m, q]] 195: end 196: }.inject([]) { |mem, list| 197: mem + list 198: } 199: 200: encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m } 201: 202: unless encoding_candidates.include?("identity") 203: encoding_candidates.push("identity") 204: end 205: 206: expanded_accept_encoding.find_all { |m, q| 207: q == 0.0 208: }.each { |m, _| 209: encoding_candidates.delete(m) 210: } 211: 212: return (encoding_candidates & available_encodings)[0] 213: end
# File lib/rack/utils.rb, line 535 535: def status_code(status) 536: if status.is_a?(Symbol) 537: SYMBOL_TO_STATUS_CODE[status] || 500 538: else 539: status.to_i 540: end 541: end
# File lib/rack/utils.rb, line 145 145: def build_nested_query(value, prefix = nil) 146: case value 147: when Array 148: value.map { |v| 149: build_nested_query(v, "#{prefix}[]") 150: }.join("&") 151: when Hash 152: value.map { |k, v| 153: build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k)) 154: }.join("&") 155: when String 156: raise ArgumentError, "value must be a Hash" if prefix.nil? 157: "#{prefix}=#{escape(value)}" 158: else 159: prefix 160: end 161: end
# File lib/rack/utils.rb, line 134 134: def build_query(params) 135: params.map { |k, v| 136: if v.class == Array 137: build_query(v.map { |x| [k, x] }) 138: else 139: v.nil? ? escape(k) : "#{escape(k)}=#{escape(v)}" 140: end 141: }.join("&") 142: end
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 309 309: def byte_ranges(env, size) 310: # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35> 311: http_range = env['HTTP_RANGE'] 312: return nil unless http_range 313: ranges = [] 314: http_range.split(/,\s*/).each do |range_spec| 315: matches = range_spec.match(/bytes=(\d*)-(\d*)/) 316: return nil unless matches 317: r0,r1 = matches[1], matches[2] 318: if r0.empty? 319: return nil if r1.empty? 320: # suffix-byte-range-spec, represents trailing suffix of file 321: r0 = [size - r1.to_i, 0].max 322: r1 = size - 1 323: else 324: r0 = r0.to_i 325: if r1.empty? 326: r1 = size - 1 327: else 328: r1 = r1.to_i 329: return nil if r1 < r0 # backwards range is syntactically invalid 330: r1 = size-1 if r1 >= size 331: end 332: end 333: ranges << (r0..r1) if r0 <= r1 334: end 335: ranges 336: end
# File lib/rack/utils.rb, line 284 284: def bytesize(string) 285: string.size 286: end
# File lib/rack/utils.rb, line 280 280: def bytesize(string) 281: string.bytesize 282: end
URI escapes. (CGI style space to +)
# File lib/rack/utils.rb, line 23 23: def escape(s) 24: URI.encode_www_form_component(s) 25: end
Escape ampersands, brackets and quotes to their HTML/XML entities.
# File lib/rack/utils.rb, line 181 181: def escape_html(string) 182: string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] } 183: end
Like URI escaping, but with %20 instead of +. Strictly speaking this is true URI escaping.
# File lib/rack/utils.rb, line 30 30: def escape_path(s) 31: escape(s).gsub('+', '%20') 32: end
# File lib/rack/utils.rb, line 97 97: def normalize_params(params, name, v = nil) 98: name =~ %(\A[\[\]]*([^\[\]]+)\]*) 99: k = $1 || '' 100: after = $' || '' 101: 102: return if k.empty? 103: 104: if after == "" 105: params[k] = v 106: elsif after == "[]" 107: params[k] ||= [] 108: raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array) 109: params[k] << v 110: elsif after =~ %(^\[\]\[([^\[\]]+)\]$) || after =~ %(^\[\](.+)$) 111: child_key = $1 112: params[k] ||= [] 113: raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array) 114: if params_hash_type?(params[k].last) && !params[k].last.key?(child_key) 115: normalize_params(params[k].last, child_key, v) 116: else 117: params[k] << normalize_params(params.class.new, child_key, v) 118: end 119: else 120: params[k] ||= params.class.new 121: raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k]) 122: params[k] = normalize_params(params[k], after, v) 123: end 124: 125: return params 126: end
# File lib/rack/utils.rb, line 129 129: def params_hash_type?(obj) 130: obj.kind_of?(KeySpaceConstrainedParams) || obj.kind_of?(Hash) 131: end
# File lib/rack/utils.rb, line 84 84: def parse_nested_query(qs, d = nil) 85: params = KeySpaceConstrainedParams.new 86: 87: (qs || '').split(d ? /[#{d}] */ : DEFAULT_SEP).each do |p| 88: k, v = p.split('=', 2).map { |s| unescape(s) } 89: 90: normalize_params(params, k, v) 91: end 92: 93: return params.to_params_hash 94: 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 63 63: def parse_query(qs, d = nil) 64: params = KeySpaceConstrainedParams.new 65: 66: (qs || '').split(d ? /[#{d}] */ : DEFAULT_SEP).each do |p| 67: k, v = p.split('=', 2).map { |x| unescape(x) } 68: 69: if cur = params[k] 70: if cur.class == Array 71: params[k] << v 72: else 73: params[k] = [cur, v] 74: end 75: else 76: params[k] = v 77: end 78: end 79: 80: return params.to_params_hash 81: 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 299 299: def rfc2822(time) 300: wday = Time::RFC2822_DAY_NAME[time.wday] 301: mon = Time::RFC2822_MONTH_NAME[time.mon - 1] 302: time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT") 303: end
# File lib/rack/utils.rb, line 186 186: def select_best_encoding(available_encodings, accept_encoding) 187: # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html 188: 189: expanded_accept_encoding = 190: accept_encoding.map { |m, q| 191: if m == "*" 192: (available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] } 193: else 194: [[m, q]] 195: end 196: }.inject([]) { |mem, list| 197: mem + list 198: } 199: 200: encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m } 201: 202: unless encoding_candidates.include?("identity") 203: encoding_candidates.push("identity") 204: end 205: 206: expanded_accept_encoding.find_all { |m, q| 207: q == 0.0 208: }.each { |m, _| 209: encoding_candidates.delete(m) 210: } 211: 212: return (encoding_candidates & available_encodings)[0] 213: end
# File lib/rack/utils.rb, line 535 535: def status_code(status) 536: if status.is_a?(Symbol) 537: SYMBOL_TO_STATUS_CODE[status] || 500 538: else 539: status.to_i 540: end 541: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.