rack 3.0.5 → 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,51 +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
10
13
 
11
- class MultipartTotalPartLimitError < StandardError; end
14
+ class MultipartTotalPartLimitError < StandardError
15
+ include BadRequest
16
+ end
12
17
 
13
18
  # Use specific error class when parsing multipart request
14
19
  # that ends early.
15
- class EmptyContentError < ::EOFError; end
20
+ class EmptyContentError < ::EOFError
21
+ include BadRequest
22
+ end
16
23
 
17
24
  # Base class for multipart exceptions that do not subclass from
18
25
  # other exception classes for backwards compatibility.
19
- 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
20
32
 
21
33
  EOL = "\r\n"
22
34
  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
35
  MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
28
- MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s*name=(#{VALUE})/ni
36
+ MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:(.*)(?=#{EOL}(\S|\z))/ni
29
37
  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
45
38
 
46
39
  class Parser
47
40
  BUFSIZE = 1_048_576
48
41
  TEXT_PLAIN = "text/plain"
49
42
  TEMPFILE_FACTORY = lambda { |filename, content_type|
50
- 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])
51
46
  }
52
47
 
53
48
  class BoundedIO # :nodoc:
@@ -98,7 +93,7 @@ module Rack
98
93
  if boundary.length > 70
99
94
  # RFC 1521 Section 7.2.1 imposes a 70 character maximum for the boundary.
100
95
  # Most clients use no more than 55 characters.
101
- raise Error, "multipart boundary size too large (#{boundary.length} characters)"
96
+ raise BoundaryTooLongError, "multipart boundary size too large (#{boundary.length} characters)"
102
97
  end
103
98
 
104
99
  io = BoundedIO.new(io, content_length) if content_length
@@ -213,6 +208,8 @@ module Rack
213
208
 
214
209
  @sbuf = StringScanner.new("".dup)
215
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)
216
213
  @rx_max_size = boundary.bytesize + 6 # (\r\n-- at start, either \r\n or -- at finish)
217
214
  @head_regex = /(.*?#{EOL})#{EOL}/m
218
215
  end
@@ -279,7 +276,14 @@ module Rack
279
276
  @state = :MIME_HEAD
280
277
  return
281
278
  when :END_BOUNDARY
282
- # 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
283
287
  else
284
288
  # no boundary found, keep reading data
285
289
  return :want_read
@@ -297,17 +301,102 @@ module Rack
297
301
  end
298
302
  end
299
303
 
304
+ CONTENT_DISPOSITION_MAX_PARAMS = 16
305
+ CONTENT_DISPOSITION_MAX_BYTES = 1536
300
306
  def handle_mime_head
301
307
  if @sbuf.scan_until(@head_regex)
302
308
  head = @sbuf[1]
303
309
  content_type = head[MULTIPART_CONTENT_TYPE, 1]
304
- if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
305
- 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
306
388
  else
307
389
  name = head[MULTIPART_CONTENT_ID, 1]
308
390
  end
309
391
 
310
- 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
311
400
 
312
401
  if name.nil? || name.empty?
313
402
  name = filename || "#{content_type || TEXT_PLAIN}[]".dup
@@ -322,7 +411,7 @@ module Rack
322
411
 
323
412
  def handle_mime_body
324
413
  if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
325
- 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
326
415
  @collector.on_mime_body @mime_index, body
327
416
  @sbuf.pos += body.length + 2 # skip \r\n after the content
328
417
  @state = :CONSUME_TOKEN
@@ -352,39 +441,14 @@ module Rack
352
441
  end
353
442
  end
354
443
 
355
- def get_filename(head)
356
- filename = nil
357
- case head
358
- when RFC2183
359
- params = Hash[*head.scan(DISPPARM).flat_map(&:compact)]
360
-
361
- if filename = params['filename*']
362
- encoding, _, filename = filename.split("'", 3)
363
- elsif filename = params['filename']
364
- filename = $1 if filename =~ /^"(.*)"$/
365
- end
366
- when BROKEN
367
- filename = $1
368
- filename = $1 if filename =~ /^"(.*)"$/
369
- end
370
-
371
- return unless filename
372
-
444
+ def normalize_filename(filename)
373
445
  if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
374
446
  filename = Utils.unescape_path(filename)
375
447
  end
376
448
 
377
449
  filename.scrub!
378
450
 
379
- if filename !~ /\\[^\\"]/
380
- filename = filename.gsub(/\\(.)/, '\1')
381
- end
382
-
383
- if encoding
384
- filename.force_encoding ::Encoding.find(encoding)
385
- end
386
-
387
- filename
451
+ filename.split(/[\/\\]/).last || String.new
388
452
  end
389
453
 
390
454
  CHARSET = "charset"
@@ -410,11 +474,7 @@ module Rack
410
474
  v.strip!
411
475
  v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
412
476
  if k == "charset"
413
- encoding = begin
414
- Encoding.find v
415
- rescue ArgumentError
416
- Encoding::BINARY
417
- end
477
+ encoding = find_encoding(v)
418
478
  end
419
479
  end
420
480
  end
@@ -424,6 +484,15 @@ module Rack
424
484
  body.force_encoding(encoding)
425
485
  end
426
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
+
427
496
  def handle_empty_content!(content)
428
497
  if content.nil? || content.empty?
429
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,6 +15,10 @@ module Rack
13
15
  module Multipart
14
16
  MULTIPART_BOUNDARY = "AaB03x"
15
17
 
18
+ class MissingInputError < StandardError
19
+ include BadRequest
20
+ end
21
+
16
22
  # Accumulator for multipart form data, conforming to the QueryParser API.
17
23
  # In future, the Parser could return the pair list directly, but that would
18
24
  # change its API.
@@ -40,7 +46,9 @@ module Rack
40
46
 
41
47
  class << self
42
48
  def parse_multipart(env, params = Rack::Utils.default_query_parser)
43
- io = env[RACK_INPUT]
49
+ unless io = env[RACK_INPUT]
50
+ raise MissingInputError, "Missing input stream!"
51
+ end
44
52
 
45
53
  if content_length = env['CONTENT_LENGTH']
46
54
  content_length = content_length.to_i
@@ -1,78 +1,56 @@
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
39
40
 
40
- # Originally stolen from Mongrel, now with some modifications:
41
+ # Stolen from Mongrel, with some small modifications:
41
42
  # Parses a query string by breaking it up at the '&'. You can also use this
42
43
  # to parse cookies by changing the characters used in the second parameter
43
44
  # (which defaults to '&').
44
- #
45
- # Returns an array of 2-element arrays, where the first element is the
46
- # key and the second element is the value.
47
- def split_query(qs, separator = nil, &unescaper)
48
- pairs = []
49
-
50
- if qs && !qs.empty?
51
- unescaper ||= method(:unescape)
52
-
53
- qs.split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
54
- next if p.empty?
55
- pair = p.split('=', 2).map!(&unescaper)
56
- pair << nil if pair.length == 1
57
- pairs << pair
58
- end
59
- end
60
-
61
- pairs
62
- rescue ArgumentError => e
63
- raise InvalidParameterError, e.message, e.backtrace
64
- end
65
-
66
- # Parses a query string by breaking it up at the '&'. You can also use this
67
- # to parse cookies by changing the characters used in the second parameter
68
- # (which defaults to '&').
69
- #
70
- # Returns a hash where each value is a string (when a key only appears once)
71
- # or an array of strings (when a key appears more than once).
72
45
  def parse_query(qs, separator = nil, &unescaper)
46
+ unescaper ||= method(:unescape)
47
+
73
48
  params = make_params
74
49
 
75
- split_query(qs, separator, &unescaper).each do |k, v|
50
+ (qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
51
+ next if p.empty?
52
+ k, v = p.split('=', 2).map!(&unescaper)
53
+
76
54
  if cur = params[k]
77
55
  if cur.class == Array
78
56
  params[k] << v
@@ -84,7 +62,7 @@ module Rack
84
62
  end
85
63
  end
86
64
 
87
- params.to_h
65
+ return params.to_h
88
66
  end
89
67
 
90
68
  # parse_nested_query expands a query string into structural types. Supported
@@ -95,11 +73,17 @@ module Rack
95
73
  def parse_nested_query(qs, separator = nil)
96
74
  params = make_params
97
75
 
98
- split_query(qs, separator).each do |k, v|
99
- _normalize_params(params, k, v, 0)
76
+ unless qs.nil? || qs.empty?
77
+ (qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
78
+ k, v = p.split('=', 2).map! { |s| unescape(s) }
79
+
80
+ _normalize_params(params, k, v, 0)
81
+ end
100
82
  end
101
83
 
102
- params.to_h
84
+ return params.to_h
85
+ rescue ArgumentError => e
86
+ raise InvalidParameterError, e.message, e.backtrace
103
87
  end
104
88
 
105
89
  # normalize_params recursively expands parameters into structural types. If
@@ -145,8 +129,6 @@ module Rack
145
129
 
146
130
  return if k.empty?
147
131
 
148
- v ||= String.new
149
-
150
132
  if after == ''
151
133
  if k == '[]' && depth != 0
152
134
  return [v]
@@ -207,63 +189,11 @@ module Rack
207
189
  true
208
190
  end
209
191
 
210
- def unescape(s)
211
- Utils.unescape(s)
192
+ def unescape(string, encoding = Encoding::UTF_8)
193
+ URI.decode_www_form_component(string, encoding)
212
194
  end
213
195
 
214
- class Params
215
- def initialize
216
- @size = 0
217
- @params = {}
218
- end
219
-
220
- def [](key)
221
- @params[key]
222
- end
223
-
224
- def []=(key, value)
225
- @params[key] = value
226
- end
227
-
228
- def key?(key)
229
- @params.key?(key)
230
- end
231
-
232
- # Recursively unwraps nested `Params` objects and constructs an object
233
- # of the same shape, but using the objects' internal representations
234
- # (Ruby hashes) in place of the objects. The result is a hash consisting
235
- # purely of Ruby primitives.
236
- #
237
- # Mutation warning!
238
- #
239
- # 1. This method mutates the internal representation of the `Params`
240
- # objects in order to save object allocations.
241
- #
242
- # 2. The value you get back is a reference to the internal hash
243
- # representation, not a copy.
244
- #
245
- # 3. Because the `Params` object's internal representation is mutable
246
- # through the `#[]=` method, it is not thread safe. The result of
247
- # getting the hash representation while another thread is adding a
248
- # key to it is non-deterministic.
249
- #
250
- def to_h
251
- @params.each do |key, value|
252
- case value
253
- when self
254
- # Handle circular references gracefully.
255
- @params[key] = @params
256
- when Params
257
- @params[key] = value.to_h
258
- when Array
259
- value.map! { |v| v.kind_of?(Params) ? v.to_h : v }
260
- else
261
- # Ignore anything that is not a `Params` object or
262
- # a collection that can contain one.
263
- end
264
- end
265
- @params
266
- end
196
+ class Params < Hash
267
197
  alias_method :to_params_hash, :to_h
268
198
  end
269
199
  end
data/lib/rack/request.rb CHANGED
@@ -482,23 +482,17 @@ 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
486
- if query_hash = get_header(RACK_REQUEST_QUERY_HASH)
487
- return query_hash
488
- end
489
- end
490
-
491
- set_header(RACK_REQUEST_QUERY_HASH, expand_params(query_param_list))
492
- end
493
-
494
- def query_param_list
495
- if get_header(RACK_REQUEST_QUERY_STRING) == query_string
496
- get_header(RACK_REQUEST_QUERY_PAIRS)
485
+ rr_query_string = get_header(RACK_REQUEST_QUERY_STRING)
486
+ query_string = self.query_string
487
+ if rr_query_string == query_string
488
+ get_header(RACK_REQUEST_QUERY_HASH)
497
489
  else
498
- query_pairs = split_query(query_string, '&')
499
- set_header RACK_REQUEST_QUERY_STRING, query_string
500
- set_header RACK_REQUEST_QUERY_HASH, nil
501
- set_header(RACK_REQUEST_QUERY_PAIRS, query_pairs)
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
493
+ query_hash = parse_query(query_string, '&')
494
+ set_header(RACK_REQUEST_QUERY_STRING, query_string)
495
+ set_header(RACK_REQUEST_QUERY_HASH, query_hash)
502
496
  end
503
497
  end
504
498
 
@@ -507,16 +501,6 @@ module Rack
507
501
  # This method support both application/x-www-form-urlencoded and
508
502
  # multipart/form-data.
509
503
  def POST
510
- if get_header(RACK_REQUEST_FORM_INPUT).equal?(get_header(RACK_INPUT))
511
- if form_hash = get_header(RACK_REQUEST_FORM_HASH)
512
- return form_hash
513
- end
514
- end
515
-
516
- set_header(RACK_REQUEST_FORM_HASH, expand_params(body_param_list))
517
- end
518
-
519
- def body_param_list
520
504
  if error = get_header(RACK_REQUEST_FORM_ERROR)
521
505
  raise error.class, error.message, cause: error.cause
522
506
  end
@@ -524,36 +508,42 @@ module Rack
524
508
  begin
525
509
  rack_input = get_header(RACK_INPUT)
526
510
 
527
- form_pairs = nil
528
-
529
- # If the form data has already been memoized from the same
530
- # input:
531
- if get_header(RACK_REQUEST_FORM_INPUT).equal?(rack_input)
532
- if form_pairs = get_header(RACK_REQUEST_FORM_PAIRS)
533
- return form_pairs
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
534
519
  end
535
520
  end
536
521
 
522
+ # Otherwise, figure out how to parse the input:
537
523
  if rack_input.nil?
538
- form_pairs = []
524
+ set_header RACK_REQUEST_FORM_INPUT, nil
525
+ set_header(RACK_REQUEST_FORM_HASH, {})
539
526
  elsif form_data? || parseable_data?
540
- unless form_pairs = Rack::Multipart.extract_multipart(self, Rack::Multipart::ParamList)
541
- form_vars = rack_input.read
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
542
532
 
543
533
  # Fix for Safari Ajax postings that always append \0
544
534
  # form_vars.sub!(/\0\z/, '') # performance replacement:
545
535
  form_vars.slice!(-1) if form_vars.end_with?("\0")
546
536
 
547
537
  set_header RACK_REQUEST_FORM_VARS, form_vars
548
- form_pairs = split_query(form_vars, '&')
538
+ set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
549
539
  end
540
+
541
+ set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
542
+ get_header RACK_REQUEST_FORM_HASH
550
543
  else
551
- form_pairs = []
544
+ set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
545
+ set_header(RACK_REQUEST_FORM_HASH, {})
552
546
  end
553
-
554
- set_header RACK_REQUEST_FORM_INPUT, rack_input
555
- set_header RACK_REQUEST_FORM_HASH, nil
556
- set_header(RACK_REQUEST_FORM_PAIRS, form_pairs)
557
547
  rescue => error
558
548
  set_header(RACK_REQUEST_FORM_ERROR, error)
559
549
  raise
@@ -626,24 +616,10 @@ module Rack
626
616
  Rack::Request.ip_filter.call(ip)
627
617
  end
628
618
 
629
- # shortcut for <tt>request.params[key]</tt>
630
- def [](key)
631
- warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead", uplevel: 1)
632
-
633
- params[key.to_s]
634
- end
635
-
636
- # shortcut for <tt>request.params[key] = value</tt>
637
- #
638
- # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
639
- def []=(key, value)
640
- warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead", uplevel: 1)
641
-
642
- params[key.to_s] = value
643
- end
644
-
645
619
  # like Hash#values_at
646
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
+
647
623
  keys.map { |key| params[key] }
648
624
  end
649
625
 
@@ -693,23 +669,11 @@ module Rack
693
669
  Rack::Multipart.extract_multipart(self, query_parser)
694
670
  end
695
671
 
696
- def split_query(query, d = '&')
697
- query_parser = query_parser()
698
- unless query_parser.respond_to?(:split_query)
699
- query_parser = Utils.default_query_parser
700
- unless query_parser.respond_to?(:split_query)
701
- query_parser = QueryParser.make_default(0)
702
- end
703
- end
704
-
705
- query_parser.split_query(query, d)
706
- end
707
-
708
- def expand_params(pairs, query_parser = query_parser())
672
+ def expand_param_pairs(pairs, query_parser = query_parser())
709
673
  params = query_parser.make_params
710
674
 
711
- pairs.each do |key, value|
712
- query_parser.normalize_params(params, key, value)
675
+ pairs.each do |k, v|
676
+ query_parser.normalize_params(params, k, v)
713
677
  end
714
678
 
715
679
  params.to_params_hash