rack 3.0.16 → 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.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -92
- data/CONTRIBUTING.md +11 -9
- data/README.md +34 -42
- data/SPEC.rdoc +38 -13
- data/lib/rack/auth/basic.rb +1 -2
- data/lib/rack/bad_request.rb +8 -0
- data/lib/rack/builder.rb +23 -10
- data/lib/rack/cascade.rb +0 -3
- data/lib/rack/common_logger.rb +2 -3
- data/lib/rack/constants.rb +3 -1
- data/lib/rack/content_length.rb +0 -1
- data/lib/rack/etag.rb +0 -3
- data/lib/rack/headers.rb +86 -2
- data/lib/rack/lint.rb +116 -34
- data/lib/rack/logger.rb +2 -1
- data/lib/rack/mime.rb +6 -5
- data/lib/rack/mock_request.rb +19 -14
- data/lib/rack/mock_response.rb +12 -14
- data/lib/rack/multipart/parser.rb +123 -62
- data/lib/rack/multipart.rb +34 -1
- data/lib/rack/query_parser.rb +19 -115
- data/lib/rack/request.rb +28 -21
- data/lib/rack/response.rb +21 -23
- data/lib/rack/sendfile.rb +1 -1
- data/lib/rack/show_exceptions.rb +6 -2
- data/lib/rack/static.rb +1 -2
- data/lib/rack/utils.rb +57 -96
- data/lib/rack/version.rb +1 -14
- data/lib/rack.rb +1 -3
- 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,7 @@ 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
|
216
212
|
@end_boundary_size = boundary.bytesize + 4 # (-- at start, -- at finish)
|
217
213
|
@rx_max_size = boundary.bytesize + 6 # (\r\n-- at start, either \r\n or -- at finish)
|
218
214
|
@head_regex = /(.*?#{EOL})#{EOL}/m
|
@@ -305,17 +301,102 @@ module Rack
|
|
305
301
|
end
|
306
302
|
end
|
307
303
|
|
304
|
+
CONTENT_DISPOSITION_MAX_PARAMS = 16
|
305
|
+
CONTENT_DISPOSITION_MAX_BYTES = 1536
|
308
306
|
def handle_mime_head
|
309
307
|
if @sbuf.scan_until(@head_regex)
|
310
308
|
head = @sbuf[1]
|
311
309
|
content_type = head[MULTIPART_CONTENT_TYPE, 1]
|
312
|
-
if
|
313
|
-
|
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
|
314
388
|
else
|
315
389
|
name = head[MULTIPART_CONTENT_ID, 1]
|
316
390
|
end
|
317
391
|
|
318
|
-
|
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
|
319
400
|
|
320
401
|
if name.nil? || name.empty?
|
321
402
|
name = filename || "#{content_type || TEXT_PLAIN}[]".dup
|
@@ -330,7 +411,7 @@ module Rack
|
|
330
411
|
|
331
412
|
def handle_mime_body
|
332
413
|
if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
|
333
|
-
body = body_with_boundary.sub(
|
414
|
+
body = body_with_boundary.sub(@body_regex_at_end, '') # remove the boundary from the string
|
334
415
|
@collector.on_mime_body @mime_index, body
|
335
416
|
@sbuf.pos += body.length + 2 # skip \r\n after the content
|
336
417
|
@state = :CONSUME_TOKEN
|
@@ -360,39 +441,14 @@ module Rack
|
|
360
441
|
end
|
361
442
|
end
|
362
443
|
|
363
|
-
def
|
364
|
-
filename = nil
|
365
|
-
case head
|
366
|
-
when RFC2183
|
367
|
-
params = Hash[*head.scan(DISPPARM).flat_map(&:compact)]
|
368
|
-
|
369
|
-
if filename = params['filename*']
|
370
|
-
encoding, _, filename = filename.split("'", 3)
|
371
|
-
elsif filename = params['filename']
|
372
|
-
filename = $1 if filename =~ /^"(.*)"$/
|
373
|
-
end
|
374
|
-
when BROKEN
|
375
|
-
filename = $1
|
376
|
-
filename = $1 if filename =~ /^"(.*)"$/
|
377
|
-
end
|
378
|
-
|
379
|
-
return unless filename
|
380
|
-
|
444
|
+
def normalize_filename(filename)
|
381
445
|
if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
|
382
446
|
filename = Utils.unescape_path(filename)
|
383
447
|
end
|
384
448
|
|
385
449
|
filename.scrub!
|
386
450
|
|
387
|
-
|
388
|
-
filename = filename.gsub(/\\(.)/, '\1')
|
389
|
-
end
|
390
|
-
|
391
|
-
if encoding
|
392
|
-
filename.force_encoding ::Encoding.find(encoding)
|
393
|
-
end
|
394
|
-
|
395
|
-
filename
|
451
|
+
filename.split(/[\/\\]/).last || String.new
|
396
452
|
end
|
397
453
|
|
398
454
|
CHARSET = "charset"
|
@@ -418,11 +474,7 @@ module Rack
|
|
418
474
|
v.strip!
|
419
475
|
v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
|
420
476
|
if k == "charset"
|
421
|
-
encoding =
|
422
|
-
Encoding.find v
|
423
|
-
rescue ArgumentError
|
424
|
-
Encoding::BINARY
|
425
|
-
end
|
477
|
+
encoding = find_encoding(v)
|
426
478
|
end
|
427
479
|
end
|
428
480
|
end
|
@@ -432,6 +484,15 @@ module Rack
|
|
432
484
|
body.force_encoding(encoding)
|
433
485
|
end
|
434
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
|
+
|
435
496
|
def handle_empty_content!(content)
|
436
497
|
if content.nil? || content.empty?
|
437
498
|
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,69 +1,41 @@
|
|
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
|
18
|
-
|
19
|
-
# QueryLimitError is for errors raised when the query provided exceeds one
|
20
|
-
# of the query parser limits.
|
21
|
-
class QueryLimitError < RangeError
|
20
|
+
class InvalidParameterError < ArgumentError
|
21
|
+
include BadRequest
|
22
22
|
end
|
23
23
|
|
24
|
-
# ParamsTooDeepError is the
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
ParamsTooDeepError = QueryLimitError
|
29
|
-
|
30
|
-
def self.make_default(_key_space_limit=(not_deprecated = true; nil), param_depth_limit, **options)
|
31
|
-
unless not_deprecated
|
32
|
-
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)
|
33
|
-
end
|
34
|
-
|
35
|
-
new Params, param_depth_limit, **options
|
24
|
+
# ParamsTooDeepError is the error that is raised when params are recursively
|
25
|
+
# nested over the specified limit.
|
26
|
+
class ParamsTooDeepError < RangeError
|
27
|
+
include BadRequest
|
36
28
|
end
|
37
29
|
|
38
|
-
|
39
|
-
|
40
|
-
env_int = lambda do |key, val|
|
41
|
-
if str_val = ENV[key]
|
42
|
-
begin
|
43
|
-
val = Integer(str_val, 10)
|
44
|
-
rescue ArgumentError
|
45
|
-
raise ArgumentError, "non-integer value provided for environment variable #{key}"
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
val
|
30
|
+
def self.make_default(param_depth_limit)
|
31
|
+
new Params, param_depth_limit
|
50
32
|
end
|
51
33
|
|
52
|
-
|
53
|
-
private_constant :BYTESIZE_LIMIT
|
54
|
-
|
55
|
-
PARAMS_LIMIT = env_int.call("RACK_QUERY_PARSER_PARAMS_LIMIT", 4096)
|
56
|
-
private_constant :PARAMS_LIMIT
|
57
|
-
|
58
|
-
def initialize(params_class, _key_space_limit=(not_deprecated = true; nil), param_depth_limit, bytesize_limit: BYTESIZE_LIMIT, params_limit: PARAMS_LIMIT)
|
59
|
-
unless not_deprecated
|
60
|
-
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)
|
61
|
-
end
|
34
|
+
attr_reader :param_depth_limit
|
62
35
|
|
36
|
+
def initialize(params_class, param_depth_limit)
|
63
37
|
@params_class = params_class
|
64
38
|
@param_depth_limit = param_depth_limit
|
65
|
-
@bytesize_limit = bytesize_limit
|
66
|
-
@params_limit = params_limit
|
67
39
|
end
|
68
40
|
|
69
41
|
# Stolen from Mongrel, with some small modifications:
|
@@ -75,7 +47,7 @@ module Rack
|
|
75
47
|
|
76
48
|
params = make_params
|
77
49
|
|
78
|
-
|
50
|
+
(qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
|
79
51
|
next if p.empty?
|
80
52
|
k, v = p.split('=', 2).map!(&unescaper)
|
81
53
|
|
@@ -102,7 +74,7 @@ module Rack
|
|
102
74
|
params = make_params
|
103
75
|
|
104
76
|
unless qs.nil? || qs.empty?
|
105
|
-
|
77
|
+
(qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
|
106
78
|
k, v = p.split('=', 2).map! { |s| unescape(s) }
|
107
79
|
|
108
80
|
_normalize_params(params, k, v, 0)
|
@@ -217,79 +189,11 @@ module Rack
|
|
217
189
|
true
|
218
190
|
end
|
219
191
|
|
220
|
-
def check_query_string(qs, sep)
|
221
|
-
if qs
|
222
|
-
if qs.bytesize > @bytesize_limit
|
223
|
-
raise QueryLimitError, "total query size (#{qs.bytesize}) exceeds limit (#{@bytesize_limit})"
|
224
|
-
end
|
225
|
-
|
226
|
-
if (param_count = qs.count(sep.is_a?(String) ? sep : '&')) >= @params_limit
|
227
|
-
raise QueryLimitError, "total number of query parameters (#{param_count+1}) exceeds limit (#{@params_limit})"
|
228
|
-
end
|
229
|
-
|
230
|
-
qs
|
231
|
-
else
|
232
|
-
''
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
192
|
def unescape(string, encoding = Encoding::UTF_8)
|
237
193
|
URI.decode_www_form_component(string, encoding)
|
238
194
|
end
|
239
195
|
|
240
|
-
class Params
|
241
|
-
def initialize
|
242
|
-
@size = 0
|
243
|
-
@params = {}
|
244
|
-
end
|
245
|
-
|
246
|
-
def [](key)
|
247
|
-
@params[key]
|
248
|
-
end
|
249
|
-
|
250
|
-
def []=(key, value)
|
251
|
-
@params[key] = value
|
252
|
-
end
|
253
|
-
|
254
|
-
def key?(key)
|
255
|
-
@params.key?(key)
|
256
|
-
end
|
257
|
-
|
258
|
-
# Recursively unwraps nested `Params` objects and constructs an object
|
259
|
-
# of the same shape, but using the objects' internal representations
|
260
|
-
# (Ruby hashes) in place of the objects. The result is a hash consisting
|
261
|
-
# purely of Ruby primitives.
|
262
|
-
#
|
263
|
-
# Mutation warning!
|
264
|
-
#
|
265
|
-
# 1. This method mutates the internal representation of the `Params`
|
266
|
-
# objects in order to save object allocations.
|
267
|
-
#
|
268
|
-
# 2. The value you get back is a reference to the internal hash
|
269
|
-
# representation, not a copy.
|
270
|
-
#
|
271
|
-
# 3. Because the `Params` object's internal representation is mutable
|
272
|
-
# through the `#[]=` method, it is not thread safe. The result of
|
273
|
-
# getting the hash representation while another thread is adding a
|
274
|
-
# key to it is non-deterministic.
|
275
|
-
#
|
276
|
-
def to_h
|
277
|
-
@params.each do |key, value|
|
278
|
-
case value
|
279
|
-
when self
|
280
|
-
# Handle circular references gracefully.
|
281
|
-
@params[key] = @params
|
282
|
-
when Params
|
283
|
-
@params[key] = value.to_h
|
284
|
-
when Array
|
285
|
-
value.map! { |v| v.kind_of?(Params) ? v.to_h : v }
|
286
|
-
else
|
287
|
-
# Ignore anything that is not a `Params` object or
|
288
|
-
# a collection that can contain one.
|
289
|
-
end
|
290
|
-
end
|
291
|
-
@params
|
292
|
-
end
|
196
|
+
class Params < Hash
|
293
197
|
alias_method :to_params_hash, :to_h
|
294
198
|
end
|
295
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,8 +642,8 @@ module Rack
|
|
645
642
|
end
|
646
643
|
|
647
644
|
def parse_http_accept_header(header)
|
648
|
-
header.to_s.split(
|
649
|
-
attribute, parameters = part.split(
|
645
|
+
header.to_s.split(/\s*,\s*/).map do |part|
|
646
|
+
attribute, parameters = part.split(/\s*;\s*/, 2)
|
650
647
|
quality = 1.0
|
651
648
|
if parameters and /\Aq=([\d.]+)/ =~ parameters
|
652
649
|
quality = $1.to_f
|
@@ -672,6 +669,16 @@ module Rack
|
|
672
669
|
Rack::Multipart.extract_multipart(self, query_parser)
|
673
670
|
end
|
674
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
|
+
|
675
682
|
def split_header(value)
|
676
683
|
value ? value.strip.split(/[,\s]+/) : []
|
677
684
|
end
|