rack 2.2.4 → 3.0.0.beta1
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 +152 -71
- data/CONTRIBUTING.md +53 -47
- data/MIT-LICENSE +1 -1
- data/README.md +287 -0
- data/Rakefile +40 -7
- data/SPEC.rdoc +175 -130
- data/contrib/LICENSE.md +7 -0
- data/contrib/logo.webp +0 -0
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +3 -1
- 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 +60 -42
- 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 +62 -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 +764 -684
- 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 +4 -0
- 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 +124 -0
- data/lib/rack/multipart/generator.rb +7 -5
- data/lib/rack/multipart/parser.rb +118 -61
- 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 +76 -44
- data/lib/rack/recursive.rb +2 -0
- data/lib/rack/reloader.rb +0 -2
- data/lib/rack/request.rb +187 -89
- data/lib/rack/response.rb +131 -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 +199 -173
- data/lib/rack/version.rb +9 -4
- data/lib/rack.rb +5 -76
- data/rack.gemspec +6 -6
- metadata +22 -34
- data/README.rdoc +0 -306
- 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/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 -203
- data/lib/rack/session/memcache.rb +0 -10
- data/lib/rack/session/pool.rb +0 -85
@@ -2,21 +2,52 @@
|
|
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
|
8
10
|
|
9
|
-
class
|
10
|
-
|
11
|
+
# Use specific error class when parsing multipart request
|
12
|
+
# that ends early.
|
13
|
+
class EmptyContentError < ::EOFError; end
|
14
|
+
|
15
|
+
# Base class for multipart exceptions that do not subclass from
|
16
|
+
# other exception classes for backwards compatibility.
|
17
|
+
class Error < StandardError; end
|
18
|
+
|
19
|
+
EOL = "\r\n"
|
20
|
+
MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni
|
21
|
+
TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
|
22
|
+
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
|
23
|
+
VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
|
24
|
+
BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i
|
25
|
+
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
|
26
|
+
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*;\s*name=(#{VALUE})/ni
|
27
|
+
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
|
28
|
+
# Updated definitions from RFC 2231
|
29
|
+
ATTRIBUTE_CHAR = %r{[^ \t\v\n\r)(><@,;:\\"/\[\]?='*%]}
|
30
|
+
ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
|
31
|
+
SECTION = /\*[0-9]+/
|
32
|
+
REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
|
33
|
+
REGULAR_PARAMETER = /(#{REGULAR_PARAMETER_NAME})=(#{VALUE})/
|
34
|
+
EXTENDED_OTHER_NAME = /#{ATTRIBUTE}\*[1-9][0-9]*\*/
|
35
|
+
EXTENDED_OTHER_VALUE = /%[0-9a-fA-F]{2}|#{ATTRIBUTE_CHAR}/
|
36
|
+
EXTENDED_OTHER_PARAMETER = /(#{EXTENDED_OTHER_NAME})=(#{EXTENDED_OTHER_VALUE}*)/
|
37
|
+
EXTENDED_INITIAL_NAME = /#{ATTRIBUTE}(?:\*0)?\*/
|
38
|
+
EXTENDED_INITIAL_VALUE = /[a-zA-Z0-9\-]*'[a-zA-Z0-9\-]*'#{EXTENDED_OTHER_VALUE}*/
|
39
|
+
EXTENDED_INITIAL_PARAMETER = /(#{EXTENDED_INITIAL_NAME})=(#{EXTENDED_INITIAL_VALUE})/
|
40
|
+
EXTENDED_PARAMETER = /#{EXTENDED_INITIAL_PARAMETER}|#{EXTENDED_OTHER_PARAMETER}/
|
41
|
+
DISPPARM = /;\s*(?:#{REGULAR_PARAMETER}|#{EXTENDED_PARAMETER})\s*/
|
42
|
+
RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
|
11
43
|
|
44
|
+
class Parser
|
12
45
|
BUFSIZE = 1_048_576
|
13
46
|
TEXT_PLAIN = "text/plain"
|
14
47
|
TEMPFILE_FACTORY = lambda { |filename, content_type|
|
15
48
|
Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
|
16
49
|
}
|
17
50
|
|
18
|
-
BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
|
19
|
-
|
20
51
|
class BoundedIO # :nodoc:
|
21
52
|
def initialize(io, content_length)
|
22
53
|
@io = io
|
@@ -38,16 +69,12 @@ module Rack
|
|
38
69
|
if str
|
39
70
|
@cursor += str.bytesize
|
40
71
|
else
|
41
|
-
# Raise an error for mismatching
|
72
|
+
# Raise an error for mismatching content-length and actual contents
|
42
73
|
raise EOFError, "bad content body"
|
43
74
|
end
|
44
75
|
|
45
76
|
str
|
46
77
|
end
|
47
|
-
|
48
|
-
def rewind
|
49
|
-
@io.rewind
|
50
|
-
end
|
51
78
|
end
|
52
79
|
|
53
80
|
MultipartInfo = Struct.new :params, :tmp_files
|
@@ -66,18 +93,17 @@ module Rack
|
|
66
93
|
boundary = parse_boundary content_type
|
67
94
|
return EMPTY unless boundary
|
68
95
|
|
96
|
+
if boundary.length > 70
|
97
|
+
# RFC 1521 Section 7.2.1 imposes a 70 character maximum for the boundary.
|
98
|
+
# Most clients use no more than 55 characters.
|
99
|
+
raise Error, "multipart boundary size too large (#{boundary.length} characters)"
|
100
|
+
end
|
101
|
+
|
69
102
|
io = BoundedIO.new(io, content_length) if content_length
|
70
|
-
outbuf = String.new
|
71
103
|
|
72
104
|
parser = new(boundary, tmpfile, bufsize, qp)
|
73
|
-
parser.
|
105
|
+
parser.parse(io)
|
74
106
|
|
75
|
-
loop do
|
76
|
-
break if parser.state == :DONE
|
77
|
-
parser.on_read io.read(bufsize, outbuf)
|
78
|
-
end
|
79
|
-
|
80
|
-
io.rewind
|
81
107
|
parser.result
|
82
108
|
end
|
83
109
|
|
@@ -167,32 +193,46 @@ module Rack
|
|
167
193
|
def initialize(boundary, tempfile, bufsize, query_parser)
|
168
194
|
@query_parser = query_parser
|
169
195
|
@params = query_parser.make_params
|
170
|
-
@boundary = "--#{boundary}"
|
171
196
|
@bufsize = bufsize
|
172
197
|
|
173
|
-
@full_boundary = @boundary
|
174
|
-
@end_boundary = @boundary + '--'
|
175
198
|
@state = :FAST_FORWARD
|
176
199
|
@mime_index = 0
|
177
200
|
@collector = Collector.new tempfile
|
178
201
|
|
179
202
|
@sbuf = StringScanner.new("".dup)
|
180
|
-
@body_regex = /(?:#{EOL})
|
181
|
-
@rx_max_size =
|
203
|
+
@body_regex = /(?:#{EOL}|\A)--#{Regexp.quote(boundary)}(?:#{EOL}|--)/m
|
204
|
+
@rx_max_size = boundary.bytesize + 6 # (\r\n-- at start, either \r\n or -- at finish)
|
182
205
|
@head_regex = /(.*?#{EOL})#{EOL}/m
|
183
206
|
end
|
184
207
|
|
185
|
-
def
|
186
|
-
|
187
|
-
|
188
|
-
|
208
|
+
def parse(io)
|
209
|
+
outbuf = String.new
|
210
|
+
read_data(io, outbuf)
|
211
|
+
|
212
|
+
loop do
|
213
|
+
status =
|
214
|
+
case @state
|
215
|
+
when :FAST_FORWARD
|
216
|
+
handle_fast_forward
|
217
|
+
when :CONSUME_TOKEN
|
218
|
+
handle_consume_token
|
219
|
+
when :MIME_HEAD
|
220
|
+
handle_mime_head
|
221
|
+
when :MIME_BODY
|
222
|
+
handle_mime_body
|
223
|
+
else # when :DONE
|
224
|
+
return
|
225
|
+
end
|
226
|
+
|
227
|
+
read_data(io, outbuf) if status == :want_read
|
228
|
+
end
|
189
229
|
end
|
190
230
|
|
191
231
|
def result
|
192
232
|
@collector.each do |part|
|
193
233
|
part.get_data do |data|
|
194
234
|
tag_multipart_encoding(part.filename, part.content_type, part.name, data)
|
195
|
-
@query_parser.normalize_params(@params, part.name, data
|
235
|
+
@query_parser.normalize_params(@params, part.name, data)
|
196
236
|
end
|
197
237
|
end
|
198
238
|
MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
|
@@ -200,29 +240,38 @@ module Rack
|
|
200
240
|
|
201
241
|
private
|
202
242
|
|
203
|
-
def
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
break if handle_fast_forward == :want_read
|
208
|
-
when :CONSUME_TOKEN
|
209
|
-
break if handle_consume_token == :want_read
|
210
|
-
when :MIME_HEAD
|
211
|
-
break if handle_mime_head == :want_read
|
212
|
-
when :MIME_BODY
|
213
|
-
break if handle_mime_body == :want_read
|
214
|
-
when :DONE
|
215
|
-
break
|
216
|
-
end
|
217
|
-
end
|
243
|
+
def dequote(str) # From WEBrick::HTTPUtils
|
244
|
+
ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
|
245
|
+
ret.gsub!(/\\(.)/, "\\1")
|
246
|
+
ret
|
218
247
|
end
|
219
248
|
|
249
|
+
def read_data(io, outbuf)
|
250
|
+
content = io.read(@bufsize, outbuf)
|
251
|
+
handle_empty_content!(content)
|
252
|
+
@sbuf.concat(content)
|
253
|
+
end
|
254
|
+
|
255
|
+
# This handles the initial parser state. We read until we find the starting
|
256
|
+
# boundary, then we can transition to the next state. If we find the ending
|
257
|
+
# boundary, this is an invalid multipart upload, but keep scanning for opening
|
258
|
+
# boundary in that case. If no boundary found, we need to keep reading data
|
259
|
+
# and retry. It's highly unlikely the initial read will not consume the
|
260
|
+
# boundary. The client would have to deliberately craft a response
|
261
|
+
# with the opening boundary beyond the buffer size for that to happen.
|
220
262
|
def handle_fast_forward
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
263
|
+
while true
|
264
|
+
case consume_boundary
|
265
|
+
when :BOUNDARY
|
266
|
+
# found opening boundary, transition to next state
|
267
|
+
@state = :MIME_HEAD
|
268
|
+
return
|
269
|
+
when :END_BOUNDARY
|
270
|
+
# invalid multipart upload, but retry for opening boundary
|
271
|
+
else
|
272
|
+
# no boundary found, keep reading data
|
273
|
+
return :want_read
|
274
|
+
end
|
226
275
|
end
|
227
276
|
end
|
228
277
|
|
@@ -241,7 +290,7 @@ module Rack
|
|
241
290
|
head = @sbuf[1]
|
242
291
|
content_type = head[MULTIPART_CONTENT_TYPE, 1]
|
243
292
|
if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
|
244
|
-
name =
|
293
|
+
name = dequote(name)
|
245
294
|
else
|
246
295
|
name = head[MULTIPART_CONTENT_ID, 1]
|
247
296
|
end
|
@@ -278,15 +327,16 @@ module Rack
|
|
278
327
|
end
|
279
328
|
end
|
280
329
|
|
281
|
-
|
282
|
-
|
330
|
+
# Scan until the we find the start or end of the boundary.
|
331
|
+
# If we find it, return the appropriate symbol for the start or
|
332
|
+
# end of the boundary. If we don't find the start or end of the
|
333
|
+
# boundary, clear the buffer and return nil.
|
283
334
|
def consume_boundary
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
return if @sbuf.eos?
|
335
|
+
if read_buffer = @sbuf.scan_until(@body_regex)
|
336
|
+
read_buffer.end_with?(EOL) ? :BOUNDARY : :END_BOUNDARY
|
337
|
+
else
|
338
|
+
@sbuf.terminate
|
339
|
+
nil
|
290
340
|
end
|
291
341
|
end
|
292
342
|
|
@@ -296,10 +346,10 @@ module Rack
|
|
296
346
|
when RFC2183
|
297
347
|
params = Hash[*head.scan(DISPPARM).flat_map(&:compact)]
|
298
348
|
|
299
|
-
if filename = params['filename']
|
300
|
-
filename = $1 if filename =~ /^"(.*)"$/
|
301
|
-
elsif filename = params['filename*']
|
349
|
+
if filename = params['filename*']
|
302
350
|
encoding, _, filename = filename.split("'", 3)
|
351
|
+
elsif filename = params['filename']
|
352
|
+
filename = $1 if filename =~ /^"(.*)"$/
|
303
353
|
end
|
304
354
|
when BROKEN
|
305
355
|
filename = $1
|
@@ -326,6 +376,7 @@ module Rack
|
|
326
376
|
end
|
327
377
|
|
328
378
|
CHARSET = "charset"
|
379
|
+
deprecate_constant :CHARSET
|
329
380
|
|
330
381
|
def tag_multipart_encoding(filename, content_type, name, body)
|
331
382
|
name = name.to_s
|
@@ -346,7 +397,13 @@ module Rack
|
|
346
397
|
k.strip!
|
347
398
|
v.strip!
|
348
399
|
v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
|
349
|
-
|
400
|
+
if k == "charset"
|
401
|
+
encoding = begin
|
402
|
+
Encoding.find v
|
403
|
+
rescue ArgumentError
|
404
|
+
Encoding::BINARY
|
405
|
+
end
|
406
|
+
end
|
350
407
|
end
|
351
408
|
end
|
352
409
|
end
|
@@ -357,7 +414,7 @@ module Rack
|
|
357
414
|
|
358
415
|
def handle_empty_content!(content)
|
359
416
|
if content.nil? || content.empty?
|
360
|
-
raise
|
417
|
+
raise EmptyContentError
|
361
418
|
end
|
362
419
|
end
|
363
420
|
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{[^ \t\v\n\r)(><@,;:\\"/\[\]?='*%]}
|
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
@@ -2,9 +2,7 @@
|
|
2
2
|
|
3
3
|
module Rack
|
4
4
|
class QueryParser
|
5
|
-
|
6
|
-
|
7
|
-
DEFAULT_SEP = /[&;] */n
|
5
|
+
DEFAULT_SEP = /[&] */n
|
8
6
|
COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
|
9
7
|
|
10
8
|
# ParameterTypeError is the error that is raised when incoming structural
|
@@ -20,29 +18,35 @@ module Rack
|
|
20
18
|
# nested over the specified limit.
|
21
19
|
class ParamsTooDeepError < RangeError; end
|
22
20
|
|
23
|
-
def self.make_default(
|
24
|
-
|
21
|
+
def self.make_default(_key_space_limit=(not_deprecated = true; nil), param_depth_limit)
|
22
|
+
unless not_deprecated
|
23
|
+
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)
|
24
|
+
end
|
25
|
+
|
26
|
+
new Params, param_depth_limit
|
25
27
|
end
|
26
28
|
|
27
|
-
attr_reader :
|
29
|
+
attr_reader :param_depth_limit
|
30
|
+
|
31
|
+
def initialize(params_class, _key_space_limit=(not_deprecated = true; nil), param_depth_limit)
|
32
|
+
unless not_deprecated
|
33
|
+
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)
|
34
|
+
end
|
28
35
|
|
29
|
-
def initialize(params_class, key_space_limit, param_depth_limit)
|
30
36
|
@params_class = params_class
|
31
|
-
@key_space_limit = key_space_limit
|
32
37
|
@param_depth_limit = param_depth_limit
|
33
38
|
end
|
34
39
|
|
35
40
|
# 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)
|
41
|
+
# Parses a query string by breaking it up at the '&'. You can also use this
|
42
|
+
# to parse cookies by changing the characters used in the second parameter
|
43
|
+
# (which defaults to '&').
|
44
|
+
def parse_query(qs, separator = nil, &unescaper)
|
41
45
|
unescaper ||= method(:unescape)
|
42
46
|
|
43
47
|
params = make_params
|
44
48
|
|
45
|
-
(qs || '').split(
|
49
|
+
(qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
|
46
50
|
next if p.empty?
|
47
51
|
k, v = p.split('=', 2).map!(&unescaper)
|
48
52
|
|
@@ -65,14 +69,14 @@ module Rack
|
|
65
69
|
# query strings with parameters of conflicting types, in this case a
|
66
70
|
# ParameterTypeError is raised. Users are encouraged to return a 400 in this
|
67
71
|
# case.
|
68
|
-
def parse_nested_query(qs,
|
72
|
+
def parse_nested_query(qs, separator = nil)
|
69
73
|
params = make_params
|
70
74
|
|
71
75
|
unless qs.nil? || qs.empty?
|
72
|
-
(qs || '').split(
|
76
|
+
(qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
|
73
77
|
k, v = p.split('=', 2).map! { |s| unescape(s) }
|
74
78
|
|
75
|
-
|
79
|
+
_normalize_params(params, k, v, 0)
|
76
80
|
end
|
77
81
|
end
|
78
82
|
|
@@ -83,58 +87,89 @@ module Rack
|
|
83
87
|
|
84
88
|
# normalize_params recursively expands parameters into structural types. If
|
85
89
|
# 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 = $' || ''
|
90
|
+
# conflict, a ParameterTypeError is raised. The depth argument is deprecated
|
91
|
+
# and should no longer be used, it is kept for backwards compatibility with
|
92
|
+
# earlier versions of rack.
|
93
|
+
def normalize_params(params, name, v, _depth=nil)
|
94
|
+
_normalize_params(params, name, v, 0)
|
95
|
+
end
|
93
96
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
+
private def _normalize_params(params, name, v, depth)
|
98
|
+
raise ParamsTooDeepError if depth >= param_depth_limit
|
99
|
+
|
100
|
+
if !name
|
101
|
+
# nil name, treat same as empty string (required by tests)
|
102
|
+
k = after = ''
|
103
|
+
elsif depth == 0
|
104
|
+
# Start of parsing, don't treat [] or [ at start of string specially
|
105
|
+
if start = name.index('[', 1)
|
106
|
+
# Start of parameter nesting, use part before brackets as key
|
107
|
+
k = name[0, start]
|
108
|
+
after = name[start, name.length]
|
97
109
|
else
|
98
|
-
|
110
|
+
# Plain parameter with no nesting
|
111
|
+
k = name
|
112
|
+
after = ''
|
99
113
|
end
|
114
|
+
elsif name.start_with?('[]')
|
115
|
+
# Array nesting
|
116
|
+
k = '[]'
|
117
|
+
after = name[2, name.length]
|
118
|
+
elsif name.start_with?('[') && (start = name.index(']', 1))
|
119
|
+
# Hash nesting, use the part inside brackets as the key
|
120
|
+
k = name[1, start-1]
|
121
|
+
after = name[start+1, name.length]
|
122
|
+
else
|
123
|
+
# Probably malformed input, nested but not starting with [
|
124
|
+
# treat full name as key for backwards compatibility.
|
125
|
+
k = name
|
126
|
+
after = ''
|
100
127
|
end
|
101
128
|
|
129
|
+
return if k.empty?
|
130
|
+
|
131
|
+
v ||= String.new
|
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
|
@@ -160,8 +195,7 @@ module Rack
|
|
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
|
|
data/lib/rack/recursive.rb
CHANGED
data/lib/rack/reloader.rb
CHANGED
@@ -22,8 +22,6 @@ module Rack
|
|
22
22
|
# It is performing a check/reload cycle at the start of every request, but
|
23
23
|
# also respects a cool down time, during which nothing will be done.
|
24
24
|
class Reloader
|
25
|
-
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
26
|
-
|
27
25
|
def initialize(app, cooldown = 10, backend = Stat)
|
28
26
|
@app = app
|
29
27
|
@cooldown = cooldown
|