rack 2.2.8 → 3.0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +213 -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 +9 -4
- 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 +138 -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 +208 -178
- data/lib/rack/version.rb +9 -4
- data/lib/rack.rb +6 -76
- metadata +14 -34
- 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
|
|