Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

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