actionpack 7.0.8.7 → 7.1.5.1

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.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +423 -342
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  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 +61 -18
  10. data/lib/abstract_controller/railties/routes_helpers.rb +1 -16
  11. data/lib/abstract_controller/rendering.rb +3 -3
  12. data/lib/abstract_controller/translation.rb +7 -24
  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 +5 -3
  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/content_security_policy.rb +1 -1
  22. data/lib/action_controller/metal/data_streaming.rb +2 -0
  23. data/lib/action_controller/metal/default_headers.rb +2 -0
  24. data/lib/action_controller/metal/etag_with_flash.rb +2 -0
  25. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
  26. data/lib/action_controller/metal/exceptions.rb +8 -0
  27. data/lib/action_controller/metal/head.rb +8 -6
  28. data/lib/action_controller/metal/helpers.rb +3 -14
  29. data/lib/action_controller/metal/http_authentication.rb +13 -8
  30. data/lib/action_controller/metal/implicit_render.rb +5 -3
  31. data/lib/action_controller/metal/instrumentation.rb +8 -1
  32. data/lib/action_controller/metal/live.rb +24 -0
  33. data/lib/action_controller/metal/mime_responds.rb +2 -2
  34. data/lib/action_controller/metal/params_wrapper.rb +4 -2
  35. data/lib/action_controller/metal/permissions_policy.rb +1 -1
  36. data/lib/action_controller/metal/redirecting.rb +7 -7
  37. data/lib/action_controller/metal/renderers.rb +2 -2
  38. data/lib/action_controller/metal/rendering.rb +0 -7
  39. data/lib/action_controller/metal/request_forgery_protection.rb +139 -50
  40. data/lib/action_controller/metal/rescue.rb +2 -0
  41. data/lib/action_controller/metal/streaming.rb +70 -30
  42. data/lib/action_controller/metal/strong_parameters.rb +174 -54
  43. data/lib/action_controller/metal/url_for.rb +7 -0
  44. data/lib/action_controller/metal.rb +79 -21
  45. data/lib/action_controller/railtie.rb +22 -9
  46. data/lib/action_controller/renderer.rb +98 -65
  47. data/lib/action_controller/test_case.rb +18 -8
  48. data/lib/action_controller.rb +13 -3
  49. data/lib/action_dispatch/constants.rb +32 -0
  50. data/lib/action_dispatch/deprecator.rb +7 -0
  51. data/lib/action_dispatch/http/cache.rb +1 -3
  52. data/lib/action_dispatch/http/content_security_policy.rb +9 -8
  53. data/lib/action_dispatch/http/filter_parameters.rb +11 -5
  54. data/lib/action_dispatch/http/headers.rb +2 -0
  55. data/lib/action_dispatch/http/mime_negotiation.rb +22 -22
  56. data/lib/action_dispatch/http/mime_type.rb +37 -11
  57. data/lib/action_dispatch/http/mime_types.rb +3 -1
  58. data/lib/action_dispatch/http/parameters.rb +1 -1
  59. data/lib/action_dispatch/http/permissions_policy.rb +38 -16
  60. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  61. data/lib/action_dispatch/http/request.rb +70 -16
  62. data/lib/action_dispatch/http/response.rb +80 -59
  63. data/lib/action_dispatch/http/upload.rb +2 -0
  64. data/lib/action_dispatch/journey/formatter.rb +8 -2
  65. data/lib/action_dispatch/journey/path/pattern.rb +14 -14
  66. data/lib/action_dispatch/journey/route.rb +3 -2
  67. data/lib/action_dispatch/journey/router.rb +9 -8
  68. data/lib/action_dispatch/journey/routes.rb +2 -2
  69. data/lib/action_dispatch/log_subscriber.rb +23 -0
  70. data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -6
  71. data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
  72. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  73. data/lib/action_dispatch/middleware/cookies.rb +81 -98
  74. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -25
  75. data/lib/action_dispatch/middleware/debug_locks.rb +4 -1
  76. data/lib/action_dispatch/middleware/debug_view.rb +7 -2
  77. data/lib/action_dispatch/middleware/exception_wrapper.rb +186 -27
  78. data/lib/action_dispatch/middleware/executor.rb +7 -1
  79. data/lib/action_dispatch/middleware/flash.rb +7 -0
  80. data/lib/action_dispatch/middleware/host_authorization.rb +6 -3
  81. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
  82. data/lib/action_dispatch/middleware/reloader.rb +7 -5
  83. data/lib/action_dispatch/middleware/remote_ip.rb +17 -16
  84. data/lib/action_dispatch/middleware/request_id.rb +2 -0
  85. data/lib/action_dispatch/middleware/server_timing.rb +4 -4
  86. data/lib/action_dispatch/middleware/session/abstract_store.rb +5 -0
  87. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  88. data/lib/action_dispatch/middleware/session/cookie_store.rb +11 -5
  89. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
  90. data/lib/action_dispatch/middleware/show_exceptions.rb +33 -19
  91. data/lib/action_dispatch/middleware/ssl.rb +18 -6
  92. data/lib/action_dispatch/middleware/stack.rb +7 -2
  93. data/lib/action_dispatch/middleware/static.rb +12 -8
  94. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  95. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
  96. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  97. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
  98. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  99. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
  100. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
  101. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
  102. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  103. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
  104. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  105. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  106. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  107. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +46 -37
  108. data/lib/action_dispatch/railtie.rb +13 -4
  109. data/lib/action_dispatch/request/session.rb +16 -6
  110. data/lib/action_dispatch/request/utils.rb +8 -3
  111. data/lib/action_dispatch/routing/inspector.rb +54 -6
  112. data/lib/action_dispatch/routing/mapper.rb +74 -26
  113. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  114. data/lib/action_dispatch/routing/redirection.rb +15 -6
  115. data/lib/action_dispatch/routing/route_set.rb +53 -23
  116. data/lib/action_dispatch/routing/routes_proxy.rb +10 -15
  117. data/lib/action_dispatch/routing/url_for.rb +5 -1
  118. data/lib/action_dispatch/routing.rb +7 -7
  119. data/lib/action_dispatch/system_test_case.rb +3 -3
  120. data/lib/action_dispatch/system_testing/browser.rb +25 -19
  121. data/lib/action_dispatch/system_testing/driver.rb +14 -22
  122. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +27 -16
  123. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  124. data/lib/action_dispatch/testing/assertions/response.rb +13 -6
  125. data/lib/action_dispatch/testing/assertions/routing.rb +67 -28
  126. data/lib/action_dispatch/testing/assertions.rb +3 -1
  127. data/lib/action_dispatch/testing/integration.rb +27 -17
  128. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  129. data/lib/action_dispatch/testing/test_process.rb +4 -3
  130. data/lib/action_dispatch/testing/test_request.rb +1 -1
  131. data/lib/action_dispatch/testing/test_response.rb +23 -9
  132. data/lib/action_dispatch.rb +41 -4
  133. data/lib/action_pack/gem_version.rb +4 -4
  134. data/lib/action_pack/version.rb +1 -1
  135. data/lib/action_pack.rb +1 -1
  136. metadata +62 -26
@@ -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.
@@ -26,16 +28,11 @@ module ActionDispatch # :nodoc:
26
28
  end
27
29
 
28
30
  class Middleware
29
- CONTENT_TYPE = "Content-Type"
30
- POLICY = "Content-Security-Policy"
31
- POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only"
32
-
33
31
  def initialize(app)
34
32
  @app = app
35
33
  end
36
34
 
37
35
  def call(env)
38
- request = ActionDispatch::Request.new env
39
36
  status, headers, _ = response = @app.call(env)
40
37
 
41
38
  # Returning CSP headers with a 304 Not Modified is harmful, since nonces in the new
@@ -44,6 +41,8 @@ module ActionDispatch # :nodoc:
44
41
 
45
42
  return response if policy_present?(headers)
46
43
 
44
+ request = ActionDispatch::Request.new env
45
+
47
46
  if policy = request.content_security_policy
48
47
  nonce = request.content_security_policy_nonce
49
48
  nonce_directives = request.content_security_policy_nonce_directives
@@ -57,14 +56,15 @@ module ActionDispatch # :nodoc:
57
56
  private
58
57
  def header_name(request)
59
58
  if request.content_security_policy_report_only
60
- POLICY_REPORT_ONLY
59
+ ActionDispatch::Constants::CONTENT_SECURITY_POLICY_REPORT_ONLY
61
60
  else
62
- POLICY
61
+ ActionDispatch::Constants::CONTENT_SECURITY_POLICY
63
62
  end
64
63
  end
65
64
 
66
65
  def policy_present?(headers)
67
- headers[POLICY] || headers[POLICY_REPORT_ONLY]
66
+ headers[ActionDispatch::Constants::CONTENT_SECURITY_POLICY] ||
67
+ headers[ActionDispatch::Constants::CONTENT_SECURITY_POLICY_REPORT_ONLY]
68
68
  end
69
69
  end
70
70
 
@@ -126,6 +126,7 @@ module ActionDispatch # :nodoc:
126
126
  MAPPINGS = {
127
127
  self: "'self'",
128
128
  unsafe_eval: "'unsafe-eval'",
129
+ unsafe_hashes: "'unsafe-hashes'",
129
130
  unsafe_inline: "'unsafe-inline'",
130
131
  none: "'none'",
131
132
  http: "http:",
@@ -4,6 +4,8 @@ require "active_support/parameter_filter"
4
4
 
5
5
  module ActionDispatch
6
6
  module Http
7
+ # = Action Dispatch HTTP Filter Parameters
8
+ #
7
9
  # Allows you to specify sensitive query string and POST parameters to filter
8
10
  # from the request log.
9
11
  #
@@ -21,6 +23,7 @@ module ActionDispatch
21
23
  @filtered_parameters = nil
22
24
  @filtered_env = nil
23
25
  @filtered_path = nil
26
+ @parameter_filter = nil
24
27
  end
25
28
 
26
29
  # Returns a hash of parameters with all sensitive data replaced.
@@ -40,13 +43,16 @@ module ActionDispatch
40
43
  @filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
41
44
  end
42
45
 
43
- private
44
- def parameter_filter # :doc:
45
- parameter_filter_for fetch_header("action_dispatch.parameter_filter") {
46
- return NULL_PARAM_FILTER
47
- }
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
48
53
  end
49
54
 
55
+ private
50
56
  def env_filter # :doc:
51
57
  user_key = fetch_header("action_dispatch.parameter_filter") {
52
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" }
@@ -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,13 +152,20 @@ 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)
165
+ return LOOKUP[string] if LOOKUP.key?(string)
166
+
167
+ # fallback to the media-type without parameters if it was not found
168
+ string = string.split(";", 2)[0]&.rstrip
145
169
  LOOKUP[string] || Type.new(string)
146
170
  end
147
171
 
@@ -171,12 +195,14 @@ module Mime
171
195
 
172
196
  def parse(accept_header)
173
197
  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
198
+ if (index = accept_header.index(PARAMETER_SEPARATOR_REGEXP))
199
+ accept_header = accept_header[0, index].strip
200
+ end
201
+ return [] if accept_header.blank?
202
+ parse_trailing_star(accept_header) || Array(Mime::Type.lookup(accept_header))
177
203
  else
178
204
  list, index = [], 0
179
- accept_header.split(",").each do |header|
205
+ accept_header.scan(ACCEPT_HEADER_REGEXP).each do |header|
180
206
  params, q = header.split(PARAMETER_SEPARATOR_REGEXP)
181
207
 
182
208
  next unless params
@@ -199,10 +225,10 @@ module Mime
199
225
  end
200
226
 
201
227
  # 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>.
228
+ # Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]]</tt>.
203
229
  #
204
230
  # 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>.
231
+ # Mime[:xml], Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]]</tt>.
206
232
  def parse_data_with_trailing_star(type)
207
233
  Mime::SET.select { |m| m.match?(type) }
208
234
  end
@@ -225,7 +251,7 @@ module Mime
225
251
  attr_reader :hash
226
252
 
227
253
  MIME_NAME = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}"
228
- MIME_PARAMETER_VALUE = "#{Regexp.escape('"')}?#{MIME_NAME}#{Regexp.escape('"')}?"
254
+ MIME_PARAMETER_VALUE = "(?:#{MIME_NAME}|\"[^\"\r\\\\]*\")"
229
255
  MIME_PARAMETER = "\s*;\s*#{MIME_NAME}(?:=#{MIME_PARAMETER_VALUE})?"
230
256
  MIME_REGEXP = /\A(?:\*\/\*|#{MIME_NAME}\/(?:\*|#{MIME_NAME})(?>#{MIME_PARAMETER})*\s*)\z/
231
257
 
@@ -291,7 +317,7 @@ module Mime
291
317
  end
292
318
 
293
319
  def html?
294
- (symbol == :html) || /html/.match?(@string)
320
+ (symbol == :html) || @string.include?("html")
295
321
  end
296
322
 
297
323
  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,32 +21,30 @@ 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
38
  return response if policy_present?(headers)
41
39
 
40
+ request = ActionDispatch::Request.new(env)
41
+
42
42
  if policy = request.permissions_policy
43
- headers[POLICY] = policy.build(request.controller_instance)
43
+ headers[ActionDispatch::Constants::FEATURE_POLICY] = policy.build(request.controller_instance)
44
44
  end
45
45
 
46
46
  if policy_empty?(policy)
47
- headers.delete(POLICY)
47
+ headers.delete(ActionDispatch::Constants::FEATURE_POLICY)
48
48
  end
49
49
 
50
50
  response
@@ -52,7 +52,7 @@ module ActionDispatch # :nodoc:
52
52
 
53
53
  private
54
54
  def policy_present?(headers)
55
- headers[POLICY]
55
+ headers[ActionDispatch::Constants::FEATURE_POLICY]
56
56
  end
57
57
 
58
58
  def policy_empty?(policy)
@@ -78,7 +78,7 @@ module ActionDispatch # :nodoc:
78
78
  }.freeze
79
79
 
80
80
  # List of available permissions can be found at
81
- # 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
82
82
  DIRECTIVES = {
83
83
  accelerometer: "accelerometer",
84
84
  ambient_light_sensor: "ambient-light-sensor",
@@ -88,15 +88,18 @@ module ActionDispatch # :nodoc:
88
88
  fullscreen: "fullscreen",
89
89
  geolocation: "geolocation",
90
90
  gyroscope: "gyroscope",
91
+ hid: "hid",
92
+ idle_detection: "idle-detection",
91
93
  magnetometer: "magnetometer",
92
94
  microphone: "microphone",
93
95
  midi: "midi",
94
96
  payment: "payment",
95
97
  picture_in_picture: "picture-in-picture",
96
- speaker: "speaker",
98
+ screen_wake_lock: "screen-wake-lock",
99
+ serial: "serial",
100
+ sync_xhr: "sync-xhr",
97
101
  usb: "usb",
98
- vibrate: "vibrate",
99
- vr: "vr",
102
+ web_share: "web-share",
100
103
  }.freeze
101
104
 
102
105
  private_constant :MAPPINGS, :DIRECTIVES
@@ -122,6 +125,25 @@ module ActionDispatch # :nodoc:
122
125
  end
123
126
  end
124
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
+
125
147
  def build(context = nil)
126
148
  build_directives(context).compact.join("; ")
127
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"
@@ -72,7 +72,7 @@ module ActionDispatch
72
72
 
73
73
  PASS_NOT_FOUND = Class.new { # :nodoc:
74
74
  def self.action(_); self; end
75
- def self.call(_); [404, { "X-Cascade" => "pass" }, []]; end
75
+ def self.call(_); [404, { Constants::X_CASCADE => "pass" }, []]; end
76
76
  def self.action_encoding_template(action); false; end
77
77
  }
78
78
 
@@ -145,6 +145,18 @@ module ActionDispatch
145
145
  @request_method ||= check_method(super)
146
146
  end
147
147
 
148
+ # Returns the URI pattern of the matched route for the request,
149
+ # using the same format as `bin/rails routes`:
150
+ #
151
+ # request.route_uri_pattern # => "/:controller(/:action(/:id))(.:format)"
152
+ def route_uri_pattern
153
+ get_header("action_dispatch.route_uri_pattern")
154
+ end
155
+
156
+ def route_uri_pattern=(pattern) # :nodoc:
157
+ set_header("action_dispatch.route_uri_pattern", pattern)
158
+ end
159
+
148
160
  def routes # :nodoc:
149
161
  get_header("action_dispatch.routes")
150
162
  end
@@ -179,13 +191,6 @@ module ActionDispatch
179
191
  get_header "action_dispatch.http_auth_salt"
180
192
  end
181
193
 
182
- def show_exceptions? # :nodoc:
183
- # We're treating `nil` as "unset", and we want the default setting to be
184
- # `true`. This logic should be extracted to `env_config` and calculated
185
- # once.
186
- !(get_header("action_dispatch.show_exceptions") == false)
187
- end
188
-
189
194
  # Returns a symbol form of the #request_method.
190
195
  def request_method_symbol
191
196
  HTTP_METHOD_LOOKUP[request_method]
@@ -194,9 +199,20 @@ module ActionDispatch
194
199
  # Returns the original value of the environment's REQUEST_METHOD,
195
200
  # even if it was overridden by middleware. See #request_method for
196
201
  # more information.
197
- def method
198
- @method ||= check_method(get_header("rack.methodoverride.original_method") || get_header("REQUEST_METHOD"))
202
+ #
203
+ # For debugging purposes, when called with arguments this method will
204
+ # fall back to Object#method
205
+ def method(*args)
206
+ if args.empty?
207
+ @method ||= check_method(
208
+ get_header("rack.methodoverride.original_method") ||
209
+ get_header("REQUEST_METHOD")
210
+ )
211
+ else
212
+ super
213
+ end
199
214
  end
215
+ ruby2_keywords(:method)
200
216
 
201
217
  # Returns a symbol form of the #method.
202
218
  def method_symbol
@@ -213,11 +229,12 @@ module ActionDispatch
213
229
  # Early Hints is an HTTP/2 status code that indicates hints to help a client start
214
230
  # making preparations for processing the final response.
215
231
  #
216
- # If the env contains +rack.early_hints+ then the server accepts HTTP2 push for Link headers.
232
+ # If the env contains +rack.early_hints+ then the server accepts HTTP2 push for
233
+ # link headers.
217
234
  #
218
235
  # The +send_early_hints+ method accepts a hash of links as follows:
219
236
  #
220
- # send_early_hints("Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload")
237
+ # send_early_hints("link" => "</style.css>; rel=preload; as=style,</script.js>; rel=preload")
221
238
  #
222
239
  # If you are using +javascript_include_tag+ or +stylesheet_link_tag+ the
223
240
  # Early Hints headers are included by default if supported.
@@ -267,6 +284,7 @@ module ActionDispatch
267
284
 
268
285
  # Returns the content length of the request as an integer.
269
286
  def content_length
287
+ return raw_post.bytesize if headers.key?("Transfer-Encoding")
270
288
  super.to_i
271
289
  end
272
290
 
@@ -321,9 +339,7 @@ module ActionDispatch
321
339
  # work with raw requests directly.
322
340
  def raw_post
323
341
  unless has_header? "RAW_POST_DATA"
324
- raw_post_body = body
325
- set_header("RAW_POST_DATA", raw_post_body.read(content_length))
326
- raw_post_body.rewind if raw_post_body.respond_to?(:rewind)
342
+ set_header("RAW_POST_DATA", read_body_stream)
327
343
  end
328
344
  get_header "RAW_POST_DATA"
329
345
  end
@@ -357,6 +373,7 @@ module ActionDispatch
357
373
 
358
374
  def reset_session
359
375
  session.destroy
376
+ reset_csrf_token
360
377
  end
361
378
 
362
379
  def session=(session) # :nodoc:
@@ -428,15 +445,52 @@ module ActionDispatch
428
445
  "#<#{self.class.name} #{method} #{original_url.dump} for #{remote_ip}>"
429
446
  end
430
447
 
448
+ def reset_csrf_token
449
+ controller_instance.reset_csrf_token(self) if controller_instance.respond_to?(:reset_csrf_token)
450
+ end
451
+
452
+ def commit_csrf_token
453
+ controller_instance.commit_csrf_token(self) if controller_instance.respond_to?(:commit_csrf_token)
454
+ end
455
+
431
456
  private
432
457
  def check_method(name)
433
- HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}")
458
+ if name
459
+ HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}")
460
+ end
461
+
434
462
  name
435
463
  end
436
464
 
437
465
  def default_session
438
466
  Session.disabled(self)
439
467
  end
468
+
469
+ def read_body_stream
470
+ if body_stream
471
+ reset_stream(body_stream) do
472
+ if headers.key?("Transfer-Encoding")
473
+ body_stream.read # Read body stream until EOF if "Transfer-Encoding" is present
474
+ else
475
+ body_stream.read(content_length)
476
+ end
477
+ end
478
+ end
479
+ end
480
+
481
+ def reset_stream(body_stream)
482
+ if body_stream.respond_to?(:rewind)
483
+ body_stream.rewind
484
+
485
+ content = yield
486
+
487
+ body_stream.rewind
488
+
489
+ content
490
+ else
491
+ yield
492
+ end
493
+ end
440
494
  end
441
495
  end
442
496