actionpack 7.0.4 → 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 +397 -269
  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 +29 -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 +9 -11
  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 +108 -117
  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
@@ -17,19 +17,21 @@ module ActionController
17
17
  # return head(:bad_request) unless valid_request?
18
18
  # render
19
19
  #
20
- # See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list of valid +status+ symbols.
21
- def head(status, options = {})
20
+ # See +Rack::Utils::SYMBOL_TO_STATUS_CODE+ for a full list of valid +status+ symbols.
21
+ def head(status, options = nil)
22
22
  if status.is_a?(Hash)
23
23
  raise ArgumentError, "#{status.inspect} is not a valid value for `status`."
24
24
  end
25
25
 
26
26
  status ||= :ok
27
27
 
28
- location = options.delete(:location)
29
- content_type = options.delete(:content_type)
28
+ if options
29
+ location = options.delete(:location)
30
+ content_type = options.delete(:content_type)
30
31
 
31
- options.each do |key, value|
32
- headers[key.to_s.split(/[-_]/).each { |v| v[0] = v[0].upcase }.join("-")] = value.to_s
32
+ options.each do |key, value|
33
+ headers[key.to_s.split(/[-_]/).each { |v| v[0] = v[0].upcase }.join("-")] = value.to_s
34
+ end
33
35
  end
34
36
 
35
37
  self.status = status
@@ -37,7 +39,7 @@ module ActionController
37
39
 
38
40
  if include_content?(response_code)
39
41
  unless self.media_type
40
- self.content_type = content_type || (Mime[formats.first] if formats) || Mime[:html]
42
+ self.content_type = content_type || ((f = formats) && Mime[f.first]) || Mime[:html]
41
43
  end
42
44
 
43
45
  response.charset = false
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionController
4
+ # = Action Controller \Helpers
5
+ #
4
6
  # The \Rails framework provides a large number of helpers for working with assets, dates, forms,
5
7
  # numbers and model objects, to name a few. These helpers are available to all templates
6
8
  # by default.
@@ -80,7 +82,7 @@ module ActionController
80
82
  # Provides a proxy to access helper methods from outside the view.
81
83
  #
82
84
  # Note that the proxy is rendered under a different view context.
83
- # This may cause incorrect behaviour with capture methods. Consider
85
+ # This may cause incorrect behavior with capture methods. Consider
84
86
  # using {helper}[rdoc-ref:AbstractController::Helpers::ClassMethods#helper]
85
87
  # instead when using +capture+.
86
88
  def helpers
@@ -104,19 +106,6 @@ module ActionController
104
106
  super(args)
105
107
  end
106
108
 
107
- # Returns a list of helper names in a given path.
108
- #
109
- # ActionController::Base.all_helpers_from_path 'app/helpers'
110
- # # => ["application", "chart", "rubygems"]
111
- def all_helpers_from_path(path)
112
- helpers = Array(path).flat_map do |_path|
113
- names = Dir["#{_path}/**/*_helper.rb"].map { |file| file[_path.to_s.size + 1..-"_helper.rb".size - 1] }
114
- names.sort!
115
- end
116
- helpers.uniq!
117
- helpers
118
- end
119
-
120
109
  private
121
110
  # Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt>
122
111
  def all_application_helpers
@@ -21,7 +21,7 @@ module ActionController
21
21
  # def edit
22
22
  # render plain: "I'm only accessible if you know the password"
23
23
  # end
24
- # end
24
+ # end
25
25
  #
26
26
  # === Advanced \Basic example
27
27
  #
@@ -316,7 +316,7 @@ module ActionController
316
316
  # of this document.
317
317
  #
318
318
  # The nonce is opaque to the client. Composed of Time, and hash of Time with secret
319
- # key from the Rails session secret generated upon creation of project. Ensures
319
+ # key from the \Rails session secret generated upon creation of project. Ensures
320
320
  # the time cannot be modified by client.
321
321
  def nonce(secret_key, time = Time.now)
322
322
  t = time.to_i
@@ -424,15 +424,20 @@ module ActionController
424
424
 
425
425
  module ControllerMethods
426
426
  # Authenticate using an HTTP Bearer token, or otherwise render an HTTP
427
- # header requesting the client to send a Bearer token.
427
+ # header requesting the client to send a Bearer token. For the authentication
428
+ # to be considered successful, +login_procedure+ should return a non-nil
429
+ # value. Typically, the authenticated user is returned.
428
430
  #
429
431
  # See ActionController::HttpAuthentication::Token for example usage.
430
432
  def authenticate_or_request_with_http_token(realm = "Application", message = nil, &login_procedure)
431
433
  authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm, message)
432
434
  end
433
435
 
434
- # Authenticate using an HTTP Bearer token. Returns true if
435
- # authentication is successful, false otherwise.
436
+ # Authenticate using an HTTP Bearer token.
437
+ # Returns the return value of +login_procedure+ if a
438
+ # token is found. Returns +nil+ if no token is found.
439
+ #
440
+ # See ActionController::HttpAuthentication::Token for example usage.
436
441
  def authenticate_with_http_token(&login_procedure)
437
442
  Token.authenticate(self, &login_procedure)
438
443
  end
@@ -447,8 +452,8 @@ module ActionController
447
452
  # If token Authorization header is present, call the login
448
453
  # procedure with the present token and options.
449
454
  #
450
- # Returns the return value of <tt>login_procedure</tt> if a
451
- # token is found. Returns <tt>nil</tt> if no token is found.
455
+ # Returns the return value of +login_procedure+ if a
456
+ # token is found. Returns +nil+ if no token is found.
452
457
  #
453
458
  # ==== Parameters
454
459
  #
@@ -502,11 +507,15 @@ module ActionController
502
507
  array_params.each { |param| (param[1] || +"").gsub! %r/^"|"$/, "" }
503
508
  end
504
509
 
510
+ WHITESPACED_AUTHN_PAIR_DELIMITERS = /\s*#{AUTHN_PAIR_DELIMITERS}\s*/
511
+ private_constant :WHITESPACED_AUTHN_PAIR_DELIMITERS
512
+
505
513
  # This method takes an authorization body and splits up the key-value
506
514
  # pairs by the standardized <tt>:</tt>, <tt>;</tt>, or <tt>\t</tt>
507
515
  # delimiters defined in +AUTHN_PAIR_DELIMITERS+.
508
516
  def raw_params(auth)
509
- _raw_params = auth.sub(TOKEN_REGEX, "").split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
517
+ _raw_params = auth.sub(TOKEN_REGEX, "").split(WHITESPACED_AUTHN_PAIR_DELIMITERS)
518
+ _raw_params.reject!(&:empty?)
510
519
 
511
520
  if !_raw_params.first&.start_with?(TOKEN_KEY)
512
521
  _raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}"
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionController
4
+ # = Action Controller Implicit Render
5
+ #
4
6
  # Handles implicit rendering for a controller action that does not
5
7
  # explicitly respond with +render+, +respond_to+, +redirect+, or +head+.
6
8
  #
@@ -17,12 +19,12 @@ module ActionController
17
19
  # Second, if we DON'T find a template but the controller action does have
18
20
  # templates for other formats, variants, etc., then we trust that you meant
19
21
  # to provide a template for this response, too, and we raise
20
- # <tt>ActionController::UnknownFormat</tt> with an explanation.
22
+ # ActionController::UnknownFormat with an explanation.
21
23
  #
22
24
  # Third, if we DON'T find a template AND the request is a page load in a web
23
25
  # browser (technically, a non-XHR GET request for an HTML response) where
24
26
  # you reasonably expect to have rendered a template, then we raise
25
- # <tt>ActionController::MissingExactTemplate</tt> with an explanation.
27
+ # ActionController::MissingExactTemplate with an explanation.
26
28
  #
27
29
  # Finally, if we DON'T find a template AND the request isn't a browser page
28
30
  # load, then we implicitly respond with <tt>204 No Content</tt>.
@@ -42,7 +44,7 @@ module ActionController
42
44
  raise ActionController::UnknownFormat, message
43
45
  elsif interactive_browser_request?
44
46
  message = "#{self.class.name}\##{action_name} is missing a template for request formats: #{request.formats.map(&:to_s).join(',')}"
45
- raise ActionController::MissingExactTemplate, message
47
+ raise ActionController::MissingExactTemplate.new(message, self.class, action_name)
46
48
  else
47
49
  logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
48
50
  super
@@ -4,6 +4,8 @@ require "benchmark"
4
4
  require "abstract_controller/logger"
5
5
 
6
6
  module ActionController
7
+ # = Action Controller \Instrumentation
8
+ #
7
9
  # Adds instrumentation to several ends in ActionController::Base. It also provides
8
10
  # some hooks related with process_action. This allows an ORM like Active Record
9
11
  # and/or DataMapper to plug in ActionController and show related information.
@@ -16,6 +18,11 @@ module ActionController
16
18
 
17
19
  attr_internal :view_runtime
18
20
 
21
+ def initialize(...) # :nodoc:
22
+ super
23
+ self.view_runtime = nil
24
+ end
25
+
19
26
  def render(*)
20
27
  render_output = nil
21
28
  self.view_runtime = cleanup_view_runtime do
@@ -58,7 +65,7 @@ module ActionController
58
65
  headers: request.headers,
59
66
  format: request.format.ref,
60
67
  method: request.request_method,
61
- path: request.fullpath
68
+ path: request.filtered_path
62
69
  }
63
70
 
64
71
  ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
@@ -5,6 +5,8 @@ require "delegate"
5
5
  require "active_support/json"
6
6
 
7
7
  module ActionController
8
+ # = Action Controller \Live
9
+ #
8
10
  # Mix this module into your controller, and all actions in that controller
9
11
  # will be able to stream data to the client as it's written.
10
12
  #
@@ -34,6 +36,21 @@ module ActionController
34
36
  # The final caveat is that your actions are executed in a separate thread than
35
37
  # the main thread. Make sure your actions are thread safe, and this shouldn't
36
38
  # be a problem (don't share state across threads, etc).
39
+ #
40
+ # Note that \Rails includes +Rack::ETag+ by default, which will buffer your
41
+ # response. As a result, streaming responses may not work properly with Rack
42
+ # 2.2.x, and you may need to implement workarounds in your application.
43
+ # You can either set the +ETag+ or +Last-Modified+ response headers or remove
44
+ # +Rack::ETag+ from the middleware stack to address this issue.
45
+ #
46
+ # Here's an example of how you can set the +Last-Modified+ header if your Rack
47
+ # version is 2.2.x:
48
+ #
49
+ # def stream
50
+ # response.headers["Content-Type"] = "text/event-stream"
51
+ # response.headers["Last-Modified"] = Time.now.httpdate # Add this line if your Rack version is 2.2.x
52
+ # ...
53
+ # end
37
54
  module Live
38
55
  extend ActiveSupport::Concern
39
56
 
@@ -49,6 +66,8 @@ module ActionController
49
66
  end
50
67
  end
51
68
 
69
+ # = Action Controller \Live Server Sent Events
70
+ #
52
71
  # This class provides the ability to write an SSE (Server Sent Event)
53
72
  # to an IO stream. The class is initialized with a stream and can be used
54
73
  # to either write a JSON string or an object which can be converted to JSON.
@@ -148,6 +167,11 @@ module ActionController
148
167
  @ignore_disconnect = false
149
168
  end
150
169
 
170
+ # ActionDispatch::Response delegates #to_ary to the internal ActionDispatch::Response::Buffer,
171
+ # defining #to_ary is an indicator that the response body can be buffered and/or cached by
172
+ # Rack middlewares, this is not the case for Live responses so we undefine it for this Buffer subclass.
173
+ undef_method :to_ary
174
+
151
175
  def write(string)
152
176
  unless @response.committed?
153
177
  @response.headers["Cache-Control"] ||= "no-cache"
@@ -321,7 +345,7 @@ module ActionController
321
345
  def send_stream(filename:, disposition: "attachment", type: nil)
322
346
  response.headers["Content-Type"] =
323
347
  (type.is_a?(Symbol) ? Mime[type].to_s : type) ||
324
- Mime::Type.lookup_by_extension(File.extname(filename).downcase.delete(".")) ||
348
+ Mime::Type.lookup_by_extension(File.extname(filename).downcase.delete("."))&.to_s ||
325
349
  "application/octet-stream"
326
350
 
327
351
  response.headers["Content-Disposition"] =
@@ -32,7 +32,7 @@ module ActionController # :nodoc:
32
32
  #
33
33
  # What that says is, "if the client wants HTML or JS in response to this action, just respond as we
34
34
  # would have before, but if the client wants XML, return them the list of people in XML format."
35
- # (Rails determines the desired response format from the HTTP Accept header submitted by the client.)
35
+ # (\Rails determines the desired response format from the HTTP Accept header submitted by the client.)
36
36
  #
37
37
  # Supposing you have an action that adds a new person, optionally creating their company
38
38
  # (by name) if it does not already exist, without web-services, it might look like this:
@@ -98,7 +98,7 @@ module ActionController # :nodoc:
98
98
  #
99
99
  # Note that you can define your own XML parameter parser which would allow you to describe multiple entities
100
100
  # in a single request (i.e., by wrapping them all in a single root node), but if you just go with the flow
101
- # and accept Rails' defaults, life will be much easier.
101
+ # and accept \Rails' defaults, life will be much easier.
102
102
  #
103
103
  # If you need to use a MIME type which isn't supported by default, you can register your own handlers in
104
104
  # +config/initializers/mime_types.rb+ as follows.
@@ -6,6 +6,8 @@ require "active_support/core_ext/module/anonymous"
6
6
  require "action_dispatch/http/mime_type"
7
7
 
8
8
  module ActionController
9
+ # = Action Controller Params Wrapper
10
+ #
9
11
  # Wraps the parameters hash into a nested hash. This will allow clients to
10
12
  # submit requests without having to specify any root elements.
11
13
  #
@@ -68,9 +70,9 @@ module ActionController
68
70
  # class Admin::UsersController < ApplicationController
69
71
  # end
70
72
  #
71
- # will try to check if <tt>Admin::User</tt> or +User+ model exists, and use it to
73
+ # will try to check if +Admin::User+ or +User+ model exists, and use it to
72
74
  # determine the wrapper key respectively. If both models don't exist,
73
- # it will then fallback to use +user+ as the key.
75
+ # it will then fall back to use +user+ as the key.
74
76
  #
75
77
  # To disable this functionality for a controller:
76
78
  #
@@ -5,7 +5,7 @@ module ActionController # :nodoc:
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  module ClassMethods
8
- # Overrides parts of the globally configured Feature-Policy
8
+ # Overrides parts of the globally configured +Feature-Policy+
9
9
  # header:
10
10
  #
11
11
  # class PagesController < ApplicationController
@@ -27,7 +27,7 @@ module ActionController # :nodoc:
27
27
  before_action(options) do
28
28
  if block_given?
29
29
  policy = request.permissions_policy.clone
30
- yield policy
30
+ instance_exec(policy, &block)
31
31
  request.permissions_policy = policy
32
32
  end
33
33
  end
@@ -9,6 +9,8 @@ module ActionController
9
9
 
10
10
  class UnsafeRedirectError < StandardError; end
11
11
 
12
+ ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/.freeze
13
+
12
14
  included do
13
15
  mattr_accessor :raise_on_open_redirects, default: false
14
16
  end
@@ -21,7 +23,7 @@ module ActionController
21
23
  # * <tt>String</tt> not containing a protocol - The current protocol and host is prepended to the string.
22
24
  # * <tt>Proc</tt> - A block that will be executed in the controller's context. Should return any option accepted by +redirect_to+.
23
25
  #
24
- # === Examples:
26
+ # === Examples
25
27
  #
26
28
  # redirect_to action: "show", id: 5
27
29
  # redirect_to @post
@@ -65,8 +67,8 @@ module ActionController
65
67
  #
66
68
  # === Open Redirect protection
67
69
  #
68
- # By default, Rails protects against redirecting to external hosts for your app's safety, so called open redirects.
69
- # Note: this was a new default in Rails 7.0, after upgrading opt-in by uncommenting the line with +raise_on_open_redirects+ in <tt>config/initializers/new_framework_defaults_7_0.rb</tt>
70
+ # By default, \Rails protects against redirecting to external hosts for your app's safety, so called open redirects.
71
+ # Note: this was a new default in \Rails 7.0, after upgrading opt-in by uncommenting the line with +raise_on_open_redirects+ in <tt>config/initializers/new_framework_defaults_7_0.rb</tt>
70
72
  #
71
73
  # Here #redirect_to automatically validates the potentially-unsafe URL:
72
74
  #
@@ -85,9 +87,13 @@ module ActionController
85
87
 
86
88
  allow_other_host = response_options.delete(:allow_other_host) { _allow_other_host }
87
89
 
88
- self.status = _extract_redirect_to_status(options, response_options)
89
- self.location = _enforce_open_redirect_protection(_compute_redirect_to_location(request, options), allow_other_host: allow_other_host)
90
- self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(response.location)}\">redirected</a>.</body></html>"
90
+ self.status = _extract_redirect_to_status(options, response_options)
91
+
92
+ redirect_to_location = _compute_redirect_to_location(request, options)
93
+ _ensure_url_is_http_header_safe(redirect_to_location)
94
+
95
+ self.location = _enforce_open_redirect_protection(redirect_to_location, allow_other_host: allow_other_host)
96
+ self.response_body = ""
91
97
  end
92
98
 
93
99
  # Soft deprecated alias for #redirect_back_or_to where the +fallback_location+ location is supplied as a keyword argument instead
@@ -148,7 +154,7 @@ module ActionController
148
154
  public :_compute_redirect_to_location
149
155
 
150
156
  # Verifies the passed +location+ is an internal URL that's safe to redirect to and returns it, or nil if not.
151
- # Useful to wrap a params provided redirect URL and fallback to an alternate URL to redirect to:
157
+ # Useful to wrap a params provided redirect URL and fall back to an alternate URL to redirect to:
152
158
  #
153
159
  # redirect_to url_from(params[:redirect_url]) || root_url
154
160
  #
@@ -196,9 +202,24 @@ module ActionController
196
202
 
197
203
  def _url_host_allowed?(url)
198
204
  host = URI(url.to_s).host
199
- host == request.host || host.nil? && url.to_s.start_with?("/")
205
+
206
+ return true if host == request.host
207
+ return false unless host.nil?
208
+ return false unless url.to_s.start_with?("/")
209
+ !url.to_s.start_with?("//")
200
210
  rescue ArgumentError, URI::Error
201
211
  false
202
212
  end
213
+
214
+ def _ensure_url_is_http_header_safe(url)
215
+ # Attempt to comply with the set of valid token characters
216
+ # defined for an HTTP header value in
217
+ # https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
218
+ if url.match?(ILLEGAL_HEADER_VALUE_REGEX)
219
+ msg = "The redirect URL #{url} contains one or more illegal HTTP header field character. " \
220
+ "Set of legal characters defined in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6"
221
+ raise UnsafeRedirectError, msg
222
+ end
223
+ end
203
224
  end
204
225
  end
@@ -3,12 +3,12 @@
3
3
  require "set"
4
4
 
5
5
  module ActionController
6
- # See <tt>Renderers.add</tt>
6
+ # See Renderers.add
7
7
  def self.add_renderer(key, &block)
8
8
  Renderers.add(key, &block)
9
9
  end
10
10
 
11
- # See <tt>Renderers.remove</tt>
11
+ # See Renderers.remove
12
12
  def self.remove_renderer(key)
13
13
  Renderers.remove(key)
14
14
  end
@@ -58,7 +58,7 @@ module ActionController
58
58
  # disposition: "attachment; filename=#{filename}.csv"
59
59
  # end
60
60
  #
61
- # Note that we used Mime[:csv] for the csv mime type as it comes with Rails.
61
+ # Note that we used Mime[:csv] for the csv mime type as it comes with \Rails.
62
62
  # For a custom renderer, you'll need to register a mime type with
63
63
  # <tt>Mime::Type.register</tt>.
64
64
  #
@@ -100,7 +100,7 @@ module ActionController
100
100
  #
101
101
  # Both ActionController::Base and ActionController::API
102
102
  # include ActionController::Renderers::All, making all renderers
103
- # available in the controller. See <tt>Renderers::RENDERERS</tt> and <tt>Renderers.add</tt>.
103
+ # available in the controller. See Renderers::RENDERERS and Renderers.add.
104
104
  #
105
105
  # Since ActionController::Metal controllers cannot render, the controller
106
106
  # must include AbstractController::Rendering, ActionController::Rendering,
@@ -24,12 +24,124 @@ module ActionController
24
24
  end
25
25
  end
26
26
 
27
+ # Renders a template and assigns the result to +self.response_body+.
28
+ #
29
+ # If no rendering mode option is specified, the template will be derived
30
+ # from the first argument.
31
+ #
32
+ # render "posts/show"
33
+ # # => renders app/views/posts/show.html.erb
34
+ #
35
+ # # In a PostsController action...
36
+ # render :show
37
+ # # => renders app/views/posts/show.html.erb
38
+ #
39
+ # If the first argument responds to +render_in+, the template will be
40
+ # rendered by calling +render_in+ with the current view context.
41
+ #
42
+ # ==== \Rendering Mode
43
+ #
44
+ # [+:partial+]
45
+ # See ActionView::PartialRenderer for details.
46
+ #
47
+ # render partial: "posts/form", locals: { post: Post.new }
48
+ # # => renders app/views/posts/_form.html.erb
49
+ #
50
+ # [+:file+]
51
+ # Renders the contents of a file. This option should <b>not</b> be used
52
+ # with unsanitized user input.
53
+ #
54
+ # render file: "/path/to/some/file"
55
+ # # => renders /path/to/some/file
56
+ #
57
+ # [+:inline+]
58
+ # Renders an ERB template string.
59
+ #
60
+ # @name = "World"
61
+ # render inline: "<h1>Hello, <%= @name %>!</h1>"
62
+ # # => renders "<h1>Hello, World!</h1>"
63
+ #
64
+ # [+:body+]
65
+ # Renders the provided text, and sets the content type as +text/plain+.
66
+ #
67
+ # render body: "Hello, World!"
68
+ # # => renders "Hello, World!"
69
+ #
70
+ # [+:plain+]
71
+ # Renders the provided text, and sets the content type as +text/plain+.
72
+ #
73
+ # render plain: "Hello, World!"
74
+ # # => renders "Hello, World!"
75
+ #
76
+ # [+:html+]
77
+ # Renders the provided HTML string, and sets the content type as +text/html+.
78
+ # If the string is not +html_safe?+, performs HTML escaping on the string
79
+ # before rendering.
80
+ #
81
+ # render html: "<h1>Hello, World!</h1>".html_safe
82
+ # # => renders "<h1>Hello, World!</h1>"
83
+ #
84
+ # render html: "<h1>Hello, World!</h1>"
85
+ # # => renders "&lt;h1&gt;Hello, World!&lt;/h1&gt;"
86
+ #
87
+ # [+:json+]
88
+ # Renders the provided object as JSON, and sets the content type as
89
+ # +application/json+. If the object is not a string, it will be converted
90
+ # to JSON by calling +to_json+.
91
+ #
92
+ # render json: { hello: "world" }
93
+ # # => renders "{\"hello\":\"world\"}"
94
+ #
95
+ # By default, when a rendering mode is specified, no layout template is
96
+ # rendered.
97
+ #
98
+ # ==== Options
99
+ #
100
+ # [+:assigns+]
101
+ # Hash of instance variable assignments for the template.
102
+ #
103
+ # render inline: "<h1>Hello, <%= @name %>!</h1>", assigns: { name: "World" }
104
+ # # => renders "<h1>Hello, World!</h1>"
105
+ #
106
+ # [+:locals+]
107
+ # Hash of local variable assignments for the template.
108
+ #
109
+ # render inline: "<h1>Hello, <%= name %>!</h1>", locals: { name: "World" }
110
+ # # => renders "<h1>Hello, World!</h1>"
111
+ #
112
+ # [+:layout+]
113
+ # The layout template to render. Can also be +false+ or +true+ to disable
114
+ # or (re)enable the default layout template.
115
+ #
116
+ # render "posts/show", layout: "holiday"
117
+ # # => renders app/views/posts/show.html.erb with the app/views/layouts/holiday.html.erb layout
118
+ #
119
+ # render "posts/show", layout: false
120
+ # # => renders app/views/posts/show.html.erb with no layout
121
+ #
122
+ # render inline: "<h1>Hello, World!</h1>", layout: true
123
+ # # => renders "<h1>Hello, World!</h1>" with the default layout
124
+ #
125
+ # [+:status+]
126
+ # The HTTP status code to send with the response. Can be specified as a
127
+ # number or as the status name in Symbol form. Defaults to 200.
128
+ #
129
+ # render "posts/new", status: 422
130
+ # # => renders app/views/posts/new.html.erb with HTTP status code 422
131
+ #
132
+ # render "posts/new", status: :unprocessable_entity
133
+ # # => renders app/views/posts/new.html.erb with HTTP status code 422
134
+ #
135
+ #--
27
136
  # Check for double render errors and set the content_type after rendering.
28
- def render(*args) # :nodoc:
137
+ def render(*args)
29
138
  raise ::AbstractController::DoubleRenderError if response_body
30
139
  super
31
140
  end
32
141
 
142
+ # Similar to #render, but only returns the rendered template as a string,
143
+ # instead of setting +self.response_body+.
144
+ #--
33
145
  # Override render_to_string because body can now be set to a Rack body.
34
146
  def render_to_string(*)
35
147
  result = super
@@ -42,7 +154,7 @@ module ActionController
42
154
  end
43
155
  end
44
156
 
45
- def render_to_body(options = {})
157
+ def render_to_body(options = {}) # :nodoc:
46
158
  super || _render_in_priorities(options) || " "
47
159
  end
48
160
 
@@ -83,13 +195,6 @@ module ActionController
83
195
  end
84
196
  end
85
197
 
86
- # Normalize arguments by catching blocks and setting them on :update.
87
- def _normalize_args(action = nil, options = {}, &blk)
88
- options = super
89
- options[:update] = blk if block_given?
90
- options
91
- end
92
-
93
198
  # Normalize both text and status options.
94
199
  def _normalize_options(options)
95
200
  _normalize_text(options)