5 # Rack::Utils contains a grab-bag of useful methods for writing web
6 # applications adopted from all kinds of Ruby libraries.
9 # Performs URI escaping so that you can construct proper
10 # query strings faster. Use this rather than the cgi.rb
11 # version since it's faster. (Stolen from Camping).
13 s
.to_s
.gsub(/([^ a-zA-Z0-9_.-]+)/n
) {
14 '%'+$1.unpack('H2'*$1.size
).join('%').upcase
17 module_function
:escape
19 # Unescapes a URI escaped string. (Stolen from Camping).
21 s
.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n
){
22 [$1.delete('%')].pack('H*')
25 module_function
:unescape
27 # Stolen from Mongrel, with some small modifications:
28 # Parses a query string by breaking it up at the '&'
29 # and ';' characters. You can also use this to parse
30 # cookies by changing the characters used in the second
31 # parameter (which defaults to '&;').
32 def parse_query(qs
, d
= '&;')
35 (qs
|| '').split(/[#{d}] */n
).each
do |p
|
36 k
, v
= unescape(p
).split('=', 2)
51 module_function
:parse_query
53 def parse_nested_query(qs
, d
= '&;')
56 (qs
|| '').split(/[#{d}] */n
).each
do |p
|
57 k
, v
= unescape(p
).split('=', 2)
58 normalize_params(params
, k
, v
)
63 module_function
:parse_nested_query
65 def normalize_params(params
, name
, v
= nil)
66 name
=~
%r([\
[\
]]*([^\
[\
]]+)\
]*)
76 raise TypeError unless params[k].is_a?(Array)
78 elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
81 raise TypeError unless params[k].is_a?(Array)
82 if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
83 normalize_params(params[k].last, child_key, v)
85 params[k] << normalize_params({}, child_key, v)
89 params[k] = normalize_params(params[k], after, v)
94 module_function :normalize_params
96 def build_query(params)
99 build_query(v.map { |x| [k, x] })
101 escape(k) + "=" + escape(v)
105 module_function :build_query
107 # Escape ampersands, brackets and quotes to their HTML/XML entities.
108 def escape_html(string)
109 string.to_s.gsub("&", "&").
115 module_function
:escape_html
117 def select_best_encoding(available_encodings
, accept_encoding
)
118 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
120 expanded_accept_encoding
=
121 accept_encoding
.map
{ |m
, q
|
123 (available_encodings
- accept_encoding
.map
{ |m2
, _
| m2
}).map
{ |m2
| [m2
, q
] }
127 }.inject([]) { |mem
, list
|
131 encoding_candidates
= expanded_accept_encoding
.sort_by
{ |_
, q
| -q
}.map
{ |m
, _
| m
}
133 unless encoding_candidates
.include?("identity")
134 encoding_candidates
.push("identity")
137 expanded_accept_encoding
.find_all
{ |m
, q
|
140 encoding_candidates
.delete(m
)
143 return (encoding_candidates
& available_encodings
)[0]
145 module_function
:select_best_encoding
147 # Return the bytesize of String; uses String#length under Ruby 1.8 and
148 # String#bytesize under 1.9.
149 if ''.respond_to
?(:bytesize)
158 module_function
:bytesize
160 # Context allows the use of a compatible middleware at different points
161 # in a request handling stack. A compatible middleware must define
162 # #context which should take the arguments env and app. The first of which
163 # would be the request environment. The second of which would be the rack
164 # application that the request would be forwarded to.
166 attr_reader
:for, :app
168 def initialize(app_f
, app_r
)
169 raise 'running context does not respond to #context' unless app_f
.respond_to
? :context
170 @for, @app = app_f
, app_r
174 @for.context(env, @app)
178 self.class.new(@for, app
)
181 def context(env, app
=@app)
182 recontext(app
).call(env)
186 # A case-insensitive Hash that preserves the original case of a
188 class HeaderHash
< Hash
189 def initialize(hash
={})
191 hash
.each
{ |k
, v
| self[k
] = v
}
195 inject({}) do |hash
, (k
,v
)|
196 if v
.respond_to
? :to_ary
197 hash
[k
] = v
.to_ary
.join("\n")
206 super @names[k
.downcase
]
211 @names[k
.downcase
] = k
216 super @names.delete(k
.downcase
)
220 @names.has_key
? k
.downcase
223 alias_method
:has_key?, :include?
224 alias_method
:member?, :include?
225 alias_method
:key?, :include?
228 other
.each
{ |k
, v
| self[k
] = v
}
238 # Every standard HTTP code mapped to the appropriate message.
239 # Stolen from Mongrel.
240 HTTP_STATUS_CODES
= {
242 101 => 'Switching Protocols',
246 203 => 'Non-Authoritative Information',
248 205 => 'Reset Content',
249 206 => 'Partial Content',
250 300 => 'Multiple Choices',
251 301 => 'Moved Permanently',
254 304 => 'Not Modified',
256 307 => 'Temporary Redirect',
257 400 => 'Bad Request',
258 401 => 'Unauthorized',
259 402 => 'Payment Required',
262 405 => 'Method Not Allowed',
263 406 => 'Not Acceptable',
264 407 => 'Proxy Authentication Required',
265 408 => 'Request Timeout',
268 411 => 'Length Required',
269 412 => 'Precondition Failed',
270 413 => 'Request Entity Too Large',
271 414 => 'Request-URI Too Large',
272 415 => 'Unsupported Media Type',
273 416 => 'Requested Range Not Satisfiable',
274 417 => 'Expectation Failed',
275 500 => 'Internal Server Error',
276 501 => 'Not Implemented',
277 502 => 'Bad Gateway',
278 503 => 'Service Unavailable',
279 504 => 'Gateway Timeout',
280 505 => 'HTTP Version Not Supported'
283 # Responses with HTTP status codes that should not have an entity body
284 STATUS_WITH_NO_ENTITY_BODY
= Set
.new((100..199).to_a
<< 204 << 304)
286 # A multipart form data parser, adapted from IOWA.
288 # Usually, Rack::Request#POST takes care of calling this.
293 def self.parse_multipart(env)
294 unless env['CONTENT_TYPE'] =~
295 %r
|\Amultipart
/form-data
.*boundary
=\"?([^
\";,]+)\"?|n
302 content_length
= env['CONTENT_LENGTH'].to_i
303 input
= env['rack.input']
305 boundary_size
= boundary
.size
+ EOL
.size
308 content_length
-= boundary_size
310 status
= input
.read(boundary_size
)
311 raise EOFError
, "bad content body" unless status
== boundary
+ EOL
313 rx
= /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/n
318 filename
= content_type
= name
= nil
320 until head
&& buf
=~ rx
321 if !head
&& i
= buf
.index("\r\n\r\n")
322 head
= buf
.slice
!(0, i
+2) # First \r\n
323 buf
.slice
!(0, 2) # Second \r\n
325 filename
= head
[/Content-Disposition:.* filename="?([^\";]*)"?/ni
, 1]
326 content_type
= head
[/Content-Type: (.*)\r\n/ni
, 1]
327 name
= head
[/Content-Disposition:.* name="?([^\";]*)"?/ni
, 1]
330 body
= Tempfile
.new("RackMultipart")
331 body
.binmode
if body
.respond_to
?(:binmode)
337 # Save the read body part.
338 if head
&& (boundary_size
+4 < buf
.size
)
339 body
<< buf
.slice
!(0, buf
.size
- (boundary_size
+4))
342 c
= input
.read(bufsize
< content_length
? bufsize
: content_length
)
343 raise EOFError
, "bad content body" if c
.nil? || c
.empty
?
345 content_length
-= c
.size
350 body
<< buf
.slice
!(0, i
)
351 buf
.slice
!(0, boundary_size
+2)
353 content_length
= -1 if $1 == "--"
357 # filename is blank which means no file has been selected
362 # Take the basename of the upload's original filename.
363 # This handles the full Windows paths given by Internet Explorer
364 # (and perhaps other broken user agents) without affecting
365 # those which give the lone filename.
366 filename
=~
/^(?:.*[:\\\/])?(.*)/m
369 data = {:filename => filename
, :type => content_type
,
370 :name => name
, :tempfile => body
, :head => head
}
375 Utils
.normalize_params(params
, name
, data) unless data.nil?
377 break if buf
.empty
? || content_length
== -1
381 input
.rewind
if input
.respond_to
?(:rewind)
383 # Handles exceptions raised by input streams that cannot be rewound
384 # such as when using plain CGI under Apache