actionpack 7.2.3 → 8.0.0.beta1
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 actionpack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +83 -187
- data/README.rdoc +1 -1
- data/lib/abstract_controller/base.rb +12 -1
- data/lib/abstract_controller/collector.rb +1 -1
- data/lib/abstract_controller/helpers.rb +1 -3
- data/lib/action_controller/metal/allow_browser.rb +1 -1
- data/lib/action_controller/metal/conditional_get.rb +5 -1
- data/lib/action_controller/metal/http_authentication.rb +4 -1
- data/lib/action_controller/metal/instrumentation.rb +1 -2
- data/lib/action_controller/metal/live.rb +11 -3
- data/lib/action_controller/metal/params_wrapper.rb +3 -3
- data/lib/action_controller/metal/rate_limiting.rb +13 -4
- data/lib/action_controller/metal/redirecting.rb +3 -4
- data/lib/action_controller/metal/renderers.rb +2 -1
- data/lib/action_controller/metal/rendering.rb +1 -1
- data/lib/action_controller/metal/request_forgery_protection.rb +1 -3
- data/lib/action_controller/metal/streaming.rb +5 -84
- data/lib/action_controller/metal/strong_parameters.rb +277 -73
- data/lib/action_controller/railtie.rb +1 -1
- data/lib/action_controller/renderer.rb +1 -0
- data/lib/action_controller/test_case.rb +2 -0
- data/lib/action_dispatch/constants.rb +0 -6
- data/lib/action_dispatch/http/cache.rb +27 -10
- data/lib/action_dispatch/http/content_security_policy.rb +13 -25
- data/lib/action_dispatch/http/filter_parameters.rb +4 -9
- data/lib/action_dispatch/http/filter_redirect.rb +2 -9
- data/lib/action_dispatch/http/mime_negotiation.rb +3 -8
- data/lib/action_dispatch/http/permissions_policy.rb +2 -0
- data/lib/action_dispatch/http/request.rb +7 -6
- data/lib/action_dispatch/http/response.rb +1 -15
- data/lib/action_dispatch/http/url.rb +2 -2
- data/lib/action_dispatch/journey/formatter.rb +3 -8
- data/lib/action_dispatch/journey/gtg/transition_table.rb +4 -4
- data/lib/action_dispatch/journey/parser.rb +99 -196
- data/lib/action_dispatch/journey/scanner.rb +40 -42
- data/lib/action_dispatch/middleware/cookies.rb +4 -2
- data/lib/action_dispatch/middleware/debug_exceptions.rb +17 -6
- data/lib/action_dispatch/middleware/exception_wrapper.rb +3 -3
- data/lib/action_dispatch/middleware/executor.rb +2 -5
- data/lib/action_dispatch/middleware/public_exceptions.rb +1 -5
- data/lib/action_dispatch/middleware/request_id.rb +2 -1
- data/lib/action_dispatch/middleware/ssl.rb +13 -3
- data/lib/action_dispatch/railtie.rb +2 -0
- data/lib/action_dispatch/routing/inspector.rb +1 -1
- data/lib/action_dispatch/routing/mapper.rb +30 -22
- data/lib/action_dispatch/routing/route_set.rb +18 -6
- data/lib/action_dispatch/system_testing/browser.rb +12 -21
- data/lib/action_dispatch/testing/assertion_response.rb +1 -1
- data/lib/action_dispatch/testing/integration.rb +3 -2
- data/lib/action_dispatch/testing/request_encoder.rb +9 -9
- data/lib/action_dispatch/testing/test_process.rb +2 -1
- data/lib/action_dispatch.rb +0 -4
- data/lib/action_pack/gem_version.rb +4 -4
- metadata +16 -49
- data/lib/action_dispatch/journey/parser.y +0 -50
- data/lib/action_dispatch/journey/parser_extras.rb +0 -33
|
@@ -30,11 +30,5 @@ module ActionDispatch
|
|
|
30
30
|
SERVER_TIMING = "server-timing"
|
|
31
31
|
STRICT_TRANSPORT_SECURITY = "strict-transport-security"
|
|
32
32
|
end
|
|
33
|
-
|
|
34
|
-
if Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3.1")
|
|
35
|
-
UNPROCESSABLE_CONTENT = :unprocessable_entity
|
|
36
|
-
else
|
|
37
|
-
UNPROCESSABLE_CONTENT = :unprocessable_content
|
|
38
|
-
end
|
|
39
33
|
end
|
|
40
34
|
end
|
|
@@ -9,6 +9,8 @@ module ActionDispatch
|
|
|
9
9
|
HTTP_IF_MODIFIED_SINCE = "HTTP_IF_MODIFIED_SINCE"
|
|
10
10
|
HTTP_IF_NONE_MATCH = "HTTP_IF_NONE_MATCH"
|
|
11
11
|
|
|
12
|
+
mattr_accessor :strict_freshness, default: false
|
|
13
|
+
|
|
12
14
|
def if_modified_since
|
|
13
15
|
if since = get_header(HTTP_IF_MODIFIED_SINCE)
|
|
14
16
|
Time.rfc2822(since) rescue nil
|
|
@@ -34,19 +36,32 @@ module ActionDispatch
|
|
|
34
36
|
end
|
|
35
37
|
end
|
|
36
38
|
|
|
37
|
-
# Check response freshness (`Last-Modified` and ETag) against request
|
|
38
|
-
# `If-Modified-Since` and `If-None-Match` conditions.
|
|
39
|
-
# supplied,
|
|
39
|
+
# Check response freshness (`Last-Modified` and `ETag`) against request
|
|
40
|
+
# `If-Modified-Since` and `If-None-Match` conditions.
|
|
41
|
+
# If both headers are supplied, based on configuration, either `ETag` is preferred over `Last-Modified`
|
|
42
|
+
# or both are considered equally. You can adjust the preference with
|
|
43
|
+
# `config.action_dispatch.strict_freshness`.
|
|
44
|
+
# Reference: http://tools.ietf.org/html/rfc7232#section-6
|
|
40
45
|
def fresh?(response)
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
if Request.strict_freshness
|
|
47
|
+
if if_none_match
|
|
48
|
+
etag_matches?(response.etag)
|
|
49
|
+
elsif if_modified_since
|
|
50
|
+
not_modified?(response.last_modified)
|
|
51
|
+
else
|
|
52
|
+
false
|
|
53
|
+
end
|
|
54
|
+
else
|
|
55
|
+
last_modified = if_modified_since
|
|
56
|
+
etag = if_none_match
|
|
43
57
|
|
|
44
|
-
|
|
58
|
+
return false unless last_modified || etag
|
|
45
59
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
success = true
|
|
61
|
+
success &&= not_modified?(response.last_modified) if last_modified
|
|
62
|
+
success &&= etag_matches?(response.etag) if etag
|
|
63
|
+
success
|
|
64
|
+
end
|
|
50
65
|
end
|
|
51
66
|
end
|
|
52
67
|
|
|
@@ -171,6 +186,7 @@ module ActionDispatch
|
|
|
171
186
|
PUBLIC = "public"
|
|
172
187
|
PRIVATE = "private"
|
|
173
188
|
MUST_REVALIDATE = "must-revalidate"
|
|
189
|
+
IMMUTABLE = "immutable"
|
|
174
190
|
|
|
175
191
|
def handle_conditional_get!
|
|
176
192
|
# Normally default cache control setting is handled by ETag middleware. But, if
|
|
@@ -221,6 +237,7 @@ module ActionDispatch
|
|
|
221
237
|
options << MUST_REVALIDATE if control[:must_revalidate]
|
|
222
238
|
options << "stale-while-revalidate=#{stale_while_revalidate.to_i}" if stale_while_revalidate
|
|
223
239
|
options << "stale-if-error=#{stale_if_error.to_i}" if stale_if_error
|
|
240
|
+
options << IMMUTABLE if control[:immutable]
|
|
224
241
|
options.concat(extras) if extras
|
|
225
242
|
end
|
|
226
243
|
|
|
@@ -8,7 +8,8 @@ require "active_support/core_ext/array/wrap"
|
|
|
8
8
|
module ActionDispatch # :nodoc:
|
|
9
9
|
# # Action Dispatch Content Security Policy
|
|
10
10
|
#
|
|
11
|
-
# Configures the HTTP [Content-Security-Policy]
|
|
11
|
+
# Configures the HTTP [Content-Security-Policy]
|
|
12
|
+
# (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)
|
|
12
13
|
# response header to help protect against XSS and
|
|
13
14
|
# injection attacks.
|
|
14
15
|
#
|
|
@@ -26,9 +27,6 @@ module ActionDispatch # :nodoc:
|
|
|
26
27
|
# policy.report_uri "/csp-violation-report-endpoint"
|
|
27
28
|
# end
|
|
28
29
|
class ContentSecurityPolicy
|
|
29
|
-
class InvalidDirectiveError < StandardError
|
|
30
|
-
end
|
|
31
|
-
|
|
32
30
|
class Middleware
|
|
33
31
|
def initialize(app)
|
|
34
32
|
@app = app
|
|
@@ -128,6 +126,7 @@ module ActionDispatch # :nodoc:
|
|
|
128
126
|
MAPPINGS = {
|
|
129
127
|
self: "'self'",
|
|
130
128
|
unsafe_eval: "'unsafe-eval'",
|
|
129
|
+
wasm_unsafe_eval: "'wasm-unsafe-eval'",
|
|
131
130
|
unsafe_hashes: "'unsafe-hashes'",
|
|
132
131
|
unsafe_inline: "'unsafe-inline'",
|
|
133
132
|
none: "'none'",
|
|
@@ -228,7 +227,8 @@ module ActionDispatch # :nodoc:
|
|
|
228
227
|
end
|
|
229
228
|
end
|
|
230
229
|
|
|
231
|
-
# Enable the [report-uri]
|
|
230
|
+
# Enable the [report-uri]
|
|
231
|
+
# (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri)
|
|
232
232
|
# directive. Violation reports will be sent to the
|
|
233
233
|
# specified URI:
|
|
234
234
|
#
|
|
@@ -238,7 +238,8 @@ module ActionDispatch # :nodoc:
|
|
|
238
238
|
@directives["report-uri"] = [uri]
|
|
239
239
|
end
|
|
240
240
|
|
|
241
|
-
# Specify asset types for which [Subresource Integrity]
|
|
241
|
+
# Specify asset types for which [Subresource Integrity]
|
|
242
|
+
# (https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) is required:
|
|
242
243
|
#
|
|
243
244
|
# policy.require_sri_for :script, :style
|
|
244
245
|
#
|
|
@@ -254,7 +255,8 @@ module ActionDispatch # :nodoc:
|
|
|
254
255
|
end
|
|
255
256
|
end
|
|
256
257
|
|
|
257
|
-
# Specify whether a [sandbox]
|
|
258
|
+
# Specify whether a [sandbox]
|
|
259
|
+
# (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox)
|
|
258
260
|
# should be enabled for the requested resource:
|
|
259
261
|
#
|
|
260
262
|
# policy.sandbox
|
|
@@ -322,9 +324,9 @@ module ActionDispatch # :nodoc:
|
|
|
322
324
|
@directives.map do |directive, sources|
|
|
323
325
|
if sources.is_a?(Array)
|
|
324
326
|
if nonce && nonce_directive?(directive, nonce_directives)
|
|
325
|
-
"#{directive} #{build_directive(
|
|
327
|
+
"#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'"
|
|
326
328
|
else
|
|
327
|
-
"#{directive} #{build_directive(
|
|
329
|
+
"#{directive} #{build_directive(sources, context).join(' ')}"
|
|
328
330
|
end
|
|
329
331
|
elsif sources
|
|
330
332
|
directive
|
|
@@ -334,22 +336,8 @@ module ActionDispatch # :nodoc:
|
|
|
334
336
|
end
|
|
335
337
|
end
|
|
336
338
|
|
|
337
|
-
def
|
|
338
|
-
sources.
|
|
339
|
-
if source.include?(";") || source != source.gsub(/[[:space:]]/, "")
|
|
340
|
-
raise InvalidDirectiveError, <<~MSG.squish
|
|
341
|
-
Invalid Content Security Policy #{directive}: "#{source}".
|
|
342
|
-
Directive values must not contain whitespace or semicolons.
|
|
343
|
-
Please use multiple arguments or other directive methods instead.
|
|
344
|
-
MSG
|
|
345
|
-
end
|
|
346
|
-
end
|
|
347
|
-
end
|
|
348
|
-
|
|
349
|
-
def build_directive(directive, sources, context)
|
|
350
|
-
resolved_sources = sources.map { |source| resolve_source(source, context) }
|
|
351
|
-
|
|
352
|
-
validate(directive, resolved_sources)
|
|
339
|
+
def build_directive(sources, context)
|
|
340
|
+
sources.map { |source| resolve_source(source, context) }
|
|
353
341
|
end
|
|
354
342
|
|
|
355
343
|
def resolve_source(source, context)
|
|
@@ -68,17 +68,12 @@ module ActionDispatch
|
|
|
68
68
|
ActiveSupport::ParameterFilter.new(filters)
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
+
KV_RE = "[^&;=]+"
|
|
72
|
+
PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
|
|
71
73
|
def filtered_query_string # :doc:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if part.include?("=")
|
|
75
|
-
key, value = part.split("=", 2)
|
|
76
|
-
parameter_filter.filter(key => value).first.join("=")
|
|
77
|
-
else
|
|
78
|
-
part
|
|
79
|
-
end
|
|
74
|
+
query_string.gsub(PAIR_RE) do |_|
|
|
75
|
+
parameter_filter.filter($1 => $2).first.join("=")
|
|
80
76
|
end
|
|
81
|
-
filtered_parts.join("")
|
|
82
77
|
end
|
|
83
78
|
end
|
|
84
79
|
end
|
|
@@ -37,16 +37,9 @@ module ActionDispatch
|
|
|
37
37
|
def parameter_filtered_location
|
|
38
38
|
uri = URI.parse(location)
|
|
39
39
|
unless uri.query.nil? || uri.query.empty?
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if part.include?("=")
|
|
43
|
-
key, value = part.split("=", 2)
|
|
44
|
-
request.parameter_filter.filter(key => value).first.join("=")
|
|
45
|
-
else
|
|
46
|
-
part
|
|
47
|
-
end
|
|
40
|
+
uri.query.gsub!(FilterParameters::PAIR_RE) do
|
|
41
|
+
request.parameter_filter.filter($1 => $2).first.join("=")
|
|
48
42
|
end
|
|
49
|
-
uri.query = filtered_parts.join("")
|
|
50
43
|
end
|
|
51
44
|
uri.to_s
|
|
52
45
|
rescue URI::Error
|
|
@@ -56,14 +56,9 @@ module ActionDispatch
|
|
|
56
56
|
|
|
57
57
|
# Returns the MIME type for the format used in the request.
|
|
58
58
|
#
|
|
59
|
-
#
|
|
60
|
-
# request.format
|
|
61
|
-
#
|
|
62
|
-
# # GET /posts/5.xhtml
|
|
63
|
-
# request.format # => Mime[:html]
|
|
64
|
-
#
|
|
65
|
-
# # GET /posts/5
|
|
66
|
-
# request.format # => Mime[:html] or Mime[:js], or request.accepts.first
|
|
59
|
+
# GET /posts/5.xml | request.format => Mime[:xml]
|
|
60
|
+
# GET /posts/5.xhtml | request.format => Mime[:html]
|
|
61
|
+
# GET /posts/5 | request.format => Mime[:html] or Mime[:js], or request.accepts.first
|
|
67
62
|
#
|
|
68
63
|
def format(_view_path = nil)
|
|
69
64
|
formats.first || Mime::NullType.instance
|
|
@@ -86,12 +86,14 @@ module ActionDispatch # :nodoc:
|
|
|
86
86
|
ambient_light_sensor: "ambient-light-sensor",
|
|
87
87
|
autoplay: "autoplay",
|
|
88
88
|
camera: "camera",
|
|
89
|
+
display_capture: "display-capture",
|
|
89
90
|
encrypted_media: "encrypted-media",
|
|
90
91
|
fullscreen: "fullscreen",
|
|
91
92
|
geolocation: "geolocation",
|
|
92
93
|
gyroscope: "gyroscope",
|
|
93
94
|
hid: "hid",
|
|
94
95
|
idle_detection: "idle-detection",
|
|
96
|
+
keyboard_map: "keyboard-map",
|
|
95
97
|
magnetometer: "magnetometer",
|
|
96
98
|
microphone: "microphone",
|
|
97
99
|
midi: "midi",
|
|
@@ -55,6 +55,8 @@ module ActionDispatch
|
|
|
55
55
|
METHOD
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
+
TRANSFER_ENCODING = "HTTP_TRANSFER_ENCODING" # :nodoc:
|
|
59
|
+
|
|
58
60
|
def self.empty
|
|
59
61
|
new({})
|
|
60
62
|
end
|
|
@@ -132,7 +134,7 @@ module ActionDispatch
|
|
|
132
134
|
|
|
133
135
|
# Populate the HTTP method lookup cache.
|
|
134
136
|
HTTP_METHODS.each { |method|
|
|
135
|
-
HTTP_METHOD_LOOKUP[method] = method.
|
|
137
|
+
HTTP_METHOD_LOOKUP[method] = method.underscore.to_sym
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
alias raw_request_method request_method # :nodoc:
|
|
@@ -236,9 +238,8 @@ module ActionDispatch
|
|
|
236
238
|
#
|
|
237
239
|
# send_early_hints("link" => "</style.css>; rel=preload; as=style,</script.js>; rel=preload")
|
|
238
240
|
#
|
|
239
|
-
# If you are using
|
|
240
|
-
#
|
|
241
|
-
# the Early Hints headers are included by default if supported.
|
|
241
|
+
# If you are using `javascript_include_tag` or `stylesheet_link_tag` the Early
|
|
242
|
+
# Hints headers are included by default if supported.
|
|
242
243
|
def send_early_hints(links)
|
|
243
244
|
env["rack.early_hints"]&.call(links)
|
|
244
245
|
end
|
|
@@ -283,7 +284,7 @@ module ActionDispatch
|
|
|
283
284
|
|
|
284
285
|
# Returns the content length of the request as an integer.
|
|
285
286
|
def content_length
|
|
286
|
-
return raw_post.bytesize if
|
|
287
|
+
return raw_post.bytesize if has_header?(TRANSFER_ENCODING)
|
|
287
288
|
super.to_i
|
|
288
289
|
end
|
|
289
290
|
|
|
@@ -469,7 +470,7 @@ module ActionDispatch
|
|
|
469
470
|
def read_body_stream
|
|
470
471
|
if body_stream
|
|
471
472
|
reset_stream(body_stream) do
|
|
472
|
-
if
|
|
473
|
+
if has_header?(TRANSFER_ENCODING)
|
|
473
474
|
body_stream.read # Read body stream until EOF if "Transfer-Encoding" is present
|
|
474
475
|
else
|
|
475
476
|
body_stream.read(content_length)
|
|
@@ -46,20 +46,6 @@ module ActionDispatch # :nodoc:
|
|
|
46
46
|
Headers = ::Rack::Utils::HeaderHash
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
class << self
|
|
50
|
-
if ActionDispatch::Constants::UNPROCESSABLE_CONTENT == :unprocessable_content
|
|
51
|
-
def rack_status_code(status) # :nodoc:
|
|
52
|
-
status = :unprocessable_content if status == :unprocessable_entity
|
|
53
|
-
Rack::Utils.status_code(status)
|
|
54
|
-
end
|
|
55
|
-
else
|
|
56
|
-
def rack_status_code(status) # :nodoc:
|
|
57
|
-
status = :unprocessable_entity if status == :unprocessable_content
|
|
58
|
-
Rack::Utils.status_code(status)
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
49
|
# To be deprecated:
|
|
64
50
|
Header = Headers
|
|
65
51
|
|
|
@@ -259,7 +245,7 @@ module ActionDispatch # :nodoc:
|
|
|
259
245
|
|
|
260
246
|
# Sets the HTTP status code.
|
|
261
247
|
def status=(status)
|
|
262
|
-
@status =
|
|
248
|
+
@status = Rack::Utils.status_code(status)
|
|
263
249
|
end
|
|
264
250
|
|
|
265
251
|
# Sets the HTTP response's content MIME type. For example, in the controller you
|
|
@@ -272,7 +272,7 @@ module ActionDispatch
|
|
|
272
272
|
end
|
|
273
273
|
end
|
|
274
274
|
|
|
275
|
-
# Returns whether this request is using the standard port
|
|
275
|
+
# Returns whether this request is using the standard port
|
|
276
276
|
#
|
|
277
277
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
|
|
278
278
|
# req.standard_port? # => true
|
|
@@ -307,7 +307,7 @@ module ActionDispatch
|
|
|
307
307
|
standard_port? ? "" : ":#{port}"
|
|
308
308
|
end
|
|
309
309
|
|
|
310
|
-
# Returns the requested port, such as 8080, based on SERVER_PORT
|
|
310
|
+
# Returns the requested port, such as 8080, based on SERVER_PORT
|
|
311
311
|
#
|
|
312
312
|
# req = ActionDispatch::Request.new 'SERVER_PORT' => '80'
|
|
313
313
|
# req.server_port # => 80
|
|
@@ -60,13 +60,8 @@ module ActionDispatch
|
|
|
60
60
|
|
|
61
61
|
def generate(name, options, path_parameters)
|
|
62
62
|
original_options = options.dup
|
|
63
|
-
path_params = options.delete(:path_params)
|
|
64
|
-
|
|
65
|
-
options = path_params.merge(options)
|
|
66
|
-
else
|
|
67
|
-
path_params = nil
|
|
68
|
-
options = options.dup
|
|
69
|
-
end
|
|
63
|
+
path_params = options.delete(:path_params) || {}
|
|
64
|
+
options = path_params.merge(options)
|
|
70
65
|
constraints = path_parameters.merge(options)
|
|
71
66
|
missing_keys = nil
|
|
72
67
|
|
|
@@ -84,7 +79,7 @@ module ActionDispatch
|
|
|
84
79
|
# top-level params' normal behavior of generating query_params should be
|
|
85
80
|
# preserved even if the same key is also a bind_param
|
|
86
81
|
parameterized_parts.key?(key) || route.defaults.key?(key) ||
|
|
87
|
-
(path_params
|
|
82
|
+
(path_params.key?(key) && !original_options.key?(key))
|
|
88
83
|
end
|
|
89
84
|
|
|
90
85
|
defaults = route.defaults
|
|
@@ -107,10 +107,10 @@ module ActionDispatch
|
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
{
|
|
110
|
-
regexp_states: simple_regexp
|
|
111
|
-
string_states: @string_states
|
|
112
|
-
stdparam_states: @stdparam_states
|
|
113
|
-
accepting: @accepting
|
|
110
|
+
regexp_states: simple_regexp,
|
|
111
|
+
string_states: @string_states,
|
|
112
|
+
stdparam_states: @stdparam_states,
|
|
113
|
+
accepting: @accepting
|
|
114
114
|
}
|
|
115
115
|
end
|
|
116
116
|
|