rack 3.0.8 → 3.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +140 -2
- data/CONTRIBUTING.md +11 -9
- data/README.md +34 -15
- data/SPEC.rdoc +38 -13
- data/lib/rack/auth/basic.rb +1 -2
- data/lib/rack/bad_request.rb +8 -0
- data/lib/rack/body_proxy.rb +18 -2
- data/lib/rack/builder.rb +23 -10
- data/lib/rack/cascade.rb +0 -3
- data/lib/rack/constants.rb +3 -0
- data/lib/rack/headers.rb +86 -2
- data/lib/rack/lint.rb +118 -34
- data/lib/rack/logger.rb +2 -1
- data/lib/rack/media_type.rb +9 -4
- data/lib/rack/mime.rb +6 -5
- data/lib/rack/mock_request.rb +10 -15
- data/lib/rack/mock_response.rb +14 -16
- data/lib/rack/multipart/parser.rb +131 -63
- data/lib/rack/multipart.rb +34 -1
- data/lib/rack/query_parser.rb +15 -68
- data/lib/rack/request.rb +40 -21
- data/lib/rack/response.rb +29 -19
- data/lib/rack/show_exceptions.rb +6 -2
- data/lib/rack/utils.rb +80 -103
- data/lib/rack/version.rb +1 -14
- data/lib/rack.rb +10 -16
- metadata +4 -10
- data/lib/rack/auth/digest/md5.rb +0 -1
- data/lib/rack/auth/digest/nonce.rb +0 -1
- data/lib/rack/auth/digest/params.rb +0 -1
- data/lib/rack/auth/digest/request.rb +0 -1
- data/lib/rack/auth/digest.rb +0 -256
- data/lib/rack/chunked.rb +0 -120
- data/lib/rack/file.rb +0 -9
@@ -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
|
10
|
+
class MultipartPartLimitError < Errno::EMFILE
|
11
|
+
include BadRequest
|
12
|
+
end
|
10
13
|
|
11
|
-
class MultipartTotalPartLimitError < StandardError
|
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
|
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
|
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:
|
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
|
-
|
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
|
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
|
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,101 @@ 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
|
305
|
-
|
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
|
-
|
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 = normalize_filename(filename)
|
398
|
+
end
|
311
399
|
|
312
400
|
if name.nil? || name.empty?
|
313
401
|
name = filename || "#{content_type || TEXT_PLAIN}[]".dup
|
@@ -322,7 +410,7 @@ module Rack
|
|
322
410
|
|
323
411
|
def handle_mime_body
|
324
412
|
if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
|
325
|
-
body = body_with_boundary.sub(
|
413
|
+
body = body_with_boundary.sub(@body_regex_at_end, '') # remove the boundary from the string
|
326
414
|
@collector.on_mime_body @mime_index, body
|
327
415
|
@sbuf.pos += body.length + 2 # skip \r\n after the content
|
328
416
|
@state = :CONSUME_TOKEN
|
@@ -352,39 +440,14 @@ module Rack
|
|
352
440
|
end
|
353
441
|
end
|
354
442
|
|
355
|
-
def
|
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
|
-
|
443
|
+
def normalize_filename(filename)
|
373
444
|
if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
|
374
445
|
filename = Utils.unescape_path(filename)
|
375
446
|
end
|
376
447
|
|
377
448
|
filename.scrub!
|
378
449
|
|
379
|
-
|
380
|
-
filename = filename.gsub(/\\(.)/, '\1')
|
381
|
-
end
|
382
|
-
|
383
|
-
if encoding
|
384
|
-
filename.force_encoding ::Encoding.find(encoding)
|
385
|
-
end
|
386
|
-
|
387
|
-
filename
|
450
|
+
filename.split(/[\/\\]/).last || String.new
|
388
451
|
end
|
389
452
|
|
390
453
|
CHARSET = "charset"
|
@@ -410,11 +473,7 @@ module Rack
|
|
410
473
|
v.strip!
|
411
474
|
v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
|
412
475
|
if k == "charset"
|
413
|
-
encoding =
|
414
|
-
Encoding.find v
|
415
|
-
rescue ArgumentError
|
416
|
-
Encoding::BINARY
|
417
|
-
end
|
476
|
+
encoding = find_encoding(v)
|
418
477
|
end
|
419
478
|
end
|
420
479
|
end
|
@@ -424,6 +483,15 @@ module Rack
|
|
424
483
|
body.force_encoding(encoding)
|
425
484
|
end
|
426
485
|
|
486
|
+
# Return the related Encoding object. However, because
|
487
|
+
# enc is submitted by the user, it may be invalid, so
|
488
|
+
# use a binary encoding in that case.
|
489
|
+
def find_encoding(enc)
|
490
|
+
Encoding.find enc
|
491
|
+
rescue ArgumentError
|
492
|
+
Encoding::BINARY
|
493
|
+
end
|
494
|
+
|
427
495
|
def handle_empty_content!(content)
|
428
496
|
if content.nil? || content.empty?
|
429
497
|
raise EmptyContentError
|
data/lib/rack/multipart.rb
CHANGED
@@ -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
|
data/lib/rack/query_parser.rb
CHANGED
@@ -1,40 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'bad_request'
|
3
4
|
require 'uri'
|
4
5
|
|
5
6
|
module Rack
|
6
7
|
class QueryParser
|
7
|
-
DEFAULT_SEP =
|
8
|
-
COMMON_SEP = { ";" =>
|
8
|
+
DEFAULT_SEP = /& */n
|
9
|
+
COMMON_SEP = { ";" => /; */n, ";," => /[;,] */n, "&" => /& */n }
|
9
10
|
|
10
11
|
# ParameterTypeError is the error that is raised when incoming structural
|
11
12
|
# parameters (parsed by parse_nested_query) contain conflicting types.
|
12
|
-
class ParameterTypeError < TypeError
|
13
|
+
class ParameterTypeError < TypeError
|
14
|
+
include BadRequest
|
15
|
+
end
|
13
16
|
|
14
17
|
# InvalidParameterError is the error that is raised when incoming structural
|
15
18
|
# parameters (parsed by parse_nested_query) contain invalid format or byte
|
16
19
|
# sequence.
|
17
|
-
class InvalidParameterError < ArgumentError
|
20
|
+
class InvalidParameterError < ArgumentError
|
21
|
+
include BadRequest
|
22
|
+
end
|
18
23
|
|
19
24
|
# ParamsTooDeepError is the error that is raised when params are recursively
|
20
25
|
# nested over the specified limit.
|
21
|
-
class ParamsTooDeepError < RangeError
|
22
|
-
|
23
|
-
|
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
|
26
|
+
class ParamsTooDeepError < RangeError
|
27
|
+
include BadRequest
|
28
|
+
end
|
27
29
|
|
30
|
+
def self.make_default(param_depth_limit)
|
28
31
|
new Params, param_depth_limit
|
29
32
|
end
|
30
33
|
|
31
34
|
attr_reader :param_depth_limit
|
32
35
|
|
33
|
-
def initialize(params_class,
|
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
|
37
|
-
|
36
|
+
def initialize(params_class, param_depth_limit)
|
38
37
|
@params_class = params_class
|
39
38
|
@param_depth_limit = param_depth_limit
|
40
39
|
end
|
@@ -194,59 +193,7 @@ module Rack
|
|
194
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
@@ -482,9 +482,14 @@ module Rack
|
|
482
482
|
|
483
483
|
# Returns the data received in the query string.
|
484
484
|
def GET
|
485
|
-
|
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)
|
@@ -505,9 +510,12 @@ module Rack
|
|
505
510
|
|
506
511
|
# If the form hash was already memoized:
|
507
512
|
if form_hash = get_header(RACK_REQUEST_FORM_HASH)
|
513
|
+
form_input = get_header(RACK_REQUEST_FORM_INPUT)
|
508
514
|
# And it was memoized from the same input:
|
509
|
-
if
|
515
|
+
if form_input.equal?(rack_input)
|
510
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
|
511
519
|
end
|
512
520
|
end
|
513
521
|
|
@@ -516,7 +524,10 @@ module Rack
|
|
516
524
|
set_header RACK_REQUEST_FORM_INPUT, nil
|
517
525
|
set_header(RACK_REQUEST_FORM_HASH, {})
|
518
526
|
elsif form_data? || parseable_data?
|
519
|
-
|
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
|
520
531
|
form_vars = get_header(RACK_INPUT).read
|
521
532
|
|
522
533
|
# Fix for Safari Ajax postings that always append \0
|
@@ -605,24 +616,10 @@ module Rack
|
|
605
616
|
Rack::Request.ip_filter.call(ip)
|
606
617
|
end
|
607
618
|
|
608
|
-
# shortcut for <tt>request.params[key]</tt>
|
609
|
-
def [](key)
|
610
|
-
warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead", uplevel: 1)
|
611
|
-
|
612
|
-
params[key.to_s]
|
613
|
-
end
|
614
|
-
|
615
|
-
# shortcut for <tt>request.params[key] = value</tt>
|
616
|
-
#
|
617
|
-
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
618
|
-
def []=(key, value)
|
619
|
-
warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead", uplevel: 1)
|
620
|
-
|
621
|
-
params[key.to_s] = value
|
622
|
-
end
|
623
|
-
|
624
619
|
# like Hash#values_at
|
625
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
|
+
|
626
623
|
keys.map { |key| params[key] }
|
627
624
|
end
|
628
625
|
|
@@ -645,14 +642,26 @@ module Rack
|
|
645
642
|
end
|
646
643
|
|
647
644
|
def parse_http_accept_header(header)
|
648
|
-
|
649
|
-
|
645
|
+
# It would be nice to use filter_map here, but it's Ruby 2.7+
|
646
|
+
parts = header.to_s.split(',')
|
647
|
+
|
648
|
+
parts.map! do |part|
|
649
|
+
part.strip!
|
650
|
+
next if part.empty?
|
651
|
+
|
652
|
+
attribute, parameters = part.split(';', 2)
|
653
|
+
attribute.strip!
|
654
|
+
parameters&.strip!
|
650
655
|
quality = 1.0
|
651
656
|
if parameters and /\Aq=([\d.]+)/ =~ parameters
|
652
657
|
quality = $1.to_f
|
653
658
|
end
|
654
659
|
[attribute, quality]
|
655
660
|
end
|
661
|
+
|
662
|
+
parts.compact!
|
663
|
+
|
664
|
+
parts
|
656
665
|
end
|
657
666
|
|
658
667
|
# Get an array of values set in the RFC 7239 `Forwarded` request header.
|
@@ -672,6 +681,16 @@ module Rack
|
|
672
681
|
Rack::Multipart.extract_multipart(self, query_parser)
|
673
682
|
end
|
674
683
|
|
684
|
+
def expand_param_pairs(pairs, query_parser = query_parser())
|
685
|
+
params = query_parser.make_params
|
686
|
+
|
687
|
+
pairs.each do |k, v|
|
688
|
+
query_parser.normalize_params(params, k, v)
|
689
|
+
end
|
690
|
+
|
691
|
+
params.to_params_hash
|
692
|
+
end
|
693
|
+
|
675
694
|
def split_header(value)
|
676
695
|
value ? value.strip.split(/[,\s]+/) : []
|
677
696
|
end
|