rack 2.0.6 → 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} +220 -155
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +77 -117
- data/Rakefile +25 -18
- data/SPEC +3 -4
- 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 -8
- 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 +14 -11
- 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 -52
- data/lib/rack/multipart/uploaded_file.rb +2 -0
- data/lib/rack/multipart.rb +5 -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 +80 -27
- 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 +104 -21
- data/lib/rack/session/cookie.rb +21 -11
- data/lib/rack/session/memcache.rb +4 -87
- data/lib/rack/session/pool.rb +17 -8
- 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 +55 -70
- data/lib/rack.rb +63 -60
- data/rack.gemspec +17 -7
- metadata +30 -171
- 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,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,16 +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
10
|
|
7
11
|
class Parser
|
8
|
-
|
12
|
+
using ::Rack::RegexpExtensions
|
13
|
+
|
14
|
+
BUFSIZE = 1_048_576
|
9
15
|
TEXT_PLAIN = "text/plain"
|
10
16
|
TEMPFILE_FACTORY = lambda { |filename, content_type|
|
11
|
-
Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0"
|
17
|
+
Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
|
12
18
|
}
|
13
19
|
|
20
|
+
BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
|
21
|
+
|
14
22
|
class BoundedIO # :nodoc:
|
15
23
|
def initialize(io, content_length)
|
16
24
|
@io = io
|
@@ -18,15 +26,15 @@ module Rack
|
|
18
26
|
@cursor = 0
|
19
27
|
end
|
20
28
|
|
21
|
-
def read(size)
|
29
|
+
def read(size, outbuf = nil)
|
22
30
|
return if @cursor >= @content_length
|
23
31
|
|
24
32
|
left = @content_length - @cursor
|
25
33
|
|
26
34
|
str = if left < size
|
27
|
-
@io.read left
|
35
|
+
@io.read left, outbuf
|
28
36
|
else
|
29
|
-
@io.read size
|
37
|
+
@io.read size, outbuf
|
30
38
|
end
|
31
39
|
|
32
40
|
if str
|
@@ -39,8 +47,6 @@ module Rack
|
|
39
47
|
str
|
40
48
|
end
|
41
49
|
|
42
|
-
def eof?; @content_length == @cursor; end
|
43
|
-
|
44
50
|
def rewind
|
45
51
|
@io.rewind
|
46
52
|
end
|
@@ -63,13 +69,14 @@ module Rack
|
|
63
69
|
return EMPTY unless boundary
|
64
70
|
|
65
71
|
io = BoundedIO.new(io, content_length) if content_length
|
72
|
+
outbuf = String.new
|
66
73
|
|
67
74
|
parser = new(boundary, tmpfile, bufsize, qp)
|
68
|
-
parser.on_read io.read(bufsize
|
75
|
+
parser.on_read io.read(bufsize, outbuf)
|
69
76
|
|
70
77
|
loop do
|
71
78
|
break if parser.state == :DONE
|
72
|
-
parser.on_read io.read(bufsize
|
79
|
+
parser.on_read io.read(bufsize, outbuf)
|
73
80
|
end
|
74
81
|
|
75
82
|
io.rewind
|
@@ -92,14 +99,14 @@ module Rack
|
|
92
99
|
# those which give the lone filename.
|
93
100
|
fn = filename.split(/[\/\\]/).last
|
94
101
|
|
95
|
-
data = {:
|
96
|
-
:
|
102
|
+
data = { filename: fn, type: content_type,
|
103
|
+
name: name, tempfile: body, head: head }
|
97
104
|
elsif !filename && content_type && body.is_a?(IO)
|
98
105
|
body.rewind
|
99
106
|
|
100
107
|
# Generic multipart cases, not coming from a form
|
101
|
-
data = {:
|
102
|
-
:
|
108
|
+
data = { type: content_type,
|
109
|
+
name: name, tempfile: body, head: head }
|
103
110
|
end
|
104
111
|
|
105
112
|
yield data
|
@@ -140,6 +147,7 @@ module Rack
|
|
140
147
|
end
|
141
148
|
|
142
149
|
@mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
|
150
|
+
|
143
151
|
check_open_files
|
144
152
|
end
|
145
153
|
|
@@ -165,25 +173,26 @@ module Rack
|
|
165
173
|
attr_reader :state
|
166
174
|
|
167
175
|
def initialize(boundary, tempfile, bufsize, query_parser)
|
168
|
-
@buf = String.new
|
169
|
-
|
170
176
|
@query_parser = query_parser
|
171
177
|
@params = query_parser.make_params
|
172
178
|
@boundary = "--#{boundary}"
|
173
179
|
@bufsize = bufsize
|
174
180
|
|
175
|
-
@rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
|
176
|
-
@rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
|
177
181
|
@full_boundary = @boundary
|
178
182
|
@end_boundary = @boundary + '--'
|
179
183
|
@state = :FAST_FORWARD
|
180
184
|
@mime_index = 0
|
181
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
|
182
191
|
end
|
183
192
|
|
184
|
-
def on_read content
|
185
|
-
handle_empty_content!(content
|
186
|
-
@
|
193
|
+
def on_read content
|
194
|
+
handle_empty_content!(content)
|
195
|
+
@sbuf.concat content
|
187
196
|
run_parser
|
188
197
|
end
|
189
198
|
|
@@ -194,7 +203,6 @@ module Rack
|
|
194
203
|
@query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
|
195
204
|
end
|
196
205
|
end
|
197
|
-
|
198
206
|
MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
|
199
207
|
end
|
200
208
|
|
@@ -221,7 +229,7 @@ module Rack
|
|
221
229
|
if consume_boundary
|
222
230
|
@state = :MIME_HEAD
|
223
231
|
else
|
224
|
-
raise EOFError, "bad content body" if @
|
232
|
+
raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
|
225
233
|
:want_read
|
226
234
|
end
|
227
235
|
end
|
@@ -229,19 +237,16 @@ module Rack
|
|
229
237
|
def handle_consume_token
|
230
238
|
tok = consume_boundary
|
231
239
|
# 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
|
-
|
240
|
+
@state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY)
|
241
|
+
:DONE
|
234
242
|
else
|
235
|
-
|
243
|
+
:MIME_HEAD
|
236
244
|
end
|
237
245
|
end
|
238
246
|
|
239
247
|
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
|
-
|
248
|
+
if @sbuf.scan_until(@head_regex)
|
249
|
+
head = @sbuf[1]
|
245
250
|
content_type = head[MULTIPART_CONTENT_TYPE, 1]
|
246
251
|
if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
|
247
252
|
name = Rack::Auth::Digest::Params::dequote(name)
|
@@ -252,7 +257,7 @@ module Rack
|
|
252
257
|
filename = get_filename(head)
|
253
258
|
|
254
259
|
if name.nil? || name.empty?
|
255
|
-
name = filename || "#{content_type || TEXT_PLAIN}[]"
|
260
|
+
name = filename || "#{content_type || TEXT_PLAIN}[]".dup
|
256
261
|
end
|
257
262
|
|
258
263
|
@collector.on_mime_head @mime_index, head, filename, content_type, name
|
@@ -263,16 +268,19 @@ module Rack
|
|
263
268
|
end
|
264
269
|
|
265
270
|
def handle_mime_body
|
266
|
-
if
|
267
|
-
|
268
|
-
@collector.on_mime_body @mime_index,
|
269
|
-
@
|
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
|
270
275
|
@state = :CONSUME_TOKEN
|
271
276
|
@mime_index += 1
|
272
277
|
else
|
273
|
-
# Save
|
274
|
-
if @rx_max_size < @
|
275
|
-
|
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
|
276
284
|
end
|
277
285
|
:want_read
|
278
286
|
end
|
@@ -280,16 +288,13 @@ module Rack
|
|
280
288
|
|
281
289
|
def full_boundary; @full_boundary; end
|
282
290
|
|
283
|
-
def rx; @rx; end
|
284
|
-
|
285
291
|
def consume_boundary
|
286
|
-
while @
|
287
|
-
read_buffer = $1
|
292
|
+
while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
|
288
293
|
case read_buffer.strip
|
289
294
|
when full_boundary then return :BOUNDARY
|
290
295
|
when @end_boundary then return :END_BOUNDARY
|
291
296
|
end
|
292
|
-
return if @
|
297
|
+
return if @sbuf.eos?
|
293
298
|
end
|
294
299
|
end
|
295
300
|
|
@@ -310,8 +315,8 @@ module Rack
|
|
310
315
|
|
311
316
|
return unless filename
|
312
317
|
|
313
|
-
if filename.scan(/%.?.?/).all? { |s|
|
314
|
-
filename = Utils.
|
318
|
+
if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
|
319
|
+
filename = Utils.unescape_path(filename)
|
315
320
|
end
|
316
321
|
|
317
322
|
filename.scrub!
|
@@ -327,7 +332,7 @@ module Rack
|
|
327
332
|
filename
|
328
333
|
end
|
329
334
|
|
330
|
-
CHARSET
|
335
|
+
CHARSET = "charset"
|
331
336
|
|
332
337
|
def tag_multipart_encoding(filename, content_type, name, body)
|
333
338
|
name = name.to_s
|
@@ -344,10 +349,10 @@ module Rack
|
|
344
349
|
if TEXT_PLAIN == type_subtype
|
345
350
|
rest = list.drop 1
|
346
351
|
rest.each do |param|
|
347
|
-
k,v = param.split('=', 2)
|
352
|
+
k, v = param.split('=', 2)
|
348
353
|
k.strip!
|
349
354
|
v.strip!
|
350
|
-
v = v[1..-2] if v
|
355
|
+
v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
|
351
356
|
encoding = Encoding.find v if k == CHARSET
|
352
357
|
end
|
353
358
|
end
|
@@ -357,11 +362,9 @@ module Rack
|
|
357
362
|
body.force_encoding(encoding)
|
358
363
|
end
|
359
364
|
|
360
|
-
|
361
|
-
def handle_empty_content!(content, eof)
|
365
|
+
def handle_empty_content!(content)
|
362
366
|
if content.nil? || content.empty?
|
363
|
-
raise EOFError
|
364
|
-
return true
|
367
|
+
raise EOFError
|
365
368
|
end
|
366
369
|
end
|
367
370
|
end
|
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,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)(><@,;:\\"/\[\]?='*%]}
|