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.

Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +83 -187
  3. data/README.rdoc +1 -1
  4. data/lib/abstract_controller/base.rb +12 -1
  5. data/lib/abstract_controller/collector.rb +1 -1
  6. data/lib/abstract_controller/helpers.rb +1 -3
  7. data/lib/action_controller/metal/allow_browser.rb +1 -1
  8. data/lib/action_controller/metal/conditional_get.rb +5 -1
  9. data/lib/action_controller/metal/http_authentication.rb +4 -1
  10. data/lib/action_controller/metal/instrumentation.rb +1 -2
  11. data/lib/action_controller/metal/live.rb +11 -3
  12. data/lib/action_controller/metal/params_wrapper.rb +3 -3
  13. data/lib/action_controller/metal/rate_limiting.rb +13 -4
  14. data/lib/action_controller/metal/redirecting.rb +3 -4
  15. data/lib/action_controller/metal/renderers.rb +2 -1
  16. data/lib/action_controller/metal/rendering.rb +1 -1
  17. data/lib/action_controller/metal/request_forgery_protection.rb +1 -3
  18. data/lib/action_controller/metal/streaming.rb +5 -84
  19. data/lib/action_controller/metal/strong_parameters.rb +277 -73
  20. data/lib/action_controller/railtie.rb +1 -1
  21. data/lib/action_controller/renderer.rb +1 -0
  22. data/lib/action_controller/test_case.rb +2 -0
  23. data/lib/action_dispatch/constants.rb +0 -6
  24. data/lib/action_dispatch/http/cache.rb +27 -10
  25. data/lib/action_dispatch/http/content_security_policy.rb +13 -25
  26. data/lib/action_dispatch/http/filter_parameters.rb +4 -9
  27. data/lib/action_dispatch/http/filter_redirect.rb +2 -9
  28. data/lib/action_dispatch/http/mime_negotiation.rb +3 -8
  29. data/lib/action_dispatch/http/permissions_policy.rb +2 -0
  30. data/lib/action_dispatch/http/request.rb +7 -6
  31. data/lib/action_dispatch/http/response.rb +1 -15
  32. data/lib/action_dispatch/http/url.rb +2 -2
  33. data/lib/action_dispatch/journey/formatter.rb +3 -8
  34. data/lib/action_dispatch/journey/gtg/transition_table.rb +4 -4
  35. data/lib/action_dispatch/journey/parser.rb +99 -196
  36. data/lib/action_dispatch/journey/scanner.rb +40 -42
  37. data/lib/action_dispatch/middleware/cookies.rb +4 -2
  38. data/lib/action_dispatch/middleware/debug_exceptions.rb +17 -6
  39. data/lib/action_dispatch/middleware/exception_wrapper.rb +3 -3
  40. data/lib/action_dispatch/middleware/executor.rb +2 -5
  41. data/lib/action_dispatch/middleware/public_exceptions.rb +1 -5
  42. data/lib/action_dispatch/middleware/request_id.rb +2 -1
  43. data/lib/action_dispatch/middleware/ssl.rb +13 -3
  44. data/lib/action_dispatch/railtie.rb +2 -0
  45. data/lib/action_dispatch/routing/inspector.rb +1 -1
  46. data/lib/action_dispatch/routing/mapper.rb +30 -22
  47. data/lib/action_dispatch/routing/route_set.rb +18 -6
  48. data/lib/action_dispatch/system_testing/browser.rb +12 -21
  49. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  50. data/lib/action_dispatch/testing/integration.rb +3 -2
  51. data/lib/action_dispatch/testing/request_encoder.rb +9 -9
  52. data/lib/action_dispatch/testing/test_process.rb +2 -1
  53. data/lib/action_dispatch.rb +0 -4
  54. data/lib/action_pack/gem_version.rb +4 -4
  55. metadata +16 -49
  56. data/lib/action_dispatch/journey/parser.y +0 -50
  57. 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. If both headers are
39
- # supplied, both must match, or the request is not considered fresh.
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
- last_modified = if_modified_since
42
- etag = if_none_match
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
- return false unless last_modified || etag
58
+ return false unless last_modified || etag
45
59
 
46
- success = true
47
- success &&= not_modified?(response.last_modified) if last_modified
48
- success &&= etag_matches?(response.etag) if etag
49
- success
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](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/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](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/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](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) is required:
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](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/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(directive, sources, context).join(' ')} 'nonce-#{nonce}'"
327
+ "#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'"
326
328
  else
327
- "#{directive} #{build_directive(directive, sources, context).join(' ')}"
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 validate(directive, sources)
338
- sources.flatten.each do |source|
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
- parts = query_string.split(/([&;])/)
73
- filtered_parts = parts.map do |part|
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
- parts = uri.query.split(/([&;])/)
41
- filtered_parts = parts.map do |part|
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
- # # GET /posts/5.xml
60
- # request.format # => Mime[:xml]
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.downcase.underscore.to_sym
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 {javascript_include_tag}[rdoc-ref:ActionView::Helpers::AssetTagHelper#javascript_include_tag]
240
- # or {stylesheet_link_tag}[rdoc-ref:ActionView::Helpers::AssetTagHelper#stylesheet_link_tag]
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 headers.key?("Transfer-Encoding")
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 headers.key?("Transfer-Encoding")
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 = Response.rack_status_code(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
- if path_params.is_a?(Hash)
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&.key?(key) && !original_options.key?(key))
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.stringify_keys,
111
- string_states: @string_states.stringify_keys,
112
- stdparam_states: @stdparam_states.stringify_keys,
113
- accepting: @accepting.stringify_keys
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