Class Index [+]

Quicksearch

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
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://www.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
Multipart

Attributes

key_space_limit[RW]

Public Class Methods

build_nested_query(value, prefix = nil) click to toggle source
     # 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
build_query(params) click to toggle source
     # 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
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 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
bytesize(string) click to toggle source
     # File lib/rack/utils.rb, line 280
280:       def bytesize(string)
281:         string.bytesize
282:       end
bytesize(string) click to toggle source
     # File lib/rack/utils.rb, line 284
284:       def bytesize(string)
285:         string.size
286:       end
escape(s) click to toggle source

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_html(string) click to toggle source

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
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 30
30:     def escape_path(s)
31:       escape(s).gsub('+', '%20')
32:     end
normalize_params(params, name, v = nil) click to toggle source
     # 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
params_hash_type?(obj) click to toggle source
     # File lib/rack/utils.rb, line 129
129:     def params_hash_type?(obj)
130:       obj.kind_of?(KeySpaceConstrainedParams) || obj.kind_of?(Hash)
131:     end
parse_nested_query(qs, d = nil) click to toggle source
    # 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
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 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
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 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
select_best_encoding(available_encodings, accept_encoding) click to toggle source
     # 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
status_code(status) click to toggle source
     # 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
unescape(s, encoding = nil) click to toggle source
    # File lib/rack/utils.rb, line 42
42:       def unescape(s, encoding = nil)
43:         URI.decode_www_form_component(s, encoding)
44:       end
unescape(s, encoding = Encoding::UTF_8) click to toggle source
    # File lib/rack/utils.rb, line 38
38:       def unescape(s, encoding = Encoding::UTF_8)
39:         URI.decode_www_form_component(s, encoding)
40:       end

Private Instance Methods

build_nested_query(value, prefix = nil) click to toggle source
     # 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
build_query(params) click to toggle source
     # 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
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 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
bytesize(string) click to toggle source
     # File lib/rack/utils.rb, line 284
284:       def bytesize(string)
285:         string.size
286:       end
bytesize(string) click to toggle source
     # File lib/rack/utils.rb, line 280
280:       def bytesize(string)
281:         string.bytesize
282:       end
escape(s) click to toggle source

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_html(string) click to toggle source

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
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 30
30:     def escape_path(s)
31:       escape(s).gsub('+', '%20')
32:     end
normalize_params(params, name, v = nil) click to toggle source
     # 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
params_hash_type?(obj) click to toggle source
     # File lib/rack/utils.rb, line 129
129:     def params_hash_type?(obj)
130:       obj.kind_of?(KeySpaceConstrainedParams) || obj.kind_of?(Hash)
131:     end
parse_nested_query(qs, d = nil) click to toggle source
    # 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
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 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
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 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
select_best_encoding(available_encodings, accept_encoding) click to toggle source
     # 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
status_code(status) click to toggle source
     # 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
unescape(s, encoding = Encoding::UTF_8) click to toggle source
    # File lib/rack/utils.rb, line 38
38:       def unescape(s, encoding = Encoding::UTF_8)
39:         URI.decode_www_form_component(s, encoding)
40:       end
unescape(s, encoding = nil) click to toggle source
    # File lib/rack/utils.rb, line 42
42:       def unescape(s, encoding = nil)
43:         URI.decode_www_form_component(s, encoding)
44:       end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.