rack 2.2.8 → 3.0.8
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 +203 -83
- data/CONTRIBUTING.md +53 -47
- data/MIT-LICENSE +1 -1
- data/README.md +309 -0
- data/SPEC.rdoc +174 -126
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +3 -1
- data/lib/rack/auth/basic.rb +0 -2
- data/lib/rack/auth/digest/md5.rb +1 -131
- data/lib/rack/auth/digest/nonce.rb +1 -54
- data/lib/rack/auth/digest/params.rb +1 -54
- data/lib/rack/auth/digest/request.rb +1 -43
- data/lib/rack/auth/digest.rb +256 -0
- data/lib/rack/body_proxy.rb +3 -1
- data/lib/rack/builder.rb +83 -63
- data/lib/rack/cascade.rb +2 -0
- data/lib/rack/chunked.rb +16 -13
- data/lib/rack/common_logger.rb +23 -18
- data/lib/rack/conditional_get.rb +18 -15
- data/lib/rack/constants.rb +64 -0
- data/lib/rack/content_length.rb +12 -16
- data/lib/rack/content_type.rb +8 -5
- data/lib/rack/deflater.rb +40 -26
- data/lib/rack/directory.rb +9 -3
- data/lib/rack/etag.rb +14 -23
- data/lib/rack/events.rb +4 -0
- data/lib/rack/file.rb +2 -0
- data/lib/rack/files.rb +15 -17
- data/lib/rack/head.rb +9 -8
- data/lib/rack/headers.rb +154 -0
- data/lib/rack/lint.rb +758 -646
- data/lib/rack/lock.rb +2 -5
- data/lib/rack/logger.rb +2 -0
- data/lib/rack/media_type.rb +1 -1
- data/lib/rack/method_override.rb +5 -1
- data/lib/rack/mime.rb +8 -0
- data/lib/rack/mock.rb +1 -271
- data/lib/rack/mock_request.rb +166 -0
- data/lib/rack/mock_response.rb +126 -0
- data/lib/rack/multipart/generator.rb +7 -5
- data/lib/rack/multipart/parser.rb +120 -64
- data/lib/rack/multipart/uploaded_file.rb +4 -0
- data/lib/rack/multipart.rb +20 -40
- data/lib/rack/null_logger.rb +9 -0
- data/lib/rack/query_parser.rb +78 -46
- data/lib/rack/recursive.rb +2 -0
- data/lib/rack/reloader.rb +0 -2
- data/lib/rack/request.rb +224 -106
- data/lib/rack/response.rb +136 -61
- data/lib/rack/rewindable_input.rb +24 -5
- data/lib/rack/runtime.rb +7 -6
- data/lib/rack/sendfile.rb +30 -25
- data/lib/rack/show_exceptions.rb +15 -2
- data/lib/rack/show_status.rb +17 -7
- data/lib/rack/static.rb +8 -8
- data/lib/rack/tempfile_reaper.rb +15 -4
- data/lib/rack/urlmap.rb +3 -1
- data/lib/rack/utils.rb +202 -176
- data/lib/rack/version.rb +9 -4
- data/lib/rack.rb +6 -76
- metadata +15 -35
- data/README.rdoc +0 -320
- data/Rakefile +0 -130
- data/bin/rackup +0 -5
- data/contrib/rack.png +0 -0
- data/contrib/rack.svg +0 -150
- data/contrib/rack_logo.svg +0 -164
- data/contrib/rdoc.css +0 -412
- data/example/lobster.ru +0 -6
- data/example/protectedlobster.rb +0 -16
- data/example/protectedlobster.ru +0 -10
- data/lib/rack/core_ext/regexp.rb +0 -14
- data/lib/rack/handler/cgi.rb +0 -59
- data/lib/rack/handler/fastcgi.rb +0 -100
- data/lib/rack/handler/lsws.rb +0 -61
- data/lib/rack/handler/scgi.rb +0 -71
- data/lib/rack/handler/thin.rb +0 -36
- data/lib/rack/handler/webrick.rb +0 -129
- data/lib/rack/handler.rb +0 -104
- data/lib/rack/lobster.rb +0 -70
- data/lib/rack/server.rb +0 -466
- data/lib/rack/session/abstract/id.rb +0 -523
- data/lib/rack/session/cookie.rb +0 -204
- data/lib/rack/session/memcache.rb +0 -10
- data/lib/rack/session/pool.rb +0 -85
- data/rack.gemspec +0 -46
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'uploaded_file'
|
4
|
+
|
3
5
|
module Rack
|
4
6
|
module Multipart
|
5
7
|
class Generator
|
@@ -74,12 +76,12 @@ module Rack
|
|
74
76
|
|
75
77
|
def content_for_tempfile(io, file, name)
|
76
78
|
length = ::File.stat(file.path).size if file.path
|
77
|
-
filename = "; filename=\"#{Utils.
|
79
|
+
filename = "; filename=\"#{Utils.escape_path(file.original_filename)}\""
|
78
80
|
<<-EOF
|
79
81
|
--#{MULTIPART_BOUNDARY}\r
|
80
|
-
|
81
|
-
|
82
|
-
#{"
|
82
|
+
content-disposition: form-data; name="#{name}"#{filename}\r
|
83
|
+
content-type: #{file.content_type}\r
|
84
|
+
#{"content-length: #{length}\r\n" if length}\r
|
83
85
|
#{io.read}\r
|
84
86
|
EOF
|
85
87
|
end
|
@@ -87,7 +89,7 @@ EOF
|
|
87
89
|
def content_for_other(file, name)
|
88
90
|
<<-EOF
|
89
91
|
--#{MULTIPART_BOUNDARY}\r
|
90
|
-
|
92
|
+
content-disposition: form-data; name="#{name}"\r
|
91
93
|
\r
|
92
94
|
#{file}\r
|
93
95
|
EOF
|
@@ -2,24 +2,54 @@
|
|
2
2
|
|
3
3
|
require 'strscan'
|
4
4
|
|
5
|
+
require_relative '../utils'
|
6
|
+
|
5
7
|
module Rack
|
6
8
|
module Multipart
|
7
9
|
class MultipartPartLimitError < Errno::EMFILE; end
|
10
|
+
|
8
11
|
class MultipartTotalPartLimitError < StandardError; end
|
9
12
|
|
10
|
-
class
|
11
|
-
|
13
|
+
# Use specific error class when parsing multipart request
|
14
|
+
# that ends early.
|
15
|
+
class EmptyContentError < ::EOFError; end
|
16
|
+
|
17
|
+
# Base class for multipart exceptions that do not subclass from
|
18
|
+
# other exception classes for backwards compatibility.
|
19
|
+
class Error < StandardError; end
|
20
|
+
|
21
|
+
EOL = "\r\n"
|
22
|
+
MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni
|
23
|
+
TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
|
24
|
+
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
|
25
|
+
VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
|
26
|
+
BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i
|
27
|
+
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
|
28
|
+
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s*name=(#{VALUE})/ni
|
29
|
+
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
|
30
|
+
# Updated definitions from RFC 2231
|
31
|
+
ATTRIBUTE_CHAR = %r{[^ \x00-\x1f\x7f)(><@,;:\\"/\[\]?='*%]}
|
32
|
+
ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
|
33
|
+
SECTION = /\*[0-9]+/
|
34
|
+
REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
|
35
|
+
REGULAR_PARAMETER = /(#{REGULAR_PARAMETER_NAME})=(#{VALUE})/
|
36
|
+
EXTENDED_OTHER_NAME = /#{ATTRIBUTE}\*[1-9][0-9]*\*/
|
37
|
+
EXTENDED_OTHER_VALUE = /%[0-9a-fA-F]{2}|#{ATTRIBUTE_CHAR}/
|
38
|
+
EXTENDED_OTHER_PARAMETER = /(#{EXTENDED_OTHER_NAME})=(#{EXTENDED_OTHER_VALUE}*)/
|
39
|
+
EXTENDED_INITIAL_NAME = /#{ATTRIBUTE}(?:\*0)?\*/
|
40
|
+
EXTENDED_INITIAL_VALUE = /[a-zA-Z0-9\-]*'[a-zA-Z0-9\-]*'#{EXTENDED_OTHER_VALUE}*/
|
41
|
+
EXTENDED_INITIAL_PARAMETER = /(#{EXTENDED_INITIAL_NAME})=(#{EXTENDED_INITIAL_VALUE})/
|
42
|
+
EXTENDED_PARAMETER = /#{EXTENDED_INITIAL_PARAMETER}|#{EXTENDED_OTHER_PARAMETER}/
|
43
|
+
DISPPARM = /;\s*(?:#{REGULAR_PARAMETER}|#{EXTENDED_PARAMETER})\s*/
|
44
|
+
RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
|
12
45
|
|
46
|
+
class Parser
|
13
47
|
BUFSIZE = 1_048_576
|
14
48
|
TEXT_PLAIN = "text/plain"
|
15
49
|
TEMPFILE_FACTORY = lambda { |filename, content_type|
|
16
|
-
|
17
|
-
|
18
|
-
Tempfile.new(["RackMultipart", extension])
|
50
|
+
Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
|
19
51
|
}
|
20
52
|
|
21
|
-
BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
|
22
|
-
|
23
53
|
class BoundedIO # :nodoc:
|
24
54
|
def initialize(io, content_length)
|
25
55
|
@io = io
|
@@ -41,16 +71,12 @@ module Rack
|
|
41
71
|
if str
|
42
72
|
@cursor += str.bytesize
|
43
73
|
else
|
44
|
-
# Raise an error for mismatching
|
74
|
+
# Raise an error for mismatching content-length and actual contents
|
45
75
|
raise EOFError, "bad content body"
|
46
76
|
end
|
47
77
|
|
48
78
|
str
|
49
79
|
end
|
50
|
-
|
51
|
-
def rewind
|
52
|
-
@io.rewind
|
53
|
-
end
|
54
80
|
end
|
55
81
|
|
56
82
|
MultipartInfo = Struct.new :params, :tmp_files
|
@@ -69,18 +95,17 @@ module Rack
|
|
69
95
|
boundary = parse_boundary content_type
|
70
96
|
return EMPTY unless boundary
|
71
97
|
|
98
|
+
if boundary.length > 70
|
99
|
+
# RFC 1521 Section 7.2.1 imposes a 70 character maximum for the boundary.
|
100
|
+
# Most clients use no more than 55 characters.
|
101
|
+
raise Error, "multipart boundary size too large (#{boundary.length} characters)"
|
102
|
+
end
|
103
|
+
|
72
104
|
io = BoundedIO.new(io, content_length) if content_length
|
73
|
-
outbuf = String.new
|
74
105
|
|
75
106
|
parser = new(boundary, tmpfile, bufsize, qp)
|
76
|
-
parser.
|
107
|
+
parser.parse(io)
|
77
108
|
|
78
|
-
loop do
|
79
|
-
break if parser.state == :DONE
|
80
|
-
parser.on_read io.read(bufsize, outbuf)
|
81
|
-
end
|
82
|
-
|
83
|
-
io.rewind
|
84
109
|
parser.result
|
85
110
|
end
|
86
111
|
|
@@ -180,32 +205,46 @@ module Rack
|
|
180
205
|
def initialize(boundary, tempfile, bufsize, query_parser)
|
181
206
|
@query_parser = query_parser
|
182
207
|
@params = query_parser.make_params
|
183
|
-
@boundary = "--#{boundary}"
|
184
208
|
@bufsize = bufsize
|
185
209
|
|
186
|
-
@full_boundary = @boundary
|
187
|
-
@end_boundary = @boundary + '--'
|
188
210
|
@state = :FAST_FORWARD
|
189
211
|
@mime_index = 0
|
190
212
|
@collector = Collector.new tempfile
|
191
213
|
|
192
214
|
@sbuf = StringScanner.new("".dup)
|
193
|
-
@body_regex = /(?:#{EOL})
|
194
|
-
@rx_max_size =
|
215
|
+
@body_regex = /(?:#{EOL}|\A)--#{Regexp.quote(boundary)}(?:#{EOL}|--)/m
|
216
|
+
@rx_max_size = boundary.bytesize + 6 # (\r\n-- at start, either \r\n or -- at finish)
|
195
217
|
@head_regex = /(.*?#{EOL})#{EOL}/m
|
196
218
|
end
|
197
219
|
|
198
|
-
def
|
199
|
-
|
200
|
-
|
201
|
-
|
220
|
+
def parse(io)
|
221
|
+
outbuf = String.new
|
222
|
+
read_data(io, outbuf)
|
223
|
+
|
224
|
+
loop do
|
225
|
+
status =
|
226
|
+
case @state
|
227
|
+
when :FAST_FORWARD
|
228
|
+
handle_fast_forward
|
229
|
+
when :CONSUME_TOKEN
|
230
|
+
handle_consume_token
|
231
|
+
when :MIME_HEAD
|
232
|
+
handle_mime_head
|
233
|
+
when :MIME_BODY
|
234
|
+
handle_mime_body
|
235
|
+
else # when :DONE
|
236
|
+
return
|
237
|
+
end
|
238
|
+
|
239
|
+
read_data(io, outbuf) if status == :want_read
|
240
|
+
end
|
202
241
|
end
|
203
242
|
|
204
243
|
def result
|
205
244
|
@collector.each do |part|
|
206
245
|
part.get_data do |data|
|
207
246
|
tag_multipart_encoding(part.filename, part.content_type, part.name, data)
|
208
|
-
@query_parser.normalize_params(@params, part.name, data
|
247
|
+
@query_parser.normalize_params(@params, part.name, data)
|
209
248
|
end
|
210
249
|
end
|
211
250
|
MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
|
@@ -213,29 +252,38 @@ module Rack
|
|
213
252
|
|
214
253
|
private
|
215
254
|
|
216
|
-
def
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
break if handle_fast_forward == :want_read
|
221
|
-
when :CONSUME_TOKEN
|
222
|
-
break if handle_consume_token == :want_read
|
223
|
-
when :MIME_HEAD
|
224
|
-
break if handle_mime_head == :want_read
|
225
|
-
when :MIME_BODY
|
226
|
-
break if handle_mime_body == :want_read
|
227
|
-
when :DONE
|
228
|
-
break
|
229
|
-
end
|
230
|
-
end
|
255
|
+
def dequote(str) # From WEBrick::HTTPUtils
|
256
|
+
ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
|
257
|
+
ret.gsub!(/\\(.)/, "\\1")
|
258
|
+
ret
|
231
259
|
end
|
232
260
|
|
261
|
+
def read_data(io, outbuf)
|
262
|
+
content = io.read(@bufsize, outbuf)
|
263
|
+
handle_empty_content!(content)
|
264
|
+
@sbuf.concat(content)
|
265
|
+
end
|
266
|
+
|
267
|
+
# This handles the initial parser state. We read until we find the starting
|
268
|
+
# boundary, then we can transition to the next state. If we find the ending
|
269
|
+
# boundary, this is an invalid multipart upload, but keep scanning for opening
|
270
|
+
# boundary in that case. If no boundary found, we need to keep reading data
|
271
|
+
# and retry. It's highly unlikely the initial read will not consume the
|
272
|
+
# boundary. The client would have to deliberately craft a response
|
273
|
+
# with the opening boundary beyond the buffer size for that to happen.
|
233
274
|
def handle_fast_forward
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
275
|
+
while true
|
276
|
+
case consume_boundary
|
277
|
+
when :BOUNDARY
|
278
|
+
# found opening boundary, transition to next state
|
279
|
+
@state = :MIME_HEAD
|
280
|
+
return
|
281
|
+
when :END_BOUNDARY
|
282
|
+
# invalid multipart upload, but retry for opening boundary
|
283
|
+
else
|
284
|
+
# no boundary found, keep reading data
|
285
|
+
return :want_read
|
286
|
+
end
|
239
287
|
end
|
240
288
|
end
|
241
289
|
|
@@ -254,7 +302,7 @@ module Rack
|
|
254
302
|
head = @sbuf[1]
|
255
303
|
content_type = head[MULTIPART_CONTENT_TYPE, 1]
|
256
304
|
if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
|
257
|
-
name =
|
305
|
+
name = dequote(name)
|
258
306
|
else
|
259
307
|
name = head[MULTIPART_CONTENT_ID, 1]
|
260
308
|
end
|
@@ -291,15 +339,16 @@ module Rack
|
|
291
339
|
end
|
292
340
|
end
|
293
341
|
|
294
|
-
|
295
|
-
|
342
|
+
# Scan until the we find the start or end of the boundary.
|
343
|
+
# If we find it, return the appropriate symbol for the start or
|
344
|
+
# end of the boundary. If we don't find the start or end of the
|
345
|
+
# boundary, clear the buffer and return nil.
|
296
346
|
def consume_boundary
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
return if @sbuf.eos?
|
347
|
+
if read_buffer = @sbuf.scan_until(@body_regex)
|
348
|
+
read_buffer.end_with?(EOL) ? :BOUNDARY : :END_BOUNDARY
|
349
|
+
else
|
350
|
+
@sbuf.terminate
|
351
|
+
nil
|
303
352
|
end
|
304
353
|
end
|
305
354
|
|
@@ -309,10 +358,10 @@ module Rack
|
|
309
358
|
when RFC2183
|
310
359
|
params = Hash[*head.scan(DISPPARM).flat_map(&:compact)]
|
311
360
|
|
312
|
-
if filename = params['filename']
|
313
|
-
filename = $1 if filename =~ /^"(.*)"$/
|
314
|
-
elsif filename = params['filename*']
|
361
|
+
if filename = params['filename*']
|
315
362
|
encoding, _, filename = filename.split("'", 3)
|
363
|
+
elsif filename = params['filename']
|
364
|
+
filename = $1 if filename =~ /^"(.*)"$/
|
316
365
|
end
|
317
366
|
when BROKEN
|
318
367
|
filename = $1
|
@@ -339,6 +388,7 @@ module Rack
|
|
339
388
|
end
|
340
389
|
|
341
390
|
CHARSET = "charset"
|
391
|
+
deprecate_constant :CHARSET
|
342
392
|
|
343
393
|
def tag_multipart_encoding(filename, content_type, name, body)
|
344
394
|
name = name.to_s
|
@@ -359,7 +409,13 @@ module Rack
|
|
359
409
|
k.strip!
|
360
410
|
v.strip!
|
361
411
|
v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
|
362
|
-
|
412
|
+
if k == "charset"
|
413
|
+
encoding = begin
|
414
|
+
Encoding.find v
|
415
|
+
rescue ArgumentError
|
416
|
+
Encoding::BINARY
|
417
|
+
end
|
418
|
+
end
|
363
419
|
end
|
364
420
|
end
|
365
421
|
end
|
@@ -370,7 +426,7 @@ module Rack
|
|
370
426
|
|
371
427
|
def handle_empty_content!(content)
|
372
428
|
if content.nil? || content.empty?
|
373
|
-
raise
|
429
|
+
raise EmptyContentError
|
374
430
|
end
|
375
431
|
end
|
376
432
|
end
|
data/lib/rack/multipart.rb
CHANGED
@@ -1,64 +1,44 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'constants'
|
4
|
+
require_relative 'utils'
|
5
|
+
|
3
6
|
require_relative 'multipart/parser'
|
7
|
+
require_relative 'multipart/generator'
|
4
8
|
|
5
9
|
module Rack
|
6
10
|
# A multipart form data parser, adapted from IOWA.
|
7
11
|
#
|
8
12
|
# Usually, Rack::Request#POST takes care of calling this.
|
9
13
|
module Multipart
|
10
|
-
autoload :UploadedFile, 'rack/multipart/uploaded_file'
|
11
|
-
autoload :Generator, 'rack/multipart/generator'
|
12
|
-
|
13
|
-
EOL = "\r\n"
|
14
14
|
MULTIPART_BOUNDARY = "AaB03x"
|
15
|
-
MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni
|
16
|
-
TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
|
17
|
-
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
|
18
|
-
VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
|
19
|
-
BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i
|
20
|
-
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
|
21
|
-
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s*name=(#{VALUE})/ni
|
22
|
-
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
|
23
|
-
# Updated definitions from RFC 2231
|
24
|
-
ATTRIBUTE_CHAR = %r{[^ \x00-\x1f\x7f)(><@,;:\\"/\[\]?='*%]}
|
25
|
-
ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
|
26
|
-
SECTION = /\*[0-9]+/
|
27
|
-
REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
|
28
|
-
REGULAR_PARAMETER = /(#{REGULAR_PARAMETER_NAME})=(#{VALUE})/
|
29
|
-
EXTENDED_OTHER_NAME = /#{ATTRIBUTE}\*[1-9][0-9]*\*/
|
30
|
-
EXTENDED_OTHER_VALUE = /%[0-9a-fA-F]{2}|#{ATTRIBUTE_CHAR}/
|
31
|
-
EXTENDED_OTHER_PARAMETER = /(#{EXTENDED_OTHER_NAME})=(#{EXTENDED_OTHER_VALUE}*)/
|
32
|
-
EXTENDED_INITIAL_NAME = /#{ATTRIBUTE}(?:\*0)?\*/
|
33
|
-
EXTENDED_INITIAL_VALUE = /[a-zA-Z0-9\-]*'[a-zA-Z0-9\-]*'#{EXTENDED_OTHER_VALUE}*/
|
34
|
-
EXTENDED_INITIAL_PARAMETER = /(#{EXTENDED_INITIAL_NAME})=(#{EXTENDED_INITIAL_VALUE})/
|
35
|
-
EXTENDED_PARAMETER = /#{EXTENDED_INITIAL_PARAMETER}|#{EXTENDED_OTHER_PARAMETER}/
|
36
|
-
DISPPARM = /;\s*(?:#{REGULAR_PARAMETER}|#{EXTENDED_PARAMETER})\s*/
|
37
|
-
RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
|
38
15
|
|
39
16
|
class << self
|
40
17
|
def parse_multipart(env, params = Rack::Utils.default_query_parser)
|
41
|
-
|
42
|
-
|
18
|
+
io = env[RACK_INPUT]
|
19
|
+
|
20
|
+
if content_length = env['CONTENT_LENGTH']
|
21
|
+
content_length = content_length.to_i
|
22
|
+
end
|
43
23
|
|
44
|
-
|
45
|
-
io = req.get_header(RACK_INPUT)
|
46
|
-
io.rewind
|
47
|
-
content_length = req.content_length
|
48
|
-
content_length = content_length.to_i if content_length
|
24
|
+
content_type = env['CONTENT_TYPE']
|
49
25
|
|
50
|
-
tempfile =
|
51
|
-
bufsize =
|
26
|
+
tempfile = env[RACK_MULTIPART_TEMPFILE_FACTORY] || Parser::TEMPFILE_FACTORY
|
27
|
+
bufsize = env[RACK_MULTIPART_BUFFER_SIZE] || Parser::BUFSIZE
|
52
28
|
|
53
|
-
info = Parser.parse
|
54
|
-
|
55
|
-
|
29
|
+
info = Parser.parse(io, content_length, content_type, tempfile, bufsize, params)
|
30
|
+
env[RACK_TEMPFILES] = info.tmp_files
|
31
|
+
|
32
|
+
return info.params
|
33
|
+
end
|
34
|
+
|
35
|
+
def extract_multipart(request, params = Rack::Utils.default_query_parser)
|
36
|
+
parse_multipart(request.env)
|
56
37
|
end
|
57
38
|
|
58
39
|
def build_multipart(params, first = true)
|
59
40
|
Generator.new(params, first).dump
|
60
41
|
end
|
61
42
|
end
|
62
|
-
|
63
43
|
end
|
64
44
|
end
|
data/lib/rack/null_logger.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'constants'
|
4
|
+
|
3
5
|
module Rack
|
4
6
|
class NullLogger
|
5
7
|
def initialize(app)
|
@@ -22,6 +24,11 @@ module Rack
|
|
22
24
|
def warn? ; end
|
23
25
|
def error? ; end
|
24
26
|
def fatal? ; end
|
27
|
+
def debug! ; end
|
28
|
+
def error! ; end
|
29
|
+
def fatal! ; end
|
30
|
+
def info! ; end
|
31
|
+
def warn! ; end
|
25
32
|
def level ; end
|
26
33
|
def progname ; end
|
27
34
|
def datetime_format ; end
|
@@ -34,6 +41,8 @@ module Rack
|
|
34
41
|
def sev_threshold=(sev_threshold); end
|
35
42
|
def close ; end
|
36
43
|
def add(severity, message = nil, progname = nil, &block); end
|
44
|
+
def log(severity, message = nil, progname = nil, &block); end
|
37
45
|
def <<(msg); end
|
46
|
+
def reopen(logdev = nil); end
|
38
47
|
end
|
39
48
|
end
|
data/lib/rack/query_parser.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'uri'
|
4
|
+
|
3
5
|
module Rack
|
4
6
|
class QueryParser
|
5
|
-
|
6
|
-
|
7
|
-
DEFAULT_SEP = /[&;] */n
|
7
|
+
DEFAULT_SEP = /[&] */n
|
8
8
|
COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
|
9
9
|
|
10
10
|
# ParameterTypeError is the error that is raised when incoming structural
|
@@ -20,29 +20,35 @@ module Rack
|
|
20
20
|
# nested over the specified limit.
|
21
21
|
class ParamsTooDeepError < RangeError; end
|
22
22
|
|
23
|
-
def self.make_default(
|
24
|
-
|
23
|
+
def self.make_default(_key_space_limit=(not_deprecated = true; nil), param_depth_limit)
|
24
|
+
unless not_deprecated
|
25
|
+
warn("`first argument `key_space limit` is deprecated and no longer has an effect. Please call with only one argument, which will be required in a future version of Rack", uplevel: 1)
|
26
|
+
end
|
27
|
+
|
28
|
+
new Params, param_depth_limit
|
25
29
|
end
|
26
30
|
|
27
|
-
attr_reader :
|
31
|
+
attr_reader :param_depth_limit
|
32
|
+
|
33
|
+
def initialize(params_class, _key_space_limit=(not_deprecated = true; nil), param_depth_limit)
|
34
|
+
unless not_deprecated
|
35
|
+
warn("`second argument `key_space limit` is deprecated and no longer has an effect. Please call with only two arguments, which will be required in a future version of Rack", uplevel: 1)
|
36
|
+
end
|
28
37
|
|
29
|
-
def initialize(params_class, key_space_limit, param_depth_limit)
|
30
38
|
@params_class = params_class
|
31
|
-
@key_space_limit = key_space_limit
|
32
39
|
@param_depth_limit = param_depth_limit
|
33
40
|
end
|
34
41
|
|
35
42
|
# Stolen from Mongrel, with some small modifications:
|
36
|
-
# Parses a query string by breaking it up at the '&'
|
37
|
-
#
|
38
|
-
#
|
39
|
-
|
40
|
-
def parse_query(qs, d = nil, &unescaper)
|
43
|
+
# Parses a query string by breaking it up at the '&'. You can also use this
|
44
|
+
# to parse cookies by changing the characters used in the second parameter
|
45
|
+
# (which defaults to '&').
|
46
|
+
def parse_query(qs, separator = nil, &unescaper)
|
41
47
|
unescaper ||= method(:unescape)
|
42
48
|
|
43
49
|
params = make_params
|
44
50
|
|
45
|
-
(qs || '').split(
|
51
|
+
(qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
|
46
52
|
next if p.empty?
|
47
53
|
k, v = p.split('=', 2).map!(&unescaper)
|
48
54
|
|
@@ -65,14 +71,14 @@ module Rack
|
|
65
71
|
# query strings with parameters of conflicting types, in this case a
|
66
72
|
# ParameterTypeError is raised. Users are encouraged to return a 400 in this
|
67
73
|
# case.
|
68
|
-
def parse_nested_query(qs,
|
74
|
+
def parse_nested_query(qs, separator = nil)
|
69
75
|
params = make_params
|
70
76
|
|
71
77
|
unless qs.nil? || qs.empty?
|
72
|
-
(qs || '').split(
|
78
|
+
(qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
|
73
79
|
k, v = p.split('=', 2).map! { |s| unescape(s) }
|
74
80
|
|
75
|
-
|
81
|
+
_normalize_params(params, k, v, 0)
|
76
82
|
end
|
77
83
|
end
|
78
84
|
|
@@ -83,58 +89,87 @@ module Rack
|
|
83
89
|
|
84
90
|
# normalize_params recursively expands parameters into structural types. If
|
85
91
|
# the structural types represented by two different parameter names are in
|
86
|
-
# conflict, a ParameterTypeError is raised.
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
name
|
91
|
-
|
92
|
-
after = $' || ''
|
92
|
+
# conflict, a ParameterTypeError is raised. The depth argument is deprecated
|
93
|
+
# and should no longer be used, it is kept for backwards compatibility with
|
94
|
+
# earlier versions of rack.
|
95
|
+
def normalize_params(params, name, v, _depth=nil)
|
96
|
+
_normalize_params(params, name, v, 0)
|
97
|
+
end
|
93
98
|
|
94
|
-
|
95
|
-
|
96
|
-
|
99
|
+
private def _normalize_params(params, name, v, depth)
|
100
|
+
raise ParamsTooDeepError if depth >= param_depth_limit
|
101
|
+
|
102
|
+
if !name
|
103
|
+
# nil name, treat same as empty string (required by tests)
|
104
|
+
k = after = ''
|
105
|
+
elsif depth == 0
|
106
|
+
# Start of parsing, don't treat [] or [ at start of string specially
|
107
|
+
if start = name.index('[', 1)
|
108
|
+
# Start of parameter nesting, use part before brackets as key
|
109
|
+
k = name[0, start]
|
110
|
+
after = name[start, name.length]
|
97
111
|
else
|
98
|
-
|
112
|
+
# Plain parameter with no nesting
|
113
|
+
k = name
|
114
|
+
after = ''
|
99
115
|
end
|
116
|
+
elsif name.start_with?('[]')
|
117
|
+
# Array nesting
|
118
|
+
k = '[]'
|
119
|
+
after = name[2, name.length]
|
120
|
+
elsif name.start_with?('[') && (start = name.index(']', 1))
|
121
|
+
# Hash nesting, use the part inside brackets as the key
|
122
|
+
k = name[1, start-1]
|
123
|
+
after = name[start+1, name.length]
|
124
|
+
else
|
125
|
+
# Probably malformed input, nested but not starting with [
|
126
|
+
# treat full name as key for backwards compatibility.
|
127
|
+
k = name
|
128
|
+
after = ''
|
100
129
|
end
|
101
130
|
|
131
|
+
return if k.empty?
|
132
|
+
|
102
133
|
if after == ''
|
103
|
-
|
134
|
+
if k == '[]' && depth != 0
|
135
|
+
return [v]
|
136
|
+
else
|
137
|
+
params[k] = v
|
138
|
+
end
|
104
139
|
elsif after == "["
|
105
140
|
params[name] = v
|
106
141
|
elsif after == "[]"
|
107
142
|
params[k] ||= []
|
108
143
|
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
109
144
|
params[k] << v
|
110
|
-
elsif after
|
111
|
-
|
145
|
+
elsif after.start_with?('[]')
|
146
|
+
# Recognize x[][y] (hash inside array) parameters
|
147
|
+
unless after[2] == '[' && after.end_with?(']') && (child_key = after[3, after.length-4]) && !child_key.empty? && !child_key.index('[') && !child_key.index(']')
|
148
|
+
# Handle other nested array parameters
|
149
|
+
child_key = after[2, after.length]
|
150
|
+
end
|
112
151
|
params[k] ||= []
|
113
152
|
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
114
153
|
if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key)
|
115
|
-
|
154
|
+
_normalize_params(params[k].last, child_key, v, depth + 1)
|
116
155
|
else
|
117
|
-
params[k] <<
|
156
|
+
params[k] << _normalize_params(make_params, child_key, v, depth + 1)
|
118
157
|
end
|
119
158
|
else
|
120
159
|
params[k] ||= make_params
|
121
160
|
raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
|
122
|
-
params[k] =
|
161
|
+
params[k] = _normalize_params(params[k], after, v, depth + 1)
|
123
162
|
end
|
124
163
|
|
125
164
|
params
|
126
165
|
end
|
127
166
|
|
128
167
|
def make_params
|
129
|
-
@params_class.new
|
130
|
-
end
|
131
|
-
|
132
|
-
def new_space_limit(key_space_limit)
|
133
|
-
self.class.new @params_class, key_space_limit, param_depth_limit
|
168
|
+
@params_class.new
|
134
169
|
end
|
135
170
|
|
136
171
|
def new_depth_limit(param_depth_limit)
|
137
|
-
self.class.new @params_class,
|
172
|
+
self.class.new @params_class, param_depth_limit
|
138
173
|
end
|
139
174
|
|
140
175
|
private
|
@@ -155,13 +190,12 @@ module Rack
|
|
155
190
|
true
|
156
191
|
end
|
157
192
|
|
158
|
-
def unescape(
|
159
|
-
|
193
|
+
def unescape(string, encoding = Encoding::UTF_8)
|
194
|
+
URI.decode_www_form_component(string, encoding)
|
160
195
|
end
|
161
196
|
|
162
197
|
class Params
|
163
|
-
def initialize
|
164
|
-
@limit = limit
|
198
|
+
def initialize
|
165
199
|
@size = 0
|
166
200
|
@params = {}
|
167
201
|
end
|
@@ -171,8 +205,6 @@ module Rack
|
|
171
205
|
end
|
172
206
|
|
173
207
|
def []=(key, value)
|
174
|
-
@size += key.size if key && !@params.key?(key)
|
175
|
-
raise ParamsTooDeepError, 'exceeded available parameter key space' if @size > @limit
|
176
208
|
@params[key] = value
|
177
209
|
end
|
178
210
|
|