diff options
Diffstat (limited to 'actionpack/lib/action_controller')
23 files changed, 207 insertions, 279 deletions
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 075e4504c2..337718afc0 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -55,7 +55,7 @@ module ActionController list = except end - Middleware.new(get_class(klass), args, list, strategy, block) + Middleware.new(klass, args, list, strategy, block) end end @@ -118,11 +118,6 @@ module ActionController class Metal < AbstractController::Base abstract! - def env - @_request.env - end - deprecate :env - # Returns the last part of the controller's name, underscored, without the ending # <tt>Controller</tt>. For instance, PostsController returns <tt>posts</tt>. # Namespaces are left out, so Admin::PostsController returns <tt>posts</tt> as well. @@ -139,8 +134,8 @@ module ActionController end end - def self.encoding_for_param(action, param) # :nodoc: - ::Encoding::UTF_8 + def self.binary_params_for?(action) # :nodoc: + false end # Delegates to the class' <tt>controller_name</tt> @@ -232,14 +227,6 @@ module ActionController middleware_stack end - # Makes the controller a Rack endpoint that runs the action in the given - # +env+'s +action_dispatch.request.path_parameters+ key. - def self.call(env) - req = ActionDispatch::Request.new env - action(req.path_parameters[:action]).call(env) - end - class << self; deprecate :call; end - # Returns a Rack endpoint for the given action name. def self.action(name) if middleware_stack.any? diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index 89bf60a0bb..eb636fa3f6 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -238,7 +238,7 @@ module ActionController ) 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 diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb index f089c8423b..731e03e2fc 100644 --- a/actionpack/lib/action_controller/metal/data_streaming.rb +++ b/actionpack/lib/action_controller/metal/data_streaming.rb @@ -11,7 +11,7 @@ module ActionController #:nodoc: DEFAULT_SEND_FILE_TYPE = "application/octet-stream".freeze #:nodoc: DEFAULT_SEND_FILE_DISPOSITION = "attachment".freeze #:nodoc: - protected + private # Sends the file. This uses a server-appropriate method (such as X-Sendfile) # via the Rack::Sendfile middleware. The header to use is set via # +config.action_dispatch.x_sendfile_header+. @@ -70,7 +70,6 @@ module ActionController #:nodoc: 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 @@ -109,10 +108,12 @@ module ActionController #:nodoc: render options.slice(:status, :content_type).merge(body: data) end - private def send_file_headers!(options) type_provided = options.has_key?(:type) + self.content_type = DEFAULT_SEND_FILE_TYPE + response.sending_file = true + content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE) raise ArgumentError, ":type option required" if content_type.nil? @@ -137,8 +138,6 @@ module ActionController #:nodoc: headers["Content-Transfer-Encoding"] = "binary" - response.sending_file = true - # Fix a problem with IE 6.0 on opening downloaded files: # If Cache-Control: no-cache is set (which Rails does by default), # IE removes the file it just downloaded from its cache immediately 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 49b5f1090e..798564db96 100644 --- a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb +++ b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb @@ -39,10 +39,10 @@ module ActionController end 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. + # 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}" diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb index 56a4b085e2..175dd9eb9e 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: @@ -24,7 +14,7 @@ module ActionController class RoutingError < ActionControllerError #:nodoc: attr_reader :failures - def initialize(message, failures=[]) + def initialize(message, failures = []) super(message) @failures = failures end diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb index 65351284b9..347fbf0e74 100644 --- a/actionpack/lib/action_controller/metal/flash.rb +++ b/actionpack/lib/action_controller/metal/flash.rb @@ -42,7 +42,7 @@ module ActionController #:nodoc: end end - protected + private def redirect_to(options = {}, response_status_and_flash = {}) #:doc: self.class._flash_types.each do |flash_type| if type = response_status_and_flash.delete(flash_type) diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb index b8976497a4..9d43e752ac 100644 --- a/actionpack/lib/action_controller/metal/force_ssl.rb +++ b/actionpack/lib/action_controller/metal/force_ssl.rb @@ -89,7 +89,7 @@ module ActionController end secure_url = ActionDispatch::Http::URL.url_for(options.slice(*URL_OPTIONS)) - flash.keep if respond_to?(:flash) + flash.keep if respond_to?(:flash) && request.respond_to?(:flash) redirect_to secure_url, options.slice(*REDIRECT_OPTIONS) end end diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index 86b5eb20d7..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 diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index 7257bbfa95..476d081239 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -108,7 +108,7 @@ module ActionController end private - # Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt> + # Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt> def all_application_helpers all_helpers_from_path(helpers_path) end diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index a335bf109e..0575360068 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -28,7 +28,7 @@ module ActionController # class ApplicationController < ActionController::Base # before_action :set_account, :authenticate # - # protected + # private # def set_account # @account = Account.find_by(url_name: request.subdomains.first) # end @@ -224,7 +224,7 @@ module ActionController # Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+ # Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead # of a plain-text password. - def expected_response(http_method, uri, credentials, password, password_is_ha1=true) + 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(":")) @@ -246,7 +246,7 @@ module ActionController 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('\'')] + [key.strip, value.to_s.gsub(/^"|"$/, "").delete('\'')] end] end @@ -314,7 +314,7 @@ module ActionController # Can be much shorter if the Stale directive is implemented. This would # 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) + def validate_nonce(secret_key, request, value, seconds_to_timeout = 5 * 60) return false if value.nil? t = ::Base64.decode64(value).split(":").first.to_i nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout @@ -363,7 +363,7 @@ module ActionController # class ApplicationController < ActionController::Base # before_action :set_account, :authenticate # - # protected + # private # def set_account # @account = Account.find_by(url_name: request.subdomains.first) # end diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index 2ede96c667..924686218f 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -46,7 +46,7 @@ module ActionController render_output end - def send_file(path, options={}) + def send_file(path, options = {}) ActiveSupport::Notifications.instrument("send_file.action_controller", options.merge(path: path)) do super @@ -83,14 +83,14 @@ module ActionController # end # # :api: plugin - def cleanup_view_runtime #:nodoc: + def cleanup_view_runtime yield end # Every time after an action is processed, this method is invoked # with the payload, so you can add more information. # :api: plugin - def append_info_to_payload(payload) #:nodoc: + def append_info_to_payload(payload) payload[:view_runtime] = view_runtime end diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index 26a16104db..fed99e6c82 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -247,7 +247,7 @@ module ActionController # Since we're processing the view in a different thread, copy the # thread locals from the main thread to the child thread. :'( - locals.each { |k,v| t2[k] = v } + locals.each { |k, v| t2[k] = v } begin super(name) diff --git a/actionpack/lib/action_controller/metal/parameter_encoding.rb b/actionpack/lib/action_controller/metal/parameter_encoding.rb index c457fd0d06..962532ff09 100644 --- a/actionpack/lib/action_controller/metal/parameter_encoding.rb +++ b/actionpack/lib/action_controller/metal/parameter_encoding.rb @@ -1,5 +1,5 @@ module ActionController - # Allows encoding to be specified per parameter per action. + # Specify binary encoding for parameters for a given action. module ParameterEncoding extend ActiveSupport::Concern @@ -13,17 +13,36 @@ module ActionController @_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 + def binary_params_for?(action) # :nodoc: + @_parameter_encodings[action.to_s] end - def parameter_encoding(action, param_name, encoding) - @_parameter_encodings[action.to_s] ||= {} - @_parameter_encodings[action.to_s][param_name.to_s] = encoding + # Specify that a given action's parameters should all be encoded as + # ASCII-8BIT (it "skips" the encoding default of UTF-8). + # + # For example, a controller would use it like this: + # + # class RepositoryController < ActionController::Base + # skip_parameter_encoding :show + # + # def show + # @repo = Repository.find_by_filesystem_path params[:file_path] + # + # # `repo_name` is guaranteed to be UTF-8, but was ASCII-8BIT, so + # # tag it as such + # @repo_name = params[:repo_name].force_encoding 'UTF-8' + # end + # + # def index + # @repositories = Repository.all + # end + # end + # + # The show action in the above controller would have all parameter values + # encoded as ASCII-8BIT. This is useful in the case where an application + # must handle data but encoding of the data is unknown, like file system data. + def skip_parameter_encoding(action) + @_parameter_encodings[action.to_s] = true end end end diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index 745f449a05..7fc898f034 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -128,14 +128,14 @@ 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: + # 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 return nil if klass.anonymous? model_name = klass.name.sub(/Controller$/, "").classify @@ -205,7 +205,7 @@ module ActionController model = name_or_model_or_options end - opts = Options.from_hash _wrapper_options.to_h.slice(:format).merge(options) + opts = Options.from_hash _wrapper_options.to_h.slice(:format).merge(options) opts.model = model opts.klass = self diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 2bd4296aff..4dfcf4da28 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: @@ -58,7 +50,7 @@ module ActionController # redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id } # redirect_to({ action: 'atom' }, alert: "Something serious happened") # - def redirect_to(options = {}, response_status = {}) #:doc: + def redirect_to(options = {}, response_status = {}) raise ActionControllerError.new("Cannot redirect to nil!") unless options raise AbstractController::DoubleRenderError if response_body @@ -77,11 +69,11 @@ 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 identical. @@ -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"] || 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 15377ddcb9..733aca195d 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -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 @@ -106,7 +104,7 @@ module ActionController # # Since <tt>ActionController::Metal</tt> controllers cannot render, the controller # must include <tt>AbstractController::Rendering</tt>, <tt>ActionController::Rendering</tt>, - # and <tt>ActionController::Renderers</tt>, and have at lest one renderer. + # and <tt>ActionController::Renderers</tt>, and have at least one renderer. # # Rather than including <tt>ActionController::Renderers::All</tt> and including all renderers, # you may specify which renderers to include by passing the renderer name or names to diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index ac17d61b96..4c01891d4c 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -4,7 +4,7 @@ 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 @@ -54,6 +54,12 @@ module ActionController private + def _process_variant(options) + if defined?(request) && !request.nil? && request.variant.present? + options[:variant] = request.variant + end + end + def _render_in_priorities(options) RENDER_FORMATS_IN_PRIORITY.each do |format| return options[format] if options.key?(format) @@ -67,42 +73,26 @@ module ActionController end def _set_rendered_content_type(format) - unless response.content_type + if format && !response.content_type self.content_type = format.to_s end end - # Normalize arguments by catching blocks and setting them on :update. - def _normalize_args(action=nil, options={}, &blk) #:nodoc: + # Normalize arguments by catching blocks and setting them on :update. + def _normalize_args(action = nil, options = {}, &blk) options = super options[:update] = blk if block_given? options end - # Normalize both text and status options. - def _normalize_options(options) #:nodoc: + # Normalize both text and status options. + def _normalize_options(options) _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 - end - if options[:html] options[:html] = ERB::Util.html_escape(options[:html]) 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 - end - if options[:status] options[:status] = Rack::Utils.status_code(options[:status]) end @@ -118,8 +108,8 @@ module ActionController end end - # Process controller specific options, as status, content-type and location. - def _process_options(options) #:nodoc: + # Process controller specific options, as status, content-type and location. + def _process_options(options) status, content_type, location = options.values_at(:status, :content_type, :location) self.status = status if status diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 3d3c121280..e8965a6561 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -152,7 +152,7 @@ module ActionController #:nodoc: request.cookie_jar = NullCookieJar.build(request, {}) end - protected + private class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc: def initialize(req) @@ -197,7 +197,7 @@ module ActionController #:nodoc: end end - protected + private # The actual before_action that is used to verify the CSRF token. # Don't override this directly. Provide your own forgery protection # strategy instead. If you override, you'll disable same-origin @@ -208,7 +208,7 @@ module ActionController #:nodoc: # enabled on an action, this before_action flags its after_action to # verify that JavaScript responses are for XHR requests, ensuring they # follow the browser's same-origin policy. - def verify_authenticity_token + def verify_authenticity_token # :doc: mark_for_same_origin_verification! if !verified_request? @@ -219,7 +219,7 @@ module ActionController #:nodoc: end end - def handle_unverified_request + def handle_unverified_request # :doc: forgery_protection_strategy.new(self).handle_unverified_request end @@ -233,7 +233,7 @@ module ActionController #:nodoc: # If `verify_authenticity_token` was run (indicating that we have # forgery protection enabled for this request) then also verify that # we aren't serving an unauthorized cross-origin response. - def verify_same_origin_request + def verify_same_origin_request # :doc: if marked_for_same_origin_verification? && non_xhr_javascript_response? if logger && log_warning_on_csrf_failure logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING @@ -243,18 +243,18 @@ module ActionController #:nodoc: end # GET requests are checked for cross-origin JavaScript after rendering. - def mark_for_same_origin_verification! + def mark_for_same_origin_verification! # :doc: @marked_for_same_origin_verification = request.get? end # If the `verify_authenticity_token` before_action ran, verify that # JavaScript responses are only served to same-origin GET requests. - def marked_for_same_origin_verification? + def marked_for_same_origin_verification? # :doc: @marked_for_same_origin_verification ||= false end # Check for cross-origin JavaScript responses. - def non_xhr_javascript_response? + def non_xhr_javascript_response? # :doc: content_type =~ %r(\Atext/javascript) && !request.xhr? end @@ -265,20 +265,20 @@ module ActionController #:nodoc: # * Is it a GET or HEAD request? Gets should be safe and idempotent # * Does the form_authenticity_token match the given token value from the params? # * Does the X-CSRF-Token header match the form_authenticity_token - def verified_request? + def verified_request? # :doc: !protect_against_forgery? || request.get? || request.head? || (valid_request_origin? && any_authenticity_token_valid?) end # Checks if any of the authenticity tokens from the request are valid. - def any_authenticity_token_valid? + def any_authenticity_token_valid? # :doc: request_authenticity_tokens.any? do |token| valid_authenticity_token?(session, token) end end # Possible authenticity tokens sent in the request. - def request_authenticity_tokens + def request_authenticity_tokens # :doc: [form_authenticity_param, request.x_csrf_token] end @@ -290,7 +290,7 @@ module ActionController #:nodoc: # Creates a masked version of the authenticity token that varies # on each request. The masking is used to mitigate SSL attacks # like BREACH. - def masked_authenticity_token(session, form_options: {}) + def masked_authenticity_token(session, form_options: {}) # :doc: action, method = form_options.values_at(:action, :method) raw_token = if per_form_csrf_tokens && action && method @@ -309,7 +309,7 @@ module ActionController #:nodoc: # Checks the client's masked token to see if it matches the # session token. Essentially the inverse of # +masked_authenticity_token+. - def valid_authenticity_token?(session, encoded_masked_token) + def valid_authenticity_token?(session, encoded_masked_token) # :doc: if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String) return false end @@ -340,7 +340,7 @@ module ActionController #:nodoc: end end - def unmask_token(masked_token) + def unmask_token(masked_token) # :doc: # Split the token into the one-time pad and the encrypted # value and decrypt it one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH] @@ -348,11 +348,11 @@ module ActionController #:nodoc: xor_byte_strings(one_time_pad, encrypted_csrf_token) end - def compare_with_real_token(token, session) + def compare_with_real_token(token, session) # :doc: ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session)) end - def valid_per_form_csrf_token?(token, session) + def valid_per_form_csrf_token?(token, session) # :doc: if per_form_csrf_tokens correct_token = per_form_csrf_token( session, @@ -366,12 +366,12 @@ module ActionController #:nodoc: end end - def real_csrf_token(session) + def real_csrf_token(session) # :doc: session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH) Base64.strict_decode64(session[:_csrf_token]) end - def per_form_csrf_token(session, action_path, method) + def per_form_csrf_token(session, action_path, method) # :doc: OpenSSL::HMAC.digest( OpenSSL::Digest::SHA256.new, real_csrf_token(session), @@ -379,25 +379,25 @@ module ActionController #:nodoc: ) end - def xor_byte_strings(s1, s2) + def xor_byte_strings(s1, s2) # :doc: s2_bytes = s2.bytes s1.each_byte.with_index { |c1, i| s2_bytes[i] ^= c1 } s2_bytes.pack("C*") end # The form's authenticity parameter. Override to provide your own. - def form_authenticity_param + def form_authenticity_param # :doc: params[request_forgery_protection_token] end # Checks if the controller allows forgery protection. - def protect_against_forgery? + def protect_against_forgery? # :doc: allow_forgery_protection end # Checks if the request originated from the same origin by looking at the # Origin header. - def valid_request_origin? + def valid_request_origin? # :doc: if forgery_protection_origin_check # We accept blank origin headers because some user agents don't send it. request.origin.nil? || request.origin == request.base_url @@ -406,7 +406,7 @@ module ActionController #:nodoc: end end - def normalize_action_path(action_path) + def normalize_action_path(action_path) # :doc: uri = URI.parse(action_path) uri.path.chomp("/") end diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 481f19f1ef..877a08b222 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -193,10 +193,10 @@ module ActionController #:nodoc: module Streaming extend ActiveSupport::Concern - protected + private # Set proper cache control and transfer encoding when streaming - def _process_options(options) #:nodoc: + def _process_options(options) super if options[:stream] if request.version == "HTTP/1.0" @@ -210,7 +210,7 @@ module ActionController #:nodoc: end # Call render_body if we are streaming instead of usual +render+. - def _render_template(options) #:nodoc: + def _render_template(options) if options.delete(:stream) Rack::Chunked::Body.new view_renderer.render_body(view_context, options) else diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 387c2aa0b9..acfeca1fcb 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -71,8 +71,8 @@ module ActionController # * +permit_all_parameters+ - If it's +true+, all the parameters will be # permitted by default. The default is +false+. # * +action_on_unpermitted_parameters+ - Allow to control the behavior when parameters - # that are not explicitly permitted are found. The values can be <tt>:log</tt> to - # write a message on the logger or <tt>:raise</tt> to raise + # that are not explicitly permitted are found. The values can be +false+ to just filter them + # out, <tt>:log</tt> to additionally write a message on the logger, or <tt>:raise</tt> to raise # ActionController::UnpermittedParameters exception. The default value is <tt>:log</tt> # in test and development environments, +false+ otherwise. # @@ -151,15 +151,6 @@ module ActionController def ==(other) if other.respond_to?(:permitted?) self.permitted? == other.permitted? && self.parameters == other.parameters - elsif other.is_a?(Hash) - ActiveSupport::Deprecation.warn <<-WARNING.squish - Comparing equality between `ActionController::Parameters` and a - `Hash` is deprecated and will be removed in Rails 5.1. Please only do - comparisons between instances of `ActionController::Parameters`. If - you need to compare to a hash, first convert it using - `ActionController::Parameters#new`. - WARNING - @parameters == other.with_indifferent_access else @parameters == other end @@ -343,6 +334,15 @@ module ActionController # params = ActionController::Parameters.new(tags: ['rails', 'parameters']) # params.permit(tags: []) # + # Sometimes it is not possible or convenient to declare the valid keys of + # a hash parameter or its internal structure. Just map to an empty hash: + # + # params.permit(preferences: {}) + # + # but be careful because this opens the door to arbitrary input. In this + # case, +permit+ ensures values in the returned structure are permitted + # scalars and filters out anything else. + # # You can also use +permit+ on nested parameters, like: # # params = ActionController::Parameters.new({ @@ -391,14 +391,15 @@ module ActionController case filter when Symbol, String permitted_scalar_filter(params, filter) - when Hash then + when Hash hash_filter(params, filter) end end unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters - params.permit! + params.permitted = true + params end # Returns a parameter for the given +key+. If not found, @@ -548,7 +549,7 @@ module ActionController new_instance_with_inherited_permitted_status(@parameters.select(&block)) end - # Equivalent to Hash#keep_if, but returns nil if no changes were made. + # Equivalent to Hash#keep_if, but returns +nil+ if no changes were made. def select!(&block) @parameters.select!(&block) self @@ -582,6 +583,13 @@ module ActionController ) end + # Returns current <tt>ActionController::Parameters</tt> instance which + # +other_hash+ merges into current hash. + def merge!(other_hash) + @parameters.merge!(other_hash.to_h) + self + end + # This is required by ActiveModel attribute assignment, so that user can # pass +Parameters+ to a mass assignment methods in a model. It should not # matter as we are using +HashWithIndifferentAccess+ internally. @@ -620,25 +628,12 @@ module ActionController end end - # Undefine `to_param` such that it gets caught in the `method_missing` - # deprecation cycle below. undef_method :to_param - 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 + # Returns duplicate of object including all parameters + def deep_dup + self.class.new(@parameters.deep_dup).tap do |duplicate| + duplicate.permitted = @permitted end end @@ -702,7 +697,7 @@ module ActionController when Parameters if object.fields_for_style? hash = object.class.new - object.each { |k,v| hash[k] = yield v } + object.each { |k, v| hash[k] = yield v } hash else yield object @@ -781,6 +776,7 @@ module ActionController end EMPTY_ARRAY = [] + EMPTY_HASH = {} def hash_filter(params, filter) filter = filter.with_indifferent_access @@ -794,6 +790,11 @@ module ActionController array_of_permitted_scalars?(self[key]) do |val| params[key] = val end + elsif filter[key] == EMPTY_HASH + # Declaration { preferences: {} }. + if value.is_a?(Parameters) + params[key] = permit_any_in_parameters(value) + end elsif non_scalar?(value) # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }. params[key] = each_element(value) do |element| @@ -803,6 +804,39 @@ module ActionController end end + def permit_any_in_parameters(params) + self.class.new.tap do |sanitized| + params.each do |key, value| + case value + when ->(v) { permitted_scalar?(v) } + sanitized[key] = value + when Array + sanitized[key] = permit_any_in_array(value) + when Parameters + sanitized[key] = permit_any_in_parameters(value) + else + # Filter this one out. + end + end + sanitized.permitted = true + end + end + + def permit_any_in_array(array) + [].tap do |sanitized| + array.each do |element| + case element + when ->(e) { permitted_scalar?(e) } + sanitized << element + when Parameters + sanitized << permit_any_in_parameters(element) + else + # Filter this one out. + end + end + end + end + def initialize_copy(source) super @parameters = @parameters.dup diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 6513a556ee..a7cdfe6a98 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -51,7 +51,7 @@ module ActionController extend ::AbstractController::Railties::RoutesHelpers.with(app.routes) extend ::ActionController::Railties::Helpers - options.each do |k,v| + options.each do |k, v| k = "#{k}=" if respond_to?(k) send(k, v) diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb index 0739f16965..3ff80e6a39 100644 --- a/actionpack/lib/action_controller/renderer.rb +++ b/actionpack/lib/action_controller/renderer.rb @@ -83,7 +83,7 @@ module ActionController private def normalize_keys(env) new_env = {} - env.each_pair { |k,v| new_env[rack_key_for(k)] = rack_value_for(k, v) } + env.each_pair { |k, v| new_env[rack_key_for(k)] = rack_value_for(k, v) } new_env end @@ -102,7 +102,9 @@ module ActionController 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 09f2a79d85..441667e556 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -3,6 +3,7 @@ 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 "active_support/testing/constant_lookup" require "action_controller/template_assertions" require "rails-dom-testing" @@ -112,8 +113,9 @@ module ActionController end end - set_header "CONTENT_LENGTH", data.length.to_s - set_header "rack.input", StringIO.new(data) + data_stream = StringIO.new(data) + set_header "CONTENT_LENGTH", data_stream.length.to_s + set_header "rack.input", data_stream end fetch_header("PATH_INFO") do |k| @@ -386,57 +388,42 @@ 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 an HTTP request to +action+ by specifying request method, # parameters and set/volley the response. # @@ -467,40 +454,14 @@ 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, as = args[0].values_at(:params, :session, :body, :flash, :method, :format, :xhr, :as) - 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 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 @@ -521,7 +482,11 @@ module ActionController 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) @@ -550,8 +515,6 @@ module ActionController @request = @controller.request @response = @controller.response - @request.delete_header "HTTP_COOKIE" - if @request.have_cookie_jar? unless @request.cookie_jar.committed? @request.cookie_jar.write(@response) @@ -641,38 +604,6 @@ module ActionController 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) - 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 |