rack 1.5.5 → 1.6.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.
- checksums.yaml +4 -4
- data/KNOWN-ISSUES +14 -0
- data/README.rdoc +10 -6
- data/Rakefile +3 -4
- data/SPEC +59 -23
- data/lib/rack.rb +2 -1
- data/lib/rack/auth/abstract/request.rb +1 -1
- data/lib/rack/auth/basic.rb +1 -1
- data/lib/rack/auth/digest/md5.rb +1 -1
- data/lib/rack/backports/uri/common_18.rb +1 -1
- data/lib/rack/builder.rb +19 -4
- data/lib/rack/cascade.rb +2 -2
- data/lib/rack/chunked.rb +12 -1
- data/lib/rack/commonlogger.rb +13 -5
- data/lib/rack/conditionalget.rb +14 -2
- data/lib/rack/content_length.rb +5 -1
- data/lib/rack/deflater.rb +52 -13
- data/lib/rack/directory.rb +8 -2
- data/lib/rack/etag.rb +14 -6
- data/lib/rack/file.rb +10 -14
- data/lib/rack/handler.rb +2 -0
- data/lib/rack/handler/fastcgi.rb +4 -1
- data/lib/rack/handler/mongrel.rb +8 -2
- data/lib/rack/handler/scgi.rb +4 -1
- data/lib/rack/handler/thin.rb +8 -2
- data/lib/rack/handler/webrick.rb +46 -6
- data/lib/rack/head.rb +7 -2
- data/lib/rack/lint.rb +73 -25
- data/lib/rack/lobster.rb +8 -3
- data/lib/rack/methodoverride.rb +14 -3
- data/lib/rack/mime.rb +1 -15
- data/lib/rack/mock.rb +15 -7
- data/lib/rack/multipart.rb +2 -2
- data/lib/rack/multipart/parser.rb +107 -53
- data/lib/rack/multipart/uploaded_file.rb +2 -2
- data/lib/rack/nulllogger.rb +21 -2
- data/lib/rack/request.rb +38 -24
- data/lib/rack/response.rb +5 -0
- data/lib/rack/sendfile.rb +10 -5
- data/lib/rack/server.rb +45 -17
- data/lib/rack/session/abstract/id.rb +7 -6
- data/lib/rack/session/cookie.rb +17 -7
- data/lib/rack/session/memcache.rb +4 -4
- data/lib/rack/session/pool.rb +3 -6
- data/lib/rack/showexceptions.rb +20 -11
- data/lib/rack/showstatus.rb +1 -1
- data/lib/rack/static.rb +27 -30
- data/lib/rack/tempfile_reaper.rb +22 -0
- data/lib/rack/urlmap.rb +17 -3
- data/lib/rack/utils.rb +78 -47
- data/lib/rack/utils/okjson.rb +90 -91
- data/rack.gemspec +3 -3
- data/test/multipart/filename_and_no_name +6 -0
- data/test/multipart/invalid_character +6 -0
- data/test/spec_builder.rb +13 -4
- data/test/spec_chunked.rb +16 -0
- data/test/spec_commonlogger.rb +36 -0
- data/test/spec_content_length.rb +3 -1
- data/test/spec_deflater.rb +283 -148
- data/test/spec_etag.rb +11 -2
- data/test/spec_file.rb +11 -3
- data/test/spec_head.rb +2 -0
- data/test/spec_lobster.rb +1 -1
- data/test/spec_mock.rb +8 -0
- data/test/spec_multipart.rb +111 -49
- data/test/spec_request.rb +109 -25
- data/test/spec_response.rb +30 -0
- data/test/spec_server.rb +20 -5
- data/test/spec_session_cookie.rb +45 -2
- data/test/spec_session_memcache.rb +1 -1
- data/test/spec_showexceptions.rb +29 -36
- data/test/spec_showstatus.rb +19 -0
- data/test/spec_tempfile_reaper.rb +63 -0
- data/test/spec_urlmap.rb +23 -0
- data/test/spec_utils.rb +60 -10
- data/test/spec_webrick.rb +41 -0
- metadata +12 -9
- data/test/cgi/lighttpd.errors +0 -1
- data/test/multipart/three_files_three_fields +0 -31
data/lib/rack/lint.rb
CHANGED
@@ -102,7 +102,17 @@ module Rack
|
|
102
102
|
## follows the <tt>?</tt>, if any. May be
|
103
103
|
## empty, but is always required!
|
104
104
|
|
105
|
-
## <tt>SERVER_NAME</tt>, <tt>SERVER_PORT</tt>::
|
105
|
+
## <tt>SERVER_NAME</tt>, <tt>SERVER_PORT</tt>::
|
106
|
+
## When combined with <tt>SCRIPT_NAME</tt> and
|
107
|
+
## <tt>PATH_INFO</tt>, these variables can be
|
108
|
+
## used to complete the URL. Note, however,
|
109
|
+
## that <tt>HTTP_HOST</tt>, if present,
|
110
|
+
## should be used in preference to
|
111
|
+
## <tt>SERVER_NAME</tt> for reconstructing
|
112
|
+
## the request URL.
|
113
|
+
## <tt>SERVER_NAME</tt> and <tt>SERVER_PORT</tt>
|
114
|
+
## can never be empty strings, and so
|
115
|
+
## are always required.
|
106
116
|
|
107
117
|
## <tt>HTTP_</tt> Variables:: Variables corresponding to the
|
108
118
|
## client-supplied HTTP request
|
@@ -112,29 +122,60 @@ module Rack
|
|
112
122
|
## variables should correspond with
|
113
123
|
## the presence or absence of the
|
114
124
|
## appropriate HTTP header in the
|
115
|
-
## request. See
|
116
|
-
##
|
125
|
+
## request. See
|
126
|
+
## <a href="https://tools.ietf.org/html/rfc3875#section-4.1.18">
|
127
|
+
## RFC3875 section 4.1.18</a> for
|
128
|
+
## specific behavior.
|
117
129
|
|
118
130
|
## In addition to this, the Rack environment must include these
|
119
131
|
## Rack-specific variables:
|
120
132
|
|
121
|
-
## <tt>rack.version</tt>:: The Array representing this version of Rack
|
122
|
-
##
|
133
|
+
## <tt>rack.version</tt>:: The Array representing this version of Rack
|
134
|
+
## See Rack::VERSION, that corresponds to
|
135
|
+
## the version of this SPEC.
|
136
|
+
|
137
|
+
## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the
|
138
|
+
## request URL.
|
139
|
+
|
123
140
|
## <tt>rack.input</tt>:: See below, the input stream.
|
141
|
+
|
124
142
|
## <tt>rack.errors</tt>:: See below, the error stream.
|
125
|
-
|
126
|
-
## <tt>rack.
|
127
|
-
##
|
128
|
-
##
|
129
|
-
|
130
|
-
## <tt>rack.
|
131
|
-
##
|
143
|
+
|
144
|
+
## <tt>rack.multithread</tt>:: true if the application object may be
|
145
|
+
## simultaneously invoked by another thread
|
146
|
+
## in the same process, false otherwise.
|
147
|
+
|
148
|
+
## <tt>rack.multiprocess</tt>:: true if an equivalent application object
|
149
|
+
## may be simultaneously invoked by another
|
150
|
+
## process, false otherwise.
|
151
|
+
|
152
|
+
## <tt>rack.run_once</tt>:: true if the server expects
|
153
|
+
## (but does not guarantee!) that the
|
154
|
+
## application will only be invoked this one
|
155
|
+
## time during the life of its containing
|
156
|
+
## process. Normally, this will only be true
|
157
|
+
## for a server based on CGI
|
158
|
+
## (or something similar).
|
159
|
+
|
160
|
+
## <tt>rack.hijack?</tt>:: present and true if the server supports
|
161
|
+
## connection hijacking. See below, hijacking.
|
162
|
+
|
163
|
+
## <tt>rack.hijack</tt>:: an object responding to #call that must be
|
164
|
+
## called at least once before using
|
165
|
+
## rack.hijack_io.
|
166
|
+
## It is recommended #call return rack.hijack_io
|
167
|
+
## as well as setting it in env if necessary.
|
168
|
+
|
169
|
+
## <tt>rack.hijack_io</tt>:: if rack.hijack? is true, and rack.hijack
|
170
|
+
## has received #call, this will contain
|
171
|
+
## an object resembling an IO. See hijacking.
|
132
172
|
|
133
173
|
## Additional environment specifications have approved to
|
134
174
|
## standardized middleware APIs. None of these are required to
|
135
175
|
## be implemented by the server.
|
136
176
|
|
137
|
-
## <tt>rack.session</tt>:: A hash like interface for storing
|
177
|
+
## <tt>rack.session</tt>:: A hash like interface for storing
|
178
|
+
## request session data.
|
138
179
|
## The store must implement:
|
139
180
|
if session = env['rack.session']
|
140
181
|
## store(key, value) (aliased as []=);
|
@@ -218,7 +259,6 @@ module Rack
|
|
218
259
|
}
|
219
260
|
}
|
220
261
|
|
221
|
-
##
|
222
262
|
## There are the following restrictions:
|
223
263
|
|
224
264
|
## * <tt>rack.version</tt> must be an array of Integers.
|
@@ -311,15 +351,23 @@ module Rack
|
|
311
351
|
v
|
312
352
|
end
|
313
353
|
|
314
|
-
## * +read+ behaves like IO#read.
|
315
|
-
##
|
316
|
-
##
|
317
|
-
##
|
318
|
-
##
|
319
|
-
##
|
320
|
-
##
|
321
|
-
##
|
322
|
-
##
|
354
|
+
## * +read+ behaves like IO#read.
|
355
|
+
## Its signature is <tt>read([length, [buffer]])</tt>.
|
356
|
+
##
|
357
|
+
## If given, +length+ must be a non-negative Integer (>= 0) or +nil+,
|
358
|
+
## and +buffer+ must be a String and may not be nil.
|
359
|
+
##
|
360
|
+
## If +length+ is given and not nil, then this method reads at most
|
361
|
+
## +length+ bytes from the input stream.
|
362
|
+
##
|
363
|
+
## If +length+ is not given or nil, then this method reads
|
364
|
+
## all data until EOF.
|
365
|
+
##
|
366
|
+
## When EOF is reached, this method returns nil if +length+ is given
|
367
|
+
## and not nil, or "" if +length+ is not given or is nil.
|
368
|
+
##
|
369
|
+
## If +buffer+ is given, then the read data will be placed
|
370
|
+
## into +buffer+ instead of a newly created String object.
|
323
371
|
def read(*args)
|
324
372
|
assert("rack.input#read called with too many arguments") {
|
325
373
|
args.size <= 2
|
@@ -584,7 +632,7 @@ module Rack
|
|
584
632
|
assert("a header value must be a String, but the value of " +
|
585
633
|
"'#{key}' is a #{value.class}") { value.kind_of? String }
|
586
634
|
## consisting of lines (for multiple header values, e.g. multiple
|
587
|
-
## <tt>Set-Cookie</tt> values)
|
635
|
+
## <tt>Set-Cookie</tt> values) separated by "\\n".
|
588
636
|
value.split("\n").each { |item|
|
589
637
|
## The lines must not contain characters below 037.
|
590
638
|
assert("invalid header value #{key}: #{item.inspect}") {
|
@@ -660,7 +708,7 @@ module Rack
|
|
660
708
|
##
|
661
709
|
## If the Body responds to +close+, it will be called after iteration. If
|
662
710
|
## the body is replaced by a middleware after action, the original body
|
663
|
-
## must be closed first, if it
|
711
|
+
## must be closed first, if it responds to close.
|
664
712
|
# XXX howto: assert("Body has not been closed") { @closed }
|
665
713
|
|
666
714
|
|
data/lib/rack/lobster.rb
CHANGED
@@ -32,9 +32,14 @@ module Rack
|
|
32
32
|
def call(env)
|
33
33
|
req = Request.new(env)
|
34
34
|
if req.GET["flip"] == "left"
|
35
|
-
lobster = LobsterString.split("\n").
|
36
|
-
|
37
|
-
|
35
|
+
lobster = LobsterString.split("\n").map do |line|
|
36
|
+
line.ljust(42).reverse.
|
37
|
+
gsub('\\', 'TEMP').
|
38
|
+
gsub('/', '\\').
|
39
|
+
gsub('TEMP', '/').
|
40
|
+
gsub('{','}').
|
41
|
+
gsub('(',')')
|
42
|
+
end.join("\n")
|
38
43
|
href = "?flip=right"
|
39
44
|
elsif req.GET["flip"] == "crash"
|
40
45
|
raise "Lobster crashed"
|
data/lib/rack/methodoverride.rb
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
module Rack
|
2
2
|
class MethodOverride
|
3
|
-
HTTP_METHODS = %w(GET HEAD PUT POST DELETE OPTIONS PATCH)
|
3
|
+
HTTP_METHODS = %w(GET HEAD PUT POST DELETE OPTIONS PATCH LINK UNLINK)
|
4
4
|
|
5
5
|
METHOD_OVERRIDE_PARAM_KEY = "_method".freeze
|
6
6
|
HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE".freeze
|
7
|
+
ALLOWED_METHODS = ["POST"]
|
7
8
|
|
8
9
|
def initialize(app)
|
9
10
|
@app = app
|
10
11
|
end
|
11
12
|
|
12
13
|
def call(env)
|
13
|
-
if env["REQUEST_METHOD"]
|
14
|
+
if allowed_methods.include?(env["REQUEST_METHOD"])
|
14
15
|
method = method_override(env)
|
15
16
|
if HTTP_METHODS.include?(method)
|
16
17
|
env["rack.methodoverride.original_method"] = env["REQUEST_METHOD"]
|
@@ -23,9 +24,19 @@ module Rack
|
|
23
24
|
|
24
25
|
def method_override(env)
|
25
26
|
req = Request.new(env)
|
26
|
-
method = req
|
27
|
+
method = method_override_param(req) ||
|
27
28
|
env[HTTP_METHOD_OVERRIDE_HEADER]
|
28
29
|
method.to_s.upcase
|
29
30
|
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def allowed_methods
|
35
|
+
ALLOWED_METHODS
|
36
|
+
end
|
37
|
+
|
38
|
+
def method_override_param(req)
|
39
|
+
req.POST[METHOD_OVERRIDE_PARAM_KEY]
|
40
|
+
end
|
30
41
|
end
|
31
42
|
end
|
data/lib/rack/mime.rb
CHANGED
@@ -29,21 +29,7 @@ module Rack
|
|
29
29
|
v1, v2 = value.split('/', 2)
|
30
30
|
m1, m2 = matcher.split('/', 2)
|
31
31
|
|
32
|
-
|
33
|
-
if m2.nil? || m2 == '*'
|
34
|
-
return true
|
35
|
-
elsif m2 == v2
|
36
|
-
return true
|
37
|
-
else
|
38
|
-
return false
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
return false if v1 != m1
|
43
|
-
|
44
|
-
return true if m2.nil? || m2 == '*'
|
45
|
-
|
46
|
-
m2 == v2
|
32
|
+
(m1 == '*' || v1 == m1) && (m2.nil? || m2 == '*' || m2 == v2)
|
47
33
|
end
|
48
34
|
module_function :match?
|
49
35
|
|
data/lib/rack/mock.rb
CHANGED
@@ -53,12 +53,13 @@ module Rack
|
|
53
53
|
@app = app
|
54
54
|
end
|
55
55
|
|
56
|
-
def get(uri, opts={})
|
57
|
-
def post(uri, opts={})
|
58
|
-
def put(uri, opts={})
|
59
|
-
def patch(uri, opts={})
|
60
|
-
def delete(uri, opts={})
|
61
|
-
def head(uri, opts={})
|
56
|
+
def get(uri, opts={}) request("GET", uri, opts) end
|
57
|
+
def post(uri, opts={}) request("POST", uri, opts) end
|
58
|
+
def put(uri, opts={}) request("PUT", uri, opts) end
|
59
|
+
def patch(uri, opts={}) request("PATCH", uri, opts) end
|
60
|
+
def delete(uri, opts={}) request("DELETE", uri, opts) end
|
61
|
+
def head(uri, opts={}) request("HEAD", uri, opts) end
|
62
|
+
def options(uri, opts={}) request("OPTIONS", uri, opts) end
|
62
63
|
|
63
64
|
def request(method="GET", uri="", opts={})
|
64
65
|
env = self.class.env_for(uri, opts.merge(:method => method))
|
@@ -76,9 +77,16 @@ module Rack
|
|
76
77
|
body.close if body.respond_to?(:close)
|
77
78
|
end
|
78
79
|
|
80
|
+
# For historical reasons, we're pinning to RFC 2396. It's easier for users
|
81
|
+
# and we get support from ruby 1.8 to 2.2 using this method.
|
82
|
+
def self.parse_uri_rfc2396(uri)
|
83
|
+
@parser ||= defined?(URI::RFC2396_Parser) ? URI::RFC2396_Parser.new : URI
|
84
|
+
@parser.parse(uri)
|
85
|
+
end
|
86
|
+
|
79
87
|
# Return the Rack environment used for a request to +uri+.
|
80
88
|
def self.env_for(uri="", opts={})
|
81
|
-
uri =
|
89
|
+
uri = parse_uri_rfc2396(uri)
|
82
90
|
uri.path = "/#{uri.path}" unless uri.path[0] == ?/
|
83
91
|
|
84
92
|
env = DEFAULT_ENV.dup
|
data/lib/rack/multipart.rb
CHANGED
@@ -9,7 +9,7 @@ module Rack
|
|
9
9
|
|
10
10
|
EOL = "\r\n"
|
11
11
|
MULTIPART_BOUNDARY = "AaB03x"
|
12
|
-
MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|
|
12
|
+
MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni
|
13
13
|
TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
|
14
14
|
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
|
15
15
|
DISPPARM = /;\s*(#{TOKEN})=("(?:\\"|[^"])*"|#{TOKEN})/
|
@@ -22,7 +22,7 @@ module Rack
|
|
22
22
|
|
23
23
|
class << self
|
24
24
|
def parse_multipart(env)
|
25
|
-
Parser.
|
25
|
+
Parser.create(env).parse
|
26
26
|
end
|
27
27
|
|
28
28
|
def build_multipart(params, first = true)
|
@@ -2,31 +2,52 @@ require 'rack/utils'
|
|
2
2
|
|
3
3
|
module Rack
|
4
4
|
module Multipart
|
5
|
-
class MultipartLimitError < Errno::EMFILE; end
|
6
|
-
|
7
5
|
class Parser
|
8
6
|
BUFSIZE = 16384
|
9
7
|
|
10
|
-
|
8
|
+
DUMMY = Struct.new(:parse).new
|
9
|
+
|
10
|
+
def self.create(env)
|
11
|
+
return DUMMY unless env['CONTENT_TYPE'] =~ MULTIPART
|
12
|
+
|
13
|
+
io = env['rack.input']
|
14
|
+
io.rewind
|
15
|
+
|
16
|
+
content_length = env['CONTENT_LENGTH']
|
17
|
+
content_length = content_length.to_i if content_length
|
18
|
+
|
19
|
+
new($1, io, content_length, env)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(boundary, io, content_length, env)
|
23
|
+
@buf = ""
|
24
|
+
|
25
|
+
if @buf.respond_to? :force_encoding
|
26
|
+
@buf.force_encoding Encoding::ASCII_8BIT
|
27
|
+
end
|
28
|
+
|
29
|
+
@params = Utils::KeySpaceConstrainedParams.new
|
30
|
+
@boundary = "--#{boundary}"
|
31
|
+
@io = io
|
32
|
+
@content_length = content_length
|
33
|
+
@boundary_size = Utils.bytesize(@boundary) + EOL.size
|
11
34
|
@env = env
|
35
|
+
|
36
|
+
if @content_length
|
37
|
+
@content_length -= @boundary_size
|
38
|
+
end
|
39
|
+
|
40
|
+
@rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
|
41
|
+
@full_boundary = @boundary + EOL
|
12
42
|
end
|
13
43
|
|
14
44
|
def parse
|
15
|
-
return nil unless setup_parse
|
16
|
-
|
17
45
|
fast_forward_to_first_boundary
|
18
46
|
|
19
|
-
opened_files = 0
|
20
47
|
loop do
|
21
|
-
|
22
48
|
head, filename, content_type, name, body =
|
23
49
|
get_current_head_and_filename_and_content_type_and_name_and_body
|
24
50
|
|
25
|
-
if Utils.multipart_part_limit > 0
|
26
|
-
opened_files += 1 if filename
|
27
|
-
raise MultipartLimitError, 'Maximum file multiparts in content reached' if opened_files >= Utils.multipart_part_limit
|
28
|
-
end
|
29
|
-
|
30
51
|
# Save the rest.
|
31
52
|
if i = @buf.index(rx)
|
32
53
|
body << @buf.slice!(0, i)
|
@@ -35,9 +56,11 @@ module Rack
|
|
35
56
|
@content_length = -1 if $1 == "--"
|
36
57
|
end
|
37
58
|
|
38
|
-
|
59
|
+
get_data(filename, body, content_type, name, head) do |data|
|
60
|
+
tag_multipart_encoding(filename, content_type, name, data)
|
39
61
|
|
40
|
-
|
62
|
+
Utils.normalize_params(@params, name, data)
|
63
|
+
end
|
41
64
|
|
42
65
|
# break if we're at the end of a buffer, but not if it is the end of a field
|
43
66
|
break if (@buf.empty? && $1 != EOL) || @content_length == -1
|
@@ -49,33 +72,9 @@ module Rack
|
|
49
72
|
end
|
50
73
|
|
51
74
|
private
|
52
|
-
def
|
53
|
-
return false unless @env['CONTENT_TYPE'] =~ MULTIPART
|
54
|
-
|
55
|
-
@boundary = "--#{$1}"
|
56
|
-
|
57
|
-
@buf = ""
|
58
|
-
@params = Utils::KeySpaceConstrainedParams.new
|
75
|
+
def full_boundary; @full_boundary; end
|
59
76
|
|
60
|
-
|
61
|
-
@io.rewind
|
62
|
-
|
63
|
-
@boundary_size = Utils.bytesize(@boundary) + EOL.size
|
64
|
-
|
65
|
-
if @content_length = @env['CONTENT_LENGTH']
|
66
|
-
@content_length = @content_length.to_i
|
67
|
-
@content_length -= @boundary_size
|
68
|
-
end
|
69
|
-
true
|
70
|
-
end
|
71
|
-
|
72
|
-
def full_boundary
|
73
|
-
@boundary + EOL
|
74
|
-
end
|
75
|
-
|
76
|
-
def rx
|
77
|
-
@rx ||= /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
|
78
|
-
end
|
77
|
+
def rx; @rx; end
|
79
78
|
|
80
79
|
def fast_forward_to_first_boundary
|
81
80
|
loop do
|
@@ -95,8 +94,12 @@ module Rack
|
|
95
94
|
def get_current_head_and_filename_and_content_type_and_name_and_body
|
96
95
|
head = nil
|
97
96
|
body = ''
|
97
|
+
|
98
|
+
if body.respond_to? :force_encoding
|
99
|
+
body.force_encoding Encoding::ASCII_8BIT
|
100
|
+
end
|
101
|
+
|
98
102
|
filename = content_type = name = nil
|
99
|
-
content = nil
|
100
103
|
|
101
104
|
until head && @buf =~ rx
|
102
105
|
if !head && i = @buf.index(EOL+EOL)
|
@@ -109,8 +112,12 @@ module Rack
|
|
109
112
|
|
110
113
|
filename = get_filename(head)
|
111
114
|
|
115
|
+
if name.nil? || name.empty? && filename
|
116
|
+
name = filename
|
117
|
+
end
|
118
|
+
|
112
119
|
if filename
|
113
|
-
body = Tempfile.new("RackMultipart")
|
120
|
+
(@env['rack.tempfiles'] ||= []) << body = Tempfile.new("RackMultipart")
|
114
121
|
body.binmode if body.respond_to?(:binmode)
|
115
122
|
end
|
116
123
|
|
@@ -134,29 +141,78 @@ module Rack
|
|
134
141
|
|
135
142
|
def get_filename(head)
|
136
143
|
filename = nil
|
137
|
-
|
144
|
+
case head
|
145
|
+
when RFC2183
|
138
146
|
filename = Hash[head.scan(DISPPARM)]['filename']
|
139
147
|
filename = $1 if filename and filename =~ /^"(.*)"$/
|
140
|
-
|
141
|
-
filename = $1
|
142
|
-
elsif head =~ BROKEN_UNQUOTED
|
148
|
+
when BROKEN_QUOTED, BROKEN_UNQUOTED
|
143
149
|
filename = $1
|
144
150
|
end
|
145
151
|
|
146
|
-
|
152
|
+
return unless filename
|
153
|
+
|
154
|
+
if filename.scan(/%.?.?/).all? { |s| s =~ /%[0-9a-fA-F]{2}/ }
|
147
155
|
filename = Utils.unescape(filename)
|
148
156
|
end
|
149
|
-
|
157
|
+
|
158
|
+
scrub_filename filename
|
159
|
+
|
160
|
+
if filename !~ /\\[^\\"]/
|
150
161
|
filename = filename.gsub(/\\(.)/, '\1')
|
151
162
|
end
|
152
163
|
filename
|
153
164
|
end
|
154
165
|
|
166
|
+
if "<3".respond_to? :valid_encoding?
|
167
|
+
def scrub_filename(filename)
|
168
|
+
unless filename.valid_encoding?
|
169
|
+
# FIXME: this force_encoding is for Ruby 2.0 and 1.9 support.
|
170
|
+
# We can remove it after they are dropped
|
171
|
+
filename.force_encoding(Encoding::ASCII_8BIT)
|
172
|
+
filename.encode!(:invalid => :replace, :undef => :replace)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
CHARSET = "charset"
|
177
|
+
TEXT_PLAIN = "text/plain"
|
178
|
+
|
179
|
+
def tag_multipart_encoding(filename, content_type, name, body)
|
180
|
+
name.force_encoding Encoding::UTF_8
|
181
|
+
|
182
|
+
return if filename
|
183
|
+
|
184
|
+
encoding = Encoding::UTF_8
|
185
|
+
|
186
|
+
if content_type
|
187
|
+
list = content_type.split(';')
|
188
|
+
type_subtype = list.first
|
189
|
+
type_subtype.strip!
|
190
|
+
if TEXT_PLAIN == type_subtype
|
191
|
+
rest = list.drop 1
|
192
|
+
rest.each do |param|
|
193
|
+
k,v = param.split('=', 2)
|
194
|
+
k.strip!
|
195
|
+
v.strip!
|
196
|
+
encoding = Encoding.find v if k == CHARSET
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
name.force_encoding encoding
|
202
|
+
body.force_encoding encoding
|
203
|
+
end
|
204
|
+
else
|
205
|
+
def scrub_filename(filename)
|
206
|
+
end
|
207
|
+
def tag_multipart_encoding(filename, content_type, name, body)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
155
211
|
def get_data(filename, body, content_type, name, head)
|
156
|
-
data =
|
212
|
+
data = body
|
157
213
|
if filename == ""
|
158
214
|
# filename is blank which means no file has been selected
|
159
|
-
return
|
215
|
+
return
|
160
216
|
elsif filename
|
161
217
|
body.rewind
|
162
218
|
|
@@ -174,11 +230,9 @@ module Rack
|
|
174
230
|
# Generic multipart cases, not coming from a form
|
175
231
|
data = {:type => content_type,
|
176
232
|
:name => name, :tempfile => body, :head => head}
|
177
|
-
else
|
178
|
-
data = body
|
179
233
|
end
|
180
234
|
|
181
|
-
|
235
|
+
yield data
|
182
236
|
end
|
183
237
|
end
|
184
238
|
end
|