actionpack 5.2.4.4 → 6.1.1

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 (155) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -322
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/abstract_controller.rb +1 -0
  6. data/lib/abstract_controller/base.rb +38 -4
  7. data/lib/abstract_controller/caching.rb +1 -1
  8. data/lib/abstract_controller/caching/fragments.rb +6 -22
  9. data/lib/abstract_controller/callbacks.rb +14 -2
  10. data/lib/abstract_controller/collector.rb +1 -2
  11. data/lib/abstract_controller/helpers.rb +106 -90
  12. data/lib/abstract_controller/railties/routes_helpers.rb +1 -1
  13. data/lib/abstract_controller/rendering.rb +9 -9
  14. data/lib/abstract_controller/translation.rb +11 -5
  15. data/lib/action_controller.rb +7 -4
  16. data/lib/action_controller/api.rb +4 -3
  17. data/lib/action_controller/base.rb +6 -9
  18. data/lib/action_controller/caching.rb +1 -3
  19. data/lib/action_controller/log_subscriber.rb +10 -7
  20. data/lib/action_controller/metal.rb +10 -8
  21. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  22. data/lib/action_controller/metal/conditional_get.rb +19 -5
  23. data/lib/action_controller/metal/content_security_policy.rb +1 -2
  24. data/lib/action_controller/metal/cookies.rb +3 -1
  25. data/lib/action_controller/metal/data_streaming.rb +6 -7
  26. data/lib/action_controller/metal/default_headers.rb +17 -0
  27. data/lib/action_controller/metal/etag_with_template_digest.rb +3 -5
  28. data/lib/action_controller/metal/exceptions.rb +56 -2
  29. data/lib/action_controller/metal/flash.rb +5 -5
  30. data/lib/action_controller/metal/head.rb +7 -4
  31. data/lib/action_controller/metal/helpers.rb +14 -5
  32. data/lib/action_controller/metal/http_authentication.rb +24 -23
  33. data/lib/action_controller/metal/implicit_render.rb +5 -15
  34. data/lib/action_controller/metal/instrumentation.rb +13 -14
  35. data/lib/action_controller/metal/live.rb +30 -32
  36. data/lib/action_controller/metal/logging.rb +20 -0
  37. data/lib/action_controller/metal/mime_responds.rb +19 -4
  38. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  39. data/lib/action_controller/metal/params_wrapper.rb +31 -22
  40. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  41. data/lib/action_controller/metal/redirecting.rb +6 -6
  42. data/lib/action_controller/metal/renderers.rb +4 -4
  43. data/lib/action_controller/metal/rendering.rb +8 -3
  44. data/lib/action_controller/metal/request_forgery_protection.rb +62 -34
  45. data/lib/action_controller/metal/rescue.rb +1 -1
  46. data/lib/action_controller/metal/streaming.rb +0 -1
  47. data/lib/action_controller/metal/strong_parameters.rb +167 -58
  48. data/lib/action_controller/metal/url_for.rb +1 -1
  49. data/lib/action_controller/railties/helpers.rb +1 -1
  50. data/lib/action_controller/renderer.rb +37 -13
  51. data/lib/action_controller/template_assertions.rb +1 -1
  52. data/lib/action_controller/test_case.rb +70 -65
  53. data/lib/action_dispatch.rb +9 -3
  54. data/lib/action_dispatch/http/cache.rb +26 -21
  55. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  56. data/lib/action_dispatch/http/content_security_policy.rb +33 -19
  57. data/lib/action_dispatch/http/filter_parameters.rb +9 -8
  58. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  59. data/lib/action_dispatch/http/headers.rb +4 -4
  60. data/lib/action_dispatch/http/mime_negotiation.rb +26 -13
  61. data/lib/action_dispatch/http/mime_type.rb +42 -23
  62. data/lib/action_dispatch/http/parameters.rb +14 -23
  63. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  64. data/lib/action_dispatch/http/request.rb +45 -22
  65. data/lib/action_dispatch/http/response.rb +45 -25
  66. data/lib/action_dispatch/http/upload.rb +9 -1
  67. data/lib/action_dispatch/http/url.rb +82 -82
  68. data/lib/action_dispatch/journey.rb +0 -2
  69. data/lib/action_dispatch/journey/formatter.rb +54 -30
  70. data/lib/action_dispatch/journey/gtg/builder.rb +22 -37
  71. data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
  72. data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -5
  73. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  74. data/lib/action_dispatch/journey/nodes/node.rb +13 -11
  75. data/lib/action_dispatch/journey/parser.rb +13 -13
  76. data/lib/action_dispatch/journey/parser.y +1 -1
  77. data/lib/action_dispatch/journey/path/pattern.rb +19 -21
  78. data/lib/action_dispatch/journey/route.rb +10 -20
  79. data/lib/action_dispatch/journey/router.rb +26 -34
  80. data/lib/action_dispatch/journey/router/utils.rb +14 -12
  81. data/lib/action_dispatch/journey/routes.rb +0 -2
  82. data/lib/action_dispatch/journey/scanner.rb +10 -4
  83. data/lib/action_dispatch/journey/visitors.rb +1 -4
  84. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  85. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  86. data/lib/action_dispatch/middleware/cookies.rb +128 -109
  87. data/lib/action_dispatch/middleware/debug_exceptions.rb +43 -66
  88. data/lib/action_dispatch/middleware/debug_locks.rb +5 -5
  89. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  90. data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -30
  91. data/lib/action_dispatch/middleware/flash.rb +1 -1
  92. data/lib/action_dispatch/middleware/host_authorization.rb +121 -0
  93. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  94. data/lib/action_dispatch/middleware/remote_ip.rb +14 -16
  95. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  96. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -3
  97. data/lib/action_dispatch/middleware/session/cookie_store.rb +3 -9
  98. data/lib/action_dispatch/middleware/show_exceptions.rb +3 -2
  99. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  100. data/lib/action_dispatch/middleware/stack.rb +56 -2
  101. data/lib/action_dispatch/middleware/static.rb +153 -93
  102. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  103. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  104. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  105. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +3 -1
  106. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  107. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  108. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  109. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  110. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  111. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
  112. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  113. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +6 -3
  114. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -1
  115. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +104 -8
  116. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  119. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  120. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +2 -2
  121. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  122. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +24 -1
  123. data/lib/action_dispatch/railtie.rb +8 -2
  124. data/lib/action_dispatch/request/session.rb +10 -9
  125. data/lib/action_dispatch/request/utils.rb +26 -2
  126. data/lib/action_dispatch/routing.rb +21 -20
  127. data/lib/action_dispatch/routing/inspector.rb +100 -52
  128. data/lib/action_dispatch/routing/mapper.rb +155 -103
  129. data/lib/action_dispatch/routing/polymorphic_routes.rb +13 -15
  130. data/lib/action_dispatch/routing/redirection.rb +3 -3
  131. data/lib/action_dispatch/routing/route_set.rb +71 -69
  132. data/lib/action_dispatch/routing/url_for.rb +2 -2
  133. data/lib/action_dispatch/system_test_case.rb +54 -11
  134. data/lib/action_dispatch/system_testing/browser.rb +53 -16
  135. data/lib/action_dispatch/system_testing/driver.rb +11 -3
  136. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +49 -7
  137. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +8 -10
  138. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  139. data/lib/action_dispatch/testing/assertions.rb +1 -1
  140. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  141. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  142. data/lib/action_dispatch/testing/integration.rb +61 -28
  143. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  144. data/lib/action_dispatch/testing/test_process.rb +29 -4
  145. data/lib/action_dispatch/testing/test_request.rb +3 -3
  146. data/lib/action_dispatch/testing/test_response.rb +4 -32
  147. data/lib/action_pack.rb +1 -1
  148. data/lib/action_pack/gem_version.rb +4 -4
  149. metadata +38 -26
  150. data/lib/action_controller/metal/force_ssl.rb +0 -99
  151. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  152. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  153. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  154. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  155. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController
4
+ module Logging
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ # Set a different log level per request.
9
+ #
10
+ # # Use the debug log level if a particular cookie is set.
11
+ # class ApplicationController < ActionController::Base
12
+ # log_at :debug, if: -> { cookies[:debug] }
13
+ # end
14
+ #
15
+ def log_at(level, **options)
16
+ around_action ->(_, action) { logger.log_at(level, &action) }, **options
17
+ end
18
+ end
19
+ end
20
+ end
@@ -11,7 +11,7 @@ module ActionController #:nodoc:
11
11
  # @people = Person.all
12
12
  # end
13
13
  #
14
- # That action implicitly responds to all formats, but formats can also be whitelisted:
14
+ # That action implicitly responds to all formats, but formats can also be explicitly enumerated:
15
15
  #
16
16
  # def index
17
17
  # @people = Person.all
@@ -105,7 +105,7 @@ module ActionController #:nodoc:
105
105
  #
106
106
  # Mime::Type.register "image/jpg", :jpg
107
107
  #
108
- # Respond to also allows you to specify a common block for different formats by using +any+:
108
+ # +respond_to+ also allows you to specify a common block for different formats by using +any+:
109
109
  #
110
110
  # def index
111
111
  # @people = Person.all
@@ -124,6 +124,14 @@ module ActionController #:nodoc:
124
124
  #
125
125
  # render json: @people
126
126
  #
127
+ # +any+ can also be used with no arguments, in which case it will be used for any format requested by
128
+ # the user:
129
+ #
130
+ # respond_to do |format|
131
+ # format.html
132
+ # format.any { redirect_to support_path }
133
+ # end
134
+ #
127
135
  # Formats can have different variants.
128
136
  #
129
137
  # The request variant is a specialization of the request format, like <tt>:tablet</tt>,
@@ -134,7 +142,7 @@ module ActionController #:nodoc:
134
142
  #
135
143
  # You can set the variant in a +before_action+:
136
144
  #
137
- # request.variant = :tablet if request.user_agent =~ /iPad/
145
+ # request.variant = :tablet if /iPad/.match?(request.user_agent)
138
146
  #
139
147
  # Respond to variants in the action just like you respond to formats:
140
148
  #
@@ -197,8 +205,11 @@ module ActionController #:nodoc:
197
205
  yield collector if block_given?
198
206
 
199
207
  if format = collector.negotiate_format(request)
208
+ if media_type && media_type != format
209
+ raise ActionController::RespondToMismatchError
210
+ end
200
211
  _process_format(format)
201
- _set_rendered_content_type format
212
+ _set_rendered_content_type(format) unless collector.any_response?
202
213
  response = collector.response
203
214
  response.call if response
204
215
  else
@@ -257,6 +268,10 @@ module ActionController #:nodoc:
257
268
  end
258
269
  end
259
270
 
271
+ def any_response?
272
+ !@responses.fetch(format, false) && @responses[Mime::ALL]
273
+ end
274
+
260
275
  def response
261
276
  response = @responses.fetch(format, @responses[Mime::ALL])
262
277
  if response.is_a?(VariantCollector) # `format.html.phone` - variant inline syntax
@@ -12,11 +12,13 @@ module ActionController
12
12
  end
13
13
 
14
14
  def setup_param_encode # :nodoc:
15
- @_parameter_encodings = {}
15
+ @_parameter_encodings = Hash.new { |h, k| h[k] = {} }
16
16
  end
17
17
 
18
- def binary_params_for?(action) # :nodoc:
19
- @_parameter_encodings[action.to_s]
18
+ def action_encoding_template(action) # :nodoc:
19
+ if @_parameter_encodings.has_key?(action.to_s)
20
+ @_parameter_encodings[action.to_s]
21
+ end
20
22
  end
21
23
 
22
24
  # Specify that a given action's parameters should all be encoded as
@@ -44,7 +46,36 @@ module ActionController
44
46
  # encoded as ASCII-8BIT. This is useful in the case where an application
45
47
  # must handle data but encoding of the data is unknown, like file system data.
46
48
  def skip_parameter_encoding(action)
47
- @_parameter_encodings[action.to_s] = true
49
+ @_parameter_encodings[action.to_s] = Hash.new { Encoding::ASCII_8BIT }
50
+ end
51
+
52
+ # Specify the encoding for a parameter on an action.
53
+ # If not specified the default is UTF-8.
54
+ #
55
+ # You can specify a binary (ASCII_8BIT) parameter with:
56
+ #
57
+ # class RepositoryController < ActionController::Base
58
+ # # This specifies that file_path is not UTF-8 and is instead ASCII_8BIT
59
+ # param_encoding :show, :file_path, Encoding::ASCII_8BIT
60
+ #
61
+ # def show
62
+ # @repo = Repository.find_by_filesystem_path params[:file_path]
63
+ #
64
+ # # params[:repo_name] remains UTF-8 encoded
65
+ # @repo_name = params[:repo_name]
66
+ # end
67
+ #
68
+ # def index
69
+ # @repositories = Repository.all
70
+ # end
71
+ # end
72
+ #
73
+ # The file_path parameter on the show action would be encoded as ASCII-8BIT,
74
+ # but all other arguments will remain UTF-8 encoded.
75
+ # This is useful in the case where an application must handle data
76
+ # but encoding of the data is unknown, like file system data.
77
+ def param_encoding(action, param, encoding)
78
+ @_parameter_encodings[action.to_s][param.to_s] = encoding
48
79
  end
49
80
  end
50
81
  end
@@ -107,15 +107,19 @@ module ActionController
107
107
 
108
108
  unless super || exclude
109
109
  if m.respond_to?(:attribute_names) && m.attribute_names.any?
110
+ self.include = m.attribute_names
111
+
110
112
  if m.respond_to?(:stored_attributes) && !m.stored_attributes.empty?
111
- self.include = m.attribute_names + m.stored_attributes.values.flatten.map(&:to_s)
112
- else
113
- self.include = m.attribute_names
113
+ self.include += m.stored_attributes.values.flatten.map(&:to_s)
114
+ end
115
+
116
+ if m.respond_to?(:attribute_aliases) && m.attribute_aliases.any?
117
+ self.include += m.attribute_aliases.keys
114
118
  end
115
119
 
116
120
  if m.respond_to?(:nested_attributes_options) && m.nested_attributes_options.keys.any?
117
121
  self.include += m.nested_attributes_options.keys.map do |key|
118
- key.to_s.dup.concat("_attributes")
122
+ (+key.to_s).concat("_attributes")
119
123
  end
120
124
  end
121
125
 
@@ -151,7 +155,7 @@ module ActionController
151
155
  # try to find Foo::Bar::User, Foo::User and finally User.
152
156
  def _default_wrap_model
153
157
  return nil if klass.anonymous?
154
- model_name = klass.name.sub(/Controller$/, "").classify
158
+ model_name = klass.name.delete_suffix("Controller").classify
155
159
 
156
160
  begin
157
161
  if model_klass = model_name.safe_constantize
@@ -189,7 +193,7 @@ module ActionController
189
193
  #
190
194
  # wrap_parameters Person
191
195
  # # wraps parameters by determining the wrapper key from Person class
192
- # (+person+, in this case) and the list of attribute names
196
+ # # (+person+, in this case) and the list of attribute names
193
197
  #
194
198
  # wrap_parameters include: [:username, :title]
195
199
  # # wraps only +:username+ and +:title+ attributes from parameters.
@@ -240,24 +244,12 @@ module ActionController
240
244
 
241
245
  # Performs parameters wrapping upon the request. Called automatically
242
246
  # by the metal call stack.
243
- def process_action(*args)
244
- if _wrapper_enabled?
245
- wrapped_hash = _wrap_parameters request.request_parameters
246
- wrapped_keys = request.request_parameters.keys
247
- wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
248
-
249
- # This will make the wrapped hash accessible from controller and view.
250
- request.parameters.merge! wrapped_hash
251
- request.request_parameters.merge! wrapped_hash
252
-
253
- # This will display the wrapped hash in the log file.
254
- request.filtered_parameters.merge! wrapped_filtered_hash
255
- end
247
+ def process_action(*)
248
+ _perform_parameter_wrapping if _wrapper_enabled?
256
249
  super
257
250
  end
258
251
 
259
252
  private
260
-
261
253
  # Returns the wrapper key which will be used to store wrapped parameters.
262
254
  def _wrapper_key
263
255
  _wrapper_options.name
@@ -276,9 +268,11 @@ module ActionController
276
268
  def _extract_parameters(parameters)
277
269
  if include_only = _wrapper_options.include
278
270
  parameters.slice(*include_only)
271
+ elsif _wrapper_options.exclude
272
+ exclude = _wrapper_options.exclude + EXCLUDE_PARAMETERS
273
+ parameters.except(*exclude)
279
274
  else
280
- exclude = _wrapper_options.exclude || []
281
- parameters.except(*(exclude + EXCLUDE_PARAMETERS))
275
+ parameters.except(*EXCLUDE_PARAMETERS)
282
276
  end
283
277
  end
284
278
 
@@ -289,5 +283,20 @@ module ActionController
289
283
  ref = request.content_mime_type.ref
290
284
  _wrapper_formats.include?(ref) && _wrapper_key && !request.parameters.key?(_wrapper_key)
291
285
  end
286
+
287
+ def _perform_parameter_wrapping
288
+ wrapped_hash = _wrap_parameters request.request_parameters
289
+ wrapped_keys = request.request_parameters.keys
290
+ wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
291
+
292
+ # This will make the wrapped hash accessible from controller and view.
293
+ request.parameters.merge! wrapped_hash
294
+ request.request_parameters.merge! wrapped_hash
295
+
296
+ # This will display the wrapped hash in the log file.
297
+ request.filtered_parameters.merge! wrapped_filtered_hash
298
+ rescue ActionDispatch::Http::Parameters::ParseError
299
+ # swallow parse error exception
300
+ end
292
301
  end
293
302
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController #:nodoc:
4
+ # HTTP Permissions Policy is a web standard for defining a mechanism to
5
+ # allow and deny the use of browser permissions in its own context, and
6
+ # in content within any <iframe> elements in the document.
7
+ #
8
+ # Full details of HTTP Permissions Policy specification and guidelines can
9
+ # be found at MDN:
10
+ #
11
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy
12
+ #
13
+ # Examples of usage:
14
+ #
15
+ # # Global policy
16
+ # Rails.application.config.permissions_policy do |f|
17
+ # f.camera :none
18
+ # f.gyroscope :none
19
+ # f.microphone :none
20
+ # f.usb :none
21
+ # f.fullscreen :self
22
+ # f.payment :self, "https://secure.example.com"
23
+ # end
24
+ #
25
+ # # Controller level policy
26
+ # class PagesController < ApplicationController
27
+ # permissions_policy do |p|
28
+ # p.geolocation "https://example.com"
29
+ # end
30
+ # end
31
+ module PermissionsPolicy
32
+ extend ActiveSupport::Concern
33
+
34
+ module ClassMethods
35
+ def permissions_policy(**options, &block)
36
+ before_action(options) do
37
+ if block_given?
38
+ policy = request.permissions_policy.clone
39
+ yield policy
40
+ request.permissions_policy = policy
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -55,11 +55,11 @@ module ActionController
55
55
  # Statements after +redirect_to+ in our controller get executed, so +redirect_to+ doesn't stop the execution of the function.
56
56
  # To terminate the execution of the function immediately after the +redirect_to+, use return.
57
57
  # redirect_to post_url(@post) and return
58
- def redirect_to(options = {}, response_status = {})
58
+ def redirect_to(options = {}, response_options = {})
59
59
  raise ActionControllerError.new("Cannot redirect to nil!") unless options
60
60
  raise AbstractController::DoubleRenderError if response_body
61
61
 
62
- self.status = _extract_redirect_to_status(options, response_status)
62
+ self.status = _extract_redirect_to_status(options, response_options)
63
63
  self.location = _compute_redirect_to_location(request, options)
64
64
  self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(response.location)}\">redirected</a>.</body></html>"
65
65
  end
@@ -85,7 +85,7 @@ module ActionController
85
85
  # * <tt>:fallback_location</tt> - The default fallback location that will be used on missing +Referer+ header.
86
86
  # * <tt>:allow_other_host</tt> - Allow or disallow redirection to the host that is different to the current host, defaults to true.
87
87
  #
88
- # All other options that can be passed to <tt>redirect_to</tt> are accepted as
88
+ # All other options that can be passed to #redirect_to are accepted as
89
89
  # options and the behavior is identical.
90
90
  def redirect_back(fallback_location:, allow_other_host: true, **args)
91
91
  referer = request.headers["Referer"]
@@ -114,11 +114,11 @@ module ActionController
114
114
  public :_compute_redirect_to_location
115
115
 
116
116
  private
117
- def _extract_redirect_to_status(options, response_status)
117
+ def _extract_redirect_to_status(options, response_options)
118
118
  if options.is_a?(Hash) && options.key?(:status)
119
119
  Rack::Utils.status_code(options.delete(:status))
120
- elsif response_status.key?(:status)
121
- Rack::Utils.status_code(response_status[:status])
120
+ elsif response_options.key?(:status)
121
+ Rack::Utils.status_code(response_options[:status])
122
122
  else
123
123
  302
124
124
  end
@@ -157,24 +157,24 @@ module ActionController
157
157
  json = json.to_json(options) unless json.kind_of?(String)
158
158
 
159
159
  if options[:callback].present?
160
- if content_type.nil? || content_type == Mime[:json]
160
+ if media_type.nil? || media_type == Mime[:json]
161
161
  self.content_type = Mime[:js]
162
162
  end
163
163
 
164
164
  "/**/#{options[:callback]}(#{json})"
165
165
  else
166
- self.content_type ||= Mime[:json]
166
+ self.content_type = Mime[:json] if media_type.nil?
167
167
  json
168
168
  end
169
169
  end
170
170
 
171
171
  add :js do |js, options|
172
- self.content_type ||= Mime[:js]
172
+ self.content_type = Mime[:js] if media_type.nil?
173
173
  js.respond_to?(:to_js) ? js.to_js(options) : js
174
174
  end
175
175
 
176
176
  add :xml do |xml, options|
177
- self.content_type ||= Mime[:xml]
177
+ self.content_type = Mime[:xml] if media_type.nil?
178
178
  xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml
179
179
  end
180
180
  end
@@ -40,7 +40,7 @@ module ActionController
40
40
  def render_to_string(*)
41
41
  result = super
42
42
  if result.respond_to?(:each)
43
- string = "".dup
43
+ string = +""
44
44
  result.each { |r| string << r }
45
45
  string
46
46
  else
@@ -53,7 +53,6 @@ module ActionController
53
53
  end
54
54
 
55
55
  private
56
-
57
56
  def _process_variant(options)
58
57
  if defined?(request) && !request.nil? && request.variant.present?
59
58
  options[:variant] = request.variant
@@ -73,11 +72,17 @@ module ActionController
73
72
  end
74
73
 
75
74
  def _set_rendered_content_type(format)
76
- if format && !response.content_type
75
+ if format && !response.media_type
77
76
  self.content_type = format.to_s
78
77
  end
79
78
  end
80
79
 
80
+ def _set_vary_header
81
+ if self.headers["Vary"].blank? && request.should_apply_vary_header?
82
+ self.headers["Vary"] = "Accept"
83
+ end
84
+ end
85
+
81
86
  # Normalize arguments by catching blocks and setting them on :update.
82
87
  def _normalize_args(action = nil, options = {}, &blk)
83
88
  options = super
@@ -3,7 +3,6 @@
3
3
  require "rack/session/abstract/id"
4
4
  require "action_controller/metal/exceptions"
5
5
  require "active_support/security_utils"
6
- require "active_support/core_ext/string/strip"
7
6
 
8
7
  module ActionController #:nodoc:
9
8
  class InvalidAuthenticityToken < ActionControllerError #:nodoc:
@@ -18,7 +17,7 @@ module ActionController #:nodoc:
18
17
  # access. When a request reaches your application, \Rails verifies the received
19
18
  # token with the token in the session. All requests are checked except GET requests
20
19
  # as these should be idempotent. Keep in mind that all session-oriented requests
21
- # should be CSRF protected, including JavaScript and HTML requests.
20
+ # are CSRF protected by default, including JavaScript and HTML requests.
22
21
  #
23
22
  # Since HTML and JavaScript requests are typically made from the browser, we
24
23
  # need to ensure to verify request authenticity for the web browser. We can
@@ -31,31 +30,30 @@ module ActionController #:nodoc:
31
30
  # URL on your site. When your JavaScript response loads on their site, it executes.
32
31
  # With carefully crafted JavaScript on their end, sensitive data in your JavaScript
33
32
  # response may be extracted. To prevent this, only XmlHttpRequest (known as XHR or
34
- # Ajax) requests are allowed to make GET requests for JavaScript responses.
33
+ # Ajax) requests are allowed to make requests for JavaScript responses.
35
34
  #
36
- # It's important to remember that XML or JSON requests are also affected and if
37
- # you're building an API you should change forgery protection method in
38
- # <tt>ApplicationController</tt> (by default: <tt>:exception</tt>):
35
+ # Subclasses of <tt>ActionController::Base</tt> are protected by default with the
36
+ # <tt>:exception</tt> strategy, which raises an
37
+ # <tt>ActionController::InvalidAuthenticityToken</tt> error on unverified requests.
38
+ #
39
+ # APIs may want to disable this behavior since they are typically designed to be
40
+ # state-less: that is, the request API client handles the session instead of Rails.
41
+ # One way to achieve this is to use the <tt>:null_session</tt> strategy instead,
42
+ # which allows unverified requests to be handled, but with an empty session:
39
43
  #
40
44
  # class ApplicationController < ActionController::Base
41
- # protect_from_forgery unless: -> { request.format.json? }
45
+ # protect_from_forgery with: :null_session
42
46
  # end
43
47
  #
44
- # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method.
45
- # By default <tt>protect_from_forgery</tt> protects your session with
46
- # <tt>:null_session</tt> method, which provides an empty session
47
- # during request.
48
- #
49
- # We may want to disable CSRF protection for APIs since they are typically
50
- # designed to be state-less. That is, the request API client will handle
51
- # the session for you instead of Rails.
48
+ # Note that API only applications don't include this module or a session middleware
49
+ # by default, and so don't require CSRF protection to be configured.
52
50
  #
53
51
  # The token parameter is named <tt>authenticity_token</tt> by default. The name and
54
52
  # value of this token must be added to every layout that renders forms by including
55
53
  # <tt>csrf_meta_tags</tt> in the HTML +head+.
56
54
  #
57
55
  # Learn more about CSRF attacks and securing your application in the
58
- # {Ruby on Rails Security Guide}[http://guides.rubyonrails.org/security.html].
56
+ # {Ruby on Rails Security Guide}[https://guides.rubyonrails.org/security.html].
59
57
  module RequestForgeryProtection
60
58
  extend ActiveSupport::Concern
61
59
 
@@ -92,6 +90,10 @@ module ActionController #:nodoc:
92
90
  config_accessor :default_protect_from_forgery
93
91
  self.default_protect_from_forgery = false
94
92
 
93
+ # Controls whether URL-safe CSRF tokens are generated.
94
+ config_accessor :urlsafe_csrf_tokens, instance_writer: false
95
+ self.urlsafe_csrf_tokens = false
96
+
95
97
  helper_method :form_authenticity_token
96
98
  helper_method :protect_against_forgery?
97
99
  end
@@ -145,7 +147,6 @@ module ActionController #:nodoc:
145
147
  end
146
148
 
147
149
  private
148
-
149
150
  def protection_method_class(name)
150
151
  ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify)
151
152
  rescue NameError
@@ -169,7 +170,6 @@ module ActionController #:nodoc:
169
170
  end
170
171
 
171
172
  private
172
-
173
173
  class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
174
174
  def initialize(req)
175
175
  super(nil, req)
@@ -276,7 +276,7 @@ module ActionController #:nodoc:
276
276
 
277
277
  # Check for cross-origin JavaScript responses.
278
278
  def non_xhr_javascript_response? # :doc:
279
- content_type =~ %r(\Atext/javascript) && !request.xhr?
279
+ %r(\A(?:text|application)/javascript).match?(media_type) && !request.xhr?
280
280
  end
281
281
 
282
282
  AUTHENTICITY_TOKEN_LENGTH = 32
@@ -321,11 +321,6 @@ module ActionController #:nodoc:
321
321
  global_csrf_token(session)
322
322
  end
323
323
 
324
- one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
325
- encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
326
- masked_token = one_time_pad + encrypted_csrf_token
327
- Base64.urlsafe_encode64(masked_token, padding: false)
328
-
329
324
  mask_token(raw_token)
330
325
  end
331
326
 
@@ -338,7 +333,7 @@ module ActionController #:nodoc:
338
333
  end
339
334
 
340
335
  begin
341
- masked_token = Base64.strict_decode64(encoded_masked_token)
336
+ masked_token = decode_csrf_token(encoded_masked_token)
342
337
  rescue ArgumentError # encoded_masked_token is invalid Base64
343
338
  return false
344
339
  end
@@ -376,7 +371,7 @@ module ActionController #:nodoc:
376
371
  one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
377
372
  encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
378
373
  masked_token = one_time_pad + encrypted_csrf_token
379
- Base64.strict_encode64(masked_token)
374
+ encode_csrf_token(masked_token)
380
375
  end
381
376
 
382
377
  def compare_with_real_token(token, session) # :doc:
@@ -391,7 +386,7 @@ module ActionController #:nodoc:
391
386
  if per_form_csrf_tokens
392
387
  correct_token = per_form_csrf_token(
393
388
  session,
394
- normalize_action_path(request.fullpath),
389
+ request.path.chomp("/"),
395
390
  request.request_method
396
391
  )
397
392
 
@@ -402,8 +397,8 @@ module ActionController #:nodoc:
402
397
  end
403
398
 
404
399
  def real_csrf_token(session) # :doc:
405
- session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
406
- Base64.strict_decode64(session[:_csrf_token])
400
+ session[:_csrf_token] ||= generate_csrf_token
401
+ decode_csrf_token(session[:_csrf_token])
407
402
  end
408
403
 
409
404
  def per_form_csrf_token(session, action_path, method) # :doc:
@@ -426,9 +421,14 @@ module ActionController #:nodoc:
426
421
  end
427
422
 
428
423
  def xor_byte_strings(s1, s2) # :doc:
429
- s2_bytes = s2.bytes
430
- s1.each_byte.with_index { |c1, i| s2_bytes[i] ^= c1 }
431
- s2_bytes.pack("C*")
424
+ s2 = s2.dup
425
+ size = s1.bytesize
426
+ i = 0
427
+ while i < size
428
+ s2.setbyte(i, s1.getbyte(i) ^ s2.getbyte(i))
429
+ i += 1
430
+ end
431
+ s2
432
432
  end
433
433
 
434
434
  # The form's authenticity parameter. Override to provide your own.
@@ -441,11 +441,11 @@ module ActionController #:nodoc:
441
441
  allow_forgery_protection
442
442
  end
443
443
 
444
- NULL_ORIGIN_MESSAGE = <<-MSG.strip_heredoc
444
+ NULL_ORIGIN_MESSAGE = <<~MSG
445
445
  The browser returned a 'null' origin for a request with origin-based forgery protection turned on. This usually
446
446
  means you have the 'no-referrer' Referrer-Policy header enabled, or that the request came from a site that
447
447
  refused to give its origin. This makes it impossible for Rails to verify the source of the requests. Likely the
448
- best solution is to change your referrer policy to something less strict like same-origin or strict-same-origin.
448
+ best solution is to change your referrer policy to something less strict like same-origin or strict-origin.
449
449
  If you cannot change the referrer policy, you can disable origin checking with the
450
450
  Rails.application.config.action_controller.forgery_protection_origin_check setting.
451
451
  MSG
@@ -466,5 +466,33 @@ module ActionController #:nodoc:
466
466
  uri = URI.parse(action_path)
467
467
  uri.path.chomp("/")
468
468
  end
469
+
470
+ def generate_csrf_token # :nodoc:
471
+ if urlsafe_csrf_tokens
472
+ SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH, padding: false)
473
+ else
474
+ SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
475
+ end
476
+ end
477
+
478
+ def encode_csrf_token(csrf_token) # :nodoc:
479
+ if urlsafe_csrf_tokens
480
+ Base64.urlsafe_encode64(csrf_token, padding: false)
481
+ else
482
+ Base64.strict_encode64(csrf_token)
483
+ end
484
+ end
485
+
486
+ def decode_csrf_token(encoded_csrf_token) # :nodoc:
487
+ if urlsafe_csrf_tokens
488
+ Base64.urlsafe_decode64(encoded_csrf_token)
489
+ else
490
+ begin
491
+ Base64.strict_decode64(encoded_csrf_token)
492
+ rescue ArgumentError
493
+ Base64.urlsafe_decode64(encoded_csrf_token)
494
+ end
495
+ end
496
+ end
469
497
  end
470
498
  end