rack 1.6.13 → 2.0.0.alpha
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 +5 -5
- data/HISTORY.md +139 -18
- data/README.rdoc +17 -25
- data/Rakefile +6 -14
- data/SPEC +8 -9
- data/contrib/rack_logo.svg +164 -111
- data/lib/rack.rb +70 -21
- data/lib/rack/auth/digest/request.rb +1 -1
- data/lib/rack/body_proxy.rb +14 -9
- data/lib/rack/builder.rb +3 -3
- data/lib/rack/chunked.rb +5 -5
- data/lib/rack/{commonlogger.rb → common_logger.rb} +2 -2
- data/lib/rack/{conditionalget.rb → conditional_get.rb} +0 -0
- data/lib/rack/content_length.rb +2 -2
- data/lib/rack/deflater.rb +4 -4
- data/lib/rack/directory.rb +49 -55
- data/lib/rack/etag.rb +2 -1
- data/lib/rack/events.rb +154 -0
- data/lib/rack/file.rb +55 -40
- data/lib/rack/handler.rb +2 -24
- data/lib/rack/handler/cgi.rb +15 -16
- data/lib/rack/handler/fastcgi.rb +13 -14
- data/lib/rack/handler/lsws.rb +11 -11
- data/lib/rack/handler/scgi.rb +15 -15
- data/lib/rack/handler/thin.rb +3 -0
- data/lib/rack/handler/webrick.rb +22 -24
- data/lib/rack/head.rb +15 -17
- data/lib/rack/lint.rb +38 -38
- data/lib/rack/lobster.rb +1 -1
- data/lib/rack/lock.rb +6 -10
- data/lib/rack/logger.rb +2 -2
- data/lib/rack/media_type.rb +38 -0
- data/lib/rack/{methodoverride.rb → method_override.rb} +4 -11
- data/lib/rack/mime.rb +18 -5
- data/lib/rack/mock.rb +35 -52
- data/lib/rack/multipart.rb +35 -6
- data/lib/rack/multipart/generator.rb +4 -4
- data/lib/rack/multipart/parser.rb +273 -158
- data/lib/rack/multipart/uploaded_file.rb +1 -2
- data/lib/rack/{nulllogger.rb → null_logger.rb} +1 -1
- data/lib/rack/query_parser.rb +174 -0
- data/lib/rack/recursive.rb +8 -8
- data/lib/rack/reloader.rb +1 -2
- data/lib/rack/request.rb +370 -304
- data/lib/rack/response.rb +129 -56
- data/lib/rack/rewindable_input.rb +1 -12
- data/lib/rack/runtime.rb +10 -18
- data/lib/rack/sendfile.rb +5 -7
- data/lib/rack/server.rb +31 -25
- data/lib/rack/session/abstract/id.rb +93 -135
- data/lib/rack/session/cookie.rb +26 -28
- data/lib/rack/session/memcache.rb +8 -14
- data/lib/rack/session/pool.rb +14 -21
- data/lib/rack/show_exceptions.rb +386 -0
- data/lib/rack/{showstatus.rb → show_status.rb} +3 -3
- data/lib/rack/static.rb +30 -5
- data/lib/rack/tempfile_reaper.rb +2 -2
- data/lib/rack/urlmap.rb +13 -14
- data/lib/rack/utils.rb +128 -221
- data/rack.gemspec +9 -5
- data/test/builder/an_underscore_app.rb +5 -0
- data/test/builder/options.ru +1 -1
- data/test/cgi/test.fcgi +1 -0
- data/test/cgi/test.gz +0 -0
- data/test/helper.rb +31 -0
- data/test/multipart/filename_with_encoded_words +7 -0
- data/test/multipart/{filename_with_null_byte → filename_with_single_quote} +1 -1
- data/test/multipart/quoted +15 -0
- data/test/multipart/rack-logo.png +0 -0
- data/test/registering_handler/rack/handler/registering_myself.rb +1 -1
- data/test/spec_auth_basic.rb +20 -19
- data/test/spec_auth_digest.rb +47 -46
- data/test/spec_body_proxy.rb +27 -27
- data/test/spec_builder.rb +51 -41
- data/test/spec_cascade.rb +24 -22
- data/test/spec_cgi.rb +49 -67
- data/test/spec_chunked.rb +36 -34
- data/test/{spec_commonlogger.rb → spec_common_logger.rb} +23 -21
- data/test/{spec_conditionalget.rb → spec_conditional_get.rb} +29 -28
- data/test/spec_config.rb +3 -2
- data/test/spec_content_length.rb +18 -17
- data/test/spec_content_type.rb +13 -12
- data/test/spec_deflater.rb +66 -40
- data/test/spec_directory.rb +72 -27
- data/test/spec_etag.rb +32 -31
- data/test/spec_events.rb +133 -0
- data/test/spec_fastcgi.rb +50 -72
- data/test/spec_file.rb +96 -77
- data/test/spec_handler.rb +19 -34
- data/test/spec_head.rb +15 -14
- data/test/spec_lint.rb +162 -197
- data/test/spec_lobster.rb +24 -23
- data/test/spec_lock.rb +69 -39
- data/test/spec_logger.rb +4 -3
- data/test/spec_media_type.rb +42 -0
- data/test/spec_method_override.rb +83 -0
- data/test/spec_mime.rb +19 -19
- data/test/spec_mock.rb +196 -151
- data/test/spec_multipart.rb +310 -202
- data/test/{spec_nulllogger.rb → spec_null_logger.rb} +5 -4
- data/test/spec_recursive.rb +17 -14
- data/test/spec_request.rb +763 -607
- data/test/spec_response.rb +209 -156
- data/test/spec_rewindable_input.rb +50 -40
- data/test/spec_runtime.rb +11 -10
- data/test/spec_sendfile.rb +30 -35
- data/test/spec_server.rb +78 -52
- data/test/spec_session_abstract_id.rb +11 -33
- data/test/spec_session_cookie.rb +97 -65
- data/test/spec_session_memcache.rb +63 -101
- data/test/spec_session_pool.rb +48 -84
- data/test/spec_show_exceptions.rb +80 -0
- data/test/{spec_showstatus.rb → spec_show_status.rb} +36 -35
- data/test/spec_static.rb +71 -32
- data/test/spec_tempfile_reaper.rb +11 -10
- data/test/spec_thin.rb +55 -50
- data/test/spec_urlmap.rb +79 -78
- data/test/spec_utils.rb +417 -345
- data/test/spec_version.rb +2 -8
- data/test/spec_webrick.rb +77 -67
- data/test/static/foo.html +1 -0
- data/test/testrequest.rb +1 -1
- data/test/unregistered_handler/rack/handler/unregistered.rb +1 -1
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +1 -1
- metadata +116 -71
- data/KNOWN-ISSUES +0 -44
- data/lib/rack/backports/uri/common_18.rb +0 -56
- data/lib/rack/backports/uri/common_192.rb +0 -52
- data/lib/rack/backports/uri/common_193.rb +0 -29
- data/lib/rack/handler/evented_mongrel.rb +0 -8
- data/lib/rack/handler/mongrel.rb +0 -106
- data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
- data/lib/rack/showexceptions.rb +0 -387
- data/lib/rack/utils/okjson.rb +0 -600
- data/test/spec_methodoverride.rb +0 -111
- data/test/spec_mongrel.rb +0 -182
- data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
- data/test/spec_showexceptions.rb +0 -98
data/lib/rack/lobster.rb
CHANGED
data/lib/rack/lock.rb
CHANGED
@@ -5,22 +5,18 @@ module Rack
|
|
5
5
|
# Rack::Lock locks every request inside a mutex, so that every request
|
6
6
|
# will effectively be executed synchronously.
|
7
7
|
class Lock
|
8
|
-
FLAG = 'rack.multithread'.freeze
|
9
|
-
|
10
8
|
def initialize(app, mutex = Mutex.new)
|
11
9
|
@app, @mutex = app, mutex
|
12
10
|
end
|
13
11
|
|
14
12
|
def call(env)
|
15
|
-
old, env[FLAG] = env[FLAG], false
|
16
13
|
@mutex.lock
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
env[FLAG] = old
|
14
|
+
begin
|
15
|
+
response = @app.call(env.merge(RACK_MULTITHREAD => false))
|
16
|
+
returned = response << BodyProxy.new(response.pop) { @mutex.unlock }
|
17
|
+
ensure
|
18
|
+
@mutex.unlock unless returned
|
19
|
+
end
|
24
20
|
end
|
25
21
|
end
|
26
22
|
end
|
data/lib/rack/logger.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
module Rack
|
2
|
+
# Rack::MediaType parse media type and parameters out of content_type string
|
3
|
+
|
4
|
+
class MediaType
|
5
|
+
SPLIT_PATTERN = %r{\s*[;,]\s*}
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# The media type (type/subtype) portion of the CONTENT_TYPE header
|
9
|
+
# without any media type parameters. e.g., when CONTENT_TYPE is
|
10
|
+
# "text/plain;charset=utf-8", the media-type is "text/plain".
|
11
|
+
#
|
12
|
+
# For more information on the use of media types in HTTP, see:
|
13
|
+
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
|
14
|
+
def type(content_type)
|
15
|
+
return nil unless content_type
|
16
|
+
content_type.split(SPLIT_PATTERN, 2).first.downcase
|
17
|
+
end
|
18
|
+
|
19
|
+
# The media type parameters provided in CONTENT_TYPE as a Hash, or
|
20
|
+
# an empty Hash if no CONTENT_TYPE or media-type parameters were
|
21
|
+
# provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
|
22
|
+
# this method responds with the following Hash:
|
23
|
+
# { 'charset' => 'utf-8' }
|
24
|
+
def params(content_type)
|
25
|
+
return {} if content_type.nil?
|
26
|
+
Hash[*content_type.split(SPLIT_PATTERN)[1..-1].
|
27
|
+
collect { |s| s.split('=', 2) }.
|
28
|
+
map { |k,v| [k.downcase, strip_doublequotes(v)] }.flatten]
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def strip_doublequotes(str)
|
34
|
+
(str[0] == ?" && str[-1] == ?") ? str[1..-2] : str
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
module Rack
|
2
2
|
class MethodOverride
|
3
|
-
HTTP_METHODS = %w
|
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 = [
|
7
|
+
ALLOWED_METHODS = %w[POST]
|
8
8
|
|
9
9
|
def initialize(app)
|
10
10
|
@app = app
|
@@ -14,7 +14,7 @@ module Rack
|
|
14
14
|
if allowed_methods.include?(env[REQUEST_METHOD])
|
15
15
|
method = method_override(env)
|
16
16
|
if HTTP_METHODS.include?(method)
|
17
|
-
env[
|
17
|
+
env[RACK_METHODOVERRIDE_ORIGINAL_METHOD] = env[REQUEST_METHOD]
|
18
18
|
env[REQUEST_METHOD] = method
|
19
19
|
end
|
20
20
|
end
|
@@ -26,11 +26,7 @@ module Rack
|
|
26
26
|
req = Request.new(env)
|
27
27
|
method = method_override_param(req) ||
|
28
28
|
env[HTTP_METHOD_OVERRIDE_HEADER]
|
29
|
-
|
30
|
-
method.to_s.upcase
|
31
|
-
rescue ArgumentError
|
32
|
-
env["rack.errors"].puts "Invalid string for method"
|
33
|
-
end
|
29
|
+
method.to_s.upcase
|
34
30
|
end
|
35
31
|
|
36
32
|
private
|
@@ -42,9 +38,6 @@ module Rack
|
|
42
38
|
def method_override_param(req)
|
43
39
|
req.POST[METHOD_OVERRIDE_PARAM_KEY]
|
44
40
|
rescue Utils::InvalidParameterError, Utils::ParameterTypeError
|
45
|
-
req.env["rack.errors"].puts "Invalid or incomplete POST params"
|
46
|
-
rescue EOFError
|
47
|
-
req.env["rack.errors"].puts "Bad request content body"
|
48
41
|
end
|
49
42
|
end
|
50
43
|
end
|
data/lib/rack/mime.rb
CHANGED
@@ -45,11 +45,6 @@ module Rack
|
|
45
45
|
#
|
46
46
|
# N.B. On Ubuntu the mime.types file does not include the leading period, so
|
47
47
|
# users may need to modify the data before merging into the hash.
|
48
|
-
#
|
49
|
-
# To add the list mongrel provides, use:
|
50
|
-
#
|
51
|
-
# require 'mongrel/handlers'
|
52
|
-
# Rack::Mime::MIME_TYPES.merge!(Mongrel::DirHandler::MIME_TYPES)
|
53
48
|
|
54
49
|
MIME_TYPES = {
|
55
50
|
".123" => "application/vnd.lotus-1-2-3",
|
@@ -154,8 +149,11 @@ module Rack
|
|
154
149
|
".dmg" => "application/octet-stream",
|
155
150
|
".dna" => "application/vnd.dna",
|
156
151
|
".doc" => "application/msword",
|
152
|
+
".docm" => "application/vnd.ms-word.document.macroEnabled.12",
|
157
153
|
".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
158
154
|
".dot" => "application/msword",
|
155
|
+
".dotm" => "application/vnd.ms-word.template.macroEnabled.12",
|
156
|
+
".dotx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
|
159
157
|
".dp" => "application/vnd.osgi.dp",
|
160
158
|
".dpg" => "application/vnd.dpgraph",
|
161
159
|
".dsc" => "text/prs.lines.tag",
|
@@ -444,10 +442,19 @@ module Rack
|
|
444
442
|
".pnm" => "image/x-portable-anymap",
|
445
443
|
".pntg" => "image/x-macpaint",
|
446
444
|
".portpkg" => "application/vnd.macports.portpkg",
|
445
|
+
".pot" => "application/vnd.ms-powerpoint",
|
446
|
+
".potm" => "application/vnd.ms-powerpoint.template.macroEnabled.12",
|
447
|
+
".potx" => "application/vnd.openxmlformats-officedocument.presentationml.template",
|
448
|
+
".ppa" => "application/vnd.ms-powerpoint",
|
449
|
+
".ppam" => "application/vnd.ms-powerpoint.addin.macroEnabled.12",
|
447
450
|
".ppd" => "application/vnd.cups-ppd",
|
448
451
|
".ppm" => "image/x-portable-pixmap",
|
449
452
|
".pps" => "application/vnd.ms-powerpoint",
|
453
|
+
".ppsm" => "application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
|
454
|
+
".ppsx" => "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
|
450
455
|
".ppt" => "application/vnd.ms-powerpoint",
|
456
|
+
".pptm" => "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
|
457
|
+
".pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
451
458
|
".prc" => "application/vnd.palm",
|
452
459
|
".pre" => "application/vnd.lotus-freelance",
|
453
460
|
".prf" => "application/pics-rules",
|
@@ -638,8 +645,14 @@ module Rack
|
|
638
645
|
".xfdl" => "application/vnd.xfdl",
|
639
646
|
".xhtml" => "application/xhtml+xml",
|
640
647
|
".xif" => "image/vnd.xiff",
|
648
|
+
".xla" => "application/vnd.ms-excel",
|
649
|
+
".xlam" => "application/vnd.ms-excel.addin.macroEnabled.12",
|
641
650
|
".xls" => "application/vnd.ms-excel",
|
651
|
+
".xlsb" => "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
|
642
652
|
".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
653
|
+
".xlsm" => "application/vnd.ms-excel.sheet.macroEnabled.12",
|
654
|
+
".xlt" => "application/vnd.ms-excel",
|
655
|
+
".xltx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
|
643
656
|
".xml" => "application/xml",
|
644
657
|
".xo" => "application/vnd.olpc-sugar",
|
645
658
|
".xop" => "application/xop+xml",
|
data/lib/rack/mock.rb
CHANGED
@@ -41,27 +41,27 @@ module Rack
|
|
41
41
|
end
|
42
42
|
|
43
43
|
DEFAULT_ENV = {
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
}
|
44
|
+
RACK_VERSION => Rack::VERSION,
|
45
|
+
RACK_INPUT => StringIO.new,
|
46
|
+
RACK_ERRORS => StringIO.new,
|
47
|
+
RACK_MULTITHREAD => true,
|
48
|
+
RACK_MULTIPROCESS => true,
|
49
|
+
RACK_RUNONCE => false,
|
50
|
+
}.freeze
|
51
51
|
|
52
52
|
def initialize(app)
|
53
53
|
@app = app
|
54
54
|
end
|
55
55
|
|
56
|
-
def get(uri, opts={}) request(
|
57
|
-
def post(uri, opts={}) request(
|
58
|
-
def put(uri, opts={}) request(
|
59
|
-
def patch(uri, opts={}) request(
|
60
|
-
def delete(uri, opts={}) request(
|
61
|
-
def head(uri, opts={}) request(
|
62
|
-
def options(uri, opts={}) request(
|
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
|
63
63
|
|
64
|
-
def request(method=
|
64
|
+
def request(method=GET, uri="", opts={})
|
65
65
|
env = self.class.env_for(uri, opts.merge(:method => method))
|
66
66
|
|
67
67
|
if opts[:lint]
|
@@ -70,17 +70,17 @@ module Rack
|
|
70
70
|
app = @app
|
71
71
|
end
|
72
72
|
|
73
|
-
errors = env[
|
73
|
+
errors = env[RACK_ERRORS]
|
74
74
|
status, headers, body = app.call(env)
|
75
75
|
MockResponse.new(status, headers, body, errors)
|
76
76
|
ensure
|
77
77
|
body.close if body.respond_to?(:close)
|
78
78
|
end
|
79
79
|
|
80
|
-
# For historical reasons, we're pinning to RFC 2396.
|
81
|
-
#
|
80
|
+
# For historical reasons, we're pinning to RFC 2396.
|
81
|
+
# URI::Parser = URI::RFC2396_Parser
|
82
82
|
def self.parse_uri_rfc2396(uri)
|
83
|
-
@parser ||=
|
83
|
+
@parser ||= URI::Parser.new
|
84
84
|
@parser.parse(uri)
|
85
85
|
end
|
86
86
|
|
@@ -91,28 +91,34 @@ module Rack
|
|
91
91
|
|
92
92
|
env = DEFAULT_ENV.dup
|
93
93
|
|
94
|
-
|
94
|
+
env[REQUEST_METHOD] = opts[:method] ? opts[:method].to_s.upcase : GET
|
95
|
+
env[SERVER_NAME] = uri.host || "example.org"
|
96
|
+
env[SERVER_PORT] = uri.port ? uri.port.to_s : "80"
|
97
|
+
env[QUERY_STRING] = uri.query.to_s
|
98
|
+
env[PATH_INFO] = (!uri.path || uri.path.empty?) ? "/" : uri.path
|
99
|
+
env[RACK_URL_SCHEME] = uri.scheme || "http"
|
100
|
+
env[HTTPS] = env[RACK_URL_SCHEME] == "https" ? "on" : "off"
|
95
101
|
|
96
102
|
env[SCRIPT_NAME] = opts[:script_name] || ""
|
97
103
|
|
98
104
|
if opts[:fatal]
|
99
|
-
env[
|
105
|
+
env[RACK_ERRORS] = FatalWarner.new
|
100
106
|
else
|
101
|
-
env[
|
107
|
+
env[RACK_ERRORS] = StringIO.new
|
102
108
|
end
|
103
109
|
|
104
110
|
if params = opts[:params]
|
105
|
-
if env[REQUEST_METHOD] ==
|
111
|
+
if env[REQUEST_METHOD] == GET
|
106
112
|
params = Utils.parse_nested_query(params) if params.is_a?(String)
|
107
113
|
params.update(Utils.parse_nested_query(env[QUERY_STRING]))
|
108
114
|
env[QUERY_STRING] = Utils.build_nested_query(params)
|
109
115
|
elsif !opts.has_key?(:input)
|
110
116
|
opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
|
111
117
|
if params.is_a?(Hash)
|
112
|
-
if data =
|
118
|
+
if data = Rack::Multipart.build_multipart(params)
|
113
119
|
opts[:input] = data
|
114
120
|
opts["CONTENT_LENGTH"] ||= data.length.to_s
|
115
|
-
opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{
|
121
|
+
opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Rack::Multipart::MULTIPART_BOUNDARY}"
|
116
122
|
else
|
117
123
|
opts[:input] = Utils.build_nested_query(params)
|
118
124
|
end
|
@@ -122,8 +128,7 @@ module Rack
|
|
122
128
|
end
|
123
129
|
end
|
124
130
|
|
125
|
-
empty_str =
|
126
|
-
empty_str.force_encoding("ASCII-8BIT") if empty_str.respond_to? :force_encoding
|
131
|
+
empty_str = ''.force_encoding(Encoding::ASCII_8BIT)
|
127
132
|
opts[:input] ||= empty_str
|
128
133
|
if String === opts[:input]
|
129
134
|
rack_input = StringIO.new(opts[:input])
|
@@ -131,10 +136,10 @@ module Rack
|
|
131
136
|
rack_input = opts[:input]
|
132
137
|
end
|
133
138
|
|
134
|
-
rack_input.set_encoding(Encoding::BINARY)
|
135
|
-
env[
|
139
|
+
rack_input.set_encoding(Encoding::BINARY)
|
140
|
+
env[RACK_INPUT] = rack_input
|
136
141
|
|
137
|
-
env["CONTENT_LENGTH"] ||= env[
|
142
|
+
env["CONTENT_LENGTH"] ||= env[RACK_INPUT].length.to_s
|
138
143
|
|
139
144
|
opts.each { |field, value|
|
140
145
|
env[field] = value if String === field
|
@@ -142,28 +147,6 @@ module Rack
|
|
142
147
|
|
143
148
|
env
|
144
149
|
end
|
145
|
-
|
146
|
-
if "<3".respond_to? :b
|
147
|
-
def self.env_with_encoding(env, opts, uri)
|
148
|
-
env[REQUEST_METHOD] = (opts[:method] ? opts[:method].to_s.upcase : "GET").b
|
149
|
-
env["SERVER_NAME"] = (uri.host || "example.org").b
|
150
|
-
env["SERVER_PORT"] = (uri.port ? uri.port.to_s : "80").b
|
151
|
-
env[QUERY_STRING] = (uri.query.to_s).b
|
152
|
-
env[PATH_INFO] = ((!uri.path || uri.path.empty?) ? "/" : uri.path).b
|
153
|
-
env["rack.url_scheme"] = (uri.scheme || "http").b
|
154
|
-
env["HTTPS"] = (env["rack.url_scheme"] == "https" ? "on" : "off").b
|
155
|
-
end
|
156
|
-
else
|
157
|
-
def self.env_with_encoding(env, opts, uri)
|
158
|
-
env[REQUEST_METHOD] = opts[:method] ? opts[:method].to_s.upcase : "GET"
|
159
|
-
env["SERVER_NAME"] = uri.host || "example.org"
|
160
|
-
env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80"
|
161
|
-
env[QUERY_STRING] = uri.query.to_s
|
162
|
-
env[PATH_INFO] = (!uri.path || uri.path.empty?) ? "/" : uri.path
|
163
|
-
env["rack.url_scheme"] = uri.scheme || "http"
|
164
|
-
env["HTTPS"] = env["rack.url_scheme"] == "https" ? "on" : "off"
|
165
|
-
end
|
166
|
-
end
|
167
150
|
end
|
168
151
|
|
169
152
|
# Rack::MockResponse provides useful helpers for testing your apps.
|
data/lib/rack/multipart.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
+
require 'rack/multipart/parser'
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
# A multipart form data parser, adapted from IOWA.
|
3
5
|
#
|
4
6
|
# Usually, Rack::Request#POST takes care of calling this.
|
5
7
|
module Multipart
|
6
8
|
autoload :UploadedFile, 'rack/multipart/uploaded_file'
|
7
|
-
autoload :Parser, 'rack/multipart/parser'
|
8
9
|
autoload :Generator, 'rack/multipart/generator'
|
9
10
|
|
10
11
|
EOL = "\r\n"
|
@@ -12,17 +13,45 @@ module Rack
|
|
12
13
|
MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni
|
13
14
|
TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
|
14
15
|
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
|
15
|
-
|
16
|
-
RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
|
16
|
+
VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
|
17
17
|
BROKEN_QUOTED = /^#{CONDISP}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
|
18
18
|
BROKEN_UNQUOTED = /^#{CONDISP}.*;\sfilename=(#{TOKEN})/i
|
19
19
|
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
|
20
|
-
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*\s+name=
|
20
|
+
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*\s+name=(#{VALUE})/ni
|
21
21
|
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
|
22
|
+
# Updated definitions from RFC 2231
|
23
|
+
ATTRIBUTE_CHAR = %r{[^ \t\v\n\r)(><@,;:\\"/\[\]?='*%]}
|
24
|
+
ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
|
25
|
+
SECTION = /\*[0-9]+/
|
26
|
+
REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
|
27
|
+
REGULAR_PARAMETER = /(#{REGULAR_PARAMETER_NAME})=(#{VALUE})/
|
28
|
+
EXTENDED_OTHER_NAME = /#{ATTRIBUTE}\*[1-9][0-9]*\*/
|
29
|
+
EXTENDED_OTHER_VALUE = /%[0-9a-fA-F]{2}|#{ATTRIBUTE_CHAR}/
|
30
|
+
EXTENDED_OTHER_PARAMETER = /(#{EXTENDED_OTHER_NAME})=(#{EXTENDED_OTHER_VALUE}*)/
|
31
|
+
EXTENDED_INITIAL_NAME = /#{ATTRIBUTE}(?:\*0)?\*/
|
32
|
+
EXTENDED_INITIAL_VALUE = /[a-zA-Z0-9\-]*'[a-zA-Z0-9\-]*'#{EXTENDED_OTHER_VALUE}*/
|
33
|
+
EXTENDED_INITIAL_PARAMETER = /(#{EXTENDED_INITIAL_NAME})=(#{EXTENDED_INITIAL_VALUE})/
|
34
|
+
EXTENDED_PARAMETER = /#{EXTENDED_INITIAL_PARAMETER}|#{EXTENDED_OTHER_PARAMETER}/
|
35
|
+
DISPPARM = /;\s*(?:#{REGULAR_PARAMETER}|#{EXTENDED_PARAMETER})\s*/
|
36
|
+
RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
|
22
37
|
|
23
38
|
class << self
|
24
|
-
def parse_multipart(env)
|
25
|
-
|
39
|
+
def parse_multipart(env, params = Rack::Utils.default_query_parser)
|
40
|
+
extract_multipart Rack::Request.new(env), params
|
41
|
+
end
|
42
|
+
|
43
|
+
def extract_multipart(req, params = Rack::Utils.default_query_parser)
|
44
|
+
io = req.get_header(RACK_INPUT)
|
45
|
+
io.rewind
|
46
|
+
content_length = req.content_length
|
47
|
+
content_length = content_length.to_i if content_length
|
48
|
+
|
49
|
+
tempfile = req.get_header(RACK_MULTIPART_TEMPFILE_FACTORY) || Parser::TEMPFILE_FACTORY
|
50
|
+
bufsize = req.get_header(RACK_MULTIPART_BUFFER_SIZE) || Parser::BUFSIZE
|
51
|
+
|
52
|
+
info = Parser.parse io, content_length, req.get_header('CONTENT_TYPE'), tempfile, bufsize, params
|
53
|
+
req.set_header(RACK_TEMPFILES, info.tmp_files)
|
54
|
+
info.params
|
26
55
|
end
|
27
56
|
|
28
57
|
def build_multipart(params, first = true)
|
@@ -11,12 +11,12 @@ module Rack
|
|
11
11
|
|
12
12
|
def dump
|
13
13
|
return nil if @first && !multipart?
|
14
|
-
return flattened_params
|
14
|
+
return flattened_params unless @first
|
15
15
|
|
16
16
|
flattened_params.map do |name, file|
|
17
17
|
if file.respond_to?(:original_filename)
|
18
|
-
::File.open(file.path,
|
19
|
-
f.set_encoding(Encoding::BINARY)
|
18
|
+
::File.open(file.path, 'rb') do |f|
|
19
|
+
f.set_encoding(Encoding::BINARY)
|
20
20
|
content_for_tempfile(f, file, name)
|
21
21
|
end
|
22
22
|
else
|
@@ -90,4 +90,4 @@ EOF
|
|
90
90
|
end
|
91
91
|
end
|
92
92
|
end
|
93
|
-
end
|
93
|
+
end
|
@@ -6,159 +6,304 @@ module Rack
|
|
6
6
|
|
7
7
|
class Parser
|
8
8
|
BUFSIZE = 16384
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
io
|
9
|
+
TEXT_PLAIN = "text/plain"
|
10
|
+
TEMPFILE_FACTORY = lambda { |filename, content_type|
|
11
|
+
Tempfile.new(["RackMultipart", ::File.extname(filename)])
|
12
|
+
}
|
13
|
+
|
14
|
+
class BoundedIO # :nodoc:
|
15
|
+
def initialize(io, content_length)
|
16
|
+
@io = io
|
17
|
+
@content_length = content_length
|
18
|
+
@cursor = 0
|
19
|
+
end
|
16
20
|
|
17
|
-
|
18
|
-
|
21
|
+
def read(size)
|
22
|
+
return if @cursor >= @content_length
|
19
23
|
|
20
|
-
|
21
|
-
lambda { |filename, content_type| Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0".freeze, '%00'.freeze))]) }
|
22
|
-
bufsize = env['rack.multipart.buffer_size'] || BUFSIZE
|
24
|
+
left = @content_length - @cursor
|
23
25
|
|
24
|
-
|
25
|
-
|
26
|
+
str = if left < size
|
27
|
+
@io.read left
|
28
|
+
else
|
29
|
+
@io.read size
|
30
|
+
end
|
26
31
|
|
27
|
-
|
28
|
-
|
32
|
+
if str
|
33
|
+
@cursor += str.bytesize
|
34
|
+
else
|
35
|
+
# Raise an error for mismatching Content-Length and actual contents
|
36
|
+
raise EOFError, "bad content body"
|
37
|
+
end
|
29
38
|
|
30
|
-
|
31
|
-
@buf.force_encoding Encoding::ASCII_8BIT
|
39
|
+
str
|
32
40
|
end
|
33
41
|
|
34
|
-
@
|
35
|
-
@boundary = "--#{boundary}"
|
36
|
-
@io = io
|
37
|
-
@content_length = content_length
|
38
|
-
@boundary_size = Utils.bytesize(@boundary) + EOL.size
|
39
|
-
@env = env
|
40
|
-
@tempfile = tempfile
|
41
|
-
@bufsize = bufsize
|
42
|
+
def eof?; @content_length == @cursor; end
|
42
43
|
|
43
|
-
|
44
|
-
@
|
44
|
+
def rewind
|
45
|
+
@io.rewind
|
45
46
|
end
|
47
|
+
end
|
46
48
|
|
47
|
-
|
48
|
-
|
49
|
+
MultipartInfo = Struct.new :params, :tmp_files
|
50
|
+
EMPTY = MultipartInfo.new(nil, [])
|
51
|
+
|
52
|
+
def self.parse_boundary(content_type)
|
53
|
+
return unless content_type
|
54
|
+
data = content_type.match(MULTIPART)
|
55
|
+
return unless data
|
56
|
+
data[1]
|
49
57
|
end
|
50
58
|
|
51
|
-
def parse
|
52
|
-
|
59
|
+
def self.parse(io, content_length, content_type, tmpfile, bufsize, qp)
|
60
|
+
return EMPTY if 0 == content_length
|
61
|
+
|
62
|
+
boundary = parse_boundary content_type
|
63
|
+
return EMPTY unless boundary
|
64
|
+
|
65
|
+
io = BoundedIO.new(io, content_length) if content_length
|
66
|
+
|
67
|
+
parser = new(boundary, tmpfile, bufsize, qp)
|
68
|
+
parser.on_read io.read(bufsize), io.eof?
|
53
69
|
|
54
|
-
opened_files = 0
|
55
70
|
loop do
|
71
|
+
break if parser.state == :DONE
|
72
|
+
parser.on_read io.read(bufsize), io.eof?
|
73
|
+
end
|
56
74
|
|
57
|
-
|
58
|
-
|
75
|
+
io.rewind
|
76
|
+
parser.result
|
77
|
+
end
|
59
78
|
|
60
|
-
|
61
|
-
|
62
|
-
|
79
|
+
class Collector
|
80
|
+
class MimePart < Struct.new(:body, :head, :filename, :content_type, :name)
|
81
|
+
def get_data
|
82
|
+
data = body
|
83
|
+
if filename == ""
|
84
|
+
# filename is blank which means no file has been selected
|
85
|
+
return
|
86
|
+
elsif filename
|
87
|
+
body.rewind if body.respond_to?(:rewind)
|
88
|
+
|
89
|
+
# Take the basename of the upload's original filename.
|
90
|
+
# This handles the full Windows paths given by Internet Explorer
|
91
|
+
# (and perhaps other broken user agents) without affecting
|
92
|
+
# those which give the lone filename.
|
93
|
+
fn = filename.split(/[\/\\]/).last
|
94
|
+
|
95
|
+
data = {:filename => fn, :type => content_type,
|
96
|
+
:name => name, :tempfile => body, :head => head}
|
97
|
+
elsif !filename && content_type && body.is_a?(IO)
|
98
|
+
body.rewind
|
99
|
+
|
100
|
+
# Generic multipart cases, not coming from a form
|
101
|
+
data = {:type => content_type,
|
102
|
+
:name => name, :tempfile => body, :head => head}
|
103
|
+
elsif !filename && data.empty?
|
104
|
+
return
|
105
|
+
end
|
106
|
+
|
107
|
+
yield data
|
63
108
|
end
|
109
|
+
end
|
64
110
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
111
|
+
class BufferPart < MimePart
|
112
|
+
def file?; false; end
|
113
|
+
def close; end
|
114
|
+
end
|
69
115
|
|
70
|
-
|
71
|
-
end
|
116
|
+
class TempfilePart < MimePart
|
117
|
+
def file?; true; end
|
118
|
+
def close; body.close; end
|
119
|
+
end
|
72
120
|
|
73
|
-
|
74
|
-
tag_multipart_encoding(filename, content_type, name, data)
|
121
|
+
include Enumerable
|
75
122
|
|
76
|
-
|
77
|
-
|
123
|
+
def initialize tempfile
|
124
|
+
@tempfile = tempfile
|
125
|
+
@mime_parts = []
|
126
|
+
@open_files = 0
|
127
|
+
end
|
78
128
|
|
79
|
-
|
80
|
-
|
129
|
+
def each
|
130
|
+
@mime_parts.each { |part| yield part }
|
81
131
|
end
|
82
132
|
|
83
|
-
|
133
|
+
def on_mime_head mime_index, head, filename, content_type, name
|
134
|
+
if filename
|
135
|
+
body = @tempfile.call(filename, content_type)
|
136
|
+
body.binmode if body.respond_to?(:binmode)
|
137
|
+
klass = TempfilePart
|
138
|
+
@open_files += 1
|
139
|
+
else
|
140
|
+
body = ''.force_encoding(Encoding::ASCII_8BIT)
|
141
|
+
klass = BufferPart
|
142
|
+
end
|
84
143
|
|
85
|
-
|
86
|
-
|
144
|
+
@mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
|
145
|
+
check_open_files
|
146
|
+
end
|
87
147
|
|
88
|
-
|
89
|
-
|
148
|
+
def on_mime_body mime_index, content
|
149
|
+
@mime_parts[mime_index].body << content
|
150
|
+
end
|
90
151
|
|
91
|
-
|
152
|
+
def on_mime_finish mime_index
|
153
|
+
end
|
92
154
|
|
93
|
-
|
94
|
-
loop do
|
95
|
-
content = @io.read(@bufsize)
|
96
|
-
raise EOFError, "bad content body" unless content
|
97
|
-
@buf << content
|
155
|
+
private
|
98
156
|
|
99
|
-
|
100
|
-
|
101
|
-
|
157
|
+
def check_open_files
|
158
|
+
if Utils.multipart_part_limit > 0
|
159
|
+
if @open_files >= Utils.multipart_part_limit
|
160
|
+
@mime_parts.each(&:close)
|
161
|
+
raise MultipartPartLimitError, 'Maximum file multiparts in content reached'
|
162
|
+
end
|
102
163
|
end
|
103
|
-
|
104
|
-
raise EOFError, "bad content body" if Utils.bytesize(@buf) >= @bufsize
|
105
164
|
end
|
106
165
|
end
|
107
166
|
|
108
|
-
|
109
|
-
head = nil
|
110
|
-
body = ''
|
167
|
+
attr_reader :state
|
111
168
|
|
112
|
-
|
113
|
-
|
114
|
-
|
169
|
+
def initialize(boundary, tempfile, bufsize, query_parser)
|
170
|
+
@buf = "".force_encoding(Encoding::ASCII_8BIT)
|
171
|
+
|
172
|
+
@query_parser = query_parser
|
173
|
+
@params = query_parser.make_params
|
174
|
+
@boundary = "--#{boundary}"
|
175
|
+
@boundary_size = @boundary.bytesize + EOL.size
|
176
|
+
@bufsize = bufsize
|
177
|
+
|
178
|
+
@rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
|
179
|
+
@full_boundary = @boundary
|
180
|
+
@end_boundary = @boundary + '--'
|
181
|
+
@state = :FAST_FORWARD
|
182
|
+
@mime_index = 0
|
183
|
+
@collector = Collector.new tempfile
|
184
|
+
end
|
115
185
|
|
116
|
-
|
186
|
+
def on_read content, eof
|
187
|
+
handle_empty_content!(content, eof)
|
188
|
+
@buf << content
|
189
|
+
run_parser
|
190
|
+
end
|
117
191
|
|
118
|
-
|
119
|
-
|
120
|
-
|
192
|
+
def result
|
193
|
+
@collector.each do |part|
|
194
|
+
part.get_data do |data|
|
195
|
+
tag_multipart_encoding(part.filename, part.content_type, part.name, data)
|
196
|
+
@query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
|
197
|
+
end
|
198
|
+
end
|
121
199
|
|
122
|
-
|
200
|
+
MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
|
201
|
+
end
|
123
202
|
|
124
|
-
|
125
|
-
name = head[MULTIPART_CONTENT_DISPOSITION, 1] || head[MULTIPART_CONTENT_ID, 1]
|
203
|
+
private
|
126
204
|
|
127
|
-
|
205
|
+
def run_parser
|
206
|
+
loop do
|
207
|
+
case @state
|
208
|
+
when :FAST_FORWARD
|
209
|
+
break if handle_fast_forward == :want_read
|
210
|
+
when :CONSUME_TOKEN
|
211
|
+
break if handle_consume_token == :want_read
|
212
|
+
when :MIME_HEAD
|
213
|
+
break if handle_mime_head == :want_read
|
214
|
+
when :MIME_BODY
|
215
|
+
break if handle_mime_body == :want_read
|
216
|
+
when :DONE
|
217
|
+
break
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
128
221
|
|
129
|
-
|
130
|
-
|
131
|
-
|
222
|
+
def handle_fast_forward
|
223
|
+
if consume_boundary
|
224
|
+
@state = :MIME_HEAD
|
225
|
+
else
|
226
|
+
raise EOFError, "bad content body" if @buf.bytesize >= @bufsize
|
227
|
+
:want_read
|
228
|
+
end
|
229
|
+
end
|
132
230
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
231
|
+
def handle_consume_token
|
232
|
+
tok = consume_boundary
|
233
|
+
# break if we're at the end of a buffer, but not if it is the end of a field
|
234
|
+
if tok == :END_BOUNDARY || (@buf.empty? && tok != :BOUNDARY)
|
235
|
+
@state = :DONE
|
236
|
+
else
|
237
|
+
@state = :MIME_HEAD
|
238
|
+
end
|
239
|
+
end
|
137
240
|
|
138
|
-
|
241
|
+
def handle_mime_head
|
242
|
+
if @buf.index(EOL + EOL)
|
243
|
+
i = @buf.index(EOL+EOL)
|
244
|
+
head = @buf.slice!(0, i+2) # First \r\n
|
245
|
+
@buf.slice!(0, 2) # Second \r\n
|
246
|
+
|
247
|
+
content_type = head[MULTIPART_CONTENT_TYPE, 1]
|
248
|
+
if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
|
249
|
+
name = Rack::Auth::Digest::Params::dequote(name)
|
250
|
+
else
|
251
|
+
name = head[MULTIPART_CONTENT_ID, 1]
|
139
252
|
end
|
140
253
|
|
141
|
-
|
142
|
-
|
143
|
-
|
254
|
+
filename = get_filename(head)
|
255
|
+
|
256
|
+
if name.nil? || name.empty?
|
257
|
+
name = filename || "#{content_type || TEXT_PLAIN}[]"
|
144
258
|
end
|
145
259
|
|
146
|
-
|
147
|
-
|
260
|
+
@collector.on_mime_head @mime_index, head, filename, content_type, name
|
261
|
+
@state = :MIME_BODY
|
262
|
+
else
|
263
|
+
:want_read
|
264
|
+
end
|
265
|
+
end
|
148
266
|
|
149
|
-
|
150
|
-
|
267
|
+
def handle_mime_body
|
268
|
+
if @buf =~ rx
|
269
|
+
# Save the rest.
|
270
|
+
if i = @buf.index(rx)
|
271
|
+
@collector.on_mime_body @mime_index, @buf.slice!(0, i)
|
272
|
+
@buf.slice!(0, 2) # Remove \r\n after the content
|
273
|
+
end
|
274
|
+
@state = :CONSUME_TOKEN
|
275
|
+
@mime_index += 1
|
276
|
+
else
|
277
|
+
:want_read
|
151
278
|
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def full_boundary; @full_boundary; end
|
152
282
|
|
153
|
-
|
283
|
+
def rx; @rx; end
|
284
|
+
|
285
|
+
def consume_boundary
|
286
|
+
while @buf.gsub!(/\A([^\n]*(?:\n|\Z))/, '')
|
287
|
+
read_buffer = $1
|
288
|
+
case read_buffer.strip
|
289
|
+
when full_boundary then return :BOUNDARY
|
290
|
+
when @end_boundary then return :END_BOUNDARY
|
291
|
+
end
|
292
|
+
return if @buf.empty?
|
293
|
+
end
|
154
294
|
end
|
155
295
|
|
156
296
|
def get_filename(head)
|
157
297
|
filename = nil
|
158
298
|
case head
|
159
299
|
when RFC2183
|
160
|
-
|
161
|
-
|
300
|
+
params = Hash[*head.scan(DISPPARM).flat_map(&:compact)]
|
301
|
+
|
302
|
+
if filename = params['filename']
|
303
|
+
filename = $1 if filename =~ /^"(.*)"$/
|
304
|
+
elsif filename = params['filename*']
|
305
|
+
encoding, _, filename = filename.split("'", 3)
|
306
|
+
end
|
162
307
|
when BROKEN_QUOTED, BROKEN_UNQUOTED
|
163
308
|
filename = $1
|
164
309
|
end
|
@@ -169,84 +314,54 @@ module Rack
|
|
169
314
|
filename = Utils.unescape(filename)
|
170
315
|
end
|
171
316
|
|
172
|
-
|
317
|
+
filename.scrub!
|
173
318
|
|
174
319
|
if filename !~ /\\[^\\"]/
|
175
320
|
filename = filename.gsub(/\\(.)/, '\1')
|
176
321
|
end
|
177
|
-
filename
|
178
|
-
end
|
179
322
|
|
180
|
-
|
181
|
-
|
182
|
-
unless filename.valid_encoding?
|
183
|
-
# FIXME: this force_encoding is for Ruby 2.0 and 1.9 support.
|
184
|
-
# We can remove it after they are dropped
|
185
|
-
filename.force_encoding(Encoding::ASCII_8BIT)
|
186
|
-
filename.encode!(:invalid => :replace, :undef => :replace)
|
187
|
-
end
|
323
|
+
if encoding
|
324
|
+
filename.force_encoding ::Encoding.find(encoding)
|
188
325
|
end
|
189
326
|
|
190
|
-
|
191
|
-
|
327
|
+
filename
|
328
|
+
end
|
329
|
+
|
330
|
+
CHARSET = "charset"
|
192
331
|
|
193
|
-
|
194
|
-
|
332
|
+
def tag_multipart_encoding(filename, content_type, name, body)
|
333
|
+
name = name.to_s
|
334
|
+
encoding = Encoding::UTF_8
|
195
335
|
|
196
|
-
|
336
|
+
name.force_encoding(encoding)
|
197
337
|
|
198
|
-
|
338
|
+
return if filename
|
199
339
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
end
|
340
|
+
if content_type
|
341
|
+
list = content_type.split(';')
|
342
|
+
type_subtype = list.first
|
343
|
+
type_subtype.strip!
|
344
|
+
if TEXT_PLAIN == type_subtype
|
345
|
+
rest = list.drop 1
|
346
|
+
rest.each do |param|
|
347
|
+
k,v = param.split('=', 2)
|
348
|
+
k.strip!
|
349
|
+
v.strip!
|
350
|
+
encoding = Encoding.find v if k == CHARSET
|
212
351
|
end
|
213
352
|
end
|
214
|
-
|
215
|
-
name.force_encoding encoding
|
216
|
-
body.force_encoding encoding
|
217
|
-
end
|
218
|
-
else
|
219
|
-
def scrub_filename(filename)
|
220
353
|
end
|
221
|
-
def tag_multipart_encoding(filename, content_type, name, body)
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
def get_data(filename, body, content_type, name, head)
|
226
|
-
data = body
|
227
|
-
if filename == ""
|
228
|
-
# filename is blank which means no file has been selected
|
229
|
-
return
|
230
|
-
elsif filename
|
231
|
-
body.rewind if body.respond_to?(:rewind)
|
232
354
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
# those which give the lone filename.
|
237
|
-
filename = filename.split(/[\/\\]/).last
|
355
|
+
name.force_encoding(encoding)
|
356
|
+
body.force_encoding(encoding)
|
357
|
+
end
|
238
358
|
|
239
|
-
data = {:filename => filename, :type => content_type,
|
240
|
-
:name => name, :tempfile => body, :head => head}
|
241
|
-
elsif !filename && content_type && body.is_a?(IO)
|
242
|
-
body.rewind
|
243
359
|
|
244
|
-
|
245
|
-
|
246
|
-
|
360
|
+
def handle_empty_content!(content, eof)
|
361
|
+
if content.nil? || content.empty?
|
362
|
+
raise EOFError if eof
|
363
|
+
return true
|
247
364
|
end
|
248
|
-
|
249
|
-
yield data
|
250
365
|
end
|
251
366
|
end
|
252
367
|
end
|