aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_controller')
-rw-r--r--actionpack/lib/action_controller/api.rb1
-rw-r--r--actionpack/lib/action_controller/base.rb10
-rw-r--r--actionpack/lib/action_controller/metal.rb18
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb4
-rw-r--r--actionpack/lib/action_controller/metal/content_security_policy.rb52
-rw-r--r--actionpack/lib/action_controller/metal/default_headers.rb17
-rw-r--r--actionpack/lib/action_controller/metal/exceptions.rb23
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb69
-rw-r--r--actionpack/lib/action_controller/metal/head.rb2
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb23
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb14
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb3
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb6
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb50
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb2
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb56
-rw-r--r--actionpack/lib/action_controller/renderer.rb15
-rw-r--r--actionpack/lib/action_controller/test_case.rb12
18 files changed, 224 insertions, 153 deletions
diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb
index b192e496de..93ffff1bd6 100644
--- a/actionpack/lib/action_controller/api.rb
+++ b/actionpack/lib/action_controller/api.rb
@@ -122,6 +122,7 @@ module ActionController
ForceSSL,
DataStreaming,
+ DefaultHeaders,
# Before callbacks should also be executed as early as possible, so
# also include them at the bottom.
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index b73269871b..2e565d5d44 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -78,7 +78,7 @@ module ActionController
#
# You can retrieve it again through the same hash:
#
- # Hello #{session[:person]}
+ # "Hello #{session[:person]}"
#
# For removing objects from the session, you can either assign a single key to +nil+:
#
@@ -225,12 +225,14 @@ module ActionController
Flash,
FormBuilder,
RequestForgeryProtection,
+ ContentSecurityPolicy,
ForceSSL,
Streaming,
DataStreaming,
HttpAuthentication::Basic::ControllerMethods,
HttpAuthentication::Digest::ControllerMethods,
HttpAuthentication::Token::ControllerMethods,
+ DefaultHeaders,
# Before callbacks should also be executed as early as possible, so
# also include them at the bottom.
@@ -263,12 +265,6 @@ module ActionController
PROTECTED_IVARS
end
- def self.make_response!(request)
- ActionDispatch::Response.create.tap do |res|
- res.request = request
- end
- end
-
ActiveSupport.run_load_hooks(:action_controller_base, self)
ActiveSupport.run_load_hooks(:action_controller, self)
end
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 457884ea08..f875aa5e6b 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -230,18 +230,16 @@ module ActionController
# Returns a Rack endpoint for the given action name.
def self.action(name)
+ app = lambda { |env|
+ req = ActionDispatch::Request.new(env)
+ res = make_response! req
+ new.dispatch(name, req, res)
+ }
+
if middleware_stack.any?
- middleware_stack.build(name) do |env|
- req = ActionDispatch::Request.new(env)
- res = make_response! req
- new.dispatch(name, req, res)
- end
+ middleware_stack.build(name, app)
else
- lambda { |env|
- req = ActionDispatch::Request.new(env)
- res = make_response! req
- new.dispatch(name, req, res)
- }
+ app
end
end
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index 06b6a95ff8..4be4557e2c 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -235,7 +235,9 @@ module ActionController
response.cache_control.merge!(
max_age: seconds,
public: options.delete(:public),
- must_revalidate: options.delete(:must_revalidate)
+ must_revalidate: options.delete(:must_revalidate),
+ stale_while_revalidate: options.delete(:stale_while_revalidate),
+ stale_if_error: options.delete(:stale_if_error),
)
options.delete(:private)
diff --git a/actionpack/lib/action_controller/metal/content_security_policy.rb b/actionpack/lib/action_controller/metal/content_security_policy.rb
new file mode 100644
index 0000000000..b8fab4ebe3
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/content_security_policy.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module ActionController #:nodoc:
+ module ContentSecurityPolicy
+ # TODO: Documentation
+ extend ActiveSupport::Concern
+
+ include AbstractController::Helpers
+ include AbstractController::Callbacks
+
+ included do
+ helper_method :content_security_policy?
+ helper_method :content_security_policy_nonce
+ end
+
+ module ClassMethods
+ def content_security_policy(enabled = true, **options, &block)
+ before_action(options) do
+ if block_given?
+ policy = current_content_security_policy
+ yield policy
+ request.content_security_policy = policy
+ end
+
+ unless enabled
+ request.content_security_policy = nil
+ end
+ end
+ end
+
+ def content_security_policy_report_only(report_only = true, **options)
+ before_action(options) do
+ request.content_security_policy_report_only = report_only
+ end
+ end
+ end
+
+ private
+
+ def content_security_policy?
+ request.content_security_policy
+ end
+
+ def content_security_policy_nonce
+ request.content_security_policy_nonce
+ end
+
+ def current_content_security_policy
+ request.content_security_policy.try(:clone) || ActionDispatch::ContentSecurityPolicy.new
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/default_headers.rb b/actionpack/lib/action_controller/metal/default_headers.rb
new file mode 100644
index 0000000000..eef0602fcd
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/default_headers.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module ActionController
+ # Allows configuring default headers that will be automatically merged into
+ # each response.
+ module DefaultHeaders
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def make_response!(request)
+ ActionDispatch::Response.create.tap do |res|
+ res.request = request
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb
index a65857d6ef..30034be018 100644
--- a/actionpack/lib/action_controller/metal/exceptions.rb
+++ b/actionpack/lib/action_controller/metal/exceptions.rb
@@ -22,7 +22,7 @@ module ActionController
end
end
- class ActionController::UrlGenerationError < ActionControllerError #:nodoc:
+ class UrlGenerationError < ActionControllerError #:nodoc:
end
class MethodNotAllowed < ActionControllerError #:nodoc:
@@ -50,4 +50,25 @@ module ActionController
class UnknownFormat < ActionControllerError #:nodoc:
end
+
+ # Raised when a nested respond_to is triggered and the content types of each
+ # are incompatible. For exampe:
+ #
+ # respond_to do |outer_type|
+ # outer_type.js do
+ # respond_to do |inner_type|
+ # inner_type.html { render body: "HTML" }
+ # end
+ # end
+ # end
+ class RespondToMismatchError < ActionControllerError
+ DEFAULT_MESSAGE = "respond_to was called multiple times and matched with conflicting formats in this action. Please note that you may only call respond_to and match on a single format per action."
+
+ def initialize(message = nil)
+ super(message || DEFAULT_MESSAGE)
+ end
+ end
+
+ class MissingExactTemplate < UnknownFormat #:nodoc:
+ end
end
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index 0ba1f9f783..8d53a30e93 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -4,18 +4,10 @@ require "active_support/core_ext/hash/except"
require "active_support/core_ext/hash/slice"
module ActionController
- # This module provides a method which will redirect the browser to use the secured HTTPS
- # protocol. This will ensure that users' sensitive information will be
- # transferred safely over the internet. You _should_ always force the browser
- # to use HTTPS when you're transferring sensitive information such as
- # user authentication, account information, or credit card information.
- #
- # Note that if you are really concerned about your application security,
- # you might consider using +config.force_ssl+ in your config file instead.
- # That will ensure all the data is transferred via HTTPS, and will
- # prevent the user from getting their session hijacked when accessing the
- # site over unsecured HTTP protocol.
- module ForceSSL
+ # This module is deprecated in favor of +config.force_ssl+ in your environment
+ # config file. This will ensure all communication to non-whitelisted endpoints
+ # served by your application occurs over HTTPS.
+ module ForceSSL # :nodoc:
extend ActiveSupport::Concern
include AbstractController::Callbacks
@@ -23,45 +15,17 @@ module ActionController
URL_OPTIONS = [:protocol, :host, :domain, :subdomain, :port, :path]
REDIRECT_OPTIONS = [:status, :flash, :alert, :notice]
- module ClassMethods
- # Force the request to this particular controller or specified actions to be
- # through the HTTPS protocol.
- #
- # If you need to disable this for any reason (e.g. development) then you can use
- # an +:if+ or +:unless+ condition.
- #
- # class AccountsController < ApplicationController
- # force_ssl if: :ssl_configured?
- #
- # def ssl_configured?
- # !Rails.env.development?
- # end
- # end
- #
- # ==== URL Options
- # You can pass any of the following options to affect the redirect url
- # * <tt>host</tt> - Redirect to a different host name
- # * <tt>subdomain</tt> - Redirect to a different subdomain
- # * <tt>domain</tt> - Redirect to a different domain
- # * <tt>port</tt> - Redirect to a non-standard port
- # * <tt>path</tt> - Redirect to a different path
- #
- # ==== Redirect Options
- # You can pass any of the following options to affect the redirect status and response
- # * <tt>status</tt> - Redirect with a custom status (default is 301 Moved Permanently)
- # * <tt>flash</tt> - Set a flash message when redirecting
- # * <tt>alert</tt> - Set an alert message when redirecting
- # * <tt>notice</tt> - Set a notice message when redirecting
- #
- # ==== Action Options
- # You can pass any of the following options to affect the before_action callback
- # * <tt>only</tt> - The callback should be run only for this action
- # * <tt>except</tt> - The callback should be run for all actions except this action
- # * <tt>if</tt> - A symbol naming an instance method or a proc; the
- # callback will be called only when it returns a true value.
- # * <tt>unless</tt> - A symbol naming an instance method or a proc; the
- # callback will be called only when it returns a false value.
+ module ClassMethods # :nodoc:
def force_ssl(options = {})
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ Controller-level `force_ssl` is deprecated and will be removed from
+ Rails 6.1. Please enable `config.force_ssl` in your environment
+ configuration to enable the ActionDispatch::SSL middleware to more
+ fully enforce that your application communicate over HTTPS. If needed,
+ you can use `config.ssl_options` to exempt matching endpoints from
+ being redirected to HTTPS.
+ MESSAGE
+
action_options = options.slice(*ACTION_OPTIONS)
redirect_options = options.except(*ACTION_OPTIONS)
before_action(action_options) do
@@ -70,11 +34,6 @@ module ActionController
end
end
- # Redirect the existing request to use the HTTPS protocol.
- #
- # ==== Parameters
- # * <tt>host_or_options</tt> - Either a host name or any of the url and
- # redirect options available to the <tt>force_ssl</tt> method.
def force_ssl_redirect(host_or_options = nil)
unless request.ssl?
options = {
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index bac9bc5e5f..3c84bebb85 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -38,7 +38,7 @@ module ActionController
self.response_body = ""
if include_content?(response_code)
- self.content_type = content_type || (Mime[formats.first] if formats)
+ self.content_type = content_type || (Mime[formats.first] if formats) || Mime[:html]
response.charset = false
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 0c8132684a..a871ccd533 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -56,8 +56,9 @@ module ActionController
# In your integration tests, you can do something like this:
#
# def test_access_granted_from_xml
- # @request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
- # get "/notes/1.xml"
+ # authorization = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
+ #
+ # get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
#
# assert_equal 200, status
# end
@@ -72,10 +73,10 @@ module ActionController
before_action(options.except(:name, :password, :realm)) do
authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
# This comparison uses & so that it doesn't short circuit and
- # uses `variable_size_secure_compare` so that length information
+ # uses `secure_compare` so that length information
# isn't leaked.
- ActiveSupport::SecurityUtils.variable_size_secure_compare(name, options[:name]) &
- ActiveSupport::SecurityUtils.variable_size_secure_compare(password, options[:password])
+ ActiveSupport::SecurityUtils.secure_compare(name, options[:name]) &
+ ActiveSupport::SecurityUtils.secure_compare(password, options[:password])
end
end
end
@@ -350,10 +351,7 @@ module ActionController
# authenticate_or_request_with_http_token do |token, options|
# # Compare the tokens in a time-constant manner, to mitigate
# # timing attacks.
- # ActiveSupport::SecurityUtils.secure_compare(
- # ::Digest::SHA256.hexdigest(token),
- # ::Digest::SHA256.hexdigest(TOKEN)
- # )
+ # ActiveSupport::SecurityUtils.secure_compare(token, TOKEN)
# end
# end
# end
@@ -392,10 +390,9 @@ module ActionController
# In your integration tests, you can do something like this:
#
# def test_access_granted_from_xml
- # get(
- # "/notes/1.xml", nil,
- # 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
- # )
+ # authorization = ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
+ #
+ # get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
#
# assert_equal 200, status
# end
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index ac0c127cdc..d3bb58f48b 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -41,18 +41,8 @@ module ActionController
raise ActionController::UnknownFormat, message
elsif interactive_browser_request?
- message = "#{self.class.name}\##{action_name} is missing a template " \
- "for this request format and variant.\n\n" \
- "request.formats: #{request.formats.map(&:to_s).inspect}\n" \
- "request.variant: #{request.variant.inspect}\n\n" \
- "NOTE! For XHR/Ajax or API requests, this action would normally " \
- "respond with 204 No Content: an empty white screen. Since you're " \
- "loading it in a web browser, we assume that you expected to " \
- "actually render a template, not nothing, so we're showing an " \
- "error to be extra-clear. If you expect 204 No Content, carry on. " \
- "That's what you'll get from an XHR or API request. Give it a shot."
-
- raise ActionController::UnknownFormat, message
+ message = "#{self.class.name}\##{action_name} is missing a template for request formats: #{request.formats.map(&:to_s).join(',')}"
+ raise ActionController::MissingExactTemplate, message
else
logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
super
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 2233b93406..2b55b9347c 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -197,6 +197,9 @@ module ActionController #:nodoc:
yield collector if block_given?
if format = collector.negotiate_format(request)
+ if content_type && content_type != format
+ raise ActionController::RespondToMismatchError
+ end
_process_format(format)
_set_rendered_content_type format
response = collector.response
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index 8de57f9199..4c2b5120eb 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -68,7 +68,7 @@ module ActionController
# if possible, otherwise redirects to the provided default fallback
# location.
#
- # The referrer information is pulled from the HTTP `Referer` (sic) header on
+ # The referrer information is pulled from the HTTP +Referer+ (sic) header on
# the request. This is an optional header and its presence on the request is
# subject to browser security settings and user preferences. If the request
# is missing this header, the <tt>fallback_location</tt> will be used.
@@ -82,8 +82,8 @@ module ActionController
# redirect_back fallback_location: '/', allow_other_host: false
#
# ==== Options
- # * <tt>:fallback_location</tt> - The default fallback location that will be used on missing `Referer` header.
- # * <tt>:allow_other_host</tt> - Allows or disallow redirection to the host that is different to the current host
+ # * <tt>:fallback_location</tt> - The default fallback location that will be used on missing +Referer+ header.
+ # * <tt>:allow_other_host</tt> - Allow or disallow redirection to the host that is different to the current host, defaults to true.
#
# All other options that can be passed to <tt>redirect_to</tt> are accepted as
# options and the behavior is identical.
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index bd133f24a1..7ed7b9d546 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -17,7 +17,7 @@ module ActionController #:nodoc:
# access. When a request reaches your application, \Rails verifies the received
# token with the token in the session. All requests are checked except GET requests
# as these should be idempotent. Keep in mind that all session-oriented requests
- # should be CSRF protected, including JavaScript and HTML requests.
+ # are CSRF protected by default, including JavaScript and HTML requests.
#
# Since HTML and JavaScript requests are typically made from the browser, we
# need to ensure to verify request authenticity for the web browser. We can
@@ -30,16 +30,23 @@ module ActionController #:nodoc:
# URL on your site. When your JavaScript response loads on their site, it executes.
# With carefully crafted JavaScript on their end, sensitive data in your JavaScript
# response may be extracted. To prevent this, only XmlHttpRequest (known as XHR or
- # Ajax) requests are allowed to make GET requests for JavaScript responses.
+ # Ajax) requests are allowed to make requests for JavaScript responses.
#
- # It's important to remember that XML or JSON requests are also affected and if
- # you're building an API you should change forgery protection method in
+ # It's important to remember that XML or JSON requests are also checked by default. If
+ # you're building an API or an SPA you could change forgery protection method in
# <tt>ApplicationController</tt> (by default: <tt>:exception</tt>):
#
# class ApplicationController < ActionController::Base
# protect_from_forgery unless: -> { request.format.json? }
# end
#
+ # It is generally safe to exclude XHR requests from CSRF protection
+ # (like the code snippet above does), because XHR requests can only be made from
+ # the same origin. Note however that any cross-origin third party domain
+ # allowed via {CORS}[https://en.wikipedia.org/wiki/Cross-origin_resource_sharing]
+ # will also be able to create XHR requests. Be sure to check your
+ # CORS whitelist before disabling forgery protection for XHR.
+ #
# CSRF protection is turned on with the <tt>protect_from_forgery</tt> method.
# By default <tt>protect_from_forgery</tt> protects your session with
# <tt>:null_session</tt> method, which provides an empty session
@@ -54,7 +61,7 @@ module ActionController #:nodoc:
# <tt>csrf_meta_tags</tt> in the HTML +head+.
#
# Learn more about CSRF attacks and securing your application in the
- # {Ruby on Rails Security Guide}[http://guides.rubyonrails.org/security.html].
+ # {Ruby on Rails Security Guide}[https://guides.rubyonrails.org/security.html].
module RequestForgeryProtection
extend ActiveSupport::Concern
@@ -216,7 +223,7 @@ module ActionController #:nodoc:
# The actual before_action that is used to verify the CSRF token.
# Don't override this directly. Provide your own forgery protection
# strategy instead. If you override, you'll disable same-origin
- # `<script>` verification.
+ # <tt><script></tt> verification.
#
# Lean on the protect_from_forgery declaration to mark which actions are
# due for same-origin request verification. If protect_from_forgery is
@@ -250,7 +257,7 @@ module ActionController #:nodoc:
private_constant :CROSS_ORIGIN_JAVASCRIPT_WARNING
# :startdoc:
- # If `verify_authenticity_token` was run (indicating that we have
+ # If +verify_authenticity_token+ was run (indicating that we have
# forgery protection enabled for this request) then also verify that
# we aren't serving an unauthorized cross-origin response.
def verify_same_origin_request # :doc:
@@ -267,7 +274,7 @@ module ActionController #:nodoc:
@marked_for_same_origin_verification = request.get?
end
- # If the `verify_authenticity_token` before_action ran, verify that
+ # If the +verify_authenticity_token+ before_action ran, verify that
# JavaScript responses are only served to same-origin GET requests.
def marked_for_same_origin_verification? # :doc:
@marked_for_same_origin_verification ||= false
@@ -275,7 +282,7 @@ module ActionController #:nodoc:
# Check for cross-origin JavaScript responses.
def non_xhr_javascript_response? # :doc:
- content_type =~ %r(\Atext/javascript) && !request.xhr?
+ content_type =~ %r(\A(?:text|application)/javascript) && !request.xhr?
end
AUTHENTICITY_TOKEN_LENGTH = 32
@@ -369,7 +376,7 @@ module ActionController #:nodoc:
end
def compare_with_real_token(token, session) # :doc:
- ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session))
+ ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, real_csrf_token(session))
end
def valid_per_form_csrf_token?(token, session) # :doc:
@@ -380,7 +387,7 @@ module ActionController #:nodoc:
request.request_method
)
- ActiveSupport::SecurityUtils.secure_compare(token, correct_token)
+ ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, correct_token)
else
false
end
@@ -400,9 +407,14 @@ module ActionController #:nodoc:
end
def xor_byte_strings(s1, s2) # :doc:
- s2_bytes = s2.bytes
- s1.each_byte.with_index { |c1, i| s2_bytes[i] ^= c1 }
- s2_bytes.pack("C*")
+ s2 = s2.dup
+ size = s1.bytesize
+ i = 0
+ while i < size
+ s2.setbyte(i, s1.getbyte(i) ^ s2.getbyte(i))
+ i += 1
+ end
+ s2
end
# The form's authenticity parameter. Override to provide your own.
@@ -415,11 +427,21 @@ module ActionController #:nodoc:
allow_forgery_protection
end
+ NULL_ORIGIN_MESSAGE = <<~MSG
+ The browser returned a 'null' origin for a request with origin-based forgery protection turned on. This usually
+ means you have the 'no-referrer' Referrer-Policy header enabled, or that the request came from a site that
+ refused to give its origin. This makes it impossible for Rails to verify the source of the requests. Likely the
+ best solution is to change your referrer policy to something less strict like same-origin or strict-same-origin.
+ If you cannot change the referrer policy, you can disable origin checking with the
+ Rails.application.config.action_controller.forgery_protection_origin_check setting.
+ MSG
+
# Checks if the request originated from the same origin by looking at the
# Origin header.
def valid_request_origin? # :doc:
if forgery_protection_origin_check
# We accept blank origin headers because some user agents don't send it.
+ raise InvalidAuthenticityToken, NULL_ORIGIN_MESSAGE if request.origin == "null"
request.origin.nil? || request.origin == request.base_url
else
true
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 0b1598bf1b..8dc01a5eb9 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -183,7 +183,7 @@ module ActionController #:nodoc:
# unicorn_rails --config-file unicorn.config.rb
#
# You may also want to configure other parameters like <tt>:tcp_nodelay</tt>.
- # Please check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen
+ # Please check its documentation for more information: https://bogomips.org/unicorn/Unicorn/Configurator.html#method-i-listen
#
# If you are using Unicorn with NGINX, you may need to tweak NGINX.
# Streaming should work out of the box on Rainbows.
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index ef7c4c4c16..7af29f8dca 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "active_support/core_ext/hash/indifferent_access"
-require "active_support/core_ext/hash/transform_values"
require "active_support/core_ext/array/wrap"
require "active_support/core_ext/string/filters"
require "active_support/core_ext/object/to_query"
@@ -335,7 +334,7 @@ module ActionController
# the same way as <tt>Hash#each_pair</tt>.
def each_pair(&block)
@parameters.each_pair do |key, value|
- yield key, convert_hashes_to_parameters(key, value)
+ yield [key, convert_hashes_to_parameters(key, value)]
end
end
alias_method :each, :each_pair
@@ -375,7 +374,7 @@ module ActionController
# Person.new(params) # => #<Person id: nil, name: "Francesco">
def permit!
each_pair do |key, value|
- Array.wrap(value).each do |v|
+ Array.wrap(value).flatten.each do |v|
v.permit! if v.respond_to? :permit!
end
end
@@ -561,12 +560,14 @@ module ActionController
# Returns a parameter for the given +key+. If the +key+
# can't be found, there are several options: With no other arguments,
# it will raise an <tt>ActionController::ParameterMissing</tt> error;
- # if more arguments are given, then that will be returned; if a block
+ # if a second argument is given, then that is returned (converted to an
+ # instance of ActionController::Parameters if possible); if a block
# is given, then that will be run and its result returned.
#
# params = ActionController::Parameters.new(person: { name: "Francesco" })
# params.fetch(:person) # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false>
# params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
+ # params.fetch(:none, {}) # => <ActionController::Parameters {} permitted: false>
# params.fetch(:none, "Francesco") # => "Francesco"
# params.fetch(:none) { "Francesco" } # => "Francesco"
def fetch(key, *args)
@@ -581,19 +582,18 @@ module ActionController
)
end
- if Hash.method_defined?(:dig)
- # Extracts the nested parameter from the given +keys+ by calling +dig+
- # at each step. Returns +nil+ if any intermediate step is +nil+.
- #
- # params = ActionController::Parameters.new(foo: { bar: { baz: 1 } })
- # params.dig(:foo, :bar, :baz) # => 1
- # params.dig(:foo, :zot, :xyz) # => nil
- #
- # params2 = ActionController::Parameters.new(foo: [10, 11, 12])
- # params2.dig(:foo, 1) # => 11
- def dig(*keys)
- convert_value_to_parameters(@parameters.dig(*keys))
- end
+ # Extracts the nested parameter from the given +keys+ by calling +dig+
+ # at each step. Returns +nil+ if any intermediate step is +nil+.
+ #
+ # params = ActionController::Parameters.new(foo: { bar: { baz: 1 } })
+ # params.dig(:foo, :bar, :baz) # => 1
+ # params.dig(:foo, :zot, :xyz) # => nil
+ #
+ # params2 = ActionController::Parameters.new(foo: [10, 11, 12])
+ # params2.dig(:foo, 1) # => 11
+ def dig(*keys)
+ convert_hashes_to_parameters(keys.first, @parameters[keys.first])
+ @parameters.dig(*keys)
end
# Returns a new <tt>ActionController::Parameters</tt> instance that
@@ -639,20 +639,18 @@ module ActionController
# params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
# params.transform_values { |x| x * 2 }
# # => <ActionController::Parameters {"a"=>2, "b"=>4, "c"=>6} permitted: false>
- def transform_values(&block)
- if block
- new_instance_with_inherited_permitted_status(
- @parameters.transform_values(&block)
- )
- else
- @parameters.transform_values
- end
+ def transform_values
+ return to_enum(:transform_values) unless block_given?
+ new_instance_with_inherited_permitted_status(
+ @parameters.transform_values { |v| yield convert_value_to_parameters(v) }
+ )
end
# Performs values transformation and returns the altered
# <tt>ActionController::Parameters</tt> instance.
- def transform_values!(&block)
- @parameters.transform_values!(&block)
+ def transform_values!
+ return to_enum(:transform_values!) unless block_given?
+ @parameters.transform_values! { |v| yield convert_value_to_parameters(v) }
self
end
@@ -795,9 +793,7 @@ module ActionController
protected
attr_reader :parameters
- def permitted=(new_permitted)
- @permitted = new_permitted
- end
+ attr_writer :permitted
def fields_for_style?
@parameters.all? { |k, v| k =~ /\A-?\d+\z/ && (v.is_a?(Hash) || v.is_a?(Parameters)) }
diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb
index 49c5b782f0..2b4559c760 100644
--- a/actionpack/lib/action_controller/renderer.rb
+++ b/actionpack/lib/action_controller/renderer.rb
@@ -71,6 +71,21 @@ module ActionController
end
# Render templates with any options from ActionController::Base#render_to_string.
+ #
+ # The primary options are:
+ # * <tt>:partial</tt> - See <tt>ActionView::PartialRenderer</tt> for details.
+ # * <tt>:file</tt> - Renders an explicit template file. Add <tt>:locals</tt> to pass in, if so desired.
+ # It shouldn’t be used directly with unsanitized user input due to lack of validation.
+ # * <tt>:inline</tt> - Renders a ERB template string.
+ # * <tt>:plain</tt> - Renders provided text and sets the content type as <tt>text/plain</tt>.
+ # * <tt>:html</tt> - Renders the provided HTML safe string, otherwise
+ # performs HTML escape on the string first. Sets the content type as <tt>text/html</tt>.
+ # * <tt>:json</tt> - Renders the provided hash or object in JSON. You don't
+ # need to call <tt>.to_json</tt> on the object you want to render.
+ # * <tt>:body</tt> - Renders provided text and sets content type of <tt>text/plain</tt>.
+ #
+ # If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, the default is
+ # to render a partial and use the second parameter as the locals hash.
def render(*args)
raise "missing controller" unless controller
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 4b408750a4..5d784ceb31 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -256,7 +256,7 @@ module ActionController
#
# def test_create
# json = {book: { title: "Love Hina" }}.to_json
- # post :create, json
+ # post :create, body: json
# end
#
# == Special instance variables
@@ -460,10 +460,6 @@ module ActionController
def process(action, method: "GET", params: {}, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil)
check_required_ivars
- if body
- @request.set_header "RAW_POST_DATA", body
- end
-
http_method = method.to_s.upcase
@html_document = nil
@@ -478,6 +474,10 @@ module ActionController
@response.request = @request
@controller.recycle!
+ if body
+ @request.set_header "RAW_POST_DATA", body
+ end
+
@request.set_header "REQUEST_METHOD", http_method
if as
@@ -604,6 +604,8 @@ module ActionController
env.delete "action_dispatch.request.query_parameters"
env.delete "action_dispatch.request.request_parameters"
env["rack.input"] = StringIO.new
+ env.delete "CONTENT_LENGTH"
+ env.delete "RAW_POST_DATA"
env
end