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.rb45
-rw-r--r--actionpack/lib/action_controller/base.rb22
-rw-r--r--actionpack/lib/action_controller/caching.rb67
-rw-r--r--actionpack/lib/action_controller/caching/fragments.rb148
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb4
-rw-r--r--actionpack/lib/action_controller/metal.rb67
-rw-r--r--actionpack/lib/action_controller/metal/basic_implicit_render.rb2
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb88
-rw-r--r--actionpack/lib/action_controller/metal/cookies.rb2
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb34
-rw-r--r--actionpack/lib/action_controller/metal/etag_with_flash.rb16
-rw-r--r--actionpack/lib/action_controller/metal/etag_with_template_digest.rb26
-rw-r--r--actionpack/lib/action_controller/metal/exceptions.rb16
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb20
-rw-r--r--actionpack/lib/action_controller/metal/head.rb30
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb17
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb48
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb77
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb25
-rw-r--r--actionpack/lib/action_controller/metal/live.rb54
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb8
-rw-r--r--actionpack/lib/action_controller/metal/parameter_encoding.rb30
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb60
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb30
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb7
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb108
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb64
-rw-r--r--actionpack/lib/action_controller/metal/rescue.rb15
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb2
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb212
-rw-r--r--actionpack/lib/action_controller/metal/testing.rb2
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb8
-rw-r--r--actionpack/lib/action_controller/railtie.rb2
-rw-r--r--actionpack/lib/action_controller/renderer.rb36
-rw-r--r--actionpack/lib/action_controller/test_case.rb290
35 files changed, 778 insertions, 904 deletions
diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb
index ff12705abe..5cd8d77ddb 100644
--- a/actionpack/lib/action_controller/api.rb
+++ b/actionpack/lib/action_controller/api.rb
@@ -1,6 +1,6 @@
-require 'action_view'
-require 'action_controller'
-require 'action_controller/log_subscriber'
+require "action_view"
+require "action_controller"
+require "action_controller/log_subscriber"
module ActionController
# API Controller is a lightweight version of <tt>ActionController::Base</tt>,
@@ -14,22 +14,22 @@ module ActionController
# flash, assets, and so on. This makes the entire controller stack thinner,
# suitable for API applications. It doesn't mean you won't have such
# features if you need them: they're all available for you to include in
- # your application, they're just not part of the default API Controller stack.
+ # your application, they're just not part of the default API controller stack.
#
- # By default, only the ApplicationController in a \Rails application inherits
- # from <tt>ActionController::API</tt>. All other controllers in turn inherit
- # from ApplicationController.
+ # Normally, +ApplicationController+ is the only controller that inherits from
+ # <tt>ActionController::API</tt>. All other controllers in turn inherit from
+ # +ApplicationController+.
#
# A sample controller could look like this:
#
# class PostsController < ApplicationController
# def index
- # @posts = Post.all
- # render json: @posts
+ # posts = Post.all
+ # render json: posts
# end
# end
#
- # Request, response and parameters objects all work the exact same way as
+ # Request, response, and parameters objects all work the exact same way as
# <tt>ActionController::Base</tt>.
#
# == Renders
@@ -37,18 +37,18 @@ module ActionController
# The default API Controller stack includes all renderers, which means you
# can use <tt>render :json</tt> and brothers freely in your controllers. Keep
# in mind that templates are not going to be rendered, so you need to ensure
- # your controller is calling either <tt>render</tt> or <tt>redirect</tt> in
- # all actions, otherwise it will return 204 No Content response.
+ # your controller is calling either <tt>render</tt> or <tt>redirect_to</tt> in
+ # all actions, otherwise it will return 204 No Content.
#
# def show
- # @post = Post.find(params[:id])
- # render json: @post
+ # post = Post.find(params[:id])
+ # render json: post
# end
#
# == Redirects
#
# Redirects are used to move from one action to another. You can use the
- # <tt>redirect</tt> method in your controllers in the same way as
+ # <tt>redirect_to</tt> method in your controllers in the same way as in
# <tt>ActionController::Base</tt>. For example:
#
# def create
@@ -56,7 +56,7 @@ module ActionController
# # do stuff here
# end
#
- # == Adding new behavior
+ # == Adding New Behavior
#
# In some scenarios you may want to add back some functionality provided by
# <tt>ActionController::Base</tt> that is not present by default in
@@ -72,18 +72,19 @@ module ActionController
#
# class PostsController < ApplicationController
# def index
- # @posts = Post.all
+ # posts = Post.all
#
# respond_to do |format|
- # format.json { render json: @posts }
- # format.xml { render xml: @posts }
+ # format.json { render json: posts }
+ # format.xml { render xml: posts }
# end
# end
# end
#
- # Quite straightforward. Make sure to check <tt>ActionController::Base</tt>
- # available modules if you want to include any other functionality that is
- # not provided by <tt>ActionController::API</tt> out of the box.
+ # Quite straightforward. Make sure to check the modules included in
+ # <tt>ActionController::Base</tt> if you want to use any other
+ # functionality that is not provided by <tt>ActionController::API</tt>
+ # out of the box.
class API < Metal
abstract!
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 04e5922ce8..ca8066cd82 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -1,4 +1,4 @@
-require 'action_view'
+require "action_view"
require "action_controller/log_subscriber"
require "action_controller/metal/params_wrapper"
@@ -32,7 +32,7 @@ module ActionController
# new post), it initiates a redirect instead. This redirect works by returning an external
# "302 Moved" HTTP response that takes the user to the index action.
#
- # These two methods represent the two basic action archetypes used in Action Controllers. Get-and-show and do-and-redirect.
+ # These two methods represent the two basic action archetypes used in Action Controllers: Get-and-show and do-and-redirect.
# Most actions are variations on these themes.
#
# == Requests
@@ -51,8 +51,8 @@ module ActionController
# == Parameters
#
# All request parameters, whether they come from a query string in the URL or form data submitted through a POST request are
- # available through the params method which returns a hash. For example, an action that was performed through
- # <tt>/posts?category=All&limit=5</tt> will include <tt>{ "category" => "All", "limit" => "5" }</tt> in params.
+ # available through the <tt>params</tt> method which returns a hash. For example, an action that was performed through
+ # <tt>/posts?category=All&limit=5</tt> will include <tt>{ "category" => "All", "limit" => "5" }</tt> in <tt>params</tt>.
#
# It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as:
#
@@ -60,7 +60,7 @@ module ActionController
# <input type="text" name="post[address]" value="hyacintvej">
#
# A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>.
- # If the address input had been named <tt>post[address][street]</tt>, the params would have included
+ # If the address input had been named <tt>post[address][street]</tt>, the <tt>params</tt> would have included
# <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting.
#
# == Sessions
@@ -213,11 +213,12 @@ module ActionController
Renderers::All,
ConditionalGet,
EtagWithTemplateDigest,
+ EtagWithFlash,
Caching,
MimeResponds,
ImplicitRender,
StrongParameters,
-
+ ParameterEncoding,
Cookies,
Flash,
FormBuilder,
@@ -229,7 +230,7 @@ module ActionController
HttpAuthentication::Digest::ControllerMethods,
HttpAuthentication::Token::ControllerMethods,
- # Before callbacks should also be executed the earliest as possible, so
+ # Before callbacks should also be executed as early as possible, so
# also include them at the bottom.
AbstractController::Callbacks,
@@ -251,9 +252,10 @@ module ActionController
setup_renderer!
# Define some internal variables that should not be propagated to the view.
- PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [
- :@_params, :@_response, :@_request,
- :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout ]
+ PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + %i(
+ @_params @_response @_request @_config @_url_options @_action_has_layout @_view_context_class
+ @_view_renderer @_lookup_context @_routes @_view_runtime @_db_runtime @_helper_proxy
+ )
def _protected_ivars # :nodoc:
PROTECTED_IVARS
diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb
index 0b8fa2ea09..a9a8508abc 100644
--- a/actionpack/lib/action_controller/caching.rb
+++ b/actionpack/lib/action_controller/caching.rb
@@ -1,6 +1,3 @@
-require 'fileutils'
-require 'uri'
-
module ActionController
# \Caching is a cheap way of speeding up slow applications by keeping the result of
# calculations, renderings, and database calls around for subsequent requests.
@@ -23,65 +20,25 @@ module ActionController
# config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new('localhost:11211')
# config.action_controller.cache_store = MyOwnStore.new('parameter')
module Caching
- extend ActiveSupport::Concern
extend ActiveSupport::Autoload
-
- eager_autoload do
- autoload :Fragments
- end
-
- module ConfigMethods
- def cache_store
- config.cache_store
- end
-
- def cache_store=(store)
- config.cache_store = ActiveSupport::Cache.lookup_store(store)
- end
-
- private
- def cache_configured?
- perform_caching && cache_store
- end
- end
-
- include AbstractController::Callbacks
-
- include ConfigMethods
- include Fragments
+ extend ActiveSupport::Concern
included do
- extend ConfigMethods
-
- config_accessor :default_static_extension
- self.default_static_extension ||= '.html'
-
- config_accessor :perform_caching
- self.perform_caching = true if perform_caching.nil?
-
- class_attribute :_view_cache_dependencies
- self._view_cache_dependencies = []
- helper_method :view_cache_dependencies if respond_to?(:helper_method)
+ include AbstractController::Caching
end
- module ClassMethods
- def view_cache_dependency(&dependency)
- self._view_cache_dependencies += [dependency]
- end
- end
+ private
- def view_cache_dependencies
- self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact
- end
+ def instrument_payload(key)
+ {
+ controller: controller_name,
+ action: action_name,
+ key: key
+ }
+ end
- protected
- # Convenience accessor.
- def cache(key, options = {}, &block)
- if cache_configured?
- cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block)
- else
- yield
- end
+ def instrument_name
+ "action_controller"
end
end
end
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb
deleted file mode 100644
index b9ad51a9cf..0000000000
--- a/actionpack/lib/action_controller/caching/fragments.rb
+++ /dev/null
@@ -1,148 +0,0 @@
-module ActionController
- module Caching
- # Fragment caching is used for caching various blocks within
- # views without caching the entire action as a whole. This is
- # useful when certain elements of an action change frequently or
- # depend on complicated state while other parts rarely change or
- # can be shared amongst multiple parties. The caching is done using
- # the +cache+ helper available in the Action View. See
- # ActionView::Helpers::CacheHelper for more information.
- #
- # While it's strongly recommended that you use key-based cache
- # expiration (see links in CacheHelper for more information),
- # it is also possible to manually expire caches. For example:
- #
- # expire_fragment('name_of_cache')
- module Fragments
- extend ActiveSupport::Concern
-
- included do
- if respond_to?(:class_attribute)
- class_attribute :fragment_cache_keys
- else
- mattr_writer :fragment_cache_keys
- end
-
- self.fragment_cache_keys = []
-
- helper_method :fragment_cache_key if respond_to?(:helper_method)
- end
-
- module ClassMethods
- # Allows you to specify controller-wide key prefixes for
- # cache fragments. Pass either a constant +value+, or a block
- # which computes a value each time a cache key is generated.
- #
- # For example, you may want to prefix all fragment cache keys
- # with a global version identifier, so you can easily
- # invalidate all caches.
- #
- # class ApplicationController
- # fragment_cache_key "v1"
- # end
- #
- # When it's time to invalidate all fragments, simply change
- # the string constant. Or, progressively roll out the cache
- # invalidation using a computed value:
- #
- # class ApplicationController
- # fragment_cache_key do
- # @account.id.odd? ? "v1" : "v2"
- # end
- # end
- def fragment_cache_key(value = nil, &key)
- self.fragment_cache_keys += [key || ->{ value }]
- end
- end
-
- # Given a key (as described in +expire_fragment+), returns
- # a key suitable for use in reading, writing, or expiring a
- # cached fragment. All keys begin with <tt>views/</tt>,
- # followed by any controller-wide key prefix values, ending
- # with the specified +key+ value. The key is expanded using
- # ActiveSupport::Cache.expand_cache_key.
- def fragment_cache_key(key)
- head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) }
- tail = key.is_a?(Hash) ? url_for(key).split("://").last : key
- ActiveSupport::Cache.expand_cache_key([*head, *tail], :views)
- end
-
- # Writes +content+ to the location signified by
- # +key+ (see +expire_fragment+ for acceptable formats).
- def write_fragment(key, content, options = nil)
- return content unless cache_configured?
-
- key = fragment_cache_key(key)
- instrument_fragment_cache :write_fragment, key do
- content = content.to_str
- cache_store.write(key, content, options)
- end
- content
- end
-
- # Reads a cached fragment from the location signified by +key+
- # (see +expire_fragment+ for acceptable formats).
- def read_fragment(key, options = nil)
- return unless cache_configured?
-
- key = fragment_cache_key(key)
- instrument_fragment_cache :read_fragment, key do
- result = cache_store.read(key, options)
- result.respond_to?(:html_safe) ? result.html_safe : result
- end
- end
-
- # Check if a cached fragment from the location signified by
- # +key+ exists (see +expire_fragment+ for acceptable formats).
- def fragment_exist?(key, options = nil)
- return unless cache_configured?
- key = fragment_cache_key(key)
-
- instrument_fragment_cache :exist_fragment?, key do
- cache_store.exist?(key, options)
- end
- end
-
- # Removes fragments from the cache.
- #
- # +key+ can take one of three forms:
- #
- # * String - This would normally take the form of a path, like
- # <tt>pages/45/notes</tt>.
- # * Hash - Treated as an implicit call to +url_for+, like
- # <tt>{ controller: 'pages', action: 'notes', id: 45}</tt>
- # * Regexp - Will remove any fragment that matches, so
- # <tt>%r{pages/\d*/notes}</tt> might remove all notes. Make sure you
- # don't use anchors in the regex (<tt>^</tt> or <tt>$</tt>) because
- # the actual filename matched looks like
- # <tt>./cache/filename/path.cache</tt>. Note: Regexp expiration is
- # only supported on caches that can iterate over all keys (unlike
- # memcached).
- #
- # +options+ is passed through to the cache store's +delete+
- # method (or <tt>delete_matched</tt>, for Regexp keys).
- def expire_fragment(key, options = nil)
- return unless cache_configured?
- key = fragment_cache_key(key) unless key.is_a?(Regexp)
-
- instrument_fragment_cache :expire_fragment, key do
- if key.is_a?(Regexp)
- cache_store.delete_matched(key, options)
- else
- cache_store.delete(key, options)
- end
- end
- end
-
- def instrument_fragment_cache(name, key) # :nodoc:
- payload = {
- controller: controller_name,
- action: action_name,
- key: key
- }
-
- ActiveSupport::Notifications.instrument("#{name}.action_controller", payload) { yield }
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index a0917b4fdb..d29a5fe68f 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -51,7 +51,7 @@ module ActionController
def unpermitted_parameters(event)
debug do
unpermitted_keys = event.payload[:keys]
- "Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.join(", ")}"
+ "Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.map { |e| ":#{e}" }.join(", ")}"
end
end
@@ -59,7 +59,7 @@ module ActionController
expire_fragment expire_page write_page).each do |method|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{method}(event)
- return unless logger.info?
+ return unless logger.info? && ActionController::Base.enable_fragment_cache_logging
key_or_path = event.payload[:key] || event.payload[:path]
human_name = #{method.to_s.humanize.inspect}
info("\#{human_name} \#{key_or_path} (\#{event.duration.round(1)}ms)")
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 1641d01c30..ed93a2f09c 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -1,7 +1,7 @@
-require 'active_support/core_ext/array/extract_options'
-require 'action_dispatch/middleware/stack'
-require 'action_dispatch/http/request'
-require 'action_dispatch/http/response'
+require "active_support/core_ext/array/extract_options"
+require "action_dispatch/middleware/stack"
+require "action_dispatch/http/request"
+require "action_dispatch/http/response"
module ActionController
# Extend ActionDispatch middleware stack to make it aware of options
@@ -34,29 +34,29 @@ module ActionController
private
- INCLUDE = ->(list, action) { list.include? action }
- EXCLUDE = ->(list, action) { !list.include? action }
- NULL = ->(list, action) { true }
-
- def build_middleware(klass, args, block)
- options = args.extract_options!
- only = Array(options.delete(:only)).map(&:to_s)
- except = Array(options.delete(:except)).map(&:to_s)
- args << options unless options.empty?
-
- strategy = NULL
- list = nil
-
- if only.any?
- strategy = INCLUDE
- list = only
- elsif except.any?
- strategy = EXCLUDE
- list = except
- end
+ INCLUDE = ->(list, action) { list.include? action }
+ EXCLUDE = ->(list, action) { !list.include? action }
+ NULL = ->(list, action) { true }
+
+ def build_middleware(klass, args, block)
+ options = args.extract_options!
+ only = Array(options.delete(:only)).map(&:to_s)
+ except = Array(options.delete(:except)).map(&:to_s)
+ args << options unless options.empty?
+
+ strategy = NULL
+ list = nil
+
+ if only.any?
+ strategy = INCLUDE
+ list = only
+ elsif except.any?
+ strategy = EXCLUDE
+ list = except
+ end
- Middleware.new(get_class(klass), args, list, strategy, block)
- end
+ Middleware.new(klass, args, list, strategy, block)
+ end
end
# <tt>ActionController::Metal</tt> is the simplest possible controller, providing a
@@ -130,7 +130,7 @@ module ActionController
# ==== Returns
# * <tt>string</tt>
def self.controller_name
- @controller_name ||= name.demodulize.sub(/Controller$/, '').underscore
+ @controller_name ||= name.demodulize.sub(/Controller$/, "").underscore
end
def self.make_response!(request)
@@ -139,15 +139,19 @@ module ActionController
end
end
+ def self.encoding_for_param(action, param) # :nodoc:
+ ::Encoding::UTF_8
+ end
+
# Delegates to the class' <tt>controller_name</tt>
def controller_name
self.class.controller_name
end
attr_internal :response, :request
- delegate :session, :to => "@_request"
+ delegate :session, to: "@_request"
delegate :headers, :status=, :location=, :content_type=,
- :status, :location, :content_type, :to => "@_response"
+ :status, :location, :content_type, to: "@_response"
def initialize
@_request = nil
@@ -175,10 +179,7 @@ module ActionController
body = [body] unless body.nil? || body.respond_to?(:each)
response.reset_body!
return unless body
- body.each { |part|
- next if part.empty?
- response.write part
- }
+ response.body = body
super
end
diff --git a/actionpack/lib/action_controller/metal/basic_implicit_render.rb b/actionpack/lib/action_controller/metal/basic_implicit_render.rb
index 6c6f8381ff..cef65a362c 100644
--- a/actionpack/lib/action_controller/metal/basic_implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/basic_implicit_render.rb
@@ -1,5 +1,5 @@
module ActionController
- module BasicImplicitRender
+ module BasicImplicitRender # :nodoc:
def send_action(method, *args)
super.tap { default_render unless performed? }
end
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index f8e0d9cf6c..89bf60a0bb 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/hash/keys'
+require "active_support/core_ext/hash/keys"
module ActionController
module ConditionalGet
@@ -36,8 +36,23 @@ module ActionController
#
# === Parameters:
#
- # * <tt>:etag</tt>.
- # * <tt>:last_modified</tt>.
+ # * <tt>:etag</tt> Sets a "weak" ETag validator on the response. See the
+ # +:weak_etag+ option.
+ # * <tt>:weak_etag</tt> Sets a "weak" ETag validator on the response.
+ # Requests that set If-None-Match header may return a 304 Not Modified
+ # response if it matches the ETag exactly. A weak ETag indicates semantic
+ # equivalence, not byte-for-byte equality, so they're good for caching
+ # HTML pages in browser caches. They can't be used for responses that
+ # must be byte-identical, like serving Range requests within a PDF file.
+ # * <tt>:strong_etag</tt> Sets a "strong" ETag validator on the response.
+ # Requests that set If-None-Match header may return a 304 Not Modified
+ # response if it matches the ETag exactly. A strong ETag implies exact
+ # equality: the response must match byte for byte. This is necessary for
+ # doing Range requests within a large video or PDF file, for example, or
+ # for compatibility with some CDNs that don't support weak ETags.
+ # * <tt>:last_modified</tt> Sets a "weak" last-update validator on the
+ # response. Subsequent requests that set If-Modified-Since may return a
+ # 304 Not Modified response if last_modified <= If-Modified-Since.
# * <tt>:public</tt> By default the Cache-Control header is private, set this to
# +true+ if you want your application to be cacheable by other devices (proxy caches).
# * <tt>:template</tt> By default, the template digest for the current
@@ -86,12 +101,16 @@ module ActionController
#
# before_action { fresh_when @article, template: 'widgets/show' }
#
- def fresh_when(object = nil, etag: object, last_modified: nil, public: false, template: nil)
+ def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, template: nil)
+ weak_etag ||= etag || object unless strong_etag
last_modified ||= object.try(:updated_at) || object.try(:maximum, :updated_at)
- if etag || template
- response.etag = combine_etags(etag: etag, last_modified: last_modified,
- public: public, template: template)
+ if strong_etag
+ response.strong_etag = combine_etags strong_etag,
+ last_modified: last_modified, public: public, template: template
+ elsif weak_etag || template
+ response.weak_etag = combine_etags weak_etag,
+ last_modified: last_modified, public: public, template: template
end
response.last_modified = last_modified if last_modified
@@ -107,8 +126,23 @@ module ActionController
#
# === Parameters:
#
- # * <tt>:etag</tt>.
- # * <tt>:last_modified</tt>.
+ # * <tt>:etag</tt> Sets a "weak" ETag validator on the response. See the
+ # +:weak_etag+ option.
+ # * <tt>:weak_etag</tt> Sets a "weak" ETag validator on the response.
+ # Requests that set If-None-Match header may return a 304 Not Modified
+ # response if it matches the ETag exactly. A weak ETag indicates semantic
+ # equivalence, not byte-for-byte equality, so they're good for caching
+ # HTML pages in browser caches. They can't be used for responses that
+ # must be byte-identical, like serving Range requests within a PDF file.
+ # * <tt>:strong_etag</tt> Sets a "strong" ETag validator on the response.
+ # Requests that set If-None-Match header may return a 304 Not Modified
+ # response if it matches the ETag exactly. A strong ETag implies exact
+ # equality: the response must match byte for byte. This is necessary for
+ # doing Range requests within a large video or PDF file, for example, or
+ # for compatibility with some CDNs that don't support weak ETags.
+ # * <tt>:last_modified</tt> Sets a "weak" last-update validator on the
+ # response. Subsequent requests that set If-Modified-Since may return a
+ # 304 Not Modified response if last_modified <= If-Modified-Since.
# * <tt>:public</tt> By default the Cache-Control header is private, set this to
# +true+ if you want your application to be cacheable by other devices (proxy caches).
# * <tt>:template</tt> By default, the template digest for the current
@@ -180,12 +214,12 @@ module ActionController
# super if stale? @article, template: 'widgets/show'
# end
#
- def stale?(object = nil, etag: object, last_modified: nil, public: nil, template: nil)
- fresh_when(object, etag: etag, last_modified: last_modified, public: public, template: template)
+ def stale?(object = nil, **freshness_kwargs)
+ fresh_when(object, **freshness_kwargs)
!request.fresh?(response)
end
- # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a +private+
+ # Sets an HTTP 1.1 Cache-Control header. Defaults to issuing a +private+
# instruction, so that intermediate caches must not cache the response.
#
# expires_in 20.minutes
@@ -195,47 +229,45 @@ module ActionController
# This method will overwrite an existing Cache-Control header.
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
#
- # The method will also ensure a HTTP Date header for client compatibility.
+ # The method will also ensure an HTTP Date header for client compatibility.
def expires_in(seconds, options = {})
response.cache_control.merge!(
- :max_age => seconds,
- :public => options.delete(:public),
- :must_revalidate => options.delete(:must_revalidate)
+ max_age: seconds,
+ public: options.delete(:public),
+ must_revalidate: options.delete(:must_revalidate)
)
options.delete(:private)
- response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"}
+ response.cache_control[:extras] = options.map { |k,v| "#{k}=#{v}" }
response.date = Time.now unless response.date?
end
- # Sets a HTTP 1.1 Cache-Control header of <tt>no-cache</tt> so no caching should
- # occur by the browser or intermediate caches (like caching proxy servers).
+ # Sets an HTTP 1.1 Cache-Control header of <tt>no-cache</tt>. This means the
+ # resource will be marked as stale, so clients must always revalidate.
+ # Intermediate/browser caches may still store the asset.
def expires_now
- response.cache_control.replace(:no_cache => true)
+ response.cache_control.replace(no_cache: true)
end
# Cache or yield the block. The cache is supposed to never expire.
#
- # You can use this method when you have a HTTP response that never changes,
+ # You can use this method when you have an HTTP response that never changes,
# and the browser and proxies should cache it indefinitely.
#
# * +public+: By default, HTTP responses are private, cached only on the
# user's web browser. To allow proxies to cache the response, set +true+ to
# indicate that they can serve the cached response to all users.
- #
- # * +version+: the version passed as a key for the cache.
- def http_cache_forever(public: false, version: 'v1')
+ def http_cache_forever(public: false)
expires_in 100.years, public: public
- yield if stale?(etag: "#{version}-#{request.fullpath}",
+ yield if stale?(etag: request.fullpath,
last_modified: Time.new(2011, 1, 1).utc,
public: public)
end
private
- def combine_etags(options)
- etags = etaggers.map { |etagger| instance_exec(options, &etagger) }.compact
- etags.unshift options[:etag]
+ def combine_etags(validator, options)
+ [validator, *etaggers.map { |etagger| instance_exec(options, &etagger) }].compact
end
end
end
diff --git a/actionpack/lib/action_controller/metal/cookies.rb b/actionpack/lib/action_controller/metal/cookies.rb
index f8efb2b076..44925641a1 100644
--- a/actionpack/lib/action_controller/metal/cookies.rb
+++ b/actionpack/lib/action_controller/metal/cookies.rb
@@ -3,7 +3,7 @@ module ActionController #:nodoc:
extend ActiveSupport::Concern
included do
- helper_method :cookies
+ helper_method :cookies if defined?(helper_method)
end
private
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 957e7a3019..f089c8423b 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -1,4 +1,4 @@
-require 'action_controller/metal/exceptions'
+require "action_controller/metal/exceptions"
module ActionController #:nodoc:
# Methods for sending arbitrary data and for streaming files to the browser,
@@ -8,8 +8,8 @@ module ActionController #:nodoc:
include ActionController::Rendering
- DEFAULT_SEND_FILE_TYPE = 'application/octet-stream'.freeze #:nodoc:
- DEFAULT_SEND_FILE_DISPOSITION = 'attachment'.freeze #:nodoc:
+ DEFAULT_SEND_FILE_TYPE = "application/octet-stream".freeze #:nodoc:
+ DEFAULT_SEND_FILE_DISPOSITION = "attachment".freeze #:nodoc:
protected
# Sends the file. This uses a server-appropriate method (such as X-Sendfile)
@@ -25,14 +25,13 @@ module ActionController #:nodoc:
# * <tt>:filename</tt> - suggests a filename for the browser to use.
# Defaults to <tt>File.basename(path)</tt>.
# * <tt>:type</tt> - specifies an HTTP content type.
- # You can specify either a string or a symbol for a registered type register with
- # <tt>Mime::Type.register</tt>, for example :json
- # If omitted, type will be guessed from the file extension specified in <tt>:filename</tt>.
- # If no content type is registered for the extension, default type 'application/octet-stream' will be used.
+ # You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example :json.
+ # If omitted, the type will be inferred from the file extension specified in <tt>:filename</tt>.
+ # If no content type is registered for the extension, the default type 'application/octet-stream' will be used.
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
# Valid values are 'inline' and 'attachment' (default).
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
- # * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from
+ # * <tt>:url_based_filename</tt> - set to +true+ if you want the browser to guess the filename from
# the URL, which is necessary for i18n filenames on certain browsers
# (setting <tt>:filename</tt> overrides this option).
#
@@ -65,12 +64,13 @@ module ActionController #:nodoc:
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
# for the Cache-Control header spec.
def send_file(path, options = {}) #:doc:
- raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
+ raise MissingFile, "Cannot read file #{path}" unless File.file?(path) && File.readable?(path)
options[:filename] ||= File.basename(path) unless options[:url_based_filename]
send_file_headers! options
self.status = options[:status] || 200
+ self.content_type = options[:type] if options.key?(:type)
self.content_type = options[:content_type] if options.key?(:content_type)
response.send_file path
end
@@ -79,14 +79,14 @@ module ActionController #:nodoc:
# <tt>render plain: data</tt>, but also allows you to specify whether
# the browser should display the response as a file attachment (i.e. in a
# download dialog) or as inline data. You may also set the content type,
- # the apparent file name, and other things.
+ # the file name, and other things.
#
# Options:
# * <tt>:filename</tt> - suggests a filename for the browser to use.
- # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify
- # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json
- # If omitted, type will be guessed from the file extension specified in <tt>:filename</tt>.
- # If no content type is registered for the extension, default type 'application/octet-stream' will be used.
+ # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'.
+ # You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example :json.
+ # If omitted, type will be inferred from the file extension specified in <tt>:filename</tt>.
+ # If no content type is registered for the extension, the default type 'application/octet-stream' will be used.
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
# Valid values are 'inline' and 'attachment' (default).
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
@@ -123,7 +123,7 @@ module ActionController #:nodoc:
else
if !type_provided && options[:filename]
# If type wasn't provided, try guessing from file extension.
- content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete('.')) || content_type
+ content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete(".")) || content_type
end
self.content_type = content_type
end
@@ -132,10 +132,10 @@ module ActionController #:nodoc:
unless disposition.nil?
disposition = disposition.to_s
disposition += %(; filename="#{options[:filename]}") if options[:filename]
- headers['Content-Disposition'] = disposition
+ headers["Content-Disposition"] = disposition
end
- headers['Content-Transfer-Encoding'] = 'binary'
+ headers["Content-Transfer-Encoding"] = "binary"
response.sending_file = true
diff --git a/actionpack/lib/action_controller/metal/etag_with_flash.rb b/actionpack/lib/action_controller/metal/etag_with_flash.rb
new file mode 100644
index 0000000000..474d75f02e
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/etag_with_flash.rb
@@ -0,0 +1,16 @@
+module ActionController
+ # When you're using the flash, it's generally used as a conditional on the view.
+ # This means the content of the view depends on the flash. Which in turn means
+ # that the etag for a response should be computed with the content of the flash
+ # in mind. This does that by including the content of the flash as a component
+ # in the etag that's generated for a response.
+ module EtagWithFlash
+ extend ActiveSupport::Concern
+
+ include ActionController::ConditionalGet
+
+ included do
+ etag { flash unless flash.empty? }
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb
index 669cf55bca..6c103bb042 100644
--- a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb
+++ b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb
@@ -33,18 +33,24 @@ module ActionController
end
private
- def determine_template_etag(options)
- if template = pick_template_for_etag(options)
- lookup_and_digest_template(template)
+ def determine_template_etag(options)
+ if template = pick_template_for_etag(options)
+ lookup_and_digest_template(template)
+ end
end
- end
- def pick_template_for_etag(options)
- options.fetch(:template) { "#{controller_name}/#{action_name}" }
- end
+ # Pick the template digest to include in the ETag. If the +:template+ option
+ # is present, use the named template. If +:template+ is nil or absent, use
+ # the default controller/action template. If +:template+ is false, omit the
+ # template digest from the ETag.
+ def pick_template_for_etag(options)
+ unless options[:template] == false
+ options[:template] || "#{controller_path}/#{action_name}"
+ end
+ end
- def lookup_and_digest_template(template)
- ActionView::Digestor.digest name: template, finder: lookup_context
- end
+ def lookup_and_digest_template(template)
+ ActionView::Digestor.digest name: template, finder: lookup_context
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb
index 5c0ada37be..3761e6172b 100644
--- a/actionpack/lib/action_controller/metal/exceptions.rb
+++ b/actionpack/lib/action_controller/metal/exceptions.rb
@@ -3,20 +3,10 @@ module ActionController
end
class BadRequest < ActionControllerError #:nodoc:
- def initialize(msg = nil, e = nil)
- if e
- ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
- "Exceptions will automatically capture the original exception.", caller)
- end
-
+ def initialize(msg = nil)
super(msg)
set_backtrace $!.backtrace if $!
end
-
- def original_exception
- ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller)
- cause
- end
end
class RenderError < ActionControllerError #:nodoc:
@@ -35,7 +25,7 @@ module ActionController
class MethodNotAllowed < ActionControllerError #:nodoc:
def initialize(*allowed_methods)
- super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.")
+ super("Only #{allowed_methods.to_sentence(locale: :en)} requests are allowed.")
end
end
@@ -49,7 +39,7 @@ module ActionController
end
class SessionOverflowError < ActionControllerError #:nodoc:
- DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.'
+ DEFAULT_MESSAGE = "Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data."
def initialize(message = nil)
super(message || DEFAULT_MESSAGE)
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index e31d65aac2..b8976497a4 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -1,18 +1,18 @@
-require 'active_support/core_ext/hash/except'
-require 'active_support/core_ext/hash/slice'
+require "active_support/core_ext/hash/except"
+require "active_support/core_ext/hash/slice"
module ActionController
- # This module provides a method which will redirect browser to use HTTPS
+ # This module provides a method which will redirect the browser to use HTTPS
# protocol. This will ensure that user's sensitive information will be
- # transferred safely over the internet. You _should_ always force browser
+ # 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 transferred via HTTPS protocol and prevent
- # user from getting session hijacked when accessing the site under unsecured
- # HTTP protocol.
+ # the user from getting their session hijacked when accessing the site over
+ # unsecured HTTP protocol.
module ForceSSL
extend ActiveSupport::Concern
include AbstractController::Callbacks
@@ -76,10 +76,10 @@ module ActionController
def force_ssl_redirect(host_or_options = nil)
unless request.ssl?
options = {
- :protocol => 'https://',
- :host => request.host,
- :path => request.fullpath,
- :status => :moved_permanently
+ protocol: "https://",
+ host: request.host,
+ path: request.fullpath,
+ status: :moved_permanently
}
if host_or_options.is_a?(Hash)
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index 5e9832fd4e..4dff23dd85 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -18,13 +18,7 @@ module ActionController
# See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list of valid +status+ symbols.
def head(status, options = {})
if status.is_a?(Hash)
- msg = status[:status] ? 'The :status option' : 'The implicit :ok status'
- options, status = status, status.delete(:status)
-
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- #{msg} on `head` has been deprecated and will be removed in Rails 5.1.
- Please pass the status as a separate parameter before the options, instead.
- MSG
+ raise ArgumentError, "#{status.inspect} is not a valid value for `status`."
end
status ||= :ok
@@ -33,7 +27,7 @@ module ActionController
content_type = options.delete(:content_type)
options.each do |key, value|
- headers[key.to_s.dasherize.split('-').each { |v| v[0] = v[0].chr.upcase }.join('-')] = value.to_s
+ headers[key.to_s.dasherize.split("-").each { |v| v[0] = v[0].chr.upcase }.join("-")] = value.to_s
end
self.status = status
@@ -41,7 +35,7 @@ module ActionController
self.response_body = ""
- if include_content?(self.response_code)
+ if include_content?(response_code)
self.content_type = content_type || (Mime[formats.first] if formats)
self.response.charset = false
end
@@ -50,15 +44,15 @@ module ActionController
end
private
- def include_content?(status)
- case status
- when 100..199
- false
- when 204, 205, 304
- false
- else
- true
+ def include_content?(status)
+ case status
+ when 100..199
+ false
+ when 204, 205, 304
+ false
+ else
+ true
+ end
end
- end
end
end
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index d3853e2e83..476d081239 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -5,7 +5,7 @@ module ActionController
#
# In addition to using the standard template helpers provided, creating custom helpers to
# extract complicated logic or reusable functionality is strongly encouraged. By default, each controller
- # will include all helpers. These helpers are only accessible on the controller through <tt>.helpers</tt>
+ # will include all helpers. These helpers are only accessible on the controller through <tt>#helpers</tt>
#
# In previous versions of \Rails the controller will include a helper which
# matches the name of the controller, e.g., <tt>MyController</tt> will automatically
@@ -71,7 +71,7 @@ module ActionController
attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
end
- # Provides a proxy to access helpers methods from outside the view.
+ # Provides a proxy to access helper methods from outside the view.
def helpers
@helper_proxy ||= begin
proxy = ActionView::Base.new
@@ -108,10 +108,15 @@ module ActionController
end
private
- # Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt>
- def all_application_helpers
- all_helpers_from_path(helpers_path)
- end
+ # Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt>
+ def all_application_helpers
+ all_helpers_from_path(helpers_path)
+ end
+ end
+
+ # Provides a proxy to access helper methods from outside the view.
+ def helpers
+ @_helper_proxy ||= view_context
end
end
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 35be6d9300..a335bf109e 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -1,5 +1,5 @@
-require 'base64'
-require 'active_support/security_utils'
+require "base64"
+require "active_support/security_utils"
module ActionController
# Makes it dead easy to do HTTP Basic, Digest and Token authentication.
@@ -99,23 +99,23 @@ module ActionController
end
def has_basic_credentials?(request)
- request.authorization.present? && (auth_scheme(request).downcase == 'basic')
+ request.authorization.present? && (auth_scheme(request).downcase == "basic")
end
def user_name_and_password(request)
- decode_credentials(request).split(':', 2)
+ decode_credentials(request).split(":", 2)
end
def decode_credentials(request)
- ::Base64.decode64(auth_param(request) || '')
+ ::Base64.decode64(auth_param(request) || "")
end
def auth_scheme(request)
- request.authorization.to_s.split(' ', 2).first
+ request.authorization.to_s.split(" ", 2).first
end
def auth_param(request)
- request.authorization.to_s.split(' ', 2).second
+ request.authorization.to_s.split(" ", 2).second
end
def encode_credentials(user_name, password)
@@ -208,7 +208,7 @@ module ActionController
password = password_procedure.call(credentials[:username])
return false unless password
- method = request.get_header('rack.methodoverride.original_method') || request.get_header('REQUEST_METHOD')
+ method = request.get_header("rack.methodoverride.original_method") || request.get_header("REQUEST_METHOD")
uri = credentials[:uri]
[true, false].any? do |trailing_question_mark|
@@ -226,17 +226,17 @@ module ActionController
# of a plain-text password.
def expected_response(http_method, uri, credentials, password, password_is_ha1=true)
ha1 = password_is_ha1 ? password : ha1(credentials, password)
- ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(':'))
- ::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(':'))
+ ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":"))
+ ::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(":"))
end
def ha1(credentials, password)
- ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':'))
+ ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":"))
end
def encode_credentials(http_method, credentials, password, password_is_ha1)
credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1)
- "Digest " + credentials.sort_by {|x| x[0].to_s }.map {|v| "#{v[0]}='#{v[1]}'" }.join(', ')
+ "Digest " + credentials.sort_by { |x| x[0].to_s }.map { |v| "#{v[0]}='#{v[1]}'" }.join(", ")
end
def decode_credentials_header(request)
@@ -244,9 +244,9 @@ module ActionController
end
def decode_credentials(header)
- ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, '').split(',').map do |pair|
- key, value = pair.split('=', 2)
- [key.strip, value.to_s.gsub(/^"|"$/,'').delete('\'')]
+ ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, "").split(",").map do |pair|
+ key, value = pair.split("=", 2)
+ [key.strip, value.to_s.gsub(/^"|"$/,"").delete('\'')]
end]
end
@@ -310,9 +310,9 @@ module ActionController
end
# Might want a shorter timeout depending on whether the request
- # is a PATCH, PUT, or POST, and if client is browser or web service.
+ # is a PATCH, PUT, or POST, and if the client is a browser or web service.
# Can be much shorter if the Stale directive is implemented. This would
- # allow a user to use new nonce without prompting user again for their
+ # allow a user to use new nonce without prompting the user again for their
# username and password.
def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
return false if value.nil?
@@ -324,7 +324,6 @@ module ActionController
def opaque(secret_key)
::Digest::MD5.hexdigest(secret_key)
end
-
end
# Makes it dead easy to do HTTP Token authentication.
@@ -347,7 +346,12 @@ module ActionController
# private
# def authenticate
# authenticate_or_request_with_http_token do |token, options|
- # token == TOKEN
+ # # Compare the tokens in a time-constant manner, to mitigate
+ # # timing attacks.
+ # ActiveSupport::SecurityUtils.secure_compare(
+ # ::Digest::SHA256.hexdigest(token),
+ # ::Digest::SHA256.hexdigest(TOKEN)
+ # )
# end
# end
# end
@@ -401,7 +405,7 @@ module ActionController
#
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
module Token
- TOKEN_KEY = 'token='
+ TOKEN_KEY = "token="
TOKEN_REGEX = /^(Token|Bearer)\s+/
AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
extend self
@@ -471,14 +475,14 @@ module ActionController
# This removes the <tt>"</tt> characters wrapping the value.
def rewrite_param_values(array_params)
- array_params.each { |param| (param[1] || "").gsub! %r/^"|"$/, '' }
+ array_params.each { |param| (param[1] || "").gsub! %r/^"|"$/, "" }
end
# This method takes an authorization body and splits up the key-value
# pairs by the standardized <tt>:</tt>, <tt>;</tt>, or <tt>\t</tt>
# delimiters defined in +AUTHN_PAIR_DELIMITERS+.
def raw_params(auth)
- _raw_params = auth.sub(TOKEN_REGEX, '').split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
+ _raw_params = auth.sub(TOKEN_REGEX, "").split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
if !(_raw_params.first =~ %r{\A#{TOKEN_KEY}})
_raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}"
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index 17fcc2fa02..8615c16c6f 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -1,36 +1,73 @@
+require "active_support/core_ext/string/strip"
+
module ActionController
+ # Handles implicit rendering for a controller action that does not
+ # explicitly respond with +render+, +respond_to+, +redirect+, or +head+.
+ #
+ # For API controllers, the implicit response is always 204 No Content.
+ #
+ # For all other controllers, we use these heuristics to decide whether to
+ # render a template, raise an error for a missing template, or respond with
+ # 204 No Content:
+ #
+ # First, if we DO find a template, it's rendered. Template lookup accounts
+ # for the action name, locales, format, variant, template handlers, and more
+ # (see +render+ for details).
+ #
+ # Second, if we DON'T find a template but the controller action does have
+ # templates for other formats, variants, etc., then we trust that you meant
+ # to provide a template for this response, too, and we raise
+ # <tt>ActionController::UnknownFormat</tt> with an explanation.
+ #
+ # Third, if we DON'T find a template AND the request is a page load in a web
+ # browser (technically, a non-XHR GET request for an HTML response) where
+ # you reasonably expect to have rendered a template, then we raise
+ # <tt>ActionView::UnknownFormat</tt> with an explanation.
+ #
+ # Finally, if we DON'T find a template AND the request isn't a browser page
+ # load, then we implicitly respond with 204 No Content.
module ImplicitRender
-
+ # :stopdoc:
include BasicImplicitRender
- # Renders the template corresponding to the controller action, if it exists.
- # The action name, format, and variant are all taken into account.
- # For example, the "new" action with an HTML format and variant "phone"
- # would try to render the <tt>new.html+phone.erb</tt> template.
- #
- # If no template is found <tt>ActionController::BasicImplicitRender</tt>'s implementation is called, unless
- # a block is passed. In that case, it will override the super implementation.
- #
- # default_render do
- # head 404 # No template was found
- # end
def default_render(*args)
if template_exists?(action_name.to_s, _prefixes, variants: request.variant)
render(*args)
+ elsif any_templates?(action_name.to_s, _prefixes)
+ message = "#{self.class.name}\##{action_name} is missing a template " \
+ "for this request format and variant.\n" \
+ "\nrequest.formats: #{request.formats.map(&:to_s).inspect}" \
+ "\nrequest.variant: #{request.variant.inspect}"
+
+ 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
else
- if block_given?
- yield(*args)
- else
- logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
- super
- end
+ logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
+ super
end
end
def method_for_action(action_name)
super || if template_exists?(action_name.to_s, _prefixes)
- "default_render"
- end
+ "default_render"
+ end
end
+
+ private
+ def interactive_browser_request?
+ request.get? && request.format == Mime[:html] && !request.xhr?
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index bf74b39ac4..2ede96c667 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -1,5 +1,5 @@
-require 'benchmark'
-require 'abstract_controller/logger'
+require "benchmark"
+require "abstract_controller/logger"
module ActionController
# Adds instrumentation to several ends in ActionController::Base. It also provides
@@ -16,12 +16,13 @@ module ActionController
def process_action(*args)
raw_payload = {
- :controller => self.class.name,
- :action => self.action_name,
- :params => request.filtered_parameters,
- :format => request.format.ref,
- :method => request.request_method,
- :path => request.fullpath
+ controller: self.class.name,
+ action: action_name,
+ params: request.filtered_parameters,
+ headers: request.headers,
+ format: request.format.ref,
+ method: request.request_method,
+ path: request.fullpath
}
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
@@ -47,7 +48,7 @@ module ActionController
def send_file(path, options={})
ActiveSupport::Notifications.instrument("send_file.action_controller",
- options.merge(:path => path)) do
+ options.merge(path: path)) do
super
end
end
@@ -71,11 +72,11 @@ module ActionController
# A hook invoked every time a before callback is halted.
def halted_callback_hook(filter)
- ActiveSupport::Notifications.instrument("halted_callback.action_controller", :filter => filter)
+ ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter)
end
- # A hook which allows you to clean up any time taken into account in
- # views wrongly, like database querying time.
+ # A hook which allows you to clean up any time, wrongly taken into account in
+ # views, like database querying time.
#
# def cleanup_view_runtime
# super - time_taken_in_something_expensive
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index fc20e7a421..26a16104db 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -1,9 +1,9 @@
-require 'action_dispatch/http/response'
-require 'delegate'
-require 'active_support/json'
+require "action_dispatch/http/response"
+require "delegate"
+require "active_support/json"
module ActionController
- # Mix this module in to your controller, and all actions in that controller
+ # Mix this module into your controller, and all actions in that controller
# will be able to stream data to the client as it's written.
#
# class MyController < ActionController::Base
@@ -20,7 +20,7 @@ module ActionController
# end
# end
#
- # There are a few caveats with this use. You *cannot* write headers after the
+ # There are a few caveats with this module. You *cannot* write headers after the
# response has been committed (Response#committed? will return truthy).
# Calling +write+ or +close+ on the response stream will cause the response
# object to be committed. Make sure all headers are set before calling write
@@ -84,7 +84,6 @@ module ActionController
# Note: SSEs are not currently supported by IE. However, they are supported
# by Chrome, Firefox, Opera, and Safari.
class SSE
-
WHITELISTED_OPTIONS = %w( retry event id )
def initialize(stream, options = {})
@@ -163,14 +162,6 @@ module ActionController
end
end
- def each
- @response.sending!
- while str = @buf.pop
- yield str
- end
- @response.sent!
- end
-
# Write a 'close' event to the buffer; the producer/writing thread
# uses this to notify us that it's finished supplying content.
#
@@ -210,23 +201,36 @@ module ActionController
def call_on_error
@error_callback.call
end
+
+ private
+
+ def each_chunk(&block)
+ loop do
+ str = nil
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
+ str = @buf.pop
+ end
+ break unless str
+ yield str
+ end
+ end
end
class Response < ActionDispatch::Response #:nodoc: all
private
- def before_committed
- super
- jar = request.cookie_jar
- # The response can be committed multiple times
- jar.write self unless committed?
- end
+ def before_committed
+ super
+ jar = request.cookie_jar
+ # The response can be committed multiple times
+ jar.write self unless committed?
+ end
- def build_buffer(response, body)
- buf = Live::Buffer.new response
- body.each { |part| buf.write part }
- buf
- end
+ def build_buffer(response, body)
+ buf = Live::Buffer.new response
+ body.each { |part| buf.write part }
+ buf
+ end
end
def process(name)
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 173a14a1d2..f6aabcb102 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -1,4 +1,4 @@
-require 'abstract_controller/collector'
+require "abstract_controller/collector"
module ActionController #:nodoc:
module MimeResponds
@@ -198,7 +198,7 @@ module ActionController #:nodoc:
_process_format(format)
_set_rendered_content_type format
response = collector.response
- response ? response.call : render({})
+ response.call if response
else
raise ActionController::UnknownFormat
end
@@ -280,8 +280,8 @@ module ActionController #:nodoc:
def any(*args, &block)
if block_given?
- if args.any? && args.none?{ |a| a == @variant }
- args.each{ |v| @variants[v] = block }
+ if args.any? && args.none? { |a| a == @variant }
+ args.each { |v| @variants[v] = block }
else
@variants[:any] = block
end
diff --git a/actionpack/lib/action_controller/metal/parameter_encoding.rb b/actionpack/lib/action_controller/metal/parameter_encoding.rb
new file mode 100644
index 0000000000..c457fd0d06
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/parameter_encoding.rb
@@ -0,0 +1,30 @@
+module ActionController
+ # Allows encoding to be specified per parameter per action.
+ module ParameterEncoding
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def inherited(klass) # :nodoc:
+ super
+ klass.setup_param_encode
+ end
+
+ def setup_param_encode # :nodoc:
+ @_parameter_encodings = {}
+ end
+
+ def encoding_for_param(action, param) # :nodoc:
+ if @_parameter_encodings[action.to_s] && @_parameter_encodings[action.to_s][param.to_s]
+ @_parameter_encodings[action.to_s][param.to_s]
+ else
+ super
+ end
+ end
+
+ def parameter_encoding(action, param_name, encoding)
+ @_parameter_encodings[action.to_s] ||= {}
+ @_parameter_encodings[action.to_s][param_name.to_s] = encoding
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index c38fc40b81..9d1b740025 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -1,7 +1,7 @@
-require 'active_support/core_ext/hash/slice'
-require 'active_support/core_ext/hash/except'
-require 'active_support/core_ext/module/anonymous'
-require 'action_dispatch/http/mime_type'
+require "active_support/core_ext/hash/slice"
+require "active_support/core_ext/hash/except"
+require "active_support/core_ext/module/anonymous"
+require "action_dispatch/http/mime_type"
module ActionController
# Wraps the parameters hash into a nested hash. This will allow clients to
@@ -71,7 +71,7 @@ module ActionController
EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8)
- require 'mutex_m'
+ require "mutex_m"
class Options < Struct.new(:name, :format, :include, :exclude, :klass, :model) # :nodoc:
include Mutex_m
@@ -128,30 +128,30 @@ module ActionController
end
private
- # Determine the wrapper model from the controller's name. By convention,
- # this could be done by trying to find the defined model that has the
- # same singular name as the controller. For example, +UsersController+
- # will try to find if the +User+ model exists.
- #
- # This method also does namespace lookup. Foo::Bar::UsersController will
- # try to find Foo::Bar::User, Foo::User and finally User.
- def _default_wrap_model #:nodoc:
- return nil if klass.anonymous?
- model_name = klass.name.sub(/Controller$/, '').classify
-
- begin
- if model_klass = model_name.safe_constantize
- model_klass
- else
- namespaces = model_name.split("::")
- namespaces.delete_at(-2)
- break if namespaces.last == model_name
- model_name = namespaces.join("::")
- end
- end until model_klass
+ # Determine the wrapper model from the controller's name. By convention,
+ # this could be done by trying to find the defined model that has the
+ # same singular name as the controller. For example, +UsersController+
+ # will try to find if the +User+ model exists.
+ #
+ # This method also does namespace lookup. Foo::Bar::UsersController will
+ # try to find Foo::Bar::User, Foo::User and finally User.
+ def _default_wrap_model #:nodoc:
+ return nil if klass.anonymous?
+ model_name = klass.name.sub(/Controller$/, "").classify
+
+ begin
+ if model_klass = model_name.safe_constantize
+ model_klass
+ else
+ namespaces = model_name.split("::")
+ namespaces.delete_at(-2)
+ break if namespaces.last == model_name
+ model_name = namespaces.join("::")
+ end
+ end until model_klass
- model_klass
- end
+ model_klass
+ end
end
included do
@@ -198,9 +198,9 @@ module ActionController
when Hash
options = name_or_model_or_options
when false
- options = options.merge(:format => [])
+ options = options.merge(format: [])
when Symbol, String
- options = options.merge(:name => name_or_model_or_options)
+ options = options.merge(name: name_or_model_or_options)
else
model = name_or_model_or_options
end
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index b13ba06962..30798c1d99 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -1,12 +1,4 @@
module ActionController
- class RedirectBackError < AbstractController::Error #:nodoc:
- DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].'
-
- def initialize(message = nil)
- super(message || DEFAULT_MESSAGE)
- end
- end
-
module Redirecting
extend ActiveSupport::Concern
@@ -24,10 +16,10 @@ module ActionController
# === Examples:
#
# redirect_to action: "show", id: 5
- # redirect_to post
+ # redirect_to @post
# redirect_to "http://www.rubyonrails.org"
# redirect_to "/images/screenshot.jpg"
- # redirect_to articles_url
+ # redirect_to posts_url
# redirect_to proc { edit_post_url(@post) }
#
# The redirection happens as a "302 Found" header unless otherwise specified using the <tt>:status</tt> option:
@@ -77,14 +69,14 @@ module ActionController
# is missing this header, the <tt>fallback_location</tt> will be used.
#
# redirect_back fallback_location: { action: "show", id: 5 }
- # redirect_back fallback_location: post
+ # redirect_back fallback_location: @post
# redirect_back fallback_location: "http://www.rubyonrails.org"
- # redirect_back fallback_location: "/images/screenshot.jpg"
- # redirect_back fallback_location: articles_url
- # redirect_back fallback_location: proc { edit_post_url(@post) }
+ # redirect_back fallback_location: "/images/screenshot.jpg"
+ # redirect_back fallback_location: posts_url
+ # redirect_back fallback_location: proc { edit_post_url(@post) }
#
# All options that can be passed to <tt>redirect_to</tt> are accepted as
- # options and the behavior is indetical.
+ # options and the behavior is identical.
def redirect_back(fallback_location:, **args)
if referer = request.headers["Referer"]
redirect_to referer, **args
@@ -104,14 +96,6 @@ module ActionController
options
when String
request.protocol + request.host_with_port + options
- when :back
- ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
- `redirect_to :back` is deprecated and will be removed from Rails 5.1.
- Please use `redirect_back(fallback_location: fallback_location)` where
- `fallback_location` represents the location to use if the request has
- no HTTP referer information.
- MESSAGE
- request.headers["Referer"] or raise RedirectBackError
when Proc
_compute_redirect_to_location request, options.call
else
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 90fb34e386..f8a037189c 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -1,4 +1,4 @@
-require 'set'
+require "set"
module ActionController
# See <tt>Renderers.add</tt>
@@ -71,8 +71,6 @@ module ActionController
# format.csv { render csv: @csvable, filename: @csvable.name }
# end
# end
- # To use renderers and their mime types in more concise ways, see
- # <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt>
def self.add(key, &block)
define_method(_render_with_renderer_method_name(key), &block)
RENDERERS << key.to_sym
@@ -94,7 +92,6 @@ module ActionController
end
module ClassMethods
-
# Adds, by name, a renderer or renderers to the +_renderers+ available
# to call within controller actions.
#
@@ -103,7 +100,7 @@ module ActionController
#
# Both <tt>ActionController::Base</tt> and <tt>ActionController::API</tt>
# include <tt>ActionController::Renderers::All</tt>, making all renderers
- # avaialable in the controller. See <tt>Renderers::RENDERERS</tt> and <tt>Renderers.add</tt>.
+ # available in the controller. See <tt>Renderers::RENDERERS</tt> and <tt>Renderers.add</tt>.
#
# Since <tt>ActionController::Metal</tt> controllers cannot render, the controller
# must include <tt>AbstractController::Rendering</tt>, <tt>ActionController::Rendering</tt>,
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index cce6fe7787..67365a143e 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -1,10 +1,10 @@
-require 'active_support/core_ext/string/filters'
+require "active_support/core_ext/string/filters"
module ActionController
module Rendering
extend ActiveSupport::Concern
- RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html]
+ RENDER_FORMATS_IN_PRIORITY = [:body, :plain, :html]
module ClassMethods
# Documentation at ActionController::Renderer#render
@@ -32,7 +32,7 @@ module ActionController
# Check for double render errors and set the content_type after rendering.
def render(*args) #:nodoc:
- raise ::AbstractController::DoubleRenderError if self.response_body
+ raise ::AbstractController::DoubleRenderError if response_body
super
end
@@ -49,84 +49,68 @@ module ActionController
end
def render_to_body(options = {})
- super || _render_in_priorities(options) || ' '
+ super || _render_in_priorities(options) || " "
end
private
- def _render_in_priorities(options)
- RENDER_FORMATS_IN_PRIORITY.each do |format|
- return options[format] if options.key?(format)
- end
-
- nil
- end
-
- def _set_html_content_type
- self.content_type = Mime[:html].to_s
- end
+ def _render_in_priorities(options)
+ RENDER_FORMATS_IN_PRIORITY.each do |format|
+ return options[format] if options.key?(format)
+ end
- def _set_rendered_content_type(format)
- unless response.content_type
- self.content_type = format.to_s
+ nil
end
- end
-
- # Normalize arguments by catching blocks and setting them on :update.
- def _normalize_args(action=nil, options={}, &blk) #:nodoc:
- options = super
- options[:update] = blk if block_given?
- options
- end
- # Normalize both text and status options.
- def _normalize_options(options) #:nodoc:
- _normalize_text(options)
-
- if options[:text]
- ActiveSupport::Deprecation.warn <<-WARNING.squish
- `render :text` is deprecated because it does not actually render a
- `text/plain` response. Switch to `render plain: 'plain text'` to
- render as `text/plain`, `render html: '<strong>HTML</strong>'` to
- render as `text/html`, or `render body: 'raw'` to match the deprecated
- behavior and render with the default Content-Type, which is
- `text/plain`.
- WARNING
+ def _set_html_content_type
+ self.content_type = Mime[:html].to_s
end
- if options[:html]
- options[:html] = ERB::Util.html_escape(options[:html])
+ def _set_rendered_content_type(format)
+ unless response.content_type
+ self.content_type = format.to_s
+ end
end
- if options.delete(:nothing)
- ActiveSupport::Deprecation.warn("`:nothing` option is deprecated and will be removed in Rails 5.1. Use `head` method to respond with empty response body.")
- options[:body] = nil
+ # Normalize arguments by catching blocks and setting them on :update.
+ def _normalize_args(action=nil, options={}, &blk) #:nodoc:
+ options = super
+ options[:update] = blk if block_given?
+ options
end
- if options[:status]
- options[:status] = Rack::Utils.status_code(options[:status])
- end
+ # Normalize both text and status options.
+ def _normalize_options(options) #:nodoc:
+ _normalize_text(options)
- super
- end
+ if options[:html]
+ options[:html] = ERB::Util.html_escape(options[:html])
+ end
- def _normalize_text(options)
- RENDER_FORMATS_IN_PRIORITY.each do |format|
- if options.key?(format) && options[format].respond_to?(:to_text)
- options[format] = options[format].to_text
+ if options[:status]
+ options[:status] = Rack::Utils.status_code(options[:status])
+ end
+
+ super
+ end
+
+ def _normalize_text(options)
+ RENDER_FORMATS_IN_PRIORITY.each do |format|
+ if options.key?(format) && options[format].respond_to?(:to_text)
+ options[format] = options[format].to_text
+ end
end
end
- end
- # Process controller specific options, as status, content-type and location.
- def _process_options(options) #:nodoc:
- status, content_type, location = options.values_at(:status, :content_type, :location)
+ # Process controller specific options, as status, content-type and location.
+ def _process_options(options) #:nodoc:
+ status, content_type, location = options.values_at(:status, :content_type, :location)
- self.status = status if status
- self.content_type = content_type if content_type
- self.headers["Location"] = url_for(location) if location
+ self.status = status if status
+ self.content_type = content_type if content_type
+ self.headers["Location"] = url_for(location) if location
- super
- end
+ super
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 6586985ff5..3d3c121280 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -1,6 +1,6 @@
-require 'rack/session/abstract/id'
-require 'action_controller/metal/exceptions'
-require 'active_support/security_utils'
+require "rack/session/abstract/id"
+require "action_controller/metal/exceptions"
+require "active_support/security_utils"
module ActionController #:nodoc:
class InvalidAuthenticityToken < ActionControllerError #:nodoc:
@@ -109,10 +109,10 @@ module ActionController #:nodoc:
# * <tt>:only/:except</tt> - Only apply forgery protection to a subset of actions. For example <tt>only: [ :create, :create_all ]</tt>.
# * <tt>:if/:unless</tt> - Turn off the forgery protection entirely depending on the passed Proc or method reference.
# * <tt>:prepend</tt> - By default, the verification of the authentication token will be added at the position of the
- # protect_from_forgery call in your application. This means any callbacks added before are run first. This is useful
- # when you want your forgery protection to depend on other callbacks, like authentication methods (Oauth vs Cookie auth).
+ # protect_from_forgery call in your application. This means any callbacks added before are run first. This is useful
+ # when you want your forgery protection to depend on other callbacks, like authentication methods (Oauth vs Cookie auth).
#
- # If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>.
+ # If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>.
# * <tt>:with</tt> - Set the method to handle unverified request.
#
# Valid unverified request handling methods are:
@@ -130,11 +130,11 @@ module ActionController #:nodoc:
private
- def protection_method_class(name)
- ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify)
- rescue NameError
- raise ArgumentError, 'Invalid request forgery protection method, use :null_session, :exception, or :reset_session'
- end
+ def protection_method_class(name)
+ ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify)
+ rescue NameError
+ raise ArgumentError, "Invalid request forgery protection method, use :null_session, :exception, or :reset_session"
+ end
end
module ProtectionMethods
@@ -154,26 +154,26 @@ module ActionController #:nodoc:
protected
- class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
- def initialize(req)
- super(nil, req)
- @data = {}
- @loaded = true
- end
+ class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
+ def initialize(req)
+ super(nil, req)
+ @data = {}
+ @loaded = true
+ end
- # no-op
- def destroy; end
+ # no-op
+ def destroy; end
- def exists?
- true
+ def exists?
+ true
+ end
end
- end
- class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
- def write(*)
- # nothing
+ class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
+ def write(*)
+ # nothing
+ end
end
- end
end
class ResetSession
@@ -213,7 +213,7 @@ module ActionController #:nodoc:
if !verified_request?
if logger && log_warning_on_csrf_failure
- logger.warn "Can't verify CSRF token authenticity"
+ logger.warn "Can't verify CSRF token authenticity."
end
handle_unverified_request
end
@@ -235,7 +235,9 @@ module ActionController #:nodoc:
# we aren't serving an unauthorized cross-origin response.
def verify_same_origin_request
if marked_for_same_origin_verification? && non_xhr_javascript_response?
- logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING if logger
+ if logger && log_warning_on_csrf_failure
+ logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING
+ end
raise ActionController::InvalidCrossOriginRequest, CROSS_ORIGIN_JAVASCRIPT_WARNING
end
end
@@ -379,7 +381,8 @@ module ActionController #:nodoc:
def xor_byte_strings(s1, s2)
s2_bytes = s2.bytes
- s1.bytes.map.with_index { |c1, i| c1 ^ s2_bytes[i] }.pack('c*')
+ s1.each_byte.with_index { |c1, i| s2_bytes[i] ^= c1 }
+ s2_bytes.pack("C*")
end
# The form's authenticity parameter. Override to provide your own.
@@ -404,7 +407,8 @@ module ActionController #:nodoc:
end
def normalize_action_path(action_path)
- action_path.split('?').first.to_s.chomp('/')
+ uri = URI.parse(action_path)
+ uri.path.chomp("/")
end
end
end
diff --git a/actionpack/lib/action_controller/metal/rescue.rb b/actionpack/lib/action_controller/metal/rescue.rb
index 81b9a7b9ed..2d99e4045b 100644
--- a/actionpack/lib/action_controller/metal/rescue.rb
+++ b/actionpack/lib/action_controller/metal/rescue.rb
@@ -1,18 +1,11 @@
module ActionController #:nodoc:
- # This module is responsible to provide `rescue_from` helpers
- # to controllers and configure when detailed exceptions must be
+ # This module is responsible for providing `rescue_from` helpers
+ # to controllers and configuring when detailed exceptions must be
# shown.
module Rescue
extend ActiveSupport::Concern
include ActiveSupport::Rescuable
- def rescue_with_handler(exception)
- if exception.cause && handler_for_rescue(exception.cause)
- exception = exception.cause
- end
- super(exception)
- end
-
# Override this method if you want to customize when detailed
# exceptions must be shown. This method is only called when
# consider_all_requests_local is false. By default, it returns
@@ -26,8 +19,8 @@ module ActionController #:nodoc:
def process_action(*args)
super
rescue Exception => exception
- request.env['action_dispatch.show_detailed_exceptions'] ||= show_detailed_exceptions?
- rescue_with_handler(exception) || raise(exception)
+ request.env["action_dispatch.show_detailed_exceptions"] ||= show_detailed_exceptions?
+ rescue_with_handler(exception) || raise
end
end
end
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index a6115674aa..481f19f1ef 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -1,4 +1,4 @@
-require 'rack/chunked'
+require "rack/chunked"
module ActionController #:nodoc:
# Allows views to be streamed back to the client as they are rendered.
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index d3382ef296..b64617d084 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -1,12 +1,14 @@
-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/rescuable'
-require 'action_dispatch/http/upload'
-require 'rack/test'
-require 'stringio'
-require 'set'
+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"
+require "active_support/rescuable"
+require "action_dispatch/http/upload"
+require "rack/test"
+require "stringio"
+require "set"
+require "yaml"
module ActionController
# Raised when a required parameter is missing.
@@ -31,19 +33,19 @@ module ActionController
#
# params = ActionController::Parameters.new(a: "123", b: "456")
# params.permit(:c)
- # # => ActionController::UnpermittedParameters: found unpermitted parameters: a, b
+ # # => ActionController::UnpermittedParameters: found unpermitted parameters: :a, :b
class UnpermittedParameters < IndexError
attr_reader :params # :nodoc:
def initialize(params) # :nodoc:
@params = params
- super("found unpermitted parameter#{'s' if params.size > 1 }: #{params.join(", ")}")
+ super("found unpermitted parameter#{'s' if params.size > 1 }: #{params.map { |e| ":#{e}" }.join(", ")}")
end
end
# == Action Controller \Parameters
#
- # Allows to choose which attributes should be whitelisted for mass updating
+ # Allows you to choose which attributes should be whitelisted for mass updating
# and thus prevent accidentally exposing that which shouldn't be exposed.
# Provides two methods for this purpose: #require and #permit. The former is
# used to mark parameters as required. The latter is used to set the parameter
@@ -58,8 +60,7 @@ module ActionController
# })
#
# permitted = params.require(:person).permit(:name, :age)
- # permitted # => {"name"=>"Francesco", "age"=>22}
- # permitted.class # => ActionController::Parameters
+ # permitted # => <ActionController::Parameters {"name"=>"Francesco", "age"=>22} permitted: true>
# permitted.permitted? # => true
#
# Person.first.update!(permitted)
@@ -87,7 +88,7 @@ module ActionController
#
# params = ActionController::Parameters.new(a: "123", b: "456")
# params.permit(:c)
- # # => {}
+ # # => <ActionController::Parameters {} permitted: true>
#
# ActionController::Parameters.action_on_unpermitted_parameters = :raise
#
@@ -107,9 +108,11 @@ module ActionController
# params["key"] # => "value"
class Parameters
cattr_accessor :permit_all_parameters, instance_accessor: false
+ self.permit_all_parameters = false
+
cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
- delegate :keys, :key?, :has_key?, :values, :has_value?, :value?, :empty?, :include?, :inspect,
+ delegate :keys, :key?, :has_key?, :values, :has_value?, :value?, :empty?, :include?,
:as_json, to: :@parameters
# By default, never raise an UnpermittedParameters exception if these
@@ -122,16 +125,6 @@ module ActionController
cattr_accessor :always_permitted_parameters
self.always_permitted_parameters = %w( controller action )
- def self.const_missing(const_name)
- return super unless const_name == :NEVER_UNPERMITTED_PARAMS
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `ActionController::Parameters::NEVER_UNPERMITTED_PARAMS` has been deprecated.
- Use `ActionController::Parameters.always_permitted_parameters` instead.
- MSG
-
- always_permitted_parameters
- end
-
# Returns a new instance of <tt>ActionController::Parameters</tt>.
# Also, sets the +permitted+ attribute to the default value of
# <tt>ActionController::Parameters.permit_all_parameters</tt>.
@@ -154,17 +147,12 @@ module ActionController
end
# Returns true if another +Parameters+ object contains the same content and
- # permitted flag, or other Hash-like object contains the same content. This
- # override is in place so you can perform a comparison with `Hash`.
- def ==(other_hash)
- if other_hash.respond_to?(:permitted?)
- super
+ # permitted flag.
+ def ==(other)
+ if other.respond_to?(:permitted?)
+ self.permitted? == other.permitted? && self.parameters == other.parameters
else
- if other_hash.is_a?(Hash)
- @parameters == other_hash.with_indifferent_access
- else
- @parameters == other_hash
- end
+ @parameters == other
end
end
@@ -190,12 +178,19 @@ module ActionController
# Returns an unsafe, unfiltered
# <tt>ActiveSupport::HashWithIndifferentAccess</tt> representation of this
# parameter.
+ #
+ # params = ActionController::Parameters.new({
+ # name: 'Senjougahara Hitagi',
+ # oddity: 'Heavy stone crab'
+ # })
+ # params.to_unsafe_h
+ # # => {"name"=>"Senjougahara Hitagi", "oddity" => "Heavy stone crab"}
def to_unsafe_h
convert_parameters_to_hashes(@parameters, :to_unsafe_h)
end
alias_method :to_unsafe_hash, :to_unsafe_h
- # Convert all hashes in values into parameters, then yield each pair like
+ # Convert all hashes in values into parameters, then yield each pair in
# the same way as <tt>Hash#each_pair</tt>
def each_pair(&block)
@parameters.each_pair do |key, value|
@@ -254,7 +249,7 @@ module ActionController
# either present or the singleton +false+, returns said value:
#
# ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person)
- # # => {"name"=>"Francesco"}
+ # # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false>
#
# Otherwise raises <tt>ActionController::ParameterMissing</tt>:
#
@@ -275,12 +270,12 @@ module ActionController
# returned:
#
# params = ActionController::Parameters.new(user: { ... }, profile: { ... })
- # user_params, profile_params = params.require(:user, :profile)
+ # user_params, profile_params = params.require([:user, :profile])
#
- # Otherwise, the method reraises the first exception found:
+ # Otherwise, the method re-raises the first exception found:
#
# params = ActionController::Parameters.new(user: {}, profile: {})
- # user_params, profile_params = params.require(:user, :profile)
+ # user_params, profile_params = params.require([:user, :profile])
# # ActionController::ParameterMissing: param is missing or the value is empty: user
#
# Technically this method can be used to fetch terminal values:
@@ -373,13 +368,13 @@ module ActionController
# })
#
# params.require(:person).permit(:contact)
- # # => {}
+ # # => <ActionController::Parameters {} permitted: true>
#
# params.require(:person).permit(contact: :phone)
- # # => {"contact"=>{"phone"=>"555-1234"}}
+ # # => <ActionController::Parameters {"contact"=><ActionController::Parameters {"phone"=>"555-1234"} permitted: true>} permitted: true>
#
# params.require(:person).permit(contact: [ :email, :phone ])
- # # => {"contact"=>{"email"=>"none@test.com", "phone"=>"555-1234"}}
+ # # => <ActionController::Parameters {"contact"=><ActionController::Parameters {"email"=>"none@test.com", "phone"=>"555-1234"} permitted: true>} permitted: true>
def permit(*filters)
params = self.class.new
@@ -401,7 +396,7 @@ module ActionController
# returns +nil+.
#
# params = ActionController::Parameters.new(person: { name: 'Francesco' })
- # params[:person] # => {"name"=>"Francesco"}
+ # params[:person] # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false>
# params[:none] # => nil
def [](key)
convert_hashes_to_parameters(key, @parameters[key])
@@ -420,7 +415,7 @@ module ActionController
# is given, then that will be run and its result returned.
#
# params = ActionController::Parameters.new(person: { name: 'Francesco' })
- # params.fetch(: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, 'Francesco') # => "Francesco"
# params.fetch(:none) { 'Francesco' } # => "Francesco"
@@ -436,13 +431,28 @@ 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
+ end
+
# Returns a new <tt>ActionController::Parameters</tt> instance that
# includes only the given +keys+. If the given +keys+
# don't exist, returns an empty hash.
#
# params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
- # params.slice(:a, :b) # => {"a"=>1, "b"=>2}
- # params.slice(:d) # => {}
+ # params.slice(:a, :b) # => <ActionController::Parameters {"a"=>1, "b"=>2} permitted: false>
+ # params.slice(:d) # => <ActionController::Parameters {} permitted: false>
def slice(*keys)
new_instance_with_inherited_permitted_status(@parameters.slice(*keys))
end
@@ -458,8 +468,8 @@ module ActionController
# filters out the given +keys+.
#
# params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
- # params.except(:a, :b) # => {"c"=>3}
- # params.except(:d) # => {"a"=>1,"b"=>2,"c"=>3}
+ # params.except(:a, :b) # => <ActionController::Parameters {"c"=>3} permitted: false>
+ # params.except(:d) # => <ActionController::Parameters {"a"=>1, "b"=>2, "c"=>3} permitted: false>
def except(*keys)
new_instance_with_inherited_permitted_status(@parameters.except(*keys))
end
@@ -467,8 +477,8 @@ module ActionController
# Removes and returns the key/value pairs matching the given keys.
#
# params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
- # params.extract!(:a, :b) # => {"a"=>1, "b"=>2}
- # params # => {"c"=>3}
+ # params.extract!(:a, :b) # => <ActionController::Parameters {"a"=>1, "b"=>2} permitted: false>
+ # params # => <ActionController::Parameters {"c"=>3} permitted: false>
def extract!(*keys)
new_instance_with_inherited_permitted_status(@parameters.extract!(*keys))
end
@@ -478,7 +488,7 @@ module ActionController
#
# params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
# params.transform_values { |x| x * 2 }
- # # => {"a"=>2, "b"=>4, "c"=>6}
+ # # => <ActionController::Parameters {"a"=>2, "b"=>4, "c"=>6} permitted: false>
def transform_values(&block)
if block
new_instance_with_inherited_permitted_status(
@@ -555,25 +565,11 @@ module ActionController
convert_value_to_parameters(@parameters.values_at(*keys))
end
- # Returns an exact copy of the <tt>ActionController::Parameters</tt>
- # instance. +permitted+ state is kept on the duped object.
- #
- # params = ActionController::Parameters.new(a: 1)
- # params.permit!
- # params.permitted? # => true
- # copy_params = params.dup # => {"a"=>1}
- # copy_params.permitted? # => true
- def dup
- super.tap do |duplicate|
- duplicate.permitted = @permitted
- end
- end
-
# Returns a new <tt>ActionController::Parameters</tt> with all keys from
# +other_hash+ merges into current hash.
def merge(other_hash)
new_instance_with_inherited_permitted_status(
- @parameters.merge(other_hash)
+ @parameters.merge(other_hash.to_h)
)
end
@@ -584,31 +580,55 @@ module ActionController
dup
end
- def method_missing(method_sym, *args, &block)
- if @parameters.respond_to?(method_sym)
- message = <<-DEPRECATE.squish
- Method #{method_sym} is deprecated and will be removed in Rails 5.1,
- as `ActionController::Parameters` no longer inherits from
- hash. Using this deprecated behavior exposes potential security
- problems. If you continue to use this method you may be creating
- a security vulnerability in your app that can be exploited. Instead,
- consider using one of these documented methods which are not
- deprecated: http://api.rubyonrails.org/v#{ActionPack.version}/classes/ActionController/Parameters.html
- DEPRECATE
- ActiveSupport::Deprecation.warn(message)
- @parameters.public_send(method_sym, *args, &block)
- else
- super
+ def inspect
+ "<#{self.class} #{@parameters} permitted: #{@permitted}>"
+ end
+
+ def self.hook_into_yaml_loading # :nodoc:
+ # Wire up YAML format compatibility with Rails 4.2 and Psych 2.0.8 and 2.0.9+.
+ # Makes the YAML parser call `init_with` when it encounters the keys below
+ # instead of trying its own parsing routines.
+ YAML.load_tags["!ruby/hash-with-ivars:ActionController::Parameters"] = name
+ YAML.load_tags["!ruby/hash:ActionController::Parameters"] = name
+ end
+ hook_into_yaml_loading
+
+ def init_with(coder) # :nodoc:
+ case coder.tag
+ when "!ruby/hash:ActionController::Parameters"
+ # YAML 2.0.8's format where hash instance variables weren't stored.
+ @parameters = coder.map.with_indifferent_access
+ @permitted = false
+ when "!ruby/hash-with-ivars:ActionController::Parameters"
+ # YAML 2.0.9's Hash subclass format where keys and values
+ # were stored under an elements hash and `permitted` within an ivars hash.
+ @parameters = coder.map["elements"].with_indifferent_access
+ @permitted = coder.map["ivars"][:@permitted]
+ when "!ruby/object:ActionController::Parameters"
+ # YAML's Object format. Only needed because of the format
+ # backwardscompability above, otherwise equivalent to YAML's initialization.
+ @parameters, @permitted = coder.map["parameters"], coder.map["permitted"]
+ end
+ end
+
+ undef_method :to_param
+
+ # Returns duplicate of object including all parameters
+ def deep_dup
+ self.class.new(@parameters.deep_dup).tap do |duplicate|
+ duplicate.permitted = @permitted
end
end
protected
+ attr_reader :parameters
+
def permitted=(new_permitted)
@permitted = new_permitted
end
def fields_for_style?
- @parameters.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
+ @parameters.all? { |k, v| k =~ /\A-?\d+\z/ && (v.is_a?(Hash) || v.is_a?(Parameters)) }
end
private
@@ -682,7 +702,7 @@ module ActionController
end
def unpermitted_keys(params)
- self.keys - params.keys - self.always_permitted_parameters
+ keys - params.keys - always_permitted_parameters
end
#
@@ -713,7 +733,7 @@ module ActionController
]
def permitted_scalar?(value)
- PERMITTED_SCALAR_TYPES.any? {|type| value.is_a?(type)}
+ PERMITTED_SCALAR_TYPES.any? { |type| value.is_a?(type) }
end
def permitted_scalar_filter(params, key)
@@ -729,11 +749,15 @@ module ActionController
end
def array_of_permitted_scalars?(value)
- if value.is_a?(Array) && value.all? {|element| permitted_scalar?(element)}
+ if value.is_a?(Array) && value.all? { |element| permitted_scalar?(element) }
yield value
end
end
+ def non_scalar?(value)
+ value.is_a?(Array) || value.is_a?(Parameters)
+ end
+
EMPTY_ARRAY = []
def hash_filter(params, filter)
filter = filter.with_indifferent_access
@@ -748,7 +772,7 @@ module ActionController
array_of_permitted_scalars?(self[key]) do |val|
params[key] = val
end
- else
+ elsif non_scalar?(value)
# Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
params[key] = each_element(value) do |element|
element.permit(*Array.wrap(filter[key]))
@@ -756,6 +780,11 @@ module ActionController
end
end
end
+
+ def initialize_copy(source)
+ super
+ @parameters = @parameters.dup
+ end
end
# == Strong \Parameters
@@ -771,7 +800,7 @@ module ActionController
#
# class PeopleController < ActionController::Base
# # Using "Person.create(params[:person])" would raise an
- # # ActiveModel::ForbiddenAttributes exception because it'd
+ # # ActiveModel::ForbiddenAttributesError exception because it'd
# # be using mass assignment without an explicit permit step.
# # This is the recommended form:
# def create
@@ -799,7 +828,8 @@ module ActionController
# end
#
# In order to use <tt>accepts_nested_attributes_for</tt> with Strong \Parameters, you
- # will need to specify which nested attributes should be whitelisted.
+ # will need to specify which nested attributes should be whitelisted. You might want
+ # to allow +:id+ and +:_destroy+, see ActiveRecord::NestedAttributes for more information.
#
# class Person
# has_many :pets
@@ -819,7 +849,7 @@ module ActionController
# # It's mandatory to specify the nested attributes that should be whitelisted.
# # If you use `permit` with just the key that points to the nested attributes hash,
# # it will return an empty hash.
- # params.require(:person).permit(:name, :age, pets_attributes: [ :name, :category ])
+ # params.require(:person).permit(:name, :age, pets_attributes: [ :id, :name, :category ])
# end
# end
#
diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb
index ac37b00010..9bb416178a 100644
--- a/actionpack/lib/action_controller/metal/testing.rb
+++ b/actionpack/lib/action_controller/metal/testing.rb
@@ -13,7 +13,7 @@ module ActionController
module ClassMethods
def before_filters
- _process_action_callbacks.find_all{|x| x.kind == :before}.map(&:name)
+ _process_action_callbacks.find_all { |x| x.kind == :before }.map(&:name)
end
end
end
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index dbf7241a14..9f3cc099d6 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -27,10 +27,10 @@ module ActionController
def url_options
@_url_options ||= {
- :host => request.host,
- :port => request.optional_port,
- :protocol => request.protocol,
- :_recall => request.path_parameters
+ host: request.host,
+ port: request.optional_port,
+ protocol: request.protocol,
+ _recall: request.path_parameters
}.merge!(super).freeze
if (same_origin = _routes.equal?(request.routes)) ||
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index 28b20052b5..6513a556ee 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -11,7 +11,7 @@ module ActionController
config.eager_load_namespaces << ActionController
- initializer "action_controller.assets_config", :group => :all do |app|
+ initializer "action_controller.assets_config", group: :all do |app|
app.config.action_controller.assets_dir ||= app.config.paths["public"].first
end
diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb
index e4d19e9dba..243a54330e 100644
--- a/actionpack/lib/action_controller/renderer.rb
+++ b/actionpack/lib/action_controller/renderer.rb
@@ -1,7 +1,7 @@
-require 'active_support/core_ext/hash/keys'
+require "active_support/core_ext/hash/keys"
module ActionController
- # ActionController::Renderer allows to render arbitrary templates
+ # ActionController::Renderer allows you to render arbitrary templates
# without requirement of being in controller actions.
#
# You get a concrete renderer class by invoking ActionController::Base#renderer.
@@ -13,11 +13,11 @@ module ActionController
#
# ApplicationController.renderer.render template: '...'
#
- # You can use a shortcut on controller to replace previous example with:
+ # You can use this shortcut in a controller, instead of the previous example:
#
# ApplicationController.render template: '...'
#
- # #render method allows you to use any options as when rendering in controller.
+ # #render allows you to use the same options that you can use when rendering in a controller.
# For example,
#
# FooController.render :action, locals: { ... }, assigns: { ... }
@@ -37,15 +37,15 @@ module ActionController
attr_reader :defaults, :controller
DEFAULTS = {
- http_host: 'example.org',
+ http_host: "example.org",
https: false,
- method: 'get',
- script_name: '',
- input: ''
+ method: "get",
+ script_name: "",
+ input: ""
}.freeze
# Create a new renderer instance for a specific controller class.
- def self.for(controller, env = {}, defaults = DEFAULTS)
+ def self.for(controller, env = {}, defaults = DEFAULTS.dup)
new(controller, env, defaults)
end
@@ -69,7 +69,7 @@ module ActionController
# Render templates with any options from ActionController::Base#render_to_string.
def render(*args)
- raise 'missing controller' unless controller
+ raise "missing controller" unless controller
request = ActionDispatch::Request.new @env
request.routes = controller._routes
@@ -88,21 +88,23 @@ module ActionController
end
RACK_KEY_TRANSLATION = {
- http_host: 'HTTP_HOST',
- https: 'HTTPS',
- method: 'REQUEST_METHOD',
- script_name: 'SCRIPT_NAME',
- input: 'rack.input'
+ http_host: "HTTP_HOST",
+ https: "HTTPS",
+ method: "REQUEST_METHOD",
+ script_name: "SCRIPT_NAME",
+ input: "rack.input"
}
IDENTITY = ->(_) { _ }
RACK_VALUE_TRANSLATION = {
- https: ->(v) { v ? 'on' : 'off' },
+ https: ->(v) { v ? "on" : "off" },
method: ->(v) { v.upcase },
}
- def rack_key_for(key); RACK_KEY_TRANSLATION[key]; end
+ def rack_key_for(key)
+ RACK_KEY_TRANSLATION.fetch(key, key.to_s)
+ end
def rack_value_for(key, value)
RACK_VALUE_TRANSLATION.fetch(key, IDENTITY).call value
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 0c4b661214..33c0201951 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -1,13 +1,12 @@
-require 'rack/session/abstract/id'
-require 'active_support/core_ext/hash/conversions'
-require 'active_support/core_ext/object/to_query'
-require 'active_support/core_ext/module/anonymous'
-require 'active_support/core_ext/hash/keys'
-require 'action_controller/template_assertions'
-require 'rails-dom-testing'
+require "rack/session/abstract/id"
+require "active_support/core_ext/hash/conversions"
+require "active_support/core_ext/object/to_query"
+require "active_support/core_ext/module/anonymous"
+require "active_support/core_ext/hash/keys"
+require "action_controller/template_assertions"
+require "rails-dom-testing"
module ActionController
- # :stopdoc:
class Metal
include Testing::Functional
end
@@ -27,18 +26,20 @@ module ActionController
# Please use ActionDispatch::IntegrationTest going forward.
class TestRequest < ActionDispatch::TestRequest #:nodoc:
DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
- DEFAULT_ENV.delete 'PATH_INFO'
+ DEFAULT_ENV.delete "PATH_INFO"
def self.new_session
TestSession.new
end
+ attr_reader :controller_class
+
# Create a new test request with default `env` values
- def self.create
+ def self.create(controller_class)
env = {}
env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
env["rack.request.cookie_hash"] = {}.with_indifferent_access
- new(default_env.merge(env), new_session)
+ new(default_env.merge(env), new_session, controller_class)
end
def self.default_env
@@ -46,13 +47,14 @@ module ActionController
end
private_class_method :default_env
- def initialize(env, session)
+ def initialize(env, session, controller_class)
super(env)
self.session = session
- self.session_options = TestSession::DEFAULT_OPTIONS
+ self.session_options = TestSession::DEFAULT_OPTIONS.dup
+ @controller_class = controller_class
@custom_param_parsers = {
- Mime[:xml] => lambda { |raw_post| Hash.from_xml(raw_post)['hash'] }
+ xml: lambda { |raw_post| Hash.from_xml(raw_post)["hash"] }
}
end
@@ -61,7 +63,7 @@ module ActionController
end
def content_type=(type)
- set_header 'CONTENT_TYPE', type
+ set_header "CONTENT_TYPE", type
end
def assign_parameters(routes, controller_path, action, parameters, generated_path, query_string_keys)
@@ -83,7 +85,7 @@ module ActionController
end
if get?
- if self.query_string.blank?
+ if query_string.blank?
self.query_string = non_path_parameters.to_query
end
else
@@ -91,8 +93,8 @@ module ActionController
self.content_type = ENCODER.content_type
data = ENCODER.build_multipart non_path_parameters
else
- fetch_header('CONTENT_TYPE') do |k|
- set_header k, 'application/x-www-form-urlencoded'
+ fetch_header("CONTENT_TYPE") do |k|
+ set_header k, "application/x-www-form-urlencoded"
end
case content_mime_type.to_sym
@@ -105,13 +107,13 @@ module ActionController
when :url_encoded_form
data = non_path_parameters.to_query
else
- @custom_param_parsers[content_mime_type] = ->(_) { non_path_parameters }
+ @custom_param_parsers[content_mime_type.symbol] = ->(_) { non_path_parameters }
data = non_path_parameters.to_query
end
end
- set_header 'CONTENT_LENGTH', data.length.to_s
- set_header 'rack.input', StringIO.new(data)
+ set_header "CONTENT_LENGTH", data.length.to_s
+ set_header "rack.input", StringIO.new(data)
end
fetch_header("PATH_INFO") do |k|
@@ -152,9 +154,9 @@ module ActionController
private
- def params_parsers
- super.merge @custom_param_parsers
- end
+ def params_parsers
+ super.merge @custom_param_parsers
+ end
end
class LiveTestResponse < Live::Response
@@ -208,10 +210,18 @@ module ActionController
end
# Superclass for ActionController functional tests. Functional tests allow you to
- # test a single controller action per test method. This should not be confused with
- # integration tests (see ActionDispatch::IntegrationTest), which are more like
- # "stories" that can involve multiple controllers and multiple actions (i.e. multiple
- # different HTTP requests).
+ # test a single controller action per test method.
+ #
+ # == Use integration style controller tests over functional style controller tests.
+ #
+ # Rails discourages the use of functional tests in favor of integration tests
+ # (use ActionDispatch::IntegrationTest).
+ #
+ # New Rails applications no longer generate functional style controller tests and they should
+ # only be used for backward compatibility. Integration style controller tests perform actual
+ # requests, whereas functional style controller tests merely simulate a request. Besides,
+ # integration tests are as fast as functional tests and provide lot of helpers such as +as+,
+ # +parsed_body+ for effective testing of controller actions including even API endpoints.
#
# == Basic example
#
@@ -320,7 +330,6 @@ module ActionController
attr_reader :response, :request
module ClassMethods
-
# Sets the controller class name. Useful if the name can't be inferred from test class.
# Normalizes +controller_class+ before using.
#
@@ -377,58 +386,43 @@ module ActionController
#
# Note that the request method is not verified. The different methods are
# available to make the tests more expressive.
- def get(action, *args)
- res = process_with_kwargs("GET", action, *args)
+ def get(action, **args)
+ res = process(action, method: "GET", **args)
cookies.update res.cookies
res
end
# Simulate a POST request with the given parameters and set/volley the response.
# See +get+ for more details.
- def post(action, *args)
- process_with_kwargs("POST", action, *args)
+ def post(action, **args)
+ process(action, method: "POST", **args)
end
# Simulate a PATCH request with the given parameters and set/volley the response.
# See +get+ for more details.
- def patch(action, *args)
- process_with_kwargs("PATCH", action, *args)
+ def patch(action, **args)
+ process(action, method: "PATCH", **args)
end
# Simulate a PUT request with the given parameters and set/volley the response.
# See +get+ for more details.
- def put(action, *args)
- process_with_kwargs("PUT", action, *args)
+ def put(action, **args)
+ process(action, method: "PUT", **args)
end
# Simulate a DELETE request with the given parameters and set/volley the response.
# See +get+ for more details.
- def delete(action, *args)
- process_with_kwargs("DELETE", action, *args)
+ def delete(action, **args)
+ process(action, method: "DELETE", **args)
end
# Simulate a HEAD request with the given parameters and set/volley the response.
# See +get+ for more details.
- def head(action, *args)
- process_with_kwargs("HEAD", action, *args)
+ def head(action, **args)
+ process(action, method: "HEAD", **args)
end
- def xml_http_request(*args)
- ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
- xhr and xml_http_request methods are deprecated in favor of
- `get :index, xhr: true` and `post :create, xhr: true`
- MSG
-
- @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
- @request.env['HTTP_ACCEPT'] ||= [Mime[:js], Mime[:html], Mime[:xml], 'text/xml', '*/*'].join(', ')
- __send__(*args).tap do
- @request.env.delete 'HTTP_X_REQUESTED_WITH'
- @request.env.delete 'HTTP_ACCEPT'
- end
- end
- alias xhr :xml_http_request
-
- # Simulate a HTTP request to +action+ by specifying request method,
+ # Simulate an HTTP request to +action+ by specifying request method,
# parameters and set/volley the response.
#
# - +action+: The controller action to call.
@@ -440,6 +434,8 @@ module ActionController
# - +session+: A hash of parameters to store in the session. This may be +nil+.
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
# - +format+: Request format. Defaults to +nil+. Can be string or symbol.
+ # - +as+: Content type. Defaults to +nil+. Must be a symbol that corresponds
+ # to a mime type.
#
# Example calling +create+ action and sending two params:
#
@@ -456,56 +452,39 @@ module ActionController
# respectively which will make tests more expressive.
#
# Note that the request method is not verified.
- def process(action, *args)
+ def process(action, method: "GET", params: {}, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil)
check_required_ivars
- if kwarg_request?(args)
- parameters, session, body, flash, http_method, format, xhr = args[0].values_at(:params, :session, :body, :flash, :method, :format, :xhr)
- else
- http_method, parameters, session, flash = args
- format = nil
-
- if parameters.is_a?(String) && http_method != 'HEAD'
- body = parameters
- parameters = nil
- end
-
- if parameters || session || flash
- non_kwarg_request_warning
- end
- end
-
if body
- @request.set_header 'RAW_POST_DATA', body
+ @request.set_header "RAW_POST_DATA", body
end
- if http_method
- http_method = http_method.to_s.upcase
- else
- http_method = "GET"
- end
-
- parameters ||= {}
-
- if format
- parameters[:format] = format
- end
+ http_method = method.to_s.upcase
@html_document = nil
- self.cookies.update @request.cookies
- self.cookies.update_cookies_from_jar
- @request.set_header 'HTTP_COOKIE', cookies.to_header
- @request.delete_header 'action_dispatch.cookies'
+ cookies.update(@request.cookies)
+ cookies.update_cookies_from_jar
+ @request.set_header "HTTP_COOKIE", cookies.to_header
+ @request.delete_header "action_dispatch.cookies"
- @request = TestRequest.new scrub_env!(@request.env), @request.session
+ @request = TestRequest.new scrub_env!(@request.env), @request.session, @controller.class
@response = build_response @response_klass
@response.request = @request
@controller.recycle!
- @request.set_header 'REQUEST_METHOD', http_method
+ @request.set_header "REQUEST_METHOD", http_method
+
+ if as
+ @request.content_type = Mime[as].to_s
+ format ||= as
+ end
- parameters = parameters.symbolize_keys
+ parameters = params.symbolize_keys
+
+ if format
+ parameters[:format] = format
+ end
generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action.to_s))
generated_path = generated_path(generated_extras)
@@ -517,9 +496,9 @@ module ActionController
@request.flash.update(flash || {})
if xhr
- @request.set_header 'HTTP_X_REQUESTED_WITH', 'XMLHttpRequest'
- @request.fetch_header('HTTP_ACCEPT') do |k|
- @request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], 'text/xml', '*/*'].join(', ')
+ @request.set_header "HTTP_X_REQUESTED_WITH", "XMLHttpRequest"
+ @request.fetch_header("HTTP_ACCEPT") do |k|
+ @request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ")
end
end
@@ -527,32 +506,37 @@ module ActionController
@request.set_header k, @controller.config.relative_url_root
end
- @controller.recycle!
- @controller.dispatch(action, @request, @response)
- @request = @controller.request
- @response = @controller.response
+ begin
+ @controller.recycle!
+ @controller.dispatch(action, @request, @response)
+ ensure
+ @request = @controller.request
+ @response = @controller.response
- @request.delete_header 'HTTP_COOKIE'
+ @request.delete_header "HTTP_COOKIE"
- if @request.have_cookie_jar?
- unless @request.cookie_jar.committed?
- @request.cookie_jar.write(@response)
- self.cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
+ if @request.have_cookie_jar?
+ unless @request.cookie_jar.committed?
+ @request.cookie_jar.write(@response)
+ cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
+ end
end
- end
- @response.prepare!
+ @response.prepare!
- if flash_value = @request.flash.to_session_value
- @request.session['flash'] = flash_value
- else
- @request.session.delete('flash')
- end
+ if flash_value = @request.flash.to_session_value
+ @request.session["flash"] = flash_value
+ else
+ @request.session.delete("flash")
+ end
- if xhr
- @request.delete_header 'HTTP_X_REQUESTED_WITH'
- @request.delete_header 'HTTP_ACCEPT'
+ if xhr
+ @request.delete_header "HTTP_X_REQUESTED_WITH"
+ @request.delete_header "HTTP_ACCEPT"
+ end
+ @request.query_string = ""
+
+ @response.sent!
end
- @request.query_string = ''
@response
end
@@ -587,7 +571,7 @@ module ActionController
end
end
- @request = TestRequest.create
+ @request = TestRequest.create(@controller.class)
@response = build_response @response_klass
@response.request = @request
@@ -606,71 +590,35 @@ module ActionController
include ActionDispatch::Assertions
class_attribute :_controller_class
setup :setup_controller_request_and_response
+ ActiveSupport.run_load_hooks(:action_controller_test_case, self)
end
private
- def scrub_env!(env)
- env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
- env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
- env.delete 'action_dispatch.request.query_parameters'
- env.delete 'action_dispatch.request.request_parameters'
- env
- end
-
- def process_with_kwargs(http_method, action, *args)
- if kwarg_request?(args)
- args.first.merge!(method: http_method)
- process(action, *args)
- else
- non_kwarg_request_warning if args.any?
-
- args = args.unshift(http_method)
- process(action, *args)
+ def scrub_env!(env)
+ env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
+ env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
+ env.delete "action_dispatch.request.query_parameters"
+ env.delete "action_dispatch.request.request_parameters"
+ env["rack.input"] = StringIO.new
+ env
end
- end
-
- REQUEST_KWARGS = %i(params session flash method body xhr)
- def kwarg_request?(args)
- args[0].respond_to?(:keys) && (
- (args[0].key?(:format) && args[0].keys.size == 1) ||
- args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) }
- )
- end
-
- def non_kwarg_request_warning
- ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
- ActionController::TestCase HTTP request methods will accept only
- keyword arguments in future Rails versions.
-
- Examples:
-
- get :show, params: { id: 1 }, session: { user_id: 1 }
- process :update, method: :post, params: { id: 1 }
- MSG
- end
- def document_root_element
- html_document.root
- end
+ def document_root_element
+ html_document.root
+ end
- def check_required_ivars
- # Sanity check for required instance variables so we can give an
- # understandable error message.
- [:@routes, :@controller, :@request, :@response].each do |iv_name|
- if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
- raise "#{iv_name} is nil: make sure you set it in your test's setup method."
+ def check_required_ivars
+ # Sanity check for required instance variables so we can give an
+ # understandable error message.
+ [:@routes, :@controller, :@request, :@response].each do |iv_name|
+ if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
+ raise "#{iv_name} is nil: make sure you set it in your test's setup method."
+ end
end
end
- end
-
- def html_format?(parameters)
- return true unless parameters.key?(:format)
- Mime.fetch(parameters[:format]) { Mime['html'] }.html?
- end
end
include Behavior
end
- # :startdoc:
end