rack 2.0.9.4 → 2.1.0
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/{HISTORY.md → CHANGELOG.md} +214 -164
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +79 -133
- data/Rakefile +25 -18
- data/SPEC +9 -9
- data/bin/rackup +1 -0
- data/example/lobster.ru +2 -0
- data/example/protectedlobster.rb +3 -1
- data/example/protectedlobster.ru +2 -0
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +2 -0
- data/lib/rack/auth/basic.rb +4 -1
- data/lib/rack/auth/digest/md5.rb +9 -7
- data/lib/rack/auth/digest/nonce.rb +6 -3
- data/lib/rack/auth/digest/params.rb +4 -2
- data/lib/rack/auth/digest/request.rb +2 -0
- data/lib/rack/body_proxy.rb +3 -6
- data/lib/rack/builder.rb +38 -15
- data/lib/rack/cascade.rb +6 -5
- data/lib/rack/chunked.rb +29 -6
- data/lib/rack/common_logger.rb +9 -11
- data/lib/rack/conditional_get.rb +3 -1
- data/lib/rack/config.rb +2 -0
- data/lib/rack/content_length.rb +3 -1
- data/lib/rack/content_type.rb +3 -1
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +28 -17
- data/lib/rack/directory.rb +17 -14
- data/lib/rack/etag.rb +3 -1
- data/lib/rack/events.rb +5 -3
- data/lib/rack/file.rb +5 -173
- data/lib/rack/files.rb +178 -0
- data/lib/rack/handler/cgi.rb +3 -1
- data/lib/rack/handler/fastcgi.rb +4 -2
- data/lib/rack/handler/lsws.rb +3 -1
- data/lib/rack/handler/scgi.rb +9 -6
- data/lib/rack/handler/thin.rb +3 -1
- data/lib/rack/handler/webrick.rb +4 -2
- data/lib/rack/handler.rb +7 -2
- data/lib/rack/head.rb +2 -0
- data/lib/rack/lint.rb +15 -12
- data/lib/rack/lobster.rb +7 -5
- data/lib/rack/lock.rb +2 -0
- data/lib/rack/logger.rb +2 -0
- data/lib/rack/media_type.rb +10 -5
- data/lib/rack/method_override.rb +4 -2
- data/lib/rack/mime.rb +9 -1
- data/lib/rack/mock.rb +74 -15
- data/lib/rack/multipart/generator.rb +6 -7
- data/lib/rack/multipart/parser.rb +55 -62
- data/lib/rack/multipart/uploaded_file.rb +2 -0
- data/lib/rack/multipart.rb +6 -3
- data/lib/rack/null_logger.rb +2 -0
- data/lib/rack/query_parser.rb +51 -25
- data/lib/rack/recursive.rb +7 -5
- data/lib/rack/reloader.rb +10 -4
- data/lib/rack/request.rb +79 -26
- data/lib/rack/response.rb +71 -31
- data/lib/rack/rewindable_input.rb +4 -2
- data/lib/rack/runtime.rb +4 -2
- data/lib/rack/sendfile.rb +15 -8
- data/lib/rack/server.rb +88 -16
- data/lib/rack/session/abstract/id.rb +40 -22
- data/lib/rack/session/cookie.rb +10 -9
- data/lib/rack/session/memcache.rb +4 -93
- data/lib/rack/session/pool.rb +4 -2
- data/lib/rack/show_exceptions.rb +15 -9
- data/lib/rack/show_status.rb +4 -2
- data/lib/rack/static.rb +15 -10
- data/lib/rack/tempfile_reaper.rb +2 -0
- data/lib/rack/urlmap.rb +11 -2
- data/lib/rack/utils.rb +64 -93
- data/lib/rack.rb +63 -60
- data/rack.gemspec +17 -7
- metadata +33 -175
- data/test/builder/an_underscore_app.rb +0 -5
- data/test/builder/anything.rb +0 -5
- data/test/builder/comment.ru +0 -4
- data/test/builder/end.ru +0 -5
- data/test/builder/line.ru +0 -1
- data/test/builder/options.ru +0 -2
- data/test/cgi/assets/folder/test.js +0 -1
- data/test/cgi/assets/fonts/font.eot +0 -1
- data/test/cgi/assets/images/image.png +0 -1
- data/test/cgi/assets/index.html +0 -1
- data/test/cgi/assets/javascripts/app.js +0 -1
- data/test/cgi/assets/stylesheets/app.css +0 -1
- data/test/cgi/lighttpd.conf +0 -26
- data/test/cgi/rackup_stub.rb +0 -6
- data/test/cgi/sample_rackup.ru +0 -5
- data/test/cgi/test +0 -9
- data/test/cgi/test+directory/test+file +0 -1
- data/test/cgi/test.fcgi +0 -9
- data/test/cgi/test.gz +0 -0
- data/test/cgi/test.ru +0 -5
- data/test/gemloader.rb +0 -10
- data/test/helper.rb +0 -34
- data/test/multipart/bad_robots +0 -259
- data/test/multipart/binary +0 -0
- data/test/multipart/content_type_and_no_filename +0 -6
- data/test/multipart/empty +0 -10
- data/test/multipart/fail_16384_nofile +0 -814
- data/test/multipart/file1.txt +0 -1
- data/test/multipart/filename_and_modification_param +0 -7
- data/test/multipart/filename_and_no_name +0 -6
- data/test/multipart/filename_with_encoded_words +0 -7
- data/test/multipart/filename_with_escaped_quotes +0 -6
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
- data/test/multipart/filename_with_null_byte +0 -7
- data/test/multipart/filename_with_percent_escaped_quotes +0 -6
- data/test/multipart/filename_with_single_quote +0 -7
- data/test/multipart/filename_with_unescaped_percentages +0 -6
- data/test/multipart/filename_with_unescaped_percentages2 +0 -6
- data/test/multipart/filename_with_unescaped_percentages3 +0 -6
- data/test/multipart/filename_with_unescaped_quotes +0 -6
- data/test/multipart/ie +0 -6
- data/test/multipart/invalid_character +0 -6
- data/test/multipart/mixed_files +0 -21
- data/test/multipart/nested +0 -10
- data/test/multipart/none +0 -9
- data/test/multipart/quoted +0 -15
- data/test/multipart/rack-logo.png +0 -0
- data/test/multipart/semicolon +0 -6
- data/test/multipart/text +0 -15
- data/test/multipart/three_files_three_fields +0 -31
- data/test/multipart/unity3d_wwwform +0 -11
- data/test/multipart/webkit +0 -32
- data/test/rackup/config.ru +0 -31
- data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
- data/test/spec_auth_basic.rb +0 -89
- data/test/spec_auth_digest.rb +0 -260
- data/test/spec_body_proxy.rb +0 -85
- data/test/spec_builder.rb +0 -233
- data/test/spec_cascade.rb +0 -63
- data/test/spec_cgi.rb +0 -84
- data/test/spec_chunked.rb +0 -103
- data/test/spec_common_logger.rb +0 -107
- data/test/spec_conditional_get.rb +0 -103
- data/test/spec_config.rb +0 -23
- data/test/spec_content_length.rb +0 -86
- data/test/spec_content_type.rb +0 -46
- data/test/spec_deflater.rb +0 -375
- data/test/spec_directory.rb +0 -148
- data/test/spec_etag.rb +0 -108
- data/test/spec_events.rb +0 -133
- data/test/spec_fastcgi.rb +0 -85
- data/test/spec_file.rb +0 -264
- data/test/spec_handler.rb +0 -57
- data/test/spec_head.rb +0 -46
- data/test/spec_lint.rb +0 -520
- data/test/spec_lobster.rb +0 -59
- data/test/spec_lock.rb +0 -204
- data/test/spec_logger.rb +0 -24
- data/test/spec_media_type.rb +0 -42
- data/test/spec_method_override.rb +0 -110
- data/test/spec_mime.rb +0 -51
- data/test/spec_mock.rb +0 -359
- data/test/spec_multipart.rb +0 -721
- data/test/spec_null_logger.rb +0 -21
- data/test/spec_recursive.rb +0 -75
- data/test/spec_request.rb +0 -1423
- data/test/spec_response.rb +0 -528
- data/test/spec_rewindable_input.rb +0 -128
- data/test/spec_runtime.rb +0 -50
- data/test/spec_sendfile.rb +0 -125
- data/test/spec_server.rb +0 -193
- data/test/spec_session_abstract_id.rb +0 -31
- data/test/spec_session_abstract_session_hash.rb +0 -45
- data/test/spec_session_cookie.rb +0 -442
- data/test/spec_session_memcache.rb +0 -357
- data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
- data/test/spec_session_pool.rb +0 -247
- data/test/spec_show_exceptions.rb +0 -93
- data/test/spec_show_status.rb +0 -104
- data/test/spec_static.rb +0 -184
- data/test/spec_tempfile_reaper.rb +0 -64
- data/test/spec_thin.rb +0 -96
- data/test/spec_urlmap.rb +0 -237
- data/test/spec_utils.rb +0 -742
- data/test/spec_version.rb +0 -11
- data/test/spec_webrick.rb +0 -206
- data/test/static/another/index.html +0 -1
- data/test/static/foo.html +0 -1
- data/test/static/index.html +0 -1
- data/test/testrequest.rb +0 -78
- data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/lib/rack/media_type.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
# Rack::MediaType parse media type and parameters out of content_type string
|
3
5
|
|
@@ -13,7 +15,7 @@ module Rack
|
|
13
15
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
|
14
16
|
def type(content_type)
|
15
17
|
return nil unless content_type
|
16
|
-
content_type.split(SPLIT_PATTERN, 2).first.downcase
|
18
|
+
content_type.split(SPLIT_PATTERN, 2).first.tap &:downcase!
|
17
19
|
end
|
18
20
|
|
19
21
|
# The media type parameters provided in CONTENT_TYPE as a Hash, or
|
@@ -23,15 +25,18 @@ module Rack
|
|
23
25
|
# { 'charset' => 'utf-8' }
|
24
26
|
def params(content_type)
|
25
27
|
return {} if content_type.nil?
|
26
|
-
|
27
|
-
|
28
|
-
|
28
|
+
|
29
|
+
content_type.split(SPLIT_PATTERN)[1..-1].each_with_object({}) do |s, hsh|
|
30
|
+
k, v = s.split('=', 2)
|
31
|
+
|
32
|
+
hsh[k.tap(&:downcase!)] = strip_doublequotes(v)
|
33
|
+
end
|
29
34
|
end
|
30
35
|
|
31
36
|
private
|
32
37
|
|
33
38
|
def strip_doublequotes(str)
|
34
|
-
(str
|
39
|
+
(str.start_with?('"') && str.end_with?('"')) ? str[1..-2] : str
|
35
40
|
end
|
36
41
|
end
|
37
42
|
end
|
data/lib/rack/method_override.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
class MethodOverride
|
3
5
|
HTTP_METHODS = %w[GET HEAD PUT POST DELETE OPTIONS PATCH LINK UNLINK]
|
4
6
|
|
5
|
-
METHOD_OVERRIDE_PARAM_KEY = "_method"
|
6
|
-
HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE"
|
7
|
+
METHOD_OVERRIDE_PARAM_KEY = "_method"
|
8
|
+
HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE"
|
7
9
|
ALLOWED_METHODS = %w[POST]
|
8
10
|
|
9
11
|
def initialize(app)
|
data/lib/rack/mime.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
module Mime
|
3
5
|
# Returns String with mime type if found, otherwise use +fallback+.
|
@@ -13,7 +15,7 @@ module Rack
|
|
13
15
|
# This is a shortcut for:
|
14
16
|
# Rack::Mime::MIME_TYPES.fetch('.foo', 'application/octet-stream')
|
15
17
|
|
16
|
-
def mime_type(ext, fallback='application/octet-stream')
|
18
|
+
def mime_type(ext, fallback = 'application/octet-stream')
|
17
19
|
MIME_TYPES.fetch(ext.to_s.downcase, fallback)
|
18
20
|
end
|
19
21
|
module_function :mime_type
|
@@ -306,6 +308,7 @@ module Rack
|
|
306
308
|
".lvp" => "audio/vnd.lucent.voice",
|
307
309
|
".lwp" => "application/vnd.lotus-wordpro",
|
308
310
|
".m3u" => "audio/x-mpegurl",
|
311
|
+
".m3u8" => "application/x-mpegurl",
|
309
312
|
".m4a" => "audio/mp4a-latm",
|
310
313
|
".m4v" => "video/mp4",
|
311
314
|
".ma" => "application/mathematica",
|
@@ -343,6 +346,7 @@ module Rack
|
|
343
346
|
".mp4s" => "application/mp4",
|
344
347
|
".mp4v" => "video/mp4",
|
345
348
|
".mpc" => "application/vnd.mophun.certificate",
|
349
|
+
".mpd" => "application/dash+xml",
|
346
350
|
".mpeg" => "video/mpeg",
|
347
351
|
".mpg" => "video/mpeg",
|
348
352
|
".mpga" => "audio/mpeg",
|
@@ -542,6 +546,7 @@ module Rack
|
|
542
546
|
".spp" => "application/scvp-vp-response",
|
543
547
|
".spq" => "application/scvp-vp-request",
|
544
548
|
".src" => "application/x-wais-source",
|
549
|
+
".srt" => "text/srt",
|
545
550
|
".srx" => "application/sparql-results+xml",
|
546
551
|
".sse" => "application/vnd.kodak-descriptor",
|
547
552
|
".ssf" => "application/vnd.epson.ssf",
|
@@ -576,6 +581,7 @@ module Rack
|
|
576
581
|
".tr" => "text/troff",
|
577
582
|
".tra" => "application/vnd.trueapp",
|
578
583
|
".trm" => "application/x-msterminal",
|
584
|
+
".ts" => "video/mp2t",
|
579
585
|
".tsv" => "text/tab-separated-values",
|
580
586
|
".ttf" => "application/octet-stream",
|
581
587
|
".twd" => "application/vnd.simtech-mindmapper",
|
@@ -600,9 +606,11 @@ module Rack
|
|
600
606
|
".vrml" => "model/vrml",
|
601
607
|
".vsd" => "application/vnd.visio",
|
602
608
|
".vsf" => "application/vnd.vsf",
|
609
|
+
".vtt" => "text/vtt",
|
603
610
|
".vtu" => "model/vnd.vtu",
|
604
611
|
".vxml" => "application/voicexml+xml",
|
605
612
|
".war" => "application/java-archive",
|
613
|
+
".wasm" => "application/wasm",
|
606
614
|
".wav" => "audio/x-wav",
|
607
615
|
".wax" => "audio/x-ms-wax",
|
608
616
|
".wbmp" => "image/vnd.wap.wbmp",
|
data/lib/rack/mock.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'uri'
|
2
4
|
require 'stringio'
|
3
5
|
require 'rack'
|
4
6
|
require 'rack/lint'
|
5
7
|
require 'rack/utils'
|
6
8
|
require 'rack/response'
|
9
|
+
require 'cgi/cookie'
|
7
10
|
|
8
11
|
module Rack
|
9
12
|
# Rack::MockRequest helps testing your Rack application without
|
@@ -53,16 +56,16 @@ module Rack
|
|
53
56
|
@app = app
|
54
57
|
end
|
55
58
|
|
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
|
59
|
+
def get(uri, opts = {}) request(GET, uri, opts) end
|
60
|
+
def post(uri, opts = {}) request(POST, uri, opts) end
|
61
|
+
def put(uri, opts = {}) request(PUT, uri, opts) end
|
62
|
+
def patch(uri, opts = {}) request(PATCH, uri, opts) end
|
63
|
+
def delete(uri, opts = {}) request(DELETE, uri, opts) end
|
64
|
+
def head(uri, opts = {}) request(HEAD, uri, opts) end
|
65
|
+
def options(uri, opts = {}) request(OPTIONS, uri, opts) end
|
63
66
|
|
64
|
-
def request(method=GET, uri="", opts={})
|
65
|
-
env = self.class.env_for(uri, opts.merge(:
|
67
|
+
def request(method = GET, uri = "", opts = {})
|
68
|
+
env = self.class.env_for(uri, opts.merge(method: method))
|
66
69
|
|
67
70
|
if opts[:lint]
|
68
71
|
app = Rack::Lint.new(@app)
|
@@ -71,7 +74,7 @@ module Rack
|
|
71
74
|
end
|
72
75
|
|
73
76
|
errors = env[RACK_ERRORS]
|
74
|
-
status, headers, body
|
77
|
+
status, headers, body = app.call(env)
|
75
78
|
MockResponse.new(status, headers, body, errors)
|
76
79
|
ensure
|
77
80
|
body.close if body.respond_to?(:close)
|
@@ -85,7 +88,7 @@ module Rack
|
|
85
88
|
end
|
86
89
|
|
87
90
|
# Return the Rack environment used for a request to +uri+.
|
88
|
-
def self.env_for(uri="", opts={})
|
91
|
+
def self.env_for(uri = "", opts = {})
|
89
92
|
uri = parse_uri_rfc2396(uri)
|
90
93
|
uri.path = "/#{uri.path}" unless uri.path[0] == ?/
|
91
94
|
|
@@ -139,7 +142,7 @@ module Rack
|
|
139
142
|
rack_input.set_encoding(Encoding::BINARY)
|
140
143
|
env[RACK_INPUT] = rack_input
|
141
144
|
|
142
|
-
env["CONTENT_LENGTH"] ||= env[RACK_INPUT].
|
145
|
+
env["CONTENT_LENGTH"] ||= env[RACK_INPUT].size.to_s if env[RACK_INPUT].respond_to?(:size)
|
143
146
|
|
144
147
|
opts.each { |field, value|
|
145
148
|
env[field] = value if String === field
|
@@ -155,16 +158,19 @@ module Rack
|
|
155
158
|
|
156
159
|
class MockResponse < Rack::Response
|
157
160
|
# Headers
|
158
|
-
attr_reader :original_headers
|
161
|
+
attr_reader :original_headers, :cookies
|
159
162
|
|
160
163
|
# Errors
|
161
164
|
attr_accessor :errors
|
162
165
|
|
163
|
-
def initialize(status, headers, body, errors=StringIO.new(""))
|
166
|
+
def initialize(status, headers, body, errors = StringIO.new(""))
|
164
167
|
@original_headers = headers
|
165
168
|
@errors = errors.string if errors.respond_to?(:string)
|
169
|
+
@cookies = parse_cookies_from_header
|
166
170
|
|
167
171
|
super(body, status, headers)
|
172
|
+
|
173
|
+
buffered_body!
|
168
174
|
end
|
169
175
|
|
170
176
|
def =~(other)
|
@@ -186,11 +192,64 @@ module Rack
|
|
186
192
|
# ...
|
187
193
|
# res.body.should == "foo!"
|
188
194
|
# end
|
189
|
-
|
195
|
+
buffer = String.new
|
196
|
+
|
197
|
+
super.each do |chunk|
|
198
|
+
buffer << chunk
|
199
|
+
end
|
200
|
+
|
201
|
+
return buffer
|
190
202
|
end
|
191
203
|
|
192
204
|
def empty?
|
193
205
|
[201, 204, 304].include? status
|
194
206
|
end
|
207
|
+
|
208
|
+
def cookie(name)
|
209
|
+
cookies.fetch(name, nil)
|
210
|
+
end
|
211
|
+
|
212
|
+
private
|
213
|
+
|
214
|
+
def parse_cookies_from_header
|
215
|
+
cookies = Hash.new
|
216
|
+
if original_headers.has_key? 'Set-Cookie'
|
217
|
+
set_cookie_header = original_headers.fetch('Set-Cookie')
|
218
|
+
set_cookie_header.split("\n").each do |cookie|
|
219
|
+
cookie_name, cookie_filling = cookie.split('=', 2)
|
220
|
+
cookie_attributes = identify_cookie_attributes cookie_filling
|
221
|
+
parsed_cookie = CGI::Cookie.new(
|
222
|
+
'name' => cookie_name.strip,
|
223
|
+
'value' => cookie_attributes.fetch('value'),
|
224
|
+
'path' => cookie_attributes.fetch('path', nil),
|
225
|
+
'domain' => cookie_attributes.fetch('domain', nil),
|
226
|
+
'expires' => cookie_attributes.fetch('expires', nil),
|
227
|
+
'secure' => cookie_attributes.fetch('secure', false)
|
228
|
+
)
|
229
|
+
cookies.store(cookie_name, parsed_cookie)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
cookies
|
233
|
+
end
|
234
|
+
|
235
|
+
def identify_cookie_attributes(cookie_filling)
|
236
|
+
cookie_bits = cookie_filling.split(';')
|
237
|
+
cookie_attributes = Hash.new
|
238
|
+
cookie_attributes.store('value', cookie_bits[0].strip)
|
239
|
+
cookie_bits.each do |bit|
|
240
|
+
if bit.include? '='
|
241
|
+
cookie_attribute, attribute_value = bit.split('=')
|
242
|
+
cookie_attributes.store(cookie_attribute.strip, attribute_value.strip)
|
243
|
+
if cookie_attribute.include? 'max-age'
|
244
|
+
cookie_attributes.store('expires', Time.now + attribute_value.strip.to_i)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
if bit.include? 'secure'
|
248
|
+
cookie_attributes.store('secure', true)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
cookie_attributes
|
252
|
+
end
|
253
|
+
|
195
254
|
end
|
196
255
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
module Multipart
|
3
5
|
class Generator
|
@@ -27,21 +29,18 @@ module Rack
|
|
27
29
|
|
28
30
|
private
|
29
31
|
def multipart?
|
30
|
-
multipart = false
|
31
|
-
|
32
32
|
query = lambda { |value|
|
33
33
|
case value
|
34
34
|
when Array
|
35
|
-
value.
|
35
|
+
value.any?(&query)
|
36
36
|
when Hash
|
37
|
-
value.values.
|
37
|
+
value.values.any?(&query)
|
38
38
|
when Rack::Multipart::UploadedFile
|
39
|
-
|
39
|
+
true
|
40
40
|
end
|
41
41
|
}
|
42
|
-
@params.values.each(&query)
|
43
42
|
|
44
|
-
|
43
|
+
@params.values.any?(&query)
|
45
44
|
end
|
46
45
|
|
47
46
|
def flattened_params
|
@@ -1,17 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rack/utils'
|
4
|
+
require 'strscan'
|
5
|
+
require 'rack/core_ext/regexp'
|
2
6
|
|
3
7
|
module Rack
|
4
8
|
module Multipart
|
5
9
|
class MultipartPartLimitError < Errno::EMFILE; end
|
6
|
-
class MultipartTotalPartLimitError < StandardError; end
|
7
10
|
|
8
11
|
class Parser
|
9
|
-
|
12
|
+
using ::Rack::RegexpExtensions
|
13
|
+
|
14
|
+
BUFSIZE = 1_048_576
|
10
15
|
TEXT_PLAIN = "text/plain"
|
11
16
|
TEMPFILE_FACTORY = lambda { |filename, content_type|
|
12
|
-
Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0"
|
17
|
+
Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
|
13
18
|
}
|
14
19
|
|
20
|
+
BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
|
21
|
+
|
15
22
|
class BoundedIO # :nodoc:
|
16
23
|
def initialize(io, content_length)
|
17
24
|
@io = io
|
@@ -19,15 +26,15 @@ module Rack
|
|
19
26
|
@cursor = 0
|
20
27
|
end
|
21
28
|
|
22
|
-
def read(size)
|
29
|
+
def read(size, outbuf = nil)
|
23
30
|
return if @cursor >= @content_length
|
24
31
|
|
25
32
|
left = @content_length - @cursor
|
26
33
|
|
27
34
|
str = if left < size
|
28
|
-
@io.read left
|
35
|
+
@io.read left, outbuf
|
29
36
|
else
|
30
|
-
@io.read size
|
37
|
+
@io.read size, outbuf
|
31
38
|
end
|
32
39
|
|
33
40
|
if str
|
@@ -62,13 +69,14 @@ module Rack
|
|
62
69
|
return EMPTY unless boundary
|
63
70
|
|
64
71
|
io = BoundedIO.new(io, content_length) if content_length
|
72
|
+
outbuf = String.new
|
65
73
|
|
66
74
|
parser = new(boundary, tmpfile, bufsize, qp)
|
67
|
-
parser.on_read io.read(bufsize)
|
75
|
+
parser.on_read io.read(bufsize, outbuf)
|
68
76
|
|
69
77
|
loop do
|
70
78
|
break if parser.state == :DONE
|
71
|
-
parser.on_read io.read(bufsize)
|
79
|
+
parser.on_read io.read(bufsize, outbuf)
|
72
80
|
end
|
73
81
|
|
74
82
|
io.rewind
|
@@ -91,14 +99,14 @@ module Rack
|
|
91
99
|
# those which give the lone filename.
|
92
100
|
fn = filename.split(/[\/\\]/).last
|
93
101
|
|
94
|
-
data = {:
|
95
|
-
:
|
102
|
+
data = { filename: fn, type: content_type,
|
103
|
+
name: name, tempfile: body, head: head }
|
96
104
|
elsif !filename && content_type && body.is_a?(IO)
|
97
105
|
body.rewind
|
98
106
|
|
99
107
|
# Generic multipart cases, not coming from a form
|
100
|
-
data = {:
|
101
|
-
:
|
108
|
+
data = { type: content_type,
|
109
|
+
name: name, tempfile: body, head: head }
|
102
110
|
end
|
103
111
|
|
104
112
|
yield data
|
@@ -140,7 +148,7 @@ module Rack
|
|
140
148
|
|
141
149
|
@mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
|
142
150
|
|
143
|
-
|
151
|
+
check_open_files
|
144
152
|
end
|
145
153
|
|
146
154
|
def on_mime_body mime_index, content
|
@@ -152,48 +160,39 @@ module Rack
|
|
152
160
|
|
153
161
|
private
|
154
162
|
|
155
|
-
def
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
if file_limit && file_limit > 0
|
160
|
-
if @open_files >= file_limit
|
163
|
+
def check_open_files
|
164
|
+
if Utils.multipart_part_limit > 0
|
165
|
+
if @open_files >= Utils.multipart_part_limit
|
161
166
|
@mime_parts.each(&:close)
|
162
167
|
raise MultipartPartLimitError, 'Maximum file multiparts in content reached'
|
163
168
|
end
|
164
169
|
end
|
165
|
-
|
166
|
-
if part_limit && part_limit > 0
|
167
|
-
if @mime_parts.size >= part_limit
|
168
|
-
@mime_parts.each(&:close)
|
169
|
-
raise MultipartTotalPartLimitError, 'Maximum total multiparts in content reached'
|
170
|
-
end
|
171
|
-
end
|
172
170
|
end
|
173
171
|
end
|
174
172
|
|
175
173
|
attr_reader :state
|
176
174
|
|
177
175
|
def initialize(boundary, tempfile, bufsize, query_parser)
|
178
|
-
@buf = String.new
|
179
|
-
|
180
176
|
@query_parser = query_parser
|
181
177
|
@params = query_parser.make_params
|
182
178
|
@boundary = "--#{boundary}"
|
183
179
|
@bufsize = bufsize
|
184
180
|
|
185
|
-
@rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
|
186
|
-
@rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
|
187
181
|
@full_boundary = @boundary
|
188
182
|
@end_boundary = @boundary + '--'
|
189
183
|
@state = :FAST_FORWARD
|
190
184
|
@mime_index = 0
|
191
185
|
@collector = Collector.new tempfile
|
186
|
+
|
187
|
+
@sbuf = StringScanner.new("".dup)
|
188
|
+
@body_regex = /(.*?)(#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/m
|
189
|
+
@rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
|
190
|
+
@head_regex = /(.*?#{EOL})#{EOL}/m
|
192
191
|
end
|
193
192
|
|
194
193
|
def on_read content
|
195
194
|
handle_empty_content!(content)
|
196
|
-
@
|
195
|
+
@sbuf.concat content
|
197
196
|
run_parser
|
198
197
|
end
|
199
198
|
|
@@ -204,7 +203,6 @@ module Rack
|
|
204
203
|
@query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
|
205
204
|
end
|
206
205
|
end
|
207
|
-
|
208
206
|
MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
|
209
207
|
end
|
210
208
|
|
@@ -231,7 +229,7 @@ module Rack
|
|
231
229
|
if consume_boundary
|
232
230
|
@state = :MIME_HEAD
|
233
231
|
else
|
234
|
-
raise EOFError, "bad content body" if @
|
232
|
+
raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
|
235
233
|
:want_read
|
236
234
|
end
|
237
235
|
end
|
@@ -239,19 +237,16 @@ module Rack
|
|
239
237
|
def handle_consume_token
|
240
238
|
tok = consume_boundary
|
241
239
|
# break if we're at the end of a buffer, but not if it is the end of a field
|
242
|
-
if tok == :END_BOUNDARY || (@
|
243
|
-
|
240
|
+
@state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY)
|
241
|
+
:DONE
|
244
242
|
else
|
245
|
-
|
243
|
+
:MIME_HEAD
|
246
244
|
end
|
247
245
|
end
|
248
246
|
|
249
247
|
def handle_mime_head
|
250
|
-
if @
|
251
|
-
|
252
|
-
head = @buf.slice!(0, i+2) # First \r\n
|
253
|
-
@buf.slice!(0, 2) # Second \r\n
|
254
|
-
|
248
|
+
if @sbuf.scan_until(@head_regex)
|
249
|
+
head = @sbuf[1]
|
255
250
|
content_type = head[MULTIPART_CONTENT_TYPE, 1]
|
256
251
|
if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
|
257
252
|
name = Rack::Auth::Digest::Params::dequote(name)
|
@@ -262,7 +257,7 @@ module Rack
|
|
262
257
|
filename = get_filename(head)
|
263
258
|
|
264
259
|
if name.nil? || name.empty?
|
265
|
-
name = filename || "#{content_type || TEXT_PLAIN}[]"
|
260
|
+
name = filename || "#{content_type || TEXT_PLAIN}[]".dup
|
266
261
|
end
|
267
262
|
|
268
263
|
@collector.on_mime_head @mime_index, head, filename, content_type, name
|
@@ -273,16 +268,19 @@ module Rack
|
|
273
268
|
end
|
274
269
|
|
275
270
|
def handle_mime_body
|
276
|
-
if
|
277
|
-
|
278
|
-
@collector.on_mime_body @mime_index,
|
279
|
-
@
|
271
|
+
if @sbuf.check_until(@body_regex) # check but do not advance the pointer yet
|
272
|
+
body = @sbuf[1]
|
273
|
+
@collector.on_mime_body @mime_index, body
|
274
|
+
@sbuf.pos += body.length + 2 # skip \r\n after the content
|
280
275
|
@state = :CONSUME_TOKEN
|
281
276
|
@mime_index += 1
|
282
277
|
else
|
283
|
-
# Save
|
284
|
-
if @rx_max_size < @
|
285
|
-
|
278
|
+
# Save what we have so far
|
279
|
+
if @rx_max_size < @sbuf.rest_size
|
280
|
+
delta = @sbuf.rest_size - @rx_max_size
|
281
|
+
@collector.on_mime_body @mime_index, @sbuf.peek(delta)
|
282
|
+
@sbuf.pos += delta
|
283
|
+
@sbuf.string = @sbuf.rest
|
286
284
|
end
|
287
285
|
:want_read
|
288
286
|
end
|
@@ -290,16 +288,13 @@ module Rack
|
|
290
288
|
|
291
289
|
def full_boundary; @full_boundary; end
|
292
290
|
|
293
|
-
def rx; @rx; end
|
294
|
-
|
295
291
|
def consume_boundary
|
296
|
-
while @
|
297
|
-
read_buffer = $1
|
292
|
+
while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
|
298
293
|
case read_buffer.strip
|
299
294
|
when full_boundary then return :BOUNDARY
|
300
295
|
when @end_boundary then return :END_BOUNDARY
|
301
296
|
end
|
302
|
-
return if @
|
297
|
+
return if @sbuf.eos?
|
303
298
|
end
|
304
299
|
end
|
305
300
|
|
@@ -314,15 +309,14 @@ module Rack
|
|
314
309
|
elsif filename = params['filename*']
|
315
310
|
encoding, _, filename = filename.split("'", 3)
|
316
311
|
end
|
317
|
-
when
|
312
|
+
when BROKEN_QUOTED, BROKEN_UNQUOTED
|
318
313
|
filename = $1
|
319
|
-
filename = $1 if filename =~ /^"(.*)"$/
|
320
314
|
end
|
321
315
|
|
322
316
|
return unless filename
|
323
317
|
|
324
|
-
if filename.scan(/%.?.?/).all? { |s|
|
325
|
-
filename = Utils.
|
318
|
+
if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
|
319
|
+
filename = Utils.unescape_path(filename)
|
326
320
|
end
|
327
321
|
|
328
322
|
filename.scrub!
|
@@ -338,7 +332,7 @@ module Rack
|
|
338
332
|
filename
|
339
333
|
end
|
340
334
|
|
341
|
-
CHARSET
|
335
|
+
CHARSET = "charset"
|
342
336
|
|
343
337
|
def tag_multipart_encoding(filename, content_type, name, body)
|
344
338
|
name = name.to_s
|
@@ -355,10 +349,10 @@ module Rack
|
|
355
349
|
if TEXT_PLAIN == type_subtype
|
356
350
|
rest = list.drop 1
|
357
351
|
rest.each do |param|
|
358
|
-
k,v = param.split('=', 2)
|
352
|
+
k, v = param.split('=', 2)
|
359
353
|
k.strip!
|
360
354
|
v.strip!
|
361
|
-
v = v[1..-2] if v
|
355
|
+
v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
|
362
356
|
encoding = Encoding.find v if k == CHARSET
|
363
357
|
end
|
364
358
|
end
|
@@ -368,7 +362,6 @@ module Rack
|
|
368
362
|
body.force_encoding(encoding)
|
369
363
|
end
|
370
364
|
|
371
|
-
|
372
365
|
def handle_empty_content!(content)
|
373
366
|
if content.nil? || content.empty?
|
374
367
|
raise EOFError
|
data/lib/rack/multipart.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rack/multipart/parser'
|
2
4
|
|
3
5
|
module Rack
|
@@ -14,12 +16,13 @@ module Rack
|
|
14
16
|
TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
|
15
17
|
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
|
16
18
|
VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
|
17
|
-
|
19
|
+
BROKEN_QUOTED = /^#{CONDISP}.*;\s*filename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
|
20
|
+
BROKEN_UNQUOTED = /^#{CONDISP}.*;\s*filename=(#{TOKEN})/i
|
18
21
|
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
|
19
|
-
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition
|
22
|
+
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*;\s*name=(#{VALUE})/ni
|
20
23
|
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
|
21
24
|
# Updated definitions from RFC 2231
|
22
|
-
ATTRIBUTE_CHAR = %r{[^ \
|
25
|
+
ATTRIBUTE_CHAR = %r{[^ \t\v\n\r)(><@,;:\\"/\[\]?='*%]}
|
23
26
|
ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
|
24
27
|
SECTION = /\*[0-9]+/
|
25
28
|
REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
|