Skip to content
This repository
Newer
Older
100644 393 lines (330 sloc) 11.376 kb
ea814ff1 » rtomayko
2008-12-23 use Set instead of Array for STATUS_WITH_NO_ENTITY_BODY
1 require 'set'
4fe5360a » chneukirchen
2007-02-28 Make Rack::Request read multipart form data
2 require 'tempfile'
3
7ed819ad » chneukirchen
2007-02-16 Add Rack::Response and Rack::Utils
4 module Rack
376fa1e3 » chneukirchen
2007-03-01 Add RDocs
5 # Rack::Utils contains a grab-bag of useful methods for writing web
6 # applications adopted from all kinds of Ruby libraries.
230d62c7 » chneukirchen
2007-05-16 Fix trailing whitespace. Sigh.
7
7ed819ad » chneukirchen
2007-02-16 Add Rack::Response and Rack::Utils
8 module Utils
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).
12 def escape(s)
13 s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
14 '%'+$1.unpack('H2'*$1.size).join('%').upcase
116f0ef5 » chneukirchen
2007-02-17 Remove trailing whitespace *sigh*
15 }.tr(' ', '+')
7ed819ad » chneukirchen
2007-02-16 Add Rack::Response and Rack::Utils
16 end
17 module_function :escape
116f0ef5 » chneukirchen
2007-02-17 Remove trailing whitespace *sigh*
18
7ed819ad » chneukirchen
2007-02-16 Add Rack::Response and Rack::Utils
19 # Unescapes a URI escaped string. (Stolen from Camping).
20 def unescape(s)
21 s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
22 [$1.delete('%')].pack('H*')
116f0ef5 » chneukirchen
2007-02-17 Remove trailing whitespace *sigh*
23 }
7ed819ad » chneukirchen
2007-02-16 Add Rack::Response and Rack::Utils
24 end
25 module_function :unescape
116f0ef5 » chneukirchen
2007-02-17 Remove trailing whitespace *sigh*
26
3adf1af2 » qerub
2008-05-02 utils.rb - Cleaned up parse_query
27 # Stolen from Mongrel, with some small modifications:
116f0ef5 » chneukirchen
2007-02-17 Remove trailing whitespace *sigh*
28 # Parses a query string by breaking it up at the '&'
7ed819ad » chneukirchen
2007-02-16 Add Rack::Response and Rack::Utils
29 # and ';' characters. You can also use this to parse
30 # cookies by changing the characters used in the second
c5b420a5 » chneukirchen
2007-05-16 Small docfixes
31 # parameter (which defaults to '&;').
7ed819ad » chneukirchen
2007-02-16 Add Rack::Response and Rack::Utils
32 def parse_query(qs, d = '&;')
33 params = {}
488194ed » josh
2008-12-02 Correct status code language to follow RFC 2616
34
3adf1af2 » qerub
2008-05-02 utils.rb - Cleaned up parse_query
35 (qs || '').split(/[#{d}] */n).each do |p|
36 k, v = unescape(p).split('=', 2)
5c00dd69 » josh
2009-02-14 Split parse_query utility into parse_query and parse_nested_query. pa…
37
38 if cur = params[k]
39 if cur.class == Array
40 params[k] << v
41 else
42 params[k] = [cur, v]
43 end
44 else
45 params[k] = v
46 end
6e330bdd » josh
2009-01-31 Add support for nested parameter parsing
47 end
48
49 return params
50 end
51 module_function :parse_query
52
5c00dd69 » josh
2009-02-14 Split parse_query utility into parse_query and parse_nested_query. pa…
53 def parse_nested_query(qs, d = '&;')
54 params = {}
55
56 (qs || '').split(/[#{d}] */n).each do |p|
57 k, v = unescape(p).split('=', 2)
58 normalize_params(params, k, v)
59 end
60
61 return params
62 end
63 module_function :parse_nested_query
64
6e330bdd » josh
2009-01-31 Add support for nested parameter parsing
65 def normalize_params(params, name, v = nil)
66 name =~ %r([\[\]]*([^\[\]]+)\]*)
67 k = $1 || ''
68 after = $' || ''
488194ed » josh
2008-12-02 Correct status code language to follow RFC 2616
69
6e330bdd » josh
2009-01-31 Add support for nested parameter parsing
70 return if k.empty?
71
72 if after == ""
5c00dd69 » josh
2009-02-14 Split parse_query utility into parse_query and parse_nested_query. pa…
73 params[k] = v
6e330bdd » josh
2009-01-31 Add support for nested parameter parsing
74 elsif after == "[]"
75 params[k] ||= []
880d9be0 » rob-at-thewebfellas
2009-03-18 Added extra nested params error handling and added messages to except…
76 raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
6e330bdd » josh
2009-01-31 Add support for nested parameter parsing
77 params[k] << v
78 elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
79 child_key = $1
80 params[k] ||= []
880d9be0 » rob-at-thewebfellas
2009-03-18 Added extra nested params error handling and added messages to except…
81 raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
6e330bdd » josh
2009-01-31 Add support for nested parameter parsing
82 if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
83 normalize_params(params[k].last, child_key, v)
84 else
85 params[k] << normalize_params({}, child_key, v)
86 end
87 else
88 params[k] ||= {}
880d9be0 » rob-at-thewebfellas
2009-03-18 Added extra nested params error handling and added messages to except…
89 raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
6e330bdd » josh
2009-01-31 Add support for nested parameter parsing
90 params[k] = normalize_params(params[k], after, v)
3adf1af2 » qerub
2008-05-02 utils.rb - Cleaned up parse_query
91 end
488194ed » josh
2008-12-02 Correct status code language to follow RFC 2616
92
7ed819ad » chneukirchen
2007-02-16 Add Rack::Response and Rack::Utils
93 return params
94 end
6e330bdd » josh
2009-01-31 Add support for nested parameter parsing
95 module_function :normalize_params
488194ed » josh
2008-12-02 Correct status code language to follow RFC 2616
96
31f8a934 » qerub
2008-05-02 utils.rb, spec_rack_utils.rb - Added build_query, the inverse of pars…
97 def build_query(params)
98 params.map { |k, v|
99 if v.class == Array
100 build_query(v.map { |x| [k, x] })
101 else
102 escape(k) + "=" + escape(v)
103 end
104 }.join("&")
105 end
106 module_function :build_query
7ed819ad » chneukirchen
2007-02-16 Add Rack::Response and Rack::Utils
107
376fa1e3 » chneukirchen
2007-03-01 Add RDocs
108 # Escape ampersands, brackets and quotes to their HTML/XML entities.
69f11f60 » chneukirchen
2007-02-17 Add Rack::ShowExceptions
109 def escape_html(string)
110 string.to_s.gsub("&", "&amp;").
111 gsub("<", "&lt;").
112 gsub(">", "&gt;").
113 gsub("'", "&#39;").
114 gsub('"', "&quot;")
115 end
116 module_function :escape_html
117
0f2dab57 » qerub
2008-07-01 Added support for Accept-Encoding (via Request#accept_encoding and Ut…
118 def select_best_encoding(available_encodings, accept_encoding)
119 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
120
121 expanded_accept_encoding =
122 accept_encoding.map { |m, q|
123 if m == "*"
124 (available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] }
125 else
126 [[m, q]]
127 end
128 }.inject([]) { |mem, list|
129 mem + list
130 }
131
132 encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m }
133
134 unless encoding_candidates.include?("identity")
135 encoding_candidates.push("identity")
136 end
137
138 expanded_accept_encoding.find_all { |m, q|
139 q == 0.0
140 }.each { |m, _|
141 encoding_candidates.delete(m)
142 }
143
144 return (encoding_candidates & available_encodings)[0]
145 end
146 module_function :select_best_encoding
147
980d6816 » rtomayko
2009-03-05 Add Rack::Utils.bytesize function, use everywhere
148 # Return the bytesize of String; uses String#length under Ruby 1.8 and
149 # String#bytesize under 1.9.
150 if ''.respond_to?(:bytesize)
151 def bytesize(string)
152 string.bytesize
153 end
154 else
155 def bytesize(string)
156 string.size
157 end
158 end
159 module_function :bytesize
160
8eac3203 » scytrin
2009-01-06 Simplification of Rack::Utils::Context
161 # Context allows the use of a compatible middleware at different points
162 # in a request handling stack. A compatible middleware must define
163 # #context which should take the arguments env and app. The first of which
164 # would be the request environment. The second of which would be the rack
165 # application that the request would be forwarded to.
166 class Context
ea7f9f7b » scytrin
2008-03-16 utils.rb - Utils::Context - addition of introspection methods
167 attr_reader :for, :app
8eac3203 » scytrin
2009-01-06 Simplification of Rack::Utils::Context
168
169 def initialize(app_f, app_r)
19900575 » scytrin
2008-06-06 Added documentation, checks, and tests for Rack::Utils::Context
170 raise 'running context does not respond to #context' unless app_f.respond_to? :context
8eac3203 » scytrin
2009-01-06 Simplification of Rack::Utils::Context
171 @for, @app = app_f, app_r
14fdd96f » scytrin
2007-11-18 Addition of Rack::Utils::Context
172 end
8eac3203 » scytrin
2009-01-06 Simplification of Rack::Utils::Context
173
174 def call(env)
175 @for.context(env, @app)
14fdd96f » scytrin
2007-11-18 Addition of Rack::Utils::Context
176 end
8eac3203 » scytrin
2009-01-06 Simplification of Rack::Utils::Context
177
178 def recontext(app)
179 self.class.new(@for, app)
ba05ad9e » scytrin
2008-03-29 utils.rb - addition of recontexting from a Context
180 end
8eac3203 » scytrin
2009-01-06 Simplification of Rack::Utils::Context
181
182 def context(env, app=@app)
183 recontext(app).call(env)
14fdd96f » scytrin
2007-11-18 Addition of Rack::Utils::Context
184 end
185 end
186
76c3aefa » rtomayko
2008-12-29 Use HeaderHash where header case should be insensitive
187 # A case-insensitive Hash that preserves the original case of a
188 # header when set.
7ed819ad » chneukirchen
2007-02-16 Add Rack::Response and Rack::Utils
189 class HeaderHash < Hash
190 def initialize(hash={})
4e174436 » rtomayko
2008-12-11 Non-normalizing HeaderHash with case-insensitive lookups
191 @names = {}
7ed819ad » chneukirchen
2007-02-16 Add Rack::Response and Rack::Utils
192 hash.each { |k, v| self[k] = v }
193 end
116f0ef5 » chneukirchen
2007-02-17 Remove trailing whitespace *sigh*
194
7ed819ad » chneukirchen
2007-02-16 Add Rack::Response and Rack::Utils
195 def to_hash
bebd5893 » rtomayko
2009-02-01 Normalize Array header values to Strings in HeaderHash#to_hash
196 inject({}) do |hash, (k,v)|
197 if v.respond_to? :to_ary
198 hash[k] = v.to_ary.join("\n")
199 else
200 hash[k] = v
201 end
202 hash
203 end
7ed819ad » chneukirchen
2007-02-16 Add Rack::Response and Rack::Utils
204 end
205
206 def [](k)
4e174436 » rtomayko
2008-12-11 Non-normalizing HeaderHash with case-insensitive lookups
207 super @names[k.downcase]
7ed819ad » chneukirchen
2007-02-16 Add Rack::Response and Rack::Utils
208 end
116f0ef5 » chneukirchen
2007-02-17 Remove trailing whitespace *sigh*
209
7ed819ad » chneukirchen
2007-02-16 Add Rack::Response and Rack::Utils
210 def []=(k, v)
4e174436 » rtomayko
2008-12-11 Non-normalizing HeaderHash with case-insensitive lookups
211 delete k
212 @names[k.downcase] = k
213 super k, v
7ed819ad » chneukirchen
2007-02-16 Add Rack::Response and Rack::Utils
214 end
215
4e174436 » rtomayko
2008-12-11 Non-normalizing HeaderHash with case-insensitive lookups
216 def delete(k)
217 super @names.delete(k.downcase)
7ed819ad » chneukirchen
2007-02-16 Add Rack::Response and Rack::Utils
218 end
4e174436 » rtomayko
2008-12-11 Non-normalizing HeaderHash with case-insensitive lookups
219
220 def include?(k)
221 @names.has_key? k.downcase
222 end
223
224 alias_method :has_key?, :include?
225 alias_method :member?, :include?
226 alias_method :key?, :include?
c00b3862 » rtomayko
2008-12-29 Implement HeaderHash#merge! and HeaderHash#merge
227
228 def merge!(other)
229 other.each { |k, v| self[k] = v }
230 self
231 end
232
233 def merge(other)
234 hash = dup
235 hash.merge! other
236 end
7ed819ad » chneukirchen
2007-02-16 Add Rack::Response and Rack::Utils
237 end
b064d530 » chneukirchen
2007-02-28 Make multipart reading more robust
238
d110c39b » chneukirchen
2007-03-29 Add a list of HTTP status messages
239 # Every standard HTTP code mapped to the appropriate message.
240 # Stolen from Mongrel.
241 HTTP_STATUS_CODES = {
242 100 => 'Continue',
243 101 => 'Switching Protocols',
244 200 => 'OK',
245 201 => 'Created',
246 202 => 'Accepted',
247 203 => 'Non-Authoritative Information',
248 204 => 'No Content',
249 205 => 'Reset Content',
250 206 => 'Partial Content',
251 300 => 'Multiple Choices',
252 301 => 'Moved Permanently',
488194ed » josh
2008-12-02 Correct status code language to follow RFC 2616
253 302 => 'Found',
d110c39b » chneukirchen
2007-03-29 Add a list of HTTP status messages
254 303 => 'See Other',
255 304 => 'Not Modified',
256 305 => 'Use Proxy',
488194ed » josh
2008-12-02 Correct status code language to follow RFC 2616
257 307 => 'Temporary Redirect',
d110c39b » chneukirchen
2007-03-29 Add a list of HTTP status messages
258 400 => 'Bad Request',
259 401 => 'Unauthorized',
260 402 => 'Payment Required',
261 403 => 'Forbidden',
262 404 => 'Not Found',
263 405 => 'Method Not Allowed',
264 406 => 'Not Acceptable',
265 407 => 'Proxy Authentication Required',
488194ed » josh
2008-12-02 Correct status code language to follow RFC 2616
266 408 => 'Request Timeout',
d110c39b » chneukirchen
2007-03-29 Add a list of HTTP status messages
267 409 => 'Conflict',
268 410 => 'Gone',
269 411 => 'Length Required',
270 412 => 'Precondition Failed',
271 413 => 'Request Entity Too Large',
272 414 => 'Request-URI Too Large',
273 415 => 'Unsupported Media Type',
488194ed » josh
2008-12-02 Correct status code language to follow RFC 2616
274 416 => 'Requested Range Not Satisfiable',
275 417 => 'Expectation Failed',
d110c39b » chneukirchen
2007-03-29 Add a list of HTTP status messages
276 500 => 'Internal Server Error',
277 501 => 'Not Implemented',
278 502 => 'Bad Gateway',
279 503 => 'Service Unavailable',
488194ed » josh
2008-12-02 Correct status code language to follow RFC 2616
280 504 => 'Gateway Timeout',
281 505 => 'HTTP Version Not Supported'
d110c39b » chneukirchen
2007-03-29 Add a list of HTTP status messages
282 }
283
77725c7d » Dan Kubb
2008-12-20 Moved STATUS_WITH_NO_ENTITY_BODY into Rack::Utils
284 # Responses with HTTP status codes that should not have an entity body
ea814ff1 » rtomayko
2008-12-23 use Set instead of Array for STATUS_WITH_NO_ENTITY_BODY
285 STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 304)
77725c7d » Dan Kubb
2008-12-20 Moved STATUS_WITH_NO_ENTITY_BODY into Rack::Utils
286
376fa1e3 » chneukirchen
2007-03-01 Add RDocs
287 # A multipart form data parser, adapted from IOWA.
288 #
289 # Usually, Rack::Request#POST takes care of calling this.
d110c39b » chneukirchen
2007-03-29 Add a list of HTTP status messages
290
4fe5360a » chneukirchen
2007-02-28 Make Rack::Request read multipart form data
291 module Multipart
292 EOL = "\r\n"
b064d530 » chneukirchen
2007-02-28 Make multipart reading more robust
293
4fe5360a » chneukirchen
2007-02-28 Make Rack::Request read multipart form data
294 def self.parse_multipart(env)
b064d530 » chneukirchen
2007-02-28 Make multipart reading more robust
295 unless env['CONTENT_TYPE'] =~
296 %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
4fe5360a » chneukirchen
2007-02-28 Make Rack::Request read multipart form data
297 nil
298 else
299 boundary = "--#{$1}"
300
301 params = {}
302 buf = ""
303 content_length = env['CONTENT_LENGTH'].to_i
304 input = env['rack.input']
305
306 boundary_size = boundary.size + EOL.size
307 bufsize = 16384
308
309 content_length -= boundary_size
310
311 status = input.read(boundary_size)
312 raise EOFError, "bad content body" unless status == boundary + EOL
313
69b24cb7 » Jonathan del Strother
2009-01-16 Fix multipart parsing of binary content in UTF8 mode
314 rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/n
b064d530 » chneukirchen
2007-02-28 Make multipart reading more robust
315
4fe5360a » chneukirchen
2007-02-28 Make Rack::Request read multipart form data
316 loop {
317 head = nil
318 body = ''
319 filename = content_type = name = nil
320
b064d530 » chneukirchen
2007-02-28 Make multipart reading more robust
321 until head && buf =~ rx
4fe5360a » chneukirchen
2007-02-28 Make Rack::Request read multipart form data
322 if !head && i = buf.index("\r\n\r\n")
323 head = buf.slice!(0, i+2) # First \r\n
324 buf.slice!(0, 2) # Second \r\n
b064d530 » chneukirchen
2007-02-28 Make multipart reading more robust
325
4fe5360a » chneukirchen
2007-02-28 Make Rack::Request read multipart form data
326 filename = head[/Content-Disposition:.* filename="?([^\";]*)"?/ni, 1]
327 content_type = head[/Content-Type: (.*)\r\n/ni, 1]
328 name = head[/Content-Disposition:.* name="?([^\";]*)"?/ni, 1]
b064d530 » chneukirchen
2007-02-28 Make multipart reading more robust
329
11146915 » chneukirchen
2008-04-18 Open multipart tempfiles in binary mode
330 if filename
331 body = Tempfile.new("RackMultipart")
fe22d0fd » chneukirchen
2008-06-24 Only call binmode when possible in the multipart parser
332 body.binmode if body.respond_to?(:binmode)
11146915 » chneukirchen
2008-04-18 Open multipart tempfiles in binary mode
333 end
4fe5360a » chneukirchen
2007-02-28 Make Rack::Request read multipart form data
334
335 next
336 end
337
338 # Save the read body part.
b064d530 » chneukirchen
2007-02-28 Make multipart reading more robust
339 if head && (boundary_size+4 < buf.size)
340 body << buf.slice!(0, buf.size - (boundary_size+4))
4fe5360a » chneukirchen
2007-02-28 Make Rack::Request read multipart form data
341 end
342
343 c = input.read(bufsize < content_length ? bufsize : content_length)
344 raise EOFError, "bad content body" if c.nil? || c.empty?
345 buf << c
346 content_length -= c.size
347 end
348
349 # Save the rest.
b064d530 » chneukirchen
2007-02-28 Make multipart reading more robust
350 if i = buf.index(rx)
4fe5360a » chneukirchen
2007-02-28 Make Rack::Request read multipart form data
351 body << buf.slice!(0, i)
352 buf.slice!(0, boundary_size+2)
b064d530 » chneukirchen
2007-02-28 Make multipart reading more robust
353
4fe5360a » chneukirchen
2007-02-28 Make Rack::Request read multipart form data
354 content_length = -1 if $1 == "--"
355 end
356
a2a26001 » josh
2009-01-20 Don't create empty tempfile if no file was selected
357 if filename == ""
358 # filename is blank which means no file has been selected
359 data = nil
360 elsif filename
4fe5360a » chneukirchen
2007-02-28 Make Rack::Request read multipart form data
361 body.rewind
5368e30d » josh
2009-01-15 Trim IE's full file path in multipart uploads
362
363 # Take the basename of the upload's original filename.
364 # This handles the full Windows paths given by Internet Explorer
365 # (and perhaps other broken user agents) without affecting
366 # those which give the lone filename.
367 filename =~ /^(?:.*[:\\\/])?(.*)/m
368 filename = $1
369
4fe5360a » chneukirchen
2007-02-28 Make Rack::Request read multipart form data
370 data = {:filename => filename, :type => content_type,
371 :name => name, :tempfile => body, :head => head}
372 else
373 data = body
374 end
375
4ab6e2a6 » rob-at-thewebfellas
2009-03-13 Remove files from params when no file is selected [#37 state:resolved]
376 Utils.normalize_params(params, name, data) unless data.nil?
4fe5360a » chneukirchen
2007-02-28 Make Rack::Request read multipart form data
377
378 break if buf.empty? || content_length == -1
379 }
380
3513f4fd » josh
2009-01-13 Add tests for multipart uploads. Also ensure multipart parser tries t…
381 begin
382 input.rewind if input.respond_to?(:rewind)
383 rescue Errno::ESPIPE
384 # Handles exceptions raised by input streams that cannot be rewound
385 # such as when using plain CGI under Apache
386 end
387
4fe5360a » chneukirchen
2007-02-28 Make Rack::Request read multipart form data
388 params
389 end
390 end
391 end
7ed819ad » chneukirchen
2007-02-16 Add Rack::Response and Rack::Utils
392 end
393 end
Something went wrong with that request. Please try again.