rack 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,49 +3,46 @@
3
3
  require 'strscan'
4
4
 
5
5
  require_relative '../utils'
6
+ require_relative '../bad_request'
6
7
 
7
8
  module Rack
8
9
  module Multipart
9
- class MultipartPartLimitError < Errno::EMFILE; end
10
+ class MultipartPartLimitError < Errno::EMFILE
11
+ include BadRequest
12
+ end
13
+
14
+ class MultipartTotalPartLimitError < StandardError
15
+ include BadRequest
16
+ end
10
17
 
11
18
  # Use specific error class when parsing multipart request
12
19
  # that ends early.
13
- class EmptyContentError < ::EOFError; end
20
+ class EmptyContentError < ::EOFError
21
+ include BadRequest
22
+ end
14
23
 
15
24
  # Base class for multipart exceptions that do not subclass from
16
25
  # other exception classes for backwards compatibility.
17
- class Error < StandardError; end
26
+ class BoundaryTooLongError < StandardError
27
+ include BadRequest
28
+ end
29
+
30
+ # Prefer to use the BoundaryTooLongError class or Rack::BadRequest.
31
+ Error = BoundaryTooLongError
18
32
 
19
33
  EOL = "\r\n"
20
34
  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
35
  MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
26
- MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*;\s*name=(#{VALUE})/ni
36
+ MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:(.*)(?=#{EOL}(\S|\z))/ni
27
37
  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
43
38
 
44
39
  class Parser
45
40
  BUFSIZE = 1_048_576
46
41
  TEXT_PLAIN = "text/plain"
47
42
  TEMPFILE_FACTORY = lambda { |filename, content_type|
48
- Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
43
+ extension = ::File.extname(filename.gsub("\0", '%00'))[0, 129]
44
+
45
+ Tempfile.new(["RackMultipart", extension])
49
46
  }
50
47
 
51
48
  class BoundedIO # :nodoc:
@@ -96,7 +93,7 @@ module Rack
96
93
  if boundary.length > 70
97
94
  # RFC 1521 Section 7.2.1 imposes a 70 character maximum for the boundary.
98
95
  # Most clients use no more than 55 characters.
99
- raise Error, "multipart boundary size too large (#{boundary.length} characters)"
96
+ raise BoundaryTooLongError, "multipart boundary size too large (#{boundary.length} characters)"
100
97
  end
101
98
 
102
99
  io = BoundedIO.new(io, content_length) if content_length
@@ -166,7 +163,7 @@ module Rack
166
163
 
167
164
  @mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
168
165
 
169
- check_open_files
166
+ check_part_limits
170
167
  end
171
168
 
172
169
  def on_mime_body(mime_index, content)
@@ -178,13 +175,23 @@ module Rack
178
175
 
179
176
  private
180
177
 
181
- def check_open_files
182
- if Utils.multipart_part_limit > 0
183
- if @open_files >= Utils.multipart_part_limit
178
+ def check_part_limits
179
+ file_limit = Utils.multipart_file_limit
180
+ part_limit = Utils.multipart_total_part_limit
181
+
182
+ if file_limit && file_limit > 0
183
+ if @open_files >= file_limit
184
184
  @mime_parts.each(&:close)
185
185
  raise MultipartPartLimitError, 'Maximum file multiparts in content reached'
186
186
  end
187
187
  end
188
+
189
+ if part_limit && part_limit > 0
190
+ if @mime_parts.size >= part_limit
191
+ @mime_parts.each(&:close)
192
+ raise MultipartTotalPartLimitError, 'Maximum total multiparts in content reached'
193
+ end
194
+ end
188
195
  end
189
196
  end
190
197
 
@@ -201,6 +208,8 @@ module Rack
201
208
 
202
209
  @sbuf = StringScanner.new("".dup)
203
210
  @body_regex = /(?:#{EOL}|\A)--#{Regexp.quote(boundary)}(?:#{EOL}|--)/m
211
+ @body_regex_at_end = /#{@body_regex}\z/m
212
+ @end_boundary_size = boundary.bytesize + 4 # (-- at start, -- at finish)
204
213
  @rx_max_size = boundary.bytesize + 6 # (\r\n-- at start, either \r\n or -- at finish)
205
214
  @head_regex = /(.*?#{EOL})#{EOL}/m
206
215
  end
@@ -267,7 +276,14 @@ module Rack
267
276
  @state = :MIME_HEAD
268
277
  return
269
278
  when :END_BOUNDARY
270
- # invalid multipart upload, but retry for opening boundary
279
+ # invalid multipart upload
280
+ if @sbuf.pos == @end_boundary_size && @sbuf.rest == EOL
281
+ # stop parsing a buffer if a buffer is only an end boundary.
282
+ @state = :DONE
283
+ return
284
+ end
285
+
286
+ # retry for opening boundary
271
287
  else
272
288
  # no boundary found, keep reading data
273
289
  return :want_read
@@ -285,17 +301,102 @@ module Rack
285
301
  end
286
302
  end
287
303
 
304
+ CONTENT_DISPOSITION_MAX_PARAMS = 16
305
+ CONTENT_DISPOSITION_MAX_BYTES = 1536
288
306
  def handle_mime_head
289
307
  if @sbuf.scan_until(@head_regex)
290
308
  head = @sbuf[1]
291
309
  content_type = head[MULTIPART_CONTENT_TYPE, 1]
292
- if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
293
- name = dequote(name)
310
+ if (disposition = head[MULTIPART_CONTENT_DISPOSITION, 1]) &&
311
+ disposition.bytesize <= CONTENT_DISPOSITION_MAX_BYTES
312
+
313
+ # ignore actual content-disposition value (should always be form-data)
314
+ i = disposition.index(';')
315
+ disposition.slice!(0, i+1)
316
+ param = nil
317
+ num_params = 0
318
+
319
+ # Parse parameter list
320
+ while i = disposition.index('=')
321
+ # Only parse up to max parameters, to avoid potential denial of service
322
+ num_params += 1
323
+ break if num_params > CONTENT_DISPOSITION_MAX_PARAMS
324
+
325
+ # Found end of parameter name, ensure forward progress in loop
326
+ param = disposition.slice!(0, i+1)
327
+
328
+ # Remove ending equals and preceding whitespace from parameter name
329
+ param.chomp!('=')
330
+ param.lstrip!
331
+
332
+ if disposition[0] == '"'
333
+ # Parameter value is quoted, parse it, handling backslash escapes
334
+ disposition.slice!(0, 1)
335
+ value = String.new
336
+
337
+ while i = disposition.index(/(["\\])/)
338
+ c = $1
339
+
340
+ # Append all content until ending quote or escape
341
+ value << disposition.slice!(0, i)
342
+
343
+ # Remove either backslash or ending quote,
344
+ # ensures forward progress in loop
345
+ disposition.slice!(0, 1)
346
+
347
+ # stop parsing parameter value if found ending quote
348
+ break if c == '"'
349
+
350
+ escaped_char = disposition.slice!(0, 1)
351
+ if param == 'filename' && escaped_char != '"'
352
+ # Possible IE uploaded filename, append both escape backslash and value
353
+ value << c << escaped_char
354
+ else
355
+ # Other only append escaped value
356
+ value << escaped_char
357
+ end
358
+ end
359
+ else
360
+ if i = disposition.index(';')
361
+ # Parameter value unquoted (which may be invalid), value ends at semicolon
362
+ value = disposition.slice!(0, i)
363
+ else
364
+ # If no ending semicolon, assume remainder of line is value and stop
365
+ # parsing
366
+ disposition.strip!
367
+ value = disposition
368
+ disposition = ''
369
+ end
370
+ end
371
+
372
+ case param
373
+ when 'name'
374
+ name = value
375
+ when 'filename'
376
+ filename = value
377
+ when 'filename*'
378
+ filename_star = value
379
+ # else
380
+ # ignore other parameters
381
+ end
382
+
383
+ # skip trailing semicolon, to proceed to next parameter
384
+ if i = disposition.index(';')
385
+ disposition.slice!(0, i+1)
386
+ end
387
+ end
294
388
  else
295
389
  name = head[MULTIPART_CONTENT_ID, 1]
296
390
  end
297
391
 
298
- filename = get_filename(head)
392
+ if filename_star
393
+ encoding, _, filename = filename_star.split("'", 3)
394
+ filename = normalize_filename(filename || '')
395
+ filename.force_encoding(find_encoding(encoding))
396
+ elsif filename
397
+ filename = $1 if filename =~ /^"(.*)"$/
398
+ filename = normalize_filename(filename)
399
+ end
299
400
 
300
401
  if name.nil? || name.empty?
301
402
  name = filename || "#{content_type || TEXT_PLAIN}[]".dup
@@ -310,7 +411,7 @@ module Rack
310
411
 
311
412
  def handle_mime_body
312
413
  if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
313
- body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string
414
+ body = body_with_boundary.sub(@body_regex_at_end, '') # remove the boundary from the string
314
415
  @collector.on_mime_body @mime_index, body
315
416
  @sbuf.pos += body.length + 2 # skip \r\n after the content
316
417
  @state = :CONSUME_TOKEN
@@ -340,39 +441,14 @@ module Rack
340
441
  end
341
442
  end
342
443
 
343
- def get_filename(head)
344
- filename = nil
345
- case head
346
- when RFC2183
347
- params = Hash[*head.scan(DISPPARM).flat_map(&:compact)]
348
-
349
- if filename = params['filename*']
350
- encoding, _, filename = filename.split("'", 3)
351
- elsif filename = params['filename']
352
- filename = $1 if filename =~ /^"(.*)"$/
353
- end
354
- when BROKEN
355
- filename = $1
356
- filename = $1 if filename =~ /^"(.*)"$/
357
- end
358
-
359
- return unless filename
360
-
444
+ def normalize_filename(filename)
361
445
  if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
362
446
  filename = Utils.unescape_path(filename)
363
447
  end
364
448
 
365
449
  filename.scrub!
366
450
 
367
- if filename !~ /\\[^\\"]/
368
- filename = filename.gsub(/\\(.)/, '\1')
369
- end
370
-
371
- if encoding
372
- filename.force_encoding ::Encoding.find(encoding)
373
- end
374
-
375
- filename
451
+ filename.split(/[\/\\]/).last || String.new
376
452
  end
377
453
 
378
454
  CHARSET = "charset"
@@ -398,11 +474,7 @@ module Rack
398
474
  v.strip!
399
475
  v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
400
476
  if k == "charset"
401
- encoding = begin
402
- Encoding.find v
403
- rescue ArgumentError
404
- Encoding::BINARY
405
- end
477
+ encoding = find_encoding(v)
406
478
  end
407
479
  end
408
480
  end
@@ -412,6 +484,15 @@ module Rack
412
484
  body.force_encoding(encoding)
413
485
  end
414
486
 
487
+ # Return the related Encoding object. However, because
488
+ # enc is submitted by the user, it may be invalid, so
489
+ # use a binary encoding in that case.
490
+ def find_encoding(enc)
491
+ Encoding.find enc
492
+ rescue ArgumentError
493
+ Encoding::BINARY
494
+ end
495
+
415
496
  def handle_empty_content!(content)
416
497
  if content.nil? || content.empty?
417
498
  raise EmptyContentError
@@ -6,6 +6,8 @@ require_relative 'utils'
6
6
  require_relative 'multipart/parser'
7
7
  require_relative 'multipart/generator'
8
8
 
9
+ require_relative 'bad_request'
10
+
9
11
  module Rack
10
12
  # A multipart form data parser, adapted from IOWA.
11
13
  #
@@ -13,9 +15,40 @@ module Rack
13
15
  module Multipart
14
16
  MULTIPART_BOUNDARY = "AaB03x"
15
17
 
18
+ class MissingInputError < StandardError
19
+ include BadRequest
20
+ end
21
+
22
+ # Accumulator for multipart form data, conforming to the QueryParser API.
23
+ # In future, the Parser could return the pair list directly, but that would
24
+ # change its API.
25
+ class ParamList # :nodoc:
26
+ def self.make_params
27
+ new
28
+ end
29
+
30
+ def self.normalize_params(params, key, value)
31
+ params << [key, value]
32
+ end
33
+
34
+ def initialize
35
+ @pairs = []
36
+ end
37
+
38
+ def <<(pair)
39
+ @pairs << pair
40
+ end
41
+
42
+ def to_params_hash
43
+ @pairs
44
+ end
45
+ end
46
+
16
47
  class << self
17
48
  def parse_multipart(env, params = Rack::Utils.default_query_parser)
18
- io = env[RACK_INPUT]
49
+ unless io = env[RACK_INPUT]
50
+ raise MissingInputError, "Missing input stream!"
51
+ end
19
52
 
20
53
  if content_length = env['CONTENT_LENGTH']
21
54
  content_length = content_length.to_i
@@ -1,38 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'bad_request'
4
+ require 'uri'
5
+
3
6
  module Rack
4
7
  class QueryParser
5
- DEFAULT_SEP = /[&] */n
6
- COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
8
+ DEFAULT_SEP = /& */n
9
+ COMMON_SEP = { ";" => /; */n, ";," => /[;,] */n, "&" => /& */n }
7
10
 
8
11
  # ParameterTypeError is the error that is raised when incoming structural
9
12
  # parameters (parsed by parse_nested_query) contain conflicting types.
10
- class ParameterTypeError < TypeError; end
13
+ class ParameterTypeError < TypeError
14
+ include BadRequest
15
+ end
11
16
 
12
17
  # InvalidParameterError is the error that is raised when incoming structural
13
18
  # parameters (parsed by parse_nested_query) contain invalid format or byte
14
19
  # sequence.
15
- class InvalidParameterError < ArgumentError; end
20
+ class InvalidParameterError < ArgumentError
21
+ include BadRequest
22
+ end
16
23
 
17
24
  # ParamsTooDeepError is the error that is raised when params are recursively
18
25
  # 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
26
+ class ParamsTooDeepError < RangeError
27
+ include BadRequest
28
+ end
25
29
 
30
+ def self.make_default(param_depth_limit)
26
31
  new Params, param_depth_limit
27
32
  end
28
33
 
29
34
  attr_reader :param_depth_limit
30
35
 
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
35
-
36
+ def initialize(params_class, param_depth_limit)
36
37
  @params_class = params_class
37
38
  @param_depth_limit = param_depth_limit
38
39
  end
@@ -128,8 +129,6 @@ module Rack
128
129
 
129
130
  return if k.empty?
130
131
 
131
- v ||= String.new
132
-
133
132
  if after == ''
134
133
  if k == '[]' && depth != 0
135
134
  return [v]
@@ -190,63 +189,11 @@ module Rack
190
189
  true
191
190
  end
192
191
 
193
- def unescape(s)
194
- Utils.unescape(s)
192
+ def unescape(string, encoding = Encoding::UTF_8)
193
+ URI.decode_www_form_component(string, encoding)
195
194
  end
196
195
 
197
- class Params
198
- def initialize
199
- @size = 0
200
- @params = {}
201
- end
202
-
203
- def [](key)
204
- @params[key]
205
- end
206
-
207
- def []=(key, value)
208
- @params[key] = value
209
- end
210
-
211
- def key?(key)
212
- @params.key?(key)
213
- end
214
-
215
- # Recursively unwraps nested `Params` objects and constructs an object
216
- # of the same shape, but using the objects' internal representations
217
- # (Ruby hashes) in place of the objects. The result is a hash consisting
218
- # purely of Ruby primitives.
219
- #
220
- # Mutation warning!
221
- #
222
- # 1. This method mutates the internal representation of the `Params`
223
- # objects in order to save object allocations.
224
- #
225
- # 2. The value you get back is a reference to the internal hash
226
- # representation, not a copy.
227
- #
228
- # 3. Because the `Params` object's internal representation is mutable
229
- # through the `#[]=` method, it is not thread safe. The result of
230
- # getting the hash representation while another thread is adding a
231
- # key to it is non-deterministic.
232
- #
233
- def to_h
234
- @params.each do |key, value|
235
- case value
236
- when self
237
- # Handle circular references gracefully.
238
- @params[key] = @params
239
- when Params
240
- @params[key] = value.to_h
241
- when Array
242
- value.map! { |v| v.kind_of?(Params) ? v.to_h : v }
243
- else
244
- # Ignore anything that is not a `Params` object or
245
- # a collection that can contain one.
246
- end
247
- end
248
- @params
249
- end
196
+ class Params < Hash
250
197
  alias_method :to_params_hash, :to_h
251
198
  end
252
199
  end
data/lib/rack/request.rb CHANGED
@@ -44,7 +44,7 @@ module Rack
44
44
  @x_forwarded_proto_priority = [:proto, :scheme]
45
45
 
46
46
  valid_ipv4_octet = /\.(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])/
47
-
47
+
48
48
  trusted_proxies = Regexp.union(
49
49
  /\A127#{valid_ipv4_octet}{3}\z/, # localhost IPv4 range 127.x.x.x, per RFC-3330
50
50
  /\A::1\z/, # localhost IPv6 ::1
@@ -54,7 +54,7 @@ module Rack
54
54
  /\A192\.168#{valid_ipv4_octet}{2}\z/, # private IPv4 range 192.168.x.x
55
55
  /\Alocalhost\z|\Aunix(\z|:)/i, # localhost hostname, and unix domain sockets
56
56
  )
57
-
57
+
58
58
  self.ip_filter = lambda { |ip| trusted_proxies.match?(ip) }
59
59
 
60
60
  ALLOWED_SCHEMES = %w(https http wss ws).freeze
@@ -482,9 +482,14 @@ module Rack
482
482
 
483
483
  # Returns the data received in the query string.
484
484
  def GET
485
- if get_header(RACK_REQUEST_QUERY_STRING) == query_string
485
+ rr_query_string = get_header(RACK_REQUEST_QUERY_STRING)
486
+ query_string = self.query_string
487
+ if rr_query_string == query_string
486
488
  get_header(RACK_REQUEST_QUERY_HASH)
487
489
  else
490
+ if rr_query_string
491
+ warn "query string used for GET parsing different from current query string. Starting in Rack 3.2, Rack will used the cached GET value instead of parsing the current query string.", uplevel: 1
492
+ end
488
493
  query_hash = parse_query(query_string, '&')
489
494
  set_header(RACK_REQUEST_QUERY_STRING, query_string)
490
495
  set_header(RACK_REQUEST_QUERY_HASH, query_hash)
@@ -496,26 +501,52 @@ module Rack
496
501
  # This method support both application/x-www-form-urlencoded and
497
502
  # multipart/form-data.
498
503
  def POST
499
- if get_header(RACK_INPUT).nil?
500
- raise "Missing rack.input"
501
- elsif get_header(RACK_REQUEST_FORM_INPUT) == get_header(RACK_INPUT)
502
- get_header(RACK_REQUEST_FORM_HASH)
503
- elsif form_data? || parseable_data?
504
- unless set_header(RACK_REQUEST_FORM_HASH, parse_multipart)
505
- form_vars = get_header(RACK_INPUT).read
506
-
507
- # Fix for Safari Ajax postings that always append \0
508
- # form_vars.sub!(/\0\z/, '') # performance replacement:
509
- form_vars.slice!(-1) if form_vars.end_with?("\0")
510
-
511
- set_header RACK_REQUEST_FORM_VARS, form_vars
512
- set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
504
+ if error = get_header(RACK_REQUEST_FORM_ERROR)
505
+ raise error.class, error.message, cause: error.cause
506
+ end
507
+
508
+ begin
509
+ rack_input = get_header(RACK_INPUT)
510
+
511
+ # If the form hash was already memoized:
512
+ if form_hash = get_header(RACK_REQUEST_FORM_HASH)
513
+ form_input = get_header(RACK_REQUEST_FORM_INPUT)
514
+ # And it was memoized from the same input:
515
+ if form_input.equal?(rack_input)
516
+ return form_hash
517
+ elsif form_input
518
+ warn "input stream used for POST parsing different from current input stream. Starting in Rack 3.2, Rack will used the cached POST value instead of parsing the current input stream.", uplevel: 1
519
+ end
513
520
  end
514
- set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
515
- get_header RACK_REQUEST_FORM_HASH
516
- else
517
- set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
518
- set_header(RACK_REQUEST_FORM_HASH, {})
521
+
522
+ # Otherwise, figure out how to parse the input:
523
+ if rack_input.nil?
524
+ set_header RACK_REQUEST_FORM_INPUT, nil
525
+ set_header(RACK_REQUEST_FORM_HASH, {})
526
+ elsif form_data? || parseable_data?
527
+ if pairs = Rack::Multipart.parse_multipart(env, Rack::Multipart::ParamList)
528
+ set_header RACK_REQUEST_FORM_PAIRS, pairs
529
+ set_header RACK_REQUEST_FORM_HASH, expand_param_pairs(pairs)
530
+ else
531
+ form_vars = get_header(RACK_INPUT).read
532
+
533
+ # Fix for Safari Ajax postings that always append \0
534
+ # form_vars.sub!(/\0\z/, '') # performance replacement:
535
+ form_vars.slice!(-1) if form_vars.end_with?("\0")
536
+
537
+ set_header RACK_REQUEST_FORM_VARS, form_vars
538
+ set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
539
+ end
540
+
541
+ set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
542
+ get_header RACK_REQUEST_FORM_HASH
543
+ else
544
+ set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
545
+ set_header(RACK_REQUEST_FORM_HASH, {})
546
+ end
547
+ rescue => error
548
+ set_header(RACK_REQUEST_FORM_ERROR, error)
549
+ raise
519
550
  end
520
551
  end
521
552
 
@@ -585,24 +616,10 @@ module Rack
585
616
  Rack::Request.ip_filter.call(ip)
586
617
  end
587
618
 
588
- # shortcut for <tt>request.params[key]</tt>
589
- def [](key)
590
- warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead", uplevel: 1)
591
-
592
- params[key.to_s]
593
- end
594
-
595
- # shortcut for <tt>request.params[key] = value</tt>
596
- #
597
- # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
598
- def []=(key, value)
599
- warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead", uplevel: 1)
600
-
601
- params[key.to_s] = value
602
- end
603
-
604
619
  # like Hash#values_at
605
620
  def values_at(*keys)
621
+ warn("Request#values_at is deprecated and will be removed in a future version of Rack. Please use request.params.values_at instead", uplevel: 1)
622
+
606
623
  keys.map { |key| params[key] }
607
624
  end
608
625
 
@@ -652,6 +669,16 @@ module Rack
652
669
  Rack::Multipart.extract_multipart(self, query_parser)
653
670
  end
654
671
 
672
+ def expand_param_pairs(pairs, query_parser = query_parser())
673
+ params = query_parser.make_params
674
+
675
+ pairs.each do |k, v|
676
+ query_parser.normalize_params(params, k, v)
677
+ end
678
+
679
+ params.to_params_hash
680
+ end
681
+
655
682
  def split_header(value)
656
683
  value ? value.strip.split(/[,\s]+/) : []
657
684
  end