diff options
Diffstat (limited to 'actionpack/lib/action_dispatch')
29 files changed, 182 insertions, 110 deletions
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index e584b84d92..077ab2561f 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -74,7 +74,7 @@ module ActionDispatch PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})} def filtered_query_string # :doc: query_string.gsub(PAIR_RE) do |_| - parameter_filter.filter([[$1, $2]]).first.join("=") + parameter_filter.filter($1 => $2).first.join("=") end end end diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 19f89edbc1..5994a01c78 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -6,8 +6,7 @@ module ActionDispatch extend ActiveSupport::Concern included do - mattr_accessor :ignore_accept_header - self.ignore_accept_header = false + mattr_accessor :ignore_accept_header, default: false end # The MIME type of the HTTP request, such as Mime[:xml]. diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 0cf010f59e..74a3afab44 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -326,11 +326,11 @@ module Mime def ref; end - def respond_to_missing?(method, include_private = false) - method.to_s.ends_with? "?" - end - private + def respond_to_missing?(method, _) + method.to_s.ends_with? "?" + end + def method_missing(method, *args) false if method.to_s.ends_with? "?" end diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb index 889f55a52a..1d2b4b902b 100644 --- a/actionpack/lib/action_dispatch/http/parameter_filter.rb +++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb @@ -54,7 +54,7 @@ module ActionDispatch end def call(original_params, parents = []) - filtered_params = {} + filtered_params = original_params.class.new original_params.each do |key, value| parents.push(key) if deep_regexps diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 79a2ef965b..7c585dbe68 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -85,7 +85,7 @@ module ActionDispatch def set_binary_encoding(params) action = params[:action] - if controller_class.binary_params_for?(action) + if binary_params_for?(action) ActionDispatch::Request::Utils.each_param_value(params) do |param| param.force_encoding ::Encoding::ASCII_8BIT end @@ -93,6 +93,12 @@ module ActionDispatch params end + def binary_params_for?(action) + controller_class.binary_params_for?(action) + rescue NameError + false + end + def parse_formatted_parameters(parsers) return yield if content_length.zero? || content_mime_type.nil? diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 2ee52eeb48..3c91677d55 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -81,8 +81,8 @@ module ActionDispatch # :nodoc: LOCATION = "Location".freeze NO_CONTENT_CODES = [100, 101, 102, 204, 205, 304] - cattr_accessor(:default_charset) { "utf-8" } - cattr_accessor(:default_headers) + cattr_accessor :default_charset, default: "utf-8" + cattr_accessor :default_headers include Rack::Response::Helpers # Aliasing these off because AD::Http::Cache::Response defines them. diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index 61ba052e45..225272d66e 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -27,14 +27,18 @@ module ActionDispatch @tempfile = hash[:tempfile] raise(ArgumentError, ":tempfile is required") unless @tempfile - @original_filename = hash[:filename] - if @original_filename + if hash[:filename] + @original_filename = hash[:filename].dup + begin @original_filename.encode!(Encoding::UTF_8) rescue EncodingError @original_filename.force_encoding(Encoding::UTF_8) end + else + @original_filename = nil end + @content_type = hash[:type] @headers = hash[:head] end diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index b6be48a48b..f902fe36e0 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -7,8 +7,7 @@ module ActionDispatch HOST_REGEXP = /(^[^:]+:\/\/)?(\[[^\]]+\]|[^:]+)(?::(\d+$))?/ PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/ - mattr_accessor :tld_length - self.tld_length = 1 + mattr_accessor :tld_length, default: 1 class << self # Returns the domain part of a host given the domain level. diff --git a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb index d692f6415c..62f052ced6 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb @@ -18,14 +18,6 @@ module ActionDispatch @tt = transition_table end - def simulate(string) - ms = memos(string) { return } - MatchData.new(ms) - end - - alias :=~ :simulate - alias :match :simulate - def memos(string) input = StringScanner.new(string) state = [0] diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb index e1ac2c873e..45aff287b1 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb @@ -82,7 +82,7 @@ module ActionDispatch end def visualizer(paths, title = "FSM") - viz_dir = File.join File.dirname(__FILE__), "..", "visualizer" + viz_dir = File.join __dir__, "..", "visualizer" fsm_js = File.read File.join(viz_dir, "fsm.js") fsm_css = File.read File.join(viz_dir, "fsm.css") erb = File.read File.join(viz_dir, "index.html.erb") diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index 7bc15aa6b3..0acbac1d9d 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -10,11 +10,11 @@ module ActionDispatch module VerbMatchers VERBS = %w{ DELETE GET HEAD OPTIONS LINK PATCH POST PUT TRACE UNLINK } VERBS.each do |v| - class_eval <<-eoc - class #{v} - def self.verb; name.split("::").last; end - def self.call(req); req.#{v.downcase}?; end - end + class_eval <<-eoc, __FILE__, __LINE__ + 1 + class #{v} + def self.verb; name.split("::").last; end + def self.call(req); req.#{v.downcase}?; end + end eoc end diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb index ffcd875b4d..6d400f3364 100644 --- a/actionpack/lib/action_dispatch/journey/router/utils.rb +++ b/actionpack/lib/action_dispatch/journey/router/utils.rb @@ -13,11 +13,13 @@ module ActionDispatch # normalize_path("") # => "/" # normalize_path("/%ab") # => "/%AB" def self.normalize_path(path) + encoding = path.encoding path = "/#{path}" path.squeeze!("/".freeze) path.sub!(%r{/+\Z}, "".freeze) path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase } path = "/" if path == "".freeze + path.force_encoding(encoding) path end @@ -59,11 +61,11 @@ module ActionDispatch end private - def escape(component, pattern) # :doc: + def escape(component, pattern) component.gsub(pattern) { |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII) end - def percent_encode(unsafe) # :doc: + def percent_encode(unsafe) safe = EMPTY.dup unsafe.each_byte { |b| safe << DEC2HEX[b] } safe @@ -84,6 +86,10 @@ module ActionDispatch ENCODER.escape_fragment(fragment.to_s) end + # Replaces any escaped sequences with their unescaped representations. + # + # uri = "/topics?title=Ruby%20on%20Rails" + # unescape_uri(uri) #=> "/topics?title=Ruby on Rails" def self.unescape_uri(uri) ENCODER.unescape_uri(uri) end diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index e565c03a8a..533925ebe1 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -43,6 +43,10 @@ module ActionDispatch get_header Cookies::ENCRYPTED_SIGNED_COOKIE_SALT end + def authenticated_encrypted_cookie_salt + get_header Cookies::AUTHENTICATED_ENCRYPTED_COOKIE_SALT + end + def secret_token get_header Cookies::SECRET_TOKEN end @@ -149,6 +153,7 @@ module ActionDispatch SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze + AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt".freeze SECRET_TOKEN = "action_dispatch.secret_token".freeze SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze @@ -207,6 +212,9 @@ module ActionDispatch # If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set, # legacy cookies signed with the old key generator will be transparently upgraded. # + # If +config.action_dispatch.encrypted_cookie_salt+ and +config.action_dispatch.encrypted_signed_cookie_salt+ + # are both set, legacy cookies encrypted with HMAC AES-256-CBC will be transparently upgraded. + # # This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+. # # Example: @@ -219,6 +227,8 @@ module ActionDispatch @encrypted ||= if upgrade_legacy_signed_cookies? UpgradeLegacyEncryptedCookieJar.new(self) + elsif upgrade_legacy_hmac_aes_cbc_cookies? + UpgradeLegacyHmacAesCbcCookieJar.new(self) else EncryptedCookieJar.new(self) end @@ -240,6 +250,13 @@ module ActionDispatch def upgrade_legacy_signed_cookies? request.secret_token.present? && request.secret_key_base.present? end + + def upgrade_legacy_hmac_aes_cbc_cookies? + request.secret_key_base.present? && + request.authenticated_encrypted_cookie_salt.present? && + request.encrypted_signed_cookie_salt.present? && + request.encrypted_cookie_salt.present? + end end # Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream @@ -415,8 +432,7 @@ module ActionDispatch end end - mattr_accessor :always_write_cookie - self.always_write_cookie = false + mattr_accessor :always_write_cookie, default: false private @@ -576,9 +592,11 @@ module ActionDispatch "Read the upgrade documentation to learn more about this new config option." end - secret = key_generator.generate_key(request.encrypted_cookie_salt || "")[0, ActiveSupport::MessageEncryptor.key_len] - sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || "") - @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer) + cipher = "aes-256-gcm" + key_len = ActiveSupport::MessageEncryptor.key_len(cipher) + secret = key_generator.generate_key(request.authenticated_encrypted_cookie_salt || "")[0, key_len] + + @encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: ActiveSupport::MessageEncryptor::NullSerializer) end private @@ -603,6 +621,32 @@ module ActionDispatch include VerifyAndUpgradeLegacySignedMessage end + # UpgradeLegacyHmacAesCbcCookieJar is used by ActionDispatch::Session::CookieStore + # to upgrade cookies encrypted with AES-256-CBC with HMAC to AES-256-GCM + class UpgradeLegacyHmacAesCbcCookieJar < EncryptedCookieJar + def initialize(parent_jar) + super + + secret = key_generator.generate_key(request.encrypted_cookie_salt || "")[0, ActiveSupport::MessageEncryptor.key_len] + sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || "") + + @legacy_encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer) + end + + def decrypt_and_verify_legacy_encrypted_message(name, signed_message) + deserialize(name, @legacy_encryptor.decrypt_and_verify(signed_message)).tap do |value| + self[name] = { value: value } + end + rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage + nil + end + + private + def parse(name, signed_message) + super || decrypt_and_verify_legacy_encrypted_message(name, signed_message) + end + end + def initialize(app) @app = app end diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index 1c720c5a8e..336a775880 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -10,7 +10,7 @@ module ActionDispatch # This middleware is responsible for logging exceptions and # showing a debugging page in case the request is local. class DebugExceptions - RESCUES_TEMPLATE_PATH = File.expand_path("../templates", __FILE__) + RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__) class DebugView < ActionView::Base def debug_params(params) diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb index 397f0a8b92..08b4541d24 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -3,9 +3,7 @@ require "rack/utils" module ActionDispatch class ExceptionWrapper - cattr_accessor :rescue_responses - @@rescue_responses = Hash.new(:internal_server_error) - @@rescue_responses.merge!( + cattr_accessor :rescue_responses, default: Hash.new(:internal_server_error).merge!( "ActionController::RoutingError" => :not_found, "AbstractController::ActionNotFound" => :not_found, "ActionController::MethodNotAllowed" => :method_not_allowed, @@ -21,9 +19,7 @@ module ActionDispatch "Rack::QueryParser::InvalidParameterError" => :bad_request ) - cattr_accessor :rescue_templates - @@rescue_templates = Hash.new("diagnostics") - @@rescue_templates.merge!( + cattr_accessor :rescue_templates, default: Hash.new("diagnostics").merge!( "ActionView::MissingTemplate" => "missing_template", "ActionController::RoutingError" => "routing_error", "AbstractController::ActionNotFound" => "unknown_action", diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index 5d10129d21..fb99f13a1c 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -6,11 +6,11 @@ module ActionDispatch # When initialized, it can accept optional HTTP headers, which will be set # when a response containing a file's contents is delivered. # - # This middleware will render the file specified in `env["PATH_INFO"]` + # This middleware will render the file specified in <tt>env["PATH_INFO"]</tt> # where the base path is in the +root+ directory. For example, if the +root+ - # is set to `public/`, then a request with `env["PATH_INFO"]` of - # `assets/application.js` will return a response with the contents of a file - # located at `public/assets/application.js` if the file exists. If the file + # is set to +public/+, then a request with <tt>env["PATH_INFO"]</tt> of + # +assets/application.js+ will return a response with the contents of a file + # located at +public/assets/application.js+ if the file exists. If the file # does not exist, a 404 "File not Found" response will be returned. class FileHandler def initialize(root, index: "index", headers: {}) @@ -23,8 +23,8 @@ module ActionDispatch # correct read permissions, the return value is a URI-escaped string # representing the filename. Otherwise, false is returned. # - # Used by the `Static` class to check the existence of a valid file - # in the server's `public/` directory (see Static#call). + # Used by the +Static+ class to check the existence of a valid file + # in the server's +public/+ directory (see Static#call). def match?(path) path = ::Rack::Utils.unescape_path path return false unless ::Rack::Utils.valid_path? path @@ -99,7 +99,7 @@ module ActionDispatch # This middleware will attempt to return the contents of a file's body from # disk in the response. If a file is not found on disk, the request will be # delegated to the application stack. This middleware is commonly initialized - # to serve assets from a server's `public/` directory. + # to serve assets from a server's +public/+ directory. # # This middleware verifies the path to ensure that only files # living in the root directory can be rendered. A request cannot diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index 16a18a7f25..7662e164b8 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -16,6 +16,7 @@ module ActionDispatch config.action_dispatch.signed_cookie_salt = "signed cookie" config.action_dispatch.encrypted_cookie_salt = "encrypted cookie" config.action_dispatch.encrypted_signed_cookie_salt = "signed encrypted cookie" + config.action_dispatch.use_authenticated_cookie_encryption = false config.action_dispatch.perform_deep_munge = true config.action_dispatch.default_headers = { @@ -36,6 +37,8 @@ module ActionDispatch ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses) ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates) + config.action_dispatch.authenticated_encrypted_cookie_salt = "authenticated encrypted cookie" if config.action_dispatch.use_authenticated_cookie_encryption + config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil? ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb index 74ba6466cf..3547a8604f 100644 --- a/actionpack/lib/action_dispatch/request/session.rb +++ b/actionpack/lib/action_dispatch/request/session.rb @@ -101,11 +101,13 @@ module ActionDispatch # Returns keys of the session as Array. def keys + load_for_read! @delegate.keys end # Returns values of the session as Array. def values + load_for_read! @delegate.values end diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb index 3615e4b1d8..4f79c4c21e 100644 --- a/actionpack/lib/action_dispatch/request/utils.rb +++ b/actionpack/lib/action_dispatch/request/utils.rb @@ -1,8 +1,7 @@ module ActionDispatch class Request class Utils # :nodoc: - mattr_accessor :perform_deep_munge - self.perform_deep_munge = true + mattr_accessor :perform_deep_munge, default: true def self.each_param_value(params, &block) case params diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 60d4789a63..87dd1eba38 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -254,14 +254,5 @@ module ActionDispatch SEPARATORS = %w( / . ? ) #:nodoc: HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc: - - #:stopdoc: - INSECURE_URL_PARAMETERS_MESSAGE = <<-MSG.squish - Attempting to generate a URL from non-sanitized request parameters! - - An attacker can inject malicious data into the generated URL, such as - changing the host. Whitelist and sanitize passed parameters to be secure. - MSG - #:startdoc: end end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 74904e3d45..88deee5f5e 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1837,7 +1837,7 @@ module ActionDispatch path_types.fetch(String, []).each do |_path| route_options = options.dup if _path && option_path - raise ArgumentError, "Ambigous route definition. Both :path and the route path where specified as strings." + raise ArgumentError, "Ambiguous route definition. Both :path and the route path where specified as strings." end to = get_to_from_path(_path, to, route_options[:action]) decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints) @@ -2038,8 +2038,8 @@ module ActionDispatch # { controller: "pages", action: "index", subdomain: "www" } # end # - # The return value from the block passed to `direct` must be a valid set of - # arguments for `url_for` which will actually build the URL string. This can + # The return value from the block passed to +direct+ must be a valid set of + # arguments for +url_for+ which will actually build the URL string. This can # be one of the following: # # * A string, which is treated as a generated URL @@ -2058,17 +2058,17 @@ module ActionDispatch # [ :products, options.merge(params.permit(:page, :size).to_h.symbolize_keys) ] # end # - # In this instance the `params` object comes from the context in which the the + # In this instance the +params+ object comes from the context in which the the # block is executed, e.g. generating a URL inside a controller action or a view. # If the block is executed where there isn't a params object such as this: # # Rails.application.routes.url_helpers.browse_path # - # then it will raise a `NameError`. Because of this you need to be aware of the + # then it will raise a +NameError+. Because of this you need to be aware of the # context in which you will use your custom URL helper when defining it. # - # NOTE: The `direct` method can't be used inside of a scope block such as - # `namespace` or `scope` and will raise an error if it detects that it is. + # NOTE: The +direct+ method can't be used inside of a scope block such as + # +namespace+ or +scope+ and will raise an error if it detects that it is. def direct(name, options = {}, &block) unless @scope.root? raise RuntimeError, "The direct method can't be used inside a routes scope block" @@ -2078,8 +2078,8 @@ module ActionDispatch end # Define custom polymorphic mappings of models to URLs. This alters the - # behavior of `polymorphic_url` and consequently the behavior of - # `link_to` and `form_for` when passed a model instance, e.g: + # behavior of +polymorphic_url+ and consequently the behavior of + # +link_to+ and +form_for+ when passed a model instance, e.g: # # resource :basket # @@ -2087,8 +2087,8 @@ module ActionDispatch # [:basket] # end # - # This will now generate "/basket" when a `Basket` instance is passed to - # `link_to` or `form_for` instead of the standard "/baskets/:id". + # This will now generate "/basket" when a +Basket+ instance is passed to + # +link_to+ or +form_for+ instead of the standard "/baskets/:id". # # NOTE: This custom behavior only applies to simple polymorphic URLs where # a single model instance is passed and not more complicated forms, e.g: @@ -2105,7 +2105,7 @@ module ActionDispatch # link_to "Profile", @current_user # link_to "Profile", [:admin, @current_user] # - # The first `link_to` will generate "/profile" but the second will generate + # The first +link_to+ will generate "/profile" but the second will generate # the standard polymorphic URL of "/admin/users/1". # # You can pass options to a polymorphic mapping - the arity for the block @@ -2116,11 +2116,11 @@ module ActionDispatch # end # # This generates the URL "/basket#items" because when the last item in an - # array passed to `polymorphic_url` is a hash then it's treated as options + # array passed to +polymorphic_url+ is a hash then it's treated as options # to the URL helper that gets called. # - # NOTE: The `resolve` method can't be used inside of a scope block such as - # `namespace` or `scope` and will raise an error if it detects that it is. + # NOTE: The +resolve+ method can't be used inside of a scope block such as + # +namespace+ or +scope+ and will raise an error if it detects that it is. def resolve(*args, &block) unless @scope.root? raise RuntimeError, "The resolve method can't be used inside a routes scope block" diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 129e90037e..68bd6d806b 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -279,6 +279,8 @@ module ActionDispatch if args.size < path_params_size path_params -= controller_options.keys path_params -= result.keys + else + path_params = path_params.dup end inner_options.each_key do |key| path_params.delete(key) @@ -318,11 +320,7 @@ module ActionDispatch when Hash args.pop when ActionController::Parameters - if last.permitted? - args.pop.to_h - else - raise ArgumentError, ActionDispatch::Routing::INSECURE_URL_PARAMETERS_MESSAGE - end + args.pop.to_h end helper.call self, args, options end diff --git a/actionpack/lib/action_dispatch/routing/routes_proxy.rb b/actionpack/lib/action_dispatch/routing/routes_proxy.rb index ee847eaeed..c1423f770f 100644 --- a/actionpack/lib/action_dispatch/routing/routes_proxy.rb +++ b/actionpack/lib/action_dispatch/routing/routes_proxy.rb @@ -19,7 +19,8 @@ module ActionDispatch end end - def respond_to_missing?(method, include_private = false) + private + def respond_to_missing?(method, _) super || @helpers.respond_to?(method) end @@ -32,7 +33,7 @@ module ActionDispatch @helpers.#{method}(*args) end RUBY - send(method, *args) + public_send(method, *args) else super end diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index 008216cc80..a9bdefa775 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -171,17 +171,10 @@ module ActionDispatch case options when nil _routes.url_for(url_options.symbolize_keys) - when Hash + when Hash, ActionController::Parameters route_name = options.delete :use_route - _routes.url_for(options.symbolize_keys.reverse_merge!(url_options), - route_name) - when ActionController::Parameters - unless options.permitted? - raise ArgumentError.new(ActionDispatch::Routing::INSECURE_URL_PARAMETERS_MESSAGE) - end - route_name = options.delete :use_route - _routes.url_for(options.to_h.symbolize_keys. - reverse_merge!(url_options), route_name) + merged_url_options = options.to_h.symbolize_keys.reverse_merge!(url_options) + _routes.url_for(merged_url_options, route_name) when String options when Symbol diff --git a/actionpack/lib/action_dispatch/system_test_case.rb b/actionpack/lib/action_dispatch/system_test_case.rb index 98fdb36c91..c39a135ce0 100644 --- a/actionpack/lib/action_dispatch/system_test_case.rb +++ b/actionpack/lib/action_dispatch/system_test_case.rb @@ -66,14 +66,18 @@ module ActionDispatch # # To use a headless driver, like Poltergeist, update your Gemfile to use # Poltergeist instead of Selenium and then declare the driver name in the - # +application_system_test_case.rb+ file. In this case you would leave out the +:using+ - # option because the driver is headless. + # +application_system_test_case.rb+ file. In this case, you would leave out + # the +:using+ option because the driver is headless, but you can still use + # +:screen_size+ to change the size of the browser screen, also you can use + # +:options+ to pass options supported by the driver. Please refer to your + # driver documentation to learn about supported options. # # require "test_helper" # require "capybara/poltergeist" # # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase - # driven_by :poltergeist + # driven_by :poltergeist, screen_size: [1400, 1400], options: + # { js_errors: true } # end # # Because <tt>ActionDispatch::SystemTestCase</tt> is a shim between Capybara diff --git a/actionpack/lib/action_dispatch/system_testing/driver.rb b/actionpack/lib/action_dispatch/system_testing/driver.rb index 5cf17883f7..1a027f2e23 100644 --- a/actionpack/lib/action_dispatch/system_testing/driver.rb +++ b/actionpack/lib/action_dispatch/system_testing/driver.rb @@ -9,23 +9,42 @@ module ActionDispatch end def use - register if selenium? + register unless rack_test? + setup end private - def selenium? - @name == :selenium + def rack_test? + @name == :rack_test end def register Capybara.register_driver @name do |app| - Capybara::Selenium::Driver.new(app, { browser: @browser }.merge(@options)).tap do |driver| - driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size) + case @name + when :selenium then register_selenium(app) + when :poltergeist then register_poltergeist(app) + when :webkit then register_webkit(app) end end end + def register_selenium(app) + Capybara::Selenium::Driver.new(app, { browser: @browser }.merge(@options)).tap do |driver| + driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size) + end + end + + def register_poltergeist(app) + Capybara::Poltergeist::Driver.new(app, @options.merge(window_size: @screen_size)) + end + + def register_webkit(app) + Capybara::Webkit::Driver.new(app, Capybara::Webkit::Configuration.to_hash.merge(@options)).tap do |driver| + driver.resize_window(*@screen_size) + end + end + def setup Capybara.current_driver = @name end diff --git a/actionpack/lib/action_dispatch/system_testing/server.rb b/actionpack/lib/action_dispatch/system_testing/server.rb index 4a214ef713..89ca6944d9 100644 --- a/actionpack/lib/action_dispatch/system_testing/server.rb +++ b/actionpack/lib/action_dispatch/system_testing/server.rb @@ -3,6 +3,12 @@ require "rack/handler/puma" module ActionDispatch module SystemTesting class Server # :nodoc: + class << self + attr_accessor :silence_puma + end + + self.silence_puma = false + def run register setup @@ -11,7 +17,12 @@ module ActionDispatch private def register Capybara.register_server :rails_puma do |app, port, host| - Rack::Handler::Puma.run(app, Port: port, Threads: "0:1") + Rack::Handler::Puma.run( + app, + Port: port, + Threads: "0:1", + Silent: self.class.silence_puma + ) end end diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb index 187ba2cc5f..f03f0d4299 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 @@ -2,7 +2,12 @@ module ActionDispatch module SystemTesting module TestHelpers module SetupAndTeardown # :nodoc: - DEFAULT_HOST = "127.0.0.1" + DEFAULT_HOST = "http://127.0.0.1" + + def host!(host) + super + Capybara.app_host = host + end def before_setup host! DEFAULT_HOST diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 2e2db98ad6..f16647fac8 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -338,8 +338,7 @@ module ActionDispatch @integration_session = nil end - %w(get post patch put head delete cookies assigns - xml_http_request xhr get_via_redirect post_via_redirect).each do |method| + %w(get post patch put head delete cookies assigns follow_redirect!).each do |method| define_method(method) do |*args| # reset the html_document variable, except for cookies/assigns calls unless method == "cookies" || method == "assigns" @@ -385,14 +384,15 @@ module ActionDispatch integration_session.default_url_options = options end - def respond_to_missing?(method, include_private = false) - integration_session.respond_to?(method, include_private) || super + private + def respond_to_missing?(method, _) + integration_session.respond_to?(method) || super end # Delegate unhandled messages to the current session instance. - def method_missing(sym, *args, &block) - if integration_session.respond_to?(sym) - integration_session.__send__(sym, *args, &block).tap do + def method_missing(method, *args, &block) + if integration_session.respond_to?(method) + integration_session.public_send(method, *args, &block).tap do copy_session_variables! end else |