rack 2.0.1 → 2.2.17
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 +5 -5
- data/CHANGELOG.md +795 -0
- data/CONTRIBUTING.md +136 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +188 -145
- data/Rakefile +37 -23
- data/{SPEC → SPEC.rdoc} +46 -17
- 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 +1 -1
- data/lib/rack/auth/basic.rb +6 -4
- data/lib/rack/auth/digest/md5.rb +13 -11
- data/lib/rack/auth/digest/nonce.rb +5 -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 +37 -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 +60 -70
- data/lib/rack/directory.rb +84 -64
- data/lib/rack/etag.rb +8 -5
- 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/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 +19 -10
- data/lib/rack/handler.rb +7 -2
- data/lib/rack/head.rb +1 -1
- data/lib/rack/lint.rb +221 -186
- data/lib/rack/lobster.rb +10 -10
- data/lib/rack/lock.rb +14 -4
- data/lib/rack/logger.rb +2 -0
- data/lib/rack/media_type.rb +23 -8
- data/lib/rack/method_override.rb +13 -4
- data/lib/rack/mime.rb +9 -1
- data/lib/rack/mock.rb +135 -29
- data/lib/rack/multipart/generator.rb +17 -13
- data/lib/rack/multipart/parser.rb +85 -68
- data/lib/rack/multipart/uploaded_file.rb +15 -7
- data/lib/rack/multipart.rb +6 -5
- data/lib/rack/null_logger.rb +2 -0
- data/lib/rack/query_parser.rb +108 -36
- data/lib/rack/recursive.rb +7 -5
- data/lib/rack/reloader.rb +8 -4
- data/lib/rack/request.rb +232 -60
- 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 +14 -10
- data/lib/rack/server.rb +97 -25
- data/lib/rack/session/abstract/id.rb +113 -25
- data/lib/rack/session/cookie.rb +22 -14
- data/lib/rack/session/memcache.rb +4 -87
- data/lib/rack/session/pool.rb +24 -10
- data/lib/rack/show_exceptions.rb +22 -18
- data/lib/rack/show_status.rb +9 -9
- data/lib/rack/static.rb +25 -12
- data/lib/rack/tempfile_reaper.rb +1 -1
- data/lib/rack/urlmap.rb +13 -7
- data/lib/rack/utils.rb +135 -123
- data/lib/rack/version.rb +29 -0
- data/lib/rack.rb +67 -73
- data/rack.gemspec +40 -29
- metadata +25 -184
- 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_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 -365
- 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 -251
- 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 -194
- data/test/spec_logger.rb +0 -24
- data/test/spec_media_type.rb +0 -42
- data/test/spec_method_override.rb +0 -83
- data/test/spec_mime.rb +0 -51
- data/test/spec_mock.rb +0 -342
- data/test/spec_multipart.rb +0 -716
- data/test/spec_null_logger.rb +0 -21
- data/test/spec_recursive.rb +0 -75
- data/test/spec_request.rb +0 -1393
- 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 -28
- 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 -80
- 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 -208
- 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
@@ -1,16 +1,25 @@
|
|
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
|
8
|
+
class MultipartTotalPartLimitError < StandardError; end
|
6
9
|
|
7
10
|
class Parser
|
8
|
-
|
11
|
+
(require_relative '../core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
12
|
+
|
13
|
+
BUFSIZE = 1_048_576
|
9
14
|
TEXT_PLAIN = "text/plain"
|
10
15
|
TEMPFILE_FACTORY = lambda { |filename, content_type|
|
11
|
-
|
16
|
+
extension = ::File.extname(filename.gsub("\0", '%00'))[0, 129]
|
17
|
+
|
18
|
+
Tempfile.new(["RackMultipart", extension])
|
12
19
|
}
|
13
20
|
|
21
|
+
BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
|
22
|
+
|
14
23
|
class BoundedIO # :nodoc:
|
15
24
|
def initialize(io, content_length)
|
16
25
|
@io = io
|
@@ -18,15 +27,15 @@ module Rack
|
|
18
27
|
@cursor = 0
|
19
28
|
end
|
20
29
|
|
21
|
-
def read(size)
|
30
|
+
def read(size, outbuf = nil)
|
22
31
|
return if @cursor >= @content_length
|
23
32
|
|
24
33
|
left = @content_length - @cursor
|
25
34
|
|
26
35
|
str = if left < size
|
27
|
-
@io.read left
|
36
|
+
@io.read left, outbuf
|
28
37
|
else
|
29
|
-
@io.read size
|
38
|
+
@io.read size, outbuf
|
30
39
|
end
|
31
40
|
|
32
41
|
if str
|
@@ -39,8 +48,6 @@ module Rack
|
|
39
48
|
str
|
40
49
|
end
|
41
50
|
|
42
|
-
def eof?; @content_length == @cursor; end
|
43
|
-
|
44
51
|
def rewind
|
45
52
|
@io.rewind
|
46
53
|
end
|
@@ -63,13 +70,14 @@ module Rack
|
|
63
70
|
return EMPTY unless boundary
|
64
71
|
|
65
72
|
io = BoundedIO.new(io, content_length) if content_length
|
73
|
+
outbuf = String.new
|
66
74
|
|
67
75
|
parser = new(boundary, tmpfile, bufsize, qp)
|
68
|
-
parser.on_read io.read(bufsize
|
76
|
+
parser.on_read io.read(bufsize, outbuf)
|
69
77
|
|
70
78
|
loop do
|
71
79
|
break if parser.state == :DONE
|
72
|
-
parser.on_read io.read(bufsize
|
80
|
+
parser.on_read io.read(bufsize, outbuf)
|
73
81
|
end
|
74
82
|
|
75
83
|
io.rewind
|
@@ -92,14 +100,8 @@ module Rack
|
|
92
100
|
# those which give the lone filename.
|
93
101
|
fn = filename.split(/[\/\\]/).last
|
94
102
|
|
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}
|
103
|
+
data = { filename: fn, type: content_type,
|
104
|
+
name: name, tempfile: body, head: head }
|
103
105
|
end
|
104
106
|
|
105
107
|
yield data
|
@@ -118,7 +120,7 @@ module Rack
|
|
118
120
|
|
119
121
|
include Enumerable
|
120
122
|
|
121
|
-
def initialize
|
123
|
+
def initialize(tempfile)
|
122
124
|
@tempfile = tempfile
|
123
125
|
@mime_parts = []
|
124
126
|
@open_files = 0
|
@@ -128,62 +130,75 @@ module Rack
|
|
128
130
|
@mime_parts.each { |part| yield part }
|
129
131
|
end
|
130
132
|
|
131
|
-
def on_mime_head
|
133
|
+
def on_mime_head(mime_index, head, filename, content_type, name)
|
132
134
|
if filename
|
133
135
|
body = @tempfile.call(filename, content_type)
|
134
136
|
body.binmode if body.respond_to?(:binmode)
|
135
137
|
klass = TempfilePart
|
136
138
|
@open_files += 1
|
137
139
|
else
|
138
|
-
body =
|
140
|
+
body = String.new
|
139
141
|
klass = BufferPart
|
140
142
|
end
|
141
143
|
|
142
144
|
@mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
|
143
|
-
|
145
|
+
|
146
|
+
check_part_limits
|
144
147
|
end
|
145
148
|
|
146
|
-
def on_mime_body
|
149
|
+
def on_mime_body(mime_index, content)
|
147
150
|
@mime_parts[mime_index].body << content
|
148
151
|
end
|
149
152
|
|
150
|
-
def on_mime_finish
|
153
|
+
def on_mime_finish(mime_index)
|
151
154
|
end
|
152
155
|
|
153
156
|
private
|
154
157
|
|
155
|
-
def
|
156
|
-
|
157
|
-
|
158
|
+
def check_part_limits
|
159
|
+
file_limit = Utils.multipart_file_limit
|
160
|
+
part_limit = Utils.multipart_total_part_limit
|
161
|
+
|
162
|
+
if file_limit && file_limit > 0
|
163
|
+
if @open_files >= file_limit
|
158
164
|
@mime_parts.each(&:close)
|
159
165
|
raise MultipartPartLimitError, 'Maximum file multiparts in content reached'
|
160
166
|
end
|
161
167
|
end
|
168
|
+
|
169
|
+
if part_limit && part_limit > 0
|
170
|
+
if @mime_parts.size >= part_limit
|
171
|
+
@mime_parts.each(&:close)
|
172
|
+
raise MultipartTotalPartLimitError, 'Maximum total multiparts in content reached'
|
173
|
+
end
|
174
|
+
end
|
162
175
|
end
|
163
176
|
end
|
164
177
|
|
165
178
|
attr_reader :state
|
166
179
|
|
167
180
|
def initialize(boundary, tempfile, bufsize, query_parser)
|
168
|
-
@buf = "".force_encoding(Encoding::ASCII_8BIT)
|
169
|
-
|
170
181
|
@query_parser = query_parser
|
171
182
|
@params = query_parser.make_params
|
172
183
|
@boundary = "--#{boundary}"
|
173
|
-
@boundary_size = @boundary.bytesize + EOL.size
|
174
184
|
@bufsize = bufsize
|
175
185
|
|
176
|
-
@rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
|
177
186
|
@full_boundary = @boundary
|
178
187
|
@end_boundary = @boundary + '--'
|
179
188
|
@state = :FAST_FORWARD
|
180
189
|
@mime_index = 0
|
181
190
|
@collector = Collector.new tempfile
|
191
|
+
|
192
|
+
@sbuf = StringScanner.new("".dup)
|
193
|
+
@body_regex = /(?:#{EOL})?#{Regexp.quote(@boundary)}(?:#{EOL}|--)/m
|
194
|
+
@end_boundary_size = boundary.bytesize + 6 # (-- at start, -- at finish, EOL at end)
|
195
|
+
@rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
|
196
|
+
@head_regex = /(.*?#{EOL})#{EOL}/m
|
182
197
|
end
|
183
198
|
|
184
|
-
def on_read
|
185
|
-
handle_empty_content!(content
|
186
|
-
@
|
199
|
+
def on_read(content)
|
200
|
+
handle_empty_content!(content)
|
201
|
+
@sbuf.concat content
|
187
202
|
run_parser
|
188
203
|
end
|
189
204
|
|
@@ -194,7 +209,6 @@ module Rack
|
|
194
209
|
@query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
|
195
210
|
end
|
196
211
|
end
|
197
|
-
|
198
212
|
MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
|
199
213
|
end
|
200
214
|
|
@@ -218,10 +232,15 @@ module Rack
|
|
218
232
|
end
|
219
233
|
|
220
234
|
def handle_fast_forward
|
221
|
-
|
235
|
+
tok = consume_boundary
|
236
|
+
|
237
|
+
if tok == :END_BOUNDARY && @sbuf.pos == @end_boundary_size && @sbuf.eos?
|
238
|
+
# stop parsing a buffer if a buffer is only an end boundary.
|
239
|
+
@state = :DONE
|
240
|
+
elsif tok
|
222
241
|
@state = :MIME_HEAD
|
223
242
|
else
|
224
|
-
raise EOFError, "bad content body" if @
|
243
|
+
raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
|
225
244
|
:want_read
|
226
245
|
end
|
227
246
|
end
|
@@ -229,19 +248,16 @@ module Rack
|
|
229
248
|
def handle_consume_token
|
230
249
|
tok = consume_boundary
|
231
250
|
# 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
|
-
|
251
|
+
@state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY)
|
252
|
+
:DONE
|
234
253
|
else
|
235
|
-
|
254
|
+
:MIME_HEAD
|
236
255
|
end
|
237
256
|
end
|
238
257
|
|
239
258
|
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
|
-
|
259
|
+
if @sbuf.scan_until(@head_regex)
|
260
|
+
head = @sbuf[1]
|
245
261
|
content_type = head[MULTIPART_CONTENT_TYPE, 1]
|
246
262
|
if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
|
247
263
|
name = Rack::Auth::Digest::Params::dequote(name)
|
@@ -252,7 +268,7 @@ module Rack
|
|
252
268
|
filename = get_filename(head)
|
253
269
|
|
254
270
|
if name.nil? || name.empty?
|
255
|
-
name = filename || "#{content_type || TEXT_PLAIN}[]"
|
271
|
+
name = filename || "#{content_type || TEXT_PLAIN}[]".dup
|
256
272
|
end
|
257
273
|
|
258
274
|
@collector.on_mime_head @mime_index, head, filename, content_type, name
|
@@ -263,31 +279,33 @@ module Rack
|
|
263
279
|
end
|
264
280
|
|
265
281
|
def handle_mime_body
|
266
|
-
if @
|
267
|
-
#
|
268
|
-
|
269
|
-
|
270
|
-
@buf.slice!(0, 2) # Remove \r\n after the content
|
271
|
-
end
|
282
|
+
if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
|
283
|
+
body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string
|
284
|
+
@collector.on_mime_body @mime_index, body
|
285
|
+
@sbuf.pos += body.length + 2 # skip \r\n after the content
|
272
286
|
@state = :CONSUME_TOKEN
|
273
287
|
@mime_index += 1
|
274
288
|
else
|
289
|
+
# Save what we have so far
|
290
|
+
if @rx_max_size < @sbuf.rest_size
|
291
|
+
delta = @sbuf.rest_size - @rx_max_size
|
292
|
+
@collector.on_mime_body @mime_index, @sbuf.peek(delta)
|
293
|
+
@sbuf.pos += delta
|
294
|
+
@sbuf.string = @sbuf.rest
|
295
|
+
end
|
275
296
|
:want_read
|
276
297
|
end
|
277
298
|
end
|
278
299
|
|
279
300
|
def full_boundary; @full_boundary; end
|
280
301
|
|
281
|
-
def rx; @rx; end
|
282
|
-
|
283
302
|
def consume_boundary
|
284
|
-
while @
|
285
|
-
read_buffer = $1
|
303
|
+
while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
|
286
304
|
case read_buffer.strip
|
287
305
|
when full_boundary then return :BOUNDARY
|
288
306
|
when @end_boundary then return :END_BOUNDARY
|
289
307
|
end
|
290
|
-
return if @
|
308
|
+
return if @sbuf.eos?
|
291
309
|
end
|
292
310
|
end
|
293
311
|
|
@@ -302,14 +320,15 @@ module Rack
|
|
302
320
|
elsif filename = params['filename*']
|
303
321
|
encoding, _, filename = filename.split("'", 3)
|
304
322
|
end
|
305
|
-
when
|
323
|
+
when BROKEN
|
306
324
|
filename = $1
|
325
|
+
filename = $1 if filename =~ /^"(.*)"$/
|
307
326
|
end
|
308
327
|
|
309
328
|
return unless filename
|
310
329
|
|
311
|
-
if filename.scan(/%.?.?/).all? { |s|
|
312
|
-
filename = Utils.
|
330
|
+
if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
|
331
|
+
filename = Utils.unescape_path(filename)
|
313
332
|
end
|
314
333
|
|
315
334
|
filename.scrub!
|
@@ -325,7 +344,7 @@ module Rack
|
|
325
344
|
filename
|
326
345
|
end
|
327
346
|
|
328
|
-
CHARSET
|
347
|
+
CHARSET = "charset"
|
329
348
|
|
330
349
|
def tag_multipart_encoding(filename, content_type, name, body)
|
331
350
|
name = name.to_s
|
@@ -340,12 +359,12 @@ module Rack
|
|
340
359
|
type_subtype = list.first
|
341
360
|
type_subtype.strip!
|
342
361
|
if TEXT_PLAIN == type_subtype
|
343
|
-
rest
|
362
|
+
rest = list.drop 1
|
344
363
|
rest.each do |param|
|
345
|
-
k,v = param.split('=', 2)
|
364
|
+
k, v = param.split('=', 2)
|
346
365
|
k.strip!
|
347
366
|
v.strip!
|
348
|
-
v = v[1..-2] if v
|
367
|
+
v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
|
349
368
|
encoding = Encoding.find v if k == CHARSET
|
350
369
|
end
|
351
370
|
end
|
@@ -355,11 +374,9 @@ module Rack
|
|
355
374
|
body.force_encoding(encoding)
|
356
375
|
end
|
357
376
|
|
358
|
-
|
359
|
-
def handle_empty_content!(content, eof)
|
377
|
+
def handle_empty_content!(content)
|
360
378
|
if content.nil? || content.empty?
|
361
|
-
raise EOFError
|
362
|
-
return true
|
379
|
+
raise EOFError
|
363
380
|
end
|
364
381
|
end
|
365
382
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
module Multipart
|
3
5
|
class UploadedFile
|
@@ -7,17 +9,23 @@ module Rack
|
|
7
9
|
# The content type of the "uploaded" file
|
8
10
|
attr_accessor :content_type
|
9
11
|
|
10
|
-
def initialize(
|
11
|
-
|
12
|
+
def initialize(filepath = nil, ct = "text/plain", bin = false,
|
13
|
+
path: filepath, content_type: ct, binary: bin, filename: nil, io: nil)
|
14
|
+
if io
|
15
|
+
@tempfile = io
|
16
|
+
@original_filename = filename
|
17
|
+
else
|
18
|
+
raise "#{path} file does not exist" unless ::File.exist?(path)
|
19
|
+
@original_filename = filename || ::File.basename(path)
|
20
|
+
@tempfile = Tempfile.new([@original_filename, ::File.extname(path)], encoding: Encoding::BINARY)
|
21
|
+
@tempfile.binmode if binary
|
22
|
+
FileUtils.copy_file(path, @tempfile.path)
|
23
|
+
end
|
12
24
|
@content_type = content_type
|
13
|
-
@original_filename = ::File.basename(path)
|
14
|
-
@tempfile = Tempfile.new([@original_filename, ::File.extname(path)], encoding: Encoding::BINARY)
|
15
|
-
@tempfile.binmode if binary
|
16
|
-
FileUtils.copy_file(path, @tempfile.path)
|
17
25
|
end
|
18
26
|
|
19
27
|
def path
|
20
|
-
@tempfile.path
|
28
|
+
@tempfile.path if @tempfile.respond_to?(:path)
|
21
29
|
end
|
22
30
|
alias_method :local_path, :path
|
23
31
|
|
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,13 +16,12 @@ module Rack
|
|
14
16
|
TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
|
15
17
|
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
|
16
18
|
VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
|
17
|
-
|
18
|
-
BROKEN_UNQUOTED = /^#{CONDISP}.*;\sfilename=(#{TOKEN})/i
|
19
|
+
BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i
|
19
20
|
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
|
20
|
-
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition
|
21
|
+
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s*name=(#{VALUE})/ni
|
21
22
|
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
|
22
23
|
# Updated definitions from RFC 2231
|
23
|
-
ATTRIBUTE_CHAR = %r{[^ \
|
24
|
+
ATTRIBUTE_CHAR = %r{[^ \x00-\x1f\x7f)(><@,;:\\"/\[\]?='*%]}
|
24
25
|
ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
|
25
26
|
SECTION = /\*[0-9]+/
|
26
27
|
REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
|
data/lib/rack/null_logger.rb
CHANGED
data/lib/rack/query_parser.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
class QueryParser
|
5
|
+
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
6
|
+
|
3
7
|
DEFAULT_SEP = /[&;] */n
|
4
8
|
COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
|
5
9
|
|
@@ -12,16 +16,47 @@ module Rack
|
|
12
16
|
# sequence.
|
13
17
|
class InvalidParameterError < ArgumentError; end
|
14
18
|
|
15
|
-
|
16
|
-
|
19
|
+
# QueryLimitError is for errors raised when the query provided exceeds one
|
20
|
+
# of the query parser limits.
|
21
|
+
class QueryLimitError < RangeError
|
22
|
+
end
|
23
|
+
|
24
|
+
# ParamsTooDeepError is the old name for the error that is raised when params
|
25
|
+
# are recursively nested over the specified limit. Make it the same as
|
26
|
+
# as QueryLimitError, so that code that rescues ParamsTooDeepError error
|
27
|
+
# to handle bad query strings also now handles other limits.
|
28
|
+
ParamsTooDeepError = QueryLimitError
|
29
|
+
|
30
|
+
def self.make_default(key_space_limit, param_depth_limit, **options)
|
31
|
+
new(Params, key_space_limit, param_depth_limit, **options)
|
17
32
|
end
|
18
33
|
|
19
34
|
attr_reader :key_space_limit, :param_depth_limit
|
20
35
|
|
21
|
-
|
36
|
+
env_int = lambda do |key, val|
|
37
|
+
if str_val = ENV[key]
|
38
|
+
begin
|
39
|
+
val = Integer(str_val, 10)
|
40
|
+
rescue ArgumentError
|
41
|
+
raise ArgumentError, "non-integer value provided for environment variable #{key}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
val
|
46
|
+
end
|
47
|
+
|
48
|
+
BYTESIZE_LIMIT = env_int.call("RACK_QUERY_PARSER_BYTESIZE_LIMIT", 4194304)
|
49
|
+
private_constant :BYTESIZE_LIMIT
|
50
|
+
|
51
|
+
PARAMS_LIMIT = env_int.call("RACK_QUERY_PARSER_PARAMS_LIMIT", 4096)
|
52
|
+
private_constant :PARAMS_LIMIT
|
53
|
+
|
54
|
+
def initialize(params_class, key_space_limit, param_depth_limit, bytesize_limit: BYTESIZE_LIMIT, params_limit: PARAMS_LIMIT)
|
22
55
|
@params_class = params_class
|
23
56
|
@key_space_limit = key_space_limit
|
24
57
|
@param_depth_limit = param_depth_limit
|
58
|
+
@bytesize_limit = bytesize_limit
|
59
|
+
@params_limit = params_limit
|
25
60
|
end
|
26
61
|
|
27
62
|
# Stolen from Mongrel, with some small modifications:
|
@@ -34,9 +69,9 @@ module Rack
|
|
34
69
|
|
35
70
|
params = make_params
|
36
71
|
|
37
|
-
(qs
|
72
|
+
check_query_string(qs, d).split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
|
38
73
|
next if p.empty?
|
39
|
-
k, v = p.split('='
|
74
|
+
k, v = p.split('=', 2).map!(&unescaper)
|
40
75
|
|
41
76
|
if cur = params[k]
|
42
77
|
if cur.class == Array
|
@@ -49,7 +84,7 @@ module Rack
|
|
49
84
|
end
|
50
85
|
end
|
51
86
|
|
52
|
-
return params.
|
87
|
+
return params.to_h
|
53
88
|
end
|
54
89
|
|
55
90
|
# parse_nested_query expands a query string into structural types. Supported
|
@@ -58,43 +93,44 @@ module Rack
|
|
58
93
|
# ParameterTypeError is raised. Users are encouraged to return a 400 in this
|
59
94
|
# case.
|
60
95
|
def parse_nested_query(qs, d = nil)
|
61
|
-
return {} if qs.nil? || qs.empty?
|
62
96
|
params = make_params
|
63
97
|
|
64
|
-
|
65
|
-
|
98
|
+
unless qs.nil? || qs.empty?
|
99
|
+
check_query_string(qs, d).split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
|
100
|
+
k, v = p.split('=', 2).map! { |s| unescape(s) }
|
66
101
|
|
67
|
-
|
102
|
+
normalize_params(params, k, v, param_depth_limit)
|
103
|
+
end
|
68
104
|
end
|
69
105
|
|
70
|
-
return params.
|
106
|
+
return params.to_h
|
71
107
|
rescue ArgumentError => e
|
72
|
-
raise InvalidParameterError, e.message
|
108
|
+
raise InvalidParameterError, e.message, e.backtrace
|
73
109
|
end
|
74
110
|
|
75
111
|
# normalize_params recursively expands parameters into structural types. If
|
76
112
|
# the structural types represented by two different parameter names are in
|
77
113
|
# conflict, a ParameterTypeError is raised.
|
78
114
|
def normalize_params(params, name, v, depth)
|
79
|
-
raise
|
115
|
+
raise ParamsTooDeepError if depth <= 0
|
80
116
|
|
81
117
|
name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
|
82
|
-
k = $1 || ''
|
83
|
-
after = $' || ''
|
118
|
+
k = $1 || ''
|
119
|
+
after = $' || ''
|
84
120
|
|
85
121
|
if k.empty?
|
86
|
-
if !v.nil? && name == "[]"
|
122
|
+
if !v.nil? && name == "[]"
|
87
123
|
return Array(v)
|
88
124
|
else
|
89
125
|
return
|
90
126
|
end
|
91
127
|
end
|
92
128
|
|
93
|
-
if after == ''
|
129
|
+
if after == ''
|
94
130
|
params[k] = v
|
95
|
-
elsif after == "["
|
131
|
+
elsif after == "["
|
96
132
|
params[name] = v
|
97
|
-
elsif after == "[]"
|
133
|
+
elsif after == "[]"
|
98
134
|
params[k] ||= []
|
99
135
|
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
100
136
|
params[k] << v
|
@@ -135,7 +171,7 @@ module Rack
|
|
135
171
|
end
|
136
172
|
|
137
173
|
def params_hash_has_key?(hash, key)
|
138
|
-
return false if
|
174
|
+
return false if /\[\]/.match?(key)
|
139
175
|
|
140
176
|
key.split(/[\[\]]+/).inject(hash) do |h, part|
|
141
177
|
next h if part == ''
|
@@ -146,8 +182,24 @@ module Rack
|
|
146
182
|
true
|
147
183
|
end
|
148
184
|
|
149
|
-
def
|
150
|
-
|
185
|
+
def check_query_string(qs, sep)
|
186
|
+
if qs
|
187
|
+
if qs.bytesize > @bytesize_limit
|
188
|
+
raise QueryLimitError, "total query size (#{qs.bytesize}) exceeds limit (#{@bytesize_limit})"
|
189
|
+
end
|
190
|
+
|
191
|
+
if (param_count = qs.count(sep.is_a?(String) ? sep : '&')) >= @params_limit
|
192
|
+
raise QueryLimitError, "total number of query parameters (#{param_count+1}) exceeds limit (#{@params_limit})"
|
193
|
+
end
|
194
|
+
|
195
|
+
qs
|
196
|
+
else
|
197
|
+
''
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def unescape(string)
|
202
|
+
Utils.unescape(string)
|
151
203
|
end
|
152
204
|
|
153
205
|
class Params
|
@@ -163,7 +215,7 @@ module Rack
|
|
163
215
|
|
164
216
|
def []=(key, value)
|
165
217
|
@size += key.size if key && !@params.key?(key)
|
166
|
-
raise
|
218
|
+
raise ParamsTooDeepError, 'exceeded available parameter key space' if @size > @limit
|
167
219
|
@params[key] = value
|
168
220
|
end
|
169
221
|
|
@@ -171,22 +223,42 @@ module Rack
|
|
171
223
|
@params.key?(key)
|
172
224
|
end
|
173
225
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
226
|
+
# Recursively unwraps nested `Params` objects and constructs an object
|
227
|
+
# of the same shape, but using the objects' internal representations
|
228
|
+
# (Ruby hashes) in place of the objects. The result is a hash consisting
|
229
|
+
# purely of Ruby primitives.
|
230
|
+
#
|
231
|
+
# Mutation warning!
|
232
|
+
#
|
233
|
+
# 1. This method mutates the internal representation of the `Params`
|
234
|
+
# objects in order to save object allocations.
|
235
|
+
#
|
236
|
+
# 2. The value you get back is a reference to the internal hash
|
237
|
+
# representation, not a copy.
|
238
|
+
#
|
239
|
+
# 3. Because the `Params` object's internal representation is mutable
|
240
|
+
# through the `#[]=` method, it is not thread safe. The result of
|
241
|
+
# getting the hash representation while another thread is adding a
|
242
|
+
# key to it is non-deterministic.
|
243
|
+
#
|
244
|
+
def to_h
|
245
|
+
@params.each do |key, value|
|
246
|
+
case value
|
247
|
+
when self
|
248
|
+
# Handle circular references gracefully.
|
249
|
+
@params[key] = @params
|
250
|
+
when Params
|
251
|
+
@params[key] = value.to_h
|
252
|
+
when Array
|
253
|
+
value.map! { |v| v.kind_of?(Params) ? v.to_h : v }
|
254
|
+
else
|
255
|
+
# Ignore anything that is not a `Params` object or
|
256
|
+
# a collection that can contain one.
|
186
257
|
end
|
187
258
|
end
|
188
|
-
|
259
|
+
@params
|
189
260
|
end
|
261
|
+
alias_method :to_params_hash, :to_h
|
190
262
|
end
|
191
263
|
end
|
192
264
|
end
|
data/lib/rack/recursive.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'uri'
|
2
4
|
|
3
5
|
module Rack
|
@@ -10,14 +12,14 @@ module Rack
|
|
10
12
|
class ForwardRequest < Exception
|
11
13
|
attr_reader :url, :env
|
12
14
|
|
13
|
-
def initialize(url, env={})
|
15
|
+
def initialize(url, env = {})
|
14
16
|
@url = URI(url)
|
15
17
|
@env = env
|
16
18
|
|
17
|
-
@env[PATH_INFO]
|
18
|
-
@env[QUERY_STRING]
|
19
|
-
@env[HTTP_HOST]
|
20
|
-
@env[
|
19
|
+
@env[PATH_INFO] = @url.path
|
20
|
+
@env[QUERY_STRING] = @url.query if @url.query
|
21
|
+
@env[HTTP_HOST] = @url.host if @url.host
|
22
|
+
@env[HTTP_PORT] = @url.port if @url.port
|
21
23
|
@env[RACK_URL_SCHEME] = @url.scheme if @url.scheme
|
22
24
|
|
23
25
|
super "forwarding to #{url}"
|