rack 2.2.3.1 → 3.0.0.rc1

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.

Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +174 -69
  3. data/CONTRIBUTING.md +53 -47
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +293 -0
  6. data/SPEC.rdoc +183 -131
  7. data/lib/rack/auth/abstract/handler.rb +3 -1
  8. data/lib/rack/auth/abstract/request.rb +3 -1
  9. data/lib/rack/auth/digest/md5.rb +1 -131
  10. data/lib/rack/auth/digest/nonce.rb +1 -54
  11. data/lib/rack/auth/digest/params.rb +1 -54
  12. data/lib/rack/auth/digest/request.rb +1 -43
  13. data/lib/rack/auth/digest.rb +256 -0
  14. data/lib/rack/body_proxy.rb +3 -1
  15. data/lib/rack/builder.rb +60 -42
  16. data/lib/rack/cascade.rb +2 -0
  17. data/lib/rack/chunked.rb +16 -13
  18. data/lib/rack/common_logger.rb +23 -18
  19. data/lib/rack/conditional_get.rb +18 -15
  20. data/lib/rack/constants.rb +63 -0
  21. data/lib/rack/content_length.rb +12 -16
  22. data/lib/rack/content_type.rb +8 -5
  23. data/lib/rack/deflater.rb +40 -26
  24. data/lib/rack/directory.rb +9 -3
  25. data/lib/rack/etag.rb +14 -21
  26. data/lib/rack/events.rb +4 -0
  27. data/lib/rack/file.rb +2 -0
  28. data/lib/rack/files.rb +15 -17
  29. data/lib/rack/head.rb +9 -8
  30. data/lib/rack/headers.rb +154 -0
  31. data/lib/rack/lint.rb +779 -684
  32. data/lib/rack/lock.rb +2 -5
  33. data/lib/rack/logger.rb +2 -0
  34. data/lib/rack/media_type.rb +1 -1
  35. data/lib/rack/method_override.rb +4 -0
  36. data/lib/rack/mime.rb +8 -0
  37. data/lib/rack/mock.rb +1 -271
  38. data/lib/rack/mock_request.rb +166 -0
  39. data/lib/rack/mock_response.rb +126 -0
  40. data/lib/rack/multipart/generator.rb +7 -5
  41. data/lib/rack/multipart/parser.rb +118 -61
  42. data/lib/rack/multipart/uploaded_file.rb +4 -0
  43. data/lib/rack/multipart.rb +20 -40
  44. data/lib/rack/null_logger.rb +9 -0
  45. data/lib/rack/query_parser.rb +80 -44
  46. data/lib/rack/recursive.rb +2 -0
  47. data/lib/rack/reloader.rb +0 -2
  48. data/lib/rack/request.rb +187 -89
  49. data/lib/rack/response.rb +131 -61
  50. data/lib/rack/rewindable_input.rb +24 -5
  51. data/lib/rack/runtime.rb +7 -6
  52. data/lib/rack/sendfile.rb +30 -25
  53. data/lib/rack/show_exceptions.rb +15 -2
  54. data/lib/rack/show_status.rb +17 -7
  55. data/lib/rack/static.rb +8 -8
  56. data/lib/rack/tempfile_reaper.rb +15 -4
  57. data/lib/rack/urlmap.rb +3 -1
  58. data/lib/rack/utils.rb +199 -170
  59. data/lib/rack/version.rb +9 -4
  60. data/lib/rack.rb +5 -76
  61. metadata +16 -36
  62. data/README.rdoc +0 -306
  63. data/Rakefile +0 -130
  64. data/bin/rackup +0 -5
  65. data/contrib/rack.png +0 -0
  66. data/contrib/rack.svg +0 -150
  67. data/contrib/rack_logo.svg +0 -164
  68. data/contrib/rdoc.css +0 -412
  69. data/example/lobster.ru +0 -6
  70. data/example/protectedlobster.rb +0 -16
  71. data/example/protectedlobster.ru +0 -10
  72. data/lib/rack/core_ext/regexp.rb +0 -14
  73. data/lib/rack/handler/cgi.rb +0 -59
  74. data/lib/rack/handler/fastcgi.rb +0 -100
  75. data/lib/rack/handler/lsws.rb +0 -61
  76. data/lib/rack/handler/scgi.rb +0 -71
  77. data/lib/rack/handler/thin.rb +0 -36
  78. data/lib/rack/handler/webrick.rb +0 -129
  79. data/lib/rack/handler.rb +0 -104
  80. data/lib/rack/lobster.rb +0 -70
  81. data/lib/rack/server.rb +0 -466
  82. data/lib/rack/session/abstract/id.rb +0 -523
  83. data/lib/rack/session/cookie.rb +0 -203
  84. data/lib/rack/session/memcache.rb +0 -10
  85. data/lib/rack/session/pool.rb +0 -85
  86. data/rack.gemspec +0 -46
@@ -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 Parser
10
- (require_relative '../core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
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 Content-Length and actual contents
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.on_read io.read(bufsize, outbuf)
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})?#{Regexp.quote(@boundary)}(?:#{EOL}|--)/m
181
- @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
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 on_read(content)
186
- handle_empty_content!(content)
187
- @sbuf.concat content
188
- run_parser
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, @query_parser.param_depth_limit)
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 run_parser
204
- loop do
205
- case @state
206
- when :FAST_FORWARD
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
- if consume_boundary
222
- @state = :MIME_HEAD
223
- else
224
- raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
225
- :want_read
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 = Rack::Auth::Digest::Params::dequote(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
- def full_boundary; @full_boundary; end
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
- while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
285
- case read_buffer.strip
286
- when full_boundary then return :BOUNDARY
287
- when @end_boundary then return :END_BOUNDARY
288
- end
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
- encoding = Encoding.find v if k == CHARSET
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 EOFError
417
+ raise EmptyContentError
361
418
  end
362
419
  end
363
420
  end
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'tempfile'
4
+ require 'fileutils'
5
+
3
6
  module Rack
4
7
  module Multipart
5
8
  class UploadedFile
9
+
6
10
  # The filename, *not* including the path, of the "uploaded" file
7
11
  attr_reader :original_filename
8
12
 
@@ -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
- extract_multipart Rack::Request.new(env), params
42
- end
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
- def extract_multipart(req, params = Rack::Utils.default_query_parser)
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 = req.get_header(RACK_MULTIPART_TEMPFILE_FACTORY) || Parser::TEMPFILE_FACTORY
51
- bufsize = req.get_header(RACK_MULTIPART_BUFFER_SIZE) || Parser::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 io, content_length, req.get_header('CONTENT_TYPE'), tempfile, bufsize, params
54
- req.set_header(RACK_TEMPFILES, info.tmp_files)
55
- info.params
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
@@ -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
@@ -2,9 +2,7 @@
2
2
 
3
3
  module Rack
4
4
  class QueryParser
5
- (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
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
@@ -16,29 +14,39 @@ module Rack
16
14
  # sequence.
17
15
  class InvalidParameterError < ArgumentError; end
18
16
 
19
- def self.make_default(key_space_limit, param_depth_limit)
20
- new Params, key_space_limit, param_depth_limit
17
+ # ParamsTooDeepError is the error that is raised when params are recursively
18
+ # nested over the specified limit.
19
+ class ParamsTooDeepError < RangeError; end
20
+
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
21
27
  end
22
28
 
23
- attr_reader :key_space_limit, :param_depth_limit
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
24
35
 
25
- def initialize(params_class, key_space_limit, param_depth_limit)
26
36
  @params_class = params_class
27
- @key_space_limit = key_space_limit
28
37
  @param_depth_limit = param_depth_limit
29
38
  end
30
39
 
31
40
  # Stolen from Mongrel, with some small modifications:
32
- # Parses a query string by breaking it up at the '&'
33
- # and ';' characters. You can also use this to parse
34
- # cookies by changing the characters used in the second
35
- # parameter (which defaults to '&;').
36
- 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)
37
45
  unescaper ||= method(:unescape)
38
46
 
39
47
  params = make_params
40
48
 
41
- (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
49
+ (qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
42
50
  next if p.empty?
43
51
  k, v = p.split('=', 2).map!(&unescaper)
44
52
 
@@ -61,14 +69,14 @@ module Rack
61
69
  # query strings with parameters of conflicting types, in this case a
62
70
  # ParameterTypeError is raised. Users are encouraged to return a 400 in this
63
71
  # case.
64
- def parse_nested_query(qs, d = nil)
72
+ def parse_nested_query(qs, separator = nil)
65
73
  params = make_params
66
74
 
67
75
  unless qs.nil? || qs.empty?
68
- (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
76
+ (qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
69
77
  k, v = p.split('=', 2).map! { |s| unescape(s) }
70
78
 
71
- normalize_params(params, k, v, param_depth_limit)
79
+ _normalize_params(params, k, v, 0)
72
80
  end
73
81
  end
74
82
 
@@ -79,58 +87,89 @@ module Rack
79
87
 
80
88
  # normalize_params recursively expands parameters into structural types. If
81
89
  # the structural types represented by two different parameter names are in
82
- # conflict, a ParameterTypeError is raised.
83
- def normalize_params(params, name, v, depth)
84
- raise RangeError if depth <= 0
85
-
86
- name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
87
- k = $1 || ''
88
- 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
89
96
 
90
- if k.empty?
91
- if !v.nil? && name == "[]"
92
- return Array(v)
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]
93
109
  else
94
- return
110
+ # Plain parameter with no nesting
111
+ k = name
112
+ after = ''
95
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 = ''
96
127
  end
97
128
 
129
+ return if k.empty?
130
+
131
+ v ||= String.new
132
+
98
133
  if after == ''
99
- params[k] = v
134
+ if k == '[]' && depth != 0
135
+ return [v]
136
+ else
137
+ params[k] = v
138
+ end
100
139
  elsif after == "["
101
140
  params[name] = v
102
141
  elsif after == "[]"
103
142
  params[k] ||= []
104
143
  raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
105
144
  params[k] << v
106
- elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
107
- child_key = $1
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
108
151
  params[k] ||= []
109
152
  raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
110
153
  if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key)
111
- normalize_params(params[k].last, child_key, v, depth - 1)
154
+ _normalize_params(params[k].last, child_key, v, depth + 1)
112
155
  else
113
- params[k] << normalize_params(make_params, child_key, v, depth - 1)
156
+ params[k] << _normalize_params(make_params, child_key, v, depth + 1)
114
157
  end
115
158
  else
116
159
  params[k] ||= make_params
117
160
  raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
118
- params[k] = normalize_params(params[k], after, v, depth - 1)
161
+ params[k] = _normalize_params(params[k], after, v, depth + 1)
119
162
  end
120
163
 
121
164
  params
122
165
  end
123
166
 
124
167
  def make_params
125
- @params_class.new @key_space_limit
126
- end
127
-
128
- def new_space_limit(key_space_limit)
129
- self.class.new @params_class, key_space_limit, param_depth_limit
168
+ @params_class.new
130
169
  end
131
170
 
132
171
  def new_depth_limit(param_depth_limit)
133
- self.class.new @params_class, key_space_limit, param_depth_limit
172
+ self.class.new @params_class, param_depth_limit
134
173
  end
135
174
 
136
175
  private
@@ -156,8 +195,7 @@ module Rack
156
195
  end
157
196
 
158
197
  class Params
159
- def initialize(limit)
160
- @limit = limit
198
+ def initialize
161
199
  @size = 0
162
200
  @params = {}
163
201
  end
@@ -167,8 +205,6 @@ module Rack
167
205
  end
168
206
 
169
207
  def []=(key, value)
170
- @size += key.size if key && !@params.key?(key)
171
- raise RangeError, 'exceeded available parameter key space' if @size > @limit
172
208
  @params[key] = value
173
209
  end
174
210
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'uri'
4
4
 
5
+ require_relative 'constants'
6
+
5
7
  module Rack
6
8
  # Rack::ForwardRequest gets caught by Rack::Recursive and redirects
7
9
  # the current request to the app at +url+.
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