diff options
Diffstat (limited to 'actionpack/lib/action_controller/metal')
19 files changed, 179 insertions, 165 deletions
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index b210ee3423..47bcfdb1e9 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -15,7 +15,7 @@ module ActionController module ClassMethods # Allows you to consider additional controller-wide information when generating an ETag. # For example, if you serve pages tailored depending on who's logged in at the moment, you - # may want to add the current user id to be part of the ETag to prevent authorized displaying + # may want to add the current user id to be part of the ETag to prevent unauthorized displaying # of cached pages. # # class InvoicesController < ApplicationController @@ -51,21 +51,31 @@ module ActionController # # def show # @article = Article.find(params[:id]) - # fresh_when(etag: @article, last_modified: @article.created_at, public: true) + # fresh_when(etag: @article, last_modified: @article.updated_at, public: true) # end # # This will render the show template if the request isn't sending a matching ETag or # If-Modified-Since header and just a <tt>304 Not Modified</tt> response if there's a match. # - # You can also just pass a record where +last_modified+ will be set by calling - # +updated_at+ and the +etag+ by passing the object itself. + # You can also just pass a record. In this case +last_modified+ will be set + # by calling +updated_at+ and +etag+ by passing the object itself. # # def show # @article = Article.find(params[:id]) # fresh_when(@article) # end # - # When passing a record, you can still set whether the public header: + # You can also pass an object that responds to +maximum+, such as a + # collection of active records. In this case +last_modified+ will be set by + # calling +maximum(:updated_at)+ on the collection (the timestamp of the + # most recently updated record) and the +etag+ by passing the object itself. + # + # def index + # @articles = Article.all + # fresh_when(@articles) + # end + # + # When passing a record or a collection, you can still set the public header: # # def show # @article = Article.find(params[:id]) @@ -77,18 +87,16 @@ module ActionController # # before_action { fresh_when @article, template: 'widgets/show' } # - def fresh_when(record_or_options, additional_options = {}) - if record_or_options.is_a? Hash - options = record_or_options - options.assert_valid_keys(:etag, :last_modified, :public, :template) - else - record = record_or_options - options = { etag: record, last_modified: record.try(:updated_at) }.merge!(additional_options) + def fresh_when(object = nil, etag: object, last_modified: nil, public: false, template: nil) + 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) end - response.etag = combine_etags(options) if options[:etag] || options[:template] - response.last_modified = options[:last_modified] if options[:last_modified] - response.cache_control[:public] = true if options[:public] + response.last_modified = last_modified if last_modified + response.cache_control[:public] = true if public head :not_modified if request.fresh?(response) end @@ -115,7 +123,7 @@ module ActionController # def show # @article = Article.find(params[:id]) # - # if stale?(etag: @article, last_modified: @article.created_at) + # if stale?(etag: @article, last_modified: @article.updated_at) # @statistics = @article.really_expensive_call # respond_to do |format| # # all the supported formats @@ -123,8 +131,8 @@ module ActionController # end # end # - # You can also just pass a record where +last_modified+ will be set by calling - # +updated_at+ and the +etag+ by passing the object itself. + # You can also just pass a record. In this case +last_modified+ will be set + # by calling +updated_at+ and +etag+ by passing the object itself. # # def show # @article = Article.find(params[:id]) @@ -137,7 +145,23 @@ module ActionController # end # end # - # When passing a record, you can still set whether the public header: + # You can also pass an object that responds to +maximum+, such as a + # collection of active records. In this case +last_modified+ will be set by + # calling +maximum(:updated_at)+ on the collection (the timestamp of the + # most recently updated record) and the +etag+ by passing the object itself. + # + # def index + # @articles = Article.all + # + # if stale?(@articles) + # @statistics = @articles.really_expensive_call + # respond_to do |format| + # # all the supported formats + # end + # end + # end + # + # When passing a record or a collection, you can still set the public header: # # def show # @article = Article.find(params[:id]) @@ -157,8 +181,8 @@ module ActionController # super if stale? @article, template: 'widgets/show' # end # - def stale?(record_or_options, additional_options = {}) - fresh_when(record_or_options, additional_options) + 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) !request.fresh?(response) end @@ -191,6 +215,24 @@ module ActionController 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, + # 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') + expires_in 100.years, public: public + + yield if stale?(etag: "#{version}-#{request.fullpath}", + last_modified: Time.parse('2011-01-01').utc, + public: public) + end + private def combine_etags(options) etags = etaggers.map { |etagger| instance_exec(options, &etagger) }.compact 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 3ca0c6837a..f9303efe6c 100644 --- a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb +++ b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb @@ -7,8 +7,8 @@ module ActionController # # config.action_controller.etag_with_template_digest = false # - # Override the template to digest by passing `:template` to `fresh_when` - # and `stale?` calls. For example: + # Override the template to digest by passing +:template+ to +fresh_when+ + # and +stale?+ calls. For example: # # # We're going to render widgets/show, not posts/show # fresh_when @post, template: 'widgets/show' diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb index d920668184..5a8c7db162 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 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 3d2badf9c2..70f42bf565 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -29,15 +29,17 @@ module ActionController self.status = status self.location = url_for(location) if location - if include_content?(self._status_code) + self.response_body = "" + + if include_content?(self.response_code) self.content_type = content_type || (Mime[formats.first] if formats) self.response.charset = false if self.response - self.response_body = " " else headers.delete('Content-Type') headers.delete('Content-Length') - self.response_body = "" end + + true end private diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index a9c3e438fb..4038101fe0 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -93,6 +93,10 @@ module ActionController super(args) end + # Returns a list of helper names in a given path. + # + # ActionController::Base.all_helpers_from_path 'app/helpers' + # # => ["application", "chart", "rubygems"] def all_helpers_from_path(path) helpers = Array(path).flat_map do |_path| extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/ diff --git a/actionpack/lib/action_controller/metal/hide_actions.rb b/actionpack/lib/action_controller/metal/hide_actions.rb deleted file mode 100644 index af36ffa240..0000000000 --- a/actionpack/lib/action_controller/metal/hide_actions.rb +++ /dev/null @@ -1,40 +0,0 @@ - -module ActionController - # Adds the ability to prevent public methods on a controller to be called as actions. - module HideActions - extend ActiveSupport::Concern - - included do - class_attribute :hidden_actions - self.hidden_actions = Set.new.freeze - end - - private - - # Overrides AbstractController::Base#action_method? to return false if the - # action name is in the list of hidden actions. - def method_for_action(action_name) - self.class.visible_action?(action_name) && super - end - - module ClassMethods - # Sets all of the actions passed in as hidden actions. - # - # ==== Parameters - # * <tt>args</tt> - A list of actions - def hide_action(*args) - self.hidden_actions = hidden_actions.dup.merge(args.map(&:to_s)).freeze - end - - def visible_action?(action_name) - not hidden_actions.include?(action_name) - end - - # Overrides AbstractController::Base#action_methods to remove any methods - # that are listed as hidden methods. - def action_methods - @action_methods ||= Set.new(super.reject { |name| hidden_actions.include?(name) }).freeze - end - end - end -end diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 25c123edf7..c492b7fb64 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -53,10 +53,8 @@ module ActionController # In your integration tests, you can do something like this: # # def test_access_granted_from_xml - # get( - # "/notes/1.xml", nil, - # 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password) - # ) + # @request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password) + # get "/notes/1.xml" # # assert_equal 200, status # end @@ -108,11 +106,11 @@ module ActionController end def auth_scheme(request) - request.authorization.split(' ', 2).first + request.authorization.to_s.split(' ', 2).first end def auth_param(request) - request.authorization.split(' ', 2).second + request.authorization.to_s.split(' ', 2).second end def encode_credentials(user_name, password) @@ -120,7 +118,7 @@ module ActionController end def authentication_request(controller, realm) - controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}") + controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub('"'.freeze, "".freeze)}") controller.status = 401 controller.response_body = "HTTP Basic: Access denied.\n" end @@ -316,7 +314,7 @@ module ActionController nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout end - # Opaque based on random generation - but changing each request? + # Opaque based on digest of secret key def opaque(secret_key) ::Digest::MD5.hexdigest(secret_key) end @@ -397,6 +395,7 @@ module ActionController # # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L] module Token + TOKEN_KEY = 'token=' TOKEN_REGEX = /^Token / AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/ extend self @@ -462,16 +461,22 @@ module ActionController raw_params.map { |param| param.split %r/=(.+)?/ } end - # This removes the `"` characters wrapping the value. + # This removes the <tt>"</tt> characters wrapping the value. def rewrite_param_values(array_params) 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 `:`, `;`, or `\t` delimiters defined in - # `AUTHN_PAIR_DELIMITERS`. + # pairs by the standardized <tt>:</tt>, <tt>;</tt>, or <tt>\t</tt> + # delimiters defined in +AUTHN_PAIR_DELIMITERS+. def raw_params(auth) - 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}" + end + + _raw_params end # Encodes the given token and options into an Authorization header value. @@ -481,7 +486,7 @@ module ActionController # # Returns String. def encode_credentials(token, options = {}) - values = ["token=#{token.to_s.inspect}"] + options.map do |key, value| + values = ["#{TOKEN_KEY}#{token.to_s.inspect}"] + options.map do |key, value| "#{key}=#{value.to_s.inspect}" end "Token #{values * ", "}" @@ -494,7 +499,7 @@ module ActionController # # Returns nothing. def authentication_request(controller, realm) - controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.gsub(/"/, "")}") + controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.gsub('"'.freeze, "".freeze)}") controller.__send__ :render, :text => "HTTP Token: Access denied.\n", :status => :unauthorized end end diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb index ae04b53825..1573ea7099 100644 --- a/actionpack/lib/action_controller/metal/implicit_render.rb +++ b/actionpack/lib/action_controller/metal/implicit_render.rb @@ -7,7 +7,12 @@ module ActionController end def default_render(*args) - render(*args) + if template_exists?(action_name.to_s, _prefixes, variants: request.variant) + render(*args) + else + logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger + head :no_content + end end def method_for_action(action_name) diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index b0e164bc57..a3e1a71b0a 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -21,17 +21,20 @@ module ActionController :action => self.action_name, :params => request.filtered_parameters, :format => request.format.try(:ref), - :method => request.method, + :method => request.request_method, :path => (request.fullpath rescue "unknown") } ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup) ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload| - result = super - payload[:status] = response.status - append_info_to_payload(payload) - result + begin + result = super + payload[:status] = response.status + result + ensure + append_info_to_payload(payload) + end end end diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index 1e13b3761f..58150cd9a9 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -102,7 +102,7 @@ module ActionController end end - message = json.gsub(/\n/, "\ndata: ") + message = json.gsub("\n".freeze, "\ndata: ".freeze) @stream.write "data: #{message}\n\n" end end @@ -189,12 +189,6 @@ module ActionController !@aborted end - def await_close - synchronize do - @cv.wait_until { @closed } - end - end - def on_error(&block) @error_callback = block end diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 591f881a53..fab1be3459 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -1,28 +1,7 @@ -require 'active_support/core_ext/array/extract_options' require 'abstract_controller/collector' module ActionController #:nodoc: module MimeResponds - extend ActiveSupport::Concern - - module ClassMethods - def respond_to(*) - raise NoMethodError, "The controller-level `respond_to' feature has " \ - "been extracted to the `responders` gem. Add it to your Gemfile to " \ - "continue using this feature:\n" \ - " gem 'responders', '~> 2.0'\n" \ - "Consult the Rails upgrade guide for details." - end - end - - def respond_with(*) - raise NoMethodError, "The `respond_with' feature has been extracted " \ - "to the `responders` gem. Add it to your Gemfile to continue using " \ - "this feature:\n" \ - " gem 'responders', '~> 2.0'\n" \ - "Consult the Rails upgrade guide for details." - end - # Without web-service support, an action which collects the data for displaying a list of people # might look something like this: # @@ -135,18 +114,6 @@ module ActionController #:nodoc: # # render json: @people # - # Since this is a common pattern, you can use the class method respond_to - # with the respond_with method to have the same results: - # - # class PeopleController < ApplicationController - # respond_to :html, :xml, :json - # - # def index - # @people = Person.all - # respond_with(@people) - # end - # end - # # Formats can have different variants. # # The request variant is a specialization of the request format, like <tt>:tablet</tt>, @@ -214,8 +181,8 @@ module ActionController #:nodoc: # format.html.phone # this gets rendered # end # - # Be sure to check the documentation of +respond_with+ and - # <tt>ActionController::MimeResponds.respond_to</tt> for more examples. + # Be sure to check the documentation of <tt>ActionController::MimeResponds.respond_to</tt> + # for more examples. def respond_to(*mimes) raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given? @@ -234,8 +201,8 @@ module ActionController #:nodoc: # A container for responses available from the current controller for # requests for different mime-types sent to a particular action. # - # The public controller methods +respond_with+ and +respond_to+ may be called - # with a block that is used to define responses to different mime-types, e.g. + # The public controller methods +respond_to+ may be called with a block + # that is used to define responses to different mime-types, e.g. # for +respond_to+ : # # respond_to do |format| @@ -321,16 +288,17 @@ module ActionController #:nodoc: end def variant - if @variant.nil? + if @variant.empty? @variants[:none] || @variants[:any] - elsif (@variants.keys & @variant).any? - @variant.each do |v| - return @variants[v] if @variants.key?(v) - end else - @variants[:any] + @variants[variant_key] end end + + private + def variant_key + @variant.find { |variant| @variants.key?(variant) } || :any + 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 09c7a6f946..0a04848eba 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -1,7 +1,6 @@ require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/hash/except' require 'active_support/core_ext/module/anonymous' -require 'active_support/core_ext/struct' require 'action_dispatch/http/mime_type' module ActionController @@ -86,7 +85,7 @@ module ActionController new name, format, include, exclude, nil, nil end - def initialize(name, format, include, exclude, klass, model) # nodoc + def initialize(name, format, include, exclude, klass, model) # :nodoc: super @include_set = include @name_set = name @@ -132,7 +131,7 @@ module ActionController 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 singularize name as the controller. For example, +UsersController+ + # 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 diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb index 545d4a7e6e..ae9d89cc8c 100644 --- a/actionpack/lib/action_controller/metal/rack_delegation.rb +++ b/actionpack/lib/action_controller/metal/rack_delegation.rb @@ -8,9 +8,15 @@ module ActionController delegate :headers, :status=, :location=, :content_type=, :status, :location, :content_type, :response_code, :to => "@_response" - def dispatch(action, request) + module ClassMethods + def build_with_env(env = {}) #:nodoc: + new.tap { |c| c.set_request! ActionDispatch::Request.new(env) } + end + end + + def set_request!(request) #:nodoc: + super set_response!(request) - super(action, request) end def response_body=(body) diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index bc94536c8c..45d3962494 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -86,8 +86,7 @@ module ActionController # end # end # To use renderers and their mime types in more concise ways, see - # <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt> and - # <tt>ActionController::MimeResponds#respond_with</tt> + # <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 diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 7bbff0450a..2d15c39d88 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -4,6 +4,17 @@ module ActionController RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html] + module ClassMethods + # Documentation at ActionController::Renderer#render + delegate :render, to: :renderer + + # Returns a renderer class (inherited from ActionController::Renderer) + # for the controller. + def renderer + @renderer ||= Renderer.for(self) + end + end + # Before processing, set the request formats in current controller formats. def process_action(*) #:nodoc: self.formats = request.formats.map(&:ref).compact diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index fd20682f8f..367b736035 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -29,14 +29,7 @@ module ActionController #:nodoc: # you're building an API you'll need something like: # # class ApplicationController < ActionController::Base - # protect_from_forgery - # skip_before_action :verify_authenticity_token, if: :json_request? - # - # protected - # - # def json_request? - # request.format.json? - # end + # protect_from_forgery unless: -> { request.format.json? } # end # # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method, @@ -87,12 +80,18 @@ module ActionController #:nodoc: # class FooController < ApplicationController # protect_from_forgery except: :index # - # You can disable CSRF protection on controller by skipping the verification before_action: + # You can disable forgery protection on controller by skipping the verification before_action: # skip_before_action :verify_authenticity_token # # Valid Options: # - # * <tt>:only/:except</tt> - Passed to the <tt>before_action</tt> call. Set which actions are verified. + # * <tt>:only/:except</tt> - Only apply forgery protection to a subset of actions. Like <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 is added to the front of the + # callback chain. If you need to make the verification depend on other callbacks, like authentication methods + # (say cookies vs oauth), this might not work for you. Pass <tt>prepend: false</tt> to just add the + # verification callback in the position of the protect_from_forgery call. This means any callbacks added + # before are run first. # * <tt>:with</tt> - Set the method to handle unverified request. # # Valid unverified request handling methods are: @@ -100,9 +99,11 @@ module ActionController #:nodoc: # * <tt>:reset_session</tt> - Resets the session. # * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified. def protect_from_forgery(options = {}) + options = options.reverse_merge(prepend: true) + self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session) self.request_forgery_protection_token ||= :authenticity_token - prepend_before_action :verify_authenticity_token, options + before_action :verify_authenticity_token, options append_after_action :verify_same_origin_request end @@ -209,6 +210,7 @@ module ActionController #:nodoc: forgery_protection_strategy.new(self).handle_unverified_request end + #:nodoc: CROSS_ORIGIN_JAVASCRIPT_WARNING = "Security warning: an embedded " \ "<script> tag on another site requested protected JavaScript. " \ "If you know what you're doing, go ahead and disable forgery " \ @@ -273,7 +275,9 @@ module ActionController #:nodoc: # session token. Essentially the inverse of # +masked_authenticity_token+. def valid_authenticity_token?(session, encoded_masked_token) - return false if encoded_masked_token.nil? || encoded_masked_token.empty? + if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String) + return false + end begin masked_token = Base64.strict_decode64(encoded_masked_token) diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index a5ee1e2159..c98e937423 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -1,7 +1,6 @@ require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/string/filters' -require 'active_support/deprecation' require 'active_support/rescuable' require 'action_dispatch/http/upload' require 'stringio' @@ -92,7 +91,11 @@ module ActionController # params.permit(:c) # # => ActionController::UnpermittedParameters: found unpermitted keys: a, b # - # <tt>ActionController::Parameters</tt> is inherited from + # Please note that these options *are not thread-safe*. In a multi-threaded + # environment they should only be set once at boot-time and never mutated at + # runtime. + # + # <tt>ActionController::Parameters</tt> inherits from # <tt>ActiveSupport::HashWithIndifferentAccess</tt>, this means # that you can fetch values using either <tt>:key</tt> or <tt>"key"</tt>. # @@ -114,7 +117,7 @@ module ActionController self.always_permitted_parameters = %w( controller action ) def self.const_missing(const_name) - super unless const_name == :NEVER_UNPERMITTED_PARAMS + 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. @@ -163,6 +166,12 @@ module ActionController end end + # Returns an unsafe, unfiltered +Hash+ representation of this parameter. + def to_unsafe_h + to_hash + end + alias_method :to_unsafe_hash, :to_unsafe_h + # Convert all hashes in values into parameters, then yield each pair like # the same way as <tt>Hash#each_pair</tt> def each_pair(&block) @@ -259,7 +268,7 @@ module ActionController # # params.permit(:name) # - # +:name+ passes it is a key of +params+ whose associated value is of type + # +:name+ passes if it is a key of +params+ whose associated value is of type # +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+, # +Date+, +Time+, +DateTime+, +StringIO+, +IO+, # +ActionDispatch::Http::UploadedFile+ or +Rack::Test::UploadedFile+. diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb index dd8da4b5dc..d01927b7cb 100644 --- a/actionpack/lib/action_controller/metal/testing.rb +++ b/actionpack/lib/action_controller/metal/testing.rb @@ -24,7 +24,7 @@ module ActionController module ClassMethods def before_filters - _process_action_callbacks.find_all{|x| x.kind == :before}.map{|x| x.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 0f2fa5fb08..5a0e5c62e4 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -4,7 +4,10 @@ module ActionController # # In addition to <tt>AbstractController::UrlFor</tt>, this module accesses the HTTP layer to define # url options like the +host+. In order to do so, this module requires the host class - # to implement +env+ and +request+, which need to be a Rack-compatible. + # to implement +env+ which needs to be Rack-compatible and +request+ + # which is either an instance of +ActionDispatch::Request+ or an object + # that responds to the +host+, +optional_port+, +protocol+ and + # +symbolized_path_parameter+ methods. # # class RootUrl # include ActionController::UrlFor @@ -30,9 +33,9 @@ module ActionController :_recall => request.path_parameters }.merge!(super).freeze - if (same_origin = _routes.equal?(env["action_dispatch.routes".freeze])) || - (script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) || - (original_script_name = env['ORIGINAL_SCRIPT_NAME'.freeze]) + if (same_origin = _routes.equal?(request.routes)) || + (script_name = request.engine_script_name(_routes)) || + (original_script_name = request.original_script_name) options = @_url_options.dup if original_script_name |