diff options
Diffstat (limited to 'actionpack')
129 files changed, 1356 insertions, 453 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 9fcff6a6ca..8bb2c73e52 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,94 @@ +* Add `Vary: Accept` header when using `Accept` header for response + + For some requests like `/users/1`, Rails uses requests' `Accept` + header to determine what to return. And if we don't add `Vary` + in the response header, browsers might accidentally cache different + types of content, which would cause issues: e.g. javascript got displayed + instead of html content. This PR fixes these issues by adding `Vary: Accept` + in these types of requests. For more detailed problem description, please read: + + https://github.com/rails/rails/pull/36213 + + Fixes #25842 + + *Stan Lo* + +* Fix IntegrationTest `follow_redirect!` to follow redirection using the same HTTP verb when following + a 307 redirection. + + *Edouard Chin* + +* System tests require Capybara 3.26 or newer. + + *George Claghorn* + +* Reduced log noise handling ActionController::RoutingErrors. + + *Alberto Fernández-Capel* + +* Add DSL for configuring HTTP Feature Policy + + This new DSL provides a way to configure a HTTP Feature Policy at a + global or per-controller level. Full details of HTTP Feature Policy + specification and guidelines can be found at MDN: + + https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy + + Example global policy + + ``` + Rails.application.config.feature_policy do |f| + f.camera :none + f.gyroscope :none + f.microphone :none + f.usb :none + f.fullscreen :self + f.payment :self, "https://secure.example.com" + end + ``` + + Example controller level policy + + ``` + class PagesController < ApplicationController + feature_policy do |p| + p.geolocation "https://example.com" + end + end + ``` + + *Jacob Bednarz* + +* Add the ability to set the CSP nonce only to the specified directives. + + Fixes #35137. + + *Yuji Yaginuma* + +* Keep part when scope option has value. + + When a route was defined within an optional scope, if that route didn't + take parameters the scope was lost when using path helpers. This commit + ensures scope is kept both when the route takes parameters or when it + doesn't. + + Fixes #33219. + + *Alberto Almagro* + +* Added `deep_transform_keys` and `deep_transform_keys!` methods to ActionController::Parameters. + + *Gustavo Gutierrez* + +* Calling `ActionController::Parameters#transform_keys/!` without a block now returns + an enumerator for the parameters instead of the underlying hash. + + *Eugene Kenny* + +* Fix strong parameters blocks all attributes even when only some keys are invalid (non-numerical). + It should only block invalid key's values instead. + + *Stan Lo* Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/actionpack/CHANGELOG.md) for previous changes. diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index 3a98931167..d1ff62a032 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "action_pack" +require "active_support" require "active_support/rails" require "active_support/i18n" diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index bb42f2e119..3ff922029b 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -176,7 +176,6 @@ module AbstractController end private - # Returns true if the name can be considered an action because # it has a method defined in the controller. # diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb index d4a078ab32..0af546cc96 100644 --- a/actionpack/lib/abstract_controller/collector.rb +++ b/actionpack/lib/abstract_controller/collector.rb @@ -22,7 +22,6 @@ module AbstractController end private - def method_missing(symbol, &block) unless mime_constant = Mime[symbol] raise NoMethodError, "To respond to a custom format, register it as a MIME type first: " \ diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index 3913259ecc..abb09456e0 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -7,7 +7,7 @@ module AbstractController extend ActiveSupport::Concern included do - class_attribute :_helpers, default: Module.new + class_attribute :_helpers, default: define_helpers_module(self) class_attribute :_helper_methods, default: Array.new end @@ -31,7 +31,7 @@ module AbstractController # independently of the child class's. def inherited(klass) helpers = _helpers - klass._helpers = Module.new { include helpers } + klass._helpers = define_helpers_module(klass, helpers) klass.class_eval { default_helper_module! } unless klass.anonymous? super end @@ -61,12 +61,17 @@ module AbstractController meths.flatten! self._helper_methods += meths + location = caller_locations(1, 1).first + file, line = location.path, location.lineno + meths.each do |meth| - _helpers.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1 - def #{meth}(*args, &blk) # def current_user(*args, &blk) - controller.send(%(#{meth}), *args, &blk) # controller.send(:current_user, *args, &blk) - end # end - ruby_eval + method_def = [ + "def #{meth}(*args, &blk)", + " controller.send(%(#{meth}), *args, &blk)", + "end" + ].join(";") + + _helpers.class_eval method_def, file, line end end @@ -170,6 +175,17 @@ module AbstractController end private + def define_helpers_module(klass, helpers = nil) + # In some tests inherited is called explicitly. In that case, just + # return the module from the first time it was defined + return klass.const_get(:HelperMethods) if klass.const_defined?(:HelperMethods, false) + + mod = Module.new + klass.const_set(:HelperMethods, mod) + mod.include(helpers) if helpers + mod + end + # Makes all the (instance) methods in the helper module available to templates # rendered through this controller. # diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 8ba2b25552..280af50a44 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -28,6 +28,7 @@ module AbstractController else _set_rendered_content_type rendered_format end + _set_vary_header self.response_body = rendered_body end @@ -109,6 +110,9 @@ module AbstractController def _set_html_content_type # :nodoc: end + def _set_vary_header # :nodoc: + end + def _set_rendered_content_type(format) # :nodoc: end diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 29d61c3ceb..22dc229599 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "active_support/rails" require "abstract_controller" require "action_dispatch" require "action_controller/metal/live" @@ -28,6 +27,7 @@ module ActionController autoload :DefaultHeaders autoload :EtagWithTemplateDigest autoload :EtagWithFlash + autoload :FeaturePolicy autoload :Flash autoload :ForceSSL autoload :Head diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 2e565d5d44..63c138af55 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -226,6 +226,7 @@ module ActionController FormBuilder, RequestForgeryProtection, ContentSecurityPolicy, + FeaturePolicy, ForceSSL, Streaming, DataStreaming, diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index bf3b00a7b7..83e3e0c37c 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -30,7 +30,6 @@ module ActionController end private - def instrument_payload(key) { controller: controller_name, diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index b9088e6d86..ec2207b8da 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -35,7 +35,6 @@ module ActionController end private - INCLUDE = ->(list, action) { list.include? action } EXCLUDE = ->(list, action) { !list.include? action } NULL = ->(list, action) { true } @@ -148,7 +147,7 @@ module ActionController attr_internal :response, :request delegate :session, to: "@_request" delegate :headers, :status=, :location=, :content_type=, - :status, :location, :content_type, to: "@_response" + :status, :location, :content_type, :media_type, to: "@_response" def initialize @_request = nil diff --git a/actionpack/lib/action_controller/metal/content_security_policy.rb b/actionpack/lib/action_controller/metal/content_security_policy.rb index b8fab4ebe3..ebd90f07c8 100644 --- a/actionpack/lib/action_controller/metal/content_security_policy.rb +++ b/actionpack/lib/action_controller/metal/content_security_policy.rb @@ -36,7 +36,6 @@ module ActionController #:nodoc: end private - def content_security_policy? request.content_security_policy end diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb index 9ef4f50df1..879745a895 100644 --- a/actionpack/lib/action_controller/metal/data_streaming.rb +++ b/actionpack/lib/action_controller/metal/data_streaming.rb @@ -53,7 +53,7 @@ module ActionController #:nodoc: # # Show a 404 page in the browser: # - # send_file '/path/to/404.html', type: 'text/html; charset=utf-8', status: 404 + # send_file '/path/to/404.html', type: 'text/html; charset=utf-8', disposition: 'inline', status: 404 # # Read about the other Content-* HTTP headers if you'd like to # provide the user with more information (such as Content-Description) in diff --git a/actionpack/lib/action_controller/metal/feature_policy.rb b/actionpack/lib/action_controller/metal/feature_policy.rb new file mode 100644 index 0000000000..a627eabea6 --- /dev/null +++ b/actionpack/lib/action_controller/metal/feature_policy.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module ActionController #:nodoc: + # HTTP Feature Policy is a web standard for defining a mechanism to + # allow and deny the use of browser features in its own context, and + # in content within any <iframe> elements in the document. + # + # Full details of HTTP Feature Policy specification and guidelines can + # be found at MDN: + # + # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy + # + # Examples of usage: + # + # # Global policy + # Rails.application.config.feature_policy do |f| + # f.camera :none + # f.gyroscope :none + # f.microphone :none + # f.usb :none + # f.fullscreen :self + # f.payment :self, "https://secure.example.com" + # end + # + # # Controller level policy + # class PagesController < ApplicationController + # feature_policy do |p| + # p.geolocation "https://example.com" + # end + # end + module FeaturePolicy + extend ActiveSupport::Concern + + module ClassMethods + def feature_policy(**options, &block) + before_action(options) do + if block_given? + policy = request.feature_policy.clone + yield policy + request.feature_policy = policy + end + end + end + end + end +end diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index 51fac08749..6f7fc0d624 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -69,7 +69,6 @@ module ActionController end private - # A hook invoked every time a before callback is halted. def halted_callback_hook(filter) ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter) diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index dd69930e25..4454ba1e3d 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -107,7 +107,6 @@ module ActionController end private - def perform_write(json, options) current_options = @options.merge(options).stringify_keys @@ -205,7 +204,6 @@ module ActionController end private - def each_chunk(&block) loop do str = nil @@ -220,7 +218,6 @@ module ActionController class Response < ActionDispatch::Response #:nodoc: all private - def before_committed super jar = request.cookie_jar @@ -286,7 +283,6 @@ module ActionController end private - # Spawn a new thread to serve up the controller in. This is to get # around the fact that Rack isn't based around IOs and we need to use # a thread to stream data from the response bodies. Nobody should call diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index bf5e7a433f..5c6f7fe396 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -205,7 +205,7 @@ module ActionController #:nodoc: yield collector if block_given? if format = collector.negotiate_format(request) - if content_type && content_type != format + if media_type && media_type != format raise ActionController::RespondToMismatchError end _process_format(format) diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index e635abec8e..150ae2666c 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -246,7 +246,6 @@ module ActionController end private - # Returns the wrapper key which will be used to store wrapped parameters. def _wrapper_key _wrapper_options.name diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index b81d3ef539..a251c29d23 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -157,7 +157,7 @@ module ActionController json = json.to_json(options) unless json.kind_of?(String) if options[:callback].present? - if content_type.nil? || content_type == Mime[:json] + if media_type.nil? || media_type == Mime[:json] self.content_type = Mime[:js] end diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 7d0a944381..fd22c4fa64 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -53,7 +53,6 @@ module ActionController end private - def _process_variant(options) if defined?(request) && !request.nil? && request.variant.present? options[:variant] = request.variant @@ -73,11 +72,15 @@ module ActionController end def _set_rendered_content_type(format) - if format && !response.content_type + if format && !response.media_type self.content_type = format.to_s end end + def _set_vary_header + self.headers["Vary"] = "Accept" if request.should_apply_vary_header? + end + # Normalize arguments by catching blocks and setting them on :update. def _normalize_args(action = nil, options = {}, &blk) options = super diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 4bf8d90b69..5a5c04234b 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -151,7 +151,6 @@ module ActionController #:nodoc: end private - def protection_method_class(name) ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify) rescue NameError @@ -175,7 +174,6 @@ module ActionController #:nodoc: end private - class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc: def initialize(req) super(nil, req) diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 8dc01a5eb9..94a62e5cab 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -196,7 +196,6 @@ module ActionController #:nodoc: extend ActiveSupport::Concern private - # Set proper cache control and transfer encoding when streaming def _process_options(options) super diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index ae774b01f1..920ae52f2b 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -223,6 +223,12 @@ module ActionController # config.always_permitted_parameters = %w( controller action format ) cattr_accessor :always_permitted_parameters, default: %w( controller action ) + class << self + def nested_attribute?(key, value) # :nodoc: + key =~ /\A-?\d+\z/ && (value.is_a?(Hash) || value.is_a?(Parameters)) + end + end + # Returns a new instance of <tt>ActionController::Parameters</tt>. # Also, sets the +permitted+ attribute to the default value of # <tt>ActionController::Parameters.permit_all_parameters</tt>. @@ -253,6 +259,11 @@ module ActionController @parameters == other end end + alias eql? == + + def hash + [@parameters.hash, @permitted].hash + end # Returns a safe <tt>ActiveSupport::HashWithIndifferentAccess</tt> # representation of the parameters with all unpermitted keys removed. @@ -673,22 +684,37 @@ module ActionController # Returns a new <tt>ActionController::Parameters</tt> instance with the # results of running +block+ once for every key. The values are unchanged. def transform_keys(&block) - if block - new_instance_with_inherited_permitted_status( - @parameters.transform_keys(&block) - ) - else - @parameters.transform_keys - end + return to_enum(:transform_keys) unless block_given? + new_instance_with_inherited_permitted_status( + @parameters.transform_keys(&block) + ) end # Performs keys transformation and returns the altered # <tt>ActionController::Parameters</tt> instance. def transform_keys!(&block) + return to_enum(:transform_keys!) unless block_given? @parameters.transform_keys!(&block) self end + # Returns a new <tt>ActionController::Parameters</tt> instance with the + # results of running +block+ once for every key. This includes the keys + # from the root hash and from all nested hashes and arrays. The values are unchanged. + def deep_transform_keys(&block) + new_instance_with_inherited_permitted_status( + @parameters.deep_transform_keys(&block) + ) + end + + # Returns the <tt>ActionController::Parameters</tt> instance changing its keys. + # This includes the keys from the root hash and from all nested hashes and arrays. + # The values are unchanged. + def deep_transform_keys!(&block) + @parameters.deep_transform_keys!(&block) + self + end + # 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 @@ -723,6 +749,18 @@ module ActionController end alias_method :delete_if, :reject! + # Returns a new instance of <tt>ActionController::Parameters</tt> without the blank values. + # Uses Object#blank? for determining if a value is blank. + def compact_blank + reject { |_k, v| v.blank? } + end + + # Removes all blank values in place and returns self. + # Uses Object#blank? for determining if a value is blank. + def compact_blank! + reject! { |_k, v| v.blank? } + end + # Returns values that were assigned to the given +keys+. Note that all the # +Hash+ objects will be converted to <tt>ActionController::Parameters</tt>. def values_at(*keys) @@ -811,8 +849,14 @@ module ActionController attr_writer :permitted - def fields_for_style? - @parameters.all? { |k, v| k =~ /\A-?\d+\z/ && (v.is_a?(Hash) || v.is_a?(Parameters)) } + def nested_attributes? + @parameters.any? { |k, v| Parameters.nested_attribute?(k, v) } + end + + def each_nested_attribute + hash = self.class.new + self.each { |k, v| hash[k] = yield v if Parameters.nested_attribute?(k, v) } + hash end private @@ -857,15 +901,13 @@ module ActionController end end - def each_element(object) + def each_element(object, &block) case object when Array object.grep(Parameters).map { |el| yield el }.compact when Parameters - if object.fields_for_style? - hash = object.class.new - object.each { |k, v| hash[k] = yield v } - hash + if object.nested_attributes? + object.each_nested_attribute(&block) else yield object end diff --git a/actionpack/lib/action_controller/template_assertions.rb b/actionpack/lib/action_controller/template_assertions.rb index dd83c1a283..ec44dbe157 100644 --- a/actionpack/lib/action_controller/template_assertions.rb +++ b/actionpack/lib/action_controller/template_assertions.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module ActionController - module TemplateAssertions + module TemplateAssertions # :nodoc: def assert_template(options = {}, message = nil) raise NoMethodError, "assert_template has been extracted to a gem. To continue using it, diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 57921f32b7..47e0099f20 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -158,7 +158,6 @@ module ActionController end.new private - def params_parsers super.merge @custom_param_parsers end @@ -208,7 +207,6 @@ module ActionController end private - def load! @id end @@ -594,7 +592,6 @@ module ActionController end private - def scrub_env!(env) env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ } env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ } diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 6a4ba9af4a..67d303a368 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -43,6 +43,7 @@ module ActionDispatch eager_autoload do autoload_under "http" do autoload :ContentSecurityPolicy + autoload :FeaturePolicy autoload :Request autoload :Response end diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index 8cc84ff36c..7be30be77a 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -123,7 +123,6 @@ module ActionDispatch end private - DATE = "Date" LAST_MODIFIED = "Last-Modified" SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public private must-revalidate]) diff --git a/actionpack/lib/action_dispatch/http/content_security_policy.rb b/actionpack/lib/action_dispatch/http/content_security_policy.rb index b1e5a28be5..9c430b57e3 100644 --- a/actionpack/lib/action_dispatch/http/content_security_policy.rb +++ b/actionpack/lib/action_dispatch/http/content_security_policy.rb @@ -22,15 +22,15 @@ module ActionDispatch #:nodoc: if policy = request.content_security_policy nonce = request.content_security_policy_nonce + nonce_directives = request.content_security_policy_nonce_directives context = request.controller_instance || request - headers[header_name(request)] = policy.build(context, nonce) + headers[header_name(request)] = policy.build(context, nonce, nonce_directives) end response end private - def html_response?(headers) if content_type = headers[CONTENT_TYPE] content_type =~ /html/ @@ -55,6 +55,7 @@ module ActionDispatch #:nodoc: POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only" NONCE_GENERATOR = "action_dispatch.content_security_policy_nonce_generator" NONCE = "action_dispatch.content_security_policy_nonce" + NONCE_DIRECTIVES = "action_dispatch.content_security_policy_nonce_directives" def content_security_policy get_header(POLICY) @@ -80,6 +81,14 @@ module ActionDispatch #:nodoc: set_header(NONCE_GENERATOR, generator) end + def content_security_policy_nonce_directives + get_header(NONCE_DIRECTIVES) + end + + def content_security_policy_nonce_directives=(generator) + set_header(NONCE_DIRECTIVES, generator) + end + def content_security_policy_nonce if content_security_policy_nonce_generator if nonce = get_header(NONCE) @@ -91,7 +100,6 @@ module ActionDispatch #:nodoc: end private - def generate_content_security_policy_nonce content_security_policy_nonce_generator.call(self) end @@ -129,13 +137,17 @@ module ActionDispatch #:nodoc: object_src: "object-src", prefetch_src: "prefetch-src", script_src: "script-src", + script_src_attr: "script-src-attr", + script_src_elem: "script-src-elem", style_src: "style-src", + style_src_attr: "style-src-attr", + style_src_elem: "style-src-elem", worker_src: "worker-src" }.freeze - NONCE_DIRECTIVES = %w[script-src style-src].freeze + DEFAULT_NONCE_DIRECTIVES = %w[script-src style-src].freeze - private_constant :MAPPINGS, :DIRECTIVES, :NONCE_DIRECTIVES + private_constant :MAPPINGS, :DIRECTIVES, :DEFAULT_NONCE_DIRECTIVES attr_reader :directives @@ -204,8 +216,9 @@ module ActionDispatch #:nodoc: end end - def build(context = nil, nonce = nil) - build_directives(context, nonce).compact.join("; ") + def build(context = nil, nonce = nil, nonce_directives = nil) + nonce_directives = DEFAULT_NONCE_DIRECTIVES if nonce_directives.nil? + build_directives(context, nonce, nonce_directives).compact.join("; ") end private @@ -228,10 +241,10 @@ module ActionDispatch #:nodoc: end end - def build_directives(context, nonce) + def build_directives(context, nonce, nonce_directives) @directives.map do |directive, sources| if sources.is_a?(Array) - if nonce && nonce_directive?(directive) + if nonce && nonce_directive?(directive, nonce_directives) "#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'" else "#{directive} #{build_directive(sources, context).join(' ')}" @@ -266,8 +279,8 @@ module ActionDispatch #:nodoc: end end - def nonce_directive?(directive) - NONCE_DIRECTIVES.include?(directive) + def nonce_directive?(directive, nonce_directives) + nonce_directives.include?(directive) end end end diff --git a/actionpack/lib/action_dispatch/http/feature_policy.rb b/actionpack/lib/action_dispatch/http/feature_policy.rb new file mode 100644 index 0000000000..592b6e4393 --- /dev/null +++ b/actionpack/lib/action_dispatch/http/feature_policy.rb @@ -0,0 +1,168 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/deep_dup" + +module ActionDispatch #:nodoc: + class FeaturePolicy + class Middleware + CONTENT_TYPE = "Content-Type" + POLICY = "Feature-Policy" + + def initialize(app) + @app = app + end + + def call(env) + request = ActionDispatch::Request.new(env) + _, headers, _ = response = @app.call(env) + + return response unless html_response?(headers) + return response if policy_present?(headers) + + if policy = request.feature_policy + headers[POLICY] = policy.build(request.controller_instance) + end + + if policy_empty?(policy) + headers.delete(POLICY) + end + + response + end + + private + def html_response?(headers) + if content_type = headers[CONTENT_TYPE] + content_type =~ /html/ + end + end + + def policy_present?(headers) + headers[POLICY] + end + + def policy_empty?(policy) + policy.try(:directives) && policy.directives.empty? + end + end + + module Request + POLICY = "action_dispatch.feature_policy" + + def feature_policy + get_header(POLICY) + end + + def feature_policy=(policy) + set_header(POLICY, policy) + end + end + + MAPPINGS = { + self: "'self'", + none: "'none'", + }.freeze + + # List of available features can be found at + # https://github.com/WICG/feature-policy/blob/master/features.md#policy-controlled-features + DIRECTIVES = { + accelerometer: "accelerometer", + ambient_light_sensor: "ambient-light-sensor", + autoplay: "autoplay", + camera: "camera", + encrypted_media: "encrypted-media", + fullscreen: "fullscreen", + geolocation: "geolocation", + gyroscope: "gyroscope", + magnetometer: "magnetometer", + microphone: "microphone", + midi: "midi", + payment: "payment", + picture_in_picture: "picture-in-picture", + speaker: "speaker", + usb: "usb", + vibrate: "vibrate", + vr: "vr", + }.freeze + + private_constant :MAPPINGS, :DIRECTIVES + + attr_reader :directives + + def initialize + @directives = {} + yield self if block_given? + end + + def initialize_copy(other) + @directives = other.directives.deep_dup + end + + DIRECTIVES.each do |name, directive| + define_method(name) do |*sources| + if sources.first + @directives[directive] = apply_mappings(sources) + else + @directives.delete(directive) + end + end + end + + def build(context = nil) + build_directives(context).compact.join("; ") + end + + private + def apply_mappings(sources) + sources.map do |source| + case source + when Symbol + apply_mapping(source) + when String, Proc + source + else + raise ArgumentError, "Invalid HTTP feature policy source: #{source.inspect}" + end + end + end + + def apply_mapping(source) + MAPPINGS.fetch(source) do + raise ArgumentError, "Unknown HTTP feature policy source mapping: #{source.inspect}" + end + end + + def build_directives(context) + @directives.map do |directive, sources| + if sources.is_a?(Array) + "#{directive} #{build_directive(sources, context).join(' ')}" + elsif sources + directive + else + nil + end + end + end + + def build_directive(sources, context) + sources.map { |source| resolve_source(source, context) } + end + + def resolve_source(source, context) + case source + when String + source + when Symbol + source.to_s + when Proc + if context.nil? + raise RuntimeError, "Missing context for the dynamic feature policy source: #{source.inspect}" + else + context.instance_exec(&source) + end + else + raise RuntimeError, "Unexpected feature policy source: #{source.inspect}" + end + end + end +end diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index cbb772175c..7a7a493f64 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -56,7 +56,6 @@ module ActionDispatch end private - def parameter_filter # :doc: parameter_filter_for fetch_header("action_dispatch.parameter_filter") { return NULL_PARAM_FILTER diff --git a/actionpack/lib/action_dispatch/http/filter_redirect.rb b/actionpack/lib/action_dispatch/http/filter_redirect.rb index 8c4e852235..d780d5f793 100644 --- a/actionpack/lib/action_dispatch/http/filter_redirect.rb +++ b/actionpack/lib/action_dispatch/http/filter_redirect.rb @@ -14,7 +14,6 @@ module ActionDispatch end private - def location_filters if request request.get_header("action_dispatch.redirect_filter") || [] diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb index 6c7d24d2d0..6ab913bfd0 100644 --- a/actionpack/lib/action_dispatch/http/headers.rb +++ b/actionpack/lib/action_dispatch/http/headers.rb @@ -116,7 +116,6 @@ module ActionDispatch def env; @req.env.dup; end private - # Converts an HTTP header name to an environment variable name if it is # not contained within the headers hash. def env_name(key) diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 4e81ba12a5..ac0ff133eb 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -62,13 +62,7 @@ module ActionDispatch def formats fetch_header("action_dispatch.request.formats") do |k| - params_readable = begin - parameters[:format] - rescue *RESCUABLE_MIME_FORMAT_ERRORS - false - end - - v = if params_readable + v = if params_readable? Array(Mime[parameters[:format]]) elsif use_accept_header && valid_accept_header accepts @@ -153,10 +147,19 @@ module ActionDispatch order.include?(Mime::ALL) ? format : nil end - private + def should_apply_vary_header? + !params_readable? && use_accept_header && valid_accept_header + end + private BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/ + def params_readable? # :doc: + parameters[:format] + rescue *RESCUABLE_MIME_FORMAT_ERRORS + false + end + def valid_accept_header # :doc: (xhr? && (accept.present? || content_mime_type)) || (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS) diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 88b3a93211..ed1d50f3b9 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -290,11 +290,9 @@ module Mime def all?; false; end protected - attr_reader :string, :synonyms private - def to_ary; end def to_a; end diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 13d0963a33..3c16817af3 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -85,7 +85,6 @@ module ActionDispatch end private - def set_binary_encoding(params, controller, action) return params unless controller && controller.valid_encoding? diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 44f23940d3..4ac7c5c2bd 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -23,6 +23,7 @@ module ActionDispatch include ActionDispatch::Http::FilterParameters include ActionDispatch::Http::URL include ActionDispatch::ContentSecurityPolicy::Request + include ActionDispatch::FeaturePolicy::Request include Rack::Request::Env autoload :Session, "action_dispatch/request/session" diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 69798f99e0..ea3692951f 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -86,6 +86,7 @@ module ActionDispatch # :nodoc: cattr_accessor :default_charset, default: "utf-8" cattr_accessor :default_headers + cattr_accessor :return_only_media_type_on_content_type, default: false include Rack::Response::Helpers # Aliasing these off because AD::Http::Cache::Response defines them. @@ -143,7 +144,6 @@ module ActionDispatch # :nodoc: end private - def each_chunk(&block) @buf.each(&block) end @@ -243,8 +243,22 @@ module ActionDispatch # :nodoc: end # Content type of response. - # It returns just MIME type and does NOT contain charset part. def content_type + if self.class.return_only_media_type_on_content_type + ActiveSupport::Deprecation.warn( + "Rails 6.1 will return Content-Type header without modification." \ + " If you want just the MIME type, please use `#media_type` instead." + ) + + content_type = super + content_type ? content_type.split(/;\s*charset=/)[0].presence : content_type + else + super.presence + end + end + + # Media type of response. + def media_type parsed_content_type_header.mime_type end @@ -405,7 +419,6 @@ module ActionDispatch # :nodoc: end private - ContentTypeHeader = Struct.new :mime_type, :charset NullContentTypeHeader = ContentTypeHeader.new nil, nil @@ -458,7 +471,7 @@ module ActionDispatch # :nodoc: end def assign_default_content_type_and_charset! - return if content_type + return if media_type ct = parsed_content_type_header set_content_type(ct.mime_type || Mime[:html].to_s, diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 8227749986..3b0f6378ea 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -78,7 +78,6 @@ module ActionDispatch end private - def add_params(path, params) params = { params: params } unless params.is_a?(Hash) params.reject! { |_, v| v.to_param.nil? } diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb index 52396ec901..a4861719f8 100644 --- a/actionpack/lib/action_dispatch/journey/formatter.rb +++ b/actionpack/lib/action_dispatch/journey/formatter.rb @@ -62,12 +62,11 @@ module ActionDispatch end private - def extract_parameterized_parts(route, options, recall, parameterize = nil) parameterized_parts = recall.merge(options) keys_to_keep = route.parts.reverse_each.drop_while { |part| - !options.key?(part) || (options[part] || recall[part]).nil? + !(options.key?(part) || route.scope_options.key?(part)) || (options[part] || recall[part]).nil? } | route.required_parts parameterized_parts.delete_if do |bad_key, _| diff --git a/actionpack/lib/action_dispatch/journey/gtg/builder.rb b/actionpack/lib/action_dispatch/journey/gtg/builder.rb index 44c31053cb..2600e7fb70 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/builder.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/builder.rb @@ -128,7 +128,6 @@ module ActionDispatch end private - def followpos_table @followpos ||= build_followpos end diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb index ea647e051a..5003e92f43 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb @@ -141,7 +141,6 @@ module ActionDispatch end private - def states_hash_for(sym) case sym when String diff --git a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb index fe55861507..b36003089d 100644 --- a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb +++ b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb @@ -94,7 +94,6 @@ module ActionDispatch end private - def inverted return @inverted if @inverted diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb index dee2980eb1..e4ba82ebdd 100644 --- a/actionpack/lib/action_dispatch/journey/path/pattern.rb +++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb @@ -174,7 +174,6 @@ module ActionDispatch end private - def regexp_visitor @anchored ? AnchoredRegexp : UnanchoredRegexp end diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index 8165709a3d..9184676801 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -4,9 +4,9 @@ module ActionDispatch # :stopdoc: module Journey class Route - attr_reader :app, :path, :defaults, :name, :precedence + attr_reader :app, :path, :defaults, :name, :precedence, :constraints, + :internal, :scope_options - attr_reader :constraints, :internal alias :conditions :constraints module VerbMatchers @@ -49,15 +49,10 @@ module ActionDispatch end end - def self.build(name, app, path, constraints, required_defaults, defaults) - request_method_match = verb_matcher(constraints.delete(:request_method)) - new name, app, path, constraints, required_defaults, defaults, request_method_match, 0 - end - ## # +path+ is a path constraint. # +constraints+ is a hash of constraints to be applied to this route. - def initialize(name, app, path, constraints, required_defaults, defaults, request_method_match, precedence, internal = false) + def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {}, request_method_match: nil, precedence: 0, scope_options: {}, internal: false) @name = name @app = app @path = path @@ -72,6 +67,7 @@ module ActionDispatch @decorated_ast = nil @precedence = precedence @path_formatter = @path.build_formatter + @scope_options = scope_options @internal = internal end @@ -152,7 +148,7 @@ module ActionDispatch end def glob? - !path.spec.grep(Nodes::Star).empty? + path.spec.any?(Nodes::Star) end def dispatcher? diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb index 89a164f968..4a6639af74 100644 --- a/actionpack/lib/action_dispatch/journey/router.rb +++ b/actionpack/lib/action_dispatch/journey/router.rb @@ -81,7 +81,6 @@ module ActionDispatch end private - def partitioned_routes routes.partition { |r| r.path.anchored && r.ast.grep(Nodes::Symbol).all? { |n| n.default_regexp? } diff --git a/actionpack/lib/action_dispatch/journey/routes.rb b/actionpack/lib/action_dispatch/journey/routes.rb index 3ba8361d77..3f055db66d 100644 --- a/actionpack/lib/action_dispatch/journey/routes.rb +++ b/actionpack/lib/action_dispatch/journey/routes.rb @@ -71,7 +71,6 @@ module ActionDispatch end private - def clear_cache! @ast = nil @simulator = nil diff --git a/actionpack/lib/action_dispatch/journey/scanner.rb b/actionpack/lib/action_dispatch/journey/scanner.rb index 2a075862e9..eb6fd17aa7 100644 --- a/actionpack/lib/action_dispatch/journey/scanner.rb +++ b/actionpack/lib/action_dispatch/journey/scanner.rb @@ -33,7 +33,6 @@ module ActionDispatch end private - # takes advantage of String @- deduping capabilities in Ruby 2.5 upwards # see: https://bugs.ruby-lang.org/issues/13077 def dedup_scan(regex) diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb index d2619cbf3a..ff26c9a3b0 100644 --- a/actionpack/lib/action_dispatch/journey/visitors.rb +++ b/actionpack/lib/action_dispatch/journey/visitors.rb @@ -59,7 +59,6 @@ module ActionDispatch end private - def visit(node) send(DISPATCH_CACHE[node.type], node) end @@ -168,7 +167,6 @@ module ActionDispatch class String < FunctionalVisitor # :nodoc: private - def binary(node, seed) visit(node.right, visit(node.left, seed)) end @@ -214,7 +212,6 @@ module ActionDispatch end private - def binary(node, seed) seed.last.concat node.children.map { |c| "#{node.object_id} -> #{c.object_id};" diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index b69bcab05c..9b5a5cf2b0 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -252,7 +252,6 @@ module ActionDispatch end private - def upgrade_legacy_hmac_aes_cbc_cookies? request.secret_key_base.present? && request.encrypted_signed_cookie_salt.present? && @@ -287,8 +286,8 @@ module ActionDispatch DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/ def self.build(req, cookies) - new(req).tap do |hash| - hash.update(cookies) + new(req).tap do |jar| + jar.update(cookies) end end @@ -347,28 +346,6 @@ module ActionDispatch @cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; " end - 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" - # If there is a provided tld length then we use it otherwise default domain regexp. - domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP - - # If host is not ip and matches domain regexp. - # (ip confirms to domain regexp so we explicitly check for ip) - options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp) - ".#{$&}" - end - elsif options[:domain].is_a? Array - # If host matches one of the supplied domains without a dot in front of it. - options[:domain] = options[:domain].find { |domain| request.host.include? domain.sub(/^\./, "") } - end - end - # Sets the cookie named +name+. The second argument may be the cookie's # value or a hash of options as documented above. def []=(name, options) @@ -428,7 +405,6 @@ module ActionDispatch mattr_accessor :always_write_cookie, default: false private - def escape(string) ::Rack::Utils.escape(string) end @@ -449,6 +425,28 @@ module ActionDispatch def write_cookie?(cookie) request.ssl? || !cookie[:secure] || always_write_cookie end + + def handle_options(options) + if options[:expires].respond_to?(:from_now) + options[:expires] = options[:expires].from_now + end + + options[:path] ||= "/" + + if options[:domain] == :all || options[:domain] == "all" + # If there is a provided tld length then we use it otherwise default domain regexp. + domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP + + # If host is not ip and matches domain regexp. + # (ip confirms to domain regexp so we explicitly check for ip) + options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp) + ".#{$&}" + end + elsif options[:domain].is_a? Array + # If host matches one of the supplied domains without a dot in front of it. + options[:domain] = options[:domain].find { |domain| request.host.include? domain.sub(/^\./, "") } + end + end end class AbstractCookieJar # :nodoc: diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index 0b15c94122..e546d1c11f 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -4,8 +4,6 @@ require "action_dispatch/http/request" require "action_dispatch/middleware/exception_wrapper" require "action_dispatch/routing/inspector" -require "active_support/actionable_error" - require "action_view" require "action_view/base" @@ -44,7 +42,6 @@ module ActionDispatch end private - def invoke_interceptors(request, exception) backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner") wrapper = ExceptionWrapper.new(backtrace_cleaner, exception) @@ -140,9 +137,7 @@ module ActionDispatch return unless logger exception = wrapper.exception - - trace = wrapper.application_trace - trace = wrapper.framework_trace if trace.empty? + trace = wrapper.exception_trace ActiveSupport::Deprecation.silence do message = [] diff --git a/actionpack/lib/action_dispatch/middleware/debug_view.rb b/actionpack/lib/action_dispatch/middleware/debug_view.rb index a03650254e..148662a48b 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_view.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_view.rb @@ -56,5 +56,11 @@ module ActionDispatch def protect_against_forgery? false end + + def params_valid? + @request.parameters + rescue ActionController::BadRequest + false + end end end diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb index 0cc56f5013..e4a2a51c57 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -36,18 +36,23 @@ module ActionDispatch "ActionView::Template::Error" ] + cattr_accessor :silent_exceptions, default: [ + "ActionController::RoutingError" + ] + attr_reader :backtrace_cleaner, :exception, :wrapped_causes, :line_number, :file def initialize(backtrace_cleaner, exception) @backtrace_cleaner = backtrace_cleaner @exception = exception + @exception_class_name = @exception.class.name @wrapped_causes = wrapped_causes_for(exception, backtrace_cleaner) expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError) end def unwrapped_exception - if wrapper_exceptions.include?(exception.class.to_s) + if wrapper_exceptions.include?(@exception_class_name) exception.cause else exception @@ -55,13 +60,19 @@ module ActionDispatch end def rescue_template - @@rescue_templates[@exception.class.name] + @@rescue_templates[@exception_class_name] end def status_code self.class.status_code_for_exception(unwrapped_exception.class.name) end + def exception_trace + trace = application_trace + trace = framework_trace if trace.empty? && !silent_exceptions.include?(@exception_class_name) + trace + end + def application_trace clean_backtrace(:silent) end @@ -130,7 +141,6 @@ module ActionDispatch end private - def backtrace Array(@exception.backtrace) end diff --git a/actionpack/lib/action_dispatch/middleware/host_authorization.rb b/actionpack/lib/action_dispatch/middleware/host_authorization.rb index b7dff1df41..de7739b9b6 100644 --- a/actionpack/lib/action_dispatch/middleware/host_authorization.rb +++ b/actionpack/lib/action_dispatch/middleware/host_authorization.rb @@ -30,7 +30,6 @@ module ActionDispatch end private - def sanitize_hosts(hosts) Array(hosts).map do |host| case host @@ -87,7 +86,6 @@ module ActionDispatch end private - def authorized?(request) origin_host = request.get_header("HTTP_HOST").to_s.sub(/:\d+\z/, "") forwarded_host = request.x_forwarded_host.to_s.split(/,\s?/).last.to_s.sub(/:\d+\z/, "") diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb index a88ad40f21..3a2a1d7334 100644 --- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb @@ -32,7 +32,6 @@ module ActionDispatch end private - def render(status, content_type, body) format = "to_#{content_type.to_sym}" if content_type if format && body.respond_to?(format) diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index a5667573f4..c5d4a0bd31 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -156,7 +156,6 @@ module ActionDispatch end private - def ips_from(header) # :doc: return [] unless header # Split the comma-separated list into an array of strings. diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 5b0be96223..3815971acb 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -30,7 +30,6 @@ module ActionDispatch end private - def initialize_sid # :doc: @default_options.delete(:sidbits) @default_options.delete(:secure_random) @@ -83,7 +82,6 @@ module ActionDispatch include SessionObject private - def set_cookie(request, session_id, cookie) request.cookie_jar[key] = cookie end diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 7c43c781c7..892d88803e 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -67,7 +67,6 @@ module ActionDispatch end private - def extract_session_id(req) stale_session_check! do unpacked_cookie_data(req)["session_id"] diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 767143a368..a35c0da3d9 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -40,7 +40,6 @@ module ActionDispatch end private - def render_exception(request, exception) backtrace_cleaner = request.get_header "action_dispatch.backtrace_cleaner" wrapper = ExceptionWrapper.new(backtrace_cleaner, exception) diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb index f0c869fba0..775110d95e 100644 --- a/actionpack/lib/action_dispatch/middleware/stack.rb +++ b/actionpack/lib/action_dispatch/middleware/stack.rb @@ -34,7 +34,11 @@ module ActionDispatch end def build(app) - InstrumentationProxy.new(klass.new(app, *args, &block), inspect) + klass.new(app, *args, &block) + end + + def build_instrumented(app) + InstrumentationProxy.new(build(app), inspect) end end @@ -119,11 +123,17 @@ module ActionDispatch end def build(app = nil, &block) - middlewares.freeze.reverse.inject(app || block) { |a, e| e.build(a) } + instrumenting = ActiveSupport::Notifications.notifier.listening?(InstrumentationProxy::EVENT_NAME) + middlewares.freeze.reverse.inject(app || block) do |a, e| + if instrumenting + e.build_instrumented(a) + else + e.build(a) + end + end end private - def assert_index(index, where) i = index.is_a?(Integer) ? index : middlewares.index { |m| m.klass == index } raise "No such middleware to insert #{where}: #{index.inspect}" unless i diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb index 49b1e83551..04271d8e8a 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb @@ -6,7 +6,9 @@ <% end %> <h2 style="margin-top: 30px">Request</h2> -<p><b>Parameters</b>:</p> <pre><%= debug_params(@request.filtered_parameters) %></pre> +<% if params_valid? %> + <p><b>Parameters</b>:</p> <pre><%= debug_params(@request.filtered_parameters) %></pre> +<% end %> <div class="details"> <div class="summary"><a href="#" onclick="return toggleSessionDump()">Toggle session dump</a></div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb index 396768ecee..ca42a6fa8b 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb @@ -1,5 +1,5 @@ <% - clean_params = @request.filtered_parameters.clone + clean_params = params_valid? ? @request.filtered_parameters.clone : {} clean_params.delete("action") clean_params.delete("controller") diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb index 999e84e4d6..57cdcf9aaf 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb @@ -1,7 +1,7 @@ <header> <h1> <%= @exception.class.to_s %> - <% if @request.parameters['controller'] %> + <% if params_valid? && @request.parameters['controller'] %> in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %> <% end %> </h1> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb index 603de54b8b..d3265563a8 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb @@ -1,5 +1,5 @@ <%= @exception.class.to_s %><% - if @request.parameters['controller'] + if params_valid? && @request.parameters['controller'] %> in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %> <% end %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb index 0f78e23b7f..f535822ccf 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb @@ -2,11 +2,14 @@ <html lang="en"> <head> <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Action Controller: Exception caught</title> <style> body { background-color: #FAFAFA; color: #333; + color-scheme: light dark; + supported-color-schemes: light dark; margin: 0px; } @@ -35,6 +38,7 @@ } h1 { + overflow-wrap: break-word; margin: 0.2em 0; line-height: 1.1em; font-size: 2em; @@ -50,7 +54,7 @@ border-radius: 4px; margin: 1em 0px; display: block; - width: 978px; + max-width: 978px; } .summary { @@ -78,7 +82,7 @@ .source { border: 1px solid #D9D9D9; background: #ECECEC; - width: 978px; + max-width: 978px; } .source pre { @@ -114,11 +118,13 @@ } .line.active { - background-color: #FFCCCC; + background-color: #FCC; } .button_to { display: inline-block; + margin-top: 0.5em; + margin-bottom: 0.5em; } .hidden { @@ -127,10 +133,67 @@ a { color: #980905; } a:visited { color: #666; } - a.trace-frames { color: #666; } + a.trace-frames { + color: #666; + overflow-wrap: break-word; + } a:hover { color: #C52F24; } a.trace-frames.selected { color: #C52F24 } + @media (prefers-color-scheme: dark) { + body { + background-color: #222; + color: #ECECEC; + } + + .details { + border-color: #666; + } + + .summary { + border-color: #666; + } + + .source { + border-color: #555; + background-color: #333; + } + + .source .data { + background: #444; + } + + .source .data .line_numbers { + background: #333; + border-color: #222; + } + + .line:hover { + background: #666; + } + + .line.active { + background-color: #977; + } + + input[type="submit"] { + color: #EEE; + background-color: #535353; + border: none; + border-radius: 3px; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0,0,0,0.15), 0 1px 1px rgba(0,0,0,0.15); + padding: 2px 7px; + } + input[type="submit"]:active { + background-color: #777; + } + + a { color: #C52F24; } + a.trace-frames { color: #999; } + a:hover { color: #E9382B; } + a.trace-frames.selected { color: #E9382B; } + } + <%= yield :style %> </style> 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 0242b706b2..2fb4650398 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb @@ -49,6 +49,17 @@ width: 80%; font-size: inherit; } + + @media (prefers-color-scheme: dark) { + #route_table tbody tr:nth-child(odd) { + background: #333; + } + + #route_table tbody.exact_matches, + #route_table tbody.fuzzy_matches { + color: #333; + } + } <% end %> <table id='route_table' class='route_table'> diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index efc3988bc3..2e09aed41d 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -23,6 +23,7 @@ module ActionDispatch config.action_dispatch.use_authenticated_cookie_encryption = false config.action_dispatch.use_cookies_with_metadata = false config.action_dispatch.perform_deep_munge = true + config.action_dispatch.return_only_media_type_on_content_type = true config.action_dispatch.default_headers = { "X-Frame-Options" => "SAMEORIGIN", @@ -43,6 +44,7 @@ module ActionDispatch ActionDispatch::Request::Utils.perform_deep_munge = app.config.action_dispatch.perform_deep_munge ActionDispatch::Response.default_charset = app.config.action_dispatch.default_charset || app.config.encoding ActionDispatch::Response.default_headers = app.config.action_dispatch.default_headers + ActionDispatch::Response.return_only_media_type_on_content_type = app.config.action_dispatch.return_only_media_type_on_content_type ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses) ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates) diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb index bc5e0670e0..8faedf15b9 100644 --- a/actionpack/lib/action_dispatch/request/session.rb +++ b/actionpack/lib/action_dispatch/request/session.rb @@ -216,7 +216,6 @@ module ActionDispatch end private - def load_for_read! load! if !loaded? && exists? end diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index 413e524ef6..6e40a18009 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -177,7 +177,6 @@ module ActionDispatch end private - def draw_section(routes) header_lengths = ["Prefix", "Verb", "URI Pattern"].map(&:length) name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max) @@ -210,7 +209,6 @@ module ActionDispatch end private - def draw_expanded_section(routes) routes.map.each_with_index do |r, i| <<~MESSAGE.chomp diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index f29f66990d..d1100089b1 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -70,17 +70,21 @@ module ActionDispatch ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z} - attr_reader :requirements, :defaults - attr_reader :to, :default_controller, :default_action - attr_reader :required_defaults, :ast + attr_reader :requirements, :defaults, :to, :default_controller, + :default_action, :required_defaults, :ast, :scope_options def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options) - options = scope[:options].merge(options) if scope[:options] - - defaults = (scope[:defaults] || {}).dup - scope_constraints = scope[:constraints] || {} + scope_params = { + blocks: scope[:blocks] || [], + constraints: scope[:constraints] || {}, + defaults: (scope[:defaults] || {}).dup, + module: scope[:module], + options: scope[:options] || {} + } - new set, ast, defaults, controller, default_action, scope[:module], to, formatted, scope_constraints, scope[:blocks] || [], via, options_constraints, anchor, options + new set: set, ast: ast, controller: controller, default_action: default_action, + to: to, formatted: formatted, via: via, options_constraints: options_constraints, + anchor: anchor, scope_params: scope_params, options: scope_params[:options].merge(options) end def self.check_via(via) @@ -111,10 +115,9 @@ module ActionDispatch format != false && path !~ OPTIONAL_FORMAT_REGEX end - def initialize(set, ast, defaults, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, via, options_constraints, anchor, options) - @defaults = defaults - @set = set - + def initialize(set:, ast:, controller:, default_action:, to:, formatted:, via:, options_constraints:, anchor:, scope_params:, options:) + @defaults = scope_params[:defaults] + @set = set @to = intern(to) @default_controller = intern(controller) @default_action = intern(default_action) @@ -122,22 +125,23 @@ module ActionDispatch @anchor = anchor @via = via @internal = options.delete(:internal) + @scope_options = scope_params[:options] path_params = ast.find_all(&:symbol?).map(&:to_sym) options = add_wildcard_options(options, formatted, ast) - options = normalize_options!(options, path_params, modyoule) + options = normalize_options!(options, path_params, scope_params[:module]) split_options = constraints(options, path_params) - constraints = scope_constraints.merge Hash[split_options[:constraints] || []] + constraints = scope_params[:constraints].merge Hash[split_options[:constraints] || []] if options_constraints.is_a?(Hash) @defaults = Hash[options_constraints.find_all { |key, default| URL_OPTIONS.include?(key) && (String === default || Integer === default) }].merge @defaults - @blocks = blocks + @blocks = scope_params[:blocks] constraints.merge! options_constraints else @blocks = blocks(options_constraints) @@ -160,8 +164,10 @@ module ActionDispatch end def make_route(name, precedence) - Journey::Route.new(name, application, path, conditions, required_defaults, - defaults, request_method, precedence, @internal) + Journey::Route.new(name: name, app: application, path: path, constraints: conditions, + required_defaults: required_defaults, defaults: defaults, + request_method_match: request_method, precedence: precedence, + scope_options: scope_options, internal: @internal) end def application @@ -1667,7 +1673,6 @@ module ActionDispatch end private - def parent_resource @scope[:scope_level_resource] end diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index 4de5f9e2f7..e3322e99ab 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -156,7 +156,6 @@ module ActionDispatch end private - def polymorphic_url_for_action(action, record_or_hash, options) polymorphic_url(record_or_hash, options.merge(action: action)) end @@ -323,7 +322,6 @@ module ActionDispatch end private - def polymorphic_mapping(target, record) if record.respond_to?(:to_model) target._routes.polymorphic_mappings[record.to_model.model_name.name] diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index bbb5762b3c..5b35b68c44 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -40,7 +40,6 @@ module ActionDispatch end private - def controller(req) req.controller_class rescue NameError => e @@ -59,7 +58,6 @@ module ActionDispatch end private - def controller(_); @controller_class; end end @@ -215,7 +213,6 @@ module ActionDispatch end private - def optimized_helper(args) params = parameterize_args(args) do raise_generation_error(args) diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index fcb8ae296b..e02a6541c1 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -215,13 +215,11 @@ module ActionDispatch end protected - def optimize_routes_generation? _routes.optimize_routes_generation? && default_url_options.empty? end private - def _with_routes(routes) # :doc: old_routes, @_routes = @_routes, routes yield diff --git a/actionpack/lib/action_dispatch/system_test_case.rb b/actionpack/lib/action_dispatch/system_test_case.rb index 066daa4a12..aae96975c7 100644 --- a/actionpack/lib/action_dispatch/system_test_case.rb +++ b/actionpack/lib/action_dispatch/system_test_case.rb @@ -1,16 +1,16 @@ # frozen_string_literal: true -gem "capybara", ">= 2.15" +gem "capybara", ">= 3.26" require "capybara/dsl" require "capybara/minitest" +require "selenium/webdriver" require "action_controller" require "action_dispatch/system_testing/driver" require "action_dispatch/system_testing/browser" 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 @@ -110,12 +110,11 @@ module ActionDispatch # Because <tt>ActionDispatch::SystemTestCase</tt> is a shim between Capybara # and Rails, any driver that is supported by Capybara is supported by system # tests as long as you include the required gems and files. - class SystemTestCase < IntegrationTest + class SystemTestCase < ActiveSupport::TestCase include Capybara::DSL include Capybara::Minitest::Assertions include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper - include SystemTesting::TestHelpers::UndefMethods def initialize(*) # :nodoc: super @@ -160,8 +159,33 @@ module ActionDispatch driven_by :selenium - ActiveSupport.run_load_hooks(:action_dispatch_system_test_case, self) - end + private + def url_helpers + @url_helpers ||= + if ActionDispatch.test_app + Class.new do + include ActionDispatch.test_app.routes.url_helpers + + def url_options + default_url_options.reverse_merge(host: Capybara.app_host || Capybara.current_session.server_url) + end + end.new + end + end - SystemTestCase.start_application + def method_missing(name, *args, &block) + if url_helpers.respond_to?(name) + url_helpers.public_send(name, *args, &block) + else + super + end + end + + def respond_to_missing?(name, include_private = false) + url_helpers.respond_to?(name) + end + end end + +ActiveSupport.run_load_hooks :action_dispatch_system_test_case, ActionDispatch::SystemTestCase +ActionDispatch::SystemTestCase.start_application diff --git a/actionpack/lib/action_dispatch/system_testing/browser.rb b/actionpack/lib/action_dispatch/system_testing/browser.rb index c34907b6cb..e861e52f09 100644 --- a/actionpack/lib/action_dispatch/system_testing/browser.rb +++ b/actionpack/lib/action_dispatch/system_testing/browser.rb @@ -39,6 +39,29 @@ module ActionDispatch end end + # driver_path can be configured as a proc. The webdrivers gem uses this + # proc to update web drivers. Running this proc early allows us to only + # update the webdriver once and avoid race conditions when using + # parallel tests. + def preload + case type + when :chrome + if ::Selenium::WebDriver::Service.respond_to? :driver_path= + ::Selenium::WebDriver::Chrome::Service.driver_path.try(:call) + else + # Selenium <= v3.141.0 + ::Selenium::WebDriver::Chrome.driver_path + end + when :firefox + if ::Selenium::WebDriver::Service.respond_to? :driver_path= + ::Selenium::WebDriver::Firefox::Service.driver_path.try(:call) + else + # Selenium <= v3.141.0 + ::Selenium::WebDriver::Firefox.driver_path + end + end + end + private def headless_chrome_browser_options capabilities.args << "--headless" diff --git a/actionpack/lib/action_dispatch/system_testing/driver.rb b/actionpack/lib/action_dispatch/system_testing/driver.rb index 25a09dd918..15943a55ea 100644 --- a/actionpack/lib/action_dispatch/system_testing/driver.rb +++ b/actionpack/lib/action_dispatch/system_testing/driver.rb @@ -9,6 +9,8 @@ module ActionDispatch @screen_size = options[:screen_size] @options = options[:options] @capabilities = capabilities + + @browser.preload end def use 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 7080dbe022..30dc21ebb9 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 @@ -4,16 +4,12 @@ module ActionDispatch module SystemTesting module TestHelpers module SetupAndTeardown # :nodoc: - DEFAULT_HOST = "http://127.0.0.1" - def host!(host) - super - Capybara.app_host = host - end + ActiveSupport::Deprecation.warn \ + "ActionDispatch::SystemTestCase#host! is deprecated with no replacement. " \ + "Set Capybara.app_host directly or rely on Capybara's default host." - def before_setup - host! DEFAULT_HOST - super + Capybara.app_host = host end def before_teardown 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 deleted file mode 100644 index d64be3b3d9..0000000000 --- a/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +++ /dev/null @@ -1,26 +0,0 @@ -# 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 dc019db6ac..79af372cc1 100644 --- a/actionpack/lib/action_dispatch/testing/assertion_response.rb +++ b/actionpack/lib/action_dispatch/testing/assertion_response.rb @@ -35,7 +35,6 @@ module ActionDispatch end private - def code_from_name(name) GENERIC_RESPONSE_CODES[name] || Rack::Utils::SYMBOL_TO_STATUS_CODE[name] end diff --git a/actionpack/lib/action_dispatch/testing/assertions.rb b/actionpack/lib/action_dispatch/testing/assertions.rb index 08c2969685..dcaf914ac9 100644 --- a/actionpack/lib/action_dispatch/testing/assertions.rb +++ b/actionpack/lib/action_dispatch/testing/assertions.rb @@ -14,7 +14,7 @@ module ActionDispatch include Rails::Dom::Testing::Assertions def html_document - @html_document ||= if @response.content_type.to_s.end_with?("xml") + @html_document ||= if @response.media_type.to_s.end_with?("xml") Nokogiri::XML::Document.parse(@response.body) else Nokogiri::HTML::Document.parse(@response.body) diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index bb8b43ad4d..9e7b4301a8 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -3,7 +3,6 @@ require "stringio" require "uri" require "active_support/core_ext/kernel/singleton_class" -require "active_support/core_ext/object/try" require "rack/test" require "minitest" @@ -50,11 +49,16 @@ module ActionDispatch # Follow a single redirect response. If the last response was not a # redirect, an exception will be raised. Otherwise, the redirect is - # performed on the location header. Any arguments are passed to the - # underlying call to `get`. + # performed on the location header. If the redirection is a 307 redirect, + # the same HTTP verb will be used when redirecting, otherwise a GET request + # will be performed. Any arguments are passed to the + # underlying request. def follow_redirect!(**args) raise "not a redirect! #{status} #{status_message}" unless redirect? - get(response.location, **args) + + method = response.status == 307 ? request.method.downcase : :get + public_send(method, response.location, **args) + status end end diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb index 6f7c86fdcf..f1dd4099c5 100644 --- a/actionpack/lib/action_dispatch/testing/test_response.rb +++ b/actionpack/lib/action_dispatch/testing/test_response.rb @@ -19,7 +19,7 @@ module ActionDispatch end def response_parser - @response_parser ||= RequestEncoder.parser(content_type) + @response_parser ||= RequestEncoder.parser(media_type) end end end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 32a0b8efeb..1decfcee95 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -335,7 +335,6 @@ module RoutingTestHelpers end private - def make_request(env) Request.new super, url_helpers, @block, strict end diff --git a/actionpack/test/controller/api/conditional_get_test.rb b/actionpack/test/controller/api/conditional_get_test.rb index e366ce9532..f1cd9e46f9 100644 --- a/actionpack/test/controller/api/conditional_get_test.rb +++ b/actionpack/test/controller/api/conditional_get_test.rb @@ -18,7 +18,6 @@ class ConditionalGetApiController < ActionController::API end private - def handle_last_modified_and_etags fresh_when(last_modified: Time.now.utc.beginning_of_day, etag: [ :foo, 123 ]) end diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb index 636b025f2c..fcf767b706 100644 --- a/actionpack/test/controller/content_type_test.rb +++ b/actionpack/test/controller/content_type_test.rb @@ -66,73 +66,72 @@ class ContentTypeTest < ActionController::TestCase def test_render_defaults get :render_defaults assert_equal "utf-8", @response.charset - assert_equal Mime[:text], @response.content_type + assert_equal Mime[:text], @response.media_type end def test_render_changed_charset_default with_default_charset "utf-16" do get :render_defaults assert_equal "utf-16", @response.charset - assert_equal Mime[:text], @response.content_type + assert_equal Mime[:text], @response.media_type end end # :ported: def test_content_type_from_body get :render_content_type_from_body - assert_equal Mime[:rss], @response.content_type + assert_equal Mime[:rss], @response.media_type assert_equal "utf-8", @response.charset end # :ported: def test_content_type_from_render get :render_content_type_from_render - assert_equal Mime[:rss], @response.content_type + assert_equal Mime[:rss], @response.media_type assert_equal "utf-8", @response.charset end # :ported: def test_charset_from_body get :render_charset_from_body - assert_equal Mime[:text], @response.content_type + assert_equal Mime[:text], @response.media_type assert_equal "utf-16", @response.charset end # :ported: def test_nil_charset_from_body get :render_nil_charset_from_body - assert_equal Mime[:text], @response.content_type + assert_equal Mime[:text], @response.media_type assert_equal "utf-8", @response.charset, @response.headers.inspect end def test_nil_default_for_erb with_default_charset nil do get :render_default_for_erb - assert_equal Mime[:html], @response.content_type + assert_equal Mime[:html], @response.media_type assert_nil @response.charset, @response.headers.inspect end end def test_default_for_erb get :render_default_for_erb - assert_equal Mime[:html], @response.content_type + assert_equal Mime[:html], @response.media_type assert_equal "utf-8", @response.charset end def test_default_for_builder get :render_default_for_builder - assert_equal Mime[:xml], @response.content_type + assert_equal Mime[:xml], @response.media_type assert_equal "utf-8", @response.charset end def test_change_for_builder get :render_change_for_builder - assert_equal Mime[:html], @response.content_type + assert_equal Mime[:html], @response.media_type assert_equal "utf-8", @response.charset end private - def with_default_charset(charset) old_default_charset = ActionDispatch::Response.default_charset ActionDispatch::Response.default_charset = charset @@ -148,22 +147,22 @@ class AcceptBasedContentTypeTest < ActionController::TestCase def test_render_default_content_types_for_respond_to @request.accept = Mime[:html].to_s get :render_default_content_types_for_respond_to - assert_equal Mime[:html], @response.content_type + assert_equal Mime[:html], @response.media_type @request.accept = Mime[:js].to_s get :render_default_content_types_for_respond_to - assert_equal Mime[:js], @response.content_type + assert_equal Mime[:js], @response.media_type end def test_render_default_content_types_for_respond_to_with_template @request.accept = Mime[:xml].to_s get :render_default_content_types_for_respond_to - assert_equal Mime[:xml], @response.content_type + assert_equal Mime[:xml], @response.media_type end def test_render_default_content_types_for_respond_to_with_overwrite @request.accept = Mime[:rss].to_s get :render_default_content_types_for_respond_to - assert_equal Mime[:xml], @response.content_type + assert_equal Mime[:xml], @response.media_type end end diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index fcee812ee4..40443a9397 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -310,7 +310,6 @@ class FilterTest < ActionController::TestCase after_action :conditional_in_parent_after, only: [:show, :another_action] private - def conditional_in_parent_before @ran_filter ||= [] @ran_filter << "conditional_in_parent_before" @@ -508,7 +507,6 @@ class FilterTest < ActionController::TestCase end private - def filter_one @filters ||= [] @filters << "filter_one" @@ -532,7 +530,6 @@ class FilterTest < ActionController::TestCase before_action :find_except, except: :edit private - def find_only @only = "Only" end diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index bf95c633e5..1f44c7a68e 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -361,7 +361,6 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest end private - # Overwrite get to send SessionSecret in env hash def get(path, *args) args[0] ||= {} diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb index de8072a994..93a2ba1071 100644 --- a/actionpack/test/controller/helper_test.rb +++ b/actionpack/test/controller/helper_test.rb @@ -150,8 +150,8 @@ class HelperTest < ActiveSupport::TestCase end def test_default_helpers_only - assert_equal [JustMeHelper], JustMeController._helpers.ancestors.reject(&:anonymous?) - assert_equal [MeTooHelper, JustMeHelper], MeTooController._helpers.ancestors.reject(&:anonymous?) + assert_equal %w[JustMeHelper], JustMeController._helpers.ancestors.reject(&:anonymous?).map(&:to_s) + assert_equal %w[MeTooController::HelperMethods MeTooHelper JustMeHelper], MeTooController._helpers.ancestors.reject(&:anonymous?).map(&:to_s) end def test_base_helper_methods_after_clear_helpers diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb index 1544a627ee..73524d0443 100644 --- a/actionpack/test/controller/http_basic_authentication_test.rb +++ b/actionpack/test/controller/http_basic_authentication_test.rb @@ -32,7 +32,6 @@ class HttpBasicAuthenticationTest < ActionController::TestCase end private - def authenticate authenticate_or_request_with_http_basic do |username, password| username == "lifo" && password == "world" @@ -172,7 +171,6 @@ class HttpBasicAuthenticationTest < ActionController::TestCase end private - def encode_credentials(username, password) "Basic #{::Base64.encode64("#{username}:#{password}")}" end diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb index dd4ff85d11..a0f543f607 100644 --- a/actionpack/test/controller/http_digest_authentication_test.rb +++ b/actionpack/test/controller/http_digest_authentication_test.rb @@ -20,7 +20,6 @@ class HttpDigestAuthenticationTest < ActionController::TestCase end private - def authenticate authenticate_or_request_with_http_digest("SuperSecret") do |username| # Returns the password @@ -254,7 +253,6 @@ class HttpDigestAuthenticationTest < ActionController::TestCase end private - def encode_credentials(options) options.reverse_merge!(nc: "00000001", cnonce: "0a4f113b", password_is_ha1: false) password = options.delete(:password) diff --git a/actionpack/test/controller/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb index 103123f98c..57b78154bc 100644 --- a/actionpack/test/controller/http_token_authentication_test.rb +++ b/actionpack/test/controller/http_token_authentication_test.rb @@ -21,7 +21,6 @@ class HttpTokenAuthenticationTest < ActionController::TestCase end private - def authenticate authenticate_or_request_with_http_token do |token, _| token == "lifo" @@ -190,7 +189,6 @@ class HttpTokenAuthenticationTest < ActionController::TestCase end private - def sample_request(token, options = { nonce: "def" }) authorization = options.inject([%{Token token="#{token}"}]) do |arr, (k, v)| arr << "#{k}=\"#{v}\"" diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index 4dddd98f9f..cb7c2467ac 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -213,6 +213,10 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest redirect_to action_url("get") end + def redirect_307 + redirect_to action_url("post"), status: 307 + end + def remove_header response.headers.delete params[:header] head :ok, "c" => "3" @@ -337,6 +341,15 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest end end + def test_307_redirect_uses_the_same_http_verb + with_test_route_set do + post "/redirect_307" + assert_equal 307, status + follow_redirect! + assert_equal "POST", request.method + end + end + def test_redirect_reset_html_document with_test_route_set do get "/redirect" @@ -522,12 +535,38 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest with_test_route_set do get "/get", headers: { "Accept" => "application/json" }, xhr: true assert_equal "application/json", request.accept - assert_equal "application/json", response.content_type + assert_equal "application/json", response.media_type get "/get", headers: { "HTTP_ACCEPT" => "application/json" }, xhr: true assert_equal "application/json", request.accept - assert_equal "application/json", response.content_type + assert_equal "application/json", response.media_type + end + end + + def test_setting_vary_header_when_request_is_xhr_with_accept_header + with_test_route_set do + get "/get", headers: { "Accept" => "application/json" }, xhr: true + assert_equal "Accept", response.headers["Vary"] + end + end + + def test_not_setting_vary_header_when_format_is_provided + with_test_route_set do + get "/get", params: { format: "json" } + assert_nil response.headers["Vary"] + end + end + + def test_not_setting_vary_header_when_ignore_accept_header_is_set + original_ignore_accept_header = ActionDispatch::Request.ignore_accept_header + ActionDispatch::Request.ignore_accept_header = true + + with_test_route_set do + get "/get", headers: { "Accept" => "application/json" }, xhr: true + assert_nil response.headers["Vary"] end + ensure + ActionDispatch::Request.ignore_accept_header = original_ignore_accept_header end private @@ -986,7 +1025,7 @@ class IntegrationRequestEncodersTest < ActionDispatch::IntegrationTest def test_encoding_as_json post_to_foos as: :json do assert_response :success - assert_equal "application/json", request.content_type + assert_equal "application/json", request.media_type assert_equal "application/json", request.accepts.first.to_s assert_equal :json, request.format.ref assert_equal({ "foo" => "fighters" }, request.request_parameters) @@ -1025,7 +1064,7 @@ class IntegrationRequestEncodersTest < ActionDispatch::IntegrationTest post_to_foos as: :wibble do assert_response :success assert_equal "/foos_wibble", request.path - assert_equal "text/wibble", request.content_type + assert_equal "text/wibble", request.media_type assert_equal "text/wibble", request.accepts.first.to_s assert_equal :wibble, request.format.ref assert_equal Hash.new, request.request_parameters # Unregistered MIME Type can't be parsed. diff --git a/actionpack/test/controller/localized_templates_test.rb b/actionpack/test/controller/localized_templates_test.rb index d84a76fb46..5c5cef66d5 100644 --- a/actionpack/test/controller/localized_templates_test.rb +++ b/actionpack/test/controller/localized_templates_test.rb @@ -43,6 +43,6 @@ class LocalizedTemplatesTest < ActionController::TestCase I18n.locale = :it get :hello_world assert_equal "Ciao Mondo", @response.body - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type end end diff --git a/actionpack/test/controller/metal/renderers_test.rb b/actionpack/test/controller/metal/renderers_test.rb index 5f0d125128..f6558f1354 100644 --- a/actionpack/test/controller/metal/renderers_test.rb +++ b/actionpack/test/controller/metal/renderers_test.rb @@ -38,13 +38,13 @@ class RenderersMetalTest < ActionController::TestCase get :one assert_response :success assert_equal({ a: "b" }.to_json, @response.body) - assert_equal "application/json", @response.content_type + assert_equal "application/json", @response.media_type end def test_render_xml get :two assert_response :success assert_equal(" ", @response.body) - assert_equal "text/plain", @response.content_type + assert_equal "text/plain", @response.media_type end end diff --git a/actionpack/test/controller/mime/accept_format_test.rb b/actionpack/test/controller/mime/accept_format_test.rb index eed671d593..fb038ae158 100644 --- a/actionpack/test/controller/mime/accept_format_test.rb +++ b/actionpack/test/controller/mime/accept_format_test.rb @@ -43,7 +43,6 @@ class PostController < AbstractPostController end private - def with_iphone request.format = "iphone" if request.env["HTTP_ACCEPT"] == "text/iphone" yield diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb index 2f8f191828..fc16c639fb 100644 --- a/actionpack/test/controller/mime/respond_to_test.rb +++ b/actionpack/test/controller/mime/respond_to_test.rb @@ -423,12 +423,12 @@ class RespondToControllerTest < ActionController::TestCase def test_using_defaults @request.accept = "*/*" get :using_defaults - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "Hello world!", @response.body @request.accept = "application/xml" get :using_defaults - assert_equal "application/xml", @response.content_type + assert_equal "application/xml", @response.media_type assert_equal "<p>Hello world!</p>\n", @response.body end @@ -449,12 +449,12 @@ class RespondToControllerTest < ActionController::TestCase def test_using_defaults_with_type_list @request.accept = "*/*" get :using_defaults_with_type_list - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "Hello world!", @response.body @request.accept = "application/xml" get :using_defaults_with_type_list - assert_equal "application/xml", @response.content_type + assert_equal "application/xml", @response.media_type assert_equal "<p>Hello world!</p>\n", @response.body end @@ -468,7 +468,7 @@ class RespondToControllerTest < ActionController::TestCase def test_using_non_conflicting_nested_js_then_js @request.accept = "*/*" get :using_non_conflicting_nested_js_then_js - assert_equal "text/javascript", @response.content_type + assert_equal "text/javascript", @response.media_type assert_equal "JS", @response.body end @@ -499,12 +499,12 @@ class RespondToControllerTest < ActionController::TestCase def test_custom_types @request.accept = "application/fancy-xml" get :custom_type_handling - assert_equal "application/fancy-xml", @response.content_type + assert_equal "application/fancy-xml", @response.media_type assert_equal "Fancy XML", @response.body @request.accept = "text/html" get :custom_type_handling - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "HTML", @response.body end @@ -595,7 +595,7 @@ class RespondToControllerTest < ActionController::TestCase @request.accept = "application/json" get :json_with_callback assert_equal "/**/alert(JS)", @response.body - assert_equal "text/javascript", @response.content_type + assert_equal "text/javascript", @response.media_type end def test_xhr @@ -605,13 +605,13 @@ class RespondToControllerTest < ActionController::TestCase def test_custom_constant get :custom_constant_handling, format: "mobile" - assert_equal "text/x-mobile", @response.content_type + assert_equal "text/x-mobile", @response.media_type assert_equal "Mobile", @response.body end def test_custom_constant_handling_without_block get :custom_constant_handling_without_block, format: "mobile" - assert_equal "text/x-mobile", @response.content_type + assert_equal "text/x-mobile", @response.media_type assert_equal "Mobile", @response.body end @@ -664,7 +664,7 @@ class RespondToControllerTest < ActionController::TestCase assert_equal '<html><div id="html">Hello future from Firefox!</div></html>', @response.body get :iphone_with_html_response_type, format: "iphone" - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body end @@ -672,7 +672,7 @@ class RespondToControllerTest < ActionController::TestCase @request.accept = "text/iphone" get :iphone_with_html_response_type assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type end def test_invalid_format @@ -702,7 +702,7 @@ class RespondToControllerTest < ActionController::TestCase def test_variant_with_implicit_template_rendering get :variant_with_implicit_template_rendering, params: { v: :mobile } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "mobile", @response.body end @@ -756,137 +756,137 @@ class RespondToControllerTest < ActionController::TestCase def test_variant_with_format_and_custom_render get :variant_with_format_and_custom_render, params: { v: :phone } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "mobile", @response.body end def test_multiple_variants_for_format get :multiple_variants_for_format, params: { v: :tablet } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "tablet", @response.body end def test_no_variant_in_variant_setup get :variant_plus_none_for_format - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "none", @response.body end def test_variant_inline_syntax get :variant_inline_syntax - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "none", @response.body get :variant_inline_syntax, params: { v: :phone } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "phone", @response.body end def test_variant_inline_syntax_with_format get :variant_inline_syntax, format: :js - assert_equal "text/javascript", @response.content_type + assert_equal "text/javascript", @response.media_type assert_equal "js", @response.body end def test_variant_inline_syntax_without_block get :variant_inline_syntax_without_block, params: { v: :phone } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "phone", @response.body end def test_variant_any get :variant_any, params: { v: :phone } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "phone", @response.body get :variant_any, params: { v: :tablet } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "any", @response.body get :variant_any, params: { v: :phablet } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "any", @response.body end def test_variant_any_any get :variant_any_any - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "any", @response.body get :variant_any_any, params: { v: :phone } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "phone", @response.body get :variant_any_any, params: { v: :yolo } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "any", @response.body end def test_variant_inline_any get :variant_any, params: { v: :phone } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "phone", @response.body get :variant_inline_any, params: { v: :tablet } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "any", @response.body get :variant_inline_any, params: { v: :phablet } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "any", @response.body end def test_variant_inline_any_any get :variant_inline_any_any, params: { v: :phone } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "phone", @response.body get :variant_inline_any_any, params: { v: :yolo } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "any", @response.body end def test_variant_any_implicit_render get :variant_any_implicit_render, params: { v: :tablet } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "tablet", @response.body get :variant_any_implicit_render, params: { v: :phablet } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "phablet", @response.body end def test_variant_any_with_none get :variant_any_with_none - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "none or phone", @response.body get :variant_any_with_none, params: { v: :phone } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "none or phone", @response.body end def test_format_any_variant_any get :format_any_variant_any, format: :js, params: { v: :tablet } - assert_equal "text/javascript", @response.content_type + assert_equal "text/javascript", @response.media_type assert_equal "tablet", @response.body end def test_variant_negotiation_inline_syntax get :variant_inline_syntax_without_block, params: { v: [:tablet, :phone] } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "phone", @response.body end def test_variant_negotiation_block_syntax get :variant_plus_none_for_format, params: { v: [:tablet, :phone] } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "phone", @response.body end def test_variant_negotiation_without_block get :variant_inline_syntax_without_block, params: { v: [:tablet, :phone] } - assert_equal "text/html", @response.content_type + assert_equal "text/html", @response.media_type assert_equal "phone", @response.body end end diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb index 14dc958475..270f75eb9e 100644 --- a/actionpack/test/controller/new_base/render_template_test.rb +++ b/actionpack/test/controller/new_base/render_template_test.rb @@ -67,7 +67,6 @@ module RenderTemplate end private - def show_detailed_exceptions? request.local? end diff --git a/actionpack/test/controller/new_base/render_test.rb b/actionpack/test/controller/new_base/render_test.rb index eb29203f59..dfeb2e2b15 100644 --- a/actionpack/test/controller/new_base/render_test.rb +++ b/actionpack/test/controller/new_base/render_test.rb @@ -37,7 +37,6 @@ module Render end private - def secretz render plain: "FAIL WHALE!" end diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb index 7789e654d5..3d1538ff64 100644 --- a/actionpack/test/controller/parameters/accessors_test.rb +++ b/actionpack/test/controller/parameters/accessors_test.rb @@ -203,6 +203,25 @@ class ParametersAccessorsTest < ActiveSupport::TestCase assert_not_predicate @params.transform_keys { |k| k }, :permitted? end + test "transform_keys without a block returns an enumerator" do + assert_kind_of Enumerator, @params.transform_keys + assert_kind_of ActionController::Parameters, @params.transform_keys.each { |k| k } + end + + test "transform_keys! without a block returns an enumerator" do + assert_kind_of Enumerator, @params.transform_keys! + assert_kind_of ActionController::Parameters, @params.transform_keys!.each { |k| k } + end + + test "deep_transform_keys retains permitted status" do + @params.permit! + assert_predicate @params.deep_transform_keys { |k| k }, :permitted? + end + + test "deep_transform_keys retains unpermitted status" do + assert_not_predicate @params.deep_transform_keys { |k| k }, :permitted? + end + test "transform_values retains permitted status" do @params.permit! assert_predicate @params.transform_values { |v| v }, :permitted? @@ -219,8 +238,9 @@ class ParametersAccessorsTest < ActiveSupport::TestCase end end - test "transform_values without block yieds an enumerator" do + test "transform_values without a block returns an enumerator" do assert_kind_of Enumerator, @params.transform_values + assert_kind_of ActionController::Parameters, @params.transform_values.each { |v| v } end test "transform_values! converts hashes to parameters" do @@ -229,8 +249,9 @@ class ParametersAccessorsTest < ActiveSupport::TestCase end end - test "transform_values! without block yields an enumerator" do + test "transform_values! without a block returns an enumerator" do assert_kind_of Enumerator, @params.transform_values! + assert_kind_of ActionController::Parameters, @params.transform_values!.each { |v| v } end test "value? returns true if the given value is present in the params" do @@ -263,12 +284,14 @@ class ParametersAccessorsTest < ActiveSupport::TestCase params1 = ActionController::Parameters.new(a: 1, b: 2) params2 = ActionController::Parameters.new(a: 1, b: 2) assert(params1 == params2) + assert(params1.hash == params2.hash) end test "is equal to Parameters instance with same permitted params" do params1 = ActionController::Parameters.new(a: 1, b: 2).permit(:a) params2 = ActionController::Parameters.new(a: 1, b: 2).permit(:a) assert(params1 == params2) + assert(params1.hash == params2.hash) end test "is equal to Parameters instance with same different source params, but same permitted params" do @@ -276,6 +299,8 @@ class ParametersAccessorsTest < ActiveSupport::TestCase params2 = ActionController::Parameters.new(a: 1, c: 3).permit(:a) assert(params1 == params2) assert(params2 == params1) + assert(params1.hash == params2.hash) + assert(params2.hash == params1.hash) end test "is not equal to an unpermitted Parameters instance with same params" do @@ -283,6 +308,8 @@ class ParametersAccessorsTest < ActiveSupport::TestCase params2 = ActionController::Parameters.new(a: 1) assert(params1 != params2) assert(params2 != params1) + assert(params1.hash != params2.hash) + assert(params2.hash != params1.hash) end test "is not equal to Parameters instance with different permitted params" do @@ -290,6 +317,8 @@ class ParametersAccessorsTest < ActiveSupport::TestCase params2 = ActionController::Parameters.new(a: 1, b: 2).permit(:a) assert(params1 != params2) assert(params2 != params1) + assert(params1.hash != params2.hash) + assert(params2.hash != params1.hash) end test "equality with simple types works" do 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 fc9229ca1d..4fffcf6b10 100644 --- a/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb +++ b/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb @@ -52,7 +52,6 @@ class LogOnUnpermittedParamsTest < ActiveSupport::TestCase end private - def assert_logged(message) old_logger = ActionController::Base.logger log = StringIO.new diff --git a/actionpack/test/controller/parameters/mutators_test.rb b/actionpack/test/controller/parameters/mutators_test.rb index 312b1e5b27..ffffd2996f 100644 --- a/actionpack/test/controller/parameters/mutators_test.rb +++ b/actionpack/test/controller/parameters/mutators_test.rb @@ -118,4 +118,31 @@ class ParametersMutatorsTest < ActiveSupport::TestCase test "transform_values! retains unpermitted status" do assert_not_predicate @params.transform_values! { |v| v }, :permitted? end + + test "deep_transform_keys! retains permitted status" do + @params.permit! + assert_predicate @params.deep_transform_keys! { |k| k }, :permitted? + end + + test "deep_transform_keys! retains unpermitted status" do + assert_not_predicate @params.deep_transform_keys! { |k| k }, :permitted? + end + + test "compact_blank retains permitted status" do + @params.permit! + assert_predicate @params.compact_blank, :permitted? + end + + test "compact_blank retains unpermitted status" do + assert_not_predicate @params.compact_blank, :permitted? + end + + test "compact_blank! retains permitted status" do + @params.permit! + assert_predicate @params.compact_blank!, :permitted? + end + + test "compact_blank! retains unpermitted status" do + assert_not_predicate @params.compact_blank!, :permitted? + end end diff --git a/actionpack/test/controller/parameters/nested_parameters_permit_test.rb b/actionpack/test/controller/parameters/nested_parameters_permit_test.rb index 1403e224c0..6243b5c51b 100644 --- a/actionpack/test/controller/parameters/nested_parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/nested_parameters_permit_test.rb @@ -125,7 +125,7 @@ class NestedParametersPermitTest < ActiveSupport::TestCase assert_nil permitted[:book][:genre] end - test "fields_for-style nested params" do + test "nested params with numeric keys" do params = ActionController::Parameters.new( book: { authors_attributes: { @@ -150,7 +150,33 @@ class NestedParametersPermitTest < ActiveSupport::TestCase assert_filtered_out permitted[:book][:authors_attributes]["0"], :age_of_death end - test "fields_for-style nested params with negative numbers" do + test "nested params with non_numeric keys" do + params = ActionController::Parameters.new( + book: { + authors_attributes: { + '0': { name: "William Shakespeare", age_of_death: "52" }, + '1': { name: "Unattributed Assistant" }, + '2': "Not a hash", + 'new_record': { name: "Some name" } + } + }) + permitted = params.permit book: { authors_attributes: [ :name ] } + + assert_not_nil permitted[:book][:authors_attributes]["0"] + assert_not_nil permitted[:book][:authors_attributes]["1"] + + assert_nil permitted[:book][:authors_attributes]["2"] + assert_nil permitted[:book][:authors_attributes]["new_record"] + assert_equal "William Shakespeare", permitted[:book][:authors_attributes]["0"][:name] + assert_equal "Unattributed Assistant", permitted[:book][:authors_attributes]["1"][:name] + + assert_equal( + { "book" => { "authors_attributes" => { "0" => { "name" => "William Shakespeare" }, "1" => { "name" => "Unattributed Assistant" } } } }, + permitted.to_h + ) + end + + test "nested params with negative numeric keys" do params = ActionController::Parameters.new( book: { authors_attributes: { diff --git a/actionpack/test/controller/params_parse_test.rb b/actionpack/test/controller/params_parse_test.rb index 440ab06fd7..091b567473 100644 --- a/actionpack/test/controller/params_parse_test.rb +++ b/actionpack/test/controller/params_parse_test.rb @@ -24,7 +24,6 @@ class ParamsParseTest < ActionController::TestCase end private - def capture_log_output output = StringIO.new request.set_header "action_dispatch.logger", ActiveSupport::Logger.new(output) diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb index c4c74e8f2b..894a3824c0 100644 --- a/actionpack/test/controller/params_wrapper_test.rb +++ b/actionpack/test/controller/params_wrapper_test.rb @@ -411,7 +411,6 @@ class IrregularInflectionParamsWrapperTest < ActionController::TestCase end private - def with_dup original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)[:en] ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original.dup) diff --git a/actionpack/test/controller/render_js_test.rb b/actionpack/test/controller/render_js_test.rb index 1efc0b9de1..da8f6e8062 100644 --- a/actionpack/test/controller/render_js_test.rb +++ b/actionpack/test/controller/render_js_test.rb @@ -2,7 +2,6 @@ require "abstract_unit" require "controller/fake_models" -require "pathname" class RenderJSTest < ActionController::TestCase class TestController < ActionController::Base @@ -26,7 +25,7 @@ class RenderJSTest < ActionController::TestCase def test_render_vanilla_js get :render_vanilla_js_hello, xhr: true assert_equal "alert('hello')", @response.body - assert_equal "text/javascript", @response.content_type + assert_equal "text/javascript", @response.media_type end def test_should_render_js_partial diff --git a/actionpack/test/controller/render_json_test.rb b/actionpack/test/controller/render_json_test.rb index 82c1ba26cb..82c6aaafe5 100644 --- a/actionpack/test/controller/render_json_test.rb +++ b/actionpack/test/controller/render_json_test.rb @@ -3,7 +3,6 @@ require "abstract_unit" require "controller/fake_models" require "active_support/logger" -require "pathname" class RenderJsonTest < ActionController::TestCase class JsonRenderable @@ -80,7 +79,7 @@ class RenderJsonTest < ActionController::TestCase def test_render_json_nil get :render_json_nil assert_equal "null", @response.body - assert_equal "application/json", @response.content_type + assert_equal "application/json", @response.media_type end def test_render_json_render_to_string @@ -91,7 +90,7 @@ class RenderJsonTest < ActionController::TestCase def test_render_json get :render_json_hello_world assert_equal '{"hello":"world"}', @response.body - assert_equal "application/json", @response.content_type + assert_equal "application/json", @response.media_type end def test_render_json_with_status @@ -103,31 +102,31 @@ class RenderJsonTest < ActionController::TestCase def test_render_json_with_callback get :render_json_hello_world_with_callback, xhr: true assert_equal '/**/alert({"hello":"world"})', @response.body - assert_equal "text/javascript", @response.content_type + assert_equal "text/javascript", @response.media_type end def test_render_json_with_custom_content_type get :render_json_with_custom_content_type, xhr: true assert_equal '{"hello":"world"}', @response.body - assert_equal "text/javascript", @response.content_type + assert_equal "text/javascript", @response.media_type end def test_render_symbol_json get :render_symbol_json assert_equal '{"hello":"world"}', @response.body - assert_equal "application/json", @response.content_type + assert_equal "application/json", @response.media_type end def test_render_json_with_render_to_string get :render_json_with_render_to_string assert_equal '{"hello":"partial html"}', @response.body - assert_equal "application/json", @response.content_type + assert_equal "application/json", @response.media_type end def test_render_json_forwards_extra_options get :render_json_with_extra_options assert_equal '{"a":"b"}', @response.body - assert_equal "application/json", @response.content_type + assert_equal "application/json", @response.media_type end def test_render_json_calls_to_json_from_object diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 8bb6617eaa..dd76824413 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -4,6 +4,11 @@ require "abstract_unit" require "controller/fake_models" class TestControllerWithExtraEtags < ActionController::Base + self.view_paths = [ActionView::FixtureResolver.new( + "test/with_implicit_template.erb" => "Hello explicitly!", + "test/hello_world.erb" => "Hello world!" + )] + def self.controller_name; "test"; end def self.controller_path; "test"; end @@ -37,6 +42,11 @@ class TestControllerWithExtraEtags < ActionController::Base end class ImplicitRenderTestController < ActionController::Base + self.view_paths = [ActionView::FixtureResolver.new( + "implicit_render_test/hello_world.erb" => "Hello world!", + "implicit_render_test/empty_action_with_template.html.erb" => "<h1>Empty action rendered this implicitly.</h1>\n" + )] + def empty_action end @@ -46,6 +56,10 @@ end module Namespaced class ImplicitRenderTestController < ActionController::Base + self.view_paths = [ActionView::FixtureResolver.new( + "namespaced/implicit_render_test/hello_world.erb" => "Hello world!" + )] + def hello_world fresh_when(etag: "abc") end @@ -265,7 +279,6 @@ class TestController < ActionController::Base end private - def set_variable_for_layout @variable_for_layout = nil end @@ -294,13 +307,15 @@ end module TemplateModificationHelper private def modify_template(name) - path = File.expand_path("../fixtures/#{name}.erb", __dir__) - original = File.read(path) - File.write(path, "#{original} Modified!") + hash = @controller.view_paths.first.instance_variable_get(:@hash) + key = name + ".erb" + original = hash[key] + hash[key] = "#{original} Modified!" ActionView::LookupContext::DetailsKey.clear yield ensure - File.write(path, original) + hash[key] = original + ActionView::LookupContext::DetailsKey.clear end end diff --git a/actionpack/test/controller/render_xml_test.rb b/actionpack/test/controller/render_xml_test.rb index a72d14e4bb..28d8e281ab 100644 --- a/actionpack/test/controller/render_xml_test.rb +++ b/actionpack/test/controller/render_xml_test.rb @@ -2,7 +2,6 @@ require "abstract_unit" require "controller/fake_models" -require "pathname" class RenderXmlTest < ActionController::TestCase class XmlRenderable @@ -92,11 +91,11 @@ class RenderXmlTest < ActionController::TestCase def test_should_render_xml_but_keep_custom_content_type get :render_xml_with_custom_content_type - assert_equal "application/atomsvc+xml", @response.content_type + assert_equal "application/atomsvc+xml", @response.media_type end def test_should_use_implicit_content_type get :implicit_content_type, format: "atom" - assert_equal Mime[:atom], @response.content_type + assert_equal Mime[:atom], @response.media_type end end diff --git a/actionpack/test/controller/renderers_test.rb b/actionpack/test/controller/renderers_test.rb index d92de6f5d5..96cce664a4 100644 --- a/actionpack/test/controller/renderers_test.rb +++ b/actionpack/test/controller/renderers_test.rb @@ -73,7 +73,7 @@ class RenderersTest < ActionController::TestCase assert_raise ActionView::MissingTemplate do get :respond_to_mime, format: "csv" end - assert_equal Mime[:csv], @response.content_type + assert_equal Mime[:csv], @response.media_type assert_equal "", @response.body end @@ -83,7 +83,7 @@ class RenderersTest < ActionController::TestCase end @request.accept = "text/csv" get :respond_to_mime, format: "csv" - assert_equal Mime[:csv], @response.content_type + assert_equal Mime[:csv], @response.media_type assert_equal "c,s,v", @response.body ensure ActionController::Renderers.remove :csv diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index ea94a3e048..01250880f5 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -112,7 +112,6 @@ class PrependProtectForgeryBaseController < ActionController::Base end private - def add_called_callback(name) @called_callbacks ||= [] @called_callbacks << name diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb index 089b0b94d4..538bc15fc9 100644 --- a/actionpack/test/controller/rescue_test.rb +++ b/actionpack/test/controller/rescue_test.rb @@ -304,7 +304,6 @@ class RescueControllerTest < ActionController::TestCase end private - def capture_log_output output = StringIO.new request.set_header "action_dispatch.logger", ActiveSupport::Logger.new(output) @@ -351,7 +350,6 @@ class RescueTest < ActionDispatch::IntegrationTest end private - def with_test_routing with_routing do |set| set.draw do diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index d2146f12a5..339025ec52 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "abstract_unit" -require "active_support/core_ext/object/try" require "active_support/core_ext/object/with_options" require "active_support/core_ext/array/extract_options" @@ -36,7 +35,6 @@ class ResourcesTest < ActionController::TestCase collection: collection_methods, member: member_methods, path_names: path_names do - assert_restful_routes_for :messages, collection: collection_methods, member: member_methods, @@ -58,7 +56,6 @@ class ResourcesTest < ActionController::TestCase collection: collection_methods, member: member_methods, path_names: path_names do |options| - collection_methods.each_key do |action| assert_named_route "/messages/#{path_names[action] || action}", "#{action}_messages_path", action: action end @@ -1251,7 +1248,7 @@ class ResourcesTest < ActionController::TestCase shallow_path = "/#{options[:shallow] ? options[:namespace] : options[:path_prefix]}#{path}" full_path = "/#{options[:path_prefix]}#{path}" name_prefix = options[:name_prefix] - shallow_prefix = options[:shallow] ? options[:namespace].try(:gsub, /\//, "_") : options[:name_prefix] + shallow_prefix = options[:shallow] ? options[:namespace]&.gsub(/\//, "_") : options[:name_prefix] new_action = "new" edit_action = "edit" diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb index 8724f9bcdb..8e1068fecf 100644 --- a/actionpack/test/controller/show_exceptions_test.rb +++ b/actionpack/test/controller/show_exceptions_test.rb @@ -51,7 +51,6 @@ module ShowExceptions class ShowExceptionsOverriddenController < ShowExceptionsController private - def show_detailed_exceptions? params["detailed"] == "1" end @@ -76,7 +75,7 @@ module ShowExceptions @app = ShowExceptionsOverriddenController.action(:boom) get "/", headers: { "HTTP_ACCEPT" => "application/json" } assert_response :internal_server_error - assert_equal "application/json", response.content_type.to_s + assert_equal "application/json", response.media_type assert_equal({ status: 500, error: "Internal Server Error" }.to_json, response.body) end @@ -84,7 +83,7 @@ module ShowExceptions @app = ShowExceptionsOverriddenController.action(:boom) get "/", headers: { "HTTP_ACCEPT" => "application/xml" } assert_response :internal_server_error - assert_equal "application/xml", response.content_type.to_s + assert_equal "application/xml", response.media_type assert_equal({ status: 500, error: "Internal Server Error" }.to_xml, response.body) end @@ -92,21 +91,21 @@ module ShowExceptions @app = ShowExceptionsOverriddenController.action(:boom) get "/", headers: { "HTTP_ACCEPT" => "text/csv" } assert_response :internal_server_error - assert_equal "text/html", response.content_type.to_s + assert_equal "text/html", response.media_type end end class ShowFailsafeExceptionsTest < ActionDispatch::IntegrationTest def test_render_failsafe_exception @app = ShowExceptionsOverriddenController.action(:boom) - middleware = @app.instance_variable_get(:@middleware) + middleware = @app @exceptions_app = middleware.instance_variable_get(:@exceptions_app) middleware.instance_variable_set(:@exceptions_app, nil) $stderr = StringIO.new get "/", headers: { "HTTP_ACCEPT" => "text/json" } assert_response :internal_server_error - assert_equal "text/plain", response.content_type.to_s + assert_equal "text/plain", response.media_type ensure middleware.instance_variable_set(:@exceptions_app, @exceptions_app) $stderr = STDERR diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 998a495d0d..635a91507d 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -165,7 +165,6 @@ XML end private - def generate_url(opts) url_for(opts.merge(action: "test_uri")) end diff --git a/actionpack/test/dispatch/callbacks_test.rb b/actionpack/test/dispatch/callbacks_test.rb index fc80191c02..aa8640c506 100644 --- a/actionpack/test/dispatch/callbacks_test.rb +++ b/actionpack/test/dispatch/callbacks_test.rb @@ -38,7 +38,6 @@ class DispatcherTest < ActiveSupport::TestCase end private - def dispatch(&block) ActionDispatch::Callbacks.new(block || DummyApp.new).call( "rack.input" => StringIO.new("") diff --git a/actionpack/test/dispatch/content_security_policy_test.rb b/actionpack/test/dispatch/content_security_policy_test.rb index c8c885f35c..3d60dc1661 100644 --- a/actionpack/test/dispatch/content_security_policy_test.rb +++ b/actionpack/test/dispatch/content_security_policy_test.rb @@ -128,12 +128,36 @@ class ContentSecurityPolicyTest < ActiveSupport::TestCase @policy.script_src false assert_no_match %r{script-src}, @policy.build + @policy.script_src_attr :self + assert_match %r{script-src-attr 'self'}, @policy.build + + @policy.script_src_attr false + assert_no_match %r{script-src-attr}, @policy.build + + @policy.script_src_elem :self + assert_match %r{script-src-elem 'self'}, @policy.build + + @policy.script_src_elem false + assert_no_match %r{script-src-elem}, @policy.build + @policy.style_src :self assert_match %r{style-src 'self'}, @policy.build @policy.style_src false assert_no_match %r{style-src}, @policy.build + @policy.style_src_attr :self + assert_match %r{style-src-attr 'self'}, @policy.build + + @policy.style_src_attr false + assert_no_match %r{style-src-attr}, @policy.build + + @policy.style_src_elem :self + assert_match %r{style-src-elem 'self'}, @policy.build + + @policy.style_src_elem false + assert_no_match %r{style-src-elem}, @policy.build + @policy.worker_src :self assert_match %r{worker-src 'self'}, @policy.build @@ -307,7 +331,6 @@ class DefaultContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationT end private - def assert_policy(expected, report_only: false) if report_only expected_header = "Content-Security-Policy-Report-Only" @@ -470,7 +493,6 @@ class ContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationTest end private - def assert_policy(expected, report_only: false) assert_response :success @@ -544,3 +566,57 @@ class DisabledContentSecurityPolicyIntegrationTest < ActionDispatch::Integration assert_equal "default-src https://example.com", response.headers["Content-Security-Policy"] end end + +class NonceDirectiveContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationTest + class PolicyController < ActionController::Base + def index + head :ok + end + end + + ROUTES = ActionDispatch::Routing::RouteSet.new + ROUTES.draw do + scope module: "nonce_directive_content_security_policy_integration_test" do + get "/", to: "policy#index" + end + end + + POLICY = ActionDispatch::ContentSecurityPolicy.new do |p| + p.default_src -> { :self } + p.script_src -> { :https } + p.style_src -> { :https } + end + + class PolicyConfigMiddleware + def initialize(app) + @app = app + end + + def call(env) + env["action_dispatch.content_security_policy"] = POLICY + env["action_dispatch.content_security_policy_nonce_generator"] = proc { "iyhD0Yc0W+c=" } + env["action_dispatch.content_security_policy_report_only"] = false + env["action_dispatch.content_security_policy_nonce_directives"] = %w(script-src) + env["action_dispatch.show_exceptions"] = false + + @app.call(env) + end + end + + APP = build_app(ROUTES) do |middleware| + middleware.use PolicyConfigMiddleware + middleware.use ActionDispatch::ContentSecurityPolicy::Middleware + end + + def app + APP + end + + def test_generate_nonce_only_specified_in_nonce_directives + get "/" + + assert_response :success + assert_match "script-src https: 'nonce-iyhD0Yc0W+c='", response.headers["Content-Security-Policy"] + assert_no_match "style-src https: 'nonce-iyhD0Yc0W+c='", response.headers["Content-Security-Policy"] + end +end diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb index 5ae8a20ae4..fa629bc761 100644 --- a/actionpack/test/dispatch/debug_exceptions_test.rb +++ b/actionpack/test/dispatch/debug_exceptions_test.rb @@ -208,7 +208,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_response 500 assert_no_match(/<header>/, body) assert_no_match(/<body>/, body) - assert_equal "text/plain", response.content_type + assert_equal "text/plain", response.media_type assert_match(/RuntimeError\npuke/, body) Rails.stub :root, Pathname.new(".") do @@ -222,31 +222,31 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest get "/not_found", headers: xhr_request_env assert_response 404 assert_no_match(/<body>/, body) - assert_equal "text/plain", response.content_type + assert_equal "text/plain", response.media_type assert_match(/#{AbstractController::ActionNotFound.name}/, body) get "/method_not_allowed", headers: xhr_request_env assert_response 405 assert_no_match(/<body>/, body) - assert_equal "text/plain", response.content_type + assert_equal "text/plain", response.media_type assert_match(/ActionController::MethodNotAllowed/, body) get "/unknown_http_method", headers: xhr_request_env assert_response 405 assert_no_match(/<body>/, body) - assert_equal "text/plain", response.content_type + assert_equal "text/plain", response.media_type assert_match(/ActionController::UnknownHttpMethod/, body) get "/bad_request", headers: xhr_request_env assert_response 400 assert_no_match(/<body>/, body) - assert_equal "text/plain", response.content_type + assert_equal "text/plain", response.media_type assert_match(/ActionController::BadRequest/, body) get "/parameter_missing", headers: xhr_request_env assert_response 400 assert_no_match(/<body>/, body) - assert_equal "text/plain", response.content_type + assert_equal "text/plain", response.media_type assert_match(/ActionController::ParameterMissing/, body) end @@ -257,37 +257,37 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_response 500 assert_no_match(/<header>/, body) assert_no_match(/<body>/, body) - assert_equal "application/json", response.content_type + assert_equal "application/json", response.media_type assert_match(/RuntimeError: puke/, body) get "/not_found", headers: { "action_dispatch.show_exceptions" => true }, as: :json assert_response 404 assert_no_match(/<body>/, body) - assert_equal "application/json", response.content_type + assert_equal "application/json", response.media_type assert_match(/#{AbstractController::ActionNotFound.name}/, body) get "/method_not_allowed", headers: { "action_dispatch.show_exceptions" => true }, as: :json assert_response 405 assert_no_match(/<body>/, body) - assert_equal "application/json", response.content_type + assert_equal "application/json", response.media_type assert_match(/ActionController::MethodNotAllowed/, body) get "/unknown_http_method", headers: { "action_dispatch.show_exceptions" => true }, as: :json assert_response 405 assert_no_match(/<body>/, body) - assert_equal "application/json", response.content_type + assert_equal "application/json", response.media_type assert_match(/ActionController::UnknownHttpMethod/, body) get "/bad_request", headers: { "action_dispatch.show_exceptions" => true }, as: :json assert_response 400 assert_no_match(/<body>/, body) - assert_equal "application/json", response.content_type + assert_equal "application/json", response.media_type assert_match(/ActionController::BadRequest/, body) get "/parameter_missing", headers: { "action_dispatch.show_exceptions" => true }, as: :json assert_response 400 assert_no_match(/<body>/, body) - assert_equal "application/json", response.content_type + assert_equal "application/json", response.media_type assert_match(/ActionController::ParameterMissing/, body) end @@ -298,7 +298,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_response 500 assert_match(/<header>/, body) assert_match(/<body>/, body) - assert_equal "text/html", response.content_type + assert_equal "text/html", response.media_type assert_match(/puke/, body) end @@ -307,7 +307,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest get "/index.xml", headers: { "action_dispatch.show_exceptions" => true } assert_response 500 - assert_equal "application/xml", response.content_type + assert_equal "application/xml", response.media_type assert_match(/RuntimeError: puke/, body) end @@ -321,7 +321,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest get "/index", headers: { "action_dispatch.show_exceptions" => true }, as: :wibble assert_response 500 - assert_equal "application/json", response.content_type + assert_equal "application/json", response.media_type assert_match(/RuntimeError: puke/, body) ensure @@ -466,6 +466,8 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest end test "logs exception backtrace when all lines silenced" do + @app = DevelopmentApp + output = StringIO.new backtrace_cleaner = ActiveSupport::BacktraceCleaner.new backtrace_cleaner.add_silencer { true } @@ -478,6 +480,27 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_operator((output.rewind && output.read).lines.count, :>, 10) end + test "doesn't log the framework backtrace when error type is a routing error" do + @app = ProductionApp + + output = StringIO.new + backtrace_cleaner = ActiveSupport::BacktraceCleaner.new + backtrace_cleaner.add_silencer { true } + + env = { "action_dispatch.show_exceptions" => true, + "action_dispatch.logger" => Logger.new(output), + "action_dispatch.backtrace_cleaner" => backtrace_cleaner } + + assert_raises ActionController::RoutingError do + get "/pass", headers: env + end + + log = output.rewind && output.read + + assert_includes log, "ActionController::RoutingError (No route matches [GET] \"/pass\")" + assert_equal 3, log.lines.count + end + test "display backtrace when error type is SyntaxError" do @app = DevelopmentApp @@ -620,4 +643,23 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_select 'input[value="Action 2"]' end end + + test "debug exceptions app shows diagnostics when malformed query parameters are provided" do + @app = DevelopmentApp + + get "/bad_request?x[y]=1&x[y][][w]=2" + + assert_response 400 + assert_match "ActionController::BadRequest", body + end + + test "debug exceptions app shows diagnostics when malformed query parameters are provided by XHR" do + @app = DevelopmentApp + xhr_request_env = { "action_dispatch.show_exceptions" => true, "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest" } + + get "/bad_request?x[y]=1&x[y][][w]=2", headers: xhr_request_env + + assert_response 400 + assert_match "ActionController::BadRequest", body + end end diff --git a/actionpack/test/dispatch/feature_policy_test.rb b/actionpack/test/dispatch/feature_policy_test.rb new file mode 100644 index 0000000000..ebcc8a8b6d --- /dev/null +++ b/actionpack/test/dispatch/feature_policy_test.rb @@ -0,0 +1,142 @@ +# frozen_string_literal: true + +require "abstract_unit" + +class FeaturePolicyTest < ActiveSupport::TestCase + def setup + @policy = ActionDispatch::FeaturePolicy.new + end + + def test_mappings + @policy.midi :self + assert_equal "midi 'self'", @policy.build + + @policy.midi :none + assert_equal "midi 'none'", @policy.build + end + + def test_multiple_sources_for_a_single_directive + @policy.geolocation :self, "https://example.com" + assert_equal "geolocation 'self' https://example.com", @policy.build + end + + def test_single_directive_for_multiple_directives + @policy.geolocation :self + @policy.usb :none + assert_equal "geolocation 'self'; usb 'none'", @policy.build + end + + def test_multiple_directives_for_multiple_directives + @policy.geolocation :self, "https://example.com" + @policy.usb :none, "https://example.com" + assert_equal "geolocation 'self' https://example.com; usb 'none' https://example.com", @policy.build + end + + def test_invalid_directive_source + exception = assert_raises(ArgumentError) do + @policy.vr [:non_existent] + end + + assert_equal "Invalid HTTP feature policy source: [:non_existent]", exception.message + end +end + +class FeaturePolicyIntegrationTest < ActionDispatch::IntegrationTest + class PolicyController < ActionController::Base + feature_policy only: :index do |f| + f.gyroscope :none + end + + feature_policy only: :sample_controller do |f| + f.gyroscope nil + f.usb :self + end + + feature_policy only: :multiple_directives do |f| + f.gyroscope nil + f.usb :self + f.autoplay "https://example.com" + f.payment "https://secure.example.com" + end + + def index + head :ok + end + + def sample_controller + head :ok + end + + def multiple_directives + head :ok + end + end + + ROUTES = ActionDispatch::Routing::RouteSet.new + ROUTES.draw do + scope module: "feature_policy_integration_test" do + get "/", to: "policy#index" + get "/sample_controller", to: "policy#sample_controller" + get "/multiple_directives", to: "policy#multiple_directives" + end + end + + POLICY = ActionDispatch::FeaturePolicy.new do |p| + p.gyroscope :self + end + + class PolicyConfigMiddleware + def initialize(app) + @app = app + end + + def call(env) + env["action_dispatch.feature_policy"] = POLICY + env["action_dispatch.show_exceptions"] = false + + @app.call(env) + end + end + + APP = build_app(ROUTES) do |middleware| + middleware.use PolicyConfigMiddleware + middleware.use ActionDispatch::FeaturePolicy::Middleware + end + + def app + APP + end + + def test_generates_feature_policy_header + get "/" + assert_policy "gyroscope 'none'" + end + + def test_generates_per_controller_feature_policy_header + get "/sample_controller" + assert_policy "usb 'self'" + end + + def test_generates_multiple_directives_feature_policy_header + get "/multiple_directives" + assert_policy "usb 'self'; autoplay https://example.com; payment https://secure.example.com" + end + + private + def env_config + Rails.application.env_config + end + + def feature_policy + env_config["action_dispatch.feature_policy"] + end + + def feature_policy=(policy) + env_config["action_dispatch.feature_policy"] = policy + end + + def assert_policy(expected) + assert_response :success + assert_equal expected, response.headers["Feature-Policy"] + end +end diff --git a/actionpack/test/dispatch/middleware_stack_test.rb b/actionpack/test/dispatch/middleware_stack_test.rb index 90f2eccd19..c534e60c74 100644 --- a/actionpack/test/dispatch/middleware_stack_test.rb +++ b/actionpack/test/dispatch/middleware_stack_test.rb @@ -121,9 +121,6 @@ class MiddlewareStackTest < ActiveSupport::TestCase end test "instruments the execution of middlewares" do - app = @stack.build(proc { |env| [200, {}, []] }) - env = {} - events = [] subscriber = proc do |*args| @@ -131,6 +128,9 @@ class MiddlewareStackTest < ActiveSupport::TestCase end ActiveSupport::Notifications.subscribed(subscriber, "process_middleware.action_dispatch") do + app = @stack.build(proc { |env| [200, {}, []] }) + + env = {} app.call(env) end diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index 63c147cb1b..f20043b9ac 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -304,7 +304,7 @@ module TestGenerationPrefix assert_equal "/omg/blog/posts/1", path end - test "[OBJECT] generating engine's route with named helpers" do + test "[OBJECT] generating engine's route with named route helpers" do path = engine_object.posts_path assert_equal "/awesome/blog/posts", path diff --git a/actionpack/test/dispatch/request_id_test.rb b/actionpack/test/dispatch/request_id_test.rb index 9df4712dab..036180c297 100644 --- a/actionpack/test/dispatch/request_id_test.rb +++ b/actionpack/test/dispatch/request_id_test.rb @@ -29,7 +29,6 @@ class RequestIdTest < ActiveSupport::TestCase end private - def stub_request(env = {}) ActionDispatch::RequestId.new(lambda { |environment| [ 200, environment, [] ] }).call(env) ActionDispatch::Request.new(env) @@ -58,7 +57,6 @@ class RequestIdResponseTest < ActionDispatch::IntegrationTest end private - def with_test_route_set with_routing do |set| set.draw do diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index eb49396145..c4cb27429d 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -681,7 +681,6 @@ end class RequestMethod < BaseRequestTest test "method returns environment's request method when it has not been overridden by middleware".squish do - ActionDispatch::Request::HTTP_METHODS.each do |method| request = stub_request("REQUEST_METHOD" => method) @@ -866,12 +865,28 @@ class RequestFormat < BaseRequestTest assert_not_predicate request.format, :json? end - test "format does not throw exceptions when malformed parameters" do + test "format does not throw exceptions when malformed GET parameters" do request = stub_request("QUERY_STRING" => "x[y]=1&x[y][][w]=2") assert request.formats assert_predicate request.format, :html? end + test "format does not throw exceptions when invalid POST parameters" do + body = "{record:{content:127.0.0.1}}" + request = stub_request( + "REQUEST_METHOD" => "POST", + "CONTENT_LENGTH" => body.length, + "CONTENT_TYPE" => "application/json", + "rack.input" => StringIO.new(body), + "action_dispatch.logger" => Logger.new(output = StringIO.new) + ) + assert request.formats + assert request.format.html? + + output.rewind && (err = output.read) + assert_match /Error occurred while parsing request parameters/, err + end + test "formats with xhr request" do request = stub_request "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", "QUERY_STRING" => "" diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 7758b0406a..ed64d89902 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -290,8 +290,8 @@ class ResponseTest < ActiveSupport::TestCase resp.to_a assert_equal("utf-16", resp.charset) - assert_equal(Mime[:xml], resp.content_type) - + assert_equal(Mime[:xml], resp.media_type) + assert_equal("application/xml; charset=utf-16", resp.content_type) assert_equal("application/xml; charset=utf-16", resp.headers["Content-Type"]) end @@ -503,8 +503,8 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest assert_response :success assert_equal("utf-16", @response.charset) - assert_equal(Mime[:xml], @response.content_type) - + assert_equal(Mime[:xml], @response.media_type) + assert_equal("application/xml; charset=utf-16", @response.content_type) assert_equal("application/xml; charset=utf-16", @response.headers["Content-Type"]) end @@ -519,8 +519,8 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest assert_response :success assert_equal("utf-16", @response.charset) - assert_equal(Mime[:xml], @response.content_type) - + assert_equal(Mime[:xml], @response.media_type) + assert_equal("application/xml; charset=utf-16", @response.content_type) assert_equal("application/xml; charset=utf-16", @response.headers["Content-Type"]) end @@ -553,7 +553,26 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest assert_response :success assert_equal("text/csv; charset=utf-16; header=present", @response.headers["Content-Type"]) - assert_equal("text/csv", @response.content_type) + assert_equal("text/csv; charset=utf-16; header=present", @response.content_type) + assert_equal("text/csv", @response.media_type) + assert_equal("utf-16", @response.charset) + end + + test "response Content-Type with optional parameters that set before charset" do + @app = lambda { |env| + [ + 200, + { "Content-Type" => "text/csv; header=present; charset=utf-16" }, + ["Hello"] + ] + } + + get "/" + assert_response :success + + assert_equal("text/csv; header=present; charset=utf-16", @response.headers["Content-Type"]) + assert_equal("text/csv; header=present; charset=utf-16", @response.content_type) + assert_equal("text/csv", @response.media_type) assert_equal("utf-16", @response.charset) end @@ -570,7 +589,37 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest assert_response :success assert_equal('text/csv; header=present; charset="utf-16"', @response.headers["Content-Type"]) - assert_equal("text/csv", @response.content_type) + assert_equal('text/csv; header=present; charset="utf-16"', @response.content_type) + assert_equal("text/csv", @response.media_type) assert_equal("utf-16", @response.charset) end + + test "`content type` returns header that excludes `charset` when specified `return_only_media_type_on_content_type`" do + original = ActionDispatch::Response.return_only_media_type_on_content_type + ActionDispatch::Response.return_only_media_type_on_content_type = true + + @app = lambda { |env| + if env["PATH_INFO"] == "/with_parameters" + [200, { "Content-Type" => "text/csv; header=present; charset=utf-16" }, [""]] + else + [200, { "Content-Type" => "text/csv; charset=utf-16" }, [""]] + end + } + + get "/" + assert_response :success + + assert_deprecated do + assert_equal("text/csv", @response.content_type) + end + + get "/with_parameters" + assert_response :success + + assert_deprecated do + assert_equal("text/csv; header=present", @response.content_type) + end + ensure + ActionDispatch::Response.return_only_media_type_on_content_type = original + end end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 362488d585..b67b1dd347 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -3810,7 +3810,6 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end private - def draw(&block) self.class.stub_controllers do |routes| routes.default_url_options = { host: "www.example.com" } @@ -4953,12 +4952,52 @@ class TestPartialDynamicPathSegments < ActionDispatch::IntegrationTest end private - def assert_params(params) assert_equal(params, request.path_parameters) end end +class TestOptionalScopesWithOrWithoutParams < ActionDispatch::IntegrationTest + Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| + app.draw do + scope module: "test_optional_scopes_with_or_without_params" do + scope "(:locale)", locale: /en|es/ do + get "home", to: "home#index" + get "with_param/:foo", to: "home#with_param", as: "with_param" + get "without_param", to: "home#without_param" + end + end + end + end + + class HomeController < ActionController::Base + include Routes.url_helpers + + def index + render inline: "<%= with_param_path(foo: 'bar') %> | <%= without_param_path %>" + end + + def with_param; end + def without_param; end + end + + APP = build_app Routes + + def app + APP + end + + def test_stays_unscoped_with_or_without_params + get "/home" + assert_equal "/with_param/bar | /without_param", response.body + end + + def test_preserves_scope_with_or_without_params + get "/es/home" + assert_equal "/es/with_param/bar | /es/without_param", response.body + end +end + class TestPathParameters < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| app.draw do @@ -5143,7 +5182,6 @@ class TestRecognizePath < ActionDispatch::IntegrationTest end private - def recognize_path(*args) Routes.recognize_path(*args) end diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index e34426a471..b6f83f4062 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -379,7 +379,6 @@ class CookieStoreTest < ActionDispatch::IntegrationTest end private - # Overwrite get to send SessionSecret in env hash def get(path, *args) args[0] ||= {} diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb index d44aa00122..1f93d594a6 100644 --- a/actionpack/test/dispatch/static_test.rb +++ b/actionpack/test/dispatch/static_test.rb @@ -232,7 +232,6 @@ module StaticTests end private - def assert_gzip(file_name, response) expected = File.read("#{FIXTURE_LOAD_PATH}/#{public_path}" + file_name) actual = ActiveSupport::Gzip.decompress(response.body) diff --git a/actionpack/test/dispatch/system_testing/driver_test.rb b/actionpack/test/dispatch/system_testing/driver_test.rb index 0d08f17af3..d3b16d0328 100644 --- a/actionpack/test/dispatch/system_testing/driver_test.rb +++ b/actionpack/test/dispatch/system_testing/driver_test.rb @@ -66,7 +66,7 @@ class DriverTest < ActiveSupport::TestCase end driver.use - expected = { args: ["start-maximized"], mobileEmulation: { deviceName: "iphone 6" }, prefs: { detach: true } } + expected = { "goog:chromeOptions" => { args: ["start-maximized"], mobileEmulation: { deviceName: "iphone 6" }, prefs: { detach: true } } } assert_equal expected, driver_option.as_json end @@ -81,7 +81,7 @@ class DriverTest < ActiveSupport::TestCase end driver.use - expected = { args: ["start-maximized"], mobileEmulation: { deviceName: "iphone 6" }, prefs: { detach: true } } + expected = { "goog:chromeOptions" => { args: ["start-maximized"], mobileEmulation: { deviceName: "iphone 6" }, prefs: { detach: true } } } assert_equal expected, driver_option.as_json end @@ -120,4 +120,17 @@ class DriverTest < ActiveSupport::TestCase driver.use end end + + test "preloads browser's driver_path" do + called = false + + original_driver_path = ::Selenium::WebDriver::Chrome::Service.driver_path + ::Selenium::WebDriver::Chrome::Service.driver_path = -> { called = true } + + ActionDispatch::SystemTesting::Driver.new(:selenium, screen_size: [1400, 1400], using: :chrome) + + assert called + ensure + ::Selenium::WebDriver::Chrome::Service.driver_path = original_driver_path + end 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 847b09dcfe..d235f7ad89 100644 --- a/actionpack/test/dispatch/system_testing/system_test_case_test.rb +++ b/actionpack/test/dispatch/system_testing/system_test_case_test.rb @@ -36,50 +36,11 @@ class SetDriverToSeleniumHeadlessFirefoxTest < DrivenBySeleniumWithHeadlessFiref 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" + assert_deprecated do + host! "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 + assert_equal "http://example.com", Capybara.app_host end end diff --git a/actionpack/test/journey/route/definition/scanner_test.rb b/actionpack/test/journey/route/definition/scanner_test.rb index 092177d315..e55ed92cc8 100644 --- a/actionpack/test/journey/route/definition/scanner_test.rb +++ b/actionpack/test/journey/route/definition/scanner_test.rb @@ -66,7 +66,6 @@ module ActionDispatch end private - def assert_tokens(expected_tokens, scanner, pattern) actual_tokens = [] while token = scanner.next_token diff --git a/actionpack/test/journey/route_test.rb b/actionpack/test/journey/route_test.rb index a8bf4a11e2..8828201e4f 100644 --- a/actionpack/test/journey/route_test.rb +++ b/actionpack/test/journey/route_test.rb @@ -9,7 +9,7 @@ module ActionDispatch app = Object.new path = Path::Pattern.from_string "/:controller(/:action(/:id(.:format)))" defaults = {} - route = Route.build("name", app, path, {}, [], defaults) + route = Route.new(name: "name", app: app, path: path, defaults: defaults) assert_equal app, route.app assert_equal path, route.path @@ -17,10 +17,9 @@ module ActionDispatch end def test_route_adds_itself_as_memo - app = Object.new - path = Path::Pattern.from_string "/:controller(/:action(/:id(.:format)))" - defaults = {} - route = Route.build("name", app, path, {}, [], defaults) + app = Object.new + path = Path::Pattern.from_string "/:controller(/:action(/:id(.:format)))" + route = Route.new(name: "name", app: app, path: path) route.ast.grep(Nodes::Terminal).each do |node| assert_equal route, node.memo @@ -28,30 +27,30 @@ module ActionDispatch end def test_path_requirements_override_defaults - path = Path::Pattern.build(":name", { name: /love/ }, "/", true) - defaults = { name: "tender" } - route = Route.build("name", nil, path, {}, [], defaults) + path = Path::Pattern.build(":name", { name: /love/ }, "/", true) + defaults = { name: "tender" } + route = Route.new(name: "name", path: path, defaults: defaults) assert_equal(/love/, route.requirements[:name]) end def test_ip_address path = Path::Pattern.from_string "/messages/:id(.:format)" - route = Route.build("name", nil, path, { ip: "192.168.1.1" }, [], - controller: "foo", action: "bar") + route = Route.new(name: "name", path: path, constraints: { ip: "192.168.1.1" }, + defaults: { controller: "foo", action: "bar" }) assert_equal "192.168.1.1", route.ip end def test_default_ip path = Path::Pattern.from_string "/messages/:id(.:format)" - route = Route.build("name", nil, path, {}, [], - controller: "foo", action: "bar") + route = Route.new(name: "name", path: path, + defaults: { controller: "foo", action: "bar" }) assert_equal(//, route.ip) end def test_format_with_star path = Path::Pattern.from_string "/:controller/*extra" - route = Route.build("name", nil, path, {}, [], - controller: "foo", action: "bar") + route = Route.new(name: "name", path: path, + defaults: { controller: "foo", action: "bar" }) assert_equal "/foo/himom", route.format( controller: "foo", extra: "himom") @@ -59,7 +58,8 @@ module ActionDispatch def test_connects_all_match path = Path::Pattern.from_string "/:controller(/:action(/:id(.:format)))" - route = Route.build("name", nil, path, { action: "bar" }, [], controller: "foo") + route = Route.new(name: "name", path: path, constraints: { action: "bar" }, + defaults: { controller: "foo" }) assert_equal "/foo/bar/10", route.format( controller: "foo", @@ -69,34 +69,33 @@ module ActionDispatch def test_extras_are_not_included_if_optional path = Path::Pattern.from_string "/page/:id(/:action)" - route = Route.build("name", nil, path, {}, [], action: "show") + route = Route.new(name: "name", path: path, defaults: { action: "show" }) assert_equal "/page/10", route.format(id: 10) end def test_extras_are_not_included_if_optional_with_parameter path = Path::Pattern.from_string "(/sections/:section)/pages/:id" - route = Route.build("name", nil, path, {}, [], action: "show") + route = Route.new(name: "name", path: path, defaults: { action: "show" }) assert_equal "/pages/10", route.format(id: 10) end def test_extras_are_not_included_if_optional_parameter_is_nil path = Path::Pattern.from_string "(/sections/:section)/pages/:id" - route = Route.build("name", nil, path, {}, [], action: "show") + route = Route.new(name: "name", path: path, defaults: { action: "show" }) assert_equal "/pages/10", route.format(id: 10, section: nil) end def test_score - constraints = {} defaults = { controller: "pages", action: "show" } path = Path::Pattern.from_string "/page/:id(/:action)(.:format)" - specific = Route.build "name", nil, path, constraints, [:controller, :action], defaults + specific = Route.new name: "name", path: path, required_defaults: [:controller, :action], defaults: defaults path = Path::Pattern.from_string "/:controller(/:action(/:id))(.:format)" - generic = Route.build "name", nil, path, constraints, [], {} + generic = Route.new name: "name", path: path knowledge = { "id" => true, "controller" => true, "action" => true } diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb index f8d89def6a..fe0e3a975b 100644 --- a/actionpack/test/journey/router_test.rb +++ b/actionpack/test/journey/router_test.rb @@ -503,7 +503,6 @@ module ActionDispatch end private - def get(*args) ActiveSupport::Deprecation.silence do mapper.get(*args) |