actionpack 7.0.4.3 → 7.1.3.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +380 -284
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -4
  5. data/lib/abstract_controller/base.rb +20 -11
  6. data/lib/abstract_controller/caching/fragments.rb +2 -0
  7. data/lib/abstract_controller/callbacks.rb +31 -6
  8. data/lib/abstract_controller/deprecator.rb +7 -0
  9. data/lib/abstract_controller/helpers.rb +75 -28
  10. data/lib/abstract_controller/railties/routes_helpers.rb +1 -16
  11. data/lib/abstract_controller/rendering.rb +12 -14
  12. data/lib/abstract_controller/translation.rb +9 -6
  13. data/lib/abstract_controller/url_for.rb +2 -0
  14. data/lib/abstract_controller.rb +6 -0
  15. data/lib/action_controller/api.rb +6 -4
  16. data/lib/action_controller/base.rb +3 -17
  17. data/lib/action_controller/caching.rb +2 -0
  18. data/lib/action_controller/deprecator.rb +7 -0
  19. data/lib/action_controller/form_builder.rb +2 -0
  20. data/lib/action_controller/log_subscriber.rb +16 -4
  21. data/lib/action_controller/metal/basic_implicit_render.rb +3 -1
  22. data/lib/action_controller/metal/conditional_get.rb +121 -123
  23. data/lib/action_controller/metal/content_security_policy.rb +5 -5
  24. data/lib/action_controller/metal/data_streaming.rb +20 -18
  25. data/lib/action_controller/metal/default_headers.rb +2 -0
  26. data/lib/action_controller/metal/etag_with_flash.rb +3 -1
  27. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
  28. data/lib/action_controller/metal/exceptions.rb +8 -0
  29. data/lib/action_controller/metal/head.rb +9 -7
  30. data/lib/action_controller/metal/helpers.rb +3 -14
  31. data/lib/action_controller/metal/http_authentication.rb +17 -8
  32. data/lib/action_controller/metal/implicit_render.rb +5 -3
  33. data/lib/action_controller/metal/instrumentation.rb +8 -1
  34. data/lib/action_controller/metal/live.rb +25 -1
  35. data/lib/action_controller/metal/mime_responds.rb +2 -2
  36. data/lib/action_controller/metal/params_wrapper.rb +4 -2
  37. data/lib/action_controller/metal/permissions_policy.rb +2 -2
  38. data/lib/action_controller/metal/redirecting.rb +25 -8
  39. data/lib/action_controller/metal/renderers.rb +4 -4
  40. data/lib/action_controller/metal/rendering.rb +114 -9
  41. data/lib/action_controller/metal/request_forgery_protection.rb +144 -53
  42. data/lib/action_controller/metal/rescue.rb +6 -3
  43. data/lib/action_controller/metal/streaming.rb +71 -31
  44. data/lib/action_controller/metal/strong_parameters.rb +158 -101
  45. data/lib/action_controller/metal/url_for.rb +9 -4
  46. data/lib/action_controller/metal.rb +79 -21
  47. data/lib/action_controller/railtie.rb +24 -10
  48. data/lib/action_controller/renderer.rb +99 -85
  49. data/lib/action_controller/test_case.rb +15 -5
  50. data/lib/action_controller.rb +8 -1
  51. data/lib/action_dispatch/constants.rb +32 -0
  52. data/lib/action_dispatch/deprecator.rb +7 -0
  53. data/lib/action_dispatch/http/cache.rb +8 -10
  54. data/lib/action_dispatch/http/content_security_policy.rb +14 -9
  55. data/lib/action_dispatch/http/filter_parameters.rb +14 -28
  56. data/lib/action_dispatch/http/headers.rb +3 -1
  57. data/lib/action_dispatch/http/mime_negotiation.rb +22 -22
  58. data/lib/action_dispatch/http/mime_type.rb +35 -12
  59. data/lib/action_dispatch/http/mime_types.rb +3 -1
  60. data/lib/action_dispatch/http/parameters.rb +1 -1
  61. data/lib/action_dispatch/http/permissions_policy.rb +38 -23
  62. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  63. data/lib/action_dispatch/http/request.rb +63 -30
  64. data/lib/action_dispatch/http/response.rb +80 -63
  65. data/lib/action_dispatch/http/upload.rb +15 -2
  66. data/lib/action_dispatch/journey/formatter.rb +8 -2
  67. data/lib/action_dispatch/journey/path/pattern.rb +14 -14
  68. data/lib/action_dispatch/journey/route.rb +3 -2
  69. data/lib/action_dispatch/journey/router.rb +9 -8
  70. data/lib/action_dispatch/journey/routes.rb +2 -2
  71. data/lib/action_dispatch/log_subscriber.rb +23 -0
  72. data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -6
  73. data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
  74. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  75. data/lib/action_dispatch/middleware/cookies.rb +85 -102
  76. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -25
  77. data/lib/action_dispatch/middleware/debug_locks.rb +4 -1
  78. data/lib/action_dispatch/middleware/debug_view.rb +7 -2
  79. data/lib/action_dispatch/middleware/exception_wrapper.rb +186 -27
  80. data/lib/action_dispatch/middleware/executor.rb +1 -1
  81. data/lib/action_dispatch/middleware/flash.rb +7 -0
  82. data/lib/action_dispatch/middleware/host_authorization.rb +18 -8
  83. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
  84. data/lib/action_dispatch/middleware/reloader.rb +7 -5
  85. data/lib/action_dispatch/middleware/remote_ip.rb +21 -20
  86. data/lib/action_dispatch/middleware/request_id.rb +4 -2
  87. data/lib/action_dispatch/middleware/server_timing.rb +4 -4
  88. data/lib/action_dispatch/middleware/session/abstract_store.rb +5 -0
  89. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  90. data/lib/action_dispatch/middleware/session/cookie_store.rb +11 -5
  91. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
  92. data/lib/action_dispatch/middleware/show_exceptions.rb +25 -18
  93. data/lib/action_dispatch/middleware/ssl.rb +18 -6
  94. data/lib/action_dispatch/middleware/stack.rb +7 -2
  95. data/lib/action_dispatch/middleware/static.rb +14 -10
  96. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  97. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
  98. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  99. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -3
  100. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -3
  101. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
  102. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  103. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
  104. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
  105. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
  106. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  107. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
  108. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  109. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  110. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  111. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -41
  112. data/lib/action_dispatch/railtie.rb +14 -4
  113. data/lib/action_dispatch/request/session.rb +16 -6
  114. data/lib/action_dispatch/request/utils.rb +8 -3
  115. data/lib/action_dispatch/routing/inspector.rb +54 -6
  116. data/lib/action_dispatch/routing/mapper.rb +58 -24
  117. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  118. data/lib/action_dispatch/routing/redirection.rb +15 -6
  119. data/lib/action_dispatch/routing/route_set.rb +52 -22
  120. data/lib/action_dispatch/routing/routes_proxy.rb +10 -15
  121. data/lib/action_dispatch/routing/url_for.rb +26 -22
  122. data/lib/action_dispatch/routing.rb +7 -7
  123. data/lib/action_dispatch/system_test_case.rb +3 -3
  124. data/lib/action_dispatch/system_testing/browser.rb +20 -19
  125. data/lib/action_dispatch/system_testing/driver.rb +14 -22
  126. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +27 -16
  127. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  128. data/lib/action_dispatch/testing/assertions/response.rb +14 -7
  129. data/lib/action_dispatch/testing/assertions/routing.rb +67 -28
  130. data/lib/action_dispatch/testing/assertions.rb +3 -1
  131. data/lib/action_dispatch/testing/integration.rb +27 -17
  132. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  133. data/lib/action_dispatch/testing/test_process.rb +4 -3
  134. data/lib/action_dispatch/testing/test_request.rb +1 -1
  135. data/lib/action_dispatch/testing/test_response.rb +23 -9
  136. data/lib/action_dispatch.rb +37 -4
  137. data/lib/action_pack/gem_version.rb +4 -4
  138. data/lib/action_pack/version.rb +1 -1
  139. data/lib/action_pack.rb +1 -1
  140. metadata +65 -29
@@ -32,8 +32,8 @@ module ActionDispatch
32
32
  end
33
33
  end
34
34
 
35
- # Check response freshness (Last-Modified and ETag) against request
36
- # If-Modified-Since and If-None-Match conditions. If both headers are
35
+ # Check response freshness (+Last-Modified+ and ETag) against request
36
+ # +If-Modified-Since+ and +If-None-Match+ conditions. If both headers are
37
37
  # supplied, both must match, or the request is not considered fresh.
38
38
  def fresh?(response)
39
39
  last_modified = if_modified_since
@@ -81,8 +81,8 @@ module ActionDispatch
81
81
 
82
82
  # This method sets a weak ETag validator on the response so browsers
83
83
  # and proxies may cache the response, keyed on the ETag. On subsequent
84
- # requests, the If-None-Match header is set to the cached ETag. If it
85
- # matches the current ETag, we can return a 304 Not Modified response
84
+ # requests, the +If-None-Match+ header is set to the cached ETag. If it
85
+ # matches the current ETag, we can return a <tt>304 Not Modified</tt> response
86
86
  # with no body, letting the browser or proxy know that their cache is
87
87
  # current. Big savings in request time and network bandwidth.
88
88
  #
@@ -92,7 +92,7 @@ module ActionDispatch
92
92
  # is viewing.
93
93
  #
94
94
  # Strong ETags are considered byte-for-byte identical. They allow a
95
- # browser or proxy cache to support Range requests, useful for paging
95
+ # browser or proxy cache to support +Range+ requests, useful for paging
96
96
  # through a PDF file or scrubbing through a video. Some CDNs only
97
97
  # support strong ETags and will ignore weak ETags entirely.
98
98
  #
@@ -112,12 +112,12 @@ module ActionDispatch
112
112
 
113
113
  def etag?; etag; end
114
114
 
115
- # True if an ETag is set and it's a weak validator (preceded with W/)
115
+ # True if an ETag is set, and it's a weak validator (preceded with <tt>W/</tt>).
116
116
  def weak_etag?
117
117
  etag? && etag.start_with?('W/"')
118
118
  end
119
119
 
120
- # True if an ETag is set and it isn't a weak validator (not preceded with W/)
120
+ # True if an ETag is set, and it isn't a weak validator (not preceded with <tt>W/</tt>).
121
121
  def strong_etag?
122
122
  etag? && !weak_etag?
123
123
  end
@@ -138,15 +138,13 @@ module ActionDispatch
138
138
  def cache_control_segments
139
139
  if cache_control = _cache_control
140
140
  cache_control.delete(" ").split(",")
141
- else
142
- []
143
141
  end
144
142
  end
145
143
 
146
144
  def cache_control_headers
147
145
  cache_control = {}
148
146
 
149
- cache_control_segments.each do |segment|
147
+ cache_control_segments&.each do |segment|
150
148
  directive, argument = segment.split("=", 2)
151
149
 
152
150
  if SPECIAL_KEYS.include? directive
@@ -4,6 +4,8 @@ require "active_support/core_ext/object/deep_dup"
4
4
  require "active_support/core_ext/array/wrap"
5
5
 
6
6
  module ActionDispatch # :nodoc:
7
+ # = Action Dispatch Content Security Policy
8
+ #
7
9
  # Configures the HTTP
8
10
  # {Content-Security-Policy}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy]
9
11
  # response header to help protect against XSS and injection attacks.
@@ -23,20 +25,21 @@ module ActionDispatch # :nodoc:
23
25
  # end
24
26
  class ContentSecurityPolicy
25
27
  class Middleware
26
- CONTENT_TYPE = "Content-Type"
27
- POLICY = "Content-Security-Policy"
28
- POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only"
29
-
30
28
  def initialize(app)
31
29
  @app = app
32
30
  end
33
31
 
34
32
  def call(env)
35
- request = ActionDispatch::Request.new env
36
- _, headers, _ = response = @app.call(env)
33
+ status, headers, _ = response = @app.call(env)
34
+
35
+ # Returning CSP headers with a 304 Not Modified is harmful, since nonces in the new
36
+ # CSP headers might not match nonces in the cached HTML.
37
+ return response if status == 304
37
38
 
38
39
  return response if policy_present?(headers)
39
40
 
41
+ request = ActionDispatch::Request.new env
42
+
40
43
  if policy = request.content_security_policy
41
44
  nonce = request.content_security_policy_nonce
42
45
  nonce_directives = request.content_security_policy_nonce_directives
@@ -50,14 +53,15 @@ module ActionDispatch # :nodoc:
50
53
  private
51
54
  def header_name(request)
52
55
  if request.content_security_policy_report_only
53
- POLICY_REPORT_ONLY
56
+ ActionDispatch::Constants::CONTENT_SECURITY_POLICY_REPORT_ONLY
54
57
  else
55
- POLICY
58
+ ActionDispatch::Constants::CONTENT_SECURITY_POLICY
56
59
  end
57
60
  end
58
61
 
59
62
  def policy_present?(headers)
60
- headers[POLICY] || headers[POLICY_REPORT_ONLY]
63
+ headers[ActionDispatch::Constants::CONTENT_SECURITY_POLICY] ||
64
+ headers[ActionDispatch::Constants::CONTENT_SECURITY_POLICY_REPORT_ONLY]
61
65
  end
62
66
  end
63
67
 
@@ -119,6 +123,7 @@ module ActionDispatch # :nodoc:
119
123
  MAPPINGS = {
120
124
  self: "'self'",
121
125
  unsafe_eval: "'unsafe-eval'",
126
+ unsafe_hashes: "'unsafe-hashes'",
122
127
  unsafe_inline: "'unsafe-inline'",
123
128
  none: "'none'",
124
129
  http: "http:",
@@ -4,33 +4,15 @@ require "active_support/parameter_filter"
4
4
 
5
5
  module ActionDispatch
6
6
  module Http
7
- # Allows you to specify sensitive parameters which will be replaced from
8
- # the request log by looking in the query string of the request and all
9
- # sub-hashes of the params hash to filter. Filtering only certain sub-keys
10
- # from a hash is possible by using the dot notation: 'credit_card.number'.
11
- # If a block is given, each key and value of the params hash and all
12
- # sub-hashes are passed to it, where the value or the key can be replaced using
13
- # String#replace or similar methods.
7
+ # = Action Dispatch HTTP Filter Parameters
14
8
  #
15
- # env["action_dispatch.parameter_filter"] = [:password]
16
- # => replaces the value to all keys matching /password/i with "[FILTERED]"
9
+ # Allows you to specify sensitive query string and POST parameters to filter
10
+ # from the request log.
17
11
  #
12
+ # # Replaces values with "[FILTERED]" for keys that match /foo|bar/i.
18
13
  # env["action_dispatch.parameter_filter"] = [:foo, "bar"]
19
- # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
20
14
  #
21
- # env["action_dispatch.parameter_filter"] = [ /\Apin\z/i, /\Apin_/i ]
22
- # => replaces the value for the exact (case-insensitive) key 'pin' and all
23
- # (case-insensitive) keys beginning with 'pin_', with "[FILTERED]"
24
- # Does not match keys with 'pin' as a substring, such as 'shipping_id'.
25
- #
26
- # env["action_dispatch.parameter_filter"] = [ "credit_card.code" ]
27
- # => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not
28
- # change { file: { code: "xxxx"} }
29
- #
30
- # env["action_dispatch.parameter_filter"] = -> (k, v) do
31
- # v.reverse! if k.match?(/secret/i)
32
- # end
33
- # => reverses the value to all keys matching /secret/i
15
+ # For more information about filter behavior, see ActiveSupport::ParameterFilter.
34
16
  module FilterParameters
35
17
  ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
36
18
  NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new # :nodoc:
@@ -41,6 +23,7 @@ module ActionDispatch
41
23
  @filtered_parameters = nil
42
24
  @filtered_env = nil
43
25
  @filtered_path = nil
26
+ @parameter_filter = nil
44
27
  end
45
28
 
46
29
  # Returns a hash of parameters with all sensitive data replaced.
@@ -60,13 +43,16 @@ module ActionDispatch
60
43
  @filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
61
44
  end
62
45
 
63
- private
64
- def parameter_filter # :doc:
65
- parameter_filter_for fetch_header("action_dispatch.parameter_filter") {
66
- return NULL_PARAM_FILTER
67
- }
46
+ # Returns the +ActiveSupport::ParameterFilter+ object used to filter in this request.
47
+ def parameter_filter
48
+ @parameter_filter ||= if has_header?("action_dispatch.parameter_filter")
49
+ parameter_filter_for get_header("action_dispatch.parameter_filter")
50
+ else
51
+ NULL_PARAM_FILTER
52
+ end
68
53
  end
69
54
 
55
+ private
70
56
  def env_filter # :doc:
71
57
  user_key = fetch_header("action_dispatch.parameter_filter") {
72
58
  return NULL_ENV_FILTER
@@ -2,6 +2,8 @@
2
2
 
3
3
  module ActionDispatch
4
4
  module Http
5
+ # = Action Dispatch HTTP \Headers
6
+ #
5
7
  # Provides access to the request's HTTP headers from the environment.
6
8
  #
7
9
  # env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" }
@@ -65,7 +67,7 @@ module ActionDispatch
65
67
  @req.set_header env_name(key), value
66
68
  end
67
69
 
68
- # Add a value to a multivalued header like Vary or Accept-Encoding.
70
+ # Add a value to a multivalued header like +Vary+ or +Accept-Encoding+.
69
71
  def add(key, value)
70
72
  @req.add_header env_name(key), value
71
73
  end
@@ -16,7 +16,20 @@ module ActionDispatch
16
16
 
17
17
  included do
18
18
  mattr_accessor :ignore_accept_header, default: false
19
- cattr_accessor :return_only_media_type_on_content_type, default: false
19
+
20
+ def return_only_media_type_on_content_type=(value)
21
+ ActionDispatch.deprecator.warn(
22
+ "`config.action_dispatch.return_only_request_media_type_on_content_type` is deprecated and will" \
23
+ " be removed in Rails 7.2."
24
+ )
25
+ end
26
+
27
+ def return_only_media_type_on_content_type
28
+ ActionDispatch.deprecator.warn(
29
+ "`config.action_dispatch.return_only_request_media_type_on_content_type` is deprecated and will" \
30
+ " be removed in Rails 7.2."
31
+ )
32
+ end
20
33
  end
21
34
 
22
35
  # The MIME type of the HTTP request, such as Mime[:xml].
@@ -33,19 +46,6 @@ module ActionDispatch
33
46
  end
34
47
  end
35
48
 
36
- def content_type
37
- if self.class.return_only_media_type_on_content_type
38
- ActiveSupport::Deprecation.warn(
39
- "Rails 7.1 will return Content-Type header without modification." \
40
- " If you want just the MIME type, please use `#media_type` instead."
41
- )
42
-
43
- content_mime_type&.to_s
44
- else
45
- super
46
- end
47
- end
48
-
49
49
  def has_content_type? # :nodoc:
50
50
  get_header "CONTENT_TYPE"
51
51
  end
@@ -72,7 +72,7 @@ module ActionDispatch
72
72
  # GET /posts/5.xhtml | request.format => Mime[:html]
73
73
  # GET /posts/5 | request.format => Mime[:html] or Mime[:js], or request.accepts.first
74
74
  #
75
- def format(view_path = [])
75
+ def format(_view_path = nil)
76
76
  formats.first || Mime::NullType.instance
77
77
  end
78
78
 
@@ -81,7 +81,7 @@ module ActionDispatch
81
81
  v = if params_readable?
82
82
  Array(Mime[parameters[:format]])
83
83
  elsif use_accept_header && valid_accept_header
84
- accepts
84
+ accepts.dup
85
85
  elsif extension_format = format_from_path_extension
86
86
  [extension_format]
87
87
  elsif xhr?
@@ -90,7 +90,7 @@ module ActionDispatch
90
90
  [Mime[:html]]
91
91
  end
92
92
 
93
- v = v.select do |format|
93
+ v.select! do |format|
94
94
  format.symbol || format.ref == "*/*"
95
95
  end
96
96
 
@@ -132,7 +132,7 @@ module ActionDispatch
132
132
  # Sets the \formats by string extensions. This differs from #format= by allowing you
133
133
  # to set multiple, ordered formats, which is useful when you want to have a fallback.
134
134
  #
135
- # In this example, the +:iphone+ format will be used if it's available, otherwise it'll fallback
135
+ # In this example, the +:iphone+ format will be used if it's available, otherwise it'll fall back
136
136
  # to the +:html+ format.
137
137
  #
138
138
  # class ApplicationController < ActionController::Base
@@ -172,22 +172,22 @@ module ActionDispatch
172
172
  # in which case we assume you're a browser and send HTML.
173
173
  BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
174
174
 
175
- def params_readable? # :doc:
175
+ def params_readable?
176
176
  parameters[:format]
177
177
  rescue *RESCUABLE_MIME_FORMAT_ERRORS
178
178
  false
179
179
  end
180
180
 
181
- def valid_accept_header # :doc:
181
+ def valid_accept_header
182
182
  (xhr? && (accept.present? || content_mime_type)) ||
183
183
  (accept.present? && !accept.match?(BROWSER_LIKE_ACCEPTS))
184
184
  end
185
185
 
186
- def use_accept_header # :doc:
186
+ def use_accept_header
187
187
  !self.class.ignore_accept_header
188
188
  end
189
189
 
190
- def format_from_path_extension # :doc:
190
+ def format_from_path_extension
191
191
  path = get_header("action_dispatch.original_path") || get_header("PATH_INFO")
192
192
  if match = path && path.match(/\.(\w+)\z/)
193
193
  Mime[match.captures.first]
@@ -11,6 +11,7 @@ module Mime
11
11
  def initialize
12
12
  @mimes = []
13
13
  @symbols = []
14
+ @symbols_set = Set.new
14
15
  end
15
16
 
16
17
  def each(&block)
@@ -19,17 +20,25 @@ module Mime
19
20
 
20
21
  def <<(type)
21
22
  @mimes << type
22
- @symbols << type.to_sym
23
+ sym_type = type.to_sym
24
+ @symbols << sym_type
25
+ @symbols_set << sym_type
23
26
  end
24
27
 
25
28
  def delete_if
26
29
  @mimes.delete_if do |x|
27
30
  if yield x
28
- @symbols.delete(x.to_sym)
31
+ sym_type = x.to_sym
32
+ @symbols.delete(sym_type)
33
+ @symbols_set.delete(sym_type)
29
34
  true
30
35
  end
31
36
  end
32
37
  end
38
+
39
+ def valid_symbols?(symbols) # :nodoc
40
+ symbols.all? { |s| @symbols_set.include?(s) }
41
+ end
33
42
  end
34
43
 
35
44
  SET = Mimes.new
@@ -42,6 +51,14 @@ module Mime
42
51
  Type.lookup_by_extension(type)
43
52
  end
44
53
 
54
+ def symbols
55
+ SET.symbols
56
+ end
57
+
58
+ def valid_symbols?(symbols) # :nodoc:
59
+ SET.valid_symbols?(symbols)
60
+ end
61
+
45
62
  def fetch(type, &block)
46
63
  return type if type.is_a?(Type)
47
64
  EXTENSION_LOOKUP.fetch(type.to_s, &block)
@@ -135,14 +152,18 @@ module Mime
135
152
 
136
153
  class << self
137
154
  TRAILING_STAR_REGEXP = /^(text|application)\/\*/
138
- PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/
155
+ # all media-type parameters need to be before the q-parameter
156
+ # https://www.rfc-editor.org/rfc/rfc7231#section-5.3.2
157
+ PARAMETER_SEPARATOR_REGEXP = /;\s*q="?/
158
+ ACCEPT_HEADER_REGEXP = /[^,\s"](?:[^,"]|"[^"]*")*/
139
159
 
140
160
  def register_callback(&block)
141
161
  @register_callbacks << block
142
162
  end
143
163
 
144
164
  def lookup(string)
145
- LOOKUP[string] || Type.new(string)
165
+ # fallback to the media-type without parameters if it was not found
166
+ LOOKUP[string] || LOOKUP[string.split(";", 2)[0]&.rstrip] || Type.new(string)
146
167
  end
147
168
 
148
169
  def lookup_by_extension(extension)
@@ -171,12 +192,14 @@ module Mime
171
192
 
172
193
  def parse(accept_header)
173
194
  if !accept_header.include?(",")
174
- accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
175
- return [] unless accept_header
176
- parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact
195
+ if (index = accept_header.index(PARAMETER_SEPARATOR_REGEXP))
196
+ accept_header = accept_header[0, index].strip
197
+ end
198
+ return [] if accept_header.blank?
199
+ parse_trailing_star(accept_header) || Array(Mime::Type.lookup(accept_header))
177
200
  else
178
201
  list, index = [], 0
179
- accept_header.split(",").each do |header|
202
+ accept_header.scan(ACCEPT_HEADER_REGEXP).each do |header|
180
203
  params, q = header.split(PARAMETER_SEPARATOR_REGEXP)
181
204
 
182
205
  next unless params
@@ -199,10 +222,10 @@ module Mime
199
222
  end
200
223
 
201
224
  # For an input of <tt>'text'</tt>, returns <tt>[Mime[:json], Mime[:xml], Mime[:ics],
202
- # Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]</tt>.
225
+ # Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]]</tt>.
203
226
  #
204
227
  # For an input of <tt>'application'</tt>, returns <tt>[Mime[:html], Mime[:js],
205
- # Mime[:xml], Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]</tt>.
228
+ # Mime[:xml], Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]]</tt>.
206
229
  def parse_data_with_trailing_star(type)
207
230
  Mime::SET.select { |m| m.match?(type) }
208
231
  end
@@ -225,7 +248,7 @@ module Mime
225
248
  attr_reader :hash
226
249
 
227
250
  MIME_NAME = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}"
228
- MIME_PARAMETER_VALUE = "#{Regexp.escape('"')}?#{MIME_NAME}#{Regexp.escape('"')}?"
251
+ MIME_PARAMETER_VALUE = "(?:#{MIME_NAME}|\"[^\"\r\\\\]*\")"
229
252
  MIME_PARAMETER = "\s*;\s*#{MIME_NAME}(?:=#{MIME_PARAMETER_VALUE})?"
230
253
  MIME_REGEXP = /\A(?:\*\/\*|#{MIME_NAME}\/(?:\*|#{MIME_NAME})(?>#{MIME_PARAMETER})*\s*)\z/
231
254
 
@@ -291,7 +314,7 @@ module Mime
291
314
  end
292
315
 
293
316
  def html?
294
- (symbol == :html) || /html/.match?(@string)
317
+ (symbol == :html) || @string.include?("html")
295
318
  end
296
319
 
297
320
  def all?; false; end
@@ -18,6 +18,7 @@ Mime::Type.register "image/gif", :gif, [], %w(gif)
18
18
  Mime::Type.register "image/bmp", :bmp, [], %w(bmp)
19
19
  Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff)
20
20
  Mime::Type.register "image/svg+xml", :svg
21
+ Mime::Type.register "image/webp", :webp, [], %w(webp)
21
22
 
22
23
  Mime::Type.register "video/mpeg", :mpeg, [], %w(mpg mpeg mpe)
23
24
 
@@ -43,7 +44,8 @@ Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form
43
44
 
44
45
  # https://www.ietf.org/rfc/rfc4627.txt
45
46
  # http://www.json.org/JSONRequest.html
46
- Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest )
47
+ # https://www.ietf.org/rfc/rfc7807.txt
48
+ Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest application/problem+json )
47
49
 
48
50
  Mime::Type.register "application/pdf", :pdf, [], %w(pdf)
49
51
  Mime::Type.register "application/zip", :zip, [], %w(zip)
@@ -76,7 +76,7 @@ module ActionDispatch
76
76
  end
77
77
 
78
78
  # Returns a hash with the \parameters used to form the \path of the request.
79
- # Returned hash keys are strings:
79
+ # Returned hash keys are symbols:
80
80
  #
81
81
  # { action: "my_action", controller: "my_controller" }
82
82
  def path_parameters
@@ -3,6 +3,8 @@
3
3
  require "active_support/core_ext/object/deep_dup"
4
4
 
5
5
  module ActionDispatch # :nodoc:
6
+ # = Action Dispatch \PermissionsPolicy
7
+ #
6
8
  # Configures the HTTP
7
9
  # {Feature-Policy}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy]
8
10
  # response header to specify which browser features the current document and
@@ -19,47 +21,38 @@ module ActionDispatch # :nodoc:
19
21
  # policy.payment :self, "https://secure.example.com"
20
22
  # end
21
23
  #
24
+ # The Feature-Policy header has been renamed to Permissions-Policy.
25
+ # The Permissions-Policy requires a different implementation and isn't
26
+ # yet supported by all browsers. To avoid having to rename this
27
+ # middleware in the future we use the new name for the middleware but
28
+ # keep the old header name and implementation for now.
22
29
  class PermissionsPolicy
23
30
  class Middleware
24
- CONTENT_TYPE = "Content-Type"
25
- # The Feature-Policy header has been renamed to Permissions-Policy.
26
- # The Permissions-Policy requires a different implementation and isn't
27
- # yet supported by all browsers. To avoid having to rename this
28
- # middleware in the future we use the new name for the middleware but
29
- # keep the old header name and implementation for now.
30
- POLICY = "Feature-Policy"
31
-
32
31
  def initialize(app)
33
32
  @app = app
34
33
  end
35
34
 
36
35
  def call(env)
37
- request = ActionDispatch::Request.new(env)
38
36
  _, headers, _ = response = @app.call(env)
39
37
 
40
- return response unless html_response?(headers)
41
38
  return response if policy_present?(headers)
42
39
 
40
+ request = ActionDispatch::Request.new(env)
41
+
43
42
  if policy = request.permissions_policy
44
- headers[POLICY] = policy.build(request.controller_instance)
43
+ headers[ActionDispatch::Constants::FEATURE_POLICY] = policy.build(request.controller_instance)
45
44
  end
46
45
 
47
46
  if policy_empty?(policy)
48
- headers.delete(POLICY)
47
+ headers.delete(ActionDispatch::Constants::FEATURE_POLICY)
49
48
  end
50
49
 
51
50
  response
52
51
  end
53
52
 
54
53
  private
55
- def html_response?(headers)
56
- if content_type = headers[CONTENT_TYPE]
57
- /html/.match?(content_type)
58
- end
59
- end
60
-
61
54
  def policy_present?(headers)
62
- headers[POLICY]
55
+ headers[ActionDispatch::Constants::FEATURE_POLICY]
63
56
  end
64
57
 
65
58
  def policy_empty?(policy)
@@ -85,7 +78,7 @@ module ActionDispatch # :nodoc:
85
78
  }.freeze
86
79
 
87
80
  # List of available permissions can be found at
88
- # https://github.com/w3c/webappsec-permissions-policy/blob/master/features.md#policy-controlled-features
81
+ # https://github.com/w3c/webappsec-permissions-policy/blob/main/features.md#policy-controlled-features
89
82
  DIRECTIVES = {
90
83
  accelerometer: "accelerometer",
91
84
  ambient_light_sensor: "ambient-light-sensor",
@@ -95,15 +88,18 @@ module ActionDispatch # :nodoc:
95
88
  fullscreen: "fullscreen",
96
89
  geolocation: "geolocation",
97
90
  gyroscope: "gyroscope",
91
+ hid: "hid",
92
+ idle_detection: "idle_detection",
98
93
  magnetometer: "magnetometer",
99
94
  microphone: "microphone",
100
95
  midi: "midi",
101
96
  payment: "payment",
102
97
  picture_in_picture: "picture-in-picture",
103
- speaker: "speaker",
98
+ screen_wake_lock: "screen-wake-lock",
99
+ serial: "serial",
100
+ sync_xhr: "sync-xhr",
104
101
  usb: "usb",
105
- vibrate: "vibrate",
106
- vr: "vr",
102
+ web_share: "web-share",
107
103
  }.freeze
108
104
 
109
105
  private_constant :MAPPINGS, :DIRECTIVES
@@ -129,6 +125,25 @@ module ActionDispatch # :nodoc:
129
125
  end
130
126
  end
131
127
 
128
+ %w[speaker vibrate vr].each do |directive|
129
+ define_method(directive) do |*sources|
130
+ ActionDispatch.deprecator.warn(<<~MSG)
131
+ The `#{directive}` permissions policy directive is deprecated
132
+ and will be removed in Rails 7.2.
133
+
134
+ There is no browser support for this directive, and no plan
135
+ for browser support in the future. You can just remove this
136
+ directive from your application.
137
+ MSG
138
+
139
+ if sources.first
140
+ @directives[directive] = apply_mappings(sources)
141
+ else
142
+ @directives.delete(directive)
143
+ end
144
+ end
145
+ end
146
+
132
147
  def build(context = nil)
133
148
  build_directives(context).compact.join("; ")
134
149
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :enddoc:
4
+
3
5
  require "rack/cache"
4
6
  require "rack/cache/context"
5
7
  require "active_support/cache"