diff options
Diffstat (limited to 'actionpack')
312 files changed, 2476 insertions, 837 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 51cec82801..e01f88e902 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,4 +1,129 @@ -* Add `action_controller_api` and `action_controller_base` load hooks to be called in `ActiveSupport.on_load` +* Make `assert_recognizes` to traverse mounted engines + + *Yuichiro Kaneko* + +* Remove deprecated `ActionController::ParamsParser::ParseError`. + + *Rafael Mendonça França* + +* Add `:allow_other_host` option to `redirect_back` method. + When `allow_other_host` is set to `false`, the `redirect_back` + will not allow a redirecting from a different host. + `allow_other_host` is `true` by default. + + *Tim Masliuchenko* + +* Add headless chrome support to System Tests. + + *Yuji Yaginuma* + +* Add ability to enable Early Hints for HTTP/2 + + If supported by the server, and enabled in Puma this allows H2 Early Hints to be used. + + The `javascript_include_tag` and the `stylesheet_link_tag` automatically add Early Hints if requested. + + *Eileen M. Uchitelle*, *Aaron Patterson* + +* Simplify cookies middleware with key rotation support + + Use the `rotate` method for both `MessageEncryptor` and + `MessageVerifier` to add key rotation support for encrypted and + signed cookies. This also helps simplify support for legacy cookie + security. + + *Michael J Coyne* + +* Use Capybara registered `:puma` server config. + + The Capybara registered `:puma` server ensures the puma server is run in process so + connection sharing and open request detection work correctly by default. + + *Thomas Walpole* + +* Cookies `:expires` option supports `ActiveSupport::Duration` object. + + cookies[:user_name] = { value: "assain", expires: 1.hour } + cookies[:key] = { value: "a yummy cookie", expires: 6.months } + + Pull Request: #30121 + + *Assain Jaleel* + +* Enforce signed/encrypted cookie expiry server side. + + Rails can thwart attacks by malicious clients that don't honor a cookie's expiry. + + It does so by stashing the expiry within the written cookie and relying on the + signing/encrypting to vouch that it hasn't been tampered with. Then on a + server-side read, the expiry is verified and any expired cookie is discarded. + + Pull Request: #30121 + + *Assain Jaleel* + +* Make `take_failed_screenshot` work within engine. + + Fixes #30405. + + *Yuji Yaginuma* + +* Deprecate `ActionDispatch::TestResponse` response aliases + + `#success?`, `#missing?` & `#error?` are not supported by the actual + `ActionDispatch::Response` object and can produce false-positives. Instead, + use the response helpers provided by `Rack::Response`. + + *Trevor Wistaff* + +* Protect from forgery by default + + Rather than protecting from forgery in the generated `ApplicationController`, + add it to `ActionController::Base` depending on + `config.action_controller.default_protect_from_forgery`. This configuration + defaults to false to support older versions which have removed it from their + `ApplicationController`, but is set to true for Rails 5.2. + + *Lisa Ugray* + +* Fallback `ActionController::Parameters#to_s` to `Hash#to_s`. + + *Kir Shatrov* + +* `driven_by` now registers poltergeist and capybara-webkit. + + If poltergeist or capybara-webkit are set as drivers is set for System Tests, + `driven_by` will register the driver and set additional options passed via + the `:options` parameter. + + Refer to the respective driver's documentation to see what options can be passed. + + *Mario Chavez* + +* AEAD encrypted cookies and sessions with GCM. + + Encrypted cookies now use AES-GCM which couples authentication and + encryption in one faster step and produces shorter ciphertexts. Cookies + encrypted using AES in CBC HMAC mode will be seamlessly upgraded when + this new mode is enabled via the + `action_dispatch.use_authenticated_cookie_encryption` configuration value. + + *Michael J Coyne* + +* Change the cache key format for fragments to make it easier to debug key churn. The new format is: + + views/template/action.html.erb:7a1156131a6928cb0026877f8b749ac9/projects/123 + ^template path ^template tree digest ^class ^id + + *DHH* + +* Add support for recyclable cache keys with fragment caching. This uses the new versioned entries in the + `ActiveSupport::Cache` stores and relies on the fact that Active Record has split `#cache_key` and `#cache_version` + to support it. + + *DHH* + +* Add `action_controller_api` and `action_controller_base` load hooks to be called in `ActiveSupport.on_load` `ActionController::Base` and `ActionController::API` have differing implementations. This means that the one umbrella hook `action_controller` is not able to address certain situations where a method @@ -10,4 +135,5 @@ *Julian Nadeau* + Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actionpack/CHANGELOG.md) for previous changes. diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc index 0720c66cb9..93b2a0932a 100644 --- a/actionpack/README.rdoc +++ b/actionpack/README.rdoc @@ -39,7 +39,7 @@ Source code can be downloaded as part of the Rails project on GitHub Action Pack is released under the MIT license: -* http://www.opensource.org/licenses/MIT +* https://opensource.org/licenses/MIT == Support diff --git a/actionpack/Rakefile b/actionpack/Rakefile index 31dd1865f9..4dd7c59ce8 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rake/testtask" test_files = Dir.glob("test/**/*_test.rb") @@ -26,7 +28,7 @@ namespace :test do end task :lines do - load File.expand_path("..", File.dirname(__FILE__)) + "/tools/line_statistics" + load File.expand_path("..", __dir__) + "/tools/line_statistics" files = FileList["lib/**/*.rb"] CodeTools::LineStatistics.new(files).print_loc end diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 2c24a54305..33d42e69d8 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -1,4 +1,6 @@ -version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip +# frozen_string_literal: true + +version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY @@ -19,10 +21,15 @@ Gem::Specification.new do |s| s.require_path = "lib" s.requirements << "none" + s.metadata = { + "source_code_uri" => "https://github.com/rails/rails/tree/v#{version}/actionpack", + "changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/actionpack/CHANGELOG.md" + } + s.add_dependency "activesupport", version s.add_dependency "rack", "~> 2.0" - s.add_dependency "rack-test", "~> 0.6.3" + s.add_dependency "rack-test", ">= 0.6.3" s.add_dependency "rails-html-sanitizer", "~> 1.0", ">= 1.0.2" s.add_dependency "rails-dom-testing", "~> 2.0" s.add_dependency "actionview", version diff --git a/actionpack/bin/test b/actionpack/bin/test index a7beb14b27..c53377cc97 100755 --- a/actionpack/bin/test +++ b/actionpack/bin/test @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true COMPONENT_ROOT = File.expand_path("..", __dir__) -require File.expand_path("../tools/test", COMPONENT_ROOT) +require_relative "../../tools/test" diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index 8bd965b198..0477e7f1c9 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_pack" require "active_support/rails" require "active_support/i18n" diff --git a/actionpack/lib/abstract_controller/asset_paths.rb b/actionpack/lib/abstract_controller/asset_paths.rb index e6170228d9..d6ee84b87b 100644 --- a/actionpack/lib/abstract_controller/asset_paths.rb +++ b/actionpack/lib/abstract_controller/asset_paths.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module AbstractController module AssetPaths #:nodoc: extend ActiveSupport::Concern diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index e7cb6347a2..a312af6715 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_controller/error" require "active_support/configurable" require "active_support/descendants_tracker" @@ -14,8 +16,16 @@ module AbstractController # expected to provide their own +render+ method, since rendering means # different things depending on the context. class Base + ## + # Returns the body of the HTTP response sent by the controller. attr_internal :response_body + + ## + # Returns the name of the action this controller is processing. attr_internal :action_name + + ## + # Returns the formats that can be processed by the controller. attr_internal :formats include ActiveSupport::Configurable @@ -170,8 +180,6 @@ module AbstractController # # ==== Parameters # * <tt>name</tt> - The name of an action to be tested - # - # :api: private def action_method?(name) self.class.action_methods.include?(name) end diff --git a/actionpack/lib/abstract_controller/caching.rb b/actionpack/lib/abstract_controller/caching.rb index 26e3f08bc1..ce6b757c3c 100644 --- a/actionpack/lib/abstract_controller/caching.rb +++ b/actionpack/lib/abstract_controller/caching.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module AbstractController module Caching extend ActiveSupport::Concern @@ -37,8 +39,7 @@ module AbstractController config_accessor :enable_fragment_cache_logging self.enable_fragment_cache_logging = false - class_attribute :_view_cache_dependencies - self._view_cache_dependencies = [] + class_attribute :_view_cache_dependencies, default: [] helper_method :view_cache_dependencies if respond_to?(:helper_method) end diff --git a/actionpack/lib/abstract_controller/caching/fragments.rb b/actionpack/lib/abstract_controller/caching/fragments.rb index c85b4adba1..f99b0830b2 100644 --- a/actionpack/lib/abstract_controller/caching/fragments.rb +++ b/actionpack/lib/abstract_controller/caching/fragments.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module AbstractController module Caching # Fragment caching is used for caching various blocks within @@ -25,7 +27,10 @@ module AbstractController self.fragment_cache_keys = [] - helper_method :fragment_cache_key if respond_to?(:helper_method) + if respond_to?(:helper_method) + helper_method :fragment_cache_key + helper_method :combined_fragment_cache_key + end end module ClassMethods @@ -62,17 +67,36 @@ module AbstractController # with the specified +key+ value. The key is expanded using # ActiveSupport::Cache.expand_cache_key. def fragment_cache_key(key) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Calling fragment_cache_key directly is deprecated and will be removed in Rails 6.0. + All fragment accessors now use the combined_fragment_cache_key method that retains the key as an array, + such that the caching stores can interrogate the parts for cache versions used in + recyclable cache keys. + MSG + 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 + # Given a key (as described in +expire_fragment+), returns + # a key array suitable for use in reading, writing, or expiring a + # cached fragment. All keys begin with <tt>:views</tt>, + # followed by ENV["RAILS_CACHE_ID"] or ENV["RAILS_APP_VERSION"] if set, + # followed by any controller-wide key prefix values, ending + # with the specified +key+ value. + def combined_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 + [ :views, (ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]), *head, *tail ].compact + 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) + key = combined_fragment_cache_key(key) instrument_fragment_cache :write_fragment, key do content = content.to_str cache_store.write(key, content, options) @@ -85,7 +109,7 @@ module AbstractController def read_fragment(key, options = nil) return unless cache_configured? - key = fragment_cache_key(key) + key = combined_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 @@ -96,7 +120,7 @@ module AbstractController # +key+ exists (see +expire_fragment+ for acceptable formats). def fragment_exist?(key, options = nil) return unless cache_configured? - key = fragment_cache_key(key) + key = combined_fragment_cache_key(key) instrument_fragment_cache :exist_fragment?, key do cache_store.exist?(key, options) @@ -123,7 +147,7 @@ module AbstractController # 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) + key = combined_fragment_cache_key(key) unless key.is_a?(Regexp) instrument_fragment_cache :expire_fragment, key do if key.is_a?(Regexp) @@ -135,8 +159,7 @@ module AbstractController end def instrument_fragment_cache(name, key) # :nodoc: - payload = instrument_payload(key) - ActiveSupport::Notifications.instrument("#{name}.#{instrument_name}", payload) { yield } + ActiveSupport::Notifications.instrument("#{name}.#{instrument_name}", instrument_payload(key)) { yield } end end end diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index ce4ecf17cc..146d17cf40 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -1,4 +1,26 @@ +# frozen_string_literal: true + module AbstractController + # = Abstract Controller Callbacks + # + # Abstract Controller provides hooks during the life cycle of a controller action. + # Callbacks allow you to trigger logic during this cycle. Available callbacks are: + # + # * <tt>after_action</tt> + # * <tt>append_after_action</tt> + # * <tt>append_around_action</tt> + # * <tt>append_before_action</tt> + # * <tt>around_action</tt> + # * <tt>before_action</tt> + # * <tt>prepend_after_action</tt> + # * <tt>prepend_around_action</tt> + # * <tt>prepend_before_action</tt> + # * <tt>skip_after_action</tt> + # * <tt>skip_around_action</tt> + # * <tt>skip_before_action</tt> + # + # NOTE: Calling the same callback multiple times will overwrite previous callback definitions. + # module Callbacks extend ActiveSupport::Concern @@ -9,12 +31,12 @@ module AbstractController included do define_callbacks :process_action, - terminator: ->(controller, result_lambda) { result_lambda.call if result_lambda.is_a?(Proc); controller.performed? }, + terminator: ->(controller, result_lambda) { result_lambda.call; controller.performed? }, skip_after_callbacks_if_terminated: true end - # Override AbstractController::Base's process_action to run the - # process_action callbacks around the normal behavior. + # Override <tt>AbstractController::Base#process_action</tt> to run the + # <tt>process_action</tt> callbacks around the normal behavior. def process_action(*args) run_callbacks(:process_action) do super diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb index 40ae5aa1ca..297ec5ca40 100644 --- a/actionpack/lib/abstract_controller/collector.rb +++ b/actionpack/lib/abstract_controller/collector.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_dispatch/http/mime_type" module AbstractController diff --git a/actionpack/lib/abstract_controller/error.rb b/actionpack/lib/abstract_controller/error.rb index 7fafce4dd4..89a54f072e 100644 --- a/actionpack/lib/abstract_controller/error.rb +++ b/actionpack/lib/abstract_controller/error.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module AbstractController class Error < StandardError #:nodoc: end diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index ef3be7af83..35b462bc92 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/dependencies" module AbstractController @@ -5,11 +7,8 @@ module AbstractController extend ActiveSupport::Concern included do - class_attribute :_helpers - self._helpers = Module.new - - class_attribute :_helper_methods - self._helper_methods = Array.new + class_attribute :_helpers, default: Module.new + class_attribute :_helper_methods, default: Array.new end class MissingHelperError < LoadError diff --git a/actionpack/lib/abstract_controller/logger.rb b/actionpack/lib/abstract_controller/logger.rb index c31ea6c5b5..8d0acc1b5c 100644 --- a/actionpack/lib/abstract_controller/logger.rb +++ b/actionpack/lib/abstract_controller/logger.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/benchmarkable" module AbstractController diff --git a/actionpack/lib/abstract_controller/railties/routes_helpers.rb b/actionpack/lib/abstract_controller/railties/routes_helpers.rb index 14b574e322..b6e5631a4e 100644 --- a/actionpack/lib/abstract_controller/railties/routes_helpers.rb +++ b/actionpack/lib/abstract_controller/railties/routes_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module AbstractController module Railties module RoutesHelpers diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 54af938a93..8ba2b25552 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_controller/error" require "action_view" require "action_view/view_paths" @@ -18,7 +20,6 @@ module AbstractController # Normalizes arguments, options and then delegates render_to_body and # sticks the result in <tt>self.response_body</tt>. - # :api: public def render(*args, &block) options = _normalize_render(*args, &block) rendered_body = render_to_body(options) @@ -40,19 +41,16 @@ module AbstractController # (as ActionController extends it to be anything that # responds to the method each), this method needs to be # overridden in order to still return a string. - # :api: plugin def render_to_string(*args, &block) options = _normalize_render(*args, &block) render_to_body(options) end # Performs the actual template rendering. - # :api: public def render_to_body(options = {}) end - # Returns Content-Type of rendered content - # :api: public + # Returns Content-Type of rendered content. def rendered_format Mime[:text] end @@ -63,7 +61,6 @@ module AbstractController # This method should return a hash with assigns. # You can overwrite this configuration per controller. - # :api: public def view_assigns protected_vars = _protected_ivars variables = instance_variables @@ -74,11 +71,11 @@ module AbstractController } end + private # Normalize args by converting <tt>render "foo"</tt> to # <tt>render :action => "foo"</tt> and <tt>render "foo/bar"</tt> to # <tt>render :file => "foo/bar"</tt>. - # :api: plugin - def _normalize_args(action = nil, options = {}) + def _normalize_args(action = nil, options = {}) # :doc: if action.respond_to?(:permitted?) if action.permitted? action @@ -93,20 +90,17 @@ module AbstractController end # Normalize options. - # :api: plugin - def _normalize_options(options) + def _normalize_options(options) # :doc: options end # Process extra options. - # :api: plugin - def _process_options(options) + def _process_options(options) # :doc: options end # Process the rendered format. - # :api: private - def _process_format(format) + def _process_format(format) # :nodoc: end def _process_variant(options) @@ -119,8 +113,7 @@ module AbstractController end # Normalize args and options. - # :api: private - def _normalize_render(*args, &block) + def _normalize_render(*args, &block) # :nodoc: options = _normalize_args(*args, &block) _process_variant(options) _normalize_options(options) diff --git a/actionpack/lib/abstract_controller/translation.rb b/actionpack/lib/abstract_controller/translation.rb index 9e3858802a..666e154e4c 100644 --- a/actionpack/lib/abstract_controller/translation.rb +++ b/actionpack/lib/abstract_controller/translation.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module AbstractController module Translation # Delegates to <tt>I18n.translate</tt>. Also aliased as <tt>t</tt>. @@ -13,7 +15,7 @@ module AbstractController path = controller_path.tr("/", ".") defaults = [:"#{path}#{key}"] defaults << options[:default] if options[:default] - options[:default] = defaults + options[:default] = defaults.flatten key = "#{path}.#{action_name}#{key}" end I18n.translate(key, options) diff --git a/actionpack/lib/abstract_controller/url_for.rb b/actionpack/lib/abstract_controller/url_for.rb index 72d07b0927..bd74c27d3b 100644 --- a/actionpack/lib/abstract_controller/url_for.rb +++ b/actionpack/lib/abstract_controller/url_for.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module AbstractController # Includes +url_for+ into the host class (e.g. an abstract controller or mailer). The class # has to provide a +RouteSet+ by implementing the <tt>_routes</tt> methods. Otherwise, an diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 50f20aa789..bd19b8cd5d 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/rails" require "abstract_controller" require "action_dispatch" diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb index 94698df730..b192e496de 100644 --- a/actionpack/lib/action_controller/api.rb +++ b/actionpack/lib/action_controller/api.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_view" require "action_controller" require "action_controller/log_subscriber" diff --git a/actionpack/lib/action_controller/api/api_rendering.rb b/actionpack/lib/action_controller/api/api_rendering.rb index 3a08d28c39..aca5265313 100644 --- a/actionpack/lib/action_controller/api/api_rendering.rb +++ b/actionpack/lib/action_controller/api/api_rendering.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionController module ApiRendering extend ActiveSupport::Concern diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 8c2b111f89..b73269871b 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_view" require "action_controller/log_subscriber" require "action_controller/metal/params_wrapper" diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index 954265ad97..97775d1dc8 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + 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. diff --git a/actionpack/lib/action_controller/form_builder.rb b/actionpack/lib/action_controller/form_builder.rb index f2656ca894..09d2ac1837 100644 --- a/actionpack/lib/action_controller/form_builder.rb +++ b/actionpack/lib/action_controller/form_builder.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionController # Override the default form builder for all views rendered by this # controller and any of its descendants. Accepts a subclass of diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index d29a5fe68f..14f41eb55f 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionController class LogSubscriber < ActiveSupport::LogSubscriber INTERNAL_PARAMS = %w(controller action format _method only_path) @@ -24,7 +26,7 @@ module ActionController exception_class_name = payload[:exception].first status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name) end - message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms" + message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms".dup message << " (#{additions.join(" | ".freeze)})" unless additions.empty? message << "\n\n" if defined?(Rails.env) && Rails.env.development? @@ -60,9 +62,9 @@ module ActionController class_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{method}(event) return unless logger.info? && ActionController::Base.enable_fragment_cache_logging - key_or_path = event.payload[:key] || event.payload[:path] + key = ActiveSupport::Cache.expand_cache_key(event.payload[:key] || event.payload[:path]) human_name = #{method.to_s.humanize.inspect} - info("\#{human_name} \#{key_or_path} (\#{event.duration.round(1)}ms)") + info("\#{human_name} \#{key} (\#{event.duration.round(1)}ms)") end METHOD end diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 246644dcbd..457884ea08 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/array/extract_options" require "action_dispatch/middleware/stack" require "action_dispatch/http/request" @@ -208,8 +210,7 @@ module ActionController @_request.reset_session end - class_attribute :middleware_stack - self.middleware_stack = ActionController::MiddlewareStack.new + class_attribute :middleware_stack, default: ActionController::MiddlewareStack.new def self.inherited(base) # :nodoc: base.middleware_stack = middleware_stack.dup diff --git a/actionpack/lib/action_controller/metal/basic_implicit_render.rb b/actionpack/lib/action_controller/metal/basic_implicit_render.rb index cef65a362c..2dc990f303 100644 --- a/actionpack/lib/action_controller/metal/basic_implicit_render.rb +++ b/actionpack/lib/action_controller/metal/basic_implicit_render.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionController module BasicImplicitRender # :nodoc: def send_action(method, *args) diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index eb636fa3f6..06b6a95ff8 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/hash/keys" module ActionController @@ -7,8 +9,7 @@ module ActionController include Head included do - class_attribute :etaggers - self.etaggers = [] + class_attribute :etaggers, default: [] end module ClassMethods @@ -227,7 +228,7 @@ module ActionController # expires_in 3.hours, public: true, must_revalidate: true # # This method will overwrite an existing Cache-Control header. - # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities. + # See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities. # # The method will also ensure an HTTP Date header for client compatibility. def expires_in(seconds, options = {}) diff --git a/actionpack/lib/action_controller/metal/cookies.rb b/actionpack/lib/action_controller/metal/cookies.rb index 44925641a1..ff46966693 100644 --- a/actionpack/lib/action_controller/metal/cookies.rb +++ b/actionpack/lib/action_controller/metal/cookies.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionController #:nodoc: module Cookies extend ActiveSupport::Concern diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb index 731e03e2fc..5a82ccf668 100644 --- a/actionpack/lib/action_controller/metal/data_streaming.rb +++ b/actionpack/lib/action_controller/metal/data_streaming.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_controller/metal/exceptions" module ActionController #:nodoc: @@ -54,14 +56,14 @@ module ActionController #:nodoc: # # Read about the other Content-* HTTP headers if you'd like to # provide the user with more information (such as Content-Description) in - # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11. + # https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11. # # Also be aware that the document may be cached by proxies and browsers. # The Pragma and Cache-Control headers declare how the file may be cached # by intermediaries. They default to require clients to validate with # the server before releasing cached responses. See - # http://www.mnot.net/cache_docs/ for an overview of web caching and - # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 + # https://www.mnot.net/cache_docs/ for an overview of web caching and + # https://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) && File.readable?(path) @@ -111,10 +113,10 @@ module ActionController #:nodoc: def send_file_headers!(options) type_provided = options.has_key?(:type) - self.content_type = DEFAULT_SEND_FILE_TYPE + content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE) + self.content_type = content_type response.sending_file = true - content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE) raise ArgumentError, ":type option required" if content_type.nil? if content_type.is_a?(Symbol) diff --git a/actionpack/lib/action_controller/metal/etag_with_flash.rb b/actionpack/lib/action_controller/metal/etag_with_flash.rb index 7bd338bd7c..38899e2f16 100644 --- a/actionpack/lib/action_controller/metal/etag_with_flash.rb +++ b/actionpack/lib/action_controller/metal/etag_with_flash.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + 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 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 798564db96..640c75536e 100644 --- a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb +++ b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionController # When our views change, they should bubble up into HTTP cache freshness # and bust browser caches. So the template digest for the current action @@ -22,8 +24,7 @@ module ActionController include ActionController::ConditionalGet included do - class_attribute :etag_with_template_digest - self.etag_with_template_digest = true + class_attribute :etag_with_template_digest, default: true ActiveSupport.on_load :action_view, yield: true do etag do |options| diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb index 175dd9eb9e..a65857d6ef 100644 --- a/actionpack/lib/action_controller/metal/exceptions.rb +++ b/actionpack/lib/action_controller/metal/exceptions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionController class ActionControllerError < StandardError #:nodoc: end @@ -32,9 +34,6 @@ module ActionController class NotImplemented < MethodNotAllowed #:nodoc: end - class UnknownController < ActionControllerError #:nodoc: - end - class MissingFile < ActionControllerError #:nodoc: end diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb index 347fbf0e74..5115c2fadf 100644 --- a/actionpack/lib/action_controller/metal/flash.rb +++ b/actionpack/lib/action_controller/metal/flash.rb @@ -1,10 +1,11 @@ +# frozen_string_literal: true + module ActionController #:nodoc: module Flash extend ActiveSupport::Concern included do - class_attribute :_flash_types, instance_accessor: false - self._flash_types = [] + class_attribute :_flash_types, instance_accessor: false, default: [] delegate :flash, to: :request add_flash_types(:alert, :notice) diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb index 73e67573ca..0ba1f9f783 100644 --- a/actionpack/lib/action_controller/metal/force_ssl.rb +++ b/actionpack/lib/action_controller/metal/force_ssl.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/hash/except" require "active_support/core_ext/hash/slice" diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index 0c50894bce..bac9bc5e5f 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionController module Head # Returns a response that has no content (merely headers). The options diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index 476d081239..22c84e440b 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionController # The \Rails framework provides a large number of helpers for working with assets, dates, forms, # numbers and model objects, to name a few. These helpers are available to all templates @@ -53,9 +55,8 @@ module ActionController include AbstractController::Helpers included do - class_attribute :helpers_path, :include_all_helpers - self.helpers_path ||= [] - self.include_all_helpers = true + class_attribute :helpers_path, default: [] + class_attribute :include_all_helpers, default: true end module ClassMethods diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index d8bc895265..0c8132684a 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "base64" require "active_support/security_utils" @@ -246,7 +248,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 @@ -475,7 +477,7 @@ 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] || "".dup).gsub! %r/^"|"$/, "" } end # This method takes an authorization body and splits up the key-value diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb index eeb27f99f4..ac0c127cdc 100644 --- a/actionpack/lib/action_controller/metal/implicit_render.rb +++ b/actionpack/lib/action_controller/metal/implicit_render.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionController # Handles implicit rendering for a controller action that does not # explicitly respond with +render+, +respond_to+, +redirect+, or +head+. diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index 2485d27cec..be9449629f 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "benchmark" require "abstract_controller/logger" @@ -81,16 +83,13 @@ module ActionController # def cleanup_view_runtime # super - time_taken_in_something_expensive # end - # - # :api: plugin - def cleanup_view_runtime + def cleanup_view_runtime # :doc: 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) + def append_info_to_payload(payload) # :doc: payload[:view_runtime] = view_runtime end @@ -98,7 +97,6 @@ module ActionController # A hook which allows other frameworks to log what happened during # controller process action. This method should return an array # with the messages to be added. - # :api: plugin def log_process_action(payload) #:nodoc: messages, view_runtime = [], payload[:view_runtime] messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index a607ee2309..2f4c8fb83c 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_dispatch/http/response" require "delegate" require "active_support/json" @@ -295,7 +297,7 @@ module ActionController return unless logger logger.fatal do - message = "\n#{exception.class} (#{exception.message}):\n" + message = "\n#{exception.class} (#{exception.message}):\n".dup message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) message << " " << exception.backtrace.join("\n ") "#{message}\n\n" diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 96bd548268..2233b93406 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_controller/collector" module ActionController #:nodoc: diff --git a/actionpack/lib/action_controller/metal/parameter_encoding.rb b/actionpack/lib/action_controller/metal/parameter_encoding.rb index ecc691619e..7a45732d31 100644 --- a/actionpack/lib/action_controller/metal/parameter_encoding.rb +++ b/actionpack/lib/action_controller/metal/parameter_encoding.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionController # Specify binary encoding for parameters for a given action. module ParameterEncoding diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index a89fc1678b..a678377d4f 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/hash/slice" require "active_support/core_ext/hash/except" require "active_support/core_ext/module/anonymous" @@ -110,6 +112,14 @@ module ActionController else self.include = m.attribute_names end + + if m.respond_to?(:nested_attributes_options) && m.nested_attributes_options.keys.any? + self.include += m.nested_attributes_options.keys.map do |key| + key.to_s.concat("_attributes") + end + end + + self.include end end end @@ -159,8 +169,7 @@ module ActionController end included do - class_attribute :_wrapper_options - self._wrapper_options = Options.from_hash(format: []) + class_attribute :_wrapper_options, default: Options.from_hash(format: []) end module ClassMethods @@ -233,12 +242,7 @@ module ActionController # by the metal call stack. def process_action(*args) if _wrapper_enabled? - if request.parameters[_wrapper_key].present? - wrapped_hash = _extract_parameters(request.parameters) - else - wrapped_hash = _wrap_parameters request.request_parameters - end - + wrapped_hash = _wrap_parameters request.request_parameters wrapped_keys = request.request_parameters.keys wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys) @@ -283,7 +287,7 @@ module ActionController return false unless request.has_content_type? ref = request.content_mime_type.ref - _wrapper_formats.include?(ref) && _wrapper_key && !request.request_parameters[_wrapper_key] + _wrapper_formats.include?(ref) && _wrapper_key && !request.parameters.key?(_wrapper_key) end end end diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index fdfe82f96b..8de57f9199 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionController module Redirecting extend ActiveSupport::Concern @@ -29,7 +31,7 @@ module ActionController # redirect_to post_url(@post), status: 301 # redirect_to action: 'atom', status: 302 # - # The status code can either be a standard {HTTP Status code}[http://www.iana.org/assignments/http-status-codes] as an + # The status code can either be a standard {HTTP Status code}[https://www.iana.org/assignments/http-status-codes] as an # integer, or a symbol representing the downcased, underscored and symbolized description. # Note that the status code must be a 3xx HTTP code, or redirection will not occur. # @@ -77,15 +79,18 @@ module ActionController # redirect_back fallback_location: "/images/screenshot.jpg" # redirect_back fallback_location: posts_url # redirect_back fallback_location: proc { edit_post_url(@post) } + # redirect_back fallback_location: '/', allow_other_host: false + # + # ==== Options + # * <tt>:fallback_location</tt> - The default fallback location that will be used on missing `Referer` header. + # * <tt>:allow_other_host</tt> - Allows or disallow redirection to the host that is different to the current host # - # All options that can be passed to <tt>redirect_to</tt> are accepted as + # All other options that can be passed to <tt>redirect_to</tt> are accepted as # options and the behavior is identical. - def redirect_back(fallback_location:, **args) - if referer = request.headers["Referer"] - redirect_to referer, **args - else - redirect_to fallback_location, **args - end + def redirect_back(fallback_location:, allow_other_host: true, **args) + referer = request.headers["Referer"] + redirect_to_referer = referer && (allow_other_host || _url_host_allowed?(referer)) + redirect_to redirect_to_referer ? referer : fallback_location, **args end def _compute_redirect_to_location(request, options) #:nodoc: @@ -93,7 +98,7 @@ module ActionController # The scheme name consist of a letter followed by any combination of # letters, digits, and the plus ("+"), period ("."), or hyphen ("-") # characters; and is terminated by a colon (":"). - # See http://tools.ietf.org/html/rfc3986#section-3.1 + # See https://tools.ietf.org/html/rfc3986#section-3.1 # The protocol relative scheme starts with a double slash "//". when /\A([a-z][a-z\d\-+\.]*:|\/\/).*/i options @@ -118,5 +123,11 @@ module ActionController 302 end end + + def _url_host_allowed?(url) + URI(url.to_s).host == request.host + rescue ArgumentError, URI::Error + false + end end end diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index 733aca195d..b81d3ef539 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "set" module ActionController @@ -26,8 +28,7 @@ module ActionController RENDERERS = Set.new included do - class_attribute :_renderers - self._renderers = Set.new.freeze + class_attribute :_renderers, default: Set.new.freeze end # Used in <tt>ActionController::Base</tt> @@ -84,7 +85,7 @@ module ActionController def self.remove(key) RENDERERS.delete(key.to_sym) method_name = _render_with_renderer_method_name(key) - remove_method(method_name) if method_defined?(method_name) + remove_possible_method(method_name) end def self._render_with_renderer_method_name(key) diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 67f207afc2..6d181e6456 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -1,4 +1,4 @@ -require "active_support/core_ext/string/filters" +# frozen_string_literal: true module ActionController module Rendering @@ -40,7 +40,7 @@ module ActionController def render_to_string(*) result = super if result.respond_to?(:each) - string = "" + string = "".dup result.each { |r| string << r } string else diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 5051c02a62..bd133f24a1 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rack/session/abstract/id" require "action_controller/metal/exceptions" require "active_support/security_utils" @@ -20,7 +22,7 @@ module ActionController #:nodoc: # Since HTML and JavaScript requests are typically made from the browser, we # need to ensure to verify request authenticity for the web browser. We can # use session-oriented authentication for these types of requests, by using - # the `protect_from_forgery` method in our controllers. + # the <tt>protect_from_forgery</tt> method in our controllers. # # GET requests are not protected since they don't have side effects like writing # to the database and don't leak sensitive information. JavaScript requests are @@ -85,6 +87,10 @@ module ActionController #:nodoc: config_accessor :per_form_csrf_tokens self.per_form_csrf_tokens = false + # Controls whether forgery protection is enabled by default. + config_accessor :default_protect_from_forgery + self.default_protect_from_forgery = false + helper_method :form_authenticity_token helper_method :protect_against_forgery? end @@ -128,6 +134,15 @@ module ActionController #:nodoc: append_after_action :verify_same_origin_request end + # Turn off request forgery protection. This is a wrapper for: + # + # skip_before_action :verify_authenticity_token + # + # See +skip_before_action+ for allowed options. + def skip_forgery_protection(options = {}) + skip_before_action :verify_authenticity_token, options + end + private def protection_method_class(name) @@ -233,6 +248,7 @@ module ActionController #:nodoc: "If you know what you're doing, go ahead and disable forgery " \ "protection on this action to permit cross-origin JavaScript embedding." private_constant :CROSS_ORIGIN_JAVASCRIPT_WARNING + # :startdoc: # If `verify_authenticity_token` was run (indicating that we have # forgery protection enabled for this request) then also verify that diff --git a/actionpack/lib/action_controller/metal/rescue.rb b/actionpack/lib/action_controller/metal/rescue.rb index 25757938f5..44f7fb7a07 100644 --- a/actionpack/lib/action_controller/metal/rescue.rb +++ b/actionpack/lib/action_controller/metal/rescue.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + module ActionController #:nodoc: - # This module is responsible for providing `rescue_from` helpers + # This module is responsible for providing +rescue_from+ helpers # to controllers and configuring when detailed exceptions must be # shown. module Rescue @@ -8,8 +10,8 @@ module ActionController #:nodoc: # 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 - # false, but someone may set it to `request.local?` so local + # +consider_all_requests_local+ is +false+. By default, it returns + # +false+, but someone may set it to <tt>request.local?</tt> so local # requests in production still show the detailed exception pages. def show_detailed_exceptions? false diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 58cf60ad2a..0b1598bf1b 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rack/chunked" module ActionController #:nodoc: diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 7864f9decd..ef7c4c4c16 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/hash/indifferent_access" require "active_support/core_ext/hash/transform_values" require "active_support/core_ext/array/wrap" @@ -119,8 +121,7 @@ module ActionController # params[:key] # => "value" # params["key"] # => "value" class Parameters - cattr_accessor :permit_all_parameters, instance_accessor: false - self.permit_all_parameters = false + cattr_accessor :permit_all_parameters, instance_accessor: false, default: false cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false @@ -181,6 +182,14 @@ module ActionController # Returns a new array of the keys of the parameters. ## + # :method: to_s + # + # :call-seq: + # to_s() + # + # Returns the content of the parameters as a string. + + ## # :method: value? # # :call-seq: @@ -196,7 +205,7 @@ module ActionController # # Returns a new array of the values of the parameters. delegate :keys, :key?, :has_key?, :values, :has_value?, :value?, :empty?, :include?, - :as_json, to: :@parameters + :as_json, :to_s, to: :@parameters # By default, never raise an UnpermittedParameters exception if these # params are present. The default includes both 'controller' and 'action' @@ -205,8 +214,7 @@ module ActionController # config. For instance: # # config.always_permitted_parameters = %w( controller action format ) - cattr_accessor :always_permitted_parameters - self.always_permitted_parameters = %w( controller action ) + cattr_accessor :always_permitted_parameters, default: %w( controller action ) # Returns a new instance of <tt>ActionController::Parameters</tt>. # Also, sets the +permitted+ attribute to the default value of @@ -247,7 +255,7 @@ module ActionController # oddity: "Heavy stone crab" # }) # params.to_h - # # => ActionController::UnfilteredParameters: unable to convert unfiltered parameters to hash + # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash # # safe_params = params.permit(:name) # safe_params.to_h # => {"name"=>"Senjougahara Hitagi"} @@ -267,7 +275,7 @@ module ActionController # oddity: "Heavy stone crab" # }) # params.to_hash - # # => ActionController::UnfilteredParameters: unable to convert unfiltered parameters to hash + # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash # # safe_params = params.permit(:name) # safe_params.to_hash # => {"name"=>"Senjougahara Hitagi"} @@ -283,6 +291,10 @@ module ActionController # nationality: "Danish" # }) # params.to_query + # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash + # + # safe_params = params.permit(:name, :nationality) + # safe_params.to_query # # => "name=David&nationality=Danish" # # An optional namespace can be passed to enclose key names: @@ -291,7 +303,8 @@ module ActionController # name: "David", # nationality: "Danish" # }) - # params.to_query("user") + # safe_params = params.permit(:name, :nationality) + # safe_params.to_query("user") # # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish" # # The string pairs "key=value" that conform the query string @@ -662,12 +675,12 @@ module ActionController self end - # Deletes and returns a key-value pair from +Parameters+ whose key is equal - # to key. If the key is not found, returns the default value. If the - # optional code block is given and the key is not found, pass in the key - # and return the result of block. - def delete(key) - convert_value_to_parameters(@parameters.delete(key)) + # Deletes a key-value pair from +Parameters+ and returns the value. If + # +key+ is not found, returns +nil+ (or, with optional code block, yields + # +key+ and returns the result). Cf. +#extract!+, which returns the + # corresponding +ActionController::Parameters+ object. + def delete(key, &block) + convert_value_to_parameters(@parameters.delete(key, &block)) end # Returns a new instance of <tt>ActionController::Parameters</tt> with only diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb index 9bb416178a..6e8a95040f 100644 --- a/actionpack/lib/action_controller/metal/testing.rb +++ b/actionpack/lib/action_controller/metal/testing.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionController module Testing extend ActiveSupport::Concern @@ -10,11 +12,5 @@ module ActionController self.params = nil end end - - module ClassMethods - def before_filters - _process_action_callbacks.find_all { |x| x.kind == :before }.map(&:name) - end - end end end diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index 21ed5b4ec8..84dbb59a63 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionController # Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing # the <tt>_routes</tt> method. Otherwise, an exception will be raised. diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index fadfc8de60..7d42f5d931 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails" require "action_controller" require "action_dispatch/railtie" @@ -22,13 +24,15 @@ module ActionController initializer "action_controller.parameters_config" do |app| options = app.config.action_controller - ActionController::Parameters.permit_all_parameters = options.delete(:permit_all_parameters) { false } - if app.config.action_controller[:always_permitted_parameters] - ActionController::Parameters.always_permitted_parameters = - app.config.action_controller.delete(:always_permitted_parameters) - end - ActionController::Parameters.action_on_unpermitted_parameters = options.delete(:action_on_unpermitted_parameters) do - (Rails.env.test? || Rails.env.development?) ? :log : false + ActiveSupport.on_load(:action_controller, run_once: true) do + ActionController::Parameters.permit_all_parameters = options.delete(:permit_all_parameters) { false } + if app.config.action_controller[:always_permitted_parameters] + ActionController::Parameters.always_permitted_parameters = + app.config.action_controller.delete(:always_permitted_parameters) + end + ActionController::Parameters.action_on_unpermitted_parameters = options.delete(:action_on_unpermitted_parameters) do + (Rails.env.test? || Rails.env.development?) ? :log : false + end end end @@ -67,5 +71,19 @@ module ActionController config.compile_methods! if config.respond_to?(:compile_methods!) end end + + initializer "action_controller.request_forgery_protection" do |app| + ActiveSupport.on_load(:action_controller_base) do + if app.config.action_controller.default_protect_from_forgery + protect_from_forgery with: :exception + end + end + end + + initializer "action_controller.eager_load_actions" do + ActiveSupport.on_load(:after_initialize) do + ActionController::Metal.descendants.each(&:action_methods) if config.eager_load + end + end end end diff --git a/actionpack/lib/action_controller/railties/helpers.rb b/actionpack/lib/action_controller/railties/helpers.rb index 3985c6b273..fa746fa9e8 100644 --- a/actionpack/lib/action_controller/railties/helpers.rb +++ b/actionpack/lib/action_controller/railties/helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionController module Railties module Helpers diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb index cbb719d8b2..49c5b782f0 100644 --- a/actionpack/lib/action_controller/renderer.rb +++ b/actionpack/lib/action_controller/renderer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/hash/keys" module ActionController diff --git a/actionpack/lib/action_controller/template_assertions.rb b/actionpack/lib/action_controller/template_assertions.rb index 0179f4afcd..dd83c1a283 100644 --- a/actionpack/lib/action_controller/template_assertions.rb +++ b/actionpack/lib/action_controller/template_assertions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionController module TemplateAssertions def assert_template(options = {}, message = nil) diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index bc42d50205..4b408750a4 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -1,7 +1,10 @@ +# frozen_string_literal: true + 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/module/redefine_method" require "active_support/core_ext/hash/keys" require "active_support/testing/constant_lookup" require "action_controller/template_assertions" @@ -17,7 +20,7 @@ module ActionController # the database on the main thread, so they could open a txn, then the # controller thread will open a new connection and try to access data # that's only visible to the main thread's txn. This is the problem in #23483. - remove_method :new_controller_thread + silence_redefinition_of_method :new_controller_thread def new_controller_thread # :nodoc: yield end diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 303790e96d..34937f3229 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- # Copyright (c) 2004-2017 David Heinemeier Hansson # diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index 985e0fb972..3328ce17a0 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch module Http module Cache @@ -95,7 +97,7 @@ module ActionDispatch # support strong ETags and will ignore weak ETags entirely. # # Weak ETags are what we almost always need, so they're the default. - # Check out `#strong_etag=` to provide a strong ETag validator. + # Check out #strong_etag= to provide a strong ETag validator. def etag=(weak_validators) self.weak_etag = weak_validators end @@ -164,19 +166,23 @@ module ActionDispatch @cache_control = cache_control_headers end - def handle_conditional_get! - if etag? || last_modified? || !@cache_control.empty? - set_conditional_cache_control!(@cache_control) - end - end - DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze NO_CACHE = "no-cache".freeze PUBLIC = "public".freeze PRIVATE = "private".freeze MUST_REVALIDATE = "must-revalidate".freeze - def set_conditional_cache_control!(cache_control) + def handle_conditional_get! + # Normally default cache control setting is handled by ETag + # middleware. But, if an etag is already set, the middleware + # defaults to `no-cache` unless a default `Cache-Control` value is + # previously set. So, set a default one here. + if (etag? || last_modified?) && !self._cache_control + self._cache_control = DEFAULT_CACHE_CONTROL + end + end + + def merge_and_normalize_cache_control!(cache_control) control = {} cc_headers = cache_control_headers if extras = cc_headers.delete(:extras) @@ -189,7 +195,7 @@ module ActionDispatch control.merge! cache_control if control.empty? - self._cache_control = DEFAULT_CACHE_CONTROL + # Let middleware handle default behavior elsif control[:no_cache] self._cache_control = NO_CACHE if control[:extras] diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index e584b84d92..ec86b8bc47 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_dispatch/http/parameter_filter" module ActionDispatch @@ -7,7 +9,7 @@ module ActionDispatch # sub-hashes of the params hash to filter. Filtering only certain sub-keys # from a hash is possible by using the dot notation: 'credit_card.number'. # If a block is given, each key and value of the params hash and all - # sub-hashes is passed to it, the value or key can be replaced using + # sub-hashes is passed to it, where the value or the key can be replaced using # String#replace or similar method. # # env["action_dispatch.parameter_filter"] = [:password] @@ -46,7 +48,7 @@ module ActionDispatch @filtered_env ||= env_filter.filter(@env) end - # Reconstructed a path with all sensitive GET parameters replaced. + # Reconstructs a path with all sensitive GET parameters replaced. def filtered_path @filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}" end @@ -74,7 +76,7 @@ module ActionDispatch PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})} def filtered_query_string # :doc: query_string.gsub(PAIR_RE) do |_| - parameter_filter.filter([[$1, $2]]).first.join("=") + parameter_filter.filter($1 => $2).first.join("=") end end end diff --git a/actionpack/lib/action_dispatch/http/filter_redirect.rb b/actionpack/lib/action_dispatch/http/filter_redirect.rb index fc3c44582a..25394fe5dd 100644 --- a/actionpack/lib/action_dispatch/http/filter_redirect.rb +++ b/actionpack/lib/action_dispatch/http/filter_redirect.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch module Http module FilterRedirect diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb index 3c03976f03..c3c2a9d8c5 100644 --- a/actionpack/lib/action_dispatch/http/headers.rb +++ b/actionpack/lib/action_dispatch/http/headers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch module Http # Provides access to the request's HTTP headers from the environment. diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 19f89edbc1..d7435fa8df 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/module/attribute_accessors" module ActionDispatch @@ -6,14 +8,10 @@ module ActionDispatch extend ActiveSupport::Concern included do - mattr_accessor :ignore_accept_header - self.ignore_accept_header = false + mattr_accessor :ignore_accept_header, default: false end # The MIME type of the HTTP request, such as Mime[:xml]. - # - # For backward compatibility, the post \format is extracted from the - # X-Post-Data-Format HTTP header if present. def content_mime_type fetch_header("action_dispatch.request.content_type") do |k| v = if get_header("CONTENT_TYPE") =~ /^([^,\;]*)/ diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 0cf010f59e..d2b2106845 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # -*- frozen-string-literal: true -*- require "singleton" @@ -326,11 +328,11 @@ module Mime def ref; end - def respond_to_missing?(method, include_private = false) - method.to_s.ends_with? "?" - end - private + def respond_to_missing?(method, _) + method.to_s.ends_with? "?" + end + def method_missing(method, *args) false if method.to_s.ends_with? "?" end diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb index 8b04174f1f..f8e6fca36d 100644 --- a/actionpack/lib/action_dispatch/http/mime_types.rb +++ b/actionpack/lib/action_dispatch/http/mime_types.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + # Build list of Mime types for HTTP responses -# http://www.iana.org/assignments/media-types/ +# https://www.iana.org/assignments/media-types/ Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml ) Mime::Type.register "text/plain", :text, [], %w(txt) @@ -26,7 +28,7 @@ Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml ), %w(yml yaml) Mime::Type.register "multipart/form-data", :multipart_form Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form -# http://www.ietf.org/rfc/rfc4627.txt +# https://www.ietf.org/rfc/rfc4627.txt # http://www.json.org/JSONRequest.html Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest ) diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb index 889f55a52a..1d58964862 100644 --- a/actionpack/lib/action_dispatch/http/parameter_filter.rb +++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/object/duplicable" module ActionDispatch @@ -54,7 +56,7 @@ module ActionDispatch end def call(original_params, parents = []) - filtered_params = {} + filtered_params = original_params.class.new original_params.each do |key, value| parents.push(key) if deep_regexps diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 79a2ef965b..8d7431fd6b 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch module Http module Parameters @@ -55,7 +57,7 @@ module ActionDispatch query_parameters.dup end params.merge!(path_parameters) - params = set_binary_encoding(params) + params = set_binary_encoding(params, params[:controller], params[:action]) set_header("action_dispatch.request.parameters", params) params end @@ -64,6 +66,7 @@ module ActionDispatch def path_parameters=(parameters) #:nodoc: delete_header("action_dispatch.request.parameters") + parameters = set_binary_encoding(parameters, parameters[:controller], parameters[:action]) # If any of the path parameters has an invalid encoding then # raise since it's likely to trigger errors further on. Request::Utils.check_param_encoding(parameters) @@ -83,9 +86,10 @@ module ActionDispatch private - def set_binary_encoding(params) - action = params[:action] - if controller_class.binary_params_for?(action) + def set_binary_encoding(params, controller, action) + return params unless controller && controller.valid_encoding? + + if binary_params_for?(controller, action) ActionDispatch::Request::Utils.each_param_value(params) do |param| param.force_encoding ::Encoding::ASCII_8BIT end @@ -93,6 +97,12 @@ module ActionDispatch params end + def binary_params_for?(controller, action) + controller_class_for(controller).binary_params_for?(action) + rescue NameError + false + end + def parse_formatted_parameters(parsers) return yield if content_length.zero? || content_mime_type.nil? @@ -113,9 +123,4 @@ module ActionDispatch end end end - - module ParamsParser - include ActiveSupport::Deprecation::DeprecatedConstantAccessor - deprecate_constant "ParseError", "ActionDispatch::Http::Parameters::ParseError" - end end diff --git a/actionpack/lib/action_dispatch/http/rack_cache.rb b/actionpack/lib/action_dispatch/http/rack_cache.rb index 003ae4029d..3e2d01aea3 100644 --- a/actionpack/lib/action_dispatch/http/rack_cache.rb +++ b/actionpack/lib/action_dispatch/http/rack_cache.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rack/cache" require "rack/cache/context" require "active_support/cache" diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 6d42404a98..d631281e4b 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "stringio" require "active_support/inflector" @@ -74,10 +76,13 @@ module ActionDispatch def controller_class params = path_parameters + params[:action] ||= "index" + controller_class_for(params[:controller]) + end - if params.key?(:controller) - controller_param = params[:controller].underscore - params[:action] ||= "index" + def controller_class_for(name) + if name + controller_param = name.underscore const_name = "#{controller_param.camelize}Controller" ActiveSupport::Dependencies.constantize(const_name) else @@ -93,14 +98,14 @@ module ActionDispatch end # List of HTTP request methods from the following RFCs: - # Hypertext Transfer Protocol -- HTTP/1.1 (http://www.ietf.org/rfc/rfc2616.txt) - # HTTP Extensions for Distributed Authoring -- WEBDAV (http://www.ietf.org/rfc/rfc2518.txt) - # Versioning Extensions to WebDAV (http://www.ietf.org/rfc/rfc3253.txt) - # Ordered Collections Protocol (WebDAV) (http://www.ietf.org/rfc/rfc3648.txt) - # Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol (http://www.ietf.org/rfc/rfc3744.txt) - # Web Distributed Authoring and Versioning (WebDAV) SEARCH (http://www.ietf.org/rfc/rfc5323.txt) - # Calendar Extensions to WebDAV (http://www.ietf.org/rfc/rfc4791.txt) - # PATCH Method for HTTP (http://www.ietf.org/rfc/rfc5789.txt) + # Hypertext Transfer Protocol -- HTTP/1.1 (https://www.ietf.org/rfc/rfc2616.txt) + # HTTP Extensions for Distributed Authoring -- WEBDAV (https://www.ietf.org/rfc/rfc2518.txt) + # Versioning Extensions to WebDAV (https://www.ietf.org/rfc/rfc3253.txt) + # Ordered Collections Protocol (WebDAV) (https://www.ietf.org/rfc/rfc3648.txt) + # Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol (https://www.ietf.org/rfc/rfc3744.txt) + # Web Distributed Authoring and Versioning (WebDAV) SEARCH (https://www.ietf.org/rfc/rfc5323.txt) + # Calendar Extensions to WebDAV (https://www.ietf.org/rfc/rfc4791.txt) + # PATCH Method for HTTP (https://www.ietf.org/rfc/rfc5789.txt) RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT) RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK) RFC3253 = %w(VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL MKACTIVITY) @@ -194,6 +199,23 @@ module ActionDispatch @headers ||= Http::Headers.new(self) end + # Early Hints is an HTTP/2 status code that indicates hints to help a client start + # making preparations for processing the final response. + # + # If the env contains +rack.early_hints+ then the server accepts HTTP2 push for Link headers. + # + # The +send_early_hints+ method accepts a hash of links as follows: + # + # send_early_hints("Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload") + # + # If you are using +javascript_include_tag+ or +stylesheet_link_tag+ the + # Early Hints headers are included by default if supported. + def send_early_hints(links) + return unless env["rack.early_hints"] + + env["rack.early_hints"].call(links) + end + # Returns a +String+ with the last requested path including their params. # # # get '/foo' @@ -298,7 +320,7 @@ module ActionDispatch # variable is already set, wrap it in a StringIO. def body if raw_post = get_header("RAW_POST_DATA") - raw_post.force_encoding(Encoding::BINARY) + raw_post = raw_post.dup.force_encoding(Encoding::BINARY) StringIO.new(raw_post) else body_stream diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 2ee52eeb48..7e50cb6d23 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/module/attribute_accessors" require "action_dispatch/http/filter_redirect" require "action_dispatch/http/cache" @@ -81,8 +83,8 @@ module ActionDispatch # :nodoc: LOCATION = "Location".freeze NO_CONTENT_CODES = [100, 101, 102, 204, 205, 304] - cattr_accessor(:default_charset) { "utf-8" } - cattr_accessor(:default_headers) + cattr_accessor :default_charset, default: "utf-8" + cattr_accessor :default_headers include Rack::Response::Helpers # Aliasing these off because AD::Http::Cache::Response defines them. @@ -103,7 +105,7 @@ module ActionDispatch # :nodoc: def body @str_body ||= begin - buf = "" + buf = "".dup each { |chunk| buf << chunk } buf end @@ -252,16 +254,15 @@ module ActionDispatch # :nodoc: end # Sets the HTTP character set. In case of +nil+ parameter - # it sets the charset to utf-8. + # it sets the charset to +default_charset+. # # response.charset = 'utf-16' # => 'utf-16' # response.charset = nil # => 'utf-8' def charset=(charset) - header_info = parsed_content_type_header + content_type = parsed_content_type_header.mime_type if false == charset - set_header CONTENT_TYPE, header_info.mime_type + set_content_type content_type, nil else - content_type = header_info.mime_type set_content_type content_type, charset || self.class.default_charset end end @@ -432,6 +433,7 @@ module ActionDispatch # :nodoc: def before_committed return if committed? assign_default_content_type_and_charset! + merge_and_normalize_cache_control!(@cache_control) handle_conditional_get! handle_no_content! end diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index 61ba052e45..0b162dc7f1 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch module Http # Models uploaded files. @@ -27,14 +29,18 @@ module ActionDispatch @tempfile = hash[:tempfile] raise(ArgumentError, ":tempfile is required") unless @tempfile - @original_filename = hash[:filename] - if @original_filename + if hash[:filename] + @original_filename = hash[:filename].dup + begin @original_filename.encode!(Encoding::UTF_8) rescue EncodingError @original_filename.force_encoding(Encoding::UTF_8) end + else + @original_filename = nil end + @content_type = hash[:type] @headers = hash[:head] end diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index b6be48a48b..f0344fd927 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/module/attribute_accessors" module ActionDispatch @@ -7,8 +9,7 @@ module ActionDispatch HOST_REGEXP = /(^[^:]+:\/\/)?(\[[^\]]+\]|[^:]+)(?::(\d+$))?/ PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/ - mattr_accessor :tld_length - self.tld_length = 1 + mattr_accessor :tld_length, default: 1 class << self # Returns the domain part of a host given the domain level. @@ -156,7 +157,7 @@ module ActionDispatch subdomain = options.fetch :subdomain, true domain = options[:domain] - host = "" + host = "".dup if subdomain == true return _host if domain.nil? diff --git a/actionpack/lib/action_dispatch/journey.rb b/actionpack/lib/action_dispatch/journey.rb index d1cfc51f3e..2852efa6ae 100644 --- a/actionpack/lib/action_dispatch/journey.rb +++ b/actionpack/lib/action_dispatch/journey.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_dispatch/journey/router" require "action_dispatch/journey/gtg/builder" require "action_dispatch/journey/gtg/simulator" diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb index 326f4e52f9..0f04839d9b 100644 --- a/actionpack/lib/action_dispatch/journey/formatter.rb +++ b/actionpack/lib/action_dispatch/journey/formatter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_controller/metal/exceptions" module ActionDispatch @@ -48,7 +50,7 @@ module ActionDispatch unmatched_keys = (missing_keys || []) & constraints.keys missing_keys = (missing_keys || []) - unmatched_keys - message = "No route matches #{Hash[constraints.sort_by { |k, v| k.to_s }].inspect}" + message = "No route matches #{Hash[constraints.sort_by { |k, v| k.to_s }].inspect}".dup message << ", missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty? message << ", possible unmatched constraints: #{unmatched_keys.sort.inspect}" if unmatched_keys && !unmatched_keys.empty? diff --git a/actionpack/lib/action_dispatch/journey/gtg/builder.rb b/actionpack/lib/action_dispatch/journey/gtg/builder.rb index 0f8bed89bf..44c31053cb 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/builder.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/builder.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_dispatch/journey/gtg/transition_table" module ActionDispatch diff --git a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb index d692f6415c..2ee4f5c30c 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "strscan" module ActionDispatch @@ -18,14 +20,6 @@ module ActionDispatch @tt = transition_table end - def simulate(string) - ms = memos(string) { return } - MatchData.new(ms) - end - - alias :=~ :simulate - alias :match :simulate - def memos(string) input = StringScanner.new(string) state = [0] diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb index e1ac2c873e..ea647e051a 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_dispatch/journey/nfa/dot" module ActionDispatch @@ -82,7 +84,7 @@ module ActionDispatch end def visualizer(paths, title = "FSM") - viz_dir = File.join File.dirname(__FILE__), "..", "visualizer" + viz_dir = File.join __dir__, "..", "visualizer" fsm_js = File.read File.join(viz_dir, "fsm.js") fsm_css = File.read File.join(viz_dir, "fsm.css") erb = File.read File.join(viz_dir, "index.html.erb") diff --git a/actionpack/lib/action_dispatch/journey/nfa/builder.rb b/actionpack/lib/action_dispatch/journey/nfa/builder.rb index 532f765094..d22302e101 100644 --- a/actionpack/lib/action_dispatch/journey/nfa/builder.rb +++ b/actionpack/lib/action_dispatch/journey/nfa/builder.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_dispatch/journey/nfa/transition_table" require "action_dispatch/journey/gtg/transition_table" diff --git a/actionpack/lib/action_dispatch/journey/nfa/dot.rb b/actionpack/lib/action_dispatch/journey/nfa/dot.rb index 8119e5d9da..bdb78d8d48 100644 --- a/actionpack/lib/action_dispatch/journey/nfa/dot.rb +++ b/actionpack/lib/action_dispatch/journey/nfa/dot.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch module Journey # :nodoc: module NFA # :nodoc: diff --git a/actionpack/lib/action_dispatch/journey/nfa/simulator.rb b/actionpack/lib/action_dispatch/journey/nfa/simulator.rb index 324d0eed15..8efe48d91c 100644 --- a/actionpack/lib/action_dispatch/journey/nfa/simulator.rb +++ b/actionpack/lib/action_dispatch/journey/nfa/simulator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "strscan" module ActionDispatch diff --git a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb index 543a670da0..fe55861507 100644 --- a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb +++ b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_dispatch/journey/nfa/dot" module ActionDispatch diff --git a/actionpack/lib/action_dispatch/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb index 0d874a84c9..08b931a3cd 100644 --- a/actionpack/lib/action_dispatch/journey/nodes/node.rb +++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_dispatch/journey/visitors" module ActionDispatch diff --git a/actionpack/lib/action_dispatch/journey/parser_extras.rb b/actionpack/lib/action_dispatch/journey/parser_extras.rb index 4c7e82d93c..18ec6c9b9b 100644 --- a/actionpack/lib/action_dispatch/journey/parser_extras.rb +++ b/actionpack/lib/action_dispatch/journey/parser_extras.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_dispatch/journey/scanner" require "action_dispatch/journey/nodes/node" diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb index cf0108ec32..2d85a89a56 100644 --- a/actionpack/lib/action_dispatch/journey/path/pattern.rb +++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch module Journey # :nodoc: module Path # :nodoc: diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index 7bc15aa6b3..8165709a3d 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch # :stopdoc: module Journey @@ -10,11 +12,11 @@ module ActionDispatch module VerbMatchers VERBS = %w{ DELETE GET HEAD OPTIONS LINK PATCH POST PUT TRACE UNLINK } VERBS.each do |v| - class_eval <<-eoc - class #{v} - def self.verb; name.split("::").last; end - def self.call(req); req.#{v.downcase}?; end - end + class_eval <<-eoc, __FILE__, __LINE__ + 1 + class #{v} + def self.verb; name.split("::").last; end + def self.call(req); req.#{v.downcase}?; end + end eoc end diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb index d55e1399e4..30af3ff930 100644 --- a/actionpack/lib/action_dispatch/journey/router.rb +++ b/actionpack/lib/action_dispatch/journey/router.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_dispatch/journey/router/utils" require "action_dispatch/journey/routes" require "action_dispatch/journey/formatter" @@ -41,6 +43,10 @@ module ActionDispatch req.path_info = "/" + req.path_info unless req.path_info.start_with? "/" end + parameters = route.defaults.merge parameters.transform_values { |val| + val.dup.force_encoding(::Encoding::UTF_8) + } + req.path_parameters = set_params.merge parameters status, headers, body = route.app.serve(req) @@ -55,7 +61,7 @@ module ActionDispatch return [status, headers, body] end - return [404, { "X-Cascade" => "pass" }, ["Not Found"]] + [404, { "X-Cascade" => "pass" }, ["Not Found"]] end def recognize(rails_req) @@ -65,6 +71,7 @@ module ActionDispatch rails_req.path_info = match.post_match.sub(/^([^\/])/, '/\1') end + parameters = route.defaults.merge parameters yield(route, parameters) end end @@ -117,7 +124,7 @@ module ActionDispatch routes.map! { |r| match_data = r.path.match(req.path_info) - path_parameters = r.defaults.dup + path_parameters = {} match_data.names.zip(match_data.captures) { |name, val| path_parameters[name.to_sym] = Utils.unescape_uri(val) if val } diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb index ffcd875b4d..df3f79a407 100644 --- a/actionpack/lib/action_dispatch/journey/router/utils.rb +++ b/actionpack/lib/action_dispatch/journey/router/utils.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch module Journey # :nodoc: class Router # :nodoc: @@ -13,21 +15,24 @@ module ActionDispatch # normalize_path("") # => "/" # normalize_path("/%ab") # => "/%AB" def self.normalize_path(path) - path = "/#{path}" + path ||= "" + encoding = path.encoding + path = "/#{path}".dup path.squeeze!("/".freeze) path.sub!(%r{/+\Z}, "".freeze) path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase } - path = "/" if path == "".freeze + path = "/".dup if path == "".freeze + path.force_encoding(encoding) path end # URI path and fragment escaping - # http://tools.ietf.org/html/rfc3986 + # https://tools.ietf.org/html/rfc3986 class UriEncoder # :nodoc: ENCODE = "%%%02X".freeze US_ASCII = Encoding::US_ASCII UTF_8 = Encoding::UTF_8 - EMPTY = "".force_encoding(US_ASCII).freeze + EMPTY = "".dup.force_encoding(US_ASCII).freeze DEC2HEX = (0..255).to_a.map { |i| ENCODE % i }.map { |s| s.force_encoding(US_ASCII) } ALPHA = "a-zA-Z".freeze @@ -59,11 +64,11 @@ module ActionDispatch end private - def escape(component, pattern) # :doc: + def escape(component, pattern) component.gsub(pattern) { |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII) end - def percent_encode(unsafe) # :doc: + def percent_encode(unsafe) safe = EMPTY.dup unsafe.each_byte { |b| safe << DEC2HEX[b] } safe @@ -84,6 +89,10 @@ module ActionDispatch ENCODER.escape_fragment(fragment.to_s) end + # Replaces any escaped sequences with their unescaped representations. + # + # uri = "/topics?title=Ruby%20on%20Rails" + # unescape_uri(uri) #=> "/topics?title=Ruby on Rails" def self.unescape_uri(uri) ENCODER.unescape_uri(uri) end diff --git a/actionpack/lib/action_dispatch/journey/routes.rb b/actionpack/lib/action_dispatch/journey/routes.rb index f7b009109e..639c063495 100644 --- a/actionpack/lib/action_dispatch/journey/routes.rb +++ b/actionpack/lib/action_dispatch/journey/routes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch module Journey # :nodoc: # The Routing table. Contains all routes for a system. Routes can be diff --git a/actionpack/lib/action_dispatch/journey/scanner.rb b/actionpack/lib/action_dispatch/journey/scanner.rb index 7dbb39b26d..4ae77903fa 100644 --- a/actionpack/lib/action_dispatch/journey/scanner.rb +++ b/actionpack/lib/action_dispatch/journey/scanner.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require "strscan" module ActionDispatch diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb index 335797f4b9..3395471a85 100644 --- a/actionpack/lib/action_dispatch/journey/visitors.rb +++ b/actionpack/lib/action_dispatch/journey/visitors.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch # :stopdoc: module Journey @@ -175,7 +177,7 @@ module ActionDispatch last_child = node.children.last node.children.inject(seed) { |s, c| string = visit(c, s) - string << "|".freeze unless last_child == c + string << "|" unless last_child == c string } end @@ -185,7 +187,7 @@ module ActionDispatch end def visit_GROUP(node, seed) - visit(node.left, seed << "(".freeze) << ")".freeze + visit(node.left, seed.dup << "(") << ")" end INSTANCE = new diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index ff129cf96a..5b2ad36dd5 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch # Provides callbacks to be executed before and after dispatching the request. class Callbacks diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index e565c03a8a..86a070c6ad 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/hash/keys" require "active_support/key_generator" require "active_support/message_verifier" @@ -43,6 +45,22 @@ module ActionDispatch get_header Cookies::ENCRYPTED_SIGNED_COOKIE_SALT end + def authenticated_encrypted_cookie_salt + get_header Cookies::AUTHENTICATED_ENCRYPTED_COOKIE_SALT + end + + def use_authenticated_cookie_encryption + get_header Cookies::USE_AUTHENTICATED_COOKIE_ENCRYPTION + end + + def encrypted_cookie_cipher + get_header Cookies::ENCRYPTED_COOKIE_CIPHER + end + + def signed_cookie_digest + get_header Cookies::SIGNED_COOKIE_DIGEST + end + def secret_token get_header Cookies::SECRET_TOKEN end @@ -58,6 +76,11 @@ module ActionDispatch def cookies_digest get_header Cookies::COOKIES_DIGEST end + + def cookies_rotations + get_header Cookies::COOKIES_ROTATIONS + end + # :startdoc: end @@ -77,16 +100,17 @@ module ActionDispatch # cookies[:lat_lon] = JSON.generate([47.68, -122.37]) # # # Sets a cookie that expires in 1 hour. - # cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now } + # cookies[:login] = { value: "XJ-122", expires: 1.hour } + # + # # Sets a cookie that expires at a specific time. + # cookies[:login] = { value: "XJ-122", expires: Time.utc(2020, 10, 15, 5) } # # # Sets a signed cookie, which prevents users from tampering with its value. - # # The cookie is signed by your app's `secrets.secret_key_base` value. # # It can be read using the signed method `cookies.signed[:name]` # cookies.signed[:user_id] = current_user.id # # # Sets an encrypted cookie value before sending it to the client which # # prevent users from reading and tampering with its value. - # # The cookie is signed by your app's `secrets.secret_key_base` value. # # It can be read using the encrypted method `cookies.encrypted[:name]` # cookies.encrypted[:discount] = 45 # @@ -94,7 +118,7 @@ module ActionDispatch # cookies.permanent[:login] = "XJ-122" # # # You can also chain these methods: - # cookies.permanent.signed[:login] = "XJ-122" + # cookies.signed.permanent[:login] = "XJ-122" # # Examples of reading: # @@ -112,7 +136,7 @@ module ActionDispatch # # cookies[:name] = { # value: 'a yummy cookie', - # expires: 1.year.from_now, + # expires: 1.year, # domain: 'domain.com' # } # @@ -138,7 +162,7 @@ module ActionDispatch # * <tt>:tld_length</tt> - When using <tt>:domain => :all</tt>, this option can be used to explicitly # set the TLD length when using a short (<= 3 character) domain that is being interpreted as part of a TLD. # For example, to share cookies between user1.lvh.me and user2.lvh.me, set <tt>:tld_length</tt> to 1. - # * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object. + # * <tt>:expires</tt> - The time at which this cookie expires, as a \Time or ActiveSupport::Duration object. # * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers. # Default is +false+. # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or @@ -149,10 +173,15 @@ module ActionDispatch SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze + AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt".freeze + USE_AUTHENTICATED_COOKIE_ENCRYPTION = "action_dispatch.use_authenticated_cookie_encryption".freeze + ENCRYPTED_COOKIE_CIPHER = "action_dispatch.encrypted_cookie_cipher".freeze + SIGNED_COOKIE_DIGEST = "action_dispatch.signed_cookie_digest".freeze SECRET_TOKEN = "action_dispatch.secret_token".freeze SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze COOKIES_DIGEST = "action_dispatch.cookies_digest".freeze + COOKIES_ROTATIONS = "action_dispatch.cookies_rotations".freeze # Cookies can typically store 4096 bytes. MAX_COOKIE_SIZE = 4096 @@ -181,10 +210,10 @@ module ActionDispatch # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed # cookie was tampered with by the user (or a 3rd party), +nil+ will be returned. # - # If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set, + # If +secret_key_base+ and +secrets.secret_token+ (deprecated) are both set, # legacy cookies signed with the old key generator will be transparently upgraded. # - # This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+. + # This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+. # # Example: # @@ -193,35 +222,28 @@ module ActionDispatch # # cookies.signed[:discount] # => 45 def signed - @signed ||= - if upgrade_legacy_signed_cookies? - UpgradeLegacySignedCookieJar.new(self) - else - SignedCookieJar.new(self) - end + @signed ||= SignedKeyRotatingCookieJar.new(self) end # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read. # If the cookie was tampered with by the user (or a 3rd party), +nil+ will be returned. # - # If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set, + # If +secret_key_base+ and +secrets.secret_token+ (deprecated) are both set, # legacy cookies signed with the old key generator will be transparently upgraded. # - # This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+. + # If +config.action_dispatch.encrypted_cookie_salt+ and +config.action_dispatch.encrypted_signed_cookie_salt+ + # are both set, legacy cookies encrypted with HMAC AES-256-CBC will be transparently upgraded. + # + # This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+. # # Example: # # cookies.encrypted[:discount] = 45 - # # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/ + # # => Set-Cookie: discount=DIQ7fw==--K3n//8vvnSbGq9dA--7Xh91HfLpwzbj1czhBiwOg==; path=/ # # cookies.encrypted[:discount] # => 45 def encrypted - @encrypted ||= - if upgrade_legacy_signed_cookies? - UpgradeLegacyEncryptedCookieJar.new(self) - else - EncryptedCookieJar.new(self) - end + @encrypted ||= EncryptedKeyRotatingCookieJar.new(self) end # Returns the +signed+ or +encrypted+ jar, preferring +encrypted+ if +secret_key_base+ is set. @@ -240,29 +262,20 @@ module ActionDispatch def upgrade_legacy_signed_cookies? request.secret_token.present? && request.secret_key_base.present? end - end - # Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream - # to the Message{Encryptor,Verifier} allows us to handle the - # (de)serialization step within the cookie jar, which gives us the - # opportunity to detect and migrate legacy cookies. - module VerifyAndUpgradeLegacySignedMessage # :nodoc: - def initialize(*args) - super - @legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, serializer: ActiveSupport::MessageEncryptor::NullSerializer) - end + def upgrade_legacy_hmac_aes_cbc_cookies? + request.secret_key_base.present? && + request.encrypted_signed_cookie_salt.present? && + request.encrypted_cookie_salt.present? && + request.use_authenticated_cookie_encryption + end - def verify_and_upgrade_legacy_signed_message(name, signed_message) - deserialize(name, @legacy_verifier.verify(signed_message)).tap do |value| - self[name] = { value: value } + def encrypted_cookie_cipher + request.encrypted_cookie_cipher || "aes-256-gcm" end - rescue ActiveSupport::MessageVerifier::InvalidSignature - nil - end - private - def parse(name, signed_message) - super || verify_and_upgrade_legacy_signed_message(name, signed_message) + def signed_cookie_digest + request.signed_cookie_digest || "SHA1" end end @@ -341,7 +354,11 @@ module ActionDispatch @cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; " end - def handle_options(options) #:nodoc: + def handle_options(options) # :nodoc: + if options[:expires].respond_to?(:from_now) + options[:expires] = options[:expires].from_now + end + options[:path] ||= "/" if options[:domain] == :all || options[:domain] == "all" @@ -415,8 +432,7 @@ module ActionDispatch end end - mattr_accessor :always_write_cookie - self.always_write_cookie = false + mattr_accessor :always_write_cookie, default: false private @@ -470,6 +486,14 @@ module ActionDispatch def request; @parent_jar.request; end private + def expiry_options(options) + if options[:expires].respond_to?(:from_now) + { expires_in: options[:expires] } + else + { expires_at: options[:expires] } + end + end + def parse(name, data); data; end def commit(options); end end @@ -493,6 +517,7 @@ module ActionDispatch module SerializedCookieJars # :nodoc: MARSHAL_SIGNATURE = "\x04\x08".freeze + SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer protected def needs_migration?(value) @@ -503,12 +528,16 @@ module ActionDispatch serializer.dump(value) end - def deserialize(name, value) + def deserialize(name) + rotate = false + value = yield -> { rotate = true } + if value - if needs_migration?(value) - Marshal.load(value).tap do |v| - self[name] = { value: v } - end + case + when needs_migration?(value) + self[name] = Marshal.load(value) + when rotate + self[name] = serializer.load(value) else serializer.load(value) end @@ -530,77 +559,98 @@ module ActionDispatch def digest request.cookies_digest || "SHA1" end - - def key_generator - request.key_generator - end end - class SignedCookieJar < AbstractCookieJar # :nodoc: + class SignedKeyRotatingCookieJar < AbstractCookieJar # :nodoc: include SerializedCookieJars def initialize(parent_jar) super - secret = key_generator.generate_key(request.signed_cookie_salt) - @verifier = ActiveSupport::MessageVerifier.new(secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer) + + secret = request.key_generator.generate_key(request.signed_cookie_salt) + @verifier = ActiveSupport::MessageVerifier.new(secret, digest: signed_cookie_digest, serializer: SERIALIZER) + + request.cookies_rotations.signed.each do |*secrets, **options| + @verifier.rotate(*secrets, serializer: SERIALIZER, **options) + end + + if upgrade_legacy_signed_cookies? + @verifier.rotate request.secret_token, serializer: SERIALIZER + end end private def parse(name, signed_message) - deserialize name, @verifier.verified(signed_message) + deserialize(name) do |rotate| + @verifier.verified(signed_message, on_rotation: rotate) + end end def commit(options) - options[:value] = @verifier.generate(serialize(options[:value])) + options[:value] = @verifier.generate(serialize(options[:value]), expiry_options(options)) raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE end end - # UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if - # secrets.secret_token and secrets.secret_key_base are both set. It reads - # legacy cookies signed with the old dummy key generator and signs and - # re-saves them using the new key generator to provide a smooth upgrade path. - class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc: - include VerifyAndUpgradeLegacySignedMessage - end - - class EncryptedCookieJar < AbstractCookieJar # :nodoc: + class EncryptedKeyRotatingCookieJar < AbstractCookieJar # :nodoc: include SerializedCookieJars def initialize(parent_jar) super - if ActiveSupport::LegacyKeyGenerator === key_generator - raise "You didn't set secrets.secret_key_base, which is required for this cookie jar. " \ - "Read the upgrade documentation to learn more about this new config option." + if request.use_authenticated_cookie_encryption + key_len = ActiveSupport::MessageEncryptor.key_len(encrypted_cookie_cipher) + secret = request.key_generator.generate_key(request.authenticated_encrypted_cookie_salt, key_len) + @encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: encrypted_cookie_cipher, serializer: SERIALIZER) + else + key_len = ActiveSupport::MessageEncryptor.key_len("aes-256-cbc") + secret = request.key_generator.generate_key(request.encrypted_cookie_salt, key_len) + sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt) + @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", serializer: SERIALIZER) + end + + request.cookies_rotations.encrypted.each do |*secrets, **options| + @encryptor.rotate(*secrets, serializer: SERIALIZER, **options) end - secret = key_generator.generate_key(request.encrypted_cookie_salt || "")[0, ActiveSupport::MessageEncryptor.key_len] - sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || "") - @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer) + if upgrade_legacy_hmac_aes_cbc_cookies? + legacy_cipher = "aes-256-cbc" + secret = request.key_generator.generate_key(request.encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len(legacy_cipher)) + sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt) + + @encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER) + end + + if upgrade_legacy_signed_cookies? + @legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, digest: digest, serializer: SERIALIZER) + end end private def parse(name, encrypted_message) - deserialize name, @encryptor.decrypt_and_verify(encrypted_message) - rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage - nil + deserialize(name) do |rotate| + @encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate) + end + rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature + parse_legacy_signed_message(name, encrypted_message) end def commit(options) - options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value])) + options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]), expiry_options(options)) raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE end - end - # UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore - # instead of EncryptedCookieJar if secrets.secret_token and secrets.secret_key_base - # are both set. It reads legacy cookies signed with the old dummy key generator and - # encrypts and re-saves them using the new key generator to provide a smooth upgrade path. - class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc: - include VerifyAndUpgradeLegacySignedMessage + def parse_legacy_signed_message(name, legacy_signed_message) + if defined?(@legacy_verifier) + deserialize(name) do |rotate| + rotate.call + + @legacy_verifier.verified(legacy_signed_message) + end + end + end end def initialize(app) diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index 1c720c5a8e..511306eb0e 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_dispatch/http/request" require "action_dispatch/middleware/exception_wrapper" require "action_dispatch/routing/inspector" @@ -10,7 +12,7 @@ module ActionDispatch # This middleware is responsible for logging exceptions and # showing a debugging page in case the request is local. class DebugExceptions - RESCUES_TEMPLATE_PATH = File.expand_path("../templates", __FILE__) + RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__) class DebugView < ActionView::Base def debug_params(params) @@ -21,7 +23,7 @@ module ActionDispatch if clean_params.empty? "None" else - PP.pp(clean_params, "", 200) + PP.pp(clean_params, "".dup, 200) end end diff --git a/actionpack/lib/action_dispatch/middleware/debug_locks.rb b/actionpack/lib/action_dispatch/middleware/debug_locks.rb index 74b952528e..03760438f7 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_locks.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_locks.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch # This middleware can be used to diagnose deadlocks in the autoload interlock. # @@ -41,7 +43,7 @@ module ActionDispatch private def render_details(req) - threads = ActiveSupport::Dependencies.interlock.raw_state do |threads| + threads = ActiveSupport::Dependencies.interlock.raw_state do |raw_threads| # The Interlock itself comes to a complete halt as long as this block # is executing. That gives us a more consistent picture of everything, # but creates a pretty strong Observer Effect. @@ -51,29 +53,29 @@ module ActionDispatch # strictly diagnostic tool (to be used when something has gone wrong), # and not for any sort of general monitoring. - threads.each.with_index do |(thread, info), idx| + raw_threads.each.with_index do |(thread, info), idx| info[:index] = idx info[:backtrace] = thread.backtrace end - threads + raw_threads end str = threads.map do |thread, info| if info[:exclusive] - lock_state = "Exclusive" + lock_state = "Exclusive".dup elsif info[:sharing] > 0 - lock_state = "Sharing" + lock_state = "Sharing".dup lock_state << " x#{info[:sharing]}" if info[:sharing] > 1 else - lock_state = "No lock" + lock_state = "No lock".dup end if info[:waiting] lock_state << " (yielded share)" end - msg = "Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n" + msg = "Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n".dup if info[:sleeper] msg << " Waiting in #{info[:sleeper]}" diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb index 397f0a8b92..4f69abfa6f 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -1,11 +1,11 @@ +# frozen_string_literal: true + require "active_support/core_ext/module/attribute_accessors" require "rack/utils" module ActionDispatch class ExceptionWrapper - cattr_accessor :rescue_responses - @@rescue_responses = Hash.new(:internal_server_error) - @@rescue_responses.merge!( + cattr_accessor :rescue_responses, default: Hash.new(:internal_server_error).merge!( "ActionController::RoutingError" => :not_found, "AbstractController::ActionNotFound" => :not_found, "ActionController::MethodNotAllowed" => :method_not_allowed, @@ -21,9 +21,7 @@ module ActionDispatch "Rack::QueryParser::InvalidParameterError" => :bad_request ) - cattr_accessor :rescue_templates - @@rescue_templates = Hash.new("diagnostics") - @@rescue_templates.merge!( + cattr_accessor :rescue_templates, default: Hash.new("diagnostics").merge!( "ActionView::MissingTemplate" => "missing_template", "ActionController::RoutingError" => "routing_error", "AbstractController::ActionNotFound" => "unknown_action", diff --git a/actionpack/lib/action_dispatch/middleware/executor.rb b/actionpack/lib/action_dispatch/middleware/executor.rb index 3d43f97a2b..129b18d3d9 100644 --- a/actionpack/lib/action_dispatch/middleware/executor.rb +++ b/actionpack/lib/action_dispatch/middleware/executor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rack/body_proxy" module ActionDispatch diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index 6b29ce63ba..3e11846778 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/hash/keys" module ActionDispatch diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb index 46f0f675b9..3feb3a19f3 100644 --- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb @@ -1,11 +1,13 @@ +# frozen_string_literal: true + module ActionDispatch # When called, this middleware renders an error page. By default if an HTML - # response is expected it will render static error pages from the `/public` + # response is expected it will render static error pages from the <tt>/public</tt> # directory. For example when this middleware receives a 500 response it will - # render the template found in `/public/500.html`. + # render the template found in <tt>/public/500.html</tt>. # If an internationalized locale is set, this middleware will attempt to render - # the template in `/public/500.<locale>.html`. If an internationalized template - # is not found it will fall back on `/public/500.html`. + # the template in <tt>/public/500.<locale>.html</tt>. If an internationalized template + # is not found it will fall back on <tt>/public/500.html</tt>. # # When a request with a content type other than HTML is made, this middleware # will attempt to convert error information into the appropriate response type. diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb index 6d64b1424b..8bb3ba7504 100644 --- a/actionpack/lib/action_dispatch/middleware/reloader.rb +++ b/actionpack/lib/action_dispatch/middleware/reloader.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch # ActionDispatch::Reloader wraps the request with callbacks provided by ActiveSupport::Reloader # callbacks, intended to assist with code reloading during development. diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index 53d5a4918c..35158f9062 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "ipaddr" module ActionDispatch @@ -10,7 +12,7 @@ module ActionDispatch # by @gingerlime. A more detailed explanation of the algorithm is given # at GetIp#calculate_ip. # - # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2] + # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2] # requires. Some Rack servers simply drop preceding headers, and only report # the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers]. # If you are behind multiple proxy servers (like NGINX to HAProxy to Unicorn) @@ -29,7 +31,7 @@ module ActionDispatch # The default trusted IPs list simply includes IP addresses that are # guaranteed by the IP specification to be private addresses. Those will # not be the ultimate client IP in production, and so are discarded. See - # http://en.wikipedia.org/wiki/Private_network for details. + # https://en.wikipedia.org/wiki/Private_network for details. TRUSTED_PROXIES = [ "127.0.0.1", # localhost IPv4 "::1", # localhost IPv6 diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb index 1925ffd9dd..805d3f2148 100644 --- a/actionpack/lib/action_dispatch/middleware/request_id.rb +++ b/actionpack/lib/action_dispatch/middleware/request_id.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "securerandom" require "active_support/core_ext/string/access" diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 21ccf5a097..5b0be96223 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rack/utils" require "rack/request" require "rack/session/abstract/id" diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb index 71274bc13a..a6d965a644 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_dispatch/middleware/session/abstract_store" module ActionDispatch diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 57d325a9d8..4ea96196d3 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/hash/keys" require "action_dispatch/middleware/session/abstract_store" require "rack/session/cookie" @@ -19,39 +21,25 @@ module ActionDispatch # knowing your app's secret key, but can easily read their +user_id+. This # was the default for Rails 3 apps. # - # If you have secret_key_base set, your cookies will be encrypted. This + # Your cookies will be encrypted using your apps secret_key_base. This # goes a step further than signed cookies in that encrypted cookies cannot # be altered or read by users. This is the default starting in Rails 4. # - # If you have both secret_token and secret_key_base set, your cookies will - # be encrypted, and signed cookies generated by Rails 3 will be - # transparently read and encrypted to provide a smooth upgrade path. - # - # Configure your session store in config/initializers/session_store.rb: + # Configure your session store in <tt>config/initializers/session_store.rb</tt>: # # Rails.application.config.session_store :cookie_store, key: '_your_app_session' # - # Configure your secret key in config/secrets.yml: - # - # development: - # secret_key_base: 'secret key' - # - # To generate a secret key for an existing application, run `rails secret`. + # By default, your secret key base is derived from your application name in + # the test and development environments. In all other environments, it is stored + # encrypted in the <tt>config/credentials.yml.enc</tt> file. # - # If you are upgrading an existing Rails 3 app, you should leave your - # existing secret_token in place and simply add the new secret_key_base. - # Note that you should wait to set secret_key_base until you have 100% of - # your userbase on Rails 4 and are reasonably sure you will not need to - # rollback to Rails 3. This is because cookies signed based on the new - # secret_key_base in Rails 4 are not backwards compatible with Rails 3. - # You are free to leave your existing secret_token in place, not set the - # new secret_key_base, and ignore the deprecation warnings until you are - # reasonably sure that your upgrade is otherwise complete. Additionally, - # you should take care to make sure you are not relying on the ability to - # decode signed cookies generated by your app in external applications or - # JavaScript before upgrading. + # If your application was not updated to Rails 5.2 defaults, the secret_key_base + # will be found in the old <tt>config/secrets.yml</tt> file. # - # Note that changing the secret key will invalidate all existing sessions! + # Note that changing your secret_key_base will invalidate all existing session. + # Additionally, you should take care to make sure you are not relying on the + # ability to decode signed cookies generated by your app in external + # applications or JavaScript before changing it. # # Because CookieStore extends Rack::Session::Abstract::Persisted, many of the # options described there can be used to customize the session cookie that diff --git a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb index ee2b1f26ad..914df3a2b1 100644 --- a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_dispatch/middleware/session/abstract_store" begin require "rack/session/dalli" diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 5a99714ec2..3c88afd4d3 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_dispatch/http/request" require "action_dispatch/middleware/exception_wrapper" diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb index 557721c301..ef633aadc6 100644 --- a/actionpack/lib/action_dispatch/middleware/ssl.rb +++ b/actionpack/lib/action_dispatch/middleware/ssl.rb @@ -1,45 +1,52 @@ +# frozen_string_literal: true + module ActionDispatch - # This middleware is added to the stack when `config.force_ssl = true`, and is passed - # the options set in `config.ssl_options`. It does three jobs to enforce secure HTTP + # This middleware is added to the stack when <tt>config.force_ssl = true</tt>, and is passed + # the options set in +config.ssl_options+. It does three jobs to enforce secure HTTP # requests: # - # 1. TLS redirect: Permanently redirects http:// requests to https:// - # with the same URL host, path, etc. Enabled by default. Set `config.ssl_options` - # to modify the destination URL - # (e.g. `redirect: { host: "secure.widgets.com", port: 8080 }`), or set - # `redirect: false` to disable this feature. + # 1. <b>TLS redirect</b>: Permanently redirects +http://+ requests to +https://+ + # with the same URL host, path, etc. Enabled by default. Set +config.ssl_options+ + # to modify the destination URL + # (e.g. <tt>redirect: { host: "secure.widgets.com", port: 8080 }</tt>), or set + # <tt>redirect: false</tt> to disable this feature. + # + # Requests can opt-out of redirection with +exclude+: + # + # config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } } # - # 2. Secure cookies: Sets the `secure` flag on cookies to tell browsers they - # mustn't be sent along with http:// requests. Enabled by default. Set - # `config.ssl_options` with `secure_cookies: false` to disable this feature. + # 2. <b>Secure cookies</b>: Sets the +secure+ flag on cookies to tell browsers they + # must not be sent along with +http://+ requests. Enabled by default. Set + # +config.ssl_options+ with <tt>secure_cookies: false</tt> to disable this feature. # - # 3. HTTP Strict Transport Security (HSTS): Tells the browser to remember - # this site as TLS-only and automatically redirect non-TLS requests. - # Enabled by default. Configure `config.ssl_options` with `hsts: false` to disable. + # 3. <b>HTTP Strict Transport Security (HSTS)</b>: Tells the browser to remember + # this site as TLS-only and automatically redirect non-TLS requests. + # Enabled by default. Configure +config.ssl_options+ with <tt>hsts: false</tt> to disable. # - # Set `config.ssl_options` with `hsts: { … }` to configure HSTS: - # * `expires`: How long, in seconds, these settings will stick. The minimum - # required to qualify for browser preload lists is `18.weeks`. Defaults to - # `180.days` (recommended). - # * `subdomains`: Set to `true` to tell the browser to apply these settings - # to all subdomains. This protects your cookies from interception by a - # vulnerable site on a subdomain. Defaults to `true`. - # * `preload`: Advertise that this site may be included in browsers' - # preloaded HSTS lists. HSTS protects your site on every visit *except the - # first visit* since it hasn't seen your HSTS header yet. To close this - # gap, browser vendors include a baked-in list of HSTS-enabled sites. - # Go to https://hstspreload.appspot.com to submit your site for inclusion. - # Defaults to `false`. + # Set +config.ssl_options+ with <tt>hsts: { ... }</tt> to configure HSTS: # - # To turn off HSTS, omitting the header is not enough. Browsers will remember the - # original HSTS directive until it expires. Instead, use the header to tell browsers to - # expire HSTS immediately. Setting `hsts: false` is a shortcut for - # `hsts: { expires: 0 }`. + # * +expires+: How long, in seconds, these settings will stick. The minimum + # required to qualify for browser preload lists is 18 weeks. Defaults to + # 180 days (recommended). # - # Requests can opt-out of redirection with `exclude`: + # * +subdomains+: Set to +true+ to tell the browser to apply these settings + # to all subdomains. This protects your cookies from interception by a + # vulnerable site on a subdomain. Defaults to +true+. # - # config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } } + # * +preload+: Advertise that this site may be included in browsers' + # preloaded HSTS lists. HSTS protects your site on every visit <i>except the + # first visit</i> since it hasn't seen your HSTS header yet. To close this + # gap, browser vendors include a baked-in list of HSTS-enabled sites. + # Go to https://hstspreload.org to submit your site for inclusion. + # Defaults to +false+. + # + # To turn off HSTS, omitting the header is not enough. Browsers will remember the + # original HSTS directive until it expires. Instead, use the header to tell browsers to + # expire HSTS immediately. Setting <tt>hsts: false</tt> is a shortcut for + # <tt>hsts: { expires: 0 }</tt>. class SSL + # :stopdoc: + # Default to 180 days, the low end for https://www.ssllabs.com/ssltest/ # and greater than the 18-week requirement for browser preload lists. HSTS_EXPIRES_IN = 15552000 @@ -92,9 +99,9 @@ module ActionDispatch end end - # http://tools.ietf.org/html/rfc6797#section-6.1 + # https://tools.ietf.org/html/rfc6797#section-6.1 def build_hsts_header(hsts) - value = "max-age=#{hsts[:expires].to_i}" + value = "max-age=#{hsts[:expires].to_i}".dup value << "; includeSubDomains" if hsts[:subdomains] value << "; preload" if hsts[:preload] value @@ -133,7 +140,7 @@ module ActionDispatch host = @redirect[:host] || request.host port = @redirect[:port] || request.port - location = "https://#{host}" + location = "https://#{host}".dup location << ":#{port}" if port != 80 && port != 443 location << request.fullpath location diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb index 6949b31e75..b82f8aa3a3 100644 --- a/actionpack/lib/action_dispatch/middleware/stack.rb +++ b/actionpack/lib/action_dispatch/middleware/stack.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/inflector/methods" require "active_support/dependencies" diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index 5d10129d21..23492e14eb 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rack/utils" require "active_support/core_ext/uri" @@ -6,11 +8,11 @@ module ActionDispatch # When initialized, it can accept optional HTTP headers, which will be set # when a response containing a file's contents is delivered. # - # This middleware will render the file specified in `env["PATH_INFO"]` + # This middleware will render the file specified in <tt>env["PATH_INFO"]</tt> # where the base path is in the +root+ directory. For example, if the +root+ - # is set to `public/`, then a request with `env["PATH_INFO"]` of - # `assets/application.js` will return a response with the contents of a file - # located at `public/assets/application.js` if the file exists. If the file + # is set to +public/+, then a request with <tt>env["PATH_INFO"]</tt> of + # +assets/application.js+ will return a response with the contents of a file + # located at +public/assets/application.js+ if the file exists. If the file # does not exist, a 404 "File not Found" response will be returned. class FileHandler def initialize(root, index: "index", headers: {}) @@ -23,8 +25,8 @@ module ActionDispatch # correct read permissions, the return value is a URI-escaped string # representing the filename. Otherwise, false is returned. # - # Used by the `Static` class to check the existence of a valid file - # in the server's `public/` directory (see Static#call). + # Used by the +Static+ class to check the existence of a valid file + # in the server's +public/+ directory (see Static#call). def match?(path) path = ::Rack::Utils.unescape_path path return false unless ::Rack::Utils.valid_path? path @@ -33,7 +35,7 @@ module ActionDispatch paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"] if match = paths.detect { |p| - path = File.join(@root, p.force_encoding(Encoding::UTF_8)) + path = File.join(@root, p.dup.force_encoding(Encoding::UTF_8)) begin File.file?(path) && File.readable?(path) rescue SystemCallError @@ -99,7 +101,7 @@ module ActionDispatch # This middleware will attempt to return the contents of a file's body from # disk in the response. If a file is not found on disk, the request will be # delegated to the application stack. This middleware is commonly initialized - # to serve assets from a server's `public/` directory. + # to serve assets from a server's +public/+ directory. # # This middleware verifies the path to ensure that only files # living in the root directory can be rendered. A request cannot diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb index e0509f56f4..39ea25bdfc 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb @@ -106,6 +106,7 @@ .line { padding-left: 10px; + white-space: pre; } .line:hover { diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb index 2d21ae63f5..1fa0691303 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb @@ -17,6 +17,10 @@ line-height: 15px; } + #route_table thead tr.bottom th input#search { + -webkit-appearance: textfield; + } + #route_table tbody tr { border-bottom: 1px solid #ddd; } diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index 16a18a7f25..855f2ffa47 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -1,4 +1,7 @@ +# frozen_string_literal: true + require "action_dispatch" +require "active_support/messages/rotation_configuration" module ActionDispatch class Railtie < Rails::Railtie # :nodoc: @@ -16,6 +19,8 @@ module ActionDispatch config.action_dispatch.signed_cookie_salt = "signed cookie" config.action_dispatch.encrypted_cookie_salt = "encrypted cookie" config.action_dispatch.encrypted_signed_cookie_salt = "signed encrypted cookie" + config.action_dispatch.authenticated_encrypted_cookie_salt = "authenticated encrypted cookie" + config.action_dispatch.use_authenticated_cookie_encryption = false config.action_dispatch.perform_deep_munge = true config.action_dispatch.default_headers = { @@ -24,6 +29,8 @@ module ActionDispatch "X-Content-Type-Options" => "nosniff" } + config.action_dispatch.cookies_rotations = ActiveSupport::Messages::RotationConfiguration.new + config.eager_load_namespaces << ActionDispatch initializer "action_dispatch.configure" do |app| diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb index 74ba6466cf..d86d0b10c2 100644 --- a/actionpack/lib/action_dispatch/request/session.rb +++ b/actionpack/lib/action_dispatch/request/session.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rack/session/abstract/id" module ActionDispatch @@ -101,11 +103,13 @@ module ActionDispatch # Returns keys of the session as Array. def keys + load_for_read! @delegate.keys end # Returns values of the session as Array. def values + load_for_read! @delegate.values end diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb index 3615e4b1d8..0ae464082d 100644 --- a/actionpack/lib/action_dispatch/request/utils.rb +++ b/actionpack/lib/action_dispatch/request/utils.rb @@ -1,8 +1,9 @@ +# frozen_string_literal: true + module ActionDispatch class Request class Utils # :nodoc: - mattr_accessor :perform_deep_munge - self.perform_deep_munge = true + mattr_accessor :perform_deep_munge, default: true def self.each_param_value(params, &block) case params @@ -33,7 +34,7 @@ module ActionDispatch unless params.valid_encoding? # Raise Rack::Utils::InvalidParameterError for consistency with Rack. # ActionDispatch::Request#GET will re-raise as a BadRequest error. - raise Rack::Utils::InvalidParameterError, "Non UTF-8 value: #{params}" + raise Rack::Utils::InvalidParameterError, "Invalid encoding for parameter: #{params.scrub}" end end end diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 87dd1eba38..72f7407c6e 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/string/filters" module ActionDispatch diff --git a/actionpack/lib/action_dispatch/routing/endpoint.rb b/actionpack/lib/action_dispatch/routing/endpoint.rb index 88aa13c3e8..24dced1efd 100644 --- a/actionpack/lib/action_dispatch/routing/endpoint.rb +++ b/actionpack/lib/action_dispatch/routing/endpoint.rb @@ -1,10 +1,14 @@ +# frozen_string_literal: true + module ActionDispatch module Routing class Endpoint # :nodoc: - def dispatcher?; false; end - def redirect?; false; end - def matches?(req); true; end - def app; self; end + def dispatcher?; false; end + def redirect?; false; end + def engine?; rack_app.respond_to?(:routes); end + def matches?(req); true; end + def app; self; end + def rack_app; app; end end end end diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index 9aa4b92df2..a2205569b4 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "delegate" require "active_support/core_ext/string/strip" @@ -13,7 +15,7 @@ module ActionDispatch end def rack_app - app.app + app.rack_app end def path @@ -45,7 +47,7 @@ module ActionDispatch end def engine? - rack_app.respond_to?(:routes) + app.engine? end end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 74904e3d45..ded42adee9 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/hash/slice" require "active_support/core_ext/enumerable" require "active_support/core_ext/array/extract_options" @@ -306,7 +308,7 @@ module ActionDispatch def check_controller_and_action(path_params, controller, action) hash = check_part(:controller, controller, path_params, {}) do |part| translate_controller(part) { - message = "'#{part}' is not a supported controller name. This can lead to potential routing problems." + message = "'#{part}' is not a supported controller name. This can lead to potential routing problems.".dup message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use" raise ArgumentError, message @@ -471,7 +473,17 @@ module ActionDispatch # <tt>params[<:param>]</tt>. # In your router: # - # resources :user, param: :name + # resources :users, param: :name + # + # The +users+ resource here will have the following routes generated for it: + # + # GET /users(.:format) + # POST /users(.:format) + # GET /users/new(.:format) + # GET /users/:name/edit(.:format) + # GET /users/:name(.:format) + # PATCH/PUT /users/:name(.:format) + # DELETE /users/:name(.:format) # # You can override <tt>ActiveRecord::Base#to_param</tt> of a related # model to construct a URL: @@ -482,8 +494,8 @@ module ActionDispatch # end # end # - # user = User.find_by(name: 'Phusion') - # user_path(user) # => "/users/Phusion" + # user = User.find_by(name: 'Phusion') + # user_path(user) # => "/users/Phusion" # # [:path] # The path prefix for the routes. @@ -652,18 +664,30 @@ module ActionDispatch def define_generate_prefix(app, name) _route = @set.named_routes.get name _routes = @set - app.routes.define_mounted_helper(name) + + script_namer = ->(options) do + prefix_options = options.slice(*_route.segment_keys) + prefix_options[:relative_url_root] = "".freeze + + if options[:_recall] + prefix_options.reverse_merge!(options[:_recall].slice(*_route.segment_keys)) + end + + # We must actually delete prefix segment keys to avoid passing them to next url_for. + _route.segment_keys.each { |k| options.delete(k) } + _routes.url_helpers.send("#{name}_path", prefix_options) + end + + app.routes.define_mounted_helper(name, script_namer) + app.routes.extend Module.new { def optimize_routes_generation?; false; end + define_method :find_script_name do |options| if options.key? :script_name super(options) else - prefix_options = options.slice(*_route.segment_keys) - prefix_options[:relative_url_root] = "".freeze - # We must actually delete prefix segment keys to avoid passing them to next url_for. - _route.segment_keys.each { |k| options.delete(k) } - _routes.url_helpers.send("#{name}_path", prefix_options) + script_namer.call(options) end end } @@ -1251,7 +1275,7 @@ module ActionDispatch # POST /profile # # === Options - # Takes same options as +resources+. + # Takes same options as resources[rdoc-ref:#resources] def resource(*resources, &block) options = resources.extract_options!.dup @@ -1316,7 +1340,7 @@ module ActionDispatch # DELETE /photos/:photo_id/comments/:id # # === Options - # Takes same options as <tt>Base#match</tt> as well as: + # Takes same options as match[rdoc-ref:Base#match] as well as: # # [:path_names] # Allows you to change the segment component of the +edit+ and +new+ actions. @@ -1837,7 +1861,7 @@ module ActionDispatch path_types.fetch(String, []).each do |_path| route_options = options.dup if _path && option_path - raise ArgumentError, "Ambigous route definition. Both :path and the route path where specified as strings." + raise ArgumentError, "Ambiguous route definition. Both :path and the route path were specified as strings." end to = get_to_from_path(_path, to, route_options[:action]) decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints) @@ -2038,8 +2062,8 @@ module ActionDispatch # { controller: "pages", action: "index", subdomain: "www" } # end # - # The return value from the block passed to `direct` must be a valid set of - # arguments for `url_for` which will actually build the URL string. This can + # The return value from the block passed to +direct+ must be a valid set of + # arguments for +url_for+ which will actually build the URL string. This can # be one of the following: # # * A string, which is treated as a generated URL @@ -2058,17 +2082,17 @@ module ActionDispatch # [ :products, options.merge(params.permit(:page, :size).to_h.symbolize_keys) ] # end # - # In this instance the `params` object comes from the context in which the the + # In this instance the +params+ object comes from the context in which the the # block is executed, e.g. generating a URL inside a controller action or a view. # If the block is executed where there isn't a params object such as this: # # Rails.application.routes.url_helpers.browse_path # - # then it will raise a `NameError`. Because of this you need to be aware of the + # then it will raise a +NameError+. Because of this you need to be aware of the # context in which you will use your custom URL helper when defining it. # - # NOTE: The `direct` method can't be used inside of a scope block such as - # `namespace` or `scope` and will raise an error if it detects that it is. + # NOTE: The +direct+ method can't be used inside of a scope block such as + # +namespace+ or +scope+ and will raise an error if it detects that it is. def direct(name, options = {}, &block) unless @scope.root? raise RuntimeError, "The direct method can't be used inside a routes scope block" @@ -2078,8 +2102,8 @@ module ActionDispatch end # Define custom polymorphic mappings of models to URLs. This alters the - # behavior of `polymorphic_url` and consequently the behavior of - # `link_to` and `form_for` when passed a model instance, e.g: + # behavior of +polymorphic_url+ and consequently the behavior of + # +link_to+ and +form_for+ when passed a model instance, e.g: # # resource :basket # @@ -2087,8 +2111,8 @@ module ActionDispatch # [:basket] # end # - # This will now generate "/basket" when a `Basket` instance is passed to - # `link_to` or `form_for` instead of the standard "/baskets/:id". + # This will now generate "/basket" when a +Basket+ instance is passed to + # +link_to+ or +form_for+ instead of the standard "/baskets/:id". # # NOTE: This custom behavior only applies to simple polymorphic URLs where # a single model instance is passed and not more complicated forms, e.g: @@ -2105,7 +2129,7 @@ module ActionDispatch # link_to "Profile", @current_user # link_to "Profile", [:admin, @current_user] # - # The first `link_to` will generate "/profile" but the second will generate + # The first +link_to+ will generate "/profile" but the second will generate # the standard polymorphic URL of "/admin/users/1". # # You can pass options to a polymorphic mapping - the arity for the block @@ -2116,11 +2140,11 @@ module ActionDispatch # end # # This generates the URL "/basket#items" because when the last item in an - # array passed to `polymorphic_url` is a hash then it's treated as options + # array passed to +polymorphic_url+ is a hash then it's treated as options # to the URL helper that gets called. # - # NOTE: The `resolve` method can't be used inside of a scope block such as - # `namespace` or `scope` and will raise an error if it detects that it is. + # NOTE: The +resolve+ method can't be used inside of a scope block such as + # +namespace+ or +scope+ and will raise an error if it detects that it is. def resolve(*args, &block) unless @scope.root? raise RuntimeError, "The resolve method can't be used inside a routes scope block" diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index e89ea8b21d..6da869c0c2 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch module Routing # Polymorphic URL helpers are methods for smart resolution to a named route call when diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb index 3bcb341758..143a4b3d62 100644 --- a/actionpack/lib/action_dispatch/routing/redirection.rb +++ b/actionpack/lib/action_dispatch/routing/redirection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_dispatch/http/request" require "active_support/core_ext/uri" require "active_support/core_ext/array/extract_options" @@ -140,7 +142,7 @@ module ActionDispatch # get "/stories" => redirect("/posts") # # This will redirect the user, while ignoring certain parts of the request, including query string, etc. - # `/stories`, `/stories?foo=bar`, etc all redirect to `/posts`. + # <tt>/stories</tt>, <tt>/stories?foo=bar</tt>, etc all redirect to <tt>/posts</tt>. # # You can also use interpolation in the supplied redirect argument: # @@ -173,8 +175,8 @@ module ActionDispatch # get '/stories', to: redirect(path: '/posts') # # This will redirect the user, while changing only the specified parts of the request, - # for example the `path` option in the last example. - # `/stories`, `/stories?foo=bar`, redirect to `/posts` and `/posts?foo=bar` respectively. + # for example the +path+ option in the last example. + # <tt>/stories</tt>, <tt>/stories?foo=bar</tt>, redirect to <tt>/posts</tt> and <tt>/posts?foo=bar</tt> respectively. # # Finally, an object which responds to call can be supplied to redirect, allowing you to reuse # common redirect routes. The call method must accept two arguments, params and request, and return diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index e1f9fc9ecc..987e709f6f 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -1,6 +1,9 @@ +# frozen_string_literal: true + require "action_dispatch/journey" require "active_support/core_ext/object/to_query" require "active_support/core_ext/hash/slice" +require "active_support/core_ext/module/redefine_method" require "active_support/core_ext/module/remove_method" require "active_support/core_ext/array/extract_options" require "action_controller/metal/exceptions" @@ -73,7 +76,6 @@ module ActionDispatch @routes = {} @path_helpers = Set.new @url_helpers = Set.new - @custom_helpers = Set.new @url_helpers_module = Module.new @path_helpers_module = Module.new end @@ -96,23 +98,9 @@ module ActionDispatch @url_helpers_module.send :remove_method, helper end - @custom_helpers.each do |helper| - path_name = :"#{helper}_path" - url_name = :"#{helper}_url" - - if @path_helpers_module.method_defined?(path_name) - @path_helpers_module.send :remove_method, path_name - end - - if @url_helpers_module.method_defined?(url_name) - @url_helpers_module.send :remove_method, url_name - end - end - @routes.clear @path_helpers.clear @url_helpers.clear - @custom_helpers.clear end def add(name, route) @@ -158,21 +146,29 @@ module ActionDispatch routes.length end + # Given a +name+, defines name_path and name_url helpers. + # Used by 'direct', 'resolve', and 'polymorphic' route helpers. def add_url_helper(name, defaults, &block) - @custom_helpers << name helper = CustomUrlHelper.new(name, defaults, &block) + path_name = :"#{name}_path" + url_name = :"#{name}_url" @path_helpers_module.module_eval do - define_method(:"#{name}_path") do |*args| + define_method(path_name) do |*args| helper.call(self, args, true) end end @url_helpers_module.module_eval do - define_method(:"#{name}_url") do |*args| + define_method(url_name) do |*args| helper.call(self, args, false) end end + + @path_helpers << path_name + @url_helpers << url_name + + self end class UrlHelper @@ -240,7 +236,7 @@ module ActionDispatch missing_keys << missing_key } constraints = Hash[@route.requirements.merge(params).sort_by { |k, v| k.to_s }] - message = "No route matches #{constraints.inspect}" + message = "No route matches #{constraints.inspect}".dup message << ", missing required keys: #{missing_keys.sort.inspect}" raise ActionController::UrlGenerationError, message @@ -279,6 +275,8 @@ module ActionDispatch if args.size < path_params_size path_params -= controller_options.keys path_params -= result.keys + else + path_params = path_params.dup end inner_options.each_key do |key| path_params.delete(key) @@ -454,7 +452,7 @@ module ActionDispatch MountedHelpers end - def define_mounted_helper(name) + def define_mounted_helper(name, script_namer = nil) return if MountedHelpers.method_defined?(name) routes = self @@ -462,7 +460,7 @@ module ActionDispatch MountedHelpers.class_eval do define_method "_#{name}" do - RoutesProxy.new(routes, _routes_context, helpers) + RoutesProxy.new(routes, _routes_context, helpers, script_namer) end end @@ -549,7 +547,7 @@ module ActionDispatch # plus a singleton class method called _routes ... included do - singleton_class.send(:redefine_method, :_routes) { routes } + redefine_singleton_method(:_routes) { routes } end # And an instance method _routes. Note that @@ -586,14 +584,14 @@ module ActionDispatch if route.segment_keys.include?(:controller) ActiveSupport::Deprecation.warn(<<-MSG.squish) Using a dynamic :controller segment in a route is deprecated and - will be removed in Rails 5.2. + will be removed in Rails 6.0. MSG end if route.segment_keys.include?(:action) ActiveSupport::Deprecation.warn(<<-MSG.squish) Using a dynamic :action segment in a route is deprecated and - will be removed in Rails 5.2. + will be removed in Rails 6.0. MSG end @@ -844,6 +842,10 @@ module ActionDispatch end req = make_request(env) + recognize_path_with_request(req, path, extras) + end + + def recognize_path_with_request(req, path, extras) @router.recognize(req) do |route, params| params.merge!(extras) params.each do |key, value| @@ -862,6 +864,9 @@ module ActionDispatch end return req.path_parameters + elsif app.matches?(req) && app.engine? + path_parameters = app.rack_app.routes.recognize_path_with_request(req, path, extras) + return path_parameters end end diff --git a/actionpack/lib/action_dispatch/routing/routes_proxy.rb b/actionpack/lib/action_dispatch/routing/routes_proxy.rb index ee847eaeed..587a72729c 100644 --- a/actionpack/lib/action_dispatch/routing/routes_proxy.rb +++ b/actionpack/lib/action_dispatch/routing/routes_proxy.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/array/extract_options" module ActionDispatch @@ -8,9 +10,10 @@ module ActionDispatch attr_accessor :scope, :routes alias :_routes :routes - def initialize(routes, scope, helpers) + def initialize(routes, scope, helpers, script_namer = nil) @routes, @scope = routes, scope @helpers = helpers + @script_namer = script_namer end def url_options @@ -19,7 +22,8 @@ module ActionDispatch end end - def respond_to_missing?(method, include_private = false) + private + def respond_to_missing?(method, _) super || @helpers.respond_to?(method) end @@ -28,15 +32,38 @@ module ActionDispatch self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{method}(*args) options = args.extract_options! - args << url_options.merge((options || {}).symbolize_keys) + options = url_options.merge((options || {}).symbolize_keys) + + if @script_namer + options[:script_name] = merge_script_names( + options[:script_name], + @script_namer.call(options) + ) + end + + args << options @helpers.#{method}(*args) end RUBY - send(method, *args) + public_send(method, *args) else super end end + + # Keeps the part of the script name provided by the global + # context via ENV["SCRIPT_NAME"], which `mount` doesn't know + # about since it depends on the specific request, but use our + # script name resolver for the mount point dependent part. + def merge_script_names(previous_script_name, new_script_name) + return new_script_name unless previous_script_name + + resolved_parts = new_script_name.count("/") + previous_parts = previous_script_name.count("/") + context_parts = previous_parts - resolved_parts + 1 + + (previous_script_name.split("/").slice(0, context_parts).join("/")) + new_script_name + end end end end diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index a9bdefa775..3ae533dd37 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch module Routing # In <tt>config/routes.rb</tt> you define URL-to-controller mappings, but the reverse @@ -107,7 +109,7 @@ module ActionDispatch end # Hook overridden in controller to add request information - # with `default_url_options`. Application logic should not + # with +default_url_options+. Application logic should not # go into url_options. def url_options default_url_options diff --git a/actionpack/lib/action_dispatch/system_test_case.rb b/actionpack/lib/action_dispatch/system_test_case.rb index 98fdb36c91..7246e01cff 100644 --- a/actionpack/lib/action_dispatch/system_test_case.rb +++ b/actionpack/lib/action_dispatch/system_test_case.rb @@ -1,3 +1,7 @@ +# frozen_string_literal: true + +gem "capybara", "~> 2.15" + require "capybara/dsl" require "capybara/minitest" require "action_controller" @@ -5,6 +9,7 @@ require "action_dispatch/system_testing/driver" require "action_dispatch/system_testing/server" require "action_dispatch/system_testing/test_helpers/screenshot_helper" require "action_dispatch/system_testing/test_helpers/setup_and_teardown" +require "action_dispatch/system_testing/test_helpers/undef_methods" module ActionDispatch # = System Testing @@ -66,14 +71,18 @@ module ActionDispatch # # To use a headless driver, like Poltergeist, update your Gemfile to use # Poltergeist instead of Selenium and then declare the driver name in the - # +application_system_test_case.rb+ file. In this case you would leave out the +:using+ - # option because the driver is headless. + # +application_system_test_case.rb+ file. In this case, you would leave out + # the +:using+ option because the driver is headless, but you can still use + # +:screen_size+ to change the size of the browser screen, also you can use + # +:options+ to pass options supported by the driver. Please refer to your + # driver documentation to learn about supported options. # # require "test_helper" # require "capybara/poltergeist" # # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase - # driven_by :poltergeist + # driven_by :poltergeist, screen_size: [1400, 1400], options: + # { js_errors: true } # end # # Because <tt>ActionDispatch::SystemTestCase</tt> is a shim between Capybara @@ -84,6 +93,7 @@ module ActionDispatch include Capybara::Minitest::Assertions include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper + include SystemTesting::TestHelpers::UndefMethods def initialize(*) # :nodoc: super @@ -113,12 +123,16 @@ module ActionDispatch # # driven_by :selenium, using: :firefox # + # driven_by :selenium, using: :headless_chrome + # # driven_by :selenium, screen_size: [800, 800] def self.driven_by(driver, using: :chrome, screen_size: [1400, 1400], options: {}) self.driver = SystemTesting::Driver.new(driver, using: using, screen_size: screen_size, options: options) end driven_by :selenium + + ActiveSupport.run_load_hooks(:action_dispatch_system_test_case, self) end SystemTestCase.start_application diff --git a/actionpack/lib/action_dispatch/system_testing/driver.rb b/actionpack/lib/action_dispatch/system_testing/driver.rb index 5cf17883f7..2687772b4b 100644 --- a/actionpack/lib/action_dispatch/system_testing/driver.rb +++ b/actionpack/lib/action_dispatch/system_testing/driver.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch module SystemTesting class Driver # :nodoc: @@ -9,23 +11,58 @@ module ActionDispatch end def use - register if selenium? + register if registerable? + setup end private - def selenium? - @name == :selenium + def registerable? + [:selenium, :poltergeist, :webkit].include?(@name) end def register Capybara.register_driver @name do |app| - Capybara::Selenium::Driver.new(app, { browser: @browser }.merge(@options)).tap do |driver| - driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size) + case @name + when :selenium then register_selenium(app) + when :poltergeist then register_poltergeist(app) + when :webkit then register_webkit(app) end end end + def browser_options + if @browser == :headless_chrome + browser_options = Selenium::WebDriver::Chrome::Options.new + browser_options.args << "--headless" + browser_options.args << "--disable-gpu" + + @options.merge(options: browser_options) + else + @options + end + end + + def browser + @browser == :headless_chrome ? :chrome : @browser + end + + def register_selenium(app) + Capybara::Selenium::Driver.new(app, { browser: browser }.merge(browser_options)).tap do |driver| + driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size) + end + end + + def register_poltergeist(app) + Capybara::Poltergeist::Driver.new(app, @options.merge(window_size: @screen_size)) + end + + def register_webkit(app) + Capybara::Webkit::Driver.new(app, Capybara::Webkit::Configuration.to_hash.merge(@options)).tap do |driver| + driver.resize_window_to(driver.current_window_handle, *@screen_size) + end + end + def setup Capybara.current_driver = @name end diff --git a/actionpack/lib/action_dispatch/system_testing/server.rb b/actionpack/lib/action_dispatch/system_testing/server.rb index 4a214ef713..8f1b6725b1 100644 --- a/actionpack/lib/action_dispatch/system_testing/server.rb +++ b/actionpack/lib/action_dispatch/system_testing/server.rb @@ -1,27 +1,26 @@ -require "rack/handler/puma" +# frozen_string_literal: true module ActionDispatch module SystemTesting class Server # :nodoc: + class << self + attr_accessor :silence_puma + end + + self.silence_puma = false + def run - register setup end private - def register - Capybara.register_server :rails_puma do |app, port, host| - Rack::Handler::Puma.run(app, Port: port, Threads: "0:1") - end - end - def setup set_server set_port end def set_server - Capybara.server = :rails_puma + Capybara.server = :puma, { Silent: self.class.silence_puma } end def set_port diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb index 859d68e475..6c337cdc31 100644 --- a/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch module SystemTesting module TestHelpers @@ -14,12 +16,12 @@ module ActionDispatch # You can set the +RAILS_SYSTEM_TESTING_SCREENSHOT+ environment variable to # control the output. Possible values are: # * [+inline+ (default)] display the screenshot in the terminal using the - # iTerm image protocol (http://iterm2.com/documentation-images.html). + # iTerm image protocol (https://iterm2.com/documentation-images.html). # * [+simple+] only display the screenshot path. # This is the default value if the +CI+ environment variables # is defined. # * [+artifact+] display the screenshot in the terminal, using the terminal - # artifact format (http://buildkite.github.io/terminal/inline-images/). + # artifact format (https://buildkite.github.io/terminal/inline-images/). def take_screenshot save_image puts display_image @@ -42,11 +44,15 @@ module ActionDispatch end def image_path - "tmp/screenshots/#{image_name}.png" + @image_path ||= absolute_image_path.relative_path_from(Pathname.pwd).to_s + end + + def absolute_image_path + Rails.root.join("tmp/screenshots/#{image_name}.png") end def save_image - page.save_screenshot(Rails.root.join(image_path)) + page.save_screenshot(absolute_image_path) end def output_type @@ -63,14 +69,14 @@ module ActionDispatch end def display_image - message = "[Screenshot]: #{image_path}\n" + message = "[Screenshot]: #{image_path}\n".dup case output_type when "artifact" - message << "\e]1338;url=artifact://#{image_path}\a\n" + message << "\e]1338;url=artifact://#{absolute_image_path}\a\n" when "inline" - name = inline_base64(File.basename(image_path)) - image = inline_base64(File.read(image_path)) + name = inline_base64(File.basename(absolute_image_path)) + image = inline_base64(File.read(absolute_image_path)) message << "\e]1337;File=name=#{name};height=400px;inline=1:#{image}\a\n" end diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb index 187ba2cc5f..ffa85f4e14 100644 --- a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb @@ -1,8 +1,15 @@ +# frozen_string_literal: true + module ActionDispatch module SystemTesting module TestHelpers module SetupAndTeardown # :nodoc: - DEFAULT_HOST = "127.0.0.1" + DEFAULT_HOST = "http://127.0.0.1" + + def host!(host) + super + Capybara.app_host = host + end def before_setup host! DEFAULT_HOST diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb new file mode 100644 index 0000000000..d64be3b3d9 --- /dev/null +++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module ActionDispatch + module SystemTesting + module TestHelpers + module UndefMethods # :nodoc: + extend ActiveSupport::Concern + included do + METHODS = %i(get post put patch delete).freeze + + METHODS.each do |verb| + undef_method verb + end + + def method_missing(method, *args, &block) + if METHODS.include?(method) + raise NoMethodError, "System tests cannot make direct requests via ##{method}; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information." + else + super + end + end + end + end + end + end +end diff --git a/actionpack/lib/action_dispatch/testing/assertion_response.rb b/actionpack/lib/action_dispatch/testing/assertion_response.rb index c37726957e..dc019db6ac 100644 --- a/actionpack/lib/action_dispatch/testing/assertion_response.rb +++ b/actionpack/lib/action_dispatch/testing/assertion_response.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch # This is a class that abstracts away an asserted response. It purposely # does not inherit from Response because it doesn't need it. That means it diff --git a/actionpack/lib/action_dispatch/testing/assertions.rb b/actionpack/lib/action_dispatch/testing/assertions.rb index 4ea18d671d..08c2969685 100644 --- a/actionpack/lib/action_dispatch/testing/assertions.rb +++ b/actionpack/lib/action_dispatch/testing/assertions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails-dom-testing" module ActionDispatch diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index 1baf979ac9..98b1965d22 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch module Assertions # A small suite of assertions that test responses from \Rails applications. @@ -79,7 +81,7 @@ module ActionDispatch def generate_response_message(expected, actual = @response.response_code) "Expected response to be a <#{code_with_name(expected)}>,"\ " but was a <#{code_with_name(actual)}>" - .concat(location_if_redirected).concat(response_body_if_short) + .dup.concat(location_if_redirected).concat(response_body_if_short) end def response_body_if_short diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 8645df4370..5390581139 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "uri" require "active_support/core_ext/hash/indifferent_access" require "active_support/core_ext/string/access" diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 2e2db98ad6..7171b6942c 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "stringio" require "uri" require "active_support/core_ext/kernel/singleton_class" @@ -10,38 +12,38 @@ require "action_dispatch/testing/request_encoder" module ActionDispatch module Integration #:nodoc: module RequestHelpers - # Performs a GET request with the given parameters. See +#process+ for more - # details. + # Performs a GET request with the given parameters. See ActionDispatch::Integration::Session#process + # for more details. def get(path, **args) process(:get, path, **args) end - # Performs a POST request with the given parameters. See +#process+ for more - # details. + # Performs a POST request with the given parameters. See ActionDispatch::Integration::Session#process + # for more details. def post(path, **args) process(:post, path, **args) end - # Performs a PATCH request with the given parameters. See +#process+ for more - # details. + # Performs a PATCH request with the given parameters. See ActionDispatch::Integration::Session#process + # for more details. def patch(path, **args) process(:patch, path, **args) end - # Performs a PUT request with the given parameters. See +#process+ for more - # details. + # Performs a PUT request with the given parameters. See ActionDispatch::Integration::Session#process + # for more details. def put(path, **args) process(:put, path, **args) end - # Performs a DELETE request with the given parameters. See +#process+ for - # more details. + # Performs a DELETE request with the given parameters. See ActionDispatch::Integration::Session#process + # for more details. def delete(path, **args) process(:delete, path, **args) end - # Performs a HEAD request with the given parameters. See +#process+ for more - # details. + # Performs a HEAD request with the given parameters. See ActionDispatch::Integration::Session#process + # for more details. def head(path, *args) process(:head, path, *args) end @@ -338,8 +340,7 @@ module ActionDispatch @integration_session = nil end - %w(get post patch put head delete cookies assigns - xml_http_request xhr get_via_redirect post_via_redirect).each do |method| + %w(get post patch put head delete cookies assigns follow_redirect!).each do |method| define_method(method) do |*args| # reset the html_document variable, except for cookies/assigns calls unless method == "cookies" || method == "assigns" @@ -385,14 +386,15 @@ module ActionDispatch integration_session.default_url_options = options end - def respond_to_missing?(method, include_private = false) - integration_session.respond_to?(method, include_private) || super + private + def respond_to_missing?(method, _) + integration_session.respond_to?(method) || super end # Delegate unhandled messages to the current session instance. - def method_missing(sym, *args, &block) - if integration_session.respond_to?(sym) - integration_session.__send__(sym, *args, &block).tap do + def method_missing(method, *args, &block) + if integration_session.respond_to?(method) + integration_session.public_send(method, *args, &block).tap do copy_session_variables! end else diff --git a/actionpack/lib/action_dispatch/testing/request_encoder.rb b/actionpack/lib/action_dispatch/testing/request_encoder.rb index 8c27e9ecb7..01246b7a2e 100644 --- a/actionpack/lib/action_dispatch/testing/request_encoder.rb +++ b/actionpack/lib/action_dispatch/testing/request_encoder.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionDispatch class RequestEncoder # :nodoc: class IdentityEncoder diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb index 0282eb15c3..8ac50c730d 100644 --- a/actionpack/lib/action_dispatch/testing/test_process.rb +++ b/actionpack/lib/action_dispatch/testing/test_process.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_dispatch/middleware/cookies" require "action_dispatch/middleware/flash" diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb index ec949c869b..6c5b7af50e 100644 --- a/actionpack/lib/action_dispatch/testing/test_request.rb +++ b/actionpack/lib/action_dispatch/testing/test_request.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/hash/indifferent_access" require "rack/utils" @@ -9,7 +11,7 @@ module ActionDispatch "HTTP_USER_AGENT" => "Rails Testing", ) - # Create a new test request with default `env` values. + # Create a new test request with default +env+ values. def self.create(env = {}) env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application env["rack.request.cookie_hash"] ||= {}.with_indifferent_access diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb index 5c89f9c75e..1e6b21f235 100644 --- a/actionpack/lib/action_dispatch/testing/test_response.rb +++ b/actionpack/lib/action_dispatch/testing/test_response.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_dispatch/testing/request_encoder" module ActionDispatch @@ -18,13 +20,31 @@ module ActionDispatch end # Was the response successful? - alias_method :success?, :successful? + def success? + ActiveSupport::Deprecation.warn(<<-MSG.squish) + The success? predicate is deprecated and will be removed in Rails 6.0. + Please use successful? as provided by Rack::Response::Helpers. + MSG + successful? + end # Was the URL not found? - alias_method :missing?, :not_found? + def missing? + ActiveSupport::Deprecation.warn(<<-MSG.squish) + The missing? predicate is deprecated and will be removed in Rails 6.0. + Please use not_found? as provided by Rack::Response::Helpers. + MSG + not_found? + end # Was there a server-side error? - alias_method :error?, :server_error? + def error? + ActiveSupport::Deprecation.warn(<<-MSG.squish) + The error? predicate is deprecated and will be removed in Rails 6.0. + Please use server_error? as provided by Rack::Response::Helpers. + MSG + server_error? + end def parsed_body @parsed_body ||= @response_parser.call(body) diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb index eec622e085..95fdd3affb 100644 --- a/actionpack/lib/action_pack.rb +++ b/actionpack/lib/action_pack.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- # Copyright (c) 2004-2017 David Heinemeier Hansson # diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb index fddc3033d5..28bc153f4d 100644 --- a/actionpack/lib/action_pack/gem_version.rb +++ b/actionpack/lib/action_pack/gem_version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionPack # Returns the version of the currently loaded Action Pack as a <tt>Gem::Version</tt> def self.gem_version diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index 3d96158431..fd039fe140 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "gem_version" module ActionPack diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb index 9c2261bf76..fdc09bd951 100644 --- a/actionpack/test/abstract/callbacks_test.rb +++ b/actionpack/test/abstract/callbacks_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module AbstractController @@ -42,7 +44,7 @@ module AbstractController def aroundz @aroundz = "FIRST" yield - @aroundz << "SECOND" + @aroundz += "SECOND" end def index diff --git a/actionpack/test/abstract/collector_test.rb b/actionpack/test/abstract/collector_test.rb index 1cd3526483..a4770b66e1 100644 --- a/actionpack/test/abstract/collector_test.rb +++ b/actionpack/test/abstract/collector_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module AbstractController diff --git a/actionpack/test/abstract/translation_test.rb b/actionpack/test/abstract/translation_test.rb index 0c4071df8d..7138044c03 100644 --- a/actionpack/test/abstract/translation_test.rb +++ b/actionpack/test/abstract/translation_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module AbstractController @@ -62,6 +64,7 @@ module AbstractController def test_default_translation @controller.stub :action_name, :index do assert_equal "bar", @controller.t("one.two") + assert_equal "baz", @controller.t(".twoz", default: ["baz", :twoz]) end end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 4185ce1a1f..5262e85a28 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -1,6 +1,8 @@ -$:.unshift(File.dirname(__FILE__) + "/lib") -$:.unshift(File.dirname(__FILE__) + "/fixtures/helpers") -$:.unshift(File.dirname(__FILE__) + "/fixtures/alternate_helpers") +# frozen_string_literal: true + +$:.unshift File.expand_path("lib", __dir__) +$:.unshift File.expand_path("fixtures/helpers", __dir__) +$:.unshift File.expand_path("fixtures/alternate_helpers", __dir__) require "active_support/core_ext/kernel/reporting" @@ -42,7 +44,7 @@ module Rails @_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "test") end - def root; end; + def root; end end end @@ -56,7 +58,7 @@ ActiveSupport::Deprecation.debug = true # Disable available locale checks to avoid warnings running the test suite. I18n.enforce_available_locales = false -FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), "fixtures") +FIXTURE_LOAD_PATH = File.join(__dir__, "fixtures") SharedTestRoutes = ActionDispatch::Routing::RouteSet.new @@ -156,7 +158,7 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase end def with_autoload_path(path) - path = File.join(File.dirname(__FILE__), "fixtures", path) + path = File.join(__dir__, "fixtures", path) if ActiveSupport::Dependencies.autoload_paths.include?(path) yield else @@ -175,7 +177,7 @@ end class Rack::TestCase < ActionDispatch::IntegrationTest def self.testing(klass = nil) if klass - @testing = "/#{klass.name.underscore}".sub!(/_controller$/, "") + @testing = "/#{klass.name.underscore}".sub(/_controller$/, "") else @testing end @@ -447,3 +449,7 @@ end class DrivenBySeleniumWithChrome < ActionDispatch::SystemTestCase driven_by :selenium, using: :chrome end + +class DrivenBySeleniumWithHeadlessChrome < ActionDispatch::SystemTestCase + driven_by :selenium, using: :headless_chrome +end diff --git a/actionpack/test/assertions/response_assertions_test.rb b/actionpack/test/assertions/response_assertions_test.rb index 14a04ccdb1..261579dce5 100644 --- a/actionpack/test/assertions/response_assertions_test.rb +++ b/actionpack/test/assertions/response_assertions_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_dispatch/testing/assertions/response" diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index 9ab152fc5c..f9a037e3cc 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "controller/fake_controllers" @@ -83,7 +85,7 @@ class ActionPackAssertionsController < ActionController::Base end def render_file_absolute_path - render file: File.expand_path("../../../README.rdoc", __FILE__) + render file: File.expand_path("../../README.rdoc", __dir__) end def render_file_relative_path diff --git a/actionpack/test/controller/api/conditional_get_test.rb b/actionpack/test/controller/api/conditional_get_test.rb index 7b70829101..fd1997f26c 100644 --- a/actionpack/test/controller/api/conditional_get_test.rb +++ b/actionpack/test/controller/api/conditional_get_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "active_support/core_ext/integer/time" require "active_support/core_ext/numeric/time" diff --git a/actionpack/test/controller/api/data_streaming_test.rb b/actionpack/test/controller/api/data_streaming_test.rb index f15b78d102..6446ff9e40 100644 --- a/actionpack/test/controller/api/data_streaming_test.rb +++ b/actionpack/test/controller/api/data_streaming_test.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + require "abstract_unit" module TestApiFileUtils - def file_path() File.expand_path(__FILE__) end + def file_path() __FILE__ end def file_data() @data ||= File.open(file_path, "rb") { |f| f.read } end end diff --git a/actionpack/test/controller/api/force_ssl_test.rb b/actionpack/test/controller/api/force_ssl_test.rb index d239964e4a..07459c3753 100644 --- a/actionpack/test/controller/api/force_ssl_test.rb +++ b/actionpack/test/controller/api/force_ssl_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class ForceSSLApiController < ActionController::API diff --git a/actionpack/test/controller/api/implicit_render_test.rb b/actionpack/test/controller/api/implicit_render_test.rb index b51ee0cf42..288fb333b0 100644 --- a/actionpack/test/controller/api/implicit_render_test.rb +++ b/actionpack/test/controller/api/implicit_render_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class ImplicitRenderAPITestController < ActionController::API diff --git a/actionpack/test/controller/api/params_wrapper_test.rb b/actionpack/test/controller/api/params_wrapper_test.rb index a1da852040..814c24bfd8 100644 --- a/actionpack/test/controller/api/params_wrapper_test.rb +++ b/actionpack/test/controller/api/params_wrapper_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class ParamsWrapperForApiTest < ActionController::TestCase diff --git a/actionpack/test/controller/api/redirect_to_test.rb b/actionpack/test/controller/api/redirect_to_test.rb index ab14409f40..f8230dd6a9 100644 --- a/actionpack/test/controller/api/redirect_to_test.rb +++ b/actionpack/test/controller/api/redirect_to_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class RedirectToApiController < ActionController::API diff --git a/actionpack/test/controller/api/renderers_test.rb b/actionpack/test/controller/api/renderers_test.rb index 04e34a1f8f..e7a9a4b2da 100644 --- a/actionpack/test/controller/api/renderers_test.rb +++ b/actionpack/test/controller/api/renderers_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "active_support/core_ext/hash/conversions" diff --git a/actionpack/test/controller/api/url_for_test.rb b/actionpack/test/controller/api/url_for_test.rb index cb4ae7a88a..aa3428bc85 100644 --- a/actionpack/test/controller/api/url_for_test.rb +++ b/actionpack/test/controller/api/url_for_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class UrlForApiController < ActionController::API diff --git a/actionpack/test/controller/api/with_cookies_test.rb b/actionpack/test/controller/api/with_cookies_test.rb index 8928237dfd..1a6e12a4f3 100644 --- a/actionpack/test/controller/api/with_cookies_test.rb +++ b/actionpack/test/controller/api/with_cookies_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class WithCookiesController < ActionController::API diff --git a/actionpack/test/controller/api/with_helpers_test.rb b/actionpack/test/controller/api/with_helpers_test.rb index 06db949153..00179d3505 100644 --- a/actionpack/test/controller/api/with_helpers_test.rb +++ b/actionpack/test/controller/api/with_helpers_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ApiWithHelper diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index 4e969fac07..9ac82c0d65 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "active_support/logger" require "controller/fake_models" diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index fa8d9dc09a..3557f9f888 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + require "fileutils" require "abstract_unit" require "lib/controller/fake_models" CACHE_DIR = "test_cache" # Don't change '/../temp/' cavalierly or you might hose something you don't want hosed -FILE_STORE_PATH = File.join(File.dirname(__FILE__), "/../temp/", CACHE_DIR) +FILE_STORE_PATH = File.join(__dir__, "../temp/", CACHE_DIR) class FragmentCachingMetalTestController < ActionController::Metal abstract! @@ -26,10 +28,6 @@ class FragmentCachingMetalTest < ActionController::TestCase @controller.request = @request @controller.response = @response end - - def test_fragment_cache_key - assert_equal "views/what a key", @controller.fragment_cache_key("what a key") - end end class CachingController < ActionController::Base @@ -43,6 +41,8 @@ class FragmentCachingTestController < CachingController end class FragmentCachingTest < ActionController::TestCase + ModelWithKeyAndVersion = Struct.new(:cache_key, :cache_version) + def setup super @store = ActiveSupport::Cache::MemoryStore.new @@ -53,12 +53,25 @@ class FragmentCachingTest < ActionController::TestCase @controller.params = @params @controller.request = @request @controller.response = @response + + @m1v1 = ModelWithKeyAndVersion.new("model/1", "1") + @m1v2 = ModelWithKeyAndVersion.new("model/1", "2") + @m2v1 = ModelWithKeyAndVersion.new("model/2", "1") + @m2v2 = ModelWithKeyAndVersion.new("model/2", "2") end def test_fragment_cache_key - assert_equal "views/what a key", @controller.fragment_cache_key("what a key") - assert_equal "views/test.host/fragment_caching_test/some_action", - @controller.fragment_cache_key(controller: "fragment_caching_test", action: "some_action") + assert_deprecated do + assert_equal "views/what a key", @controller.fragment_cache_key("what a key") + assert_equal "views/test.host/fragment_caching_test/some_action", + @controller.fragment_cache_key(controller: "fragment_caching_test", action: "some_action") + end + end + + def test_combined_fragment_cache_key + assert_equal [ :views, "what a key" ], @controller.combined_fragment_cache_key("what a key") + assert_equal [ :views, "test.host/fragment_caching_test/some_action" ], + @controller.combined_fragment_cache_key(controller: "fragment_caching_test", action: "some_action") end def test_read_fragment_with_caching_enabled @@ -72,6 +85,12 @@ class FragmentCachingTest < ActionController::TestCase assert_nil @controller.read_fragment("name") end + def test_read_fragment_with_versioned_model + @controller.write_fragment([ "stuff", @m1v1 ], "hello") + assert_equal "hello", @controller.read_fragment([ "stuff", @m1v1 ]) + assert_nil @controller.read_fragment([ "stuff", @m1v2 ]) + end + def test_fragment_exist_with_caching_enabled @store.write("views/name", "value") assert @controller.fragment_exist?("name") @@ -198,7 +217,7 @@ CACHED assert_equal expected_body, @response.body assert_equal "This bit's fragment cached", - @store.read("views/test.host/functional_caching/fragment_cached/#{template_digest("functional_caching/fragment_cached")}") + @store.read("views/functional_caching/fragment_cached:#{template_digest("functional_caching/fragment_cached")}/fragment") end def test_fragment_caching_in_partials @@ -207,7 +226,7 @@ CACHED assert_match(/Old fragment caching in a partial/, @response.body) assert_match("Old fragment caching in a partial", - @store.read("views/test.host/functional_caching/html_fragment_cached_with_partial/#{template_digest("functional_caching/_partial")}")) + @store.read("views/functional_caching/_partial:#{template_digest("functional_caching/_partial")}/test.host/functional_caching/html_fragment_cached_with_partial")) end def test_skipping_fragment_cache_digesting @@ -237,7 +256,7 @@ CACHED assert_match(/Some inline content/, @response.body) assert_match(/Some cached content/, @response.body) assert_match("Some cached content", - @store.read("views/test.host/functional_caching/inline_fragment_cached/#{template_digest("functional_caching/inline_fragment_cached")}")) + @store.read("views/functional_caching/inline_fragment_cached:#{template_digest("functional_caching/inline_fragment_cached")}/test.host/functional_caching/inline_fragment_cached")) end def test_fragment_cache_instrumentation @@ -264,7 +283,7 @@ CACHED assert_equal expected_body, @response.body assert_equal "<p>ERB</p>", - @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached")}") + @store.read("views/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached")}/fragment") end def test_xml_formatted_fragment_caching @@ -275,7 +294,7 @@ CACHED assert_equal expected_body, @response.body assert_equal " <p>Builder</p>\n", - @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached")}") + @store.read("views/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached")}/fragment") end def test_fragment_caching_with_variant @@ -286,7 +305,7 @@ CACHED assert_equal expected_body, @response.body assert_equal "<p>PHONE</p>", - @store.read("views/test.host/functional_caching/formatted_fragment_cached_with_variant/#{template_digest("functional_caching/formatted_fragment_cached_with_variant")}") + @store.read("views/functional_caching/formatted_fragment_cached_with_variant:#{template_digest("functional_caching/formatted_fragment_cached_with_variant")}/fragment") end private @@ -298,7 +317,7 @@ end class CacheHelperOutputBufferTest < ActionController::TestCase class MockController def read_fragment(name, options) - return false + false end def write_fragment(name, fragment, options) @@ -314,9 +333,9 @@ class CacheHelperOutputBufferTest < ActionController::TestCase output_buffer = ActionView::OutputBuffer.new controller = MockController.new cache_helper = Class.new do - def self.controller; end; - def self.output_buffer; end; - def self.output_buffer=; end; + def self.controller; end + def self.output_buffer; end + def self.output_buffer=; end end cache_helper.extend(ActionView::Helpers::CacheHelper) @@ -335,9 +354,9 @@ class CacheHelperOutputBufferTest < ActionController::TestCase output_buffer = ActiveSupport::SafeBuffer.new controller = MockController.new cache_helper = Class.new do - def self.controller; end; - def self.output_buffer; end; - def self.output_buffer=; end; + def self.controller; end + def self.output_buffer; end + def self.output_buffer=; end end cache_helper.extend(ActionView::Helpers::CacheHelper) @@ -412,7 +431,7 @@ class CollectionCacheTest < ActionController::TestCase def test_collection_fetches_cached_views get :index assert_equal 1, @controller.partial_rendered_times - assert_customer_cached "david/1", "david, 1" + assert_match "david, 1", ActionView::PartialRenderer.collection_cache.read("views/customers/_customer:7c228ab609f0baf0b1f2367469210937/david/1") get :index assert_equal 1, @controller.partial_rendered_times @@ -444,14 +463,8 @@ class CollectionCacheTest < ActionController::TestCase def test_caching_with_callable_cache_key get :index_with_callable_cache_key - assert_customer_cached "cached_david", "david, 1" + assert_match "david, 1", ActionView::PartialRenderer.collection_cache.read("views/customers/_customer:7c228ab609f0baf0b1f2367469210937/cached_david") end - - private - def assert_customer_cached(key, content) - assert_match content, - ActionView::PartialRenderer.collection_cache.read("views/#{key}/7c228ab609f0baf0b1f2367469210937") - end end class FragmentCacheKeyTestController < CachingController @@ -470,11 +483,21 @@ class FragmentCacheKeyTest < ActionController::TestCase @controller.cache_store = @store end - def test_fragment_cache_key + def test_combined_fragment_cache_key @controller.account_id = "123" - assert_equal "views/v1/123/what a key", @controller.fragment_cache_key("what a key") + assert_equal [ :views, "v1", "123", "what a key" ], @controller.combined_fragment_cache_key("what a key") @controller.account_id = nil - assert_equal "views/v1//what a key", @controller.fragment_cache_key("what a key") + assert_equal [ :views, "v1", "what a key" ], @controller.combined_fragment_cache_key("what a key") + end + + def test_combined_fragment_cache_key_with_envs + ENV["RAILS_APP_VERSION"] = "55" + assert_equal [ :views, "55", "v1", "what a key" ], @controller.combined_fragment_cache_key("what a key") + + ENV["RAILS_CACHE_ID"] = "66" + assert_equal [ :views, "66", "v1", "what a key" ], @controller.combined_fragment_cache_key("what a key") + ensure + ENV["RAILS_CACHE_ID"] = ENV["RAILS_APP_VERSION"] = nil end end diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb index fcb2632b80..636b025f2c 100644 --- a/actionpack/test/controller/content_type_test.rb +++ b/actionpack/test/controller/content_type_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class OldContentTypeController < ActionController::Base diff --git a/actionpack/test/controller/default_url_options_with_before_action_test.rb b/actionpack/test/controller/default_url_options_with_before_action_test.rb index e3fe7a6495..fc5b8288cd 100644 --- a/actionpack/test/controller/default_url_options_with_before_action_test.rb +++ b/actionpack/test/controller/default_url_options_with_before_action_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class ControllerWithBeforeActionAndDefaultUrlOptions < ActionController::Base diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 5f1463cfa8..9f0a9dec7a 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class ActionController::Base @@ -346,7 +348,7 @@ class FilterTest < ActionController::TestCase class AroundFilter def before(controller) @execution_log = "before" - controller.class.execution_log << " before aroundfilter " if controller.respond_to? :execution_log + controller.class.execution_log += " before aroundfilter " if controller.respond_to? :execution_log controller.instance_variable_set(:"@before_ran", true) end diff --git a/actionpack/test/controller/flash_hash_test.rb b/actionpack/test/controller/flash_hash_test.rb index 45b598a594..f31a4d9329 100644 --- a/actionpack/test/controller/flash_hash_test.rb +++ b/actionpack/test/controller/flash_hash_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ActionDispatch diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index dc641c19ab..34bc2c0caa 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + require "abstract_unit" -require "active_support/key_generator" +require "active_support/messages/rotation_configuration" class FlashTest < ActionController::TestCase class TestController < ActionController::Base @@ -241,6 +243,7 @@ end class FlashIntegrationTest < ActionDispatch::IntegrationTest SessionKey = "_myapp_session" Generator = ActiveSupport::LegacyKeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33") + Rotations = ActiveSupport::Messages::RotationConfiguration.new class TestController < ActionController::Base add_flash_types :bar @@ -346,6 +349,7 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest args[0] ||= {} args[0][:env] ||= {} args[0][:env]["action_dispatch.key_generator"] ||= Generator + args[0][:env]["action_dispatch.cookies_rotations"] = Rotations super(path, *args) end diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb index 2b3859aa57..84ac1fda3c 100644 --- a/actionpack/test/controller/force_ssl_test.rb +++ b/actionpack/test/controller/force_ssl_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class ForceSSLController < ActionController::Base diff --git a/actionpack/test/controller/form_builder_test.rb b/actionpack/test/controller/form_builder_test.rb index 5a3dc2ee03..2db0834c5e 100644 --- a/actionpack/test/controller/form_builder_test.rb +++ b/actionpack/test/controller/form_builder_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class FormBuilderController < ActionController::Base diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb index 4c6a772062..de8072a994 100644 --- a/actionpack/test/controller/helper_test.rb +++ b/actionpack/test/controller/helper_test.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require "abstract_unit" -ActionController::Base.helpers_path = File.expand_path("../../fixtures/helpers", __FILE__) +ActionController::Base.helpers_path = File.expand_path("../fixtures/helpers", __dir__) module Fun class GamesController < ActionController::Base @@ -48,7 +50,7 @@ end class HelpersPathsController < ActionController::Base paths = ["helpers2_pack", "helpers1_pack"].map do |path| - File.join(File.expand_path("../../fixtures", __FILE__), path) + File.join(File.expand_path("../fixtures", __dir__), path) end $:.unshift(*paths) @@ -61,7 +63,7 @@ class HelpersPathsController < ActionController::Base end class HelpersTypoController < ActionController::Base - path = File.expand_path("../../fixtures/helpers_typo", __FILE__) + path = File.expand_path("../fixtures/helpers_typo", __dir__) $:.unshift(path) self.helpers_path = path end @@ -106,7 +108,7 @@ class HelperTest < ActiveSupport::TestCase def setup # Increment symbol counter. - @symbol = (@@counter ||= "A0").succ!.dup + @symbol = (@@counter ||= "A0").succ.dup # Generate new controller class. controller_class_name = "Helper#{@symbol}Controller" @@ -178,7 +180,7 @@ class HelperTest < ActiveSupport::TestCase end def test_all_helpers_with_alternate_helper_dir - @controller_class.helpers_path = File.expand_path("../../fixtures/alternate_helpers", __FILE__) + @controller_class.helpers_path = File.expand_path("../fixtures/alternate_helpers", __dir__) # Reload helpers @controller_class._helpers = Module.new diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb index d9ae787689..1544a627ee 100644 --- a/actionpack/test/controller/http_basic_authentication_test.rb +++ b/actionpack/test/controller/http_basic_authentication_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class HttpBasicAuthenticationTest < ActionController::TestCase diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb index 0b59e123d7..76ff784926 100644 --- a/actionpack/test/controller/http_digest_authentication_test.rb +++ b/actionpack/test/controller/http_digest_authentication_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "active_support/key_generator" diff --git a/actionpack/test/controller/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb index 09d2793c9a..672aa1351c 100644 --- a/actionpack/test/controller/http_token_authentication_test.rb +++ b/actionpack/test/controller/http_token_authentication_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class HttpTokenAuthenticationTest < ActionController::TestCase @@ -148,7 +150,7 @@ class HttpTokenAuthenticationTest < ActionController::TestCase end test "token_and_options returns empty string with empty token" do - token = "" + token = "".dup actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first expected = token assert_equal(expected, actual) diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index 57f58fd835..fd1c5e693f 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "controller/fake_controllers" require "rails/engine" @@ -335,6 +337,18 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest end end + def test_redirect_reset_html_document + with_test_route_set do + get "/redirect" + previous_html_document = html_document + + follow_redirect! + + assert_response :ok + refute_same previous_html_document, html_document + end + end + def test_xml_http_request_get with_test_route_set do get "/get", xhr: true @@ -1091,7 +1105,7 @@ class IntegrationFileUploadTest < ActionDispatch::IntegrationTest end def self.fixture_path - File.dirname(__FILE__) + "/../fixtures/multipart" + File.expand_path("../fixtures/multipart", __dir__) end routes.draw do diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb index 581081dd07..8cfb43a6bc 100644 --- a/actionpack/test/controller/live_stream_test.rb +++ b/actionpack/test/controller/live_stream_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "timeout" require "concurrent/atomic/count_down_latch" @@ -28,7 +30,7 @@ module ActionController def sse_with_retry sse = SSE.new(response.stream, retry: 1000) sse.write("{\"name\":\"John\"}") - sse.write({ name: "Ryan" }, retry: 1500) + sse.write({ name: "Ryan" }, { retry: 1500 }) ensure sse.close end @@ -36,7 +38,7 @@ module ActionController def sse_with_id sse = SSE.new(response.stream) sse.write("{\"name\":\"John\"}", id: 1) - sse.write({ name: "Ryan" }, id: 2) + sse.write({ name: "Ryan" }, { id: 2 }) ensure sse.close end @@ -152,7 +154,7 @@ module ActionController end def write_sleep_autoload - path = File.join(File.dirname(__FILE__), "../fixtures") + path = File.expand_path("../fixtures", __dir__) ActiveSupport::Dependencies.autoload_paths << path response.headers["Content-Type"] = "text/event-stream" diff --git a/actionpack/test/controller/localized_templates_test.rb b/actionpack/test/controller/localized_templates_test.rb index 0f2242b693..d84a76fb46 100644 --- a/actionpack/test/controller/localized_templates_test.rb +++ b/actionpack/test/controller/localized_templates_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class LocalizedController < ActionController::Base diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb index 45a120acb6..be455642de 100644 --- a/actionpack/test/controller/log_subscriber_test.rb +++ b/actionpack/test/controller/log_subscriber_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "active_support/log_subscriber/test_helper" require "action_controller/log_subscriber" @@ -96,7 +98,7 @@ class ACLogSubscriberTest < ActionController::TestCase @old_logger = ActionController::Base.logger - @cache_path = File.join Dir.tmpdir, Dir::Tmpname.make_tmpname("tmp", "cache") + @cache_path = Dir.mktmpdir(%w[tmp cache]) @controller.cache_store = :file_store, @cache_path ActionController::LogSubscriber.attach_to :action_controller end diff --git a/actionpack/test/controller/metal/renderers_test.rb b/actionpack/test/controller/metal/renderers_test.rb index 7dc3dd6a6d..5f0d125128 100644 --- a/actionpack/test/controller/metal/renderers_test.rb +++ b/actionpack/test/controller/metal/renderers_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "active_support/core_ext/hash/conversions" diff --git a/actionpack/test/controller/metal_test.rb b/actionpack/test/controller/metal_test.rb index e16452ed6f..c235c9df86 100644 --- a/actionpack/test/controller/metal_test.rb +++ b/actionpack/test/controller/metal_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class MetalControllerInstanceTests < ActiveSupport::TestCase diff --git a/actionpack/test/controller/mime/accept_format_test.rb b/actionpack/test/controller/mime/accept_format_test.rb index a22fa39051..eed671d593 100644 --- a/actionpack/test/controller/mime/accept_format_test.rb +++ b/actionpack/test/controller/mime/accept_format_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class StarStarMimeController < ActionController::Base @@ -29,7 +31,7 @@ class StarStarMimeControllerTest < ActionController::TestCase end class AbstractPostController < ActionController::Base - self.view_paths = File.dirname(__FILE__) + "/../../fixtures/post_test/" + self.view_paths = File.expand_path("../../fixtures/post_test", __dir__) end # For testing layouts which are set automatically diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb index 61bd5c80c4..f9ffd5f54c 100644 --- a/actionpack/test/controller/mime/respond_to_test.rb +++ b/actionpack/test/controller/mime/respond_to_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "active_support/log_subscriber/test_helper" diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb index 054757fab3..b049022a06 100644 --- a/actionpack/test/controller/new_base/bare_metal_test.rb +++ b/actionpack/test/controller/new_base/bare_metal_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module BareMetalTest @@ -11,7 +13,7 @@ module BareMetalTest test "response body is a Rack-compatible response" do status, headers, body = BareController.action(:index).call(Rack::MockRequest.env_for("/")) assert_equal 200, status - string = "" + string = "".dup body.each do |part| assert part.is_a?(String), "Each part of the body must be a String" diff --git a/actionpack/test/controller/new_base/base_test.rb b/actionpack/test/controller/new_base/base_test.rb index b891df4c0f..280134f8d2 100644 --- a/actionpack/test/controller/new_base/base_test.rb +++ b/actionpack/test/controller/new_base/base_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" # Tests the controller dispatching happy path @@ -45,7 +47,6 @@ module Dispatching end class BaseTest < Rack::TestCase - # :api: plugin test "simple dispatching" do get "/dispatching/simple/index" @@ -54,14 +55,12 @@ module Dispatching assert_content_type "text/plain; charset=utf-8" end - # :api: plugin test "directly modifying response body" do get "/dispatching/simple/modify_response_body" assert_body "success" end - # :api: plugin test "directly modifying response body twice" do get "/dispatching/simple/modify_response_body_twice" diff --git a/actionpack/test/controller/new_base/content_negotiation_test.rb b/actionpack/test/controller/new_base/content_negotiation_test.rb index b870745031..7205e90176 100644 --- a/actionpack/test/controller/new_base/content_negotiation_test.rb +++ b/actionpack/test/controller/new_base/content_negotiation_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ContentNegotiation diff --git a/actionpack/test/controller/new_base/content_type_test.rb b/actionpack/test/controller/new_base/content_type_test.rb index 85089bafe2..d3ee4a8a6f 100644 --- a/actionpack/test/controller/new_base/content_type_test.rb +++ b/actionpack/test/controller/new_base/content_type_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ContentType diff --git a/actionpack/test/controller/new_base/middleware_test.rb b/actionpack/test/controller/new_base/middleware_test.rb index 0493291c03..df69650a7b 100644 --- a/actionpack/test/controller/new_base/middleware_test.rb +++ b/actionpack/test/controller/new_base/middleware_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module MiddlewareTest @@ -21,7 +23,7 @@ module MiddlewareTest def call(env) result = @app.call(env) - result[1]["Middleware-Order"] << "!" + result[1]["Middleware-Order"] += "!" result end end diff --git a/actionpack/test/controller/new_base/render_action_test.rb b/actionpack/test/controller/new_base/render_action_test.rb index 4b59a3d676..33b55dc5a8 100644 --- a/actionpack/test/controller/new_base/render_action_test.rb +++ b/actionpack/test/controller/new_base/render_action_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module RenderAction diff --git a/actionpack/test/controller/new_base/render_body_test.rb b/actionpack/test/controller/new_base/render_body_test.rb index b1467a0deb..d0b61f0665 100644 --- a/actionpack/test/controller/new_base/render_body_test.rb +++ b/actionpack/test/controller/new_base/render_body_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module RenderBody diff --git a/actionpack/test/controller/new_base/render_context_test.rb b/actionpack/test/controller/new_base/render_context_test.rb index 25b73ac78c..07fbadae9f 100644 --- a/actionpack/test/controller/new_base/render_context_test.rb +++ b/actionpack/test/controller/new_base/render_context_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" # This is testing the decoupling of view renderer and view context diff --git a/actionpack/test/controller/new_base/render_file_test.rb b/actionpack/test/controller/new_base/render_file_test.rb index 6d651e0104..de8af029e0 100644 --- a/actionpack/test/controller/new_base/render_file_test.rb +++ b/actionpack/test/controller/new_base/render_file_test.rb @@ -1,16 +1,18 @@ +# frozen_string_literal: true + require "abstract_unit" module RenderFile class BasicController < ActionController::Base - self.view_paths = File.dirname(__FILE__) + self.view_paths = __dir__ def index - render file: File.join(File.dirname(__FILE__), *%w[.. .. fixtures test hello_world]) + render file: File.expand_path("../../fixtures/test/hello_world", __dir__) end def with_instance_variables @secret = "in the sauce" - render file: File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_ivar") + render file: File.expand_path("../../fixtures/test/render_file_with_ivar", __dir__) end def relative_path @@ -25,11 +27,11 @@ module RenderFile def pathname @secret = "in the sauce" - render file: Pathname.new(File.dirname(__FILE__)).join(*%w[.. .. fixtures test dot.directory render_file_with_ivar]) + render file: Pathname.new(__dir__).join(*%w[.. .. fixtures test dot.directory render_file_with_ivar]) end def with_locals - path = File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_locals") + path = File.expand_path("../../fixtures/test/render_file_with_locals", __dir__) render file: path, locals: { secret: "in the sauce" } end end diff --git a/actionpack/test/controller/new_base/render_html_test.rb b/actionpack/test/controller/new_base/render_html_test.rb index 8019aa1eb5..4bea2ba2e9 100644 --- a/actionpack/test/controller/new_base/render_html_test.rb +++ b/actionpack/test/controller/new_base/render_html_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module RenderHtml diff --git a/actionpack/test/controller/new_base/render_implicit_action_test.rb b/actionpack/test/controller/new_base/render_implicit_action_test.rb index 796283466a..8c26d34b00 100644 --- a/actionpack/test/controller/new_base/render_implicit_action_test.rb +++ b/actionpack/test/controller/new_base/render_implicit_action_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module RenderImplicitAction @@ -6,7 +8,7 @@ module RenderImplicitAction "render_implicit_action/simple/hello_world.html.erb" => "Hello world!", "render_implicit_action/simple/hyphen-ated.html.erb" => "Hello hyphen-ated!", "render_implicit_action/simple/not_implemented.html.erb" => "Not Implemented" - ), ActionView::FileSystemResolver.new(File.expand_path("../../../controller", __FILE__))] + ), ActionView::FileSystemResolver.new(File.expand_path("../../controller", __dir__))] def hello_world() end end diff --git a/actionpack/test/controller/new_base/render_layout_test.rb b/actionpack/test/controller/new_base/render_layout_test.rb index 0a3809560e..806c6206dc 100644 --- a/actionpack/test/controller/new_base/render_layout_test.rb +++ b/actionpack/test/controller/new_base/render_layout_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ControllerLayouts diff --git a/actionpack/test/controller/new_base/render_partial_test.rb b/actionpack/test/controller/new_base/render_partial_test.rb index 4511826978..a0c7cbc686 100644 --- a/actionpack/test/controller/new_base/render_partial_test.rb +++ b/actionpack/test/controller/new_base/render_partial_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module RenderPartial diff --git a/actionpack/test/controller/new_base/render_plain_test.rb b/actionpack/test/controller/new_base/render_plain_test.rb index 44be8dd380..640979e4f5 100644 --- a/actionpack/test/controller/new_base/render_plain_test.rb +++ b/actionpack/test/controller/new_base/render_plain_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module RenderPlain diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb index 1177b8b03e..23dc6bca40 100644 --- a/actionpack/test/controller/new_base/render_streaming_test.rb +++ b/actionpack/test/controller/new_base/render_streaming_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module RenderStreaming diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb index 1102305f3e..14dc958475 100644 --- a/actionpack/test/controller/new_base/render_template_test.rb +++ b/actionpack/test/controller/new_base/render_template_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module RenderTemplate diff --git a/actionpack/test/controller/new_base/render_test.rb b/actionpack/test/controller/new_base/render_test.rb index cea3f9b5fd..eb29203f59 100644 --- a/actionpack/test/controller/new_base/render_test.rb +++ b/actionpack/test/controller/new_base/render_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module Render diff --git a/actionpack/test/controller/new_base/render_xml_test.rb b/actionpack/test/controller/new_base/render_xml_test.rb index 8bab413377..0dc16d64e2 100644 --- a/actionpack/test/controller/new_base/render_xml_test.rb +++ b/actionpack/test/controller/new_base/render_xml_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module RenderXml diff --git a/actionpack/test/controller/output_escaping_test.rb b/actionpack/test/controller/output_escaping_test.rb index c7047d95ae..e33a99068f 100644 --- a/actionpack/test/controller/output_escaping_test.rb +++ b/actionpack/test/controller/output_escaping_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class OutputEscapingTest < ActiveSupport::TestCase diff --git a/actionpack/test/controller/parameter_encoding_test.rb b/actionpack/test/controller/parameter_encoding_test.rb index 234d0bddd1..e2194e8974 100644 --- a/actionpack/test/controller/parameter_encoding_test.rb +++ b/actionpack/test/controller/parameter_encoding_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class ParameterEncodingController < ActionController::Base diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb index 7725c25e22..43cabae7d2 100644 --- a/actionpack/test/controller/parameters/accessors_test.rb +++ b/actionpack/test/controller/parameters/accessors_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_controller/metal/strong_parameters" require "active_support/core_ext/hash/transform_values" @@ -35,6 +37,11 @@ class ParametersAccessorsTest < ActiveSupport::TestCase assert @params.as_json.key? "person" end + test "to_s returns the string representation of the parameters hash" do + assert_equal '{"person"=>{"age"=>"32", "name"=>{"first"=>"David", "last"=>"Heinemeier Hansson"}, ' \ + '"addresses"=>[{"city"=>"Chicago", "state"=>"Illinois"}]}}', @params.to_s + end + test "each carries permitted status" do @params.permit! @params.each { |key, value| assert(value.permitted?) if key == "person" } diff --git a/actionpack/test/controller/parameters/always_permitted_parameters_test.rb b/actionpack/test/controller/parameters/always_permitted_parameters_test.rb index cd7c98f112..1e8b71d789 100644 --- a/actionpack/test/controller/parameters/always_permitted_parameters_test.rb +++ b/actionpack/test/controller/parameters/always_permitted_parameters_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_controller/metal/strong_parameters" diff --git a/actionpack/test/controller/parameters/dup_test.rb b/actionpack/test/controller/parameters/dup_test.rb index fb707a1354..f5833aff46 100644 --- a/actionpack/test/controller/parameters/dup_test.rb +++ b/actionpack/test/controller/parameters/dup_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_controller/metal/strong_parameters" require "active_support/core_ext/object/deep_dup" diff --git a/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb b/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb index c800c1d3df..fc9229ca1d 100644 --- a/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb +++ b/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_controller/metal/strong_parameters" diff --git a/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb b/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb index 88fb477c10..dcf848a620 100644 --- a/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb +++ b/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_controller/metal/strong_parameters" diff --git a/actionpack/test/controller/parameters/mutators_test.rb b/actionpack/test/controller/parameters/mutators_test.rb index e61bbdbe13..49dede03c2 100644 --- a/actionpack/test/controller/parameters/mutators_test.rb +++ b/actionpack/test/controller/parameters/mutators_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_controller/metal/strong_parameters" require "active_support/core_ext/hash/transform_values" @@ -25,6 +27,27 @@ class ParametersMutatorsTest < ActiveSupport::TestCase assert_not @params.delete(:person).permitted? end + test "delete returns the value when the key is present" do + assert_equal "32", @params[:person].delete(:age) + end + + test "delete removes the entry when the key present" do + @params[:person].delete(:age) + assert_not @params[:person].key?(:age) + end + + test "delete returns nil when the key is not present" do + assert_nil @params[:person].delete(:first_name) + end + + test "delete returns the value of the given block when the key is not present" do + assert_equal "David", @params[:person].delete(:first_name) { "David" } + end + + test "delete yields the key to the given block when the key is not present" do + assert_equal "first_name: David", @params[:person].delete(:first_name) { |k| "#{k}: David" } + end + test "delete_if retains permitted status" do @params.permit! assert @params.delete_if { |k| k == "person" }.permitted? diff --git a/actionpack/test/controller/parameters/nested_parameters_permit_test.rb b/actionpack/test/controller/parameters/nested_parameters_permit_test.rb index 00e591d5a7..c9fcc483ee 100644 --- a/actionpack/test/controller/parameters/nested_parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/nested_parameters_permit_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_controller/metal/strong_parameters" diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index ae2b45c9f0..ebdaca0162 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_dispatch/http/upload" require "action_controller/metal/strong_parameters" diff --git a/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb b/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb index 8fab7b28e9..4afd3da593 100644 --- a/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb +++ b/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_controller/metal/strong_parameters" diff --git a/actionpack/test/controller/parameters/serialization_test.rb b/actionpack/test/controller/parameters/serialization_test.rb index 6fba2fde91..823f01d82a 100644 --- a/actionpack/test/controller/parameters/serialization_test.rb +++ b/actionpack/test/controller/parameters/serialization_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_controller/metal/strong_parameters" require "active_support/core_ext/string/strip" diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb index 2a41d57b26..c4c74e8f2b 100644 --- a/actionpack/test/controller/params_wrapper_test.rb +++ b/actionpack/test/controller/params_wrapper_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module Admin; class User; end; end @@ -170,6 +172,14 @@ class ParamsWrapperTest < ActionController::TestCase end end + def test_no_double_wrap_if_key_exists_and_value_is_nil + with_default_wrapper_options do + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "user" => nil } + assert_parameters("user" => nil) + end + end + def test_nested_params with_default_wrapper_options do @request.env["CONTENT_TYPE"] = "application/json" @@ -218,6 +228,14 @@ class ParamsWrapperTest < ActionController::TestCase end end + def test_preserves_query_string_params_in_filtered_params + with_default_wrapper_options do + @request.env["CONTENT_TYPE"] = "application/json" + get :parse, params: { "user" => { "username" => "nixon" } } + assert_equal({ "controller" => "params_wrapper_test/users", "action" => "parse", "user" => { "username" => "nixon" } }, @request.filtered_parameters) + end + end + def test_empty_parameter_set with_default_wrapper_options do @request.env["CONTENT_TYPE"] = "application/json" @@ -237,6 +255,20 @@ class ParamsWrapperTest < ActionController::TestCase assert_equal "", @response.body end end + + def test_derived_wrapped_keys_from_nested_attributes + def User.nested_attributes_options + { person: {} } + end + + assert_called(User, :attribute_names, times: 2, returns: ["username"]) do + with_default_wrapper_options do + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "username" => "sikachu", "person_attributes" => { "title" => "Developer" } } + assert_parameters("username" => "sikachu", "person_attributes" => { "title" => "Developer" }, "user" => { "username" => "sikachu", "person_attributes" => { "title" => "Developer" } }) + end + end + end end class NamespacedParamsWrapperTest < ActionController::TestCase @@ -244,7 +276,7 @@ class NamespacedParamsWrapperTest < ActionController::TestCase module Admin module Users - class UsersController < ActionController::Base; + class UsersController < ActionController::Base class << self attr_accessor :last_parameters end diff --git a/actionpack/test/controller/permitted_params_test.rb b/actionpack/test/controller/permitted_params_test.rb index 6205a09816..caac88ffb2 100644 --- a/actionpack/test/controller/permitted_params_test.rb +++ b/actionpack/test/controller/permitted_params_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class PeopleController < ActionController::Base diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb index 5b16af78c4..2959dc3e4d 100644 --- a/actionpack/test/controller/redirect_test.rb +++ b/actionpack/test/controller/redirect_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class Workshop @@ -33,7 +35,7 @@ class RedirectController < ActionController::Base end def redirect_with_status_hash - redirect_to({ action: "hello_world" }, status: 301) + redirect_to({ action: "hello_world" }, { status: 301 }) end def redirect_with_protocol @@ -60,6 +62,10 @@ class RedirectController < ActionController::Base redirect_back(fallback_location: "/things/stuff", status: 307) end + def safe_redirect_back_with_status + redirect_back(fallback_location: "/things/stuff", status: 307, allow_other_host: false) + end + def host_redirect redirect_to action: "other_host", only_path: false, host: "other.test.host" end @@ -257,6 +263,23 @@ class RedirectTest < ActionController::TestCase assert_equal "http://test.host/things/stuff", redirect_to_url end + def test_safe_redirect_back_from_other_host + @request.env["HTTP_REFERER"] = "http://another.host/coming/from" + get :safe_redirect_back_with_status + + assert_response 307 + assert_equal "http://test.host/things/stuff", redirect_to_url + end + + def test_safe_redirect_back_from_the_same_host + referer = "http://test.host/coming/from" + @request.env["HTTP_REFERER"] = referer + get :safe_redirect_back_with_status + + assert_response 307 + assert_equal referer, redirect_to_url + end + def test_redirect_to_record with_routing do |set| set.draw do diff --git a/actionpack/test/controller/render_js_test.rb b/actionpack/test/controller/render_js_test.rb index 290218d4a2..1efc0b9de1 100644 --- a/actionpack/test/controller/render_js_test.rb +++ b/actionpack/test/controller/render_js_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "controller/fake_models" require "pathname" diff --git a/actionpack/test/controller/render_json_test.rb b/actionpack/test/controller/render_json_test.rb index 79552ec8f1..82c1ba26cb 100644 --- a/actionpack/test/controller/render_json_test.rb +++ b/actionpack/test/controller/render_json_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "controller/fake_models" require "active_support/logger" diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 3a0a0a8bde..37a62edc15 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "controller/fake_models" @@ -160,6 +162,17 @@ class TestController < ActionController::Base render action: "hello_world" end + def conditional_hello_with_expires_and_confliciting_cache_control_headers + response.headers["Cache-Control"] = "no-cache, must-revalidate" + expires_now + render action: "hello_world" + end + + def conditional_hello_without_expires_and_confliciting_cache_control_headers + response.headers["Cache-Control"] = "no-cache, must-revalidate" + render action: "hello_world" + end + def conditional_hello_with_bangs render action: "hello_world" end @@ -257,7 +270,7 @@ end module TemplateModificationHelper private def modify_template(name) - path = File.expand_path("../../fixtures/#{name}.erb", __FILE__) + path = File.expand_path("../fixtures/#{name}.erb", __dir__) original = File.read(path) File.write(path, "#{original} Modified!") ActionView::LookupContext::DetailsKey.clear @@ -287,9 +300,9 @@ class ExpiresInRenderTest < ActionController::TestCase def test_dynamic_render_with_file # This is extremely bad, but should be possible to do. - assert File.exist?(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")) + assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__)) response = get :dynamic_render_with_file, params: { id: '../\\../test/abstract_unit.rb' } - assert_equal File.read(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")), + assert_equal File.read(File.expand_path("../../test/abstract_unit.rb", __dir__)), response.body end @@ -306,16 +319,16 @@ class ExpiresInRenderTest < ActionController::TestCase end def test_dynamic_render - assert File.exist?(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")) + assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__)) assert_raises ActionView::MissingTemplate do get :dynamic_render, params: { id: '../\\../test/abstract_unit.rb' } end end def test_permitted_dynamic_render_file_hash - assert File.exist?(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")) + assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__)) response = get :dynamic_render_permit, params: { id: { file: '../\\../test/abstract_unit.rb' } } - assert_equal File.read(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")), + assert_equal File.read(File.expand_path("../../test/abstract_unit.rb", __dir__)), response.body end @@ -366,6 +379,16 @@ class ExpiresInRenderTest < ActionController::TestCase assert_match(/no-transform/, @response.headers["Cache-Control"]) end + def test_expires_now_with_conflicting_cache_control_headers + get :conditional_hello_with_expires_and_confliciting_cache_control_headers + assert_equal "no-cache", @response.headers["Cache-Control"] + end + + def test_no_expires_now_with_conflicting_cache_control_headers + get :conditional_hello_without_expires_and_confliciting_cache_control_headers + assert_equal "no-cache", @response.headers["Cache-Control"] + end + def test_date_header_when_expires_in time = Time.mktime(2011, 10, 30) Time.stub :now, time do @@ -604,6 +627,18 @@ class MetalRenderTest < ActionController::TestCase end end +class ActionControllerRenderTest < ActionController::TestCase + class MinimalController < ActionController::Metal + include AbstractController::Rendering + include ActionController::Rendering + end + + def test_direct_render_to_string_with_body + mc = MinimalController.new + assert_equal "Hello world!", mc.render_to_string(body: ["Hello world!"]) + end +end + class ActionControllerBaseRenderTest < ActionController::TestCase def test_direct_render_to_string ac = ActionController::Base.new() diff --git a/actionpack/test/controller/render_xml_test.rb b/actionpack/test/controller/render_xml_test.rb index 24866d7d6a..a72d14e4bb 100644 --- a/actionpack/test/controller/render_xml_test.rb +++ b/actionpack/test/controller/render_xml_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "controller/fake_models" require "pathname" diff --git a/actionpack/test/controller/renderer_test.rb b/actionpack/test/controller/renderer_test.rb index 052c974d68..ae8330e029 100644 --- a/actionpack/test/controller/renderer_test.rb +++ b/actionpack/test/controller/renderer_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class RendererTest < ActiveSupport::TestCase diff --git a/actionpack/test/controller/renderers_test.rb b/actionpack/test/controller/renderers_test.rb index ccc700d79c..d92de6f5d5 100644 --- a/actionpack/test/controller/renderers_test.rb +++ b/actionpack/test/controller/renderers_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "controller/fake_models" require "active_support/logger" diff --git a/actionpack/test/controller/request/test_request_test.rb b/actionpack/test/controller/request/test_request_test.rb index 1440db00f6..b8d86696de 100644 --- a/actionpack/test/controller/request/test_request_test.rb +++ b/actionpack/test/controller/request/test_request_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "stringio" diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 521d93f02e..eb3d2f34a8 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -1,5 +1,8 @@ +# frozen_string_literal: true + require "abstract_unit" require "active_support/log_subscriber/test_helper" +require "active_support/messages/rotation_configuration" # common controller actions module RequestForgeryProtectionActions @@ -163,6 +166,13 @@ class PerFormTokensController < ActionController::Base end end +class SkipProtectionController < ActionController::Base + include RequestForgeryProtectionActions + protect_from_forgery with: :exception + skip_forgery_protection if: :skip_requested + attr_accessor :skip_requested +end + # common test methods module RequestForgeryProtectionTests def setup @@ -621,13 +631,14 @@ end class RequestForgeryProtectionControllerUsingNullSessionTest < ActionController::TestCase class NullSessionDummyKeyGenerator - def generate_key(secret) + def generate_key(secret, length = nil) "03312270731a2ed0d11ed091c2338a06" end end def setup @request.env[ActionDispatch::Cookies::GENERATOR_KEY] = NullSessionDummyKeyGenerator.new + @request.env[ActionDispatch::Cookies::COOKIES_ROTATIONS] = ActiveSupport::Messages::RotationConfiguration.new end test "should allow to set signed cookies" do @@ -964,3 +975,26 @@ class PerFormTokensControllerTest < ActionController::TestCase assert_equal expected, actual end end + +class SkipProtectionControllerTest < ActionController::TestCase + def test_should_not_allow_post_without_token_when_not_skipping + @controller.skip_requested = false + assert_blocked { post :index } + end + + def test_should_allow_post_without_token_when_skipping + @controller.skip_requested = true + assert_not_blocked { post :index } + end + + def assert_blocked + assert_raises(ActionController::InvalidAuthenticityToken) do + yield + end + end + + def assert_not_blocked + assert_nothing_raised { yield } + assert_response :success + end +end diff --git a/actionpack/test/controller/required_params_test.rb b/actionpack/test/controller/required_params_test.rb index 46bb374b3f..4a83d07e7d 100644 --- a/actionpack/test/controller/required_params_test.rb +++ b/actionpack/test/controller/required_params_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class BooksController < ActionController::Base diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb index 9ae22c4554..4ed79073e5 100644 --- a/actionpack/test/controller/rescue_test.rb +++ b/actionpack/test/controller/rescue_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class RescueController < ActionController::Base @@ -31,7 +33,7 @@ class RescueController < ActionController::Base class ResourceUnavailableToRescueAsString < StandardError end - # We use a fully-qualified name in some strings, and a relative constant + # We use a fully qualified name in some strings, and a relative constant # name in some other to test correct handling of both cases. rescue_from NotAuthorized, with: :deny_access diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index fad34dacce..3d98237003 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "active_support/core_ext/object/try" require "active_support/core_ext/object/with_options" @@ -941,8 +943,8 @@ class ResourcesTest < ActionController::TestCase assert_resource_allowed_routes("products", {}, { id: "1" }, [], [:index, :new, :create, :show, :edit, :update, :destroy]) assert_resource_allowed_routes("products", { format: "xml" }, { id: "1" }, [], [:index, :new, :create, :show, :edit, :update, :destroy]) - assert_recognizes({ controller: "products", action: "sale" }, path: "products/sale", method: :get) - assert_recognizes({ controller: "products", action: "sale", format: "xml" }, path: "products/sale.xml", method: :get) + assert_recognizes({ controller: "products", action: "sale" }, { path: "products/sale", method: :get }) + assert_recognizes({ controller: "products", action: "sale", format: "xml" }, { path: "products/sale.xml", method: :get }) end end @@ -957,8 +959,8 @@ class ResourcesTest < ActionController::TestCase assert_resource_allowed_routes("products", {}, { id: "1" }, [], [:index, :new, :create, :show, :edit, :update, :destroy]) assert_resource_allowed_routes("products", { format: "xml" }, { id: "1" }, [], [:index, :new, :create, :show, :edit, :update, :destroy]) - assert_recognizes({ controller: "products", action: "preview", id: "1" }, path: "products/1/preview", method: :get) - assert_recognizes({ controller: "products", action: "preview", id: "1", format: "xml" }, path: "products/1/preview.xml", method: :get) + assert_recognizes({ controller: "products", action: "preview", id: "1" }, { path: "products/1/preview", method: :get }) + assert_recognizes({ controller: "products", action: "preview", id: "1", format: "xml" }, { path: "products/1/preview.xml", method: :get }) end end @@ -975,8 +977,8 @@ class ResourcesTest < ActionController::TestCase assert_singleton_resource_allowed_routes("accounts", {}, [], [:new, :create, :show, :edit, :update, :destroy]) assert_singleton_resource_allowed_routes("accounts", { format: "xml" }, [], [:new, :create, :show, :edit, :update, :destroy]) - assert_recognizes({ controller: "accounts", action: "signup" }, path: "account/signup", method: :get) - assert_recognizes({ controller: "accounts", action: "signup", format: "xml" }, path: "account/signup.xml", method: :get) + assert_recognizes({ controller: "accounts", action: "signup" }, { path: "account/signup", method: :get }) + assert_recognizes({ controller: "accounts", action: "signup", format: "xml" }, { path: "account/signup.xml", method: :get }) end end @@ -993,8 +995,8 @@ class ResourcesTest < ActionController::TestCase assert_resource_allowed_routes("images", { product_id: "1" }, { id: "2" }, :show, [:index, :new, :create, :edit, :update, :destroy], "products/1/images") assert_resource_allowed_routes("images", { product_id: "1", format: "xml" }, { id: "2" }, :show, [:index, :new, :create, :edit, :update, :destroy], "products/1/images") - assert_recognizes({ controller: "images", action: "thumbnail", product_id: "1", id: "2" }, path: "products/1/images/2/thumbnail", method: :get) - assert_recognizes({ controller: "images", action: "thumbnail", product_id: "1", id: "2", format: "jpg" }, path: "products/1/images/2/thumbnail.jpg", method: :get) + assert_recognizes({ controller: "images", action: "thumbnail", product_id: "1", id: "2" }, { path: "products/1/images/2/thumbnail", method: :get }) + assert_recognizes({ controller: "images", action: "thumbnail", product_id: "1", id: "2", format: "jpg" }, { path: "products/1/images/2/thumbnail.jpg", method: :get }) end end @@ -1067,7 +1069,7 @@ class ResourcesTest < ActionController::TestCase match "/products", to: "products#show", via: :all end - assert_routing({ method: "all", path: "/products" }, controller: "products", action: "show") + assert_routing({ method: "all", path: "/products" }, { controller: "products", action: "show" }) end end @@ -1078,7 +1080,7 @@ class ResourcesTest < ActionController::TestCase end assert_raises(Minitest::Assertion) do - assert_routing({ method: "all", path: "/products" }, controller: "products", action: "show") + assert_routing({ method: "all", path: "/products" }, { controller: "products", action: "show" }) end end end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 56b39510bb..f09051b306 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "controller/fake_controllers" require "active_support/core_ext/object/with_options" @@ -94,6 +96,22 @@ class LegacyRouteSetTests < ActiveSupport::TestCase assert_equal({ "artist" => "journey", "song" => "faithfully" }, hash) end + def test_id_encoding + rs.draw do + get "/journey/:id", to: lambda { |env| + param = ActionDispatch::Request.new(env).path_parameters + resp = ActiveSupport::JSON.encode param + [200, {}, [resp]] + } + end + + # The encoding of the URL in production is *binary*, so we add a + # .b here. + hash = ActiveSupport::JSON.decode get(URI("http://example.org/journey/%E5%A4%AA%E9%83%8E".b)) + assert_equal({ "id" => "太郎" }, hash) + assert_equal ::Encoding::UTF_8, hash["id"].encoding + end + def test_id_with_dash rs.draw do get "/journey/:id", to: lambda { |env| @@ -656,7 +674,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase assert_equal "/page/foo", url_for(rs, controller: "content", action: "show_page", id: "foo") assert_equal({ controller: "content", action: "show_page", id: "foo" }, rs.recognize_path("/page/foo")) - token = "\321\202\320\265\320\272\321\201\321\202" # 'text' in Russian + token = "\321\202\320\265\320\272\321\201\321\202".dup # 'text' in Russian token.force_encoding(Encoding::BINARY) escaped_token = CGI::escape(token) diff --git a/actionpack/test/controller/runner_test.rb b/actionpack/test/controller/runner_test.rb index 3c0c1907f9..a96c9c519b 100644 --- a/actionpack/test/controller/runner_test.rb +++ b/actionpack/test/controller/runner_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_dispatch/testing/integration" diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb index 9e6b975fe2..fd2399e433 100644 --- a/actionpack/test/controller/send_file_test.rb +++ b/actionpack/test/controller/send_file_test.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + require "abstract_unit" module TestFileUtils def file_name() File.basename(__FILE__) end - def file_path() File.expand_path(__FILE__) end + def file_path() __FILE__ end def file_data() @data ||= File.open(file_path, "rb") { |f| f.read } end end diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb index 38c601ee81..2094aa1aed 100644 --- a/actionpack/test/controller/show_exceptions_test.rb +++ b/actionpack/test/controller/show_exceptions_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ShowExceptions diff --git a/actionpack/test/controller/streaming_test.rb b/actionpack/test/controller/streaming_test.rb index d685467cad..5a42e2ae6d 100644 --- a/actionpack/test/controller/streaming_test.rb +++ b/actionpack/test/controller/streaming_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ActionController diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 3a4307b64b..536c5ed97a 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + require "abstract_unit" require "controller/fake_controllers" require "active_support/json/decoding" require "rails/engine" class TestCaseTest < ActionController::TestCase - def self.fixture_path; end; + def self.fixture_path; end class TestController < ActionController::Base def no_op @@ -122,7 +124,7 @@ XML end def test_send_file - send_file(File.expand_path(__FILE__)) + send_file(__FILE__) end def redirect_to_same_controller @@ -390,9 +392,9 @@ XML def test_assert_generates assert_generates "controller/action/5", controller: "controller", action: "action", id: "5" - assert_generates "controller/action/7", { id: "7" }, controller: "controller", action: "action" - assert_generates "controller/action/5", { controller: "controller", action: "action", id: "5", name: "bob" }, {}, name: "bob" - assert_generates "controller/action/7", { id: "7", name: "bob" }, { controller: "controller", action: "action" }, name: "bob" + assert_generates "controller/action/7", { id: "7" }, { controller: "controller", action: "action" } + assert_generates "controller/action/5", { controller: "controller", action: "action", id: "5", name: "bob" }, {}, { name: "bob" } + assert_generates "controller/action/7", { id: "7", name: "bob" }, { controller: "controller", action: "action" }, { name: "bob" } assert_generates "controller/action/7", { id: "7" }, { controller: "controller", action: "action", name: "bob" }, {} end @@ -403,7 +405,7 @@ XML def test_assert_routing_with_method with_routing do |set| set.draw { resources(:content) } - assert_routing({ method: "post", path: "content" }, controller: "content", action: "create") + assert_routing({ method: "post", path: "content" }, { controller: "content", action: "create" }) end end @@ -780,7 +782,7 @@ XML end end - FILES_DIR = File.dirname(__FILE__) + "/../fixtures/multipart" + FILES_DIR = File.expand_path("../fixtures/multipart", __dir__) READ_BINARY = "rb:binary" READ_PLAIN = "r:binary" @@ -855,7 +857,7 @@ XML end def test_fixture_file_upload_ignores_fixture_path_given_full_path - TestCaseTest.stub :fixture_path, File.dirname(__FILE__) do + TestCaseTest.stub :fixture_path, __dir__ do uploaded_file = fixture_file_upload("#{FILES_DIR}/ruby_on_rails.jpg", "image/jpg") assert_equal File.open("#{FILES_DIR}/ruby_on_rails.jpg", READ_PLAIN).read, uploaded_file.read end diff --git a/actionpack/test/controller/url_for_integration_test.rb b/actionpack/test/controller/url_for_integration_test.rb index f640e77b99..a7c7356921 100644 --- a/actionpack/test/controller/url_for_integration_test.rb +++ b/actionpack/test/controller/url_for_integration_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "controller/fake_controllers" require "active_support/core_ext/object/with_options" diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index 2afe67ed91..cf11227897 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module AbstractController diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb index a055e6d177..0f79c83b6d 100644 --- a/actionpack/test/controller/url_rewriter_test.rb +++ b/actionpack/test/controller/url_rewriter_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "controller/fake_controllers" diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb index 6f97a4b62e..4a10637b54 100644 --- a/actionpack/test/controller/webservice_test.rb +++ b/actionpack/test/controller/webservice_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "active_support/json/decoding" @@ -21,8 +23,8 @@ class WebServiceTest < ActionDispatch::IntegrationTest value = "" end - s << ", " unless s.empty? - s << "#{k}#{value}" + s += ", " unless s.empty? + s += "#{k}#{value}" end end end diff --git a/actionpack/test/dispatch/callbacks_test.rb b/actionpack/test/dispatch/callbacks_test.rb index 29a5dfc0ad..fc80191c02 100644 --- a/actionpack/test/dispatch/callbacks_test.rb +++ b/actionpack/test/dispatch/callbacks_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class DispatcherTest < ActiveSupport::TestCase diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 664faa31bb..40cbad3b0d 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + require "abstract_unit" require "openssl" require "active_support/key_generator" -require "active_support/message_verifier" +require "active_support/messages/rotation_configuration" class CookieJarTest < ActiveSupport::TestCase attr_reader :request @@ -276,20 +278,34 @@ class CookiesTest < ActionController::TestCase def encrypted_cookie cookies.encrypted["foo"] end + + def cookie_expires_in_two_hours + cookies[:user_name] = { value: "assain", expires: 2.hours } + head :ok + end end tests TestController - SALT = "b3c631c314c0bbca50c1b2843150fe33" + SECRET_KEY_BASE = "b3c631c314c0bbca50c1b2843150fe33" + SIGNED_COOKIE_SALT = "signed cookie" + ENCRYPTED_COOKIE_SALT = "encrypted cookie" + ENCRYPTED_SIGNED_COOKIE_SALT = "sigend encrypted cookie" + AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "authenticated encrypted cookie" def setup super - @request.env["action_dispatch.key_generator"] = ActiveSupport::KeyGenerator.new(SALT, iterations: 2) + @request.env["action_dispatch.key_generator"] = ActiveSupport::KeyGenerator.new(SECRET_KEY_BASE, iterations: 2) + @request.env["action_dispatch.cookies_rotations"] = ActiveSupport::Messages::RotationConfiguration.new - @request.env["action_dispatch.signed_cookie_salt"] = - @request.env["action_dispatch.encrypted_cookie_salt"] = - @request.env["action_dispatch.encrypted_signed_cookie_salt"] = SALT + @request.env["action_dispatch.secret_key_base"] = SECRET_KEY_BASE + @request.env["action_dispatch.use_authenticated_cookie_encryption"] = true + + @request.env["action_dispatch.signed_cookie_salt"] = SIGNED_COOKIE_SALT + @request.env["action_dispatch.encrypted_cookie_salt"] = ENCRYPTED_COOKIE_SALT + @request.env["action_dispatch.encrypted_signed_cookie_salt"] = ENCRYPTED_SIGNED_COOKIE_SALT + @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] = AUTHENTICATED_ENCRYPTED_COOKIE_SALT @request.host = "www.nextangle.com" end @@ -424,28 +440,72 @@ class CookiesTest < ActionController::TestCase assert_equal 45, cookies.signed[:user_id] key_generator = @request.env["action_dispatch.key_generator"] - signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"] - secret = key_generator.generate_key(signed_cookie_salt) + secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"]) verifier = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal, digest: "SHA1") assert_equal verifier.generate(45), cookies[:user_id] end def test_signed_cookie_using_custom_digest - @request.env["action_dispatch.cookies_digest"] = "SHA256" + @request.env["action_dispatch.signed_cookie_digest"] = "SHA256" + get :set_signed_cookie cookies = @controller.send :cookies assert_not_equal 45, cookies[:user_id] assert_equal 45, cookies.signed[:user_id] key_generator = @request.env["action_dispatch.key_generator"] - signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"] - secret = key_generator.generate_key(signed_cookie_salt) + secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"]) verifier = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal, digest: "SHA256") assert_equal verifier.generate(45), cookies[:user_id] end + def test_signed_cookie_rotating_secret_and_digest + secret = "b3c631c314c0bbca50c1b2843150fe33" + + @request.env["action_dispatch.signed_cookie_digest"] = "SHA256" + @request.env["action_dispatch.cookies_rotations"].rotate :signed, secret, digest: "SHA1" + + old_message = ActiveSupport::MessageVerifier.new(secret, digest: "SHA1", serializer: Marshal).generate(45) + @request.headers["Cookie"] = "user_id=#{old_message}" + + get :get_signed_cookie + assert_equal 45, @controller.send(:cookies).signed[:user_id] + + key_generator = @request.env["action_dispatch.key_generator"] + secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"]) + verifier = ActiveSupport::MessageVerifier.new(secret, digest: "SHA256", serializer: Marshal) + assert_equal 45, verifier.verify(@response.cookies["user_id"]) + end + + def test_signed_cookie_with_legacy_secret_scheme + @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" + + old_message = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", digest: "SHA1", serializer: Marshal).generate(45) + + @request.headers["Cookie"] = "user_id=#{old_message}" + get :get_signed_cookie + assert_equal 45, @controller.send(:cookies).signed[:user_id] + + key_generator = @request.env["action_dispatch.key_generator"] + secret = key_generator.generate_key("signed cookie") + verifier = ActiveSupport::MessageVerifier.new(secret, digest: "SHA1", serializer: Marshal) + assert_equal 45, verifier.verify(@response.cookies["user_id"]) + end + + def test_tampered_with_signed_cookie + key_generator = @request.env["action_dispatch.key_generator"] + secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"]) + + verifier = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal, digest: "SHA1") + message = verifier.generate(45) + + @request.headers["Cookie"] = "user_id=#{Marshal.dump 45}--#{message.split("--").last}" + get :get_signed_cookie + assert_nil @controller.send(:cookies).signed[:user_id] + end + def test_signed_cookie_using_default_serializer get :set_signed_cookie cookies = @controller.send :cookies @@ -488,8 +548,7 @@ class CookiesTest < ActionController::TestCase @request.env["action_dispatch.cookies_serializer"] = :hybrid key_generator = @request.env["action_dispatch.key_generator"] - signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"] - secret = key_generator.generate_key(signed_cookie_salt) + secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"]) marshal_value = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal).generate(45) @request.headers["Cookie"] = "user_id=#{marshal_value}" @@ -508,8 +567,8 @@ class CookiesTest < ActionController::TestCase @request.env["action_dispatch.cookies_serializer"] = :hybrid key_generator = @request.env["action_dispatch.key_generator"] - signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"] - secret = key_generator.generate_key(signed_cookie_salt) + secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"]) + json_value = ActiveSupport::MessageVerifier.new(secret, serializer: JSON).generate(45) @request.headers["Cookie"] = "user_id=#{json_value}" @@ -531,9 +590,7 @@ class CookiesTest < ActionController::TestCase get :set_encrypted_cookie cookies = @controller.send :cookies assert_not_equal "bar", cookies[:foo] - assert_raise TypeError do - cookies.signed[:foo] - end + assert_nil cookies.signed[:foo] assert_equal "bar", cookies.encrypted[:foo] end @@ -542,9 +599,7 @@ class CookiesTest < ActionController::TestCase get :set_encrypted_cookie cookies = @controller.send :cookies assert_not_equal "bar", cookies[:foo] - assert_raises TypeError do - cookies.signed[:foo] - end + assert_nil cookies.signed[:foo] assert_equal "bar", cookies.encrypted[:foo] end @@ -553,9 +608,7 @@ class CookiesTest < ActionController::TestCase get :set_encrypted_cookie cookies = @controller.send :cookies assert_not_equal "bar", cookies[:foo] - assert_raises ::JSON::ParserError do - cookies.signed[:foo] - end + assert_nil cookies.signed[:foo] assert_equal "bar", cookies.encrypted[:foo] end @@ -564,9 +617,7 @@ class CookiesTest < ActionController::TestCase get :set_wrapped_encrypted_cookie cookies = @controller.send :cookies assert_not_equal "wrapped: bar", cookies[:foo] - assert_raises ::JSON::ParserError do - cookies.signed[:foo] - end + assert_nil cookies.signed[:foo] assert_equal "wrapped: bar", cookies.encrypted[:foo] end @@ -577,38 +628,15 @@ class CookiesTest < ActionController::TestCase assert_equal "bar was dumped and loaded", cookies.encrypted[:foo] end - def test_encrypted_cookie_using_custom_digest - @request.env["action_dispatch.cookies_digest"] = "SHA256" - get :set_encrypted_cookie - cookies = @controller.send :cookies - assert_not_equal "bar", cookies[:foo] - assert_equal "bar", cookies.encrypted[:foo] - - sign_secret = @request.env["action_dispatch.key_generator"].generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"]) - - sha1_verifier = ActiveSupport::MessageVerifier.new(sign_secret, serializer: ActiveSupport::MessageEncryptor::NullSerializer, digest: "SHA1") - sha256_verifier = ActiveSupport::MessageVerifier.new(sign_secret, serializer: ActiveSupport::MessageEncryptor::NullSerializer, digest: "SHA256") - - assert_raises(ActiveSupport::MessageVerifier::InvalidSignature) do - sha1_verifier.verify(cookies[:foo]) - end - - assert_nothing_raised do - sha256_verifier.verify(cookies[:foo]) - end - end - def test_encrypted_cookie_using_hybrid_serializer_can_migrate_marshal_dumped_value_to_json @request.env["action_dispatch.cookies_serializer"] = :hybrid key_generator = @request.env["action_dispatch.key_generator"] - encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"] - encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"] - secret = key_generator.generate_key(encrypted_cookie_salt) - sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt) + secret = key_generator.generate_key(@request.env["action_dispatch.authenticated_encrypted_cookie_salt"], 32) - marshal_value = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: Marshal).encrypt_and_sign("bar") - @request.headers["Cookie"] = "foo=#{marshal_value}" + encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: Marshal) + marshal_value = encryptor.encrypt_and_sign("bar") + @request.headers["Cookie"] = "foo=#{::Rack::Utils.escape marshal_value}" get :get_encrypted_cookie @@ -616,40 +644,27 @@ class CookiesTest < ActionController::TestCase assert_not_equal "bar", cookies[:foo] assert_equal "bar", cookies.encrypted[:foo] - encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON) - assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) + json_encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: JSON) + assert_not_nil @response.cookies["foo"] + assert_equal "bar", json_encryptor.decrypt_and_verify(@response.cookies["foo"]) end def test_encrypted_cookie_using_hybrid_serializer_can_read_from_json_dumped_value @request.env["action_dispatch.cookies_serializer"] = :hybrid key_generator = @request.env["action_dispatch.key_generator"] - encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"] - encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"] - secret = key_generator.generate_key(encrypted_cookie_salt) - sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt) - json_value = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON).encrypt_and_sign("bar") - @request.headers["Cookie"] = "foo=#{json_value}" - - get :get_encrypted_cookie - - cookies = @controller.send :cookies - assert_not_equal "bar", cookies[:foo] - assert_equal "bar", cookies.encrypted[:foo] - - assert_nil @response.cookies["foo"] - end + secret = key_generator.generate_key(@request.env["action_dispatch.authenticated_encrypted_cookie_salt"], 32) - def test_compat_encrypted_cookie_using_64_byte_key - # Cookie generated with 64 bytes secret - message = ["566d4e75536d686e633246564e6b493062557079626c566d51574d30515430394c53315665564a694e4563786555744f57537454576b396a5a31566a626e52525054303d2d2d34663234333330623130623261306163363562316266323335396164666364613564643134623131"].pack("H*") - @request.headers["Cookie"] = "foo=#{message}" + encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: JSON) + json_value = encryptor.encrypt_and_sign("bar") + @request.headers["Cookie"] = "foo=#{::Rack::Utils.escape json_value}" get :get_encrypted_cookie cookies = @controller.send :cookies assert_not_equal "bar", cookies[:foo] assert_equal "bar", cookies.encrypted[:foo] + assert_nil @response.cookies["foo"] end @@ -727,65 +742,8 @@ class CookiesTest < ActionController::TestCase } end - def test_signed_uses_signed_cookie_jar_if_only_secret_token_is_set - @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - @request.env["action_dispatch.secret_key_base"] = nil - get :set_signed_cookie - assert_kind_of ActionDispatch::Cookies::SignedCookieJar, cookies.signed - end - - def test_signed_uses_signed_cookie_jar_if_only_secret_key_base_is_set - @request.env["action_dispatch.secret_token"] = nil - @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" - get :set_signed_cookie - assert_kind_of ActionDispatch::Cookies::SignedCookieJar, cookies.signed - end - - def test_signed_uses_upgrade_legacy_signed_cookie_jar_if_both_secret_token_and_secret_key_base_are_set - @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" - get :set_signed_cookie - assert_kind_of ActionDispatch::Cookies::UpgradeLegacySignedCookieJar, cookies.signed - end - - def test_signed_or_encrypted_uses_signed_cookie_jar_if_only_secret_token_is_set - @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - @request.env["action_dispatch.secret_key_base"] = nil - get :get_encrypted_cookie - assert_kind_of ActionDispatch::Cookies::SignedCookieJar, cookies.signed_or_encrypted - end - - def test_signed_or_encrypted_uses_encrypted_cookie_jar_if_only_secret_key_base_is_set - @request.env["action_dispatch.secret_token"] = nil - @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" - get :get_encrypted_cookie - assert_kind_of ActionDispatch::Cookies::EncryptedCookieJar, cookies.signed_or_encrypted - end - - def test_signed_or_encrypted_uses_upgrade_legacy_encrypted_cookie_jar_if_both_secret_token_and_secret_key_base_are_set - @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" - get :get_encrypted_cookie - assert_kind_of ActionDispatch::Cookies::UpgradeLegacyEncryptedCookieJar, cookies.signed_or_encrypted - end - - def test_encrypted_uses_encrypted_cookie_jar_if_only_secret_key_base_is_set - @request.env["action_dispatch.secret_token"] = nil - @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" - get :get_encrypted_cookie - assert_kind_of ActionDispatch::Cookies::EncryptedCookieJar, cookies.encrypted - end - - def test_encrypted_uses_upgrade_legacy_encrypted_cookie_jar_if_both_secret_token_and_secret_key_base_are_set - @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" - get :get_encrypted_cookie - assert_kind_of ActionDispatch::Cookies::UpgradeLegacyEncryptedCookieJar, cookies.encrypted - end - def test_legacy_signed_cookie_is_read_and_transparently_upgraded_by_signed_cookie_jar_if_both_secret_token_and_secret_key_base_are_set @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate(45) @@ -802,9 +760,6 @@ class CookiesTest < ActionController::TestCase def test_legacy_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_cookie_jar_if_both_secret_token_and_secret_key_base_are_set @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" - @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee" - @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9" legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate("bar") @@ -813,17 +768,14 @@ class CookiesTest < ActionController::TestCase assert_equal "bar", @controller.send(:cookies).encrypted[:foo] - key_generator = @request.env["action_dispatch.key_generator"] - secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"]) - sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"]) - encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret) + secret = @request.env["action_dispatch.key_generator"].generate_key(@request.env["action_dispatch.authenticated_encrypted_cookie_salt"], 32) + encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: Marshal) assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) end def test_legacy_json_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_cookie_jar_if_both_secret_token_and_secret_key_base_are_set @request.env["action_dispatch.cookies_serializer"] = :json @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate(45) @@ -841,9 +793,6 @@ class CookiesTest < ActionController::TestCase def test_legacy_json_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_json_cookie_jar_if_both_secret_token_and_secret_key_base_are_set @request.env["action_dispatch.cookies_serializer"] = :json @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" - @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee" - @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9" legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate("bar") @@ -852,17 +801,16 @@ class CookiesTest < ActionController::TestCase assert_equal "bar", @controller.send(:cookies).encrypted[:foo] - key_generator = @request.env["action_dispatch.key_generator"] - secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"]) - sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"]) - encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON) + cipher = "aes-256-gcm" + salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] + secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)] + encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON) assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) end def test_legacy_json_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_hybrid_jar_if_both_secret_token_and_secret_key_base_are_set @request.env["action_dispatch.cookies_serializer"] = :hybrid @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate(45) @@ -880,9 +828,6 @@ class CookiesTest < ActionController::TestCase def test_legacy_json_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_hybrid_cookie_jar_if_both_secret_token_and_secret_key_base_are_set @request.env["action_dispatch.cookies_serializer"] = :hybrid @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" - @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee" - @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9" legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate("bar") @@ -891,17 +836,15 @@ class CookiesTest < ActionController::TestCase assert_equal "bar", @controller.send(:cookies).encrypted[:foo] - key_generator = @request.env["action_dispatch.key_generator"] - secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"]) - sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"]) - encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON) + salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] + secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len("aes-256-gcm")] + encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: JSON) assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) end def test_legacy_marshal_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_hybrid_jar_if_both_secret_token_and_secret_key_base_are_set @request.env["action_dispatch.cookies_serializer"] = :hybrid @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate(45) @@ -918,10 +861,10 @@ class CookiesTest < ActionController::TestCase def test_legacy_marshal_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_hybrid_cookie_jar_if_both_secret_token_and_secret_key_base_are_set @request.env["action_dispatch.cookies_serializer"] = :hybrid + + @request.env["action_dispatch.use_authenticated_cookie_encryption"] = true @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" - @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee" - @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9" legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate("bar") @@ -930,16 +873,14 @@ class CookiesTest < ActionController::TestCase assert_equal "bar", @controller.send(:cookies).encrypted[:foo] - key_generator = @request.env["action_dispatch.key_generator"] - secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"]) - sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"]) - encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON) + salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] + secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len("aes-256-gcm")] + encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: JSON) assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) end def test_legacy_signed_cookie_is_treated_as_nil_by_signed_cookie_jar_if_tampered @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" @request.headers["Cookie"] = "user_id=45" get :get_signed_cookie @@ -950,7 +891,6 @@ class CookiesTest < ActionController::TestCase def test_legacy_signed_cookie_is_treated_as_nil_by_encrypted_cookie_jar_if_tampered @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" @request.headers["Cookie"] = "foo=baz" get :get_encrypted_cookie @@ -959,6 +899,134 @@ class CookiesTest < ActionController::TestCase assert_nil @response.cookies["foo"] end + def test_use_authenticated_cookie_encryption_uses_legacy_hmac_aes_cbc_encryption_when_not_enabled + @request.env["action_dispatch.use_authenticated_cookie_encryption"] = nil + + key_generator = @request.env["action_dispatch.key_generator"] + encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"] + encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"] + secret = key_generator.generate_key(encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len("aes-256-cbc")) + sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt) + encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", digest: "SHA1", serializer: Marshal) + + get :set_encrypted_cookie + + cookies = @controller.send :cookies + assert_not_equal "bar", cookies[:foo] + assert_equal "bar", cookies.encrypted[:foo] + assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) + end + + def test_rotating_signed_cookies_digest + @request.env["action_dispatch.signed_cookie_digest"] = "SHA256" + @request.env["action_dispatch.cookies_rotations"].rotate :signed, digest: "SHA1" + + key_generator = @request.env["action_dispatch.key_generator"] + + old_secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"]) + old_value = ActiveSupport::MessageVerifier.new(old_secret).generate(45) + + @request.headers["Cookie"] = "user_id=#{old_value}" + get :get_signed_cookie + + assert_equal 45, @controller.send(:cookies).signed[:user_id] + + secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"]) + verifier = ActiveSupport::MessageVerifier.new(secret, digest: "SHA256") + assert_equal 45, verifier.verify(@response.cookies["user_id"]) + end + + def test_legacy_hmac_aes_cbc_encrypted_marshal_cookie_is_upgraded_to_authenticated_encrypted_cookie + key_generator = @request.env["action_dispatch.key_generator"] + encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"] + encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"] + secret = key_generator.generate_key(encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len("aes-256-cbc")) + sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt) + marshal_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", serializer: Marshal).encrypt_and_sign("bar") + + @request.headers["Cookie"] = "foo=#{marshal_value}" + + get :get_encrypted_cookie + + cookies = @controller.send :cookies + assert_not_equal "bar", cookies[:foo] + assert_equal "bar", cookies.encrypted[:foo] + + aead_salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] + aead_secret = key_generator.generate_key(aead_salt, ActiveSupport::MessageEncryptor.key_len("aes-256-gcm")) + aead_encryptor = ActiveSupport::MessageEncryptor.new(aead_secret, cipher: "aes-256-gcm", serializer: Marshal) + + assert_equal "bar", aead_encryptor.decrypt_and_verify(@response.cookies["foo"]) + end + + def test_legacy_hmac_aes_cbc_encrypted_json_cookie_is_upgraded_to_authenticated_encrypted_cookie + @request.env["action_dispatch.cookies_serializer"] = :json + + key_generator = @request.env["action_dispatch.key_generator"] + encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"] + encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"] + secret = key_generator.generate_key(encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len("aes-256-cbc")) + sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt) + marshal_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", serializer: JSON).encrypt_and_sign("bar") + + @request.headers["Cookie"] = "foo=#{marshal_value}" + + get :get_encrypted_cookie + + cookies = @controller.send :cookies + assert_not_equal "bar", cookies[:foo] + assert_equal "bar", cookies.encrypted[:foo] + + aead_salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] + aead_secret = key_generator.generate_key(aead_salt)[0, ActiveSupport::MessageEncryptor.key_len("aes-256-gcm")] + aead_encryptor = ActiveSupport::MessageEncryptor.new(aead_secret, cipher: "aes-256-gcm", serializer: JSON) + + assert_equal "bar", aead_encryptor.decrypt_and_verify(@response.cookies["foo"]) + end + + def test_legacy_hmac_aes_cbc_encrypted_cookie_using_64_byte_key_is_upgraded_to_authenticated_encrypted_cookie + @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" + @request.env["action_dispatch.encrypted_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33" + @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33" + + # Cookie generated with 64 bytes secret + message = ["566d4e75536d686e633246564e6b493062557079626c566d51574d30515430394c53315665564a694e4563786555744f57537454576b396a5a31566a626e52525054303d2d2d34663234333330623130623261306163363562316266323335396164666364613564643134623131"].pack("H*") + @request.headers["Cookie"] = "foo=#{message}" + + get :get_encrypted_cookie + + cookies = @controller.send :cookies + assert_not_equal "bar", cookies[:foo] + assert_equal "bar", cookies.encrypted[:foo] + + salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] + secret = @request.env["action_dispatch.key_generator"].generate_key(salt, ActiveSupport::MessageEncryptor.key_len("aes-256-gcm")) + encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: Marshal) + + assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) + end + + def test_encrypted_cookie_rotating_secret + secret = "b3c631c314c0bbca50c1b2843150fe33" + + @request.env["action_dispatch.encrypted_cookie_cipher"] = "aes-256-gcm" + @request.env["action_dispatch.cookies_rotations"].rotate :encrypted, secret + + key_len = ActiveSupport::MessageEncryptor.key_len("aes-256-gcm") + + old_message = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: Marshal).encrypt_and_sign(45) + + @request.headers["Cookie"] = "foo=#{::Rack::Utils.escape old_message}" + + get :get_encrypted_cookie + assert_equal 45, @controller.send(:cookies).encrypted[:foo] + + key_generator = @request.env["action_dispatch.key_generator"] + secret = key_generator.generate_key(@request.env["action_dispatch.authenticated_encrypted_cookie_salt"], key_len) + encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: Marshal) + assert_equal 45, encryptor.decrypt_and_verify(@response.cookies["foo"]) + end + def test_cookie_with_all_domain_option get :set_cookie_with_domain assert_response :success @@ -1199,6 +1267,33 @@ class CookiesTest < ActionController::TestCase assert_equal "bar", @controller.encrypted_cookie end + def test_signed_cookie_with_expires_set_relatively + cookies.signed[:user_name] = { value: "assain", expires: 2.hours } + + travel 1.hour + assert_equal "assain", cookies.signed[:user_name] + + travel 2.hours + assert_nil cookies.signed[:user_name] + end + + def test_encrypted_cookie_with_expires_set_relatively + cookies.encrypted[:user_name] = { value: "assain", expires: 2.hours } + + travel 1.hour + assert_equal "assain", cookies.encrypted[:user_name] + + travel 2.hours + assert_nil cookies.encrypted[:user_name] + end + + def test_vanilla_cookie_with_expires_set_relatively + travel_to Time.utc(2017, 8, 15) do + get :cookie_expires_in_two_hours + assert_cookie_header "user_name=assain; path=/; expires=Tue, 15 Aug 2017 02:00:00 -0000" + end + end + private def assert_cookie_header(expected) header = @response.headers["Set-Cookie"] diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb index ea477e8908..60acba0616 100644 --- a/actionpack/test/dispatch/debug_exceptions_test.rb +++ b/actionpack/test/dispatch/debug_exceptions_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class DebugExceptionsTest < ActionDispatch::IntegrationTest @@ -344,7 +346,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest }) assert_response 500 - assert_includes(body, CGI.escapeHTML(PP.pp(params, "", 200))) + assert_includes(body, CGI.escapeHTML(PP.pp(params, "".dup, 200))) end test "sets the HTTP charset parameter" do diff --git a/actionpack/test/dispatch/debug_locks_test.rb b/actionpack/test/dispatch/debug_locks_test.rb new file mode 100644 index 0000000000..d69614bd79 --- /dev/null +++ b/actionpack/test/dispatch/debug_locks_test.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "abstract_unit" + +class DebugLocksTest < ActionDispatch::IntegrationTest + setup do + build_app + end + + def test_render_threads_status + thread_ready = Concurrent::CountDownLatch.new + test_terminated = Concurrent::CountDownLatch.new + + thread = Thread.new do + ActiveSupport::Dependencies.interlock.running do + thread_ready.count_down + test_terminated.wait + end + end + + thread_ready.wait + + get "/rails/locks" + + test_terminated.count_down + + assert_match(/Thread.*?Sharing/, @response.body) + ensure + thread.join + end + + private + def build_app + @app = self.class.build_app do |middleware| + middleware.use ActionDispatch::DebugLocks + end + end +end diff --git a/actionpack/test/dispatch/exception_wrapper_test.rb b/actionpack/test/dispatch/exception_wrapper_test.rb index 316661a116..f6e70382a8 100644 --- a/actionpack/test/dispatch/exception_wrapper_test.rb +++ b/actionpack/test/dispatch/exception_wrapper_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ActionDispatch diff --git a/actionpack/test/dispatch/executor_test.rb b/actionpack/test/dispatch/executor_test.rb index 0b4e0849c3..8eb6450385 100644 --- a/actionpack/test/dispatch/executor_test.rb +++ b/actionpack/test/dispatch/executor_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class ExecutorTest < ActiveSupport::TestCase diff --git a/actionpack/test/dispatch/header_test.rb b/actionpack/test/dispatch/header_test.rb index 958450072e..3a265a056b 100644 --- a/actionpack/test/dispatch/header_test.rb +++ b/actionpack/test/dispatch/header_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class HeaderTest < ActiveSupport::TestCase diff --git a/actionpack/test/dispatch/live_response_test.rb b/actionpack/test/dispatch/live_response_test.rb index d10fc7d575..2901148a9e 100644 --- a/actionpack/test/dispatch/live_response_test.rb +++ b/actionpack/test/dispatch/live_response_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "concurrent/atomic/count_down_latch" diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb index 1596d23b1e..969a08efed 100644 --- a/actionpack/test/dispatch/mapper_test.rb +++ b/actionpack/test/dispatch/mapper_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ActionDispatch diff --git a/actionpack/test/dispatch/middleware_stack_test.rb b/actionpack/test/dispatch/middleware_stack_test.rb index 481aa22b10..e9f7ad41dd 100644 --- a/actionpack/test/dispatch/middleware_stack_test.rb +++ b/actionpack/test/dispatch/middleware_stack_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class MiddlewareStackTest < ActiveSupport::TestCase diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb index 2ca03c535a..90e95e972d 100644 --- a/actionpack/test/dispatch/mime_type_test.rb +++ b/actionpack/test/dispatch/mime_type_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class MimeTypeTest < ActiveSupport::TestCase diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb index a7d5ba2345..f6cf653980 100644 --- a/actionpack/test/dispatch/mount_test.rb +++ b/actionpack/test/dispatch/mount_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "rails/engine" diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index 0e093d2188..85ea04356a 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "rack/test" require "rails/engine" @@ -11,7 +13,7 @@ module TestGenerationPrefix end def self.model_name - klass = "Post" + klass = "Post".dup def klass.name; self end ActiveModel::Name.new(klass) diff --git a/actionpack/test/dispatch/rack_cache_test.rb b/actionpack/test/dispatch/rack_cache_test.rb index d7bb90abbf..86b375a2a8 100644 --- a/actionpack/test/dispatch/rack_cache_test.rb +++ b/actionpack/test/dispatch/rack_cache_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_dispatch/http/rack_cache" diff --git a/actionpack/test/dispatch/reloader_test.rb b/actionpack/test/dispatch/reloader_test.rb index 9eb78fe059..e529229fae 100644 --- a/actionpack/test/dispatch/reloader_test.rb +++ b/actionpack/test/dispatch/reloader_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class ReloaderTest < ActiveSupport::TestCase diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb index 10234a4815..beab8e78b5 100644 --- a/actionpack/test/dispatch/request/json_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class JsonParamsParsingTest < ActionDispatch::IntegrationTest diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb index 01c5ff1429..da8233c074 100644 --- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class MultipartParamsParsingTest < ActionDispatch::IntegrationTest @@ -21,7 +23,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest end end - FIXTURE_PATH = File.dirname(__FILE__) + "/../../fixtures/multipart" + FIXTURE_PATH = File.expand_path("../../fixtures/multipart", __dir__) def teardown TestController.last_request_parameters = nil diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb index 2499c33cef..f9ae5ef4e8 100644 --- a/actionpack/test/dispatch/request/query_string_parsing_test.rb +++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class QueryStringParsingTest < ActionDispatch::IntegrationTest diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb index 311b80ea0a..7b6ce31f29 100644 --- a/actionpack/test/dispatch/request/session_test.rb +++ b/actionpack/test/dispatch/request/session_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_dispatch/middleware/session/abstract_store" @@ -54,6 +56,11 @@ module ActionDispatch assert_equal %w[rails adequate], s.keys end + def test_keys_with_deferred_loading + s = Session.create(store_with_data, req, {}) + assert_equal %w[sample_key], s.keys + end + def test_values s = Session.create(store, req, {}) s["rails"] = "ftw" @@ -61,6 +68,11 @@ module ActionDispatch assert_equal %w[ftw awesome], s.values end + def test_values_with_deferred_loading + s = Session.create(store_with_data, req, {}) + assert_equal %w[sample_value], s.values + end + def test_clear s = Session.create(store, req, {}) s["rails"] = "ftw" @@ -113,6 +125,14 @@ module ActionDispatch def delete_session(env, id, options); 123; end }.new end + + def store_with_data + Class.new { + def load_session(env); [1, { "sample_key" => "sample_value" }]; end + def session_exists?(env); true; end + def delete_session(env, id, options); 123; end + }.new + end end class SessionIntegrationTest < ActionDispatch::IntegrationTest diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb index 1169bf0cdb..9e55a7242e 100644 --- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest @@ -107,7 +109,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest query = [ "customers[boston][first][name]=David", "something_else=blah", - "logo=#{File.expand_path(__FILE__)}" + "logo=#{__FILE__}" ].join("&") expected = { "customers" => { @@ -118,7 +120,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest } }, "something_else" => "blah", - "logo" => File.expand_path(__FILE__), + "logo" => __FILE__, } assert_parses expected, query end diff --git a/actionpack/test/dispatch/request_id_test.rb b/actionpack/test/dispatch/request_id_test.rb index 4fcd45acf5..aa3175c986 100644 --- a/actionpack/test/dispatch/request_id_test.rb +++ b/actionpack/test/dispatch/request_id_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class RequestIdTest < ActiveSupport::TestCase diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 2f9228a62d..8661dc56d6 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class BaseRequestTest < ActiveSupport::TestCase @@ -110,8 +112,8 @@ class RequestIP < BaseRequestTest request.remote_ip } assert_match(/IP spoofing attack/, e.message) - assert_match(/HTTP_X_FORWARDED_FOR="1.1.1.1"/, e.message) - assert_match(/HTTP_CLIENT_IP="2.2.2.2"/, e.message) + assert_match(/HTTP_X_FORWARDED_FOR="1\.1\.1\.1"/, e.message) + assert_match(/HTTP_CLIENT_IP="2\.2\.2\.2"/, e.message) end test "remote ip with spoof detection disabled" do @@ -761,7 +763,7 @@ class RequestMethod < BaseRequestTest test "post uneffected by local inflections" do existing_acronyms = ActiveSupport::Inflector.inflections.acronyms.dup - existing_acronym_regex = ActiveSupport::Inflector.inflections.acronym_regex.dup + assert_deprecated { ActiveSupport::Inflector.inflections.acronym_regex.dup } begin ActiveSupport::Inflector.inflections do |inflect| inflect.acronym "POS" @@ -775,7 +777,7 @@ class RequestMethod < BaseRequestTest # Reset original acronym set ActiveSupport::Inflector.inflections do |inflect| inflect.send(:instance_variable_set, "@acronyms", existing_acronyms) - inflect.send(:instance_variable_set, "@acronym_regex", existing_acronym_regex) + inflect.send(:define_acronym_regex_patterns) end end end @@ -1024,7 +1026,8 @@ class RequestParameters < BaseRequestTest request.path_parameters = { foo: "\xBE" } end - assert_equal "Invalid path parameters: Non UTF-8 value: \xBE", err.message + assert_predicate err.message, :valid_encoding? + assert_equal "Invalid path parameters: Invalid encoding for parameter: �", err.message end test "parameters not accessible after rack parse error of invalid UTF8 character" do @@ -1098,6 +1101,19 @@ class RequestParameterFilter < BaseRequestTest end end + test "parameter filter should maintain hash with indifferent access" do + test_hashes = [ + [{ "foo" => "bar" }.with_indifferent_access, ["blah"]], + [{ "foo" => "bar" }.with_indifferent_access, []] + ] + + test_hashes.each do |before_filter, filter_words| + parameter_filter = ActionDispatch::Http::ParameterFilter.new(filter_words) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, + parameter_filter.filter(before_filter) + end + end + test "filtered_parameters returns params filtered" do request = stub_request( "action_dispatch.request.parameters" => { @@ -1288,3 +1304,18 @@ class RequestFormData < BaseRequestTest assert !request.form_data? end end + +class EarlyHintsRequestTest < BaseRequestTest + def setup + super + @env["rack.early_hints"] = lambda { |links| links } + @request = stub_request + end + + test "when early hints is set in the env link headers are sent" do + early_hints = @request.send_early_hints("Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload") + expected_hints = { "Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload" } + + assert_equal expected_hints, early_hints + end +end diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 7433c5ce0c..c4ee3add2a 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "timeout" require "rack/content_length" @@ -294,13 +296,8 @@ class ResponseTest < ActiveSupport::TestCase end test "read content type with default charset utf-8" do - original = ActionDispatch::Response.default_charset - begin - resp = ActionDispatch::Response.new(200, "Content-Type" => "text/xml") - assert_equal("utf-8", resp.charset) - ensure - ActionDispatch::Response.default_charset = original - end + resp = ActionDispatch::Response.new(200, "Content-Type" => "text/xml") + assert_equal("utf-8", resp.charset) end test "read content type with charset utf-16" do @@ -381,10 +378,10 @@ class ResponseTest < ActiveSupport::TestCase app = lambda { |env| @response.to_a } env = Rack::MockRequest.env_for("/") - status, headers, body = app.call(env) + _status, headers, _body = app.call(env) assert_nil headers["Content-Length"] - status, headers, body = Rack::ContentLength.new(app).call(env) + _status, headers, _body = Rack::ContentLength.new(app).call(env) assert_equal "5", headers["Content-Length"] end end diff --git a/actionpack/test/dispatch/routing/concerns_test.rb b/actionpack/test/dispatch/routing/concerns_test.rb index 2d71c37562..503a7ccd56 100644 --- a/actionpack/test/dispatch/routing/concerns_test.rb +++ b/actionpack/test/dispatch/routing/concerns_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class ReviewsController < ResourcesController; end diff --git a/actionpack/test/dispatch/routing/custom_url_helpers_test.rb b/actionpack/test/dispatch/routing/custom_url_helpers_test.rb index 338992dda5..a1a1e79884 100644 --- a/actionpack/test/dispatch/routing/custom_url_helpers_test.rb +++ b/actionpack/test/dispatch/routing/custom_url_helpers_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class TestCustomUrlHelpers < ActionDispatch::IntegrationTest @@ -322,4 +324,10 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest end end end + + def test_defining_direct_url_registers_helper_method + assert_equal "http://www.example.com/basket", Routes.url_helpers.symbol_url + assert_equal true, Routes.named_routes.route_defined?(:symbol_url), "'symbol_url' named helper not found" + assert_equal true, Routes.named_routes.route_defined?(:symbol_path), "'symbol_path' named helper not found" + end end diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb index a4babf8554..438a918567 100644 --- a/actionpack/test/dispatch/routing/inspector_test.rb +++ b/actionpack/test/dispatch/routing/inspector_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "rails/engine" require "action_dispatch/routing/inspector" diff --git a/actionpack/test/dispatch/routing/ipv6_redirect_test.rb b/actionpack/test/dispatch/routing/ipv6_redirect_test.rb index 179aee9ba7..31559bffc7 100644 --- a/actionpack/test/dispatch/routing/ipv6_redirect_test.rb +++ b/actionpack/test/dispatch/routing/ipv6_redirect_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class IPv6IntegrationTest < ActionDispatch::IntegrationTest diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb index ace35dda53..e61d47b160 100644 --- a/actionpack/test/dispatch/routing/route_set_test.rb +++ b/actionpack/test/dispatch/routing/route_set_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ActionDispatch @@ -138,6 +140,15 @@ module ActionDispatch assert_equal "/a/users/1", url_helpers.user_path(1, foo: "a") end + test "implicit path components consistently return the same result" do + draw do + resources :users, to: SimpleApp.new("foo#index") + end + assert_equal "/users/1.json", url_helpers.user_path(1, :json) + assert_equal "/users/1.json", url_helpers.user_path(1, format: :json) + assert_equal "/users/1.json", url_helpers.user_path(1, :json) + end + private def draw(&block) @set.draw(&block) diff --git a/actionpack/test/dispatch/routing_assertions_test.rb b/actionpack/test/dispatch/routing_assertions_test.rb index 917ce7e668..a5198f2f13 100644 --- a/actionpack/test/dispatch/routing_assertions_test.rb +++ b/actionpack/test/dispatch/routing_assertions_test.rb @@ -1,12 +1,40 @@ +# frozen_string_literal: true + require "abstract_unit" +require "rails/engine" require "controller/fake_controllers" class SecureArticlesController < ArticlesController; end class BlockArticlesController < ArticlesController; end class QueryArticlesController < ArticlesController; end +class SecureBooksController < BooksController; end +class BlockBooksController < BooksController; end +class QueryBooksController < BooksController; end + class RoutingAssertionsTest < ActionController::TestCase def setup + engine = Class.new(Rails::Engine) do + def self.name + "blog_engine" + end + end + engine.routes.draw do + resources :books + + scope "secure", constraints: { protocol: "https://" } do + resources :books, controller: "secure_books" + end + + scope "block", constraints: lambda { |r| r.ssl? } do + resources :books, controller: "block_books" + end + + scope "query", constraints: lambda { |r| r.params[:use_query] == "true" } do + resources :books, controller: "query_books" + end + end + @routes = ActionDispatch::Routing::RouteSet.new @routes.draw do resources :articles @@ -22,6 +50,8 @@ class RoutingAssertionsTest < ActionController::TestCase scope "query", constraints: lambda { |r| r.params[:use_query] == "true" } do resources :articles, controller: "query_articles" end + + mount engine => "/shelf" end end @@ -31,11 +61,11 @@ class RoutingAssertionsTest < ActionController::TestCase end def test_assert_generates_with_defaults - assert_generates("/articles/1/edit", { controller: "articles", action: "edit" }, id: "1") + assert_generates("/articles/1/edit", { controller: "articles", action: "edit" }, { id: "1" }) end def test_assert_generates_with_extras - assert_generates("/articles", { controller: "articles", action: "index", page: "1" }, {}, page: "1") + assert_generates("/articles", { controller: "articles", action: "index", page: "1" }, {}, { page: "1" }) end def test_assert_recognizes @@ -48,8 +78,8 @@ class RoutingAssertionsTest < ActionController::TestCase end def test_assert_recognizes_with_method - assert_recognizes({ controller: "articles", action: "create" }, path: "/articles", method: :post) - assert_recognizes({ controller: "articles", action: "update", id: "1" }, path: "/articles/1", method: :put) + assert_recognizes({ controller: "articles", action: "create" }, { path: "/articles", method: :post }) + assert_recognizes({ controller: "articles", action: "update", id: "1" }, { path: "/articles/1", method: :put }) end def test_assert_recognizes_with_hash_constraint @@ -81,6 +111,49 @@ class RoutingAssertionsTest < ActionController::TestCase assert_match err.message, "This is a really bad msg" end + def test_assert_recognizes_with_engine + assert_recognizes({ controller: "books", action: "index" }, "/shelf/books") + assert_recognizes({ controller: "books", action: "show", id: "1" }, "/shelf/books/1") + end + + def test_assert_recognizes_with_engine_and_extras + assert_recognizes({ controller: "books", action: "index", page: "1" }, "/shelf/books", page: "1") + end + + def test_assert_recognizes_with_engine_and_method + assert_recognizes({ controller: "books", action: "create" }, { path: "/shelf/books", method: :post }) + assert_recognizes({ controller: "books", action: "update", id: "1" }, { path: "/shelf/books/1", method: :put }) + end + + def test_assert_recognizes_with_engine_and_hash_constraint + assert_raise(Assertion) do + assert_recognizes({ controller: "secure_books", action: "index" }, "http://test.host/shelf/secure/books") + end + assert_recognizes({ controller: "secure_books", action: "index", protocol: "https://" }, "https://test.host/shelf/secure/books") + end + + def test_assert_recognizes_with_engine_and_block_constraint + assert_raise(Assertion) do + assert_recognizes({ controller: "block_books", action: "index" }, "http://test.host/shelf/block/books") + end + assert_recognizes({ controller: "block_books", action: "index" }, "https://test.host/shelf/block/books") + end + + def test_assert_recognizes_with_engine_and_query_constraint + assert_raise(Assertion) do + assert_recognizes({ controller: "query_books", action: "index", use_query: "false" }, "/shelf/query/books", use_query: "false") + end + assert_recognizes({ controller: "query_books", action: "index", use_query: "true" }, "/shelf/query/books", use_query: "true") + end + + def test_assert_recognizes_raises_message_with_engine + err = assert_raise(Assertion) do + assert_recognizes({ controller: "secure_books", action: "index" }, "http://test.host/shelf/secure/books", {}, "This is a really bad msg") + end + + assert_match err.message, "This is a really bad msg" + end + def test_assert_routing assert_routing("/articles", controller: "articles", action: "index") end @@ -94,11 +167,11 @@ class RoutingAssertionsTest < ActionController::TestCase end def test_assert_routing_with_defaults - assert_routing("/articles/1/edit", { controller: "articles", action: "edit", id: "1" }, id: "1") + assert_routing("/articles/1/edit", { controller: "articles", action: "edit", id: "1" }, { id: "1" }) end def test_assert_routing_with_extras - assert_routing("/articles", { controller: "articles", action: "index", page: "1" }, {}, page: "1") + assert_routing("/articles", { controller: "articles", action: "index", page: "1" }, {}, { page: "1" }) end def test_assert_routing_with_hash_constraint diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index d64917e0d3..44f902c163 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1,6 +1,9 @@ +# frozen_string_literal: true + require "erb" require "abstract_unit" require "controller/fake_controllers" +require "active_support/messages/rotation_configuration" class TestRoutingMapper < ActionDispatch::IntegrationTest SprocketsApp = lambda { |env| @@ -4414,22 +4417,24 @@ end class TestInvalidUrls < ActionDispatch::IntegrationTest class FooController < ActionController::Base + def self.binary_params_for?(action) + action == "show" + end + def show render plain: "foo#show" end end - test "invalid UTF-8 encoding returns a 400 Bad Request" do + test "invalid UTF-8 encoding returns a bad request" do with_routing do |set| set.draw do get "/bar/:id", to: redirect("/foo/show/%{id}") - get "/foo/show(/:id)", to: "test_invalid_urls/foo#show" ok = lambda { |env| [200, { "Content-Type" => "text/plain" }, []] } get "/foobar/:id", to: ok ActiveSupport::Deprecation.silence do - get "/foo(/:action(/:id))", controller: "test_invalid_urls/foo" get "/:controller(/:action(/:id))" end end @@ -4440,9 +4445,6 @@ class TestInvalidUrls < ActionDispatch::IntegrationTest get "/foo/%E2%EF%BF%BD%A6" assert_response :bad_request - get "/foo/show/%E2%EF%BF%BD%A6" - assert_response :bad_request - get "/bar/%E2%EF%BF%BD%A6" assert_response :bad_request @@ -4450,6 +4452,17 @@ class TestInvalidUrls < ActionDispatch::IntegrationTest assert_response :bad_request end end + + test "params encoded with binary_params_for? are treated as ASCII 8bit" do + with_routing do |set| + set.draw do + get "/foo/show(/:id)", to: "test_invalid_urls/foo#show" + end + + get "/foo/show/%E2%EF%BF%BD%A6" + assert_response :ok + end + end end class TestOptionalRootSegments < ActionDispatch::IntegrationTest @@ -4935,6 +4948,7 @@ end class FlashRedirectTest < ActionDispatch::IntegrationTest SessionKey = "_myapp_session" Generator = ActiveSupport::LegacyKeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33") + Rotations = ActiveSupport::Messages::RotationConfiguration.new class KeyGeneratorMiddleware def initialize(app) @@ -4943,6 +4957,8 @@ class FlashRedirectTest < ActionDispatch::IntegrationTest def call(env) env["action_dispatch.key_generator"] ||= Generator + env["action_dispatch.cookies_rotations"] ||= Rotations + @app.call(env) end end diff --git a/actionpack/test/dispatch/runner_test.rb b/actionpack/test/dispatch/runner_test.rb index b76bf4a320..f16c7963af 100644 --- a/actionpack/test/dispatch/runner_test.rb +++ b/actionpack/test/dispatch/runner_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class RunnerTest < ActiveSupport::TestCase diff --git a/actionpack/test/dispatch/session/abstract_store_test.rb b/actionpack/test/dispatch/session/abstract_store_test.rb index fd4d359cf8..47616db15a 100644 --- a/actionpack/test/dispatch/session/abstract_store_test.rb +++ b/actionpack/test/dispatch/session/abstract_store_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_dispatch/middleware/session/abstract_store" diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb index 859059063f..06e67fac9f 100644 --- a/actionpack/test/dispatch/session/cache_store_test.rb +++ b/actionpack/test/dispatch/session/cache_store_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "fixtures/session_autoload_test/session_autoload_test/foo" diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index 63dfc07c0d..cf51c47068 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -1,11 +1,15 @@ +# frozen_string_literal: true + require "abstract_unit" require "stringio" require "active_support/key_generator" +require "active_support/messages/rotation_configuration" class CookieStoreTest < ActionDispatch::IntegrationTest SessionKey = "_myapp_session" SessionSecret = "b3c631c314c0bbca50c1b2843150fe33" Generator = ActiveSupport::LegacyKeyGenerator.new(SessionSecret) + Rotations = ActiveSupport::Messages::RotationConfiguration.new Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, digest: "SHA1") SignedBar = Verifier.generate(foo: "bar", session_id: SecureRandom.hex(16)) @@ -24,6 +28,11 @@ class CookieStoreTest < ActionDispatch::IntegrationTest render plain: Rack::Utils.escape(Verifier.generate(session.to_hash)) end + def set_session_value_expires_in_five_hours + session[:foo] = "bar" + render plain: Rack::Utils.escape(Verifier.generate(session.to_hash, expires_in: 5.hours)) + end + def get_session_value render plain: "foo: #{session[:foo].inspect}" end @@ -281,7 +290,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest cookies[SessionKey] = SignedBar - get "/set_session_value" + get "/set_session_value_expires_in_five_hours" assert_response :success cookie_body = response.body @@ -297,7 +306,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest get "/no_session_access" assert_response :success - assert_equal "_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; HttpOnly", + assert_equal "_myapp_session=#{cookies[SessionKey]}; path=/; expires=#{expected_expiry}; HttpOnly", headers["Set-Cookie"] end end @@ -339,6 +348,8 @@ class CookieStoreTest < ActionDispatch::IntegrationTest args[0] ||= {} args[0][:headers] ||= {} args[0][:headers]["action_dispatch.key_generator"] ||= Generator + args[0][:headers]["action_dispatch.cookies_rotations"] ||= Rotations + super(path, *args) end diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb index 121e9ebef7..9b51ee1cad 100644 --- a/actionpack/test/dispatch/session/mem_cache_store_test.rb +++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "securerandom" diff --git a/actionpack/test/dispatch/session/test_session_test.rb b/actionpack/test/dispatch/session/test_session_test.rb index 0bf3a8b3ee..e90162a5fe 100644 --- a/actionpack/test/dispatch/session/test_session_test.rb +++ b/actionpack/test/dispatch/session/test_session_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "stringio" diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb index 3513534d72..b69071b44b 100644 --- a/actionpack/test/dispatch/show_exceptions_test.rb +++ b/actionpack/test/dispatch/show_exceptions_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class ShowExceptionsTest < ActionDispatch::IntegrationTest diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb index 757e26973f..8ac9502af9 100644 --- a/actionpack/test/dispatch/ssl_test.rb +++ b/actionpack/test/dispatch/ssl_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class SSLTest < ActionDispatch::IntegrationTest diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb index 3082d1072b..0bdff68692 100644 --- a/actionpack/test/dispatch/static_test.rb +++ b/actionpack/test/dispatch/static_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "zlib" @@ -29,7 +31,7 @@ module StaticTests end def test_handles_urls_with_ascii_8bit - assert_equal "Hello, World!", get("/doorkeeper%E3E4".force_encoding("ASCII-8BIT")).body + assert_equal "Hello, World!", get("/doorkeeper%E3E4".dup.force_encoding("ASCII-8BIT")).body end def test_handles_urls_with_ascii_8bit_on_win_31j @@ -37,7 +39,7 @@ module StaticTests Encoding.default_internal = "Windows-31J" Encoding.default_external = "Windows-31J" end - assert_equal "Hello, World!", get("/doorkeeper%E3E4".force_encoding("ASCII-8BIT")).body + assert_equal "Hello, World!", get("/doorkeeper%E3E4".dup.force_encoding("ASCII-8BIT")).body end def test_handles_urls_with_null_byte diff --git a/actionpack/test/dispatch/system_testing/driver_test.rb b/actionpack/test/dispatch/system_testing/driver_test.rb index 814e1d707b..75feae6fe0 100644 --- a/actionpack/test/dispatch/system_testing/driver_test.rb +++ b/actionpack/test/dispatch/system_testing/driver_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_dispatch/system_testing/driver" @@ -15,7 +17,29 @@ class DriverTest < ActiveSupport::TestCase assert_equal ({ url: "http://example.com/wd/hub" }), driver.instance_variable_get(:@options) end - test "selenium? returns false if driver is poltergeist" do - assert_not ActionDispatch::SystemTesting::Driver.new(:poltergeist).send(:selenium?) + test "initializing the driver with a headless chrome" do + driver = ActionDispatch::SystemTesting::Driver.new(:selenium, using: :headless_chrome, screen_size: [1400, 1400], options: { url: "http://example.com/wd/hub" }) + assert_equal :selenium, driver.instance_variable_get(:@name) + assert_equal :headless_chrome, driver.instance_variable_get(:@browser) + assert_equal [1400, 1400], driver.instance_variable_get(:@screen_size) + assert_equal ({ url: "http://example.com/wd/hub" }), driver.instance_variable_get(:@options) + end + + test "initializing the driver with a poltergeist" do + driver = ActionDispatch::SystemTesting::Driver.new(:poltergeist, screen_size: [1400, 1400], options: { js_errors: false }) + assert_equal :poltergeist, driver.instance_variable_get(:@name) + assert_equal [1400, 1400], driver.instance_variable_get(:@screen_size) + assert_equal ({ js_errors: false }), driver.instance_variable_get(:@options) + end + + test "initializing the driver with a webkit" do + driver = ActionDispatch::SystemTesting::Driver.new(:webkit, screen_size: [1400, 1400], options: { skip_image_loading: true }) + assert_equal :webkit, driver.instance_variable_get(:@name) + assert_equal [1400, 1400], driver.instance_variable_get(:@screen_size) + assert_equal ({ skip_image_loading: true }), driver.instance_variable_get(:@options) + end + + test "registerable? returns false if driver is rack_test" do + assert_not ActionDispatch::SystemTesting::Driver.new(:rack_test).send(:registerable?) end end diff --git a/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb b/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb index a83818fd80..2afda31cf5 100644 --- a/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb +++ b/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_dispatch/system_testing/test_helpers/screenshot_helper" require "capybara/dsl" @@ -6,24 +8,55 @@ class ScreenshotHelperTest < ActiveSupport::TestCase test "image path is saved in tmp directory" do new_test = DrivenBySeleniumWithChrome.new("x") - assert_equal "tmp/screenshots/x.png", new_test.send(:image_path) + Rails.stub :root, Pathname.getwd do + assert_equal "tmp/screenshots/x.png", new_test.send(:image_path) + end end test "image path includes failures text if test did not pass" do new_test = DrivenBySeleniumWithChrome.new("x") - new_test.stub :passed?, false do - assert_equal "tmp/screenshots/failures_x.png", new_test.send(:image_path) + Rails.stub :root, Pathname.getwd do + new_test.stub :passed?, false do + assert_equal "tmp/screenshots/failures_x.png", new_test.send(:image_path) + end end end test "image path does not include failures text if test skipped" do new_test = DrivenBySeleniumWithChrome.new("x") - new_test.stub :passed?, false do - new_test.stub :skipped?, true do - assert_equal "tmp/screenshots/x.png", new_test.send(:image_path) + Rails.stub :root, Pathname.getwd do + new_test.stub :passed?, false do + new_test.stub :skipped?, true do + assert_equal "tmp/screenshots/x.png", new_test.send(:image_path) + end + end + end + end + + test "display_image return artifact format when specify RAILS_SYSTEM_TESTING_SCREENSHOT environment" do + begin + original_output_type = ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"] + ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"] = "artifact" + + new_test = DrivenBySeleniumWithChrome.new("x") + + Rails.stub :root, Pathname.getwd do + new_test.stub :passed?, false do + assert_match %r|url=artifact://.+?tmp/screenshots/failures_x\.png|, new_test.send(:display_image) + end end + ensure + ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"] = original_output_type + end + end + + test "image path returns the relative path from current directory" do + new_test = DrivenBySeleniumWithChrome.new("x") + + Rails.stub :root, Pathname.getwd.join("..") do + assert_equal "../tmp/screenshots/x.png", new_test.send(:image_path) end end end diff --git a/actionpack/test/dispatch/system_testing/server_test.rb b/actionpack/test/dispatch/system_testing/server_test.rb index 10412d6367..1866225fc1 100644 --- a/actionpack/test/dispatch/system_testing/server_test.rb +++ b/actionpack/test/dispatch/system_testing/server_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "capybara/dsl" require "action_dispatch/system_testing/server" @@ -7,10 +9,6 @@ class ServerTest < ActiveSupport::TestCase ActionDispatch::SystemTesting::Server.new.run end - test "initializing the server port" do - assert_includes Capybara.servers, :rails_puma - end - test "port is always included" do assert Capybara.always_include_port, "expected Capybara.always_include_port to be true" end diff --git a/actionpack/test/dispatch/system_testing/system_test_case_test.rb b/actionpack/test/dispatch/system_testing/system_test_case_test.rb index 33d98f924f..c6a6aef92b 100644 --- a/actionpack/test/dispatch/system_testing/system_test_case_test.rb +++ b/actionpack/test/dispatch/system_testing/system_test_case_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class SetDriverToRackTestTest < DrivenByRackTest @@ -19,3 +21,58 @@ class SetDriverToSeleniumTest < DrivenBySeleniumWithChrome assert_equal :selenium, Capybara.current_driver end end + +class SetDriverToSeleniumHeadlessChromeTest < DrivenBySeleniumWithHeadlessChrome + test "uses selenium headless chrome" do + assert_equal :selenium, Capybara.current_driver + end +end + +class SetHostTest < DrivenByRackTest + test "sets default host" do + assert_equal "http://127.0.0.1", Capybara.app_host + end + + test "overrides host" do + host! "http://example.com" + + assert_equal "http://example.com", Capybara.app_host + end +end + +class UndefMethodsTest < DrivenBySeleniumWithChrome + test "get" do + exception = assert_raise NoMethodError do + get "http://example.com" + end + assert_equal "System tests cannot make direct requests via #get; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message + end + + test "post" do + exception = assert_raise NoMethodError do + post "http://example.com" + end + assert_equal "System tests cannot make direct requests via #post; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message + end + + test "put" do + exception = assert_raise NoMethodError do + put "http://example.com" + end + assert_equal "System tests cannot make direct requests via #put; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message + end + + test "patch" do + exception = assert_raise NoMethodError do + patch "http://example.com" + end + assert_equal "System tests cannot make direct requests via #patch; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message + end + + test "delete" do + exception = assert_raise NoMethodError do + delete "http://example.com" + end + assert_equal "System tests cannot make direct requests via #delete; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message + end +end diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb index 85a6df4975..e56537d80b 100644 --- a/actionpack/test/dispatch/test_request_test.rb +++ b/actionpack/test/dispatch/test_request_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class TestRequestTest < ActiveSupport::TestCase diff --git a/actionpack/test/dispatch/test_response_test.rb b/actionpack/test/dispatch/test_response_test.rb index 98eafb5119..f0b8f7785d 100644 --- a/actionpack/test/dispatch/test_response_test.rb +++ b/actionpack/test/dispatch/test_response_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class TestResponseTest < ActiveSupport::TestCase @@ -25,4 +27,11 @@ class TestResponseTest < ActiveSupport::TestCase response = ActionDispatch::TestResponse.create(200, { "Content-Type" => "application/json" }, '{ "foo": "fighters" }') assert_equal({ "foo" => "fighters" }, response.parsed_body) end + + test "response status aliases deprecated" do + response = ActionDispatch::TestResponse.create + assert_deprecated { response.success? } + assert_deprecated { response.missing? } + assert_deprecated { response.error? } + end end diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb index 51680216e4..4673d7cc11 100644 --- a/actionpack/test/dispatch/uploaded_file_test.rb +++ b/actionpack/test/dispatch/uploaded_file_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ActionDispatch @@ -13,6 +15,12 @@ module ActionDispatch assert_equal "foo", uf.original_filename end + def test_filename_is_different_object + file_str = "foo" + uf = Http::UploadedFile.new(filename: file_str, tempfile: Object.new) + assert_not_equal file_str.object_id , uf.original_filename.object_id + end + def test_filename_should_be_in_utf_8 uf = Http::UploadedFile.new(filename: "foo", tempfile: Object.new) assert_equal "UTF-8", uf.original_filename.encoding.to_s diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb index 5d81fd6834..aef9351de1 100644 --- a/actionpack/test/dispatch/url_generation_test.rb +++ b/actionpack/test/dispatch/url_generation_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module TestUrlGeneration diff --git a/actionpack/test/fixtures/alternate_helpers/foo_helper.rb b/actionpack/test/fixtures/alternate_helpers/foo_helper.rb index 2528584473..3aadb6145e 100644 --- a/actionpack/test/fixtures/alternate_helpers/foo_helper.rb +++ b/actionpack/test/fixtures/alternate_helpers/foo_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module FooHelper redefine_method(:baz) {} end diff --git a/actionpack/test/fixtures/company.rb b/actionpack/test/fixtures/company.rb index 9f527acdd8..93afdd5472 100644 --- a/actionpack/test/fixtures/company.rb +++ b/actionpack/test/fixtures/company.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Company < ActiveRecord::Base has_one :mascot self.sequence_name = :companies_nonstd_seq diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb index 9b88fa1f5a..dfcd423978 100644 --- a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb +++ b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb @@ -1,3 +1,3 @@ <body> -<%= cache do %><p>ERB</p><% end %> +<%= cache("fragment") do %><p>ERB</p><% end %> </body> diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder index efdcc28e0f..6599579740 100644 --- a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder +++ b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder @@ -1,5 +1,5 @@ xml.body do - cache do + cache("fragment") do xml.p "Builder" end end diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb index e523b74ae3..abf7017ce6 100644 --- a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb +++ b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb @@ -1,3 +1,3 @@ <body> -<%= cache do %><p>PHONE</p><% end %> +<%= cache("fragment") do %><p>PHONE</p><% end %> </body> diff --git a/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb b/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb index fa5e6bd318..1148d83ad7 100644 --- a/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb +++ b/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb @@ -1,3 +1,3 @@ Hello -<%= cache do %>This bit's fragment cached<% end %> +<%= cache "fragment" do %>This bit's fragment cached<% end %> <%= 'Ciao' %> diff --git a/actionpack/test/fixtures/helpers/abc_helper.rb b/actionpack/test/fixtures/helpers/abc_helper.rb index cf2774bb5f..999b9b5c6e 100644 --- a/actionpack/test/fixtures/helpers/abc_helper.rb +++ b/actionpack/test/fixtures/helpers/abc_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module AbcHelper def bare_a() end end diff --git a/actionpack/test/fixtures/helpers/fun/games_helper.rb b/actionpack/test/fixtures/helpers/fun/games_helper.rb index 2d5e50f5a5..8b325927f3 100644 --- a/actionpack/test/fixtures/helpers/fun/games_helper.rb +++ b/actionpack/test/fixtures/helpers/fun/games_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Fun module GamesHelper def stratego() "Iz guuut!" end diff --git a/actionpack/test/fixtures/helpers/fun/pdf_helper.rb b/actionpack/test/fixtures/helpers/fun/pdf_helper.rb index 16057fd466..7ce6591de3 100644 --- a/actionpack/test/fixtures/helpers/fun/pdf_helper.rb +++ b/actionpack/test/fixtures/helpers/fun/pdf_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Fun module PdfHelper def foobar() "baz" end diff --git a/actionpack/test/fixtures/helpers/just_me_helper.rb b/actionpack/test/fixtures/helpers/just_me_helper.rb index 9b43fc6d49..bd977a22d9 100644 --- a/actionpack/test/fixtures/helpers/just_me_helper.rb +++ b/actionpack/test/fixtures/helpers/just_me_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module JustMeHelper def me() "mine!" end end diff --git a/actionpack/test/fixtures/helpers/me_too_helper.rb b/actionpack/test/fixtures/helpers/me_too_helper.rb index 8e312e7cd0..c6fc053dee 100644 --- a/actionpack/test/fixtures/helpers/me_too_helper.rb +++ b/actionpack/test/fixtures/helpers/me_too_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MeTooHelper def me() "me too!" end end diff --git a/actionpack/test/fixtures/helpers1_pack/pack1_helper.rb b/actionpack/test/fixtures/helpers1_pack/pack1_helper.rb index 9faa427736..cf75b6875e 100644 --- a/actionpack/test/fixtures/helpers1_pack/pack1_helper.rb +++ b/actionpack/test/fixtures/helpers1_pack/pack1_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Pack1Helper def conflicting_helper "pack1" diff --git a/actionpack/test/fixtures/helpers2_pack/pack2_helper.rb b/actionpack/test/fixtures/helpers2_pack/pack2_helper.rb index cf56697dfb..c8e51d40a2 100644 --- a/actionpack/test/fixtures/helpers2_pack/pack2_helper.rb +++ b/actionpack/test/fixtures/helpers2_pack/pack2_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Pack2Helper def conflicting_helper "pack2" diff --git a/actionpack/test/fixtures/helpers_typo/admin/users_helper.rb b/actionpack/test/fixtures/helpers_typo/admin/users_helper.rb index 64aa1a0476..0455e26b93 100644 --- a/actionpack/test/fixtures/helpers_typo/admin/users_helper.rb +++ b/actionpack/test/fixtures/helpers_typo/admin/users_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Admin module UsersHelpeR end diff --git a/actionpack/test/fixtures/load_me.rb b/actionpack/test/fixtures/load_me.rb index e516512a4e..efafe6898f 100644 --- a/actionpack/test/fixtures/load_me.rb +++ b/actionpack/test/fixtures/load_me.rb @@ -1,2 +1,4 @@ +# frozen_string_literal: true + class LoadMe end diff --git a/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb b/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb index 18fa5cd923..deb81c647d 100644 --- a/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb +++ b/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SessionAutoloadTest class Foo def initialize(bar = "baz") diff --git a/actionpack/test/journey/gtg/builder_test.rb b/actionpack/test/journey/gtg/builder_test.rb index aa8427b265..b92460884d 100644 --- a/actionpack/test/journey/gtg/builder_test.rb +++ b/actionpack/test/journey/gtg/builder_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ActionDispatch diff --git a/actionpack/test/journey/gtg/transition_table_test.rb b/actionpack/test/journey/gtg/transition_table_test.rb index c7315c0338..9044934f05 100644 --- a/actionpack/test/journey/gtg/transition_table_test.rb +++ b/actionpack/test/journey/gtg/transition_table_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "active_support/json/decoding" @@ -19,7 +21,7 @@ module ActionDispatch assert json["accepting"] end - if system("dot -V 2>/dev/null") + if system("dot -V", 2 => File::NULL) def test_to_svg table = tt %w{ /articles(.:format) @@ -35,25 +37,25 @@ module ActionDispatch def test_simulate_gt sim = simulator_for ["/foo", "/bar"] - assert_match sim, "/foo" + assert_match_route sim, "/foo" end def test_simulate_gt_regexp sim = simulator_for [":foo"] - assert_match sim, "foo" + assert_match_route sim, "foo" end def test_simulate_gt_regexp_mix sim = simulator_for ["/get", "/:method/foo"] - assert_match sim, "/get" - assert_match sim, "/get/foo" + assert_match_route sim, "/get" + assert_match_route sim, "/get/foo" end def test_simulate_optional sim = simulator_for ["/foo(/bar)"] - assert_match sim, "/foo" - assert_match sim, "/foo/bar" - assert_no_match sim, "/foo/" + assert_match_route sim, "/foo" + assert_match_route sim, "/foo/bar" + assert_no_match_route sim, "/foo/" end def test_match_data @@ -65,11 +67,11 @@ module ActionDispatch sim = GTG::Simulator.new tt - match = sim.match "/get" - assert_equal [paths.first], match.memos + memos = sim.memos "/get" + assert_equal [paths.first], memos - match = sim.match "/get/foo" - assert_equal [paths.last], match.memos + memos = sim.memos "/get/foo" + assert_equal [paths.last], memos end def test_match_data_ambiguous @@ -86,8 +88,8 @@ module ActionDispatch builder = GTG::Builder.new ast sim = GTG::Simulator.new builder.transition_table - match = sim.match "/articles/new" - assert_equal [paths[1], paths[3]], match.memos + memos = sim.memos "/articles/new" + assert_equal [paths[1], paths[3]], memos end private @@ -109,6 +111,14 @@ module ActionDispatch def simulator_for(paths) GTG::Simulator.new tt(paths) end + + def assert_match_route(simulator, path) + assert simulator.memos(path), "Simulator should match #{path}." + end + + def assert_no_match_route(simulator, path) + assert_not simulator.memos(path) { nil }, "Simulator should not match #{path}." + end end end end diff --git a/actionpack/test/journey/nfa/simulator_test.rb b/actionpack/test/journey/nfa/simulator_test.rb index 38f99398cb..6b9f87b452 100644 --- a/actionpack/test/journey/nfa/simulator_test.rb +++ b/actionpack/test/journey/nfa/simulator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ActionDispatch diff --git a/actionpack/test/journey/nfa/transition_table_test.rb b/actionpack/test/journey/nfa/transition_table_test.rb index 0bc6bc1cf8..c23611e980 100644 --- a/actionpack/test/journey/nfa/transition_table_test.rb +++ b/actionpack/test/journey/nfa/transition_table_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ActionDispatch diff --git a/actionpack/test/journey/nodes/symbol_test.rb b/actionpack/test/journey/nodes/symbol_test.rb index baf60f40b8..1e687acef2 100644 --- a/actionpack/test/journey/nodes/symbol_test.rb +++ b/actionpack/test/journey/nodes/symbol_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ActionDispatch diff --git a/actionpack/test/journey/path/pattern_test.rb b/actionpack/test/journey/path/pattern_test.rb index 2c74617944..3e7aea57f1 100644 --- a/actionpack/test/journey/path/pattern_test.rb +++ b/actionpack/test/journey/path/pattern_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ActionDispatch diff --git a/actionpack/test/journey/route/definition/parser_test.rb b/actionpack/test/journey/route/definition/parser_test.rb index 8c6e3c0371..39693198b8 100644 --- a/actionpack/test/journey/route/definition/parser_test.rb +++ b/actionpack/test/journey/route/definition/parser_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ActionDispatch diff --git a/actionpack/test/journey/route/definition/scanner_test.rb b/actionpack/test/journey/route/definition/scanner_test.rb index 98578ddbf1..070886c7df 100644 --- a/actionpack/test/journey/route/definition/scanner_test.rb +++ b/actionpack/test/journey/route/definition/scanner_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ActionDispatch diff --git a/actionpack/test/journey/route_test.rb b/actionpack/test/journey/route_test.rb index 8fd73970b8..a8bf4a11e2 100644 --- a/actionpack/test/journey/route_test.rb +++ b/actionpack/test/journey/route_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ActionDispatch diff --git a/actionpack/test/journey/router/utils_test.rb b/actionpack/test/journey/router/utils_test.rb index b77bf6628a..2d09098f11 100644 --- a/actionpack/test/journey/router/utils_test.rb +++ b/actionpack/test/journey/router/utils_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ActionDispatch @@ -21,7 +23,7 @@ module ActionDispatch end def test_uri_unescape_with_utf8_string - assert_equal "Šašinková", Utils.unescape_uri("%C5%A0a%C5%A1inkov%C3%A1".force_encoding(Encoding::US_ASCII)) + assert_equal "Šašinková", Utils.unescape_uri("%C5%A0a%C5%A1inkov%C3%A1".dup.force_encoding(Encoding::US_ASCII)) end def test_normalize_path_not_greedy @@ -31,6 +33,15 @@ module ActionDispatch def test_normalize_path_uppercase assert_equal "/foo%AAbar%AAbaz", Utils.normalize_path("/foo%aabar%aabaz") end + + def test_normalize_path_maintains_string_encoding + path = "/foo%AAbar%AAbaz".b + assert_equal Encoding::ASCII_8BIT, Utils.normalize_path(path).encoding + end + + def test_normalize_path_with_nil + assert_equal "/", Utils.normalize_path(nil) + end end end end diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb index f223a125a3..29cc74471d 100644 --- a/actionpack/test/journey/router_test.rb +++ b/actionpack/test/journey/router_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ActionDispatch @@ -184,14 +186,14 @@ module ActionDispatch def test_required_part_in_recall get "/messages/:a/:b", to: "foo#bar" - path, _ = @formatter.generate(nil, { controller: "foo", action: "bar", a: "a" }, b: "b") + path, _ = @formatter.generate(nil, { controller: "foo", action: "bar", a: "a" }, { b: "b" }) assert_equal "/messages/a/b", path end def test_splat_in_recall get "/*path", to: "foo#bar" - path, _ = @formatter.generate(nil, { controller: "foo", action: "bar" }, path: "b") + path, _ = @formatter.generate(nil, { controller: "foo", action: "bar" }, { path: "b" }) assert_equal "/b", path end @@ -199,7 +201,7 @@ module ActionDispatch get "/messages/:action(/:id(.:format))", to: "foo#bar" get "/messages/:id(.:format)", to: "bar#baz" - path, _ = @formatter.generate(nil, { controller: "foo", id: 10 }, action: "index") + path, _ = @formatter.generate(nil, { controller: "foo", id: 10 }, { action: "index" }) assert_equal "/messages/index/10", path end @@ -312,7 +314,7 @@ module ActionDispatch path, params = @formatter.generate( nil, { controller: "tasks", id: 10 }, - action: "index") + { action: "index" }) assert_equal "/tasks/index/10", path assert_equal({}, params) end @@ -323,7 +325,7 @@ module ActionDispatch path, params = @formatter.generate( "tasks", { controller: "tasks" }, - controller: "tasks", action: "index") + { controller: "tasks", action: "index" }) assert_equal "/tasks", path assert_equal({}, params) end diff --git a/actionpack/test/journey/routes_test.rb b/actionpack/test/journey/routes_test.rb index d8db5ffad1..81ce07526f 100644 --- a/actionpack/test/journey/routes_test.rb +++ b/actionpack/test/journey/routes_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ActionDispatch diff --git a/actionpack/test/lib/controller/fake_controllers.rb b/actionpack/test/lib/controller/fake_controllers.rb index 1a2863b689..e985716f43 100644 --- a/actionpack/test/lib/controller/fake_controllers.rb +++ b/actionpack/test/lib/controller/fake_controllers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ContentController < ActionController::Base; end module Admin diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index b768553e7a..01c7ec26ae 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_model" Customer = Struct.new(:name, :id) do @@ -26,6 +28,10 @@ Customer = Struct.new(:name, :id) do def persisted? id.present? end + + def cache_key + "#{name}/#{id}" + end end Post = Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost) do diff --git a/actionpack/test/routing/helper_test.rb b/actionpack/test/routing/helper_test.rb index 0debacedf7..d13b043b0b 100644 --- a/actionpack/test/routing/helper_test.rb +++ b/actionpack/test/routing/helper_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ActionDispatch |