rack 2.0.6 → 2.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +694 -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} +38 -10
- 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 +57 -62
- 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 +100 -22
- data/lib/rack/session/cookie.rb +22 -14
- data/lib/rack/session/memcache.rb +4 -87
- data/lib/rack/session/pool.rb +18 -9
- 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 +102 -109
- data/lib/rack/version.rb +29 -0
- data/rack.gemspec +40 -28
- metadata +39 -181
- 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 -1398
- 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 -320
- data/test/spec_session_pool.rb +0 -210
- 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
|
|
@@ -39,8 +45,6 @@ module Rack
|
|
|
39
45
|
str
|
|
40
46
|
end
|
|
41
47
|
|
|
42
|
-
def eof?; @content_length == @cursor; end
|
|
43
|
-
|
|
44
48
|
def rewind
|
|
45
49
|
@io.rewind
|
|
46
50
|
end
|
|
@@ -63,13 +67,14 @@ module Rack
|
|
|
63
67
|
return EMPTY unless boundary
|
|
64
68
|
|
|
65
69
|
io = BoundedIO.new(io, content_length) if content_length
|
|
70
|
+
outbuf = String.new
|
|
66
71
|
|
|
67
72
|
parser = new(boundary, tmpfile, bufsize, qp)
|
|
68
|
-
parser.on_read io.read(bufsize
|
|
73
|
+
parser.on_read io.read(bufsize, outbuf)
|
|
69
74
|
|
|
70
75
|
loop do
|
|
71
76
|
break if parser.state == :DONE
|
|
72
|
-
parser.on_read io.read(bufsize
|
|
77
|
+
parser.on_read io.read(bufsize, outbuf)
|
|
73
78
|
end
|
|
74
79
|
|
|
75
80
|
io.rewind
|
|
@@ -92,14 +97,8 @@ module Rack
|
|
|
92
97
|
# those which give the lone filename.
|
|
93
98
|
fn = filename.split(/[\/\\]/).last
|
|
94
99
|
|
|
95
|
-
data = {:
|
|
96
|
-
:
|
|
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}
|
|
100
|
+
data = { filename: fn, type: content_type,
|
|
101
|
+
name: name, tempfile: body, head: head }
|
|
103
102
|
end
|
|
104
103
|
|
|
105
104
|
yield data
|
|
@@ -118,7 +117,7 @@ module Rack
|
|
|
118
117
|
|
|
119
118
|
include Enumerable
|
|
120
119
|
|
|
121
|
-
def initialize
|
|
120
|
+
def initialize(tempfile)
|
|
122
121
|
@tempfile = tempfile
|
|
123
122
|
@mime_parts = []
|
|
124
123
|
@open_files = 0
|
|
@@ -128,7 +127,7 @@ module Rack
|
|
|
128
127
|
@mime_parts.each { |part| yield part }
|
|
129
128
|
end
|
|
130
129
|
|
|
131
|
-
def on_mime_head
|
|
130
|
+
def on_mime_head(mime_index, head, filename, content_type, name)
|
|
132
131
|
if filename
|
|
133
132
|
body = @tempfile.call(filename, content_type)
|
|
134
133
|
body.binmode if body.respond_to?(:binmode)
|
|
@@ -140,14 +139,15 @@ module Rack
|
|
|
140
139
|
end
|
|
141
140
|
|
|
142
141
|
@mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
|
|
142
|
+
|
|
143
143
|
check_open_files
|
|
144
144
|
end
|
|
145
145
|
|
|
146
|
-
def on_mime_body
|
|
146
|
+
def on_mime_body(mime_index, content)
|
|
147
147
|
@mime_parts[mime_index].body << content
|
|
148
148
|
end
|
|
149
149
|
|
|
150
|
-
def on_mime_finish
|
|
150
|
+
def on_mime_finish(mime_index)
|
|
151
151
|
end
|
|
152
152
|
|
|
153
153
|
private
|
|
@@ -165,25 +165,26 @@ module Rack
|
|
|
165
165
|
attr_reader :state
|
|
166
166
|
|
|
167
167
|
def initialize(boundary, tempfile, bufsize, query_parser)
|
|
168
|
-
@buf = String.new
|
|
169
|
-
|
|
170
168
|
@query_parser = query_parser
|
|
171
169
|
@params = query_parser.make_params
|
|
172
170
|
@boundary = "--#{boundary}"
|
|
173
171
|
@bufsize = bufsize
|
|
174
172
|
|
|
175
|
-
@rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
|
|
176
|
-
@rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
|
|
177
173
|
@full_boundary = @boundary
|
|
178
174
|
@end_boundary = @boundary + '--'
|
|
179
175
|
@state = :FAST_FORWARD
|
|
180
176
|
@mime_index = 0
|
|
181
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
|
|
182
183
|
end
|
|
183
184
|
|
|
184
|
-
def on_read
|
|
185
|
-
handle_empty_content!(content
|
|
186
|
-
@
|
|
185
|
+
def on_read(content)
|
|
186
|
+
handle_empty_content!(content)
|
|
187
|
+
@sbuf.concat content
|
|
187
188
|
run_parser
|
|
188
189
|
end
|
|
189
190
|
|
|
@@ -194,7 +195,6 @@ module Rack
|
|
|
194
195
|
@query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
|
|
195
196
|
end
|
|
196
197
|
end
|
|
197
|
-
|
|
198
198
|
MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
|
|
199
199
|
end
|
|
200
200
|
|
|
@@ -221,7 +221,7 @@ module Rack
|
|
|
221
221
|
if consume_boundary
|
|
222
222
|
@state = :MIME_HEAD
|
|
223
223
|
else
|
|
224
|
-
raise EOFError, "bad content body" if @
|
|
224
|
+
raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
|
|
225
225
|
:want_read
|
|
226
226
|
end
|
|
227
227
|
end
|
|
@@ -229,19 +229,16 @@ module Rack
|
|
|
229
229
|
def handle_consume_token
|
|
230
230
|
tok = consume_boundary
|
|
231
231
|
# break if we're at the end of a buffer, but not if it is the end of a field
|
|
232
|
-
if tok == :END_BOUNDARY || (@
|
|
233
|
-
|
|
232
|
+
@state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY)
|
|
233
|
+
:DONE
|
|
234
234
|
else
|
|
235
|
-
|
|
235
|
+
:MIME_HEAD
|
|
236
236
|
end
|
|
237
237
|
end
|
|
238
238
|
|
|
239
239
|
def handle_mime_head
|
|
240
|
-
if @
|
|
241
|
-
|
|
242
|
-
head = @buf.slice!(0, i+2) # First \r\n
|
|
243
|
-
@buf.slice!(0, 2) # Second \r\n
|
|
244
|
-
|
|
240
|
+
if @sbuf.scan_until(@head_regex)
|
|
241
|
+
head = @sbuf[1]
|
|
245
242
|
content_type = head[MULTIPART_CONTENT_TYPE, 1]
|
|
246
243
|
if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
|
|
247
244
|
name = Rack::Auth::Digest::Params::dequote(name)
|
|
@@ -252,7 +249,7 @@ module Rack
|
|
|
252
249
|
filename = get_filename(head)
|
|
253
250
|
|
|
254
251
|
if name.nil? || name.empty?
|
|
255
|
-
name = filename || "#{content_type || TEXT_PLAIN}[]"
|
|
252
|
+
name = filename || "#{content_type || TEXT_PLAIN}[]".dup
|
|
256
253
|
end
|
|
257
254
|
|
|
258
255
|
@collector.on_mime_head @mime_index, head, filename, content_type, name
|
|
@@ -263,16 +260,19 @@ module Rack
|
|
|
263
260
|
end
|
|
264
261
|
|
|
265
262
|
def handle_mime_body
|
|
266
|
-
if
|
|
267
|
-
#
|
|
268
|
-
@collector.on_mime_body @mime_index,
|
|
269
|
-
@
|
|
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
|
|
270
267
|
@state = :CONSUME_TOKEN
|
|
271
268
|
@mime_index += 1
|
|
272
269
|
else
|
|
273
|
-
# Save
|
|
274
|
-
if @rx_max_size < @
|
|
275
|
-
|
|
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
|
|
276
276
|
end
|
|
277
277
|
:want_read
|
|
278
278
|
end
|
|
@@ -280,16 +280,13 @@ module Rack
|
|
|
280
280
|
|
|
281
281
|
def full_boundary; @full_boundary; end
|
|
282
282
|
|
|
283
|
-
def rx; @rx; end
|
|
284
|
-
|
|
285
283
|
def consume_boundary
|
|
286
|
-
while @
|
|
287
|
-
read_buffer = $1
|
|
284
|
+
while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
|
|
288
285
|
case read_buffer.strip
|
|
289
286
|
when full_boundary then return :BOUNDARY
|
|
290
287
|
when @end_boundary then return :END_BOUNDARY
|
|
291
288
|
end
|
|
292
|
-
return if @
|
|
289
|
+
return if @sbuf.eos?
|
|
293
290
|
end
|
|
294
291
|
end
|
|
295
292
|
|
|
@@ -310,8 +307,8 @@ module Rack
|
|
|
310
307
|
|
|
311
308
|
return unless filename
|
|
312
309
|
|
|
313
|
-
if filename.scan(/%.?.?/).all? { |s|
|
|
314
|
-
filename = Utils.
|
|
310
|
+
if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
|
|
311
|
+
filename = Utils.unescape_path(filename)
|
|
315
312
|
end
|
|
316
313
|
|
|
317
314
|
filename.scrub!
|
|
@@ -327,7 +324,7 @@ module Rack
|
|
|
327
324
|
filename
|
|
328
325
|
end
|
|
329
326
|
|
|
330
|
-
CHARSET
|
|
327
|
+
CHARSET = "charset"
|
|
331
328
|
|
|
332
329
|
def tag_multipart_encoding(filename, content_type, name, body)
|
|
333
330
|
name = name.to_s
|
|
@@ -342,12 +339,12 @@ module Rack
|
|
|
342
339
|
type_subtype = list.first
|
|
343
340
|
type_subtype.strip!
|
|
344
341
|
if TEXT_PLAIN == type_subtype
|
|
345
|
-
rest
|
|
342
|
+
rest = list.drop 1
|
|
346
343
|
rest.each do |param|
|
|
347
|
-
k,v = param.split('=', 2)
|
|
344
|
+
k, v = param.split('=', 2)
|
|
348
345
|
k.strip!
|
|
349
346
|
v.strip!
|
|
350
|
-
v = v[1..-2] if v
|
|
347
|
+
v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
|
|
351
348
|
encoding = Encoding.find v if k == CHARSET
|
|
352
349
|
end
|
|
353
350
|
end
|
|
@@ -357,11 +354,9 @@ module Rack
|
|
|
357
354
|
body.force_encoding(encoding)
|
|
358
355
|
end
|
|
359
356
|
|
|
360
|
-
|
|
361
|
-
def handle_empty_content!(content, eof)
|
|
357
|
+
def handle_empty_content!(content)
|
|
362
358
|
if content.nil? || content.empty?
|
|
363
|
-
raise EOFError
|
|
364
|
-
return true
|
|
359
|
+
raise EOFError
|
|
365
360
|
end
|
|
366
361
|
end
|
|
367
362
|
end
|