rack 2.0.8 → 2.2.2
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/CHANGELOG.md +690 -0
- data/CONTRIBUTING.md +136 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +152 -148
- data/Rakefile +37 -23
- data/{SPEC → SPEC.rdoc} +29 -5
- 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.rb +67 -73
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +1 -1
- data/lib/rack/auth/basic.rb +7 -4
- data/lib/rack/auth/digest/md5.rb +13 -11
- 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 +5 -3
- data/lib/rack/body_proxy.rb +15 -14
- data/lib/rack/builder.rb +116 -23
- data/lib/rack/cascade.rb +28 -12
- data/lib/rack/chunked.rb +68 -20
- data/lib/rack/common_logger.rb +33 -25
- data/lib/rack/conditional_get.rb +20 -16
- data/lib/rack/config.rb +2 -0
- data/lib/rack/content_length.rb +8 -7
- data/lib/rack/content_type.rb +5 -4
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +59 -34
- data/lib/rack/directory.rb +84 -64
- data/lib/rack/etag.rb +5 -4
- data/lib/rack/events.rb +19 -20
- data/lib/rack/file.rb +4 -173
- data/lib/rack/files.rb +218 -0
- data/lib/rack/handler.rb +7 -2
- data/lib/rack/handler/cgi.rb +2 -3
- data/lib/rack/handler/fastcgi.rb +4 -4
- data/lib/rack/handler/lsws.rb +3 -3
- data/lib/rack/handler/scgi.rb +9 -8
- data/lib/rack/handler/thin.rb +3 -3
- data/lib/rack/handler/webrick.rb +15 -6
- data/lib/rack/head.rb +1 -1
- data/lib/rack/lint.rb +71 -25
- data/lib/rack/lobster.rb +10 -10
- data/lib/rack/lock.rb +2 -1
- 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 +97 -20
- data/lib/rack/multipart.rb +6 -4
- data/lib/rack/multipart/generator.rb +17 -13
- data/lib/rack/multipart/parser.rb +54 -56
- data/lib/rack/multipart/uploaded_file.rb +15 -7
- data/lib/rack/null_logger.rb +2 -0
- data/lib/rack/query_parser.rb +53 -28
- data/lib/rack/recursive.rb +7 -5
- data/lib/rack/reloader.rb +8 -4
- data/lib/rack/request.rb +220 -61
- data/lib/rack/response.rb +127 -44
- data/lib/rack/rewindable_input.rb +4 -3
- data/lib/rack/runtime.rb +6 -4
- data/lib/rack/sendfile.rb +13 -9
- data/lib/rack/server.rb +95 -24
- data/lib/rack/session/abstract/id.rb +36 -23
- data/lib/rack/session/cookie.rb +11 -12
- data/lib/rack/session/memcache.rb +4 -93
- data/lib/rack/session/pool.rb +5 -3
- data/lib/rack/show_exceptions.rb +21 -17
- data/lib/rack/show_status.rb +9 -9
- data/lib/rack/static.rb +23 -11
- data/lib/rack/tempfile_reaper.rb +1 -1
- data/lib/rack/urlmap.rb +12 -6
- data/lib/rack/utils.rb +98 -109
- data/lib/rack/version.rb +29 -0
- data/rack.gemspec +40 -28
- metadata +36 -177
- data/HISTORY.md +0 -505
- 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 -95
- 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 -515
- 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 -722
- data/test/spec_null_logger.rb +0 -21
- data/test/spec_recursive.rb +0 -75
- data/test/spec_request.rb +0 -1407
- data/test/spec_response.rb +0 -510
- 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_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/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,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'uri'
|
2
4
|
require 'stringio'
|
3
|
-
|
4
|
-
require '
|
5
|
-
require 'rack/utils'
|
6
|
-
require 'rack/response'
|
5
|
+
require_relative '../rack'
|
6
|
+
require 'cgi/cookie'
|
7
7
|
|
8
8
|
module Rack
|
9
9
|
# Rack::MockRequest helps testing your Rack application without
|
@@ -53,16 +53,26 @@ module Rack
|
|
53
53
|
@app = app
|
54
54
|
end
|
55
55
|
|
56
|
-
|
57
|
-
def
|
58
|
-
|
59
|
-
def
|
60
|
-
|
61
|
-
def
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
56
|
+
# Make a GET request and return a MockResponse. See #request.
|
57
|
+
def get(uri, opts = {}) request(GET, uri, opts) end
|
58
|
+
# Make a POST request and return a MockResponse. See #request.
|
59
|
+
def post(uri, opts = {}) request(POST, uri, opts) end
|
60
|
+
# Make a PUT request and return a MockResponse. See #request.
|
61
|
+
def put(uri, opts = {}) request(PUT, uri, opts) end
|
62
|
+
# Make a PATCH request and return a MockResponse. See #request.
|
63
|
+
def patch(uri, opts = {}) request(PATCH, uri, opts) end
|
64
|
+
# Make a DELETE request and return a MockResponse. See #request.
|
65
|
+
def delete(uri, opts = {}) request(DELETE, uri, opts) end
|
66
|
+
# Make a HEAD request and return a MockResponse. See #request.
|
67
|
+
def head(uri, opts = {}) request(HEAD, uri, opts) end
|
68
|
+
# Make an OPTIONS request and return a MockResponse. See #request.
|
69
|
+
def options(uri, opts = {}) request(OPTIONS, uri, opts) end
|
70
|
+
|
71
|
+
# Make a request using the given request method for the given
|
72
|
+
# uri to the rack application and return a MockResponse.
|
73
|
+
# Options given are passed to MockRequest.env_for.
|
74
|
+
def request(method = GET, uri = "", opts = {})
|
75
|
+
env = self.class.env_for(uri, opts.merge(method: method))
|
66
76
|
|
67
77
|
if opts[:lint]
|
68
78
|
app = Rack::Lint.new(@app)
|
@@ -71,7 +81,7 @@ module Rack
|
|
71
81
|
end
|
72
82
|
|
73
83
|
errors = env[RACK_ERRORS]
|
74
|
-
status, headers, body
|
84
|
+
status, headers, body = app.call(env)
|
75
85
|
MockResponse.new(status, headers, body, errors)
|
76
86
|
ensure
|
77
87
|
body.close if body.respond_to?(:close)
|
@@ -85,7 +95,14 @@ module Rack
|
|
85
95
|
end
|
86
96
|
|
87
97
|
# Return the Rack environment used for a request to +uri+.
|
88
|
-
|
98
|
+
# All options that are strings are added to the returned environment.
|
99
|
+
# Options:
|
100
|
+
# :fatal :: Whether to raise an exception if request outputs to rack.errors
|
101
|
+
# :input :: The rack.input to set
|
102
|
+
# :method :: The HTTP request method to use
|
103
|
+
# :params :: The params to use
|
104
|
+
# :script_name :: The SCRIPT_NAME to set
|
105
|
+
def self.env_for(uri = "", opts = {})
|
89
106
|
uri = parse_uri_rfc2396(uri)
|
90
107
|
uri.path = "/#{uri.path}" unless uri.path[0] == ?/
|
91
108
|
|
@@ -139,7 +156,7 @@ module Rack
|
|
139
156
|
rack_input.set_encoding(Encoding::BINARY)
|
140
157
|
env[RACK_INPUT] = rack_input
|
141
158
|
|
142
|
-
env["CONTENT_LENGTH"] ||= env[RACK_INPUT].
|
159
|
+
env["CONTENT_LENGTH"] ||= env[RACK_INPUT].size.to_s if env[RACK_INPUT].respond_to?(:size)
|
143
160
|
|
144
161
|
opts.each { |field, value|
|
145
162
|
env[field] = value if String === field
|
@@ -154,17 +171,24 @@ module Rack
|
|
154
171
|
# MockRequest.
|
155
172
|
|
156
173
|
class MockResponse < Rack::Response
|
174
|
+
class << self
|
175
|
+
alias [] new
|
176
|
+
end
|
177
|
+
|
157
178
|
# Headers
|
158
|
-
attr_reader :original_headers
|
179
|
+
attr_reader :original_headers, :cookies
|
159
180
|
|
160
181
|
# Errors
|
161
182
|
attr_accessor :errors
|
162
183
|
|
163
|
-
def initialize(status, headers, body, errors=StringIO.new(""))
|
184
|
+
def initialize(status, headers, body, errors = StringIO.new(""))
|
164
185
|
@original_headers = headers
|
165
186
|
@errors = errors.string if errors.respond_to?(:string)
|
187
|
+
@cookies = parse_cookies_from_header
|
166
188
|
|
167
189
|
super(body, status, headers)
|
190
|
+
|
191
|
+
buffered_body!
|
168
192
|
end
|
169
193
|
|
170
194
|
def =~(other)
|
@@ -186,11 +210,64 @@ module Rack
|
|
186
210
|
# ...
|
187
211
|
# res.body.should == "foo!"
|
188
212
|
# end
|
189
|
-
|
213
|
+
buffer = String.new
|
214
|
+
|
215
|
+
super.each do |chunk|
|
216
|
+
buffer << chunk
|
217
|
+
end
|
218
|
+
|
219
|
+
return buffer
|
190
220
|
end
|
191
221
|
|
192
222
|
def empty?
|
193
223
|
[201, 204, 304].include? status
|
194
224
|
end
|
225
|
+
|
226
|
+
def cookie(name)
|
227
|
+
cookies.fetch(name, nil)
|
228
|
+
end
|
229
|
+
|
230
|
+
private
|
231
|
+
|
232
|
+
def parse_cookies_from_header
|
233
|
+
cookies = Hash.new
|
234
|
+
if original_headers.has_key? 'Set-Cookie'
|
235
|
+
set_cookie_header = original_headers.fetch('Set-Cookie')
|
236
|
+
set_cookie_header.split("\n").each do |cookie|
|
237
|
+
cookie_name, cookie_filling = cookie.split('=', 2)
|
238
|
+
cookie_attributes = identify_cookie_attributes cookie_filling
|
239
|
+
parsed_cookie = CGI::Cookie.new(
|
240
|
+
'name' => cookie_name.strip,
|
241
|
+
'value' => cookie_attributes.fetch('value'),
|
242
|
+
'path' => cookie_attributes.fetch('path', nil),
|
243
|
+
'domain' => cookie_attributes.fetch('domain', nil),
|
244
|
+
'expires' => cookie_attributes.fetch('expires', nil),
|
245
|
+
'secure' => cookie_attributes.fetch('secure', false)
|
246
|
+
)
|
247
|
+
cookies.store(cookie_name, parsed_cookie)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
cookies
|
251
|
+
end
|
252
|
+
|
253
|
+
def identify_cookie_attributes(cookie_filling)
|
254
|
+
cookie_bits = cookie_filling.split(';')
|
255
|
+
cookie_attributes = Hash.new
|
256
|
+
cookie_attributes.store('value', cookie_bits[0].strip)
|
257
|
+
cookie_bits.each do |bit|
|
258
|
+
if bit.include? '='
|
259
|
+
cookie_attribute, attribute_value = bit.split('=')
|
260
|
+
cookie_attributes.store(cookie_attribute.strip, attribute_value.strip)
|
261
|
+
if cookie_attribute.include? 'max-age'
|
262
|
+
cookie_attributes.store('expires', Time.now + attribute_value.strip.to_i)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
if bit.include? 'secure'
|
266
|
+
cookie_attributes.store('secure', true)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
cookie_attributes
|
270
|
+
end
|
271
|
+
|
195
272
|
end
|
196
273
|
end
|
data/lib/rack/multipart.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'multipart/parser'
|
2
4
|
|
3
5
|
module Rack
|
4
6
|
# A multipart form data parser, adapted from IOWA.
|
@@ -14,10 +16,10 @@ module Rack
|
|
14
16
|
TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
|
15
17
|
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
|
16
18
|
VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
|
17
|
-
BROKEN_QUOTED = /^#{CONDISP}.*;\
|
18
|
-
BROKEN_UNQUOTED = /^#{CONDISP}.*;\
|
19
|
+
BROKEN_QUOTED = /^#{CONDISP}.*;\s*filename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
|
20
|
+
BROKEN_UNQUOTED = /^#{CONDISP}.*;\s*filename=(#{TOKEN})/i
|
19
21
|
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
|
20
|
-
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition
|
22
|
+
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*;\s*name=(#{VALUE})/ni
|
21
23
|
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
|
22
24
|
# Updated definitions from RFC 2231
|
23
25
|
ATTRIBUTE_CHAR = %r{[^ \t\v\n\r)(><@,;:\\"/\[\]?='*%]}
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
module Multipart
|
3
5
|
class Generator
|
@@ -15,9 +17,13 @@ module Rack
|
|
15
17
|
|
16
18
|
flattened_params.map do |name, file|
|
17
19
|
if file.respond_to?(:original_filename)
|
18
|
-
|
19
|
-
|
20
|
-
|
20
|
+
if file.path
|
21
|
+
::File.open(file.path, 'rb') do |f|
|
22
|
+
f.set_encoding(Encoding::BINARY)
|
23
|
+
content_for_tempfile(f, file, name)
|
24
|
+
end
|
25
|
+
else
|
26
|
+
content_for_tempfile(file, file, name)
|
21
27
|
end
|
22
28
|
else
|
23
29
|
content_for_other(file, name)
|
@@ -27,21 +33,18 @@ module Rack
|
|
27
33
|
|
28
34
|
private
|
29
35
|
def multipart?
|
30
|
-
multipart = false
|
31
|
-
|
32
36
|
query = lambda { |value|
|
33
37
|
case value
|
34
38
|
when Array
|
35
|
-
value.
|
39
|
+
value.any?(&query)
|
36
40
|
when Hash
|
37
|
-
value.values.
|
41
|
+
value.values.any?(&query)
|
38
42
|
when Rack::Multipart::UploadedFile
|
39
|
-
|
43
|
+
true
|
40
44
|
end
|
41
45
|
}
|
42
|
-
@params.values.each(&query)
|
43
46
|
|
44
|
-
|
47
|
+
@params.values.any?(&query)
|
45
48
|
end
|
46
49
|
|
47
50
|
def flattened_params
|
@@ -70,12 +73,13 @@ module Rack
|
|
70
73
|
end
|
71
74
|
|
72
75
|
def content_for_tempfile(io, file, name)
|
76
|
+
length = ::File.stat(file.path).size if file.path
|
77
|
+
filename = "; filename=\"#{Utils.escape(file.original_filename)}\"" if file.original_filename
|
73
78
|
<<-EOF
|
74
79
|
--#{MULTIPART_BOUNDARY}\r
|
75
|
-
Content-Disposition: form-data; name="#{name}"
|
80
|
+
Content-Disposition: form-data; name="#{name}"#{filename}\r
|
76
81
|
Content-Type: #{file.content_type}\r
|
77
|
-
Content-Length: #{
|
78
|
-
\r
|
82
|
+
#{"Content-Length: #{length}\r\n" if length}\r
|
79
83
|
#{io.read}\r
|
80
84
|
EOF
|
81
85
|
end
|
@@ -1,16 +1,22 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'strscan'
|
2
4
|
|
3
5
|
module Rack
|
4
6
|
module Multipart
|
5
7
|
class MultipartPartLimitError < Errno::EMFILE; end
|
6
8
|
|
7
9
|
class Parser
|
8
|
-
|
10
|
+
(require_relative '../core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
11
|
+
|
12
|
+
BUFSIZE = 1_048_576
|
9
13
|
TEXT_PLAIN = "text/plain"
|
10
14
|
TEMPFILE_FACTORY = lambda { |filename, content_type|
|
11
|
-
Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0"
|
15
|
+
Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
|
12
16
|
}
|
13
17
|
|
18
|
+
BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
|
19
|
+
|
14
20
|
class BoundedIO # :nodoc:
|
15
21
|
def initialize(io, content_length)
|
16
22
|
@io = io
|
@@ -18,15 +24,15 @@ module Rack
|
|
18
24
|
@cursor = 0
|
19
25
|
end
|
20
26
|
|
21
|
-
def read(size)
|
27
|
+
def read(size, outbuf = nil)
|
22
28
|
return if @cursor >= @content_length
|
23
29
|
|
24
30
|
left = @content_length - @cursor
|
25
31
|
|
26
32
|
str = if left < size
|
27
|
-
@io.read left
|
33
|
+
@io.read left, outbuf
|
28
34
|
else
|
29
|
-
@io.read size
|
35
|
+
@io.read size, outbuf
|
30
36
|
end
|
31
37
|
|
32
38
|
if str
|
@@ -61,13 +67,14 @@ module Rack
|
|
61
67
|
return EMPTY unless boundary
|
62
68
|
|
63
69
|
io = BoundedIO.new(io, content_length) if content_length
|
70
|
+
outbuf = String.new
|
64
71
|
|
65
72
|
parser = new(boundary, tmpfile, bufsize, qp)
|
66
|
-
parser.on_read io.read(bufsize)
|
73
|
+
parser.on_read io.read(bufsize, outbuf)
|
67
74
|
|
68
75
|
loop do
|
69
76
|
break if parser.state == :DONE
|
70
|
-
parser.on_read io.read(bufsize)
|
77
|
+
parser.on_read io.read(bufsize, outbuf)
|
71
78
|
end
|
72
79
|
|
73
80
|
io.rewind
|
@@ -90,14 +97,8 @@ module Rack
|
|
90
97
|
# those which give the lone filename.
|
91
98
|
fn = filename.split(/[\/\\]/).last
|
92
99
|
|
93
|
-
data = {:
|
94
|
-
:
|
95
|
-
elsif !filename && content_type && body.is_a?(IO)
|
96
|
-
body.rewind
|
97
|
-
|
98
|
-
# Generic multipart cases, not coming from a form
|
99
|
-
data = {:type => content_type,
|
100
|
-
:name => name, :tempfile => body, :head => head}
|
100
|
+
data = { filename: fn, type: content_type,
|
101
|
+
name: name, tempfile: body, head: head }
|
101
102
|
end
|
102
103
|
|
103
104
|
yield data
|
@@ -116,7 +117,7 @@ module Rack
|
|
116
117
|
|
117
118
|
include Enumerable
|
118
119
|
|
119
|
-
def initialize
|
120
|
+
def initialize(tempfile)
|
120
121
|
@tempfile = tempfile
|
121
122
|
@mime_parts = []
|
122
123
|
@open_files = 0
|
@@ -126,7 +127,7 @@ module Rack
|
|
126
127
|
@mime_parts.each { |part| yield part }
|
127
128
|
end
|
128
129
|
|
129
|
-
def on_mime_head
|
130
|
+
def on_mime_head(mime_index, head, filename, content_type, name)
|
130
131
|
if filename
|
131
132
|
body = @tempfile.call(filename, content_type)
|
132
133
|
body.binmode if body.respond_to?(:binmode)
|
@@ -138,14 +139,15 @@ module Rack
|
|
138
139
|
end
|
139
140
|
|
140
141
|
@mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
|
142
|
+
|
141
143
|
check_open_files
|
142
144
|
end
|
143
145
|
|
144
|
-
def on_mime_body
|
146
|
+
def on_mime_body(mime_index, content)
|
145
147
|
@mime_parts[mime_index].body << content
|
146
148
|
end
|
147
149
|
|
148
|
-
def on_mime_finish
|
150
|
+
def on_mime_finish(mime_index)
|
149
151
|
end
|
150
152
|
|
151
153
|
private
|
@@ -163,25 +165,26 @@ module Rack
|
|
163
165
|
attr_reader :state
|
164
166
|
|
165
167
|
def initialize(boundary, tempfile, bufsize, query_parser)
|
166
|
-
@buf = String.new
|
167
|
-
|
168
168
|
@query_parser = query_parser
|
169
169
|
@params = query_parser.make_params
|
170
170
|
@boundary = "--#{boundary}"
|
171
171
|
@bufsize = bufsize
|
172
172
|
|
173
|
-
@rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
|
174
|
-
@rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
|
175
173
|
@full_boundary = @boundary
|
176
174
|
@end_boundary = @boundary + '--'
|
177
175
|
@state = :FAST_FORWARD
|
178
176
|
@mime_index = 0
|
179
177
|
@collector = Collector.new tempfile
|
178
|
+
|
179
|
+
@sbuf = StringScanner.new("".dup)
|
180
|
+
@body_regex = /(?:#{EOL})?#{Regexp.quote(@boundary)}(?:#{EOL}|--)/m
|
181
|
+
@rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
|
182
|
+
@head_regex = /(.*?#{EOL})#{EOL}/m
|
180
183
|
end
|
181
184
|
|
182
|
-
def on_read
|
185
|
+
def on_read(content)
|
183
186
|
handle_empty_content!(content)
|
184
|
-
@
|
187
|
+
@sbuf.concat content
|
185
188
|
run_parser
|
186
189
|
end
|
187
190
|
|
@@ -192,7 +195,6 @@ module Rack
|
|
192
195
|
@query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
|
193
196
|
end
|
194
197
|
end
|
195
|
-
|
196
198
|
MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
|
197
199
|
end
|
198
200
|
|
@@ -219,7 +221,7 @@ module Rack
|
|
219
221
|
if consume_boundary
|
220
222
|
@state = :MIME_HEAD
|
221
223
|
else
|
222
|
-
raise EOFError, "bad content body" if @
|
224
|
+
raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
|
223
225
|
:want_read
|
224
226
|
end
|
225
227
|
end
|
@@ -227,19 +229,16 @@ module Rack
|
|
227
229
|
def handle_consume_token
|
228
230
|
tok = consume_boundary
|
229
231
|
# break if we're at the end of a buffer, but not if it is the end of a field
|
230
|
-
if tok == :END_BOUNDARY || (@
|
231
|
-
|
232
|
+
@state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY)
|
233
|
+
:DONE
|
232
234
|
else
|
233
|
-
|
235
|
+
:MIME_HEAD
|
234
236
|
end
|
235
237
|
end
|
236
238
|
|
237
239
|
def handle_mime_head
|
238
|
-
if @
|
239
|
-
|
240
|
-
head = @buf.slice!(0, i+2) # First \r\n
|
241
|
-
@buf.slice!(0, 2) # Second \r\n
|
242
|
-
|
240
|
+
if @sbuf.scan_until(@head_regex)
|
241
|
+
head = @sbuf[1]
|
243
242
|
content_type = head[MULTIPART_CONTENT_TYPE, 1]
|
244
243
|
if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
|
245
244
|
name = Rack::Auth::Digest::Params::dequote(name)
|
@@ -250,7 +249,7 @@ module Rack
|
|
250
249
|
filename = get_filename(head)
|
251
250
|
|
252
251
|
if name.nil? || name.empty?
|
253
|
-
name = filename || "#{content_type || TEXT_PLAIN}[]"
|
252
|
+
name = filename || "#{content_type || TEXT_PLAIN}[]".dup
|
254
253
|
end
|
255
254
|
|
256
255
|
@collector.on_mime_head @mime_index, head, filename, content_type, name
|
@@ -261,16 +260,19 @@ module Rack
|
|
261
260
|
end
|
262
261
|
|
263
262
|
def handle_mime_body
|
264
|
-
if
|
265
|
-
#
|
266
|
-
@collector.on_mime_body @mime_index,
|
267
|
-
@
|
263
|
+
if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
|
264
|
+
body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string
|
265
|
+
@collector.on_mime_body @mime_index, body
|
266
|
+
@sbuf.pos += body.length + 2 # skip \r\n after the content
|
268
267
|
@state = :CONSUME_TOKEN
|
269
268
|
@mime_index += 1
|
270
269
|
else
|
271
|
-
# Save
|
272
|
-
if @rx_max_size < @
|
273
|
-
|
270
|
+
# Save what we have so far
|
271
|
+
if @rx_max_size < @sbuf.rest_size
|
272
|
+
delta = @sbuf.rest_size - @rx_max_size
|
273
|
+
@collector.on_mime_body @mime_index, @sbuf.peek(delta)
|
274
|
+
@sbuf.pos += delta
|
275
|
+
@sbuf.string = @sbuf.rest
|
274
276
|
end
|
275
277
|
:want_read
|
276
278
|
end
|
@@ -278,16 +280,13 @@ module Rack
|
|
278
280
|
|
279
281
|
def full_boundary; @full_boundary; end
|
280
282
|
|
281
|
-
def rx; @rx; end
|
282
|
-
|
283
283
|
def consume_boundary
|
284
|
-
while @
|
285
|
-
read_buffer = $1
|
284
|
+
while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
|
286
285
|
case read_buffer.strip
|
287
286
|
when full_boundary then return :BOUNDARY
|
288
287
|
when @end_boundary then return :END_BOUNDARY
|
289
288
|
end
|
290
|
-
return if @
|
289
|
+
return if @sbuf.eos?
|
291
290
|
end
|
292
291
|
end
|
293
292
|
|
@@ -308,8 +307,8 @@ module Rack
|
|
308
307
|
|
309
308
|
return unless filename
|
310
309
|
|
311
|
-
if filename.scan(/%.?.?/).all? { |s|
|
312
|
-
filename = Utils.
|
310
|
+
if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
|
311
|
+
filename = Utils.unescape_path(filename)
|
313
312
|
end
|
314
313
|
|
315
314
|
filename.scrub!
|
@@ -325,7 +324,7 @@ module Rack
|
|
325
324
|
filename
|
326
325
|
end
|
327
326
|
|
328
|
-
CHARSET
|
327
|
+
CHARSET = "charset"
|
329
328
|
|
330
329
|
def tag_multipart_encoding(filename, content_type, name, body)
|
331
330
|
name = name.to_s
|
@@ -340,12 +339,12 @@ module Rack
|
|
340
339
|
type_subtype = list.first
|
341
340
|
type_subtype.strip!
|
342
341
|
if TEXT_PLAIN == type_subtype
|
343
|
-
rest
|
342
|
+
rest = list.drop 1
|
344
343
|
rest.each do |param|
|
345
|
-
k,v = param.split('=', 2)
|
344
|
+
k, v = param.split('=', 2)
|
346
345
|
k.strip!
|
347
346
|
v.strip!
|
348
|
-
v = v[1..-2] if v
|
347
|
+
v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
|
349
348
|
encoding = Encoding.find v if k == CHARSET
|
350
349
|
end
|
351
350
|
end
|
@@ -355,7 +354,6 @@ module Rack
|
|
355
354
|
body.force_encoding(encoding)
|
356
355
|
end
|
357
356
|
|
358
|
-
|
359
357
|
def handle_empty_content!(content)
|
360
358
|
if content.nil? || content.empty?
|
361
359
|
raise EOFError
|