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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +213 -83
  3. data/CONTRIBUTING.md +53 -47
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +309 -0
  6. data/SPEC.rdoc +174 -126
  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/basic.rb +0 -2
  10. data/lib/rack/auth/digest/md5.rb +1 -131
  11. data/lib/rack/auth/digest/nonce.rb +1 -54
  12. data/lib/rack/auth/digest/params.rb +1 -54
  13. data/lib/rack/auth/digest/request.rb +1 -43
  14. data/lib/rack/auth/digest.rb +256 -0
  15. data/lib/rack/body_proxy.rb +3 -1
  16. data/lib/rack/builder.rb +83 -63
  17. data/lib/rack/cascade.rb +2 -0
  18. data/lib/rack/chunked.rb +16 -13
  19. data/lib/rack/common_logger.rb +23 -18
  20. data/lib/rack/conditional_get.rb +18 -15
  21. data/lib/rack/constants.rb +64 -0
  22. data/lib/rack/content_length.rb +12 -16
  23. data/lib/rack/content_type.rb +8 -5
  24. data/lib/rack/deflater.rb +40 -26
  25. data/lib/rack/directory.rb +9 -3
  26. data/lib/rack/etag.rb +14 -23
  27. data/lib/rack/events.rb +4 -0
  28. data/lib/rack/file.rb +2 -0
  29. data/lib/rack/files.rb +15 -17
  30. data/lib/rack/head.rb +9 -8
  31. data/lib/rack/headers.rb +154 -0
  32. data/lib/rack/lint.rb +758 -646
  33. data/lib/rack/lock.rb +2 -5
  34. data/lib/rack/logger.rb +2 -0
  35. data/lib/rack/media_type.rb +9 -4
  36. data/lib/rack/method_override.rb +5 -1
  37. data/lib/rack/mime.rb +8 -0
  38. data/lib/rack/mock.rb +1 -271
  39. data/lib/rack/mock_request.rb +166 -0
  40. data/lib/rack/mock_response.rb +126 -0
  41. data/lib/rack/multipart/generator.rb +7 -5
  42. data/lib/rack/multipart/parser.rb +120 -64
  43. data/lib/rack/multipart/uploaded_file.rb +4 -0
  44. data/lib/rack/multipart.rb +20 -40
  45. data/lib/rack/null_logger.rb +9 -0
  46. data/lib/rack/query_parser.rb +78 -46
  47. data/lib/rack/recursive.rb +2 -0
  48. data/lib/rack/reloader.rb +0 -2
  49. data/lib/rack/request.rb +224 -106
  50. data/lib/rack/response.rb +138 -61
  51. data/lib/rack/rewindable_input.rb +24 -5
  52. data/lib/rack/runtime.rb +7 -6
  53. data/lib/rack/sendfile.rb +30 -25
  54. data/lib/rack/show_exceptions.rb +15 -2
  55. data/lib/rack/show_status.rb +17 -7
  56. data/lib/rack/static.rb +8 -8
  57. data/lib/rack/tempfile_reaper.rb +15 -4
  58. data/lib/rack/urlmap.rb +3 -1
  59. data/lib/rack/utils.rb +208 -178
  60. data/lib/rack/version.rb +9 -4
  61. data/lib/rack.rb +6 -76
  62. metadata +14 -34
  63. data/README.rdoc +0 -320
  64. data/Rakefile +0 -130
  65. data/bin/rackup +0 -5
  66. data/contrib/rack.png +0 -0
  67. data/contrib/rack.svg +0 -150
  68. data/contrib/rack_logo.svg +0 -164
  69. data/contrib/rdoc.css +0 -412
  70. data/example/lobster.ru +0 -6
  71. data/example/protectedlobster.rb +0 -16
  72. data/example/protectedlobster.ru +0 -10
  73. data/lib/rack/core_ext/regexp.rb +0 -14
  74. data/lib/rack/handler/cgi.rb +0 -59
  75. data/lib/rack/handler/fastcgi.rb +0 -100
  76. data/lib/rack/handler/lsws.rb +0 -61
  77. data/lib/rack/handler/scgi.rb +0 -71
  78. data/lib/rack/handler/thin.rb +0 -36
  79. data/lib/rack/handler/webrick.rb +0 -129
  80. data/lib/rack/handler.rb +0 -104
  81. data/lib/rack/lobster.rb +0 -70
  82. data/lib/rack/server.rb +0 -466
  83. data/lib/rack/session/abstract/id.rb +0 -523
  84. data/lib/rack/session/cookie.rb +0 -204
  85. data/lib/rack/session/memcache.rb +0 -10
  86. data/lib/rack/session/pool.rb +0 -85
  87. 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.escape(file.original_filename)}\"" if file.original_filename
79
+ filename = "; filename=\"#{Utils.escape_path(file.original_filename)}\""
78
80
  <<-EOF
79
81
  --#{MULTIPART_BOUNDARY}\r
80
- Content-Disposition: form-data; name="#{name}"#{filename}\r
81
- Content-Type: #{file.content_type}\r
82
- #{"Content-Length: #{length}\r\n" if length}\r
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
- Content-Disposition: form-data; name="#{name}"\r
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 Parser
11
- (require_relative '../core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
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
- extension = ::File.extname(filename.gsub("\0", '%00'))[0, 129]
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 Content-Length and actual contents
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.on_read io.read(bufsize, outbuf)
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})?#{Regexp.quote(@boundary)}(?:#{EOL}|--)/m
194
- @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
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 on_read(content)
199
- handle_empty_content!(content)
200
- @sbuf.concat content
201
- run_parser
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, @query_parser.param_depth_limit)
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 run_parser
217
- loop do
218
- case @state
219
- when :FAST_FORWARD
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
- if consume_boundary
235
- @state = :MIME_HEAD
236
- else
237
- raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
238
- :want_read
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 = Rack::Auth::Digest::Params::dequote(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
- def full_boundary; @full_boundary; end
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
- while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
298
- case read_buffer.strip
299
- when full_boundary then return :BOUNDARY
300
- when @end_boundary then return :END_BOUNDARY
301
- end
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
- encoding = Encoding.find v if k == CHARSET
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 EOFError
429
+ raise EmptyContentError
374
430
  end
375
431
  end
376
432
  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{[^ \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
- 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
@@ -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
- (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
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(key_space_limit, param_depth_limit)
24
- new Params, key_space_limit, param_depth_limit
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 :key_space_limit, :param_depth_limit
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
- # and ';' characters. You can also use this to parse
38
- # cookies by changing the characters used in the second
39
- # parameter (which defaults to '&;').
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(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
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, d = nil)
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(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
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
- normalize_params(params, k, v, param_depth_limit)
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
- def normalize_params(params, name, v, depth)
88
- raise ParamsTooDeepError if depth <= 0
89
-
90
- name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
91
- k = $1 || ''
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
- if k.empty?
95
- if !v.nil? && name == "[]"
96
- return Array(v)
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
- return
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
- params[k] = v
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 =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
111
- 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
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
- normalize_params(params[k].last, child_key, v, depth - 1)
154
+ _normalize_params(params[k].last, child_key, v, depth + 1)
116
155
  else
117
- params[k] << normalize_params(make_params, child_key, v, depth - 1)
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] = normalize_params(params[k], after, v, depth - 1)
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 @key_space_limit
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, key_space_limit, param_depth_limit
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(s)
159
- Utils.unescape(s)
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(limit)
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