rack 3.1.16 → 3.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +70 -1
- data/README.md +41 -20
- data/SPEC.rdoc +199 -306
- data/lib/rack/auth/abstract/request.rb +2 -0
- data/lib/rack/builder.rb +6 -0
- data/lib/rack/conditional_get.rb +4 -3
- data/lib/rack/constants.rb +1 -0
- data/lib/rack/head.rb +2 -3
- data/lib/rack/lint.rb +430 -457
- data/lib/rack/media_type.rb +6 -7
- data/lib/rack/mock_response.rb +19 -25
- data/lib/rack/multipart/parser.rb +33 -7
- data/lib/rack/multipart/uploaded_file.rb +42 -5
- data/lib/rack/query_parser.rb +41 -24
- data/lib/rack/request.rb +45 -54
- data/lib/rack/rewindable_input.rb +4 -1
- data/lib/rack/show_exceptions.rb +4 -2
- data/lib/rack/show_status.rb +0 -2
- data/lib/rack/utils.rb +14 -23
- data/lib/rack/version.rb +4 -8
- data/lib/rack.rb +0 -1
- metadata +5 -5
- data/lib/rack/logger.rb +0 -23
data/lib/rack/media_type.rb
CHANGED
@@ -14,12 +14,11 @@ module Rack
|
|
14
14
|
# For more information on the use of media types in HTTP, see:
|
15
15
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
|
16
16
|
def type(content_type)
|
17
|
-
return nil unless content_type
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
17
|
+
return nil unless content_type && !content_type.empty?
|
18
|
+
type = content_type.split(SPLIT_PATTERN, 2).first
|
19
|
+
type.rstrip!
|
20
|
+
type.downcase!
|
21
|
+
type
|
23
22
|
end
|
24
23
|
|
25
24
|
# The media type parameters provided in CONTENT_TYPE as a Hash, or
|
@@ -33,7 +32,7 @@ module Rack
|
|
33
32
|
# and "text/plain;charset" will return { 'charset' => '' }, similarly to
|
34
33
|
# the query params parser (barring the latter case, which returns nil instead)).
|
35
34
|
def params(content_type)
|
36
|
-
return {} if content_type.nil?
|
35
|
+
return {} if content_type.nil? || content_type.empty?
|
37
36
|
|
38
37
|
content_type.split(SPLIT_PATTERN)[1..-1].each_with_object({}) do |s, hsh|
|
39
38
|
s.strip!
|
data/lib/rack/mock_response.rb
CHANGED
@@ -10,33 +10,27 @@ module Rack
|
|
10
10
|
# MockRequest.
|
11
11
|
|
12
12
|
class MockResponse < Rack::Response
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
@path = args["path"]
|
25
|
-
@domain = args["domain"]
|
26
|
-
@expires = args["expires"]
|
27
|
-
@secure = args["secure"]
|
28
|
-
end
|
13
|
+
class Cookie
|
14
|
+
attr_reader :name, :value, :path, :domain, :expires, :secure
|
15
|
+
|
16
|
+
def initialize(args)
|
17
|
+
@name = args["name"]
|
18
|
+
@value = args["value"]
|
19
|
+
@path = args["path"]
|
20
|
+
@domain = args["domain"]
|
21
|
+
@expires = args["expires"]
|
22
|
+
@secure = args["secure"]
|
23
|
+
end
|
29
24
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
25
|
+
def method_missing(method_name, *args, &block)
|
26
|
+
@value.send(method_name, *args, &block)
|
27
|
+
end
|
28
|
+
# :nocov:
|
29
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
30
|
+
# :nocov:
|
36
31
|
|
37
|
-
|
38
|
-
|
39
|
-
end
|
32
|
+
def respond_to_missing?(method_name, include_all = false)
|
33
|
+
@value.respond_to?(method_name, include_all) || super
|
40
34
|
end
|
41
35
|
end
|
42
36
|
|
@@ -38,6 +38,18 @@ module Rack
|
|
38
38
|
MULTIPART_CONTENT_DISPOSITION = /^Content-Disposition:#{FWS}?(#{HEADER_VALUE})/ni
|
39
39
|
MULTIPART_CONTENT_ID = /^Content-ID:#{FWS}?(#{HEADER_VALUE})/ni
|
40
40
|
|
41
|
+
# Rack::Multipart::Parser handles parsing of multipart/form-data requests.
|
42
|
+
#
|
43
|
+
# File Parameter Contents
|
44
|
+
#
|
45
|
+
# When processing file uploads, the parser returns a hash containing
|
46
|
+
# information about uploaded files. For +file+ parameters, the hash includes:
|
47
|
+
#
|
48
|
+
# * +:filename+ - The original filename, already URL decoded by the parser
|
49
|
+
# * +:type+ - The content type of the uploaded file
|
50
|
+
# * +:name+ - The parameter name from the form
|
51
|
+
# * +:tempfile+ - A Tempfile object containing the uploaded data
|
52
|
+
# * +:head+ - The raw header content for this part
|
41
53
|
class Parser
|
42
54
|
BUFSIZE = 1_048_576
|
43
55
|
TEXT_PLAIN = "text/plain"
|
@@ -243,7 +255,8 @@ module Rack
|
|
243
255
|
@collector.each do |part|
|
244
256
|
part.get_data do |data|
|
245
257
|
tag_multipart_encoding(part.filename, part.content_type, part.name, data)
|
246
|
-
|
258
|
+
name, data = handle_dummy_encoding(part.name, data)
|
259
|
+
@query_parser.normalize_params(@params, name, data)
|
247
260
|
end
|
248
261
|
end
|
249
262
|
MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
|
@@ -251,12 +264,6 @@ module Rack
|
|
251
264
|
|
252
265
|
private
|
253
266
|
|
254
|
-
def dequote(str) # From WEBrick::HTTPUtils
|
255
|
-
ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
|
256
|
-
ret.gsub!(/\\(.)/, "\\1")
|
257
|
-
ret
|
258
|
-
end
|
259
|
-
|
260
267
|
def read_data(io, outbuf)
|
261
268
|
content = io.read(@bufsize, outbuf)
|
262
269
|
handle_empty_content!(content)
|
@@ -494,6 +501,25 @@ module Rack
|
|
494
501
|
Encoding::BINARY
|
495
502
|
end
|
496
503
|
|
504
|
+
REENCODE_DUMMY_ENCODINGS = {
|
505
|
+
# ISO-2022-JP is a legacy but still widely used encoding in Japan
|
506
|
+
# Here we convert ISO-2022-JP to UTF-8 so that it can be handled.
|
507
|
+
Encoding::ISO_2022_JP => true
|
508
|
+
|
509
|
+
# Other dummy encodings are rarely used and have not been supported yet.
|
510
|
+
# Adding support for them will require careful considerations.
|
511
|
+
}
|
512
|
+
|
513
|
+
def handle_dummy_encoding(name, body)
|
514
|
+
# A string object with a 'dummy' encoding does not have full functionality and can cause errors.
|
515
|
+
# So here we covert it to UTF-8 so that it can be handled properly.
|
516
|
+
if name.encoding.dummy? && REENCODE_DUMMY_ENCODINGS[name.encoding]
|
517
|
+
name = name.encode(Encoding::UTF_8)
|
518
|
+
body = body.encode(Encoding::UTF_8)
|
519
|
+
end
|
520
|
+
return name, body
|
521
|
+
end
|
522
|
+
|
497
523
|
def handle_empty_content!(content)
|
498
524
|
if content.nil? || content.empty?
|
499
525
|
raise EmptyContentError
|
@@ -5,14 +5,47 @@ require 'fileutils'
|
|
5
5
|
|
6
6
|
module Rack
|
7
7
|
module Multipart
|
8
|
+
# Despite the misleading name, UploadedFile is designed for use for
|
9
|
+
# preparing multipart file upload bodies, generally for use in tests.
|
10
|
+
# It is not designed for and should not be used for handling uploaded
|
11
|
+
# files (there is no need for that, since Rack's multipart parser
|
12
|
+
# already creates Tempfiles for that). Using this with non-trusted
|
13
|
+
# filenames can create a security vulnerability.
|
14
|
+
#
|
15
|
+
# You should only use this class if you plan on passing the instances
|
16
|
+
# to Rack::MockRequest for use in creating multipart request bodies.
|
17
|
+
#
|
18
|
+
# UploadedFile delegates most methods to the tempfile it contains.
|
8
19
|
class UploadedFile
|
9
|
-
|
10
|
-
#
|
20
|
+
# The provided name of the file. This generally is the basename of
|
21
|
+
# path provided during initialization, but it can contain slashes if they
|
22
|
+
# were present in the filename argument when the instance was created.
|
11
23
|
attr_reader :original_filename
|
12
24
|
|
13
|
-
# The content type of the
|
25
|
+
# The content type of the instance.
|
14
26
|
attr_accessor :content_type
|
15
27
|
|
28
|
+
# Create a new UploadedFile. For backwards compatibility, this accepts
|
29
|
+
# both positional and keyword versions of the same arguments:
|
30
|
+
#
|
31
|
+
# filepath/path :: The path to the file
|
32
|
+
# ct/content_type :: The content_type of the file
|
33
|
+
# bin/binary :: Whether to set binmode on the file before copying data into it.
|
34
|
+
#
|
35
|
+
# If both positional and keyword arguments are present, the keyword arguments
|
36
|
+
# take precedence.
|
37
|
+
#
|
38
|
+
# The following keyword-only arguments are also accepted:
|
39
|
+
#
|
40
|
+
# filename :: Override the filename to use for the file. This is so the
|
41
|
+
# filename for the upload does not need to match the basename of
|
42
|
+
# the file path. This should not contain slashes, unless you are
|
43
|
+
# trying to test how an application handles invalid filenames in
|
44
|
+
# multipart upload bodies.
|
45
|
+
# io :: Use the given IO-like instance as the tempfile, instead of creating
|
46
|
+
# a Tempfile instance. This is useful for building multipart file
|
47
|
+
# upload bodies without a file being present on the filesystem. If you are
|
48
|
+
# providing this, you should also provide the filename argument.
|
16
49
|
def initialize(filepath = nil, ct = "text/plain", bin = false,
|
17
50
|
path: filepath, content_type: ct, binary: bin, filename: nil, io: nil)
|
18
51
|
if io
|
@@ -28,15 +61,19 @@ module Rack
|
|
28
61
|
@content_type = content_type
|
29
62
|
end
|
30
63
|
|
64
|
+
# The path of the tempfile for the instance, if the tempfile has a path.
|
65
|
+
# nil if the tempfile does not have a path.
|
31
66
|
def path
|
32
67
|
@tempfile.path if @tempfile.respond_to?(:path)
|
33
68
|
end
|
34
69
|
alias_method :local_path, :path
|
35
70
|
|
36
|
-
|
37
|
-
|
71
|
+
# Return true if the tempfile responds to the method.
|
72
|
+
def respond_to_missing?(*args)
|
73
|
+
@tempfile.respond_to?(*args)
|
38
74
|
end
|
39
75
|
|
76
|
+
# Delegate method missing calls to the tempfile.
|
40
77
|
def method_missing(method_name, *args, &block) #:nodoc:
|
41
78
|
@tempfile.__send__(method_name, *args, &block)
|
42
79
|
end
|
data/lib/rack/query_parser.rb
CHANGED
@@ -69,14 +69,9 @@ module Rack
|
|
69
69
|
# to parse cookies by changing the characters used in the second parameter
|
70
70
|
# (which defaults to '&').
|
71
71
|
def parse_query(qs, separator = nil, &unescaper)
|
72
|
-
unescaper ||= method(:unescape)
|
73
|
-
|
74
72
|
params = make_params
|
75
73
|
|
76
|
-
|
77
|
-
next if p.empty?
|
78
|
-
k, v = p.split('=', 2).map!(&unescaper)
|
79
|
-
|
74
|
+
each_query_pair(qs, separator, unescaper) do |k, v|
|
80
75
|
if cur = params[k]
|
81
76
|
if cur.class == Array
|
82
77
|
params[k] << v
|
@@ -91,6 +86,19 @@ module Rack
|
|
91
86
|
return params.to_h
|
92
87
|
end
|
93
88
|
|
89
|
+
# Parses a query string by breaking it up at the '&', returning all key-value
|
90
|
+
# pairs as an array of [key, value] arrays. Unlike parse_query, this preserves
|
91
|
+
# all duplicate keys rather than collapsing them.
|
92
|
+
def parse_query_pairs(qs, separator = nil)
|
93
|
+
pairs = []
|
94
|
+
|
95
|
+
each_query_pair(qs, separator) do |k, v|
|
96
|
+
pairs << [k, v]
|
97
|
+
end
|
98
|
+
|
99
|
+
pairs
|
100
|
+
end
|
101
|
+
|
94
102
|
# parse_nested_query expands a query string into structural types. Supported
|
95
103
|
# types are Arrays, Hashes and basic value types. It is possible to supply
|
96
104
|
# query strings with parameters of conflicting types, in this case a
|
@@ -99,17 +107,11 @@ module Rack
|
|
99
107
|
def parse_nested_query(qs, separator = nil)
|
100
108
|
params = make_params
|
101
109
|
|
102
|
-
|
103
|
-
|
104
|
-
k, v = p.split('=', 2).map! { |s| unescape(s) }
|
105
|
-
|
106
|
-
_normalize_params(params, k, v, 0)
|
107
|
-
end
|
110
|
+
each_query_pair(qs, separator) do |k, v|
|
111
|
+
_normalize_params(params, k, v, 0)
|
108
112
|
end
|
109
113
|
|
110
114
|
return params.to_h
|
111
|
-
rescue ArgumentError => e
|
112
|
-
raise InvalidParameterError, e.message, e.backtrace
|
113
115
|
end
|
114
116
|
|
115
117
|
# normalize_params recursively expands parameters into structural types. If
|
@@ -215,20 +217,35 @@ module Rack
|
|
215
217
|
true
|
216
218
|
end
|
217
219
|
|
218
|
-
def
|
219
|
-
if qs
|
220
|
-
if qs.bytesize > @bytesize_limit
|
221
|
-
raise QueryLimitError, "total query size (#{qs.bytesize}) exceeds limit (#{@bytesize_limit})"
|
222
|
-
end
|
220
|
+
def each_query_pair(qs, separator, unescaper = nil)
|
221
|
+
return if !qs || qs.empty?
|
223
222
|
|
224
|
-
|
225
|
-
|
226
|
-
|
223
|
+
if qs.bytesize > @bytesize_limit
|
224
|
+
raise QueryLimitError, "total query size (#{qs.bytesize}) exceeds limit (#{@bytesize_limit})"
|
225
|
+
end
|
226
|
+
|
227
|
+
pairs = qs.split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP, @params_limit + 1)
|
228
|
+
|
229
|
+
if pairs.size > @params_limit
|
230
|
+
param_count = pairs.size + pairs.last.count(separator || "&")
|
231
|
+
raise QueryLimitError, "total number of query parameters (#{param_count}) exceeds limit (#{@params_limit})"
|
232
|
+
end
|
227
233
|
|
228
|
-
|
234
|
+
if unescaper
|
235
|
+
pairs.each do |p|
|
236
|
+
next if p.empty?
|
237
|
+
k, v = p.split('=', 2).map!(&unescaper)
|
238
|
+
yield k, v
|
239
|
+
end
|
229
240
|
else
|
230
|
-
|
241
|
+
pairs.each do |p|
|
242
|
+
next if p.empty?
|
243
|
+
k, v = p.split('=', 2).map! { |s| unescape(s) }
|
244
|
+
yield k, v
|
245
|
+
end
|
231
246
|
end
|
247
|
+
rescue ArgumentError => e
|
248
|
+
raise InvalidParameterError, e.message, e.backtrace
|
232
249
|
end
|
233
250
|
|
234
251
|
def unescape(string, encoding = Encoding::UTF_8)
|
data/lib/rack/request.rb
CHANGED
@@ -61,9 +61,14 @@ module Rack
|
|
61
61
|
|
62
62
|
def initialize(env)
|
63
63
|
@env = env
|
64
|
+
@ip = nil
|
64
65
|
@params = nil
|
65
66
|
end
|
66
67
|
|
68
|
+
def ip
|
69
|
+
@ip ||= super
|
70
|
+
end
|
71
|
+
|
67
72
|
def params
|
68
73
|
@params ||= super
|
69
74
|
end
|
@@ -398,8 +403,8 @@ module Rack
|
|
398
403
|
return forwarded.last
|
399
404
|
end
|
400
405
|
when :x_forwarded
|
401
|
-
if value = get_header(HTTP_X_FORWARDED_HOST)
|
402
|
-
return wrap_ipv6(
|
406
|
+
if (value = get_header(HTTP_X_FORWARDED_HOST)) && (x_forwarded_host = split_header(value).last)
|
407
|
+
return wrap_ipv6(x_forwarded_host)
|
403
408
|
end
|
404
409
|
end
|
405
410
|
end
|
@@ -413,10 +418,9 @@ module Rack
|
|
413
418
|
|
414
419
|
def ip
|
415
420
|
remote_addresses = split_header(get_header('REMOTE_ADDR'))
|
416
|
-
external_addresses = reject_trusted_ip_addresses(remote_addresses)
|
417
421
|
|
418
|
-
|
419
|
-
return
|
422
|
+
remote_addresses.reverse_each do |ip|
|
423
|
+
return ip unless trusted_proxy?(ip)
|
420
424
|
end
|
421
425
|
|
422
426
|
if (forwarded_for = self.forwarded_for) && !forwarded_for.empty?
|
@@ -424,7 +428,10 @@ module Rack
|
|
424
428
|
# So we reject all the trusted addresses (proxy*) and return the
|
425
429
|
# last client. Or if we trust everyone, we just return the first
|
426
430
|
# address.
|
427
|
-
|
431
|
+
forwarded_for.reverse_each do |ip|
|
432
|
+
return ip unless trusted_proxy?(ip)
|
433
|
+
end
|
434
|
+
return forwarded_for.first
|
428
435
|
end
|
429
436
|
|
430
437
|
# If all the addresses are trusted, and we aren't forwarded, just return
|
@@ -482,51 +489,29 @@ module Rack
|
|
482
489
|
|
483
490
|
# Returns the data received in the query string.
|
484
491
|
def GET
|
485
|
-
|
486
|
-
query_string = self.query_string
|
487
|
-
if rr_query_string == query_string
|
488
|
-
get_header(RACK_REQUEST_QUERY_HASH)
|
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
|
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)
|
496
|
-
end
|
492
|
+
get_header(RACK_REQUEST_QUERY_HASH) || set_header(RACK_REQUEST_QUERY_HASH, parse_query(query_string, '&'))
|
497
493
|
end
|
498
494
|
|
499
|
-
# Returns the data received in the request body.
|
495
|
+
# Returns the form data pairs received in the request body.
|
500
496
|
#
|
501
497
|
# This method support both application/x-www-form-urlencoded and
|
502
498
|
# multipart/form-data.
|
503
|
-
def
|
504
|
-
if
|
499
|
+
def form_pairs
|
500
|
+
if pairs = get_header(RACK_REQUEST_FORM_PAIRS)
|
501
|
+
return pairs
|
502
|
+
elsif error = get_header(RACK_REQUEST_FORM_ERROR)
|
505
503
|
raise error.class, error.message, cause: error.cause
|
506
504
|
end
|
507
505
|
|
508
506
|
begin
|
509
507
|
rack_input = get_header(RACK_INPUT)
|
510
508
|
|
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
|
520
|
-
end
|
521
|
-
|
522
509
|
# Otherwise, figure out how to parse the input:
|
523
510
|
if rack_input.nil?
|
524
|
-
set_header
|
525
|
-
set_header(RACK_REQUEST_FORM_HASH, {})
|
511
|
+
set_header(RACK_REQUEST_FORM_PAIRS, [])
|
526
512
|
elsif form_data? || parseable_data?
|
527
513
|
if pairs = Rack::Multipart.parse_multipart(env, Rack::Multipart::ParamList)
|
528
514
|
set_header RACK_REQUEST_FORM_PAIRS, pairs
|
529
|
-
set_header RACK_REQUEST_FORM_HASH, expand_param_pairs(pairs)
|
530
515
|
else
|
531
516
|
form_vars = get_header(RACK_INPUT).read
|
532
517
|
|
@@ -535,14 +520,11 @@ module Rack
|
|
535
520
|
form_vars.slice!(-1) if form_vars.end_with?("\0")
|
536
521
|
|
537
522
|
set_header RACK_REQUEST_FORM_VARS, form_vars
|
538
|
-
|
523
|
+
pairs = query_parser.parse_query_pairs(form_vars, '&')
|
524
|
+
set_header(RACK_REQUEST_FORM_PAIRS, pairs)
|
539
525
|
end
|
540
|
-
|
541
|
-
set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
|
542
|
-
get_header RACK_REQUEST_FORM_HASH
|
543
526
|
else
|
544
|
-
set_header
|
545
|
-
set_header(RACK_REQUEST_FORM_HASH, {})
|
527
|
+
set_header(RACK_REQUEST_FORM_PAIRS, [])
|
546
528
|
end
|
547
529
|
rescue => error
|
548
530
|
set_header(RACK_REQUEST_FORM_ERROR, error)
|
@@ -550,6 +532,21 @@ module Rack
|
|
550
532
|
end
|
551
533
|
end
|
552
534
|
|
535
|
+
# Returns the data received in the request body.
|
536
|
+
#
|
537
|
+
# This method support both application/x-www-form-urlencoded and
|
538
|
+
# multipart/form-data.
|
539
|
+
def POST
|
540
|
+
if form_hash = get_header(RACK_REQUEST_FORM_HASH)
|
541
|
+
return form_hash
|
542
|
+
elsif error = get_header(RACK_REQUEST_FORM_ERROR)
|
543
|
+
raise error.class, error.message, cause: error.cause
|
544
|
+
end
|
545
|
+
|
546
|
+
pairs = form_pairs
|
547
|
+
set_header RACK_REQUEST_FORM_HASH, expand_param_pairs(pairs)
|
548
|
+
end
|
549
|
+
|
553
550
|
# The union of GET and POST data.
|
554
551
|
#
|
555
552
|
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
@@ -557,6 +554,10 @@ module Rack
|
|
557
554
|
self.GET.merge(self.POST)
|
558
555
|
end
|
559
556
|
|
557
|
+
# Allow overriding the query parser that the receiver will use.
|
558
|
+
# By default Rack::Utils.default_query_parser is used.
|
559
|
+
attr_writer :query_parser
|
560
|
+
|
560
561
|
# Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
|
561
562
|
#
|
562
563
|
# The parameter is updated wherever it was previous defined, so GET, POST, or both. If it wasn't previously defined, it's inserted into GET.
|
@@ -616,13 +617,6 @@ module Rack
|
|
616
617
|
Rack::Request.ip_filter.call(ip)
|
617
618
|
end
|
618
619
|
|
619
|
-
# like Hash#values_at
|
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
|
-
|
623
|
-
keys.map { |key| params[key] }
|
624
|
-
end
|
625
|
-
|
626
620
|
private
|
627
621
|
|
628
622
|
def default_session; {}; end
|
@@ -670,7 +664,7 @@ module Rack
|
|
670
664
|
end
|
671
665
|
|
672
666
|
def query_parser
|
673
|
-
Utils.default_query_parser
|
667
|
+
@query_parser || Utils.default_query_parser
|
674
668
|
end
|
675
669
|
|
676
670
|
def parse_query(qs, d = '&')
|
@@ -678,6 +672,7 @@ module Rack
|
|
678
672
|
end
|
679
673
|
|
680
674
|
def parse_multipart
|
675
|
+
warn "Rack::Request#parse_multipart is deprecated and will be removed in a future version of Rack.", uplevel: 1
|
681
676
|
Rack::Multipart.extract_multipart(self, query_parser)
|
682
677
|
end
|
683
678
|
|
@@ -692,7 +687,7 @@ module Rack
|
|
692
687
|
end
|
693
688
|
|
694
689
|
def split_header(value)
|
695
|
-
value ? value.strip.split(/[
|
690
|
+
value ? value.strip.split(/[, \t]+/) : []
|
696
691
|
end
|
697
692
|
|
698
693
|
# ipv6 extracted from resolv stdlib, simplified
|
@@ -740,10 +735,6 @@ module Rack
|
|
740
735
|
return match[:host], match[:address], match[:port]&.to_i
|
741
736
|
end
|
742
737
|
|
743
|
-
def reject_trusted_ip_addresses(ip_addresses)
|
744
|
-
ip_addresses.reject { |ip| trusted_proxy?(ip) }
|
745
|
-
end
|
746
|
-
|
747
738
|
FORWARDED_SCHEME_HEADERS = {
|
748
739
|
proto: HTTP_X_FORWARDED_PROTO,
|
749
740
|
scheme: HTTP_X_FORWARDED_SCHEME
|
data/lib/rack/show_exceptions.rb
CHANGED
@@ -65,8 +65,12 @@ module Rack
|
|
65
65
|
def dump_exception(exception)
|
66
66
|
if exception.respond_to?(:detailed_message)
|
67
67
|
message = exception.detailed_message(highlight: false)
|
68
|
+
# :nocov:
|
69
|
+
# Ruby 3.2 added Exception#detailed_message, so the else
|
70
|
+
# branch cannot be hit on the current Ruby version.
|
68
71
|
else
|
69
72
|
message = exception.message
|
73
|
+
# :nocov:
|
70
74
|
end
|
71
75
|
string = "#{exception.class}: #{message}\n".dup
|
72
76
|
string << exception.backtrace.map { |l| "\t#{l}" }.join("\n")
|
@@ -401,7 +405,5 @@ module Rack
|
|
401
405
|
</body>
|
402
406
|
</html>
|
403
407
|
HTML
|
404
|
-
|
405
|
-
# :startdoc:
|
406
408
|
end
|
407
409
|
end
|
data/lib/rack/show_status.rb
CHANGED
data/lib/rack/utils.rb
CHANGED
@@ -181,12 +181,16 @@ module Rack
|
|
181
181
|
# doesn't get monkey-patched by rails
|
182
182
|
if defined?(ERB::Escape) && ERB::Escape.instance_method(:html_escape)
|
183
183
|
define_method(:escape_html, ERB::Escape.instance_method(:html_escape))
|
184
|
+
# :nocov:
|
185
|
+
# Ruby 3.2/ERB 4.0 added ERB::Escape#html_escape, so the else
|
186
|
+
# branch cannot be hit on the current Ruby version.
|
184
187
|
else
|
185
188
|
require 'cgi/escape'
|
186
189
|
# Escape ampersands, brackets and quotes to their HTML/XML entities.
|
187
190
|
def escape_html(string)
|
188
191
|
CGI.escapeHTML(string.to_s)
|
189
192
|
end
|
193
|
+
# :nocov:
|
190
194
|
end
|
191
195
|
|
192
196
|
def select_best_encoding(available_encodings, accept_encoding)
|
@@ -254,26 +258,18 @@ module Rack
|
|
254
258
|
parse_cookies_header env[HTTP_COOKIE]
|
255
259
|
end
|
256
260
|
|
257
|
-
# A valid cookie key according to RFC2616.
|
261
|
+
# A valid cookie key according to RFC6265 and RFC2616.
|
258
262
|
# A <cookie-name> can be any US-ASCII characters, except control characters, spaces, or tabs. It also must not contain a separator character like the following: ( ) < > @ , ; : \ " / [ ] ? = { }.
|
259
263
|
VALID_COOKIE_KEY = /\A[!#$%&'*+\-\.\^_`|~0-9a-zA-Z]+\z/.freeze
|
260
264
|
private_constant :VALID_COOKIE_KEY
|
261
265
|
|
262
|
-
private def escape_cookie_key(key)
|
263
|
-
if key =~ VALID_COOKIE_KEY
|
264
|
-
key
|
265
|
-
else
|
266
|
-
warn "Cookie key #{key.inspect} is not valid according to RFC2616; it will be escaped. This behaviour is deprecated and will be removed in a future version of Rack.", uplevel: 2
|
267
|
-
escape(key)
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
266
|
# :call-seq:
|
272
267
|
# set_cookie_header(key, value) -> encoded string
|
273
268
|
#
|
274
269
|
# Generate an encoded string using the provided +key+ and +value+ suitable
|
275
270
|
# for the +set-cookie+ header according to RFC6265. The +value+ may be an
|
276
|
-
# instance of either +String+ or +Hash+.
|
271
|
+
# instance of either +String+ or +Hash+. If the cookie key is invalid (as
|
272
|
+
# defined by RFC6265), an +ArgumentError+ will be raised.
|
277
273
|
#
|
278
274
|
# If the cookie +value+ is an instance of +Hash+, it considers the following
|
279
275
|
# cookie attribute keys: +domain+, +max_age+, +expires+ (must be instance
|
@@ -281,10 +277,6 @@ module Rack
|
|
281
277
|
# details about the interpretation of these fields, consult
|
282
278
|
# [RFC6265 Section 5.2](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2).
|
283
279
|
#
|
284
|
-
# An extra cookie attribute +escape_key+ can be provided to control whether
|
285
|
-
# or not the cookie key is URL encoded. If explicitly set to +false+, the
|
286
|
-
# cookie key name will not be url encoded (escaped). The default is +true+.
|
287
|
-
#
|
288
280
|
# set_cookie_header("myname", "myvalue")
|
289
281
|
# # => "myname=myvalue"
|
290
282
|
#
|
@@ -292,9 +284,12 @@ module Rack
|
|
292
284
|
# # => "myname=myvalue; max-age=10"
|
293
285
|
#
|
294
286
|
def set_cookie_header(key, value)
|
287
|
+
unless key =~ VALID_COOKIE_KEY
|
288
|
+
raise ArgumentError, "invalid cookie key: #{key.inspect}"
|
289
|
+
end
|
290
|
+
|
295
291
|
case value
|
296
292
|
when Hash
|
297
|
-
key = escape_cookie_key(key) unless value[:escape_key] == false
|
298
293
|
domain = "; domain=#{value[:domain]}" if value[:domain]
|
299
294
|
path = "; path=#{value[:path]}" if value[:path]
|
300
295
|
max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
|
@@ -316,8 +311,6 @@ module Rack
|
|
316
311
|
end
|
317
312
|
partitioned = "; partitioned" if value[:partitioned]
|
318
313
|
value = value[:value]
|
319
|
-
else
|
320
|
-
key = escape_cookie_key(key)
|
321
314
|
end
|
322
315
|
|
323
316
|
value = [value] unless Array === value
|
@@ -416,7 +409,7 @@ module Rack
|
|
416
409
|
return nil if size.zero?
|
417
410
|
return nil unless http_range && http_range =~ /bytes=([^;]+)/
|
418
411
|
ranges = []
|
419
|
-
$1.split(
|
412
|
+
$1.split(/,[ \t]*/).each do |range_spec|
|
420
413
|
return nil unless range_spec.include?('-')
|
421
414
|
range = range_spec.split('-')
|
422
415
|
r0, r1 = range[0], range[1]
|
@@ -592,11 +585,9 @@ module Rack
|
|
592
585
|
fallback_code = OBSOLETE_SYMBOLS_TO_STATUS_CODES.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
|
593
586
|
message = "Status code #{status.inspect} is deprecated and will be removed in a future version of Rack."
|
594
587
|
if canonical_symbol = OBSOLETE_SYMBOL_MAPPINGS[status]
|
595
|
-
|
596
|
-
# For now, let's not emit any warning when there is a mapping.
|
597
|
-
else
|
598
|
-
warn message, uplevel: 3
|
588
|
+
message = "#{message} Please use #{canonical_symbol.inspect} instead."
|
599
589
|
end
|
590
|
+
warn message, uplevel: 3
|
600
591
|
fallback_code
|
601
592
|
end
|
602
593
|
else
|