rack 1.2.8 → 1.3.0.beta
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- data/README +9 -177
- data/Rakefile +2 -1
- data/SPEC +2 -2
- data/lib/rack.rb +2 -13
- data/lib/rack/auth/abstract/request.rb +7 -5
- data/lib/rack/auth/digest/md5.rb +6 -2
- data/lib/rack/auth/digest/params.rb +5 -7
- data/lib/rack/auth/digest/request.rb +1 -1
- data/lib/rack/backports/uri/common.rb +64 -0
- data/lib/rack/builder.rb +60 -3
- data/lib/rack/chunked.rb +29 -22
- data/lib/rack/conditionalget.rb +35 -16
- data/lib/rack/content_length.rb +3 -3
- data/lib/rack/deflater.rb +5 -2
- data/lib/rack/etag.rb +38 -10
- data/lib/rack/file.rb +76 -43
- data/lib/rack/handler.rb +13 -7
- data/lib/rack/handler/cgi.rb +0 -2
- data/lib/rack/handler/fastcgi.rb +13 -4
- data/lib/rack/handler/lsws.rb +0 -2
- data/lib/rack/handler/mongrel.rb +12 -2
- data/lib/rack/handler/scgi.rb +9 -1
- data/lib/rack/handler/thin.rb +7 -1
- data/lib/rack/handler/webrick.rb +12 -5
- data/lib/rack/lint.rb +2 -2
- data/lib/rack/lock.rb +29 -3
- data/lib/rack/methodoverride.rb +1 -1
- data/lib/rack/mime.rb +2 -2
- data/lib/rack/mock.rb +28 -33
- data/lib/rack/multipart.rb +34 -0
- data/lib/rack/multipart/generator.rb +93 -0
- data/lib/rack/multipart/parser.rb +164 -0
- data/lib/rack/multipart/uploaded_file.rb +30 -0
- data/lib/rack/request.rb +55 -19
- data/lib/rack/response.rb +10 -8
- data/lib/rack/sendfile.rb +14 -18
- data/lib/rack/server.rb +55 -8
- data/lib/rack/session/abstract/id.rb +233 -22
- data/lib/rack/session/cookie.rb +99 -46
- data/lib/rack/session/memcache.rb +30 -56
- data/lib/rack/session/pool.rb +22 -43
- data/lib/rack/showexceptions.rb +40 -11
- data/lib/rack/showstatus.rb +9 -2
- data/lib/rack/static.rb +29 -9
- data/lib/rack/urlmap.rb +6 -1
- data/lib/rack/utils.rb +67 -326
- data/rack.gemspec +2 -3
- data/test/builder/anything.rb +5 -0
- data/test/builder/comment.ru +4 -0
- data/test/builder/end.ru +3 -0
- data/test/builder/options.ru +2 -0
- data/test/cgi/lighttpd.conf +1 -1
- data/test/cgi/lighttpd.errors +412 -0
- data/test/multipart/content_type_and_no_filename +6 -0
- data/test/multipart/text +5 -0
- data/test/multipart/webkit +32 -0
- data/test/registering_handler/rack/handler/registering_myself.rb +8 -0
- data/test/spec_auth_digest.rb +20 -5
- data/test/spec_builder.rb +29 -0
- data/test/spec_cgi.rb +11 -0
- data/test/spec_chunked.rb +1 -1
- data/test/spec_commonlogger.rb +1 -1
- data/test/spec_conditionalget.rb +47 -0
- data/test/spec_content_length.rb +0 -6
- data/test/spec_content_type.rb +5 -5
- data/test/spec_deflater.rb +46 -2
- data/test/spec_etag.rb +68 -1
- data/test/spec_fastcgi.rb +11 -0
- data/test/spec_file.rb +54 -3
- data/test/spec_handler.rb +23 -5
- data/test/spec_lint.rb +2 -2
- data/test/spec_lock.rb +111 -5
- data/test/spec_methodoverride.rb +2 -2
- data/test/spec_mock.rb +3 -3
- data/test/spec_mongrel.rb +1 -2
- data/test/spec_multipart.rb +279 -0
- data/test/spec_request.rb +222 -38
- data/test/spec_response.rb +9 -3
- data/test/spec_server.rb +74 -0
- data/test/spec_session_abstract_id.rb +43 -0
- data/test/spec_session_cookie.rb +97 -15
- data/test/spec_session_memcache.rb +60 -50
- data/test/spec_session_pool.rb +63 -40
- data/test/spec_showexceptions.rb +64 -0
- data/test/spec_static.rb +23 -0
- data/test/spec_utils.rb +65 -351
- data/test/spec_webrick.rb +23 -4
- metadata +35 -15
- data/test/spec_auth.rb +0 -57
data/lib/rack/showstatus.rb
CHANGED
@@ -23,9 +23,16 @@ module Rack
|
|
23
23
|
|
24
24
|
# client or server error, or explicit message
|
25
25
|
if (status.to_i >= 400 && empty) || env["rack.showstatus.detail"]
|
26
|
-
|
26
|
+
# This double assignment is to prevent an "unused variable" warning on
|
27
|
+
# Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
|
28
|
+
req = req = Rack::Request.new(env)
|
29
|
+
|
27
30
|
message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s
|
28
|
-
|
31
|
+
|
32
|
+
# This double assignment is to prevent an "unused variable" warning on
|
33
|
+
# Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
|
34
|
+
detail = detail = env["rack.showstatus.detail"] || message
|
35
|
+
|
29
36
|
body = @template.result(binding)
|
30
37
|
size = Rack::Utils.bytesize(body)
|
31
38
|
[status, headers.merge("Content-Type" => "text/html", "Content-Length" => size.to_s), [body]]
|
data/lib/rack/static.rb
CHANGED
@@ -1,18 +1,31 @@
|
|
1
1
|
module Rack
|
2
2
|
|
3
3
|
# The Rack::Static middleware intercepts requests for static files
|
4
|
-
# (javascript files, images, stylesheets, etc) based on the url prefixes
|
5
|
-
# passed in the options, and serves them using a Rack::File
|
6
|
-
# allows a Rack stack to serve both static and dynamic content.
|
4
|
+
# (javascript files, images, stylesheets, etc) based on the url prefixes or
|
5
|
+
# route mappings passed in the options, and serves them using a Rack::File
|
6
|
+
# object. This allows a Rack stack to serve both static and dynamic content.
|
7
7
|
#
|
8
8
|
# Examples:
|
9
|
+
#
|
10
|
+
# Serve all requests beginning with /media from the "media" folder located
|
11
|
+
# in the current directory (ie media/*):
|
12
|
+
#
|
9
13
|
# use Rack::Static, :urls => ["/media"]
|
10
|
-
#
|
11
|
-
#
|
14
|
+
#
|
15
|
+
# Serve all requests beginning with /css or /images from the folder "public"
|
16
|
+
# in the current directory (ie public/css/* and public/images/*):
|
12
17
|
#
|
13
18
|
# use Rack::Static, :urls => ["/css", "/images"], :root => "public"
|
14
|
-
#
|
15
|
-
#
|
19
|
+
#
|
20
|
+
# Serve all requests to / with "index.html" from the folder "public" in the
|
21
|
+
# current directory (ie public/index.html):
|
22
|
+
#
|
23
|
+
# use Rack::Static, :urls => {"/" => 'index.html'}, :root => 'public'
|
24
|
+
#
|
25
|
+
# Set a fixed Cache-Control header for all served files:
|
26
|
+
#
|
27
|
+
# use Rack::Static, :root => 'public', :cache_control => 'public'
|
28
|
+
#
|
16
29
|
|
17
30
|
class Static
|
18
31
|
|
@@ -20,14 +33,21 @@ module Rack
|
|
20
33
|
@app = app
|
21
34
|
@urls = options[:urls] || ["/favicon.ico"]
|
22
35
|
root = options[:root] || Dir.pwd
|
23
|
-
|
36
|
+
cache_control = options[:cache_control]
|
37
|
+
@file_server = Rack::File.new(root, cache_control)
|
24
38
|
end
|
25
39
|
|
26
40
|
def call(env)
|
27
41
|
path = env["PATH_INFO"]
|
28
|
-
|
42
|
+
|
43
|
+
unless @urls.kind_of? Hash
|
44
|
+
can_serve = @urls.any? { |url| path.index(url) == 0 }
|
45
|
+
else
|
46
|
+
can_serve = @urls.key? path
|
47
|
+
end
|
29
48
|
|
30
49
|
if can_serve
|
50
|
+
env["PATH_INFO"] = @urls[path] if @urls.kind_of? Hash
|
31
51
|
@file_server.call(env)
|
32
52
|
else
|
33
53
|
@app.call(env)
|
data/lib/rack/urlmap.rb
CHANGED
@@ -12,11 +12,16 @@ module Rack
|
|
12
12
|
# first, since they are most specific.
|
13
13
|
|
14
14
|
class URLMap
|
15
|
+
NEGATIVE_INFINITY = -1.0 / 0.0
|
16
|
+
|
15
17
|
def initialize(map = {})
|
16
18
|
remap(map)
|
17
19
|
end
|
18
20
|
|
19
21
|
def remap(map)
|
22
|
+
longest_path_first = lambda do |(host, location, _, _)|
|
23
|
+
[host ? -host.size : NEGATIVE_INFINITY, -location.size]
|
24
|
+
end
|
20
25
|
@mapping = map.map { |location, app|
|
21
26
|
if location =~ %r{\Ahttps?://(.*?)(/.*)}
|
22
27
|
host, location = $1, $2
|
@@ -31,7 +36,7 @@ module Rack
|
|
31
36
|
match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
|
32
37
|
|
33
38
|
[host, location, match, app]
|
34
|
-
}.sort_by
|
39
|
+
}.sort_by(&longest_path_first)
|
35
40
|
end
|
36
41
|
|
37
42
|
def call(env)
|
data/lib/rack/utils.rb
CHANGED
@@ -4,39 +4,41 @@ require 'fileutils'
|
|
4
4
|
require 'set'
|
5
5
|
require 'tempfile'
|
6
6
|
|
7
|
+
require 'rack/multipart'
|
8
|
+
|
9
|
+
if RUBY_VERSION[/^\d+\.\d+/] == '1.8'
|
10
|
+
# pull in backports
|
11
|
+
require 'rack/backports/uri/common'
|
12
|
+
else
|
13
|
+
require 'uri/common'
|
14
|
+
end
|
15
|
+
|
7
16
|
module Rack
|
8
17
|
# Rack::Utils contains a grab-bag of useful methods for writing web
|
9
18
|
# applications adopted from all kinds of Ruby libraries.
|
10
19
|
|
11
20
|
module Utils
|
12
|
-
#
|
13
|
-
# query strings faster. Use this rather than the cgi.rb
|
14
|
-
# version since it's faster. (Stolen from Camping).
|
21
|
+
# URI escapes a string. (CGI style space to +)
|
15
22
|
def escape(s)
|
16
|
-
|
17
|
-
'%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
|
18
|
-
}.tr(' ', '+')
|
23
|
+
URI.encode_www_form_component(s)
|
19
24
|
end
|
20
25
|
module_function :escape
|
21
26
|
|
22
|
-
#
|
27
|
+
# Like URI escaping, but with %20 instead of +. Strictly speaking this is
|
28
|
+
# true URI escaping.
|
29
|
+
def escape_path(s)
|
30
|
+
escape(s).gsub('+', '%20')
|
31
|
+
end
|
32
|
+
module_function :escape_path
|
33
|
+
|
34
|
+
# Unescapes a URI escaped string.
|
23
35
|
def unescape(s)
|
24
|
-
|
25
|
-
[$1.delete('%')].pack('H*')
|
26
|
-
}
|
36
|
+
URI.decode_www_form_component(s)
|
27
37
|
end
|
28
38
|
module_function :unescape
|
29
39
|
|
30
40
|
DEFAULT_SEP = /[&;] */n
|
31
41
|
|
32
|
-
class << self
|
33
|
-
attr_accessor :key_space_limit
|
34
|
-
end
|
35
|
-
|
36
|
-
# The default number of bytes to allow parameter keys to take up.
|
37
|
-
# This helps prevent a rogue client from flooding a Request.
|
38
|
-
self.key_space_limit = 65536
|
39
|
-
|
40
42
|
# Stolen from Mongrel, with some small modifications:
|
41
43
|
# Parses a query string by breaking it up at the '&'
|
42
44
|
# and ';' characters. You can also use this to parse
|
@@ -45,19 +47,8 @@ module Rack
|
|
45
47
|
def parse_query(qs, d = nil)
|
46
48
|
params = {}
|
47
49
|
|
48
|
-
max_key_space = Utils.key_space_limit
|
49
|
-
bytes = 0
|
50
|
-
|
51
50
|
(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
|
52
51
|
k, v = p.split('=', 2).map { |x| unescape(x) }
|
53
|
-
|
54
|
-
if k
|
55
|
-
bytes += k.size
|
56
|
-
if bytes > max_key_space
|
57
|
-
raise RangeError, "exceeded available parameter key space"
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
52
|
if cur = params[k]
|
62
53
|
if cur.class == Array
|
63
54
|
params[k] << v
|
@@ -76,19 +67,8 @@ module Rack
|
|
76
67
|
def parse_nested_query(qs, d = nil)
|
77
68
|
params = {}
|
78
69
|
|
79
|
-
max_key_space = Utils.key_space_limit
|
80
|
-
bytes = 0
|
81
|
-
|
82
70
|
(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
|
83
|
-
k, v =
|
84
|
-
|
85
|
-
if k
|
86
|
-
bytes += k.size
|
87
|
-
if bytes > max_key_space
|
88
|
-
raise RangeError, "exceeded available parameter key space"
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
71
|
+
k, v = p.split('=', 2).map { |s| unescape(s) }
|
92
72
|
normalize_params(params, k, v)
|
93
73
|
end
|
94
74
|
|
@@ -162,17 +142,11 @@ module Rack
|
|
162
142
|
"&" => "&",
|
163
143
|
"<" => "<",
|
164
144
|
">" => ">",
|
165
|
-
"'" => "&#
|
145
|
+
"'" => "'",
|
166
146
|
'"' => """,
|
167
|
-
"/" => "&#
|
147
|
+
"/" => "/"
|
168
148
|
}
|
169
|
-
|
170
|
-
ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
|
171
|
-
else
|
172
|
-
# On 1.8, there is a kcode = 'u' bug that allows for XSS otherwhise
|
173
|
-
# TODO doesn't apply to jruby, so a better condition above might be preferable?
|
174
|
-
ESCAPE_HTML_PATTERN = /#{Regexp.union(*ESCAPE_HTML.keys)}/n
|
175
|
-
end
|
149
|
+
ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
|
176
150
|
|
177
151
|
# Escape ampersands, brackets and quotes to their HTML/XML entities.
|
178
152
|
def escape_html(string)
|
@@ -269,7 +243,7 @@ module Rack
|
|
269
243
|
end
|
270
244
|
module_function :delete_cookie_header!
|
271
245
|
|
272
|
-
# Return the bytesize of String; uses String#
|
246
|
+
# Return the bytesize of String; uses String#size under Ruby 1.8 and
|
273
247
|
# String#bytesize under 1.9.
|
274
248
|
if ''.respond_to?(:bytesize)
|
275
249
|
def bytesize(string)
|
@@ -294,34 +268,42 @@ module Rack
|
|
294
268
|
def rfc2822(time)
|
295
269
|
wday = Time::RFC2822_DAY_NAME[time.wday]
|
296
270
|
mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
|
297
|
-
time.strftime("#{wday}, %d-#{mon}-%Y %
|
271
|
+
time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
|
298
272
|
end
|
299
273
|
module_function :rfc2822
|
300
274
|
|
301
|
-
#
|
302
|
-
#
|
303
|
-
if
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
275
|
+
# Parses the "Range:" header, if present, into an array of Range objects.
|
276
|
+
# Returns nil if the header is missing or syntactically invalid.
|
277
|
+
# Returns an empty array if none of the ranges are satisfiable.
|
278
|
+
def byte_ranges(env, size)
|
279
|
+
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
|
280
|
+
http_range = env['HTTP_RANGE']
|
281
|
+
return nil unless http_range
|
282
|
+
ranges = []
|
283
|
+
http_range.split(/,\s*/).each do |range_spec|
|
284
|
+
matches = range_spec.match(/bytes=(\d*)-(\d*)/)
|
285
|
+
return nil unless matches
|
286
|
+
r0,r1 = matches[1], matches[2]
|
287
|
+
if r0.empty?
|
288
|
+
return nil if r1.empty?
|
289
|
+
# suffix-byte-range-spec, represents trailing suffix of file
|
290
|
+
r0 = [size - r1.to_i, 0].max
|
291
|
+
r1 = size - 1
|
292
|
+
else
|
293
|
+
r0 = r0.to_i
|
294
|
+
if r1.empty?
|
295
|
+
r1 = size - 1
|
296
|
+
else
|
297
|
+
r1 = r1.to_i
|
298
|
+
return nil if r1 < r0 # backwards range is syntactically invalid
|
299
|
+
r1 = size-1 if r1 >= size
|
300
|
+
end
|
301
|
+
end
|
302
|
+
ranges << (r0..r1) if r0 <= r1
|
310
303
|
end
|
304
|
+
ranges
|
311
305
|
end
|
312
|
-
module_function :
|
313
|
-
|
314
|
-
# Constant time string comparison.
|
315
|
-
def secure_compare(a, b)
|
316
|
-
return false unless bytesize(a) == bytesize(b)
|
317
|
-
|
318
|
-
l = a.unpack("C*")
|
319
|
-
|
320
|
-
r, i = 0, -1
|
321
|
-
b.each_byte { |v| r |= v ^ l[i+=1] }
|
322
|
-
r == 0
|
323
|
-
end
|
324
|
-
module_function :secure_compare
|
306
|
+
module_function :byte_ranges
|
325
307
|
|
326
308
|
# Context allows the use of a compatible middleware at different points
|
327
309
|
# in a request handling stack. A compatible middleware must define
|
@@ -369,24 +351,19 @@ module Rack
|
|
369
351
|
end
|
370
352
|
|
371
353
|
def to_hash
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
else
|
376
|
-
hash[k] = v
|
377
|
-
end
|
378
|
-
hash
|
379
|
-
end
|
354
|
+
Hash[*map do |k, v|
|
355
|
+
[k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v]
|
356
|
+
end.flatten]
|
380
357
|
end
|
381
358
|
|
382
359
|
def [](k)
|
383
|
-
super(
|
384
|
-
super(@names[k.downcase])
|
360
|
+
super(k) || super(@names[k.downcase])
|
385
361
|
end
|
386
362
|
|
387
363
|
def []=(k, v)
|
388
|
-
|
389
|
-
@names[
|
364
|
+
canonical = k.downcase
|
365
|
+
delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
|
366
|
+
@names[k] = @names[canonical] = k
|
390
367
|
super k, v
|
391
368
|
end
|
392
369
|
|
@@ -484,10 +461,9 @@ module Rack
|
|
484
461
|
# Responses with HTTP status codes that should not have an entity body
|
485
462
|
STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 304)
|
486
463
|
|
487
|
-
SYMBOL_TO_STATUS_CODE = HTTP_STATUS_CODES.
|
488
|
-
|
489
|
-
|
490
|
-
}
|
464
|
+
SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
|
465
|
+
[message.downcase.gsub(/\s|-/, '_').to_sym, code]
|
466
|
+
}.flatten]
|
491
467
|
|
492
468
|
def status_code(status)
|
493
469
|
if status.is_a?(Symbol)
|
@@ -498,242 +474,7 @@ module Rack
|
|
498
474
|
end
|
499
475
|
module_function :status_code
|
500
476
|
|
501
|
-
|
502
|
-
#
|
503
|
-
# Usually, Rack::Request#POST takes care of calling this.
|
504
|
-
|
505
|
-
module Multipart
|
506
|
-
class UploadedFile
|
507
|
-
# The filename, *not* including the path, of the "uploaded" file
|
508
|
-
attr_reader :original_filename
|
509
|
-
|
510
|
-
# The content type of the "uploaded" file
|
511
|
-
attr_accessor :content_type
|
512
|
-
|
513
|
-
def initialize(path, content_type = "text/plain", binary = false)
|
514
|
-
raise "#{path} file does not exist" unless ::File.exist?(path)
|
515
|
-
@content_type = content_type
|
516
|
-
@original_filename = ::File.basename(path)
|
517
|
-
@tempfile = Tempfile.new(@original_filename)
|
518
|
-
@tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
|
519
|
-
@tempfile.binmode if binary
|
520
|
-
FileUtils.copy_file(path, @tempfile.path)
|
521
|
-
end
|
522
|
-
|
523
|
-
def path
|
524
|
-
@tempfile.path
|
525
|
-
end
|
526
|
-
alias_method :local_path, :path
|
527
|
-
|
528
|
-
def method_missing(method_name, *args, &block) #:nodoc:
|
529
|
-
@tempfile.__send__(method_name, *args, &block)
|
530
|
-
end
|
531
|
-
end
|
532
|
-
|
533
|
-
EOL = "\r\n"
|
534
|
-
MULTIPART_BOUNDARY = "AaB03x"
|
477
|
+
Multipart = Rack::Multipart
|
535
478
|
|
536
|
-
def self.parse_multipart(env)
|
537
|
-
unless env['CONTENT_TYPE'] =~
|
538
|
-
%r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
|
539
|
-
nil
|
540
|
-
else
|
541
|
-
boundary = "--#{$1}"
|
542
|
-
|
543
|
-
params = {}
|
544
|
-
buf = ""
|
545
|
-
content_length = env['CONTENT_LENGTH'].to_i
|
546
|
-
input = env['rack.input']
|
547
|
-
input.rewind
|
548
|
-
|
549
|
-
boundary_size = Utils.bytesize(boundary) + EOL.size
|
550
|
-
bufsize = 16384
|
551
|
-
|
552
|
-
content_length -= boundary_size
|
553
|
-
|
554
|
-
read_buffer = ''
|
555
|
-
|
556
|
-
status = input.read(boundary_size, read_buffer)
|
557
|
-
raise EOFError, "bad content body" unless status == boundary + EOL
|
558
|
-
|
559
|
-
rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/n
|
560
|
-
|
561
|
-
max_key_space = Utils.key_space_limit
|
562
|
-
bytes = 0
|
563
|
-
|
564
|
-
loop {
|
565
|
-
head = nil
|
566
|
-
body = ''
|
567
|
-
filename = content_type = name = nil
|
568
|
-
|
569
|
-
until head && buf =~ rx
|
570
|
-
if !head && i = buf.index(EOL+EOL)
|
571
|
-
head = buf.slice!(0, i+2) # First \r\n
|
572
|
-
buf.slice!(0, 2) # Second \r\n
|
573
|
-
|
574
|
-
token = /[^\s()<>,;:\\"\/\[\]?=]+/
|
575
|
-
condisp = /Content-Disposition:\s*#{token}\s*/i
|
576
|
-
dispparm = /;\s*(#{token})=("(?:\\"|[^"])*"|#{token})/
|
577
|
-
|
578
|
-
rfc2183 = /^#{condisp}(#{dispparm})+$/i
|
579
|
-
broken_quoted = /^#{condisp}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{token}=)/i
|
580
|
-
broken_unquoted = /^#{condisp}.*;\sfilename=(#{token})/i
|
581
|
-
|
582
|
-
if head =~ rfc2183
|
583
|
-
filename = Hash[head.scan(dispparm)]['filename']
|
584
|
-
filename = $1 if filename and filename =~ /^"(.*)"$/
|
585
|
-
elsif head =~ broken_quoted
|
586
|
-
filename = $1
|
587
|
-
elsif head =~ broken_unquoted
|
588
|
-
filename = $1
|
589
|
-
end
|
590
|
-
|
591
|
-
if filename && filename !~ /\\[^\\"]/
|
592
|
-
filename = Utils.unescape(filename).gsub(/\\(.)/, '\1')
|
593
|
-
end
|
594
|
-
|
595
|
-
content_type = head[/Content-Type: (.*)#{EOL}/ni, 1]
|
596
|
-
name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1]
|
597
|
-
|
598
|
-
if name
|
599
|
-
bytes += name.size
|
600
|
-
if bytes > max_key_space
|
601
|
-
raise RangeError, "exceeded available parameter key space"
|
602
|
-
end
|
603
|
-
end
|
604
|
-
|
605
|
-
if content_type || filename
|
606
|
-
body = Tempfile.new("RackMultipart")
|
607
|
-
body.binmode if body.respond_to?(:binmode)
|
608
|
-
end
|
609
|
-
|
610
|
-
next
|
611
|
-
end
|
612
|
-
|
613
|
-
# Save the read body part.
|
614
|
-
if head && (boundary_size+4 < buf.size)
|
615
|
-
body << buf.slice!(0, buf.size - (boundary_size+4))
|
616
|
-
end
|
617
|
-
|
618
|
-
c = input.read(bufsize < content_length ? bufsize : content_length, read_buffer)
|
619
|
-
raise EOFError, "bad content body" if c.nil? || c.empty?
|
620
|
-
buf << c
|
621
|
-
content_length -= c.size
|
622
|
-
end
|
623
|
-
|
624
|
-
# Save the rest.
|
625
|
-
if i = buf.index(rx)
|
626
|
-
body << buf.slice!(0, i)
|
627
|
-
buf.slice!(0, boundary_size+2)
|
628
|
-
|
629
|
-
content_length = -1 if $1 == "--"
|
630
|
-
end
|
631
|
-
|
632
|
-
if filename == ""
|
633
|
-
# filename is blank which means no file has been selected
|
634
|
-
data = nil
|
635
|
-
elsif filename
|
636
|
-
body.rewind
|
637
|
-
|
638
|
-
# Take the basename of the upload's original filename.
|
639
|
-
# This handles the full Windows paths given by Internet Explorer
|
640
|
-
# (and perhaps other broken user agents) without affecting
|
641
|
-
# those which give the lone filename.
|
642
|
-
filename = filename.split(/[\/\\]/).last
|
643
|
-
|
644
|
-
data = {:filename => filename, :type => content_type,
|
645
|
-
:name => name, :tempfile => body, :head => head}
|
646
|
-
elsif !filename && content_type
|
647
|
-
body.rewind
|
648
|
-
|
649
|
-
# Generic multipart cases, not coming from a form
|
650
|
-
data = {:type => content_type,
|
651
|
-
:name => name, :tempfile => body, :head => head}
|
652
|
-
else
|
653
|
-
data = body
|
654
|
-
end
|
655
|
-
|
656
|
-
Utils.normalize_params(params, name, data) unless data.nil?
|
657
|
-
|
658
|
-
# break if we're at the end of a buffer, but not if it is the end of a field
|
659
|
-
break if (buf.empty? && $1 != EOL) || content_length == -1
|
660
|
-
}
|
661
|
-
|
662
|
-
input.rewind
|
663
|
-
|
664
|
-
params
|
665
|
-
end
|
666
|
-
end
|
667
|
-
|
668
|
-
def self.build_multipart(params, first = true)
|
669
|
-
if first
|
670
|
-
unless params.is_a?(Hash)
|
671
|
-
raise ArgumentError, "value must be a Hash"
|
672
|
-
end
|
673
|
-
|
674
|
-
multipart = false
|
675
|
-
query = lambda { |value|
|
676
|
-
case value
|
677
|
-
when Array
|
678
|
-
value.each(&query)
|
679
|
-
when Hash
|
680
|
-
value.values.each(&query)
|
681
|
-
when UploadedFile
|
682
|
-
multipart = true
|
683
|
-
end
|
684
|
-
}
|
685
|
-
params.values.each(&query)
|
686
|
-
return nil unless multipart
|
687
|
-
end
|
688
|
-
|
689
|
-
flattened_params = Hash.new
|
690
|
-
|
691
|
-
params.each do |key, value|
|
692
|
-
k = first ? key.to_s : "[#{key}]"
|
693
|
-
|
694
|
-
case value
|
695
|
-
when Array
|
696
|
-
value.map { |v|
|
697
|
-
build_multipart(v, false).each { |subkey, subvalue|
|
698
|
-
flattened_params["#{k}[]#{subkey}"] = subvalue
|
699
|
-
}
|
700
|
-
}
|
701
|
-
when Hash
|
702
|
-
build_multipart(value, false).each { |subkey, subvalue|
|
703
|
-
flattened_params[k + subkey] = subvalue
|
704
|
-
}
|
705
|
-
else
|
706
|
-
flattened_params[k] = value
|
707
|
-
end
|
708
|
-
end
|
709
|
-
|
710
|
-
if first
|
711
|
-
flattened_params.map { |name, file|
|
712
|
-
if file.respond_to?(:original_filename)
|
713
|
-
::File.open(file.path, "rb") do |f|
|
714
|
-
f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
|
715
|
-
<<-EOF
|
716
|
-
--#{MULTIPART_BOUNDARY}\r
|
717
|
-
Content-Disposition: form-data; name="#{name}"; filename="#{Utils.escape(file.original_filename)}"\r
|
718
|
-
Content-Type: #{file.content_type}\r
|
719
|
-
Content-Length: #{::File.stat(file.path).size}\r
|
720
|
-
\r
|
721
|
-
#{f.read}\r
|
722
|
-
EOF
|
723
|
-
end
|
724
|
-
else
|
725
|
-
<<-EOF
|
726
|
-
--#{MULTIPART_BOUNDARY}\r
|
727
|
-
Content-Disposition: form-data; name="#{name}"\r
|
728
|
-
\r
|
729
|
-
#{file}\r
|
730
|
-
EOF
|
731
|
-
end
|
732
|
-
}.join + "--#{MULTIPART_BOUNDARY}--\r"
|
733
|
-
else
|
734
|
-
flattened_params
|
735
|
-
end
|
736
|
-
end
|
737
|
-
end
|
738
479
|
end
|
739
480
|
end
|